@fransek/form 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/lib/Field.js +18 -0
- package/dist/cjs/lib/Field.js.map +1 -1
- package/dist/cjs/lib/Form.d.ts +1 -1
- package/dist/cjs/lib/Form.js +6 -5
- package/dist/cjs/lib/Form.js.map +1 -1
- package/dist/cjs/lib/types.d.ts +6 -0
- package/dist/esm/lib/Field.js +18 -0
- package/dist/esm/lib/Field.js.map +1 -1
- package/dist/esm/lib/Form.d.ts +1 -1
- package/dist/esm/lib/Form.js +6 -5
- package/dist/esm/lib/Form.js.map +1 -1
- package/dist/esm/lib/types.d.ts +6 -0
- package/package.json +1 -1
package/dist/cjs/lib/Field.js
CHANGED
|
@@ -24,6 +24,14 @@ function Field(props) {
|
|
|
24
24
|
const id = React.useId();
|
|
25
25
|
React.useEffect(() => {
|
|
26
26
|
async function performValidation() {
|
|
27
|
+
if (!validation?.onSubmit &&
|
|
28
|
+
!validation?.onSubmitAsync &&
|
|
29
|
+
!stateRef.current.isValidating &&
|
|
30
|
+
stateRef.current.isTouched &&
|
|
31
|
+
stateRef.current.isDirty &&
|
|
32
|
+
document.activeElement !== fieldRef.current) {
|
|
33
|
+
return stateRef.current.isValid;
|
|
34
|
+
}
|
|
27
35
|
validationIdRef.current++;
|
|
28
36
|
pendingValidationRef.current = fieldState.validate(stateRef.current, validation?.onChange, validation?.onBlur, validation?.onSubmit);
|
|
29
37
|
if (pendingValidationRef.current.isValid) {
|
|
@@ -96,6 +104,16 @@ function Field(props) {
|
|
|
96
104
|
}
|
|
97
105
|
async function handleBlur() {
|
|
98
106
|
onBlur?.();
|
|
107
|
+
const shouldValidateOnBlur = validationMode === "touched" ||
|
|
108
|
+
validationMode === "touchedOrDirty" ||
|
|
109
|
+
stateRef.current.isDirty;
|
|
110
|
+
if (!shouldValidateOnBlur) {
|
|
111
|
+
onChange({
|
|
112
|
+
...stateRef.current,
|
|
113
|
+
isTouched: true,
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
99
117
|
const currentValidation = validationIdRef.current;
|
|
100
118
|
let errorMessage = stateRef.current.errorMessage ||
|
|
101
119
|
validation?.onBlur?.(stateRef.current.value);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Field.js","sources":["../../../src/lib/Field.tsx"],"sourcesContent":["import { useEffect, useId, useRef } from \"react\";\nimport { validate, validateAsync } from \"./fieldState\";\nimport { useFormContext } from \"./Form\";\nimport { FieldProps, FieldState } from \"./types\";\n\n/**\n * A headless form field component that manages validation state using a render prop pattern.\n *\n * Connects to a parent {@link Form} for submit validation coordination.\n * Passes current field state and event handlers to the `children` render function.\n */\nexport function Field<T>(props: FieldProps<T>) {\n const {\n registerField,\n unregisterField,\n validationMode: formValidationMode,\n debounceMs: formDebounceMs,\n } = useFormContext();\n\n const {\n children,\n state,\n onChange,\n onInput,\n onBlur,\n validation,\n debounceMs = formDebounceMs || 500,\n validationMode = formValidationMode || \"touchedAndDirty\",\n } = props;\n\n const stateRef = useRef(state);\n stateRef.current = state;\n const validationTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n );\n const validationIdRef = useRef(0);\n const isValidatingOnBlurRef = useRef(false);\n const isValidatingOnChangeRef = useRef(false);\n const pendingValidationRef = useRef<FieldState<T> | null>(null);\n const fieldRef = useRef<HTMLElement | null>(null);\n const id = useId();\n\n useEffect(() => {\n async function performValidation() {\n validationIdRef.current++;\n pendingValidationRef.current = validate(\n stateRef.current,\n validation?.onChange,\n validation?.onBlur,\n validation?.onSubmit,\n );\n if (pendingValidationRef.current.isValid) {\n pendingValidationRef.current = await validateAsync(\n stateRef.current,\n validation?.onChangeAsync,\n validation?.onBlurAsync,\n validation?.onSubmitAsync,\n );\n }\n return pendingValidationRef.current.isValid;\n }\n\n function commitPendingValidation() {\n if (pendingValidationRef.current) {\n onChange(pendingValidationRef.current);\n pendingValidationRef.current = null;\n }\n }\n\n registerField(\n id,\n fieldRef.current,\n performValidation,\n commitPendingValidation,\n );\n\n return () => {\n unregisterField(id);\n };\n }, [id, registerField, validation, onChange, unregisterField]);\n\n useEffect(() => {\n return () => {\n if (validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n };\n }, []);\n\n function handleChange(value: T) {\n onInput?.(value);\n\n if (validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n\n const currentValidation = ++validationIdRef.current;\n\n const shouldValidate =\n validationMode === \"dirty\" ||\n validationMode === \"touchedOrDirty\" ||\n stateRef.current.isTouched;\n\n if (shouldValidate) {\n const errorMessage = validation?.onChange?.(value);\n const willValidateAsync = Boolean(\n validation?.onChangeAsync && !errorMessage,\n );\n\n onChange({\n ...stateRef.current,\n value,\n errorMessage,\n isDirty: true,\n isValid: !errorMessage,\n isValidating: willValidateAsync,\n });\n\n if (willValidateAsync) {\n isValidatingOnChangeRef.current = true;\n validationTimeoutRef.current = setTimeout(async () => {\n const asyncErrorMessage = await validation?.onChangeAsync?.(value);\n\n isValidatingOnChangeRef.current = false;\n if (currentValidation === validationIdRef.current) {\n onChange({\n ...stateRef.current,\n errorMessage: asyncErrorMessage,\n isValid: !asyncErrorMessage,\n isValidating: isValidatingOnBlurRef.current,\n });\n }\n }, debounceMs);\n }\n } else {\n onChange({\n ...stateRef.current,\n value,\n isDirty: true,\n isValidating: false,\n });\n }\n }\n\n async function handleBlur() {\n onBlur?.();\n\n const currentValidation = validationIdRef.current;\n\n let errorMessage =\n stateRef.current.errorMessage ||\n validation?.onBlur?.(stateRef.current.value);\n\n const shouldValidateOnChange =\n !stateRef.current.isTouched &&\n (validationMode === \"touched\" ||\n (validationMode === \"touchedOrDirty\" && !stateRef.current.isDirty) ||\n (validationMode === \"touchedAndDirty\" && stateRef.current.isDirty));\n\n if (!errorMessage && shouldValidateOnChange && validation?.onChange) {\n errorMessage = validation.onChange(stateRef.current.value);\n }\n\n if (\n !errorMessage &&\n (validation?.onBlurAsync || validation?.onChangeAsync)\n ) {\n isValidatingOnBlurRef.current = true;\n onChange({\n ...stateRef.current,\n isValidating: true,\n isTouched: true,\n });\n\n const asyncValidations: Promise<React.ReactNode>[] = [];\n\n if (validation?.onBlurAsync) {\n asyncValidations.push(validation.onBlurAsync(stateRef.current.value));\n }\n\n if (shouldValidateOnChange && validation?.onChangeAsync) {\n asyncValidations.push(validation.onChangeAsync(stateRef.current.value));\n }\n\n const [blurError, changeError] = await Promise.all(asyncValidations);\n errorMessage = blurError || changeError;\n }\n\n isValidatingOnBlurRef.current = false;\n\n if (errorMessage && validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n\n if (currentValidation !== validationIdRef.current) {\n onChange({\n ...stateRef.current,\n isTouched: true,\n });\n return;\n }\n\n onChange({\n ...stateRef.current,\n errorMessage,\n isTouched: true,\n isValid: !errorMessage,\n isValidating: isValidatingOnChangeRef.current,\n });\n }\n\n const ref = (el: HTMLElement | null) => {\n fieldRef.current = el;\n };\n\n return children({\n ...stateRef.current,\n handleChange,\n handleBlur,\n ref,\n });\n}\n"],"names":["useFormContext","useRef","useId","useEffect","validate","validateAsync"],"mappings":";;;;;;AAKA;;;;;AAKG;AACG,SAAU,KAAK,CAAI,KAAoB,EAAA;AAC3C,IAAA,MAAM,EACJ,aAAa,EACb,eAAe,EACf,cAAc,EAAE,kBAAkB,EAClC,UAAU,EAAE,cAAc,GAC3B,GAAGA,mBAAc,EAAE;IAEpB,MAAM,EACJ,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,OAAO,EACP,MAAM,EACN,UAAU,EACV,UAAU,GAAG,cAAc,IAAI,GAAG,EAClC,cAAc,GAAG,kBAAkB,IAAI,iBAAiB,GACzD,GAAG,KAAK;AAET,IAAA,MAAM,QAAQ,GAAGC,YAAM,CAAC,KAAK,CAAC;AAC9B,IAAA,QAAQ,CAAC,OAAO,GAAG,KAAK;AACxB,IAAA,MAAM,oBAAoB,GAAGA,YAAM,CACjC,IAAI,CACL;AACD,IAAA,MAAM,eAAe,GAAGA,YAAM,CAAC,CAAC,CAAC;AACjC,IAAA,MAAM,qBAAqB,GAAGA,YAAM,CAAC,KAAK,CAAC;AAC3C,IAAA,MAAM,uBAAuB,GAAGA,YAAM,CAAC,KAAK,CAAC;AAC7C,IAAA,MAAM,oBAAoB,GAAGA,YAAM,CAAuB,IAAI,CAAC;AAC/D,IAAA,MAAM,QAAQ,GAAGA,YAAM,CAAqB,IAAI,CAAC;AACjD,IAAA,MAAM,EAAE,GAAGC,WAAK,EAAE;IAElBC,eAAS,CAAC,MAAK;AACb,QAAA,eAAe,iBAAiB,GAAA;YAC9B,eAAe,CAAC,OAAO,EAAE;YACzB,oBAAoB,CAAC,OAAO,GAAGC,mBAAQ,CACrC,QAAQ,CAAC,OAAO,EAChB,UAAU,EAAE,QAAQ,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,QAAQ,CACrB;AACD,YAAA,IAAI,oBAAoB,CAAC,OAAO,CAAC,OAAO,EAAE;gBACxC,oBAAoB,CAAC,OAAO,GAAG,MAAMC,wBAAa,CAChD,QAAQ,CAAC,OAAO,EAChB,UAAU,EAAE,aAAa,EACzB,UAAU,EAAE,WAAW,EACvB,UAAU,EAAE,aAAa,CAC1B;YACH;AACA,YAAA,OAAO,oBAAoB,CAAC,OAAO,CAAC,OAAO;QAC7C;AAEA,QAAA,SAAS,uBAAuB,GAAA;AAC9B,YAAA,IAAI,oBAAoB,CAAC,OAAO,EAAE;AAChC,gBAAA,QAAQ,CAAC,oBAAoB,CAAC,OAAO,CAAC;AACtC,gBAAA,oBAAoB,CAAC,OAAO,GAAG,IAAI;YACrC;QACF;QAEA,aAAa,CACX,EAAE,EACF,QAAQ,CAAC,OAAO,EAChB,iBAAiB,EACjB,uBAAuB,CACxB;AAED,QAAA,OAAO,MAAK;YACV,eAAe,CAAC,EAAE,CAAC;AACrB,QAAA,CAAC;AACH,IAAA,CAAC,EAAE,CAAC,EAAE,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;IAE9DF,eAAS,CAAC,MAAK;AACb,QAAA,OAAO,MAAK;AACV,YAAA,IAAI,oBAAoB,CAAC,OAAO,EAAE;AAChC,gBAAA,YAAY,CAAC,oBAAoB,CAAC,OAAO,CAAC;YAC5C;AACF,QAAA,CAAC;IACH,CAAC,EAAE,EAAE,CAAC;IAEN,SAAS,YAAY,CAAC,KAAQ,EAAA;AAC5B,QAAA,OAAO,GAAG,KAAK,CAAC;AAEhB,QAAA,IAAI,oBAAoB,CAAC,OAAO,EAAE;AAChC,YAAA,YAAY,CAAC,oBAAoB,CAAC,OAAO,CAAC;QAC5C;AAEA,QAAA,MAAM,iBAAiB,GAAG,EAAE,eAAe,CAAC,OAAO;AAEnD,QAAA,MAAM,cAAc,GAClB,cAAc,KAAK,OAAO;AAC1B,YAAA,cAAc,KAAK,gBAAgB;AACnC,YAAA,QAAQ,CAAC,OAAO,CAAC,SAAS;QAE5B,IAAI,cAAc,EAAE;YAClB,MAAM,YAAY,GAAG,UAAU,EAAE,QAAQ,GAAG,KAAK,CAAC;YAClD,MAAM,iBAAiB,GAAG,OAAO,CAC/B,UAAU,EAAE,aAAa,IAAI,CAAC,YAAY,CAC3C;AAED,YAAA,QAAQ,CAAC;gBACP,GAAG,QAAQ,CAAC,OAAO;gBACnB,KAAK;gBACL,YAAY;AACZ,gBAAA,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,YAAY;AACtB,gBAAA,YAAY,EAAE,iBAAiB;AAChC,aAAA,CAAC;YAEF,IAAI,iBAAiB,EAAE;AACrB,gBAAA,uBAAuB,CAAC,OAAO,GAAG,IAAI;AACtC,gBAAA,oBAAoB,CAAC,OAAO,GAAG,UAAU,CAAC,YAAW;oBACnD,MAAM,iBAAiB,GAAG,MAAM,UAAU,EAAE,aAAa,GAAG,KAAK,CAAC;AAElE,oBAAA,uBAAuB,CAAC,OAAO,GAAG,KAAK;AACvC,oBAAA,IAAI,iBAAiB,KAAK,eAAe,CAAC,OAAO,EAAE;AACjD,wBAAA,QAAQ,CAAC;4BACP,GAAG,QAAQ,CAAC,OAAO;AACnB,4BAAA,YAAY,EAAE,iBAAiB;4BAC/B,OAAO,EAAE,CAAC,iBAAiB;4BAC3B,YAAY,EAAE,qBAAqB,CAAC,OAAO;AAC5C,yBAAA,CAAC;oBACJ;gBACF,CAAC,EAAE,UAAU,CAAC;YAChB;QACF;aAAO;AACL,YAAA,QAAQ,CAAC;gBACP,GAAG,QAAQ,CAAC,OAAO;gBACnB,KAAK;AACL,gBAAA,OAAO,EAAE,IAAI;AACb,gBAAA,YAAY,EAAE,KAAK;AACpB,aAAA,CAAC;QACJ;IACF;AAEA,IAAA,eAAe,UAAU,GAAA;QACvB,MAAM,IAAI;AAEV,QAAA,MAAM,iBAAiB,GAAG,eAAe,CAAC,OAAO;AAEjD,QAAA,IAAI,YAAY,GACd,QAAQ,CAAC,OAAO,CAAC,YAAY;YAC7B,UAAU,EAAE,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC;AAE9C,QAAA,MAAM,sBAAsB,GAC1B,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS;aAC1B,cAAc,KAAK,SAAS;iBAC1B,cAAc,KAAK,gBAAgB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC;iBACjE,cAAc,KAAK,iBAAiB,IAAI,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAEvE,IAAI,CAAC,YAAY,IAAI,sBAAsB,IAAI,UAAU,EAAE,QAAQ,EAAE;YACnE,YAAY,GAAG,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC;QAC5D;AAEA,QAAA,IACE,CAAC,YAAY;aACZ,UAAU,EAAE,WAAW,IAAI,UAAU,EAAE,aAAa,CAAC,EACtD;AACA,YAAA,qBAAqB,CAAC,OAAO,GAAG,IAAI;AACpC,YAAA,QAAQ,CAAC;gBACP,GAAG,QAAQ,CAAC,OAAO;AACnB,gBAAA,YAAY,EAAE,IAAI;AAClB,gBAAA,SAAS,EAAE,IAAI;AAChB,aAAA,CAAC;YAEF,MAAM,gBAAgB,GAA+B,EAAE;AAEvD,YAAA,IAAI,UAAU,EAAE,WAAW,EAAE;AAC3B,gBAAA,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACvE;AAEA,YAAA,IAAI,sBAAsB,IAAI,UAAU,EAAE,aAAa,EAAE;AACvD,gBAAA,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACzE;AAEA,YAAA,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;AACpE,YAAA,YAAY,GAAG,SAAS,IAAI,WAAW;QACzC;AAEA,QAAA,qBAAqB,CAAC,OAAO,GAAG,KAAK;AAErC,QAAA,IAAI,YAAY,IAAI,oBAAoB,CAAC,OAAO,EAAE;AAChD,YAAA,YAAY,CAAC,oBAAoB,CAAC,OAAO,CAAC;QAC5C;AAEA,QAAA,IAAI,iBAAiB,KAAK,eAAe,CAAC,OAAO,EAAE;AACjD,YAAA,QAAQ,CAAC;gBACP,GAAG,QAAQ,CAAC,OAAO;AACnB,gBAAA,SAAS,EAAE,IAAI;AAChB,aAAA,CAAC;YACF;QACF;AAEA,QAAA,QAAQ,CAAC;YACP,GAAG,QAAQ,CAAC,OAAO;YACnB,YAAY;AACZ,YAAA,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,CAAC,YAAY;YACtB,YAAY,EAAE,uBAAuB,CAAC,OAAO;AAC9C,SAAA,CAAC;IACJ;AAEA,IAAA,MAAM,GAAG,GAAG,CAAC,EAAsB,KAAI;AACrC,QAAA,QAAQ,CAAC,OAAO,GAAG,EAAE;AACvB,IAAA,CAAC;AAED,IAAA,OAAO,QAAQ,CAAC;QACd,GAAG,QAAQ,CAAC,OAAO;QACnB,YAAY;QACZ,UAAU;QACV,GAAG;AACJ,KAAA,CAAC;AACJ;;;;"}
|
|
1
|
+
{"version":3,"file":"Field.js","sources":["../../../src/lib/Field.tsx"],"sourcesContent":["import { useEffect, useId, useRef } from \"react\";\nimport { validate, validateAsync } from \"./fieldState\";\nimport { useFormContext } from \"./Form\";\nimport { FieldProps, FieldState } from \"./types\";\n\n/**\n * A headless form field component that manages validation state using a render prop pattern.\n *\n * Connects to a parent {@link Form} for submit validation coordination.\n * Passes current field state and event handlers to the `children` render function.\n */\nexport function Field<T>(props: FieldProps<T>) {\n const {\n registerField,\n unregisterField,\n validationMode: formValidationMode,\n debounceMs: formDebounceMs,\n } = useFormContext();\n\n const {\n children,\n state,\n onChange,\n onInput,\n onBlur,\n validation,\n debounceMs = formDebounceMs || 500,\n validationMode = formValidationMode || \"touchedAndDirty\",\n } = props;\n\n const stateRef = useRef(state);\n stateRef.current = state;\n const validationTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n );\n const validationIdRef = useRef(0);\n const isValidatingOnBlurRef = useRef(false);\n const isValidatingOnChangeRef = useRef(false);\n const pendingValidationRef = useRef<FieldState<T> | null>(null);\n const fieldRef = useRef<HTMLElement | null>(null);\n const id = useId();\n\n useEffect(() => {\n async function performValidation() {\n if (\n !validation?.onSubmit &&\n !validation?.onSubmitAsync &&\n !stateRef.current.isValidating &&\n stateRef.current.isTouched &&\n stateRef.current.isDirty &&\n document.activeElement !== fieldRef.current\n ) {\n return stateRef.current.isValid;\n }\n validationIdRef.current++;\n pendingValidationRef.current = validate(\n stateRef.current,\n validation?.onChange,\n validation?.onBlur,\n validation?.onSubmit,\n );\n if (pendingValidationRef.current.isValid) {\n pendingValidationRef.current = await validateAsync(\n stateRef.current,\n validation?.onChangeAsync,\n validation?.onBlurAsync,\n validation?.onSubmitAsync,\n );\n }\n return pendingValidationRef.current.isValid;\n }\n\n function commitPendingValidation() {\n if (pendingValidationRef.current) {\n onChange(pendingValidationRef.current);\n pendingValidationRef.current = null;\n }\n }\n\n registerField(\n id,\n fieldRef.current,\n performValidation,\n commitPendingValidation,\n );\n\n return () => {\n unregisterField(id);\n };\n }, [id, registerField, validation, onChange, unregisterField]);\n\n useEffect(() => {\n return () => {\n if (validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n };\n }, []);\n\n function handleChange(value: T) {\n onInput?.(value);\n\n if (validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n\n const currentValidation = ++validationIdRef.current;\n\n const shouldValidate =\n validationMode === \"dirty\" ||\n validationMode === \"touchedOrDirty\" ||\n stateRef.current.isTouched;\n\n if (shouldValidate) {\n const errorMessage = validation?.onChange?.(value);\n const willValidateAsync = Boolean(\n validation?.onChangeAsync && !errorMessage,\n );\n\n onChange({\n ...stateRef.current,\n value,\n errorMessage,\n isDirty: true,\n isValid: !errorMessage,\n isValidating: willValidateAsync,\n });\n\n if (willValidateAsync) {\n isValidatingOnChangeRef.current = true;\n validationTimeoutRef.current = setTimeout(async () => {\n const asyncErrorMessage = await validation?.onChangeAsync?.(value);\n\n isValidatingOnChangeRef.current = false;\n if (currentValidation === validationIdRef.current) {\n onChange({\n ...stateRef.current,\n errorMessage: asyncErrorMessage,\n isValid: !asyncErrorMessage,\n isValidating: isValidatingOnBlurRef.current,\n });\n }\n }, debounceMs);\n }\n } else {\n onChange({\n ...stateRef.current,\n value,\n isDirty: true,\n isValidating: false,\n });\n }\n }\n\n async function handleBlur() {\n onBlur?.();\n\n const shouldValidateOnBlur =\n validationMode === \"touched\" ||\n validationMode === \"touchedOrDirty\" ||\n stateRef.current.isDirty;\n\n if (!shouldValidateOnBlur) {\n onChange({\n ...stateRef.current,\n isTouched: true,\n });\n return;\n }\n\n const currentValidation = validationIdRef.current;\n\n let errorMessage =\n stateRef.current.errorMessage ||\n validation?.onBlur?.(stateRef.current.value);\n\n const shouldValidateOnChange =\n !stateRef.current.isTouched &&\n (validationMode === \"touched\" ||\n (validationMode === \"touchedOrDirty\" && !stateRef.current.isDirty) ||\n (validationMode === \"touchedAndDirty\" && stateRef.current.isDirty));\n\n if (!errorMessage && shouldValidateOnChange && validation?.onChange) {\n errorMessage = validation.onChange(stateRef.current.value);\n }\n\n if (\n !errorMessage &&\n (validation?.onBlurAsync || validation?.onChangeAsync)\n ) {\n isValidatingOnBlurRef.current = true;\n onChange({\n ...stateRef.current,\n isValidating: true,\n isTouched: true,\n });\n\n const asyncValidations: Promise<React.ReactNode>[] = [];\n\n if (validation?.onBlurAsync) {\n asyncValidations.push(validation.onBlurAsync(stateRef.current.value));\n }\n\n if (shouldValidateOnChange && validation?.onChangeAsync) {\n asyncValidations.push(validation.onChangeAsync(stateRef.current.value));\n }\n\n const [blurError, changeError] = await Promise.all(asyncValidations);\n errorMessage = blurError || changeError;\n }\n\n isValidatingOnBlurRef.current = false;\n\n if (errorMessage && validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n\n if (currentValidation !== validationIdRef.current) {\n onChange({\n ...stateRef.current,\n isTouched: true,\n });\n return;\n }\n\n onChange({\n ...stateRef.current,\n errorMessage,\n isTouched: true,\n isValid: !errorMessage,\n isValidating: isValidatingOnChangeRef.current,\n });\n }\n\n const ref = (el: HTMLElement | null) => {\n fieldRef.current = el;\n };\n\n return children({\n ...stateRef.current,\n handleChange,\n handleBlur,\n ref,\n });\n}\n"],"names":["useFormContext","useRef","useId","useEffect","validate","validateAsync"],"mappings":"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"}
|
package/dist/cjs/lib/Form.d.ts
CHANGED
package/dist/cjs/lib/Form.js
CHANGED
|
@@ -22,7 +22,8 @@ function Form({ onSubmit, validationMode, debounceMs, ...props }) {
|
|
|
22
22
|
const unregisterField = React.useCallback((id) => {
|
|
23
23
|
fieldsRef.current.delete(id);
|
|
24
24
|
}, []);
|
|
25
|
-
const validateForm = React.useCallback(async () => {
|
|
25
|
+
const validateForm = React.useCallback(async (options = {}) => {
|
|
26
|
+
const { focusFirstError: shouldFocusFirstError = true, scrollOffset = 100, } = options;
|
|
26
27
|
const fields = Array.from(fieldsRef.current.values());
|
|
27
28
|
const validationPromises = fields.map(async (field) => ({
|
|
28
29
|
isValid: await field.validate(),
|
|
@@ -31,8 +32,8 @@ function Form({ onSubmit, validationMode, debounceMs, ...props }) {
|
|
|
31
32
|
const results = await Promise.all(validationPromises);
|
|
32
33
|
fields.forEach((field) => field.commitPendingValidation());
|
|
33
34
|
const hasErrors = results.some((result) => !result.isValid);
|
|
34
|
-
if (hasErrors) {
|
|
35
|
-
focusFirstError(results);
|
|
35
|
+
if (hasErrors && shouldFocusFirstError) {
|
|
36
|
+
focusFirstError(results, scrollOffset);
|
|
36
37
|
}
|
|
37
38
|
return !hasErrors;
|
|
38
39
|
}, []);
|
|
@@ -51,7 +52,7 @@ const FormContext = React.createContext({
|
|
|
51
52
|
function useFormContext() {
|
|
52
53
|
return React.useContext(FormContext);
|
|
53
54
|
}
|
|
54
|
-
function focusFirstError(results) {
|
|
55
|
+
function focusFirstError(results, scrollOffset) {
|
|
55
56
|
const firstInvalidField = results
|
|
56
57
|
.filter((field) => !field.isValid && field.ref)
|
|
57
58
|
.map((field) => field.ref)
|
|
@@ -63,7 +64,7 @@ function focusFirstError(results) {
|
|
|
63
64
|
firstInvalidField.focus();
|
|
64
65
|
const rect = firstInvalidField.getBoundingClientRect();
|
|
65
66
|
window.scrollTo({
|
|
66
|
-
top: rect.top + window.scrollY -
|
|
67
|
+
top: rect.top + window.scrollY - scrollOffset,
|
|
67
68
|
});
|
|
68
69
|
}
|
|
69
70
|
|
package/dist/cjs/lib/Form.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Form.js","sources":["../../../src/lib/Form.tsx"],"sourcesContent":["import React, { useCallback, useRef } from \"react\";\nimport {
|
|
1
|
+
{"version":3,"file":"Form.js","sources":["../../../src/lib/Form.tsx"],"sourcesContent":["import React, { useCallback, useRef } from \"react\";\nimport {\n FieldMap,\n FormContextValue,\n ValidateFormOptions,\n ValidationMode,\n} from \"./types\";\n\n/** Props for the {@link Form} component. */\ninterface FormProps extends Omit<React.ComponentProps<\"form\">, \"onSubmit\"> {\n /** Default validation mode applied to all fields in the form. Defaults to `\"touchedAndDirty\"`. */\n validationMode?: ValidationMode;\n /** Default debounce delay in milliseconds for async validators. Defaults to `500`. */\n debounceMs?: number;\n /**\n * Submit handler called when the form is submitted.\n * Receives the submit event and a `validateAllFields` function that triggers\n * validation on all registered fields and returns whether the form is valid.\n */\n onSubmit?: (\n e: React.SubmitEvent<HTMLFormElement>,\n validateForm: () => Promise<boolean>,\n ) => void;\n}\n\n/**\n * A form component that provides context for coordinating field validation.\n *\n * Wraps a native `<form>` element and prevents the default submit behavior.\n * On submit, the `onSubmit` callback is called with the submit event and a\n * `validateAllFields` function that can be used to trigger validation on all\n * registered {@link Field} components and focus the first invalid field.\n */\nexport function Form({\n onSubmit,\n validationMode,\n debounceMs,\n ...props\n}: FormProps) {\n const fieldsRef = useRef<FieldMap>(new Map());\n\n const registerField = useCallback(\n (\n id: string,\n ref: HTMLElement | null,\n validate: () => Promise<boolean>,\n commitPendingValidation: () => void,\n ) => {\n fieldsRef.current.set(id, {\n ref,\n validate,\n commitPendingValidation,\n });\n },\n [],\n );\n\n const unregisterField = useCallback((id: string) => {\n fieldsRef.current.delete(id);\n }, []);\n\n const validateForm = useCallback(\n async (options: ValidateFormOptions = {}) => {\n const {\n focusFirstError: shouldFocusFirstError = true,\n scrollOffset = 100,\n } = options;\n\n const fields = Array.from(fieldsRef.current.values());\n const validationPromises = fields.map(async (field) => ({\n isValid: await field.validate(),\n ref: field.ref,\n }));\n const results = await Promise.all(validationPromises);\n fields.forEach((field) => field.commitPendingValidation());\n const hasErrors = results.some((result) => !result.isValid);\n\n if (hasErrors && shouldFocusFirstError) {\n focusFirstError(results, scrollOffset);\n }\n return !hasErrors;\n },\n [],\n );\n\n return (\n <FormContext.Provider\n value={{\n registerField,\n unregisterField,\n validationMode,\n debounceMs,\n }}\n >\n <form onSubmit={(e) => onSubmit?.(e, validateForm)} {...props} />\n </FormContext.Provider>\n );\n}\n\nexport const FormContext = React.createContext<FormContextValue>({\n registerField: () => {},\n unregisterField: () => {},\n});\n\nexport function useFormContext() {\n return React.useContext(FormContext);\n}\n\nexport function focusFirstError(\n results: {\n isValid: boolean;\n ref: HTMLElement | null;\n }[],\n scrollOffset: number,\n) {\n const firstInvalidField = results\n .filter((field) => !field.isValid && field.ref)\n .map((field) => field.ref!)\n .sort((a, b) =>\n a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING ? 1 : -1,\n )\n .at(0);\n\n if (!firstInvalidField) {\n return;\n }\n\n firstInvalidField.focus();\n const rect = firstInvalidField.getBoundingClientRect();\n window.scrollTo({\n top: rect.top + window.scrollY - scrollOffset,\n });\n}\n"],"names":["useRef","useCallback"],"mappings":"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"}
|
package/dist/cjs/lib/types.d.ts
CHANGED
|
@@ -86,3 +86,9 @@ export type FieldMap = Map<string, {
|
|
|
86
86
|
validate: () => Promise<boolean>;
|
|
87
87
|
commitPendingValidation: () => void;
|
|
88
88
|
}>;
|
|
89
|
+
export interface ValidateFormOptions {
|
|
90
|
+
/** If `true`, the first invalid field will be focused after validation. Defaults to `true`. */
|
|
91
|
+
focusFirstError?: boolean;
|
|
92
|
+
/** Additional offset in pixels to apply when scrolling to the first error. Defaults to `100`. */
|
|
93
|
+
scrollOffset?: number;
|
|
94
|
+
}
|
package/dist/esm/lib/Field.js
CHANGED
|
@@ -22,6 +22,14 @@ function Field(props) {
|
|
|
22
22
|
const id = useId();
|
|
23
23
|
useEffect(() => {
|
|
24
24
|
async function performValidation() {
|
|
25
|
+
if (!validation?.onSubmit &&
|
|
26
|
+
!validation?.onSubmitAsync &&
|
|
27
|
+
!stateRef.current.isValidating &&
|
|
28
|
+
stateRef.current.isTouched &&
|
|
29
|
+
stateRef.current.isDirty &&
|
|
30
|
+
document.activeElement !== fieldRef.current) {
|
|
31
|
+
return stateRef.current.isValid;
|
|
32
|
+
}
|
|
25
33
|
validationIdRef.current++;
|
|
26
34
|
pendingValidationRef.current = validate(stateRef.current, validation?.onChange, validation?.onBlur, validation?.onSubmit);
|
|
27
35
|
if (pendingValidationRef.current.isValid) {
|
|
@@ -94,6 +102,16 @@ function Field(props) {
|
|
|
94
102
|
}
|
|
95
103
|
async function handleBlur() {
|
|
96
104
|
onBlur?.();
|
|
105
|
+
const shouldValidateOnBlur = validationMode === "touched" ||
|
|
106
|
+
validationMode === "touchedOrDirty" ||
|
|
107
|
+
stateRef.current.isDirty;
|
|
108
|
+
if (!shouldValidateOnBlur) {
|
|
109
|
+
onChange({
|
|
110
|
+
...stateRef.current,
|
|
111
|
+
isTouched: true,
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
97
115
|
const currentValidation = validationIdRef.current;
|
|
98
116
|
let errorMessage = stateRef.current.errorMessage ||
|
|
99
117
|
validation?.onBlur?.(stateRef.current.value);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Field.js","sources":["../../../src/lib/Field.tsx"],"sourcesContent":["import { useEffect, useId, useRef } from \"react\";\nimport { validate, validateAsync } from \"./fieldState\";\nimport { useFormContext } from \"./Form\";\nimport { FieldProps, FieldState } from \"./types\";\n\n/**\n * A headless form field component that manages validation state using a render prop pattern.\n *\n * Connects to a parent {@link Form} for submit validation coordination.\n * Passes current field state and event handlers to the `children` render function.\n */\nexport function Field<T>(props: FieldProps<T>) {\n const {\n registerField,\n unregisterField,\n validationMode: formValidationMode,\n debounceMs: formDebounceMs,\n } = useFormContext();\n\n const {\n children,\n state,\n onChange,\n onInput,\n onBlur,\n validation,\n debounceMs = formDebounceMs || 500,\n validationMode = formValidationMode || \"touchedAndDirty\",\n } = props;\n\n const stateRef = useRef(state);\n stateRef.current = state;\n const validationTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n );\n const validationIdRef = useRef(0);\n const isValidatingOnBlurRef = useRef(false);\n const isValidatingOnChangeRef = useRef(false);\n const pendingValidationRef = useRef<FieldState<T> | null>(null);\n const fieldRef = useRef<HTMLElement | null>(null);\n const id = useId();\n\n useEffect(() => {\n async function performValidation() {\n validationIdRef.current++;\n pendingValidationRef.current = validate(\n stateRef.current,\n validation?.onChange,\n validation?.onBlur,\n validation?.onSubmit,\n );\n if (pendingValidationRef.current.isValid) {\n pendingValidationRef.current = await validateAsync(\n stateRef.current,\n validation?.onChangeAsync,\n validation?.onBlurAsync,\n validation?.onSubmitAsync,\n );\n }\n return pendingValidationRef.current.isValid;\n }\n\n function commitPendingValidation() {\n if (pendingValidationRef.current) {\n onChange(pendingValidationRef.current);\n pendingValidationRef.current = null;\n }\n }\n\n registerField(\n id,\n fieldRef.current,\n performValidation,\n commitPendingValidation,\n );\n\n return () => {\n unregisterField(id);\n };\n }, [id, registerField, validation, onChange, unregisterField]);\n\n useEffect(() => {\n return () => {\n if (validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n };\n }, []);\n\n function handleChange(value: T) {\n onInput?.(value);\n\n if (validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n\n const currentValidation = ++validationIdRef.current;\n\n const shouldValidate =\n validationMode === \"dirty\" ||\n validationMode === \"touchedOrDirty\" ||\n stateRef.current.isTouched;\n\n if (shouldValidate) {\n const errorMessage = validation?.onChange?.(value);\n const willValidateAsync = Boolean(\n validation?.onChangeAsync && !errorMessage,\n );\n\n onChange({\n ...stateRef.current,\n value,\n errorMessage,\n isDirty: true,\n isValid: !errorMessage,\n isValidating: willValidateAsync,\n });\n\n if (willValidateAsync) {\n isValidatingOnChangeRef.current = true;\n validationTimeoutRef.current = setTimeout(async () => {\n const asyncErrorMessage = await validation?.onChangeAsync?.(value);\n\n isValidatingOnChangeRef.current = false;\n if (currentValidation === validationIdRef.current) {\n onChange({\n ...stateRef.current,\n errorMessage: asyncErrorMessage,\n isValid: !asyncErrorMessage,\n isValidating: isValidatingOnBlurRef.current,\n });\n }\n }, debounceMs);\n }\n } else {\n onChange({\n ...stateRef.current,\n value,\n isDirty: true,\n isValidating: false,\n });\n }\n }\n\n async function handleBlur() {\n onBlur?.();\n\n const currentValidation = validationIdRef.current;\n\n let errorMessage =\n stateRef.current.errorMessage ||\n validation?.onBlur?.(stateRef.current.value);\n\n const shouldValidateOnChange =\n !stateRef.current.isTouched &&\n (validationMode === \"touched\" ||\n (validationMode === \"touchedOrDirty\" && !stateRef.current.isDirty) ||\n (validationMode === \"touchedAndDirty\" && stateRef.current.isDirty));\n\n if (!errorMessage && shouldValidateOnChange && validation?.onChange) {\n errorMessage = validation.onChange(stateRef.current.value);\n }\n\n if (\n !errorMessage &&\n (validation?.onBlurAsync || validation?.onChangeAsync)\n ) {\n isValidatingOnBlurRef.current = true;\n onChange({\n ...stateRef.current,\n isValidating: true,\n isTouched: true,\n });\n\n const asyncValidations: Promise<React.ReactNode>[] = [];\n\n if (validation?.onBlurAsync) {\n asyncValidations.push(validation.onBlurAsync(stateRef.current.value));\n }\n\n if (shouldValidateOnChange && validation?.onChangeAsync) {\n asyncValidations.push(validation.onChangeAsync(stateRef.current.value));\n }\n\n const [blurError, changeError] = await Promise.all(asyncValidations);\n errorMessage = blurError || changeError;\n }\n\n isValidatingOnBlurRef.current = false;\n\n if (errorMessage && validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n\n if (currentValidation !== validationIdRef.current) {\n onChange({\n ...stateRef.current,\n isTouched: true,\n });\n return;\n }\n\n onChange({\n ...stateRef.current,\n errorMessage,\n isTouched: true,\n isValid: !errorMessage,\n isValidating: isValidatingOnChangeRef.current,\n });\n }\n\n const ref = (el: HTMLElement | null) => {\n fieldRef.current = el;\n };\n\n return children({\n ...stateRef.current,\n handleChange,\n handleBlur,\n ref,\n });\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Field.js","sources":["../../../src/lib/Field.tsx"],"sourcesContent":["import { useEffect, useId, useRef } from \"react\";\nimport { validate, validateAsync } from \"./fieldState\";\nimport { useFormContext } from \"./Form\";\nimport { FieldProps, FieldState } from \"./types\";\n\n/**\n * A headless form field component that manages validation state using a render prop pattern.\n *\n * Connects to a parent {@link Form} for submit validation coordination.\n * Passes current field state and event handlers to the `children` render function.\n */\nexport function Field<T>(props: FieldProps<T>) {\n const {\n registerField,\n unregisterField,\n validationMode: formValidationMode,\n debounceMs: formDebounceMs,\n } = useFormContext();\n\n const {\n children,\n state,\n onChange,\n onInput,\n onBlur,\n validation,\n debounceMs = formDebounceMs || 500,\n validationMode = formValidationMode || \"touchedAndDirty\",\n } = props;\n\n const stateRef = useRef(state);\n stateRef.current = state;\n const validationTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n null,\n );\n const validationIdRef = useRef(0);\n const isValidatingOnBlurRef = useRef(false);\n const isValidatingOnChangeRef = useRef(false);\n const pendingValidationRef = useRef<FieldState<T> | null>(null);\n const fieldRef = useRef<HTMLElement | null>(null);\n const id = useId();\n\n useEffect(() => {\n async function performValidation() {\n if (\n !validation?.onSubmit &&\n !validation?.onSubmitAsync &&\n !stateRef.current.isValidating &&\n stateRef.current.isTouched &&\n stateRef.current.isDirty &&\n document.activeElement !== fieldRef.current\n ) {\n return stateRef.current.isValid;\n }\n validationIdRef.current++;\n pendingValidationRef.current = validate(\n stateRef.current,\n validation?.onChange,\n validation?.onBlur,\n validation?.onSubmit,\n );\n if (pendingValidationRef.current.isValid) {\n pendingValidationRef.current = await validateAsync(\n stateRef.current,\n validation?.onChangeAsync,\n validation?.onBlurAsync,\n validation?.onSubmitAsync,\n );\n }\n return pendingValidationRef.current.isValid;\n }\n\n function commitPendingValidation() {\n if (pendingValidationRef.current) {\n onChange(pendingValidationRef.current);\n pendingValidationRef.current = null;\n }\n }\n\n registerField(\n id,\n fieldRef.current,\n performValidation,\n commitPendingValidation,\n );\n\n return () => {\n unregisterField(id);\n };\n }, [id, registerField, validation, onChange, unregisterField]);\n\n useEffect(() => {\n return () => {\n if (validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n };\n }, []);\n\n function handleChange(value: T) {\n onInput?.(value);\n\n if (validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n\n const currentValidation = ++validationIdRef.current;\n\n const shouldValidate =\n validationMode === \"dirty\" ||\n validationMode === \"touchedOrDirty\" ||\n stateRef.current.isTouched;\n\n if (shouldValidate) {\n const errorMessage = validation?.onChange?.(value);\n const willValidateAsync = Boolean(\n validation?.onChangeAsync && !errorMessage,\n );\n\n onChange({\n ...stateRef.current,\n value,\n errorMessage,\n isDirty: true,\n isValid: !errorMessage,\n isValidating: willValidateAsync,\n });\n\n if (willValidateAsync) {\n isValidatingOnChangeRef.current = true;\n validationTimeoutRef.current = setTimeout(async () => {\n const asyncErrorMessage = await validation?.onChangeAsync?.(value);\n\n isValidatingOnChangeRef.current = false;\n if (currentValidation === validationIdRef.current) {\n onChange({\n ...stateRef.current,\n errorMessage: asyncErrorMessage,\n isValid: !asyncErrorMessage,\n isValidating: isValidatingOnBlurRef.current,\n });\n }\n }, debounceMs);\n }\n } else {\n onChange({\n ...stateRef.current,\n value,\n isDirty: true,\n isValidating: false,\n });\n }\n }\n\n async function handleBlur() {\n onBlur?.();\n\n const shouldValidateOnBlur =\n validationMode === \"touched\" ||\n validationMode === \"touchedOrDirty\" ||\n stateRef.current.isDirty;\n\n if (!shouldValidateOnBlur) {\n onChange({\n ...stateRef.current,\n isTouched: true,\n });\n return;\n }\n\n const currentValidation = validationIdRef.current;\n\n let errorMessage =\n stateRef.current.errorMessage ||\n validation?.onBlur?.(stateRef.current.value);\n\n const shouldValidateOnChange =\n !stateRef.current.isTouched &&\n (validationMode === \"touched\" ||\n (validationMode === \"touchedOrDirty\" && !stateRef.current.isDirty) ||\n (validationMode === \"touchedAndDirty\" && stateRef.current.isDirty));\n\n if (!errorMessage && shouldValidateOnChange && validation?.onChange) {\n errorMessage = validation.onChange(stateRef.current.value);\n }\n\n if (\n !errorMessage &&\n (validation?.onBlurAsync || validation?.onChangeAsync)\n ) {\n isValidatingOnBlurRef.current = true;\n onChange({\n ...stateRef.current,\n isValidating: true,\n isTouched: true,\n });\n\n const asyncValidations: Promise<React.ReactNode>[] = [];\n\n if (validation?.onBlurAsync) {\n asyncValidations.push(validation.onBlurAsync(stateRef.current.value));\n }\n\n if (shouldValidateOnChange && validation?.onChangeAsync) {\n asyncValidations.push(validation.onChangeAsync(stateRef.current.value));\n }\n\n const [blurError, changeError] = await Promise.all(asyncValidations);\n errorMessage = blurError || changeError;\n }\n\n isValidatingOnBlurRef.current = false;\n\n if (errorMessage && validationTimeoutRef.current) {\n clearTimeout(validationTimeoutRef.current);\n }\n\n if (currentValidation !== validationIdRef.current) {\n onChange({\n ...stateRef.current,\n isTouched: true,\n });\n return;\n }\n\n onChange({\n ...stateRef.current,\n errorMessage,\n isTouched: true,\n isValid: !errorMessage,\n isValidating: isValidatingOnChangeRef.current,\n });\n }\n\n const ref = (el: HTMLElement | null) => {\n fieldRef.current = el;\n };\n\n return children({\n ...stateRef.current,\n handleChange,\n handleBlur,\n ref,\n });\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/lib/Form.d.ts
CHANGED
package/dist/esm/lib/Form.js
CHANGED
|
@@ -20,7 +20,8 @@ function Form({ onSubmit, validationMode, debounceMs, ...props }) {
|
|
|
20
20
|
const unregisterField = useCallback((id) => {
|
|
21
21
|
fieldsRef.current.delete(id);
|
|
22
22
|
}, []);
|
|
23
|
-
const validateForm = useCallback(async () => {
|
|
23
|
+
const validateForm = useCallback(async (options = {}) => {
|
|
24
|
+
const { focusFirstError: shouldFocusFirstError = true, scrollOffset = 100, } = options;
|
|
24
25
|
const fields = Array.from(fieldsRef.current.values());
|
|
25
26
|
const validationPromises = fields.map(async (field) => ({
|
|
26
27
|
isValid: await field.validate(),
|
|
@@ -29,8 +30,8 @@ function Form({ onSubmit, validationMode, debounceMs, ...props }) {
|
|
|
29
30
|
const results = await Promise.all(validationPromises);
|
|
30
31
|
fields.forEach((field) => field.commitPendingValidation());
|
|
31
32
|
const hasErrors = results.some((result) => !result.isValid);
|
|
32
|
-
if (hasErrors) {
|
|
33
|
-
focusFirstError(results);
|
|
33
|
+
if (hasErrors && shouldFocusFirstError) {
|
|
34
|
+
focusFirstError(results, scrollOffset);
|
|
34
35
|
}
|
|
35
36
|
return !hasErrors;
|
|
36
37
|
}, []);
|
|
@@ -49,7 +50,7 @@ const FormContext = React.createContext({
|
|
|
49
50
|
function useFormContext() {
|
|
50
51
|
return React.useContext(FormContext);
|
|
51
52
|
}
|
|
52
|
-
function focusFirstError(results) {
|
|
53
|
+
function focusFirstError(results, scrollOffset) {
|
|
53
54
|
const firstInvalidField = results
|
|
54
55
|
.filter((field) => !field.isValid && field.ref)
|
|
55
56
|
.map((field) => field.ref)
|
|
@@ -61,7 +62,7 @@ function focusFirstError(results) {
|
|
|
61
62
|
firstInvalidField.focus();
|
|
62
63
|
const rect = firstInvalidField.getBoundingClientRect();
|
|
63
64
|
window.scrollTo({
|
|
64
|
-
top: rect.top + window.scrollY -
|
|
65
|
+
top: rect.top + window.scrollY - scrollOffset,
|
|
65
66
|
});
|
|
66
67
|
}
|
|
67
68
|
|
package/dist/esm/lib/Form.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Form.js","sources":["../../../src/lib/Form.tsx"],"sourcesContent":["import React, { useCallback, useRef } from \"react\";\nimport {
|
|
1
|
+
{"version":3,"file":"Form.js","sources":["../../../src/lib/Form.tsx"],"sourcesContent":["import React, { useCallback, useRef } from \"react\";\nimport {\n FieldMap,\n FormContextValue,\n ValidateFormOptions,\n ValidationMode,\n} from \"./types\";\n\n/** Props for the {@link Form} component. */\ninterface FormProps extends Omit<React.ComponentProps<\"form\">, \"onSubmit\"> {\n /** Default validation mode applied to all fields in the form. Defaults to `\"touchedAndDirty\"`. */\n validationMode?: ValidationMode;\n /** Default debounce delay in milliseconds for async validators. Defaults to `500`. */\n debounceMs?: number;\n /**\n * Submit handler called when the form is submitted.\n * Receives the submit event and a `validateAllFields` function that triggers\n * validation on all registered fields and returns whether the form is valid.\n */\n onSubmit?: (\n e: React.SubmitEvent<HTMLFormElement>,\n validateForm: () => Promise<boolean>,\n ) => void;\n}\n\n/**\n * A form component that provides context for coordinating field validation.\n *\n * Wraps a native `<form>` element and prevents the default submit behavior.\n * On submit, the `onSubmit` callback is called with the submit event and a\n * `validateAllFields` function that can be used to trigger validation on all\n * registered {@link Field} components and focus the first invalid field.\n */\nexport function Form({\n onSubmit,\n validationMode,\n debounceMs,\n ...props\n}: FormProps) {\n const fieldsRef = useRef<FieldMap>(new Map());\n\n const registerField = useCallback(\n (\n id: string,\n ref: HTMLElement | null,\n validate: () => Promise<boolean>,\n commitPendingValidation: () => void,\n ) => {\n fieldsRef.current.set(id, {\n ref,\n validate,\n commitPendingValidation,\n });\n },\n [],\n );\n\n const unregisterField = useCallback((id: string) => {\n fieldsRef.current.delete(id);\n }, []);\n\n const validateForm = useCallback(\n async (options: ValidateFormOptions = {}) => {\n const {\n focusFirstError: shouldFocusFirstError = true,\n scrollOffset = 100,\n } = options;\n\n const fields = Array.from(fieldsRef.current.values());\n const validationPromises = fields.map(async (field) => ({\n isValid: await field.validate(),\n ref: field.ref,\n }));\n const results = await Promise.all(validationPromises);\n fields.forEach((field) => field.commitPendingValidation());\n const hasErrors = results.some((result) => !result.isValid);\n\n if (hasErrors && shouldFocusFirstError) {\n focusFirstError(results, scrollOffset);\n }\n return !hasErrors;\n },\n [],\n );\n\n return (\n <FormContext.Provider\n value={{\n registerField,\n unregisterField,\n validationMode,\n debounceMs,\n }}\n >\n <form onSubmit={(e) => onSubmit?.(e, validateForm)} {...props} />\n </FormContext.Provider>\n );\n}\n\nexport const FormContext = React.createContext<FormContextValue>({\n registerField: () => {},\n unregisterField: () => {},\n});\n\nexport function useFormContext() {\n return React.useContext(FormContext);\n}\n\nexport function focusFirstError(\n results: {\n isValid: boolean;\n ref: HTMLElement | null;\n }[],\n scrollOffset: number,\n) {\n const firstInvalidField = results\n .filter((field) => !field.isValid && field.ref)\n .map((field) => field.ref!)\n .sort((a, b) =>\n a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING ? 1 : -1,\n )\n .at(0);\n\n if (!firstInvalidField) {\n return;\n }\n\n firstInvalidField.focus();\n const rect = firstInvalidField.getBoundingClientRect();\n window.scrollTo({\n top: rect.top + window.scrollY - scrollOffset,\n });\n}\n"],"names":[],"mappings":";;AAyBA;;;;;;;AAOG;AACG,SAAU,IAAI,CAAC,EACnB,QAAQ,EACR,cAAc,EACd,UAAU,EACV,GAAG,KAAK,EACE,EAAA;IACV,MAAM,SAAS,GAAG,MAAM,CAAW,IAAI,GAAG,EAAE,CAAC;AAE7C,IAAA,MAAM,aAAa,GAAG,WAAW,CAC/B,CACE,EAAU,EACV,GAAuB,EACvB,QAAgC,EAChC,uBAAmC,KACjC;AACF,QAAA,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE;YACxB,GAAG;YACH,QAAQ;YACR,uBAAuB;AACxB,SAAA,CAAC;IACJ,CAAC,EACD,EAAE,CACH;AAED,IAAA,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,EAAU,KAAI;AACjD,QAAA,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;IAC9B,CAAC,EAAE,EAAE,CAAC;IAEN,MAAM,YAAY,GAAG,WAAW,CAC9B,OAAO,OAAA,GAA+B,EAAE,KAAI;AAC1C,QAAA,MAAM,EACJ,eAAe,EAAE,qBAAqB,GAAG,IAAI,EAC7C,YAAY,GAAG,GAAG,GACnB,GAAG,OAAO;AAEX,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;AACrD,QAAA,MAAM,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,KAAK,MAAM;AACtD,YAAA,OAAO,EAAE,MAAM,KAAK,CAAC,QAAQ,EAAE;YAC/B,GAAG,EAAE,KAAK,CAAC,GAAG;AACf,SAAA,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;AACrD,QAAA,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,uBAAuB,EAAE,CAAC;AAC1D,QAAA,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;AAE3D,QAAA,IAAI,SAAS,IAAI,qBAAqB,EAAE;AACtC,YAAA,eAAe,CAAC,OAAO,EAAE,YAAY,CAAC;QACxC;QACA,OAAO,CAAC,SAAS;IACnB,CAAC,EACD,EAAE,CACH;AAED,IAAA,QACE,KAAA,CAAA,aAAA,CAAC,WAAW,CAAC,QAAQ,EAAA,EACnB,KAAK,EAAE;YACL,aAAa;YACb,eAAe;YACf,cAAc;YACd,UAAU;AACX,SAAA,EAAA;AAED,QAAA,KAAA,CAAA,aAAA,CAAA,MAAA,EAAA,EAAM,QAAQ,EAAE,CAAC,CAAC,KAAK,QAAQ,GAAG,CAAC,EAAE,YAAY,CAAC,EAAA,GAAM,KAAK,EAAA,CAAI,CAC5C;AAE3B;AAEO,MAAM,WAAW,GAAG,KAAK,CAAC,aAAa,CAAmB;AAC/D,IAAA,aAAa,EAAE,MAAK,EAAE,CAAC;AACvB,IAAA,eAAe,EAAE,MAAK,EAAE,CAAC;AAC1B,CAAA;SAEe,cAAc,GAAA;AAC5B,IAAA,OAAO,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;AACtC;AAEM,SAAU,eAAe,CAC7B,OAGG,EACH,YAAoB,EAAA;IAEpB,MAAM,iBAAiB,GAAG;AACvB,SAAA,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,GAAG;SAC7C,GAAG,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,GAAI;SACzB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KACT,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,2BAA2B,GAAG,CAAC,GAAG,EAAE;SAEzE,EAAE,CAAC,CAAC,CAAC;IAER,IAAI,CAAC,iBAAiB,EAAE;QACtB;IACF;IAEA,iBAAiB,CAAC,KAAK,EAAE;AACzB,IAAA,MAAM,IAAI,GAAG,iBAAiB,CAAC,qBAAqB,EAAE;IACtD,MAAM,CAAC,QAAQ,CAAC;QACd,GAAG,EAAE,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,OAAO,GAAG,YAAY;AAC9C,KAAA,CAAC;AACJ;;;;"}
|
package/dist/esm/lib/types.d.ts
CHANGED
|
@@ -86,3 +86,9 @@ export type FieldMap = Map<string, {
|
|
|
86
86
|
validate: () => Promise<boolean>;
|
|
87
87
|
commitPendingValidation: () => void;
|
|
88
88
|
}>;
|
|
89
|
+
export interface ValidateFormOptions {
|
|
90
|
+
/** If `true`, the first invalid field will be focused after validation. Defaults to `true`. */
|
|
91
|
+
focusFirstError?: boolean;
|
|
92
|
+
/** Additional offset in pixels to apply when scrolling to the first error. Defaults to `100`. */
|
|
93
|
+
scrollOffset?: number;
|
|
94
|
+
}
|