diff --git a/.changeset/wet-scissors-speak.md b/.changeset/wet-scissors-speak.md new file mode 100644 index 0000000..9a88d53 --- /dev/null +++ b/.changeset/wet-scissors-speak.md @@ -0,0 +1,5 @@ +--- +"signal-form": patch +--- + +Improve change event handling and add filter property diff --git a/lib/controls/check-box-input.tsx b/lib/controls/check-box-input.tsx index f511b36..a76d41d 100644 --- a/lib/controls/check-box-input.tsx +++ b/lib/controls/check-box-input.tsx @@ -9,10 +9,11 @@ export type CheckBoxInputProps = Omit< "type" > & { name: string; + onAfterChange?: ChangeEventHandler; }; export const CheckBoxInput = forwardRef( - ({ name, onChange, ...props }, forwardedRef) => { + ({ name, onChange, onAfterChange, ...props }, forwardedRef) => { let field = useField(name); let value = useFieldData(name, false); let ref = useForwardedRef(forwardedRef); @@ -26,10 +27,13 @@ export const CheckBoxInput = forwardRef( let onChangeHandler: ChangeEventHandler = useCallback( (e) => { onChange?.(e); - batch(() => { - field.setData(e.target.checked); - field.setTouched(); - }); + if (!e.isDefaultPrevented()) { + batch(() => { + field.setData(e.target.checked); + field.setTouched(); + }); + } + onAfterChange?.(e); }, [] ); diff --git a/lib/controls/input.tsx b/lib/controls/input.tsx index 083a433..0cd83ab 100644 --- a/lib/controls/input.tsx +++ b/lib/controls/input.tsx @@ -11,10 +11,15 @@ export type InputProps = Omit< > & { name: string; value?: string | ReadonlySignal; + onAfterChange?: ChangeEventHandler; + filter?: (value: string) => string; }; export const Input = forwardRef( - ({ name, value, onChange, ...props }, forwardedRef) => { + ( + { name, value, onChange, onAfterChange, filter, ...props }, + forwardedRef + ) => { let field = useField(name); let ref = useForwardedRef(forwardedRef); @@ -37,10 +42,13 @@ export const Input = forwardRef( let onChangeHandler: ChangeEventHandler = useCallback( (e) => { onChange?.(e); - batch(() => { - field.setData(e.target.value); - field.setTouched(); - }); + if (!e.isDefaultPrevented()) { + batch(() => { + field.setData(filter ? filter(e.target.value) : e.target.value); + field.setTouched(); + }); + } + onAfterChange?.(e); }, [] ); diff --git a/lib/controls/radio-input.tsx b/lib/controls/radio-input.tsx index 33211ac..8b3adc3 100644 --- a/lib/controls/radio-input.tsx +++ b/lib/controls/radio-input.tsx @@ -10,10 +10,11 @@ export type RadioInputProps = Omit< > & { name: string; value: string; + onAfterChange?: ChangeEventHandler; }; export const RadioInput = forwardRef( - ({ name, value, onChange, ...props }, forwardedRef) => { + ({ name, value, onChange, onAfterChange, ...props }, forwardedRef) => { let field = useField(name); let isChecked = useComputedValue(() => { return field.data.value?.toString() === value.toString(); @@ -30,10 +31,13 @@ export const RadioInput = forwardRef( let onChangeHandler: ChangeEventHandler = useCallback( (e) => { onChange?.(e); - batch(() => { - field.setData(value); - field.setTouched(); - }); + if (!e.isDefaultPrevented()) { + batch(() => { + field.setData(value); + field.setTouched(); + }); + } + onAfterChange?.(e); }, [] ); diff --git a/lib/controls/select.tsx b/lib/controls/select.tsx index 286998e..cb75f39 100644 --- a/lib/controls/select.tsx +++ b/lib/controls/select.tsx @@ -6,6 +6,7 @@ import { useForwardedRef } from "~/utils/use-forwarded-ref"; export type SelectProps = SelectHTMLAttributes & { name: string; + onAfterChange?: ChangeEventHandler; }; export const Select = forwardRef( @@ -19,7 +20,7 @@ export const Select = forwardRef( ); export const SingleSelect = forwardRef( - ({ name, multiple, onChange, ...props }, forwardedRef) => { + ({ name, multiple, onChange, onAfterChange, ...props }, forwardedRef) => { let field = useField(name); let value = useFieldData(name, ""); let ref = useForwardedRef(forwardedRef); @@ -33,10 +34,13 @@ export const SingleSelect = forwardRef( let onChangeHandler: ChangeEventHandler = useCallback( (e) => { onChange?.(e); - batch(() => { - field.setData(e.target.value); - field.setTouched(); - }); + if (!e.isDefaultPrevented()) { + batch(() => { + field.setData(e.target.value); + field.setTouched(); + }); + } + onAfterChange?.(e); }, [] ); @@ -55,7 +59,7 @@ export const SingleSelect = forwardRef( ); export const MultipleSelect = forwardRef( - ({ name, multiple, onChange, ...props }, forwardedRef) => { + ({ name, multiple, onChange, onAfterChange, ...props }, forwardedRef) => { let field = useField(name); let value = useFieldData(name, []); let ref = useForwardedRef(forwardedRef); @@ -69,10 +73,13 @@ export const MultipleSelect = forwardRef( let onChangeHandler: ChangeEventHandler = useCallback( (e) => { onChange?.(e); - batch(() => { - field.setData(Array.from(e.target.selectedOptions, (o) => o.value)); - field.setTouched(); - }); + if (!e.isDefaultPrevented()) { + batch(() => { + field.setData(Array.from(e.target.selectedOptions, (o) => o.value)); + field.setTouched(); + }); + } + onAfterChange?.(e); }, [] ); diff --git a/lib/controls/text-area.tsx b/lib/controls/text-area.tsx index 7cae5a1..13b14d8 100644 --- a/lib/controls/text-area.tsx +++ b/lib/controls/text-area.tsx @@ -11,10 +11,11 @@ export type TextAreaProps = Omit< > & { name: string; value?: string | ReadonlySignal; + onAfterChange?: ChangeEventHandler; }; export const TextArea = forwardRef( - ({ name, value, onChange, ...props }, forwardedRef) => { + ({ name, value, onChange, onAfterChange, ...props }, forwardedRef) => { let field = useField(name); let ref = useForwardedRef(forwardedRef); @@ -37,10 +38,13 @@ export const TextArea = forwardRef( let onChangeHandler: ChangeEventHandler = useCallback( (e) => { onChange?.(e); - batch(() => { - field.setData(e.target.value); - field.setTouched(); - }); + if (!e.isDefaultPrevented()) { + batch(() => { + field.setData(e.target.value); + field.setTouched(); + }); + } + onAfterChange?.(e); }, [] ); diff --git a/stories/Input.stories.tsx b/stories/Input.stories.tsx index 78f8534..43eb720 100644 --- a/stories/Input.stories.tsx +++ b/stories/Input.stories.tsx @@ -127,3 +127,18 @@ export const Reactive: Story = { ); }, }; + +export const Filtered: Story = { + render() { + return ( +
+

+ +

+
+ ); + }, +};