| 32 | * Form management hook for Rezi widgets. |
| 33 | */ |
| 34 | export function useForm<T extends Record<string, unknown>, State = void>( |
| 35 | ctx: WidgetContext<State>, |
| 36 | options: UseFormOptions<T>, |
| 37 | ): UseFormReturn<T> { |
| 38 | const [state, setState] = ctx.useState<FormState<T>>(() => createInitialState(options)); |
| 39 | const stateRef = ctx.useRef(state); |
| 40 | stateRef.current = state; |
| 41 | |
| 42 | const initialValuesRef = ctx.useRef<T>(cloneInitialValues(options.initialValues)); |
| 43 | |
| 44 | const asyncValidatorRef = ctx.useRef< |
| 45 | ReturnType<typeof createDebouncedAsyncValidator<T>> | undefined |
| 46 | >(undefined); |
| 47 | |
| 48 | const pendingAsyncValuesRef = ctx.useRef<T | null>(null); |
| 49 | const submittingRef = ctx.useRef(false); |
| 50 | const submitAttemptRef = ctx.useRef(0); |
| 51 | const validateRef = ctx.useRef(options.validate); |
| 52 | validateRef.current = options.validate; |
| 53 | const nonTextBindingWarningsRef = ctx.useRef<Set<string>>(new Set()); |
| 54 | |
| 55 | const fieldArrayKeysRef = ctx.useRef<Partial<Record<keyof T, string[]>>>({}); |
| 56 | const fieldArrayKeyCounterRef = ctx.useRef<number>(0); |
| 57 | |
| 58 | const updateFormState = ( |
| 59 | nextState: FormState<T> | ((prev: FormState<T>) => FormState<T>), |
| 60 | ): void => { |
| 61 | setState((prev) => { |
| 62 | const resolved = |
| 63 | typeof nextState === "function" |
| 64 | ? (nextState as (prev: FormState<T>) => FormState<T>)(prev) |
| 65 | : nextState; |
| 66 | stateRef.current = resolved; |
| 67 | return resolved; |
| 68 | }); |
| 69 | }; |
| 70 | |
| 71 | const wizardSteps = options.wizard?.steps ?? []; |
| 72 | const stepCount = wizardSteps.length; |
| 73 | const hasWizard = stepCount > 0; |
| 74 | const currentStep = hasWizard ? clampStepIndex(state.currentStep, stepCount) : 0; |
| 75 | const isFirstStep = !hasWizard || currentStep === 0; |
| 76 | const isLastStep = !hasWizard || currentStep === stepCount - 1; |
| 77 | |
| 78 | const { |
| 79 | isFieldDisabledInternal, |
| 80 | isFieldReadOnlyInternal, |
| 81 | isFieldEditableInternal, |
| 82 | filterDisabledValidationErrors, |
| 83 | runSyncValidationFiltered, |
| 84 | runAsyncValidationFiltered, |
| 85 | warnUnsupportedTextBinding, |
| 86 | canBindFieldAsText, |
| 87 | } = createFormStateAccessors({ |
| 88 | stateRef, |
| 89 | initialValuesRef, |
| 90 | validateRef, |
| 91 | validateAsync: options.validateAsync, |