UNPKG

@mantine/form

Version:

Mantine form management library

294 lines (293 loc) 11.3 kB
"use client"; import { useFormActions } from "./actions/actions.mjs"; import { getInputOnChange } from "./get-input-on-change/get-input-on-change.mjs"; import { useFormErrors } from "./hooks/use-form-errors/use-form-errors.mjs"; import { getPath } from "./paths/get-path.mjs"; import { getDataPath } from "./paths/get-data-path.mjs"; import { useFormList } from "./hooks/use-form-list/use-form-list.mjs"; import { useFormStatus } from "./hooks/use-form-status/use-form-status.mjs"; import { useFormValidating } from "./hooks/use-form-validating/use-form-validating.mjs"; import { useFormValues } from "./hooks/use-form-values/use-form-values.mjs"; import { useFormWatch } from "./hooks/use-form-watch/use-form-watch.mjs"; import { validateValues } from "./validate/validate-values.mjs"; import { validateFieldValue } from "./validate/validate-field-value.mjs"; import { shouldValidateOnChange } from "./validate/should-validate-on-change.mjs"; import { useCallback, useMemo, useRef, useState } from "react"; //#region packages/@mantine/form/src/use-form.ts const defaultResolveValidationError = (err) => err instanceof Error ? err.message : String(err); function useForm({ name, mode = "controlled", initialValues, initialErrors = {}, initialDirty = {}, initialTouched = {}, clearInputErrorOnChange = true, validateInputOnChange = false, validateInputOnBlur = false, onValuesChange, transformValues = ((values) => values), enhanceGetInputProps, validate: rules, onSubmitPreventDefault = "always", touchTrigger = "change", cascadeUpdates = false, validateDebounce = 0, resolveValidationError = defaultResolveValidationError } = {}) { const $errors = useFormErrors(initialErrors); const $values = useFormValues({ initialValues, onValuesChange, mode }); const $status = useFormStatus({ initialDirty, initialTouched, $values, mode }); const $watch = useFormWatch({ $values, $status, cascadeUpdates }); const $list = useFormList({ $values, $errors, $status, $watch }); const $validating = useFormValidating(); const [formKey, setFormKey] = useState(0); const [fieldKeys, setFieldKeys] = useState({}); const [submitting, setSubmitting] = useState(false); const validateGeneration = useRef(0); const reset = useCallback(() => { $values.resetValues(); $errors.clearErrors(); $status.resetDirty(); $status.resetTouched(); $validating.clearValidating(); mode === "uncontrolled" && setFormKey((key) => key + 1); }, []); const handleValuesChanges = useCallback((previousValues) => { clearInputErrorOnChange && $errors.clearErrors(); mode === "uncontrolled" && setFormKey((key) => key + 1); $watch.notifyWatchSubscribers(previousValues); }, [clearInputErrorOnChange]); const initialize = useCallback((values) => { const previousValues = $values.refValues.current; $values.initialize(values, () => mode === "uncontrolled" && setFormKey((key) => key + 1)); handleValuesChanges(previousValues); }, [handleValuesChanges]); const debouncedValidateField = useMemo(() => { const timers = {}; const handleValidation = (path) => { const signal = $validating.getAbortSignal(path); const result = validateFieldValue(path, rules, $values.refValues.current, resolveValidationError, signal); const applyResult = (results) => { if (signal.aborted) return; if (results.hasError) $errors.setFieldError(path, results.error); else $errors.clearFieldError(path); }; const cleanup = () => { if (!signal.aborted) $validating.setFieldValidating(path, false); }; if (result instanceof Promise) { $validating.setFieldValidating(path, true); result.then(applyResult).finally(cleanup); } else applyResult(result); }; return (path) => { clearTimeout(timers[path]); if (validateDebounce > 0) timers[path] = setTimeout(() => handleValidation(path), validateDebounce); else handleValidation(path); }; }, [ validateDebounce, rules, resolveValidationError ]); const setFieldValue = useCallback((path, value, options) => { const shouldValidate = shouldValidateOnChange(path, validateInputOnChange); const resolvedValue = value instanceof Function ? value(getPath(path, $values.refValues.current)) : value; $status.setCalculatedFieldDirty(path, resolvedValue); touchTrigger === "change" && $status.setFieldTouched(path, true); !shouldValidate && clearInputErrorOnChange && $errors.clearFieldError(path); $values.setFieldValue({ path, value, updateState: mode === "controlled", subscribers: [ ...$watch.getFieldSubscribers(path), shouldValidate ? () => debouncedValidateField(String(path)) : null, options?.forceUpdate !== false && mode !== "controlled" ? () => setFieldKeys((keys) => ({ ...keys, [path]: (keys[path] || 0) + 1 })) : null ] }); }, [ onValuesChange, rules, debouncedValidateField ]); const setValues = useCallback((values) => { const previousValues = $values.refValues.current; $values.setValues({ values, updateState: mode === "controlled" }); handleValuesChanges(previousValues); }, [onValuesChange, handleValuesChanges]); const validate = useCallback(() => { const generation = ++validateGeneration.current; const signal = $validating.getAbortSignal("__form__"); const handleResult = (results) => { if (generation !== validateGeneration.current) return { hasErrors: false, errors: {} }; $errors.setErrors(results.errors); return results; }; const cleanup = () => { if (generation === validateGeneration.current) $validating.setFormValidating(false); }; const result = validateValues(rules, $values.refValues.current, resolveValidationError, signal); if (result instanceof Promise) { $validating.setFormValidating(true); return result.then(handleResult).finally(cleanup); } return handleResult(result); }, [rules, resolveValidationError]); const validateField = useCallback((path) => { const signal = $validating.getAbortSignal(String(path)); const applyResult = (results) => { if (signal.aborted) return { hasError: false, error: null }; if (results.hasError) $errors.setFieldError(path, results.error); else $errors.clearFieldError(path); return results; }; const cleanup = () => { if (!signal.aborted) $validating.setFieldValidating(String(path), false); }; const result = validateFieldValue(path, rules, $values.refValues.current, resolveValidationError, signal); if (result instanceof Promise) { $validating.setFieldValidating(String(path), true); return result.then(applyResult).finally(cleanup); } return applyResult(result); }, [rules, resolveValidationError]); const getInputProps = (path, { type = "input", withError = true, withFocus, ...otherOptions } = {}) => { const _withFocus = withFocus ?? type !== "radio"; const payload = { onChange: getInputOnChange((value) => setFieldValue(path, value, { forceUpdate: false })), "data-path": getDataPath(name, path) }; if (withError) payload.error = $errors.errorsState[path]; if (type === "checkbox") payload[mode === "controlled" ? "checked" : "defaultChecked"] = getPath(path, $values.refValues.current); else if (type === "radio") { payload[mode === "controlled" ? "checked" : "defaultChecked"] = getPath(path, $values.refValues.current) === otherOptions.value; payload.value = otherOptions.value; } else payload[mode === "controlled" ? "value" : "defaultValue"] = getPath(path, $values.refValues.current); if (_withFocus) { payload.onFocus = () => $status.setFieldTouched(path, true); payload.onBlur = () => { if (shouldValidateOnChange(path, validateInputOnBlur)) debouncedValidateField(String(path)); }; } return Object.assign(payload, enhanceGetInputProps?.({ inputProps: payload, field: path, options: { type, withError, withFocus: _withFocus, ...otherOptions }, form })); }; const onSubmit = (handleSubmit, handleValidationFailure) => (event) => { if (onSubmitPreventDefault === "always") event?.preventDefault(); setSubmitting(true); const handleValidation = (results) => { if (results.hasErrors) { if (onSubmitPreventDefault === "validation-failed") event?.preventDefault(); handleValidationFailure?.(results.errors, $values.refValues.current, event); setSubmitting(false); } else { const submitResult = handleSubmit?.(transformValues($values.refValues.current), event); if (submitResult instanceof Promise) submitResult.finally(() => setSubmitting(false)); else setSubmitting(false); } }; const result = validate(); if (result instanceof Promise) result.then(handleValidation).catch(() => { setSubmitting(false); }); else handleValidation(result); }; const getTransformedValues = (input) => transformValues(input || $values.refValues.current); const onReset = useCallback((event) => { event.preventDefault(); reset(); }, []); const isValid = useCallback((path) => { const signal = new AbortController().signal; if (path) { const result = validateFieldValue(path, rules, $values.refValues.current, resolveValidationError, signal); if (result instanceof Promise) return result.then((r) => !r.hasError); return !result.hasError; } const result = validateValues(rules, $values.refValues.current, resolveValidationError, signal); if (result instanceof Promise) return result.then((r) => !r.hasErrors); return !result.hasErrors; }, [rules, resolveValidationError]); const key = (path) => `${formKey}-${String(path)}-${fieldKeys[String(path)] || 0}`; const getInputNode = useCallback((path) => document.querySelector(`[data-path="${getDataPath(name, path)}"]`), []); const resetField = useCallback((path) => { $values.resetField(path, [mode !== "controlled" ? () => setFieldKeys((keys) => ({ ...keys, [path]: (keys[path] || 0) + 1 })) : null]); }, [ $values.resetField, mode, setFieldKeys ]); const form = { watch: $watch.watch, initialized: $values.initialized.current, values: mode === "uncontrolled" ? $values.refValues.current : $values.stateValues, getValues: $values.getValues, getInitialValues: $values.getValuesSnapshot, setInitialValues: $values.setValuesSnapshot, resetField, initialize, setValues, setFieldValue, submitting, setSubmitting, validating: $validating.validating, isValidating: $validating.isValidating, errors: $errors.errorsState, setErrors: $errors.setErrors, setFieldError: $errors.setFieldError, clearFieldError: $errors.clearFieldError, clearErrors: $errors.clearErrors, resetDirty: $status.resetDirty, setTouched: $status.setTouched, setDirty: $status.setDirty, isTouched: $status.isTouched, resetTouched: $status.resetTouched, isDirty: $status.isDirty, getTouched: $status.getTouched, getDirty: $status.getDirty, reorderListItem: $list.reorderListItem, insertListItem: $list.insertListItem, removeListItem: $list.removeListItem, replaceListItem: $list.replaceListItem, reset, validate, validateField, getInputProps, onSubmit, onReset, isValid, getTransformedValues, key, getInputNode }; useFormActions(name, form); return form; } //#endregion export { useForm }; //# sourceMappingURL=use-form.mjs.map