@formality-ui/react 0.2.4 → 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/index.cjs +85 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +193 -18
- package/dist/index.d.ts +193 -18
- package/dist/index.js +85 -30
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -201,13 +201,14 @@ declare function defineInputs<T extends Record<string, ReactInputConfig>>(inputs
|
|
|
201
201
|
*
|
|
202
202
|
* `<Field>` renders your input component via React Hook Form's `<Controller>`.
|
|
203
203
|
* At runtime Formality merges a `coreProps` bundle onto the component (name,
|
|
204
|
-
* value, onChange, onBlur, and —
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
204
|
+
* value, onChange, onBlur, and `forwardRef` — RHF's ref callback, delivered as
|
|
205
|
+
* a regular enumerable top-level prop, NOT React's special `ref` key; see §20.1
|
|
206
|
+
* and "Runtime delivery" below). The three members below are the
|
|
207
|
+
* **injected-props contract**: `formState` always reaches templates and
|
|
208
|
+
* render-prop children, and reaches plain components that have opted into
|
|
209
|
+
* Formality state via `provideState` / `passSubscriptions`; `state`
|
|
210
|
+
* (subscribed field state) and `forwardRef` are delivered at runtime by
|
|
211
|
+
* `<Field>` (see "Runtime delivery" below).
|
|
211
212
|
*
|
|
212
213
|
* **Destructure before forwarding.** Component authors MUST destructure
|
|
213
214
|
* `state`, `formState`, and `forwardRef` OUT of props before spreading the
|
|
@@ -593,6 +594,14 @@ interface FormProps<TFieldValues extends FieldValues = FieldValues> {
|
|
|
593
594
|
* (`false` for immediate, or a number for a per-field delay).
|
|
594
595
|
*/
|
|
595
596
|
debounce?: number | false;
|
|
597
|
+
/**
|
|
598
|
+
* React Hook Form validation trigger `mode`, forwarded to `useForm({ mode })`.
|
|
599
|
+
* One of `'onChange'` (default) | `'onBlur'` | `'onSubmit'` | `'onTouched'` |
|
|
600
|
+
* `'all'`. Honored as-is — auto-save is mode-agnostic: its validity gates
|
|
601
|
+
* trigger validation of the touched fields themselves via `methods.trigger`
|
|
602
|
+
* (which ignores `mode`), so it works under any mode. See PRD §12.
|
|
603
|
+
*/
|
|
604
|
+
mode?: "onChange" | "onBlur" | "onSubmit" | "onTouched" | "all";
|
|
596
605
|
/** Form-level validation */
|
|
597
606
|
validate?: (values: Partial<TFieldValues>) => Record<string, string> | Promise<Record<string, string>>;
|
|
598
607
|
}
|
|
@@ -644,7 +653,7 @@ interface FormRenderAPI<TFieldValues extends FieldValues = FieldValues> {
|
|
|
644
653
|
* </Form>
|
|
645
654
|
* ```
|
|
646
655
|
*/
|
|
647
|
-
declare function Form<TFieldValues extends FieldValues = FieldValues>({ children, config, formConfig, onSubmit, record, autoSave, debounce: debounceMs, validate, }: FormProps<TFieldValues>): JSX.Element;
|
|
656
|
+
declare function Form<TFieldValues extends FieldValues = FieldValues>({ children, config, formConfig, onSubmit, record, autoSave, debounce: debounceMs, mode, validate, }: FormProps<TFieldValues>): JSX.Element;
|
|
648
657
|
|
|
649
658
|
/**
|
|
650
659
|
* Field component props.
|
|
@@ -714,15 +723,15 @@ interface FieldRenderAPI {
|
|
|
714
723
|
formState: UseFormStateReturn<FieldValues>;
|
|
715
724
|
}
|
|
716
725
|
/**
|
|
717
|
-
* Field component -
|
|
726
|
+
* Field component - Thin wrapper over {@link useField}.
|
|
718
727
|
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
* -
|
|
722
|
-
*
|
|
723
|
-
* -
|
|
724
|
-
*
|
|
725
|
-
* -
|
|
728
|
+
* The RHF Controller integration (props resolution / 8-layer merge, conditions,
|
|
729
|
+
* parse/format, validation, forwardRef delivery, template/host rendering,
|
|
730
|
+
* render-prop `children` application) lives in the {@link useField} hook
|
|
731
|
+
* (PRD §1.3.3 / §5.3 / §20). This component owns ONLY:
|
|
732
|
+
* - the form-registry registration `useEffect` (`registerField`/
|
|
733
|
+
* `unregisterField`, gated on `shouldRegister`) — contract bullet b; and
|
|
734
|
+
* - delegating to `useField(params)` and returning its `renderedField`.
|
|
726
735
|
*
|
|
727
736
|
* @example
|
|
728
737
|
* ```tsx
|
|
@@ -735,7 +744,7 @@ interface FieldRenderAPI {
|
|
|
735
744
|
* // Custom render
|
|
736
745
|
* <Field name="name">
|
|
737
746
|
* {({ renderedField, fieldState }) => (
|
|
738
|
-
* <div className={fieldState.error ?
|
|
747
|
+
* <div className={fieldState.error ? "has-error" : ""}>
|
|
739
748
|
* {renderedField}
|
|
740
749
|
* </div>
|
|
741
750
|
* )}
|
|
@@ -1098,4 +1107,170 @@ declare function useInferredInputs(options: UseInferredInputsOptions): string[];
|
|
|
1098
1107
|
*/
|
|
1099
1108
|
declare function useSubscriptions(fieldName: string, subscriptions: string[]): void;
|
|
1100
1109
|
|
|
1101
|
-
|
|
1110
|
+
/**
|
|
1111
|
+
* Parameters for {@link useField}.
|
|
1112
|
+
*
|
|
1113
|
+
* Mirrors the subset of `FieldProps` (in `../components/Field`) that the hook
|
|
1114
|
+
* consumes as the single seam for **RHF Controller integration** (gap_analysis.md
|
|
1115
|
+
* **G6**; PRD §1.3.3).
|
|
1116
|
+
*
|
|
1117
|
+
* Per PRD §1.3.3, `hooks/useField` is the module that owns **RHF Controller
|
|
1118
|
+
* integration** for a field — "Uses Core: transform/pipeline, validation/
|
|
1119
|
+
* validate". This interface is the **input contract** for that module: it
|
|
1120
|
+
* carries the field identity + the overrides + the passthrough props threaded
|
|
1121
|
+
* into the parse/format pipeline (§5.3.5), change handler (§5.3.6), validation
|
|
1122
|
+
* wiring (§5.3.7), forwardRef delivery (§20.1), and template/host rendering
|
|
1123
|
+
* (§5.3.8).
|
|
1124
|
+
*
|
|
1125
|
+
* Generic over `TName` (default `string`) so a narrowed name type from
|
|
1126
|
+
* `FieldProps<TName>` can be threaded straight through (PRD §C.4 / T2.1). The
|
|
1127
|
+
* default keeps a bare `UseFieldParams` identical to `UseFieldParams<string>`.
|
|
1128
|
+
*
|
|
1129
|
+
* @template TName - Field name literal/union type (default `string`).
|
|
1130
|
+
*/
|
|
1131
|
+
interface UseFieldParams<TName extends string = string> {
|
|
1132
|
+
/**
|
|
1133
|
+
* Field name (must match a key in Form's config). When `TName` is narrowed,
|
|
1134
|
+
* this is checked against `TName` at compile time.
|
|
1135
|
+
*/
|
|
1136
|
+
name: TName;
|
|
1137
|
+
/** Override the input type resolved from config. */
|
|
1138
|
+
type?: string;
|
|
1139
|
+
/**
|
|
1140
|
+
* Override disabled state. Highest priority in the §5.3.4 disabled
|
|
1141
|
+
* resolution (prop > condition > config).
|
|
1142
|
+
*/
|
|
1143
|
+
disabled?: boolean;
|
|
1144
|
+
/** Override hidden state (inverse of visible). */
|
|
1145
|
+
hidden?: boolean;
|
|
1146
|
+
/**
|
|
1147
|
+
* Whether to register this field in Form's field registry (default `true`).
|
|
1148
|
+
*
|
|
1149
|
+
* NOTE: the registration `useEffect` itself is OWNED by the `<Field>`
|
|
1150
|
+
* component (contract bullet b) — this flag is threaded through so a future
|
|
1151
|
+
* direct-hook consumer can opt out. `useField` does NOT call
|
|
1152
|
+
* `registerField`/`unregisterField`.
|
|
1153
|
+
*/
|
|
1154
|
+
shouldRegister?: boolean;
|
|
1155
|
+
/**
|
|
1156
|
+
* Per-field input-config override. Merged at the highest-priority layer over
|
|
1157
|
+
* provider/form config (e.g. per-field debounce). See `resolveInputConfig`.
|
|
1158
|
+
*/
|
|
1159
|
+
inputConfig?: Partial<InputConfig>;
|
|
1160
|
+
/**
|
|
1161
|
+
* Optional render-prop. Applied INSIDE the hook's Controller render callback
|
|
1162
|
+
* (§5.3.8) against {@link UseFieldReturn}, so the render-prop always sees the
|
|
1163
|
+
* live `fieldState`/`formState`/`fieldProps` values.
|
|
1164
|
+
*/
|
|
1165
|
+
children?: ReactNode | ((api: UseFieldReturn) => ReactNode);
|
|
1166
|
+
/**
|
|
1167
|
+
* Additional props forwarded to the input component (the 8-layer
|
|
1168
|
+
* `componentProps` merge layer, §5.3.2). Captured by `mergeFieldProps`.
|
|
1169
|
+
*/
|
|
1170
|
+
[key: string]: unknown;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Return value of {@link useField}.
|
|
1174
|
+
*
|
|
1175
|
+
* **Structurally identical** to `FieldRenderAPI` (in `../components/Field`) —
|
|
1176
|
+
* the data the render layer (template / host / render-prop `children`) consumes.
|
|
1177
|
+
* This equivalence is enforced bidirectionally by
|
|
1178
|
+
* `__typechecks__/useField.test-d.ts`.
|
|
1179
|
+
*
|
|
1180
|
+
* Per PRD §1.3.3, the hook owns the RHF Controller integration, so the fields
|
|
1181
|
+
* below are exactly the Controller-produced state the render layer needs:
|
|
1182
|
+
* `fieldState` + `formState` come from the Controller render callback;
|
|
1183
|
+
* `renderedField` is the RAW rendered input (template/component/host) when
|
|
1184
|
+
* visible, `null` when hidden (the Controller mounts ONLY when visible, so
|
|
1185
|
+
* hidden fields are not RHF-registered — §5.3.8 / §20);
|
|
1186
|
+
* `fieldProps` is the final 8-layer-merged props (§5.3.2, via `mergeFieldProps`);
|
|
1187
|
+
* `watchers` is the map of fields currently watching this field (§5.3.1 step 3).
|
|
1188
|
+
*/
|
|
1189
|
+
interface UseFieldReturn {
|
|
1190
|
+
/**
|
|
1191
|
+
* RHF field state from the Controller render callback
|
|
1192
|
+
* (`invalid` / `isTouched` / `isDirty` / `error` / …).
|
|
1193
|
+
*/
|
|
1194
|
+
fieldState: ControllerFieldState;
|
|
1195
|
+
/**
|
|
1196
|
+
* The RAW rendered input (template / component / host element), or `null`
|
|
1197
|
+
* when the field is not visible. The render-prop `children` is applied
|
|
1198
|
+
* INSIDE the Controller render callback that produces this element.
|
|
1199
|
+
*/
|
|
1200
|
+
renderedField: ReactNode;
|
|
1201
|
+
/**
|
|
1202
|
+
* Final merged props passed to the input — the output of the 8-layer
|
|
1203
|
+
* `mergeFieldProps` pipeline (§5.3.2).
|
|
1204
|
+
*/
|
|
1205
|
+
fieldProps: Record<string, unknown>;
|
|
1206
|
+
/**
|
|
1207
|
+
* Map of fields watching this field (`true` = currently subscribed). Populated
|
|
1208
|
+
* via the watcher-setter registered with `useSubscriptions` (§5.3.1 step 3).
|
|
1209
|
+
*/
|
|
1210
|
+
watchers: Record<string, boolean>;
|
|
1211
|
+
/**
|
|
1212
|
+
* RHF form state from the Controller. Typed as `UseFormStateReturn<FieldValues>`
|
|
1213
|
+
* to match `FieldRenderAPI` exactly (NOT bare `UseFormStateReturn`).
|
|
1214
|
+
*/
|
|
1215
|
+
formState: UseFormStateReturn<FieldValues>;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* useField — RHF Controller integration for a single Field (PRD §1.3.3 / §20).
|
|
1219
|
+
*
|
|
1220
|
+
* This hook owns the **entire** Controller lifecycle for one field: it reads
|
|
1221
|
+
* the form/config/group contexts, resolves the input config, sets up watchers
|
|
1222
|
+
* + subscriptions + conditions, builds the setValue effect (ref pattern,
|
|
1223
|
+
* §7.1.1), resolves the disabled/visible/label state, builds the validation
|
|
1224
|
+
* rules + change handler, mounts the `<Controller>` element with its full
|
|
1225
|
+
* render callback (format → state injection → `mergeFieldProps` 8-layer merge →
|
|
1226
|
+
* forwardRef delivery §20.1/§20.4 → template/host/component → render-prop
|
|
1227
|
+
* `children`), and returns the {@link UseFieldReturn}.
|
|
1228
|
+
*
|
|
1229
|
+
* ## Responsibilities (PRD §1.3.3 + §5.3.x + §20)
|
|
1230
|
+
*
|
|
1231
|
+
* - **Controller integration** — wraps RHF's `<Controller>` render callback.
|
|
1232
|
+
* The `<Controller>` mounts ONLY when the field is visible (the
|
|
1233
|
+
* `renderedField` is `null` when hidden), preserving the
|
|
1234
|
+
* conditionally-hidden-field invariant (hidden fields are NOT RHF-registered).
|
|
1235
|
+
* - **Parse/format pipeline (§5.3.5)** — Core `parse` / `format` from
|
|
1236
|
+
* `@formality-ui/core` (`transform/pipeline`).
|
|
1237
|
+
* - **Change handler (§5.3.6)** — `changeField` + watcher fan-out.
|
|
1238
|
+
* - **Validation (§5.3.7)** — Core `runValidator` / `resolveErrorMessage`
|
|
1239
|
+
* (`validation/validate`).
|
|
1240
|
+
* - **forwardRef delivery (§20.1)** — delivers the input's ref via the merged
|
|
1241
|
+
* props' `forwardRef` key (the CURRENT implemented behavior — NOT the legacy
|
|
1242
|
+
* `ref` key shown in the stale §5.3.2 pseudo-code). The host-element path
|
|
1243
|
+
* (§20.4 narrow exception) translates `forwardRef` back into React's special
|
|
1244
|
+
* `ref` key for string components.
|
|
1245
|
+
* - **Props merge (§5.3.2)** — `resolveInputConfig` + `resolveLabel` +
|
|
1246
|
+
* `mergeFieldProps` (the 8-layer merge).
|
|
1247
|
+
* - **Template/host render (§5.3.8)** — produces the RAW `renderedField`.
|
|
1248
|
+
*
|
|
1249
|
+
* It consumes these context hooks (`useFormContext`, `useConfigContext`,
|
|
1250
|
+
* `useGroupContext`) and composed hooks (`useConditions`, `usePropsEvaluation`,
|
|
1251
|
+
* `useInferredInputs`, `useSubscriptions`), and the Core fns above.
|
|
1252
|
+
*
|
|
1253
|
+
* NOTE: field **registration** (`registerField`/`unregisterField`) is OWNED by
|
|
1254
|
+
* the `<Field>` component (contract bullet b), NOT this hook. The hook reads
|
|
1255
|
+
* `useFormContext()` for everything else (config, methods, changeField,
|
|
1256
|
+
* setFieldValidating, register/unregisterWatcherSetter).
|
|
1257
|
+
*
|
|
1258
|
+
* @param params - Field parameters (see {@link UseFieldParams}).
|
|
1259
|
+
* @returns The Controller-produced state + the RAW rendered input
|
|
1260
|
+
* (see {@link UseFieldReturn}).
|
|
1261
|
+
*
|
|
1262
|
+
* @example
|
|
1263
|
+
* ```tsx
|
|
1264
|
+
* // Direct hook usage (custom layout):
|
|
1265
|
+
* function MyField() {
|
|
1266
|
+
* const { renderedField, fieldState } = useField({ name: "email" });
|
|
1267
|
+
* return <div className={fieldState.error ? "has-error" : ""}>{renderedField}</div>;
|
|
1268
|
+
* }
|
|
1269
|
+
*
|
|
1270
|
+
* // Equivalent via the thin wrapper component:
|
|
1271
|
+
* <Field name="email" />;
|
|
1272
|
+
* ```
|
|
1273
|
+
*/
|
|
1274
|
+
declare function useField<TName extends string = string>({ name, type: typeProp, disabled: disabledProp, hidden: hiddenProp, children, inputConfig: inputConfigProp, ...restProps }: UseFieldParams<TName>): UseFieldReturn;
|
|
1275
|
+
|
|
1276
|
+
export { ConfigContext, type ConfigContextValue, type CustomFieldState, type DebouncedFunction, type ExtendedFormState, Field, FieldGroup, type FieldGroupProps, type FieldProps, type FieldRenderAPI, Form, FormContext, type FormContextValue, type FormProps, type FormRenderAPI, type FormalityFieldComponentProps, FormalityProvider, type FormalityProviderProps, GroupContext, type GroupContextValue, type GroupState, type InputTemplateProps, type ReactFieldConfig, type ReactFormFieldsConfig, type ReactInputConfig, UnusedFields, type UnusedFieldsProps, type UseFieldParams, type UseFieldReturn, type UseFormStateOptions, type WatcherSetterFn, defineInputs, makeDeepProxyState, makeProxyState, useConditions, useConfigContext, useField, useFormContext, useFormState, useGroupContext, useInferredInputs, usePropsEvaluation, useSubscriptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -201,13 +201,14 @@ declare function defineInputs<T extends Record<string, ReactInputConfig>>(inputs
|
|
|
201
201
|
*
|
|
202
202
|
* `<Field>` renders your input component via React Hook Form's `<Controller>`.
|
|
203
203
|
* At runtime Formality merges a `coreProps` bundle onto the component (name,
|
|
204
|
-
* value, onChange, onBlur, and —
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
204
|
+
* value, onChange, onBlur, and `forwardRef` — RHF's ref callback, delivered as
|
|
205
|
+
* a regular enumerable top-level prop, NOT React's special `ref` key; see §20.1
|
|
206
|
+
* and "Runtime delivery" below). The three members below are the
|
|
207
|
+
* **injected-props contract**: `formState` always reaches templates and
|
|
208
|
+
* render-prop children, and reaches plain components that have opted into
|
|
209
|
+
* Formality state via `provideState` / `passSubscriptions`; `state`
|
|
210
|
+
* (subscribed field state) and `forwardRef` are delivered at runtime by
|
|
211
|
+
* `<Field>` (see "Runtime delivery" below).
|
|
211
212
|
*
|
|
212
213
|
* **Destructure before forwarding.** Component authors MUST destructure
|
|
213
214
|
* `state`, `formState`, and `forwardRef` OUT of props before spreading the
|
|
@@ -593,6 +594,14 @@ interface FormProps<TFieldValues extends FieldValues = FieldValues> {
|
|
|
593
594
|
* (`false` for immediate, or a number for a per-field delay).
|
|
594
595
|
*/
|
|
595
596
|
debounce?: number | false;
|
|
597
|
+
/**
|
|
598
|
+
* React Hook Form validation trigger `mode`, forwarded to `useForm({ mode })`.
|
|
599
|
+
* One of `'onChange'` (default) | `'onBlur'` | `'onSubmit'` | `'onTouched'` |
|
|
600
|
+
* `'all'`. Honored as-is — auto-save is mode-agnostic: its validity gates
|
|
601
|
+
* trigger validation of the touched fields themselves via `methods.trigger`
|
|
602
|
+
* (which ignores `mode`), so it works under any mode. See PRD §12.
|
|
603
|
+
*/
|
|
604
|
+
mode?: "onChange" | "onBlur" | "onSubmit" | "onTouched" | "all";
|
|
596
605
|
/** Form-level validation */
|
|
597
606
|
validate?: (values: Partial<TFieldValues>) => Record<string, string> | Promise<Record<string, string>>;
|
|
598
607
|
}
|
|
@@ -644,7 +653,7 @@ interface FormRenderAPI<TFieldValues extends FieldValues = FieldValues> {
|
|
|
644
653
|
* </Form>
|
|
645
654
|
* ```
|
|
646
655
|
*/
|
|
647
|
-
declare function Form<TFieldValues extends FieldValues = FieldValues>({ children, config, formConfig, onSubmit, record, autoSave, debounce: debounceMs, validate, }: FormProps<TFieldValues>): JSX.Element;
|
|
656
|
+
declare function Form<TFieldValues extends FieldValues = FieldValues>({ children, config, formConfig, onSubmit, record, autoSave, debounce: debounceMs, mode, validate, }: FormProps<TFieldValues>): JSX.Element;
|
|
648
657
|
|
|
649
658
|
/**
|
|
650
659
|
* Field component props.
|
|
@@ -714,15 +723,15 @@ interface FieldRenderAPI {
|
|
|
714
723
|
formState: UseFormStateReturn<FieldValues>;
|
|
715
724
|
}
|
|
716
725
|
/**
|
|
717
|
-
* Field component -
|
|
726
|
+
* Field component - Thin wrapper over {@link useField}.
|
|
718
727
|
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
* -
|
|
722
|
-
*
|
|
723
|
-
* -
|
|
724
|
-
*
|
|
725
|
-
* -
|
|
728
|
+
* The RHF Controller integration (props resolution / 8-layer merge, conditions,
|
|
729
|
+
* parse/format, validation, forwardRef delivery, template/host rendering,
|
|
730
|
+
* render-prop `children` application) lives in the {@link useField} hook
|
|
731
|
+
* (PRD §1.3.3 / §5.3 / §20). This component owns ONLY:
|
|
732
|
+
* - the form-registry registration `useEffect` (`registerField`/
|
|
733
|
+
* `unregisterField`, gated on `shouldRegister`) — contract bullet b; and
|
|
734
|
+
* - delegating to `useField(params)` and returning its `renderedField`.
|
|
726
735
|
*
|
|
727
736
|
* @example
|
|
728
737
|
* ```tsx
|
|
@@ -735,7 +744,7 @@ interface FieldRenderAPI {
|
|
|
735
744
|
* // Custom render
|
|
736
745
|
* <Field name="name">
|
|
737
746
|
* {({ renderedField, fieldState }) => (
|
|
738
|
-
* <div className={fieldState.error ?
|
|
747
|
+
* <div className={fieldState.error ? "has-error" : ""}>
|
|
739
748
|
* {renderedField}
|
|
740
749
|
* </div>
|
|
741
750
|
* )}
|
|
@@ -1098,4 +1107,170 @@ declare function useInferredInputs(options: UseInferredInputsOptions): string[];
|
|
|
1098
1107
|
*/
|
|
1099
1108
|
declare function useSubscriptions(fieldName: string, subscriptions: string[]): void;
|
|
1100
1109
|
|
|
1101
|
-
|
|
1110
|
+
/**
|
|
1111
|
+
* Parameters for {@link useField}.
|
|
1112
|
+
*
|
|
1113
|
+
* Mirrors the subset of `FieldProps` (in `../components/Field`) that the hook
|
|
1114
|
+
* consumes as the single seam for **RHF Controller integration** (gap_analysis.md
|
|
1115
|
+
* **G6**; PRD §1.3.3).
|
|
1116
|
+
*
|
|
1117
|
+
* Per PRD §1.3.3, `hooks/useField` is the module that owns **RHF Controller
|
|
1118
|
+
* integration** for a field — "Uses Core: transform/pipeline, validation/
|
|
1119
|
+
* validate". This interface is the **input contract** for that module: it
|
|
1120
|
+
* carries the field identity + the overrides + the passthrough props threaded
|
|
1121
|
+
* into the parse/format pipeline (§5.3.5), change handler (§5.3.6), validation
|
|
1122
|
+
* wiring (§5.3.7), forwardRef delivery (§20.1), and template/host rendering
|
|
1123
|
+
* (§5.3.8).
|
|
1124
|
+
*
|
|
1125
|
+
* Generic over `TName` (default `string`) so a narrowed name type from
|
|
1126
|
+
* `FieldProps<TName>` can be threaded straight through (PRD §C.4 / T2.1). The
|
|
1127
|
+
* default keeps a bare `UseFieldParams` identical to `UseFieldParams<string>`.
|
|
1128
|
+
*
|
|
1129
|
+
* @template TName - Field name literal/union type (default `string`).
|
|
1130
|
+
*/
|
|
1131
|
+
interface UseFieldParams<TName extends string = string> {
|
|
1132
|
+
/**
|
|
1133
|
+
* Field name (must match a key in Form's config). When `TName` is narrowed,
|
|
1134
|
+
* this is checked against `TName` at compile time.
|
|
1135
|
+
*/
|
|
1136
|
+
name: TName;
|
|
1137
|
+
/** Override the input type resolved from config. */
|
|
1138
|
+
type?: string;
|
|
1139
|
+
/**
|
|
1140
|
+
* Override disabled state. Highest priority in the §5.3.4 disabled
|
|
1141
|
+
* resolution (prop > condition > config).
|
|
1142
|
+
*/
|
|
1143
|
+
disabled?: boolean;
|
|
1144
|
+
/** Override hidden state (inverse of visible). */
|
|
1145
|
+
hidden?: boolean;
|
|
1146
|
+
/**
|
|
1147
|
+
* Whether to register this field in Form's field registry (default `true`).
|
|
1148
|
+
*
|
|
1149
|
+
* NOTE: the registration `useEffect` itself is OWNED by the `<Field>`
|
|
1150
|
+
* component (contract bullet b) — this flag is threaded through so a future
|
|
1151
|
+
* direct-hook consumer can opt out. `useField` does NOT call
|
|
1152
|
+
* `registerField`/`unregisterField`.
|
|
1153
|
+
*/
|
|
1154
|
+
shouldRegister?: boolean;
|
|
1155
|
+
/**
|
|
1156
|
+
* Per-field input-config override. Merged at the highest-priority layer over
|
|
1157
|
+
* provider/form config (e.g. per-field debounce). See `resolveInputConfig`.
|
|
1158
|
+
*/
|
|
1159
|
+
inputConfig?: Partial<InputConfig>;
|
|
1160
|
+
/**
|
|
1161
|
+
* Optional render-prop. Applied INSIDE the hook's Controller render callback
|
|
1162
|
+
* (§5.3.8) against {@link UseFieldReturn}, so the render-prop always sees the
|
|
1163
|
+
* live `fieldState`/`formState`/`fieldProps` values.
|
|
1164
|
+
*/
|
|
1165
|
+
children?: ReactNode | ((api: UseFieldReturn) => ReactNode);
|
|
1166
|
+
/**
|
|
1167
|
+
* Additional props forwarded to the input component (the 8-layer
|
|
1168
|
+
* `componentProps` merge layer, §5.3.2). Captured by `mergeFieldProps`.
|
|
1169
|
+
*/
|
|
1170
|
+
[key: string]: unknown;
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Return value of {@link useField}.
|
|
1174
|
+
*
|
|
1175
|
+
* **Structurally identical** to `FieldRenderAPI` (in `../components/Field`) —
|
|
1176
|
+
* the data the render layer (template / host / render-prop `children`) consumes.
|
|
1177
|
+
* This equivalence is enforced bidirectionally by
|
|
1178
|
+
* `__typechecks__/useField.test-d.ts`.
|
|
1179
|
+
*
|
|
1180
|
+
* Per PRD §1.3.3, the hook owns the RHF Controller integration, so the fields
|
|
1181
|
+
* below are exactly the Controller-produced state the render layer needs:
|
|
1182
|
+
* `fieldState` + `formState` come from the Controller render callback;
|
|
1183
|
+
* `renderedField` is the RAW rendered input (template/component/host) when
|
|
1184
|
+
* visible, `null` when hidden (the Controller mounts ONLY when visible, so
|
|
1185
|
+
* hidden fields are not RHF-registered — §5.3.8 / §20);
|
|
1186
|
+
* `fieldProps` is the final 8-layer-merged props (§5.3.2, via `mergeFieldProps`);
|
|
1187
|
+
* `watchers` is the map of fields currently watching this field (§5.3.1 step 3).
|
|
1188
|
+
*/
|
|
1189
|
+
interface UseFieldReturn {
|
|
1190
|
+
/**
|
|
1191
|
+
* RHF field state from the Controller render callback
|
|
1192
|
+
* (`invalid` / `isTouched` / `isDirty` / `error` / …).
|
|
1193
|
+
*/
|
|
1194
|
+
fieldState: ControllerFieldState;
|
|
1195
|
+
/**
|
|
1196
|
+
* The RAW rendered input (template / component / host element), or `null`
|
|
1197
|
+
* when the field is not visible. The render-prop `children` is applied
|
|
1198
|
+
* INSIDE the Controller render callback that produces this element.
|
|
1199
|
+
*/
|
|
1200
|
+
renderedField: ReactNode;
|
|
1201
|
+
/**
|
|
1202
|
+
* Final merged props passed to the input — the output of the 8-layer
|
|
1203
|
+
* `mergeFieldProps` pipeline (§5.3.2).
|
|
1204
|
+
*/
|
|
1205
|
+
fieldProps: Record<string, unknown>;
|
|
1206
|
+
/**
|
|
1207
|
+
* Map of fields watching this field (`true` = currently subscribed). Populated
|
|
1208
|
+
* via the watcher-setter registered with `useSubscriptions` (§5.3.1 step 3).
|
|
1209
|
+
*/
|
|
1210
|
+
watchers: Record<string, boolean>;
|
|
1211
|
+
/**
|
|
1212
|
+
* RHF form state from the Controller. Typed as `UseFormStateReturn<FieldValues>`
|
|
1213
|
+
* to match `FieldRenderAPI` exactly (NOT bare `UseFormStateReturn`).
|
|
1214
|
+
*/
|
|
1215
|
+
formState: UseFormStateReturn<FieldValues>;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* useField — RHF Controller integration for a single Field (PRD §1.3.3 / §20).
|
|
1219
|
+
*
|
|
1220
|
+
* This hook owns the **entire** Controller lifecycle for one field: it reads
|
|
1221
|
+
* the form/config/group contexts, resolves the input config, sets up watchers
|
|
1222
|
+
* + subscriptions + conditions, builds the setValue effect (ref pattern,
|
|
1223
|
+
* §7.1.1), resolves the disabled/visible/label state, builds the validation
|
|
1224
|
+
* rules + change handler, mounts the `<Controller>` element with its full
|
|
1225
|
+
* render callback (format → state injection → `mergeFieldProps` 8-layer merge →
|
|
1226
|
+
* forwardRef delivery §20.1/§20.4 → template/host/component → render-prop
|
|
1227
|
+
* `children`), and returns the {@link UseFieldReturn}.
|
|
1228
|
+
*
|
|
1229
|
+
* ## Responsibilities (PRD §1.3.3 + §5.3.x + §20)
|
|
1230
|
+
*
|
|
1231
|
+
* - **Controller integration** — wraps RHF's `<Controller>` render callback.
|
|
1232
|
+
* The `<Controller>` mounts ONLY when the field is visible (the
|
|
1233
|
+
* `renderedField` is `null` when hidden), preserving the
|
|
1234
|
+
* conditionally-hidden-field invariant (hidden fields are NOT RHF-registered).
|
|
1235
|
+
* - **Parse/format pipeline (§5.3.5)** — Core `parse` / `format` from
|
|
1236
|
+
* `@formality-ui/core` (`transform/pipeline`).
|
|
1237
|
+
* - **Change handler (§5.3.6)** — `changeField` + watcher fan-out.
|
|
1238
|
+
* - **Validation (§5.3.7)** — Core `runValidator` / `resolveErrorMessage`
|
|
1239
|
+
* (`validation/validate`).
|
|
1240
|
+
* - **forwardRef delivery (§20.1)** — delivers the input's ref via the merged
|
|
1241
|
+
* props' `forwardRef` key (the CURRENT implemented behavior — NOT the legacy
|
|
1242
|
+
* `ref` key shown in the stale §5.3.2 pseudo-code). The host-element path
|
|
1243
|
+
* (§20.4 narrow exception) translates `forwardRef` back into React's special
|
|
1244
|
+
* `ref` key for string components.
|
|
1245
|
+
* - **Props merge (§5.3.2)** — `resolveInputConfig` + `resolveLabel` +
|
|
1246
|
+
* `mergeFieldProps` (the 8-layer merge).
|
|
1247
|
+
* - **Template/host render (§5.3.8)** — produces the RAW `renderedField`.
|
|
1248
|
+
*
|
|
1249
|
+
* It consumes these context hooks (`useFormContext`, `useConfigContext`,
|
|
1250
|
+
* `useGroupContext`) and composed hooks (`useConditions`, `usePropsEvaluation`,
|
|
1251
|
+
* `useInferredInputs`, `useSubscriptions`), and the Core fns above.
|
|
1252
|
+
*
|
|
1253
|
+
* NOTE: field **registration** (`registerField`/`unregisterField`) is OWNED by
|
|
1254
|
+
* the `<Field>` component (contract bullet b), NOT this hook. The hook reads
|
|
1255
|
+
* `useFormContext()` for everything else (config, methods, changeField,
|
|
1256
|
+
* setFieldValidating, register/unregisterWatcherSetter).
|
|
1257
|
+
*
|
|
1258
|
+
* @param params - Field parameters (see {@link UseFieldParams}).
|
|
1259
|
+
* @returns The Controller-produced state + the RAW rendered input
|
|
1260
|
+
* (see {@link UseFieldReturn}).
|
|
1261
|
+
*
|
|
1262
|
+
* @example
|
|
1263
|
+
* ```tsx
|
|
1264
|
+
* // Direct hook usage (custom layout):
|
|
1265
|
+
* function MyField() {
|
|
1266
|
+
* const { renderedField, fieldState } = useField({ name: "email" });
|
|
1267
|
+
* return <div className={fieldState.error ? "has-error" : ""}>{renderedField}</div>;
|
|
1268
|
+
* }
|
|
1269
|
+
*
|
|
1270
|
+
* // Equivalent via the thin wrapper component:
|
|
1271
|
+
* <Field name="email" />;
|
|
1272
|
+
* ```
|
|
1273
|
+
*/
|
|
1274
|
+
declare function useField<TName extends string = string>({ name, type: typeProp, disabled: disabledProp, hidden: hiddenProp, children, inputConfig: inputConfigProp, ...restProps }: UseFieldParams<TName>): UseFieldReturn;
|
|
1275
|
+
|
|
1276
|
+
export { ConfigContext, type ConfigContextValue, type CustomFieldState, type DebouncedFunction, type ExtendedFormState, Field, FieldGroup, type FieldGroupProps, type FieldProps, type FieldRenderAPI, Form, FormContext, type FormContextValue, type FormProps, type FormRenderAPI, type FormalityFieldComponentProps, FormalityProvider, type FormalityProviderProps, GroupContext, type GroupContextValue, type GroupState, type InputTemplateProps, type ReactFieldConfig, type ReactFormFieldsConfig, type ReactInputConfig, UnusedFields, type UnusedFieldsProps, type UseFieldParams, type UseFieldReturn, type UseFormStateOptions, type WatcherSetterFn, defineInputs, makeDeepProxyState, makeProxyState, useConditions, useConfigContext, useField, useFormContext, useFormState, useGroupContext, useInferredInputs, usePropsEvaluation, useSubscriptions };
|