@uniform-ts/core 0.0.7 → 0.0.9

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/README.md CHANGED
@@ -45,7 +45,9 @@ UniForm introspects the schema, renders appropriate inputs, validates with Zod,
45
45
 
46
46
  **`createAutoForm(defaults)`** — factory that bakes in your design system defaults (components, classNames, fieldWrapper) once, so you don't repeat them on every form.
47
47
 
48
- **`components`** — a registry mapping Zod types (`string`, `number`, `boolean`, etc.) to your own input components. Pass a component directly on a field via `fields` for one-off overrides.
48
+ **`useArrayField(fieldName)`** — a React hook for external array controls (toolbars, section headers, sticky footers) inside the `<AutoForm>` tree. It returns `append/remove/move/...` from `useFieldArray` plus `rowCount`, `canAdd`, and `atMin` derived from the array's `minItems`/`maxItems`.
49
+
50
+ **`components`** — a registry mapping Zod types (`string`, `number`, `boolean`, etc.) to your own input components. Pass a component directly on a field via `fields` for one-off overrides. For custom components, type field values precisely with `FieldProps<Value>` (for example, `FieldProps<number>` for a rating widget).
49
51
 
50
52
  **`fields`** — per-field overrides using dot-notated paths. Control labels, descriptions, ordering, sections, conditions, and custom components without touching the schema.
51
53
 
@@ -64,27 +66,28 @@ UniForm introspects the schema, renders appropriate inputs, validates with Zod,
64
66
 
65
67
  ## Core Props
66
68
 
67
- | Prop | Type | Description |
68
- | --------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
69
- | `form` | `UniForm<TSchema>` | Schema + onChange handlers from `createForm()` |
70
- | `onSubmit` | `(values) => void \| Promise<void>` | Called with typed values after successful validation |
71
- | `defaultValues` | `Partial<...>` or `() => Promise<...>` | Initial values; async function shows `loadingFallback` |
72
- | `components` | `ComponentRegistry` | Map Zod types to your input components |
73
- | `fields` | `Record<string, FieldOverride>` | Per-field label, description, order, section, condition |
74
- | `fieldWrapper` | `React.ComponentType<FieldWrapperProps>` | Custom wrapper around every scalar field |
75
- | `layout` | `LayoutSlots` | Replace form/section/object/array wrappers, submit button, array rows |
76
- | `classNames` | `FormClassNames` | CSS classes for form, fields, labels, errors, fieldset/legend wrappers |
77
- | `ref` | `React.Ref<AutoFormHandle>` | Imperative `reset`, `submit`, `setValues`, `getValues` |
78
- | `persistKey` | `string` | Auto-save form state to `localStorage` under this key |
79
- | `labels` | `FormLabels` | Override built-in UI strings for i18n; import a ready-made locale pack from `@uniform-ts/core/locales/{en,he,es}` |
69
+ | Prop | Type | Description |
70
+ | --------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
71
+ | `form` | `UniForm<TSchema>` | Schema + onChange handlers from `createForm()` |
72
+ | `onSubmit` | `(values) => void \| Promise<void>` | Called with typed values after successful validation |
73
+ | `defaultValues` | `Partial<...>` or `() => Promise<...>` | Initial values; async function shows `loadingFallback` |
74
+ | `components` | `ComponentRegistry` | Map Zod types to your input components |
75
+ | `fields` | `Record<string, FieldOverride>` | Per-field label, description, order, section, condition |
76
+ | `fieldWrapper` | `React.ComponentType<FieldWrapperProps>` | Custom wrapper around every scalar field |
77
+ | `layout` | `LayoutSlots` | Replace form/section/object/array wrappers, submit button, array rows. Set `null` on omittable slots (submit/array buttons) to hide them |
78
+ | `classNames` | `FormClassNames` | CSS classes for form, fields, labels, errors, fieldset/legend wrappers |
79
+ | `ref` | `React.Ref<AutoFormHandle>` | Imperative `reset`, `submit`, `setValues`, `getValues` |
80
+ | `persistKey` | `string` | Auto-save form state to `localStorage` under this key |
81
+ | `labels` | `FormLabels` | Override built-in UI strings for i18n; import a ready-made locale pack from `@uniform-ts/core/locales/{en,he,es}` |
80
82
 
81
83
  ## Features
82
84
 
83
85
  - **Full Zod V4 support** — scalars, enums, objects, arrays, optionals, defaults, unions, discriminated unions
84
86
  - **react-hook-form** under the hood — performant, uncontrolled forms with `zodResolver`
85
87
  - **Section grouping** — group fields into named sections via `meta.section`
86
- - **Conditional fields** — show/hide fields based on form values; hidden fields reset to default
87
- - **Array fields** — movable, duplicable, collapsible rows; `minItems`/`maxItems` from Zod schema
88
+ - **Conditional fields** — show/hide fields based on form values; `hidden` and row-local sibling conditions work inside array rows too
89
+ - **Array fields** — movable, duplicable, collapsible rows; `minItems`/`maxItems` from Zod schema; per-row conditional fields
90
+ - **External array controls** — use `useArrayField('path.to.array')` to place Add/Remove controls outside the default array block while staying in sync with schema limits
88
91
  - **Programmatic control** — `reset()`, `submit()`, `setValues()`, `getValues()`, `setErrors()`, `focus()` via ref
89
92
  - **Form persistence** — auto-save to `localStorage` (or custom storage) with configurable debounce
90
93
  - **Pluggable coercion** — automatic `string → number`, `string → Date` with customizable coercion map
@@ -27,14 +27,45 @@ type ArrayItem<T> = T extends (infer U)[] ? U : never;
27
27
  type DeepKeys<T> = T extends object ? {
28
28
  [K in keyof T & string]: T[K] extends unknown[] ? ArrayItem<T[K]> extends object ? K | `${K}.${DeepKeys<ArrayItem<T[K]>>}` : K : T[K] extends object ? K | `${K}.${DeepKeys<T[K]>}` : K;
29
29
  }[keyof T & string] : never;
30
+ /**
31
+ * Extends `DeepKeys` with index-based array paths like `"items.0.name"`.
32
+ * Used by `setOnChange` to allow registering handlers for specific rows.
33
+ *
34
+ * @example
35
+ * // Given { tasks: { priority: string; notes: string }[] }
36
+ * // DeepKeysIndexed adds: "tasks.0.priority" | "tasks.0.notes" | "tasks.1.priority" | ...
37
+ * // (represented as template literal `tasks.${number}.priority`)
38
+ */
39
+ type DeepKeysIndexed<T> = T extends object ? {
40
+ [K in keyof T & string]: T[K] extends unknown[] ? ArrayItem<T[K]> extends object ? K | `${K}.${DeepKeys<ArrayItem<T[K]>>}` | `${K}.${number}.${DeepKeys<ArrayItem<T[K]>>}` : K : T[K] extends object ? K | `${K}.${DeepKeysIndexed<T[K]>}` : K;
41
+ }[keyof T & string] : never;
30
42
  /**
31
43
  * Resolves the value type at a dot-notated path within a type `T`.
32
44
  * Array fields use the unprefixed child path (matching `DeepKeys` convention).
45
+ * Also supports indexed paths like `"items.0.qty"` — the numeric segment is skipped.
33
46
  *
34
47
  * @example
35
48
  * // DeepFieldValue<{ name: string; items: { qty: number }[] }, 'items.qty'> → number
49
+ * // DeepFieldValue<{ items: { qty: number }[] }, 'items.0.qty'> → number
50
+ */
51
+ type DeepFieldValue<T, K extends string> = K extends keyof T ? T[K] : K extends `${infer Head}.${infer Tail}` ? Head extends keyof T ? T[Head] extends (infer Item)[] ? Tail extends `${number}.${infer Rest}` ? DeepFieldValue<NonNullable<Item>, Rest> : DeepFieldValue<NonNullable<Item>, Tail> : DeepFieldValue<NonNullable<T[Head]>, Tail> : unknown : unknown;
52
+ /**
53
+ * Resolves the values type that a `setCondition` predicate receives for a
54
+ * given field key `K` within form values type `TValues`.
55
+ *
56
+ * - **Array item fields** (e.g. `"tasks.note"`): the predicate receives the
57
+ * array item type (`{ title, priority, note }`), enabling row-local sibling
58
+ * conditions like `(row) => row.priority === 'high'`.
59
+ * - **All other fields**: the predicate receives the full form values type.
60
+ *
61
+ * @example
62
+ * // ConditionValues<{ name: string; tasks: { priority: string; note: string }[] }, 'tasks.note'>
63
+ * // → { priority: string; note: string }
64
+ *
65
+ * // ConditionValues<{ name: string; address: { street: string } }, 'address.street'>
66
+ * // → { name: string; address: { street: string } } (full form — not an array)
36
67
  */
37
- type DeepFieldValue<T, K extends string> = K extends keyof T ? T[K] : K extends `${infer Head}.${infer Tail}` ? Head extends keyof T ? T[Head] extends (infer Item)[] ? DeepFieldValue<NonNullable<Item>, Tail> : DeepFieldValue<NonNullable<T[Head]>, Tail> : unknown : unknown;
68
+ type ConditionValues<TValues, K extends string> = K extends `${infer Head}.${string}` ? Head extends keyof TValues ? TValues[Head] extends readonly (infer Item)[] ? Item : TValues : TValues : TValues;
38
69
 
39
70
  /**
40
71
  * A single option entry used in `select` / enum fields.
@@ -140,8 +171,8 @@ interface ArrayRowLayoutProps {
140
171
  moveDown: React$1.ReactNode | null;
141
172
  /** Button to duplicate the row, or `null` if at max items. */
142
173
  duplicate: React$1.ReactNode | null;
143
- /** Button to remove the row. */
144
- remove: React$1.ReactNode;
174
+ /** Button to remove the row, or `null` when omitted via layout slot. */
175
+ remove: React$1.ReactNode | null;
145
176
  /** Button to collapse/expand the row, or `null` if collapsing is disabled. */
146
177
  collapse: React$1.ReactNode | null;
147
178
  };
@@ -195,6 +226,7 @@ interface SubmitButtonProps {
195
226
  isSubmitting: boolean;
196
227
  label: string;
197
228
  }
229
+ type OptionalSlotComponent<TProps> = React$1.ComponentType<TProps> | null;
198
230
  /**
199
231
  * Per-section styling overrides forwarded to the `sectionWrapper` component.
200
232
  * Keys are section titles; values control how that section wrapper is styled.
@@ -212,35 +244,35 @@ type SectionConfig = {
212
244
  * itself falls back to a plain `<button>`.
213
245
  */
214
246
  type ArrayButtonSlots = {
215
- /** Used for every array button that has no specific override. */
216
- base?: React$1.ComponentType<ArrayButtonProps>;
217
- /** Override for the add-row button only. */
218
- add?: React$1.ComponentType<ArrayButtonProps>;
219
- /** Override for the remove-row button only. */
220
- remove?: React$1.ComponentType<ArrayButtonProps>;
221
- /** Override for the move-up button only. */
222
- moveUp?: React$1.ComponentType<ArrayButtonProps>;
223
- /** Override for the move-down button only. */
224
- moveDown?: React$1.ComponentType<ArrayButtonProps>;
225
- /** Override for the duplicate-row button only. */
226
- duplicate?: React$1.ComponentType<ArrayButtonProps>;
247
+ /** Used for every array button that has no specific override. Set to `null` to omit all unspecified buttons. */
248
+ base?: OptionalSlotComponent<ArrayButtonProps>;
249
+ /** Override for the add-row button only. Set to `null` to omit. */
250
+ add?: OptionalSlotComponent<ArrayButtonProps>;
251
+ /** Override for the remove-row button only. Set to `null` to omit. */
252
+ remove?: OptionalSlotComponent<ArrayButtonProps>;
253
+ /** Override for the move-up button only. Set to `null` to omit. */
254
+ moveUp?: OptionalSlotComponent<ArrayButtonProps>;
255
+ /** Override for the move-down button only. Set to `null` to omit. */
256
+ moveDown?: OptionalSlotComponent<ArrayButtonProps>;
257
+ /** Override for the duplicate-row button only. Set to `null` to omit. */
258
+ duplicate?: OptionalSlotComponent<ArrayButtonProps>;
227
259
  /**
228
260
  * Override for the collapse/expand toggle button only.
229
261
  * Receives `isCollapsed` in addition to standard `ArrayButtonProps`.
230
262
  */
231
- collapse?: React$1.ComponentType<ArrayCollapseButtonProps>;
263
+ collapse?: OptionalSlotComponent<ArrayCollapseButtonProps>;
232
264
  };
233
265
  /**
234
266
  * Resolved button slots where every entry is guaranteed to be defined.
235
267
  */
236
268
  type ResolvedArrayButtonSlots = {
237
- base: React$1.ComponentType<ArrayButtonProps>;
238
- add: React$1.ComponentType<ArrayButtonProps>;
239
- remove: React$1.ComponentType<ArrayButtonProps>;
240
- moveUp: React$1.ComponentType<ArrayButtonProps>;
241
- moveDown: React$1.ComponentType<ArrayButtonProps>;
242
- duplicate: React$1.ComponentType<ArrayButtonProps>;
243
- collapse: React$1.ComponentType<ArrayCollapseButtonProps>;
269
+ base: OptionalSlotComponent<ArrayButtonProps>;
270
+ add: OptionalSlotComponent<ArrayButtonProps>;
271
+ remove: OptionalSlotComponent<ArrayButtonProps>;
272
+ moveUp: OptionalSlotComponent<ArrayButtonProps>;
273
+ moveDown: OptionalSlotComponent<ArrayButtonProps>;
274
+ duplicate: OptionalSlotComponent<ArrayButtonProps>;
275
+ collapse: OptionalSlotComponent<ArrayCollapseButtonProps>;
244
276
  };
245
277
  /**
246
278
  * Optional layout slot overrides for top-level structural components of the
@@ -252,8 +284,8 @@ type LayoutSlots = {
252
284
  formWrapper?: React$1.ComponentType<FormWrapperProps>;
253
285
  /** Wrapper rendered around each named field section. */
254
286
  sectionWrapper?: React$1.ComponentType<SectionWrapperProps>;
255
- /** Custom submit button component. */
256
- submitButton?: React$1.ComponentType<SubmitButtonProps>;
287
+ /** Custom submit button component. Set to `null` to omit rendering it. */
288
+ submitButton?: OptionalSlotComponent<SubmitButtonProps>;
257
289
  /** Custom layout component for individual rows in array fields. */
258
290
  arrayRowLayout?: React$1.ComponentType<ArrayRowLayoutProps>;
259
291
  /**
@@ -295,7 +327,7 @@ type LayoutSlots = {
295
327
  type ResolvedLayoutSlots = {
296
328
  formWrapper: React$1.ComponentType<FormWrapperProps>;
297
329
  sectionWrapper: React$1.ComponentType<SectionWrapperProps>;
298
- submitButton: React$1.ComponentType<SubmitButtonProps>;
330
+ submitButton: OptionalSlotComponent<SubmitButtonProps>;
299
331
  arrayRowLayout: React$1.ComponentType<ArrayRowLayoutProps>;
300
332
  arrayFieldLayout: React$1.ComponentType<ArrayFieldLayoutProps>;
301
333
  objectWrapper: React$1.ComponentType<ObjectWrapperProps>;
@@ -680,13 +712,13 @@ type FieldConfig = FieldConfigBase & ({
680
712
  * value, change/blur handlers, and all resolved UI metadata needed to render
681
713
  * a single field.
682
714
  */
683
- interface FieldProps {
715
+ interface FieldProps<Value = unknown> {
684
716
  /** Dot-notated field path (e.g. `"address.street"`). */
685
717
  name: string;
686
718
  /** The current field value. */
687
- value: unknown;
719
+ value: Value;
688
720
  /** Callback to update the field value. */
689
- onChange: (value: unknown) => void;
721
+ onChange: (value: Value) => void;
690
722
  /** Callback fired when the field loses focus. */
691
723
  onBlur: () => void;
692
724
  /** Ref callback for registering the DOM element with `react-hook-form`. */
@@ -727,4 +759,4 @@ type FieldOverride<TSchema extends z.$ZodObject = z.$ZodObject, TValue = unknown
727
759
  [key: string]: unknown;
728
760
  };
729
761
 
730
- export type { AutoFormProps as A, ComponentRegistry as C, DeepKeys as D, FieldMetaBase as F, LayoutSlots as L, ObjectWrapperProps as O, PersistStorage as P, ResolvedLayoutSlots as R, SectionConfig as S, ValidationMessages as V, FieldConfig as a, AutoFormHandle as b, FieldProps as c, FieldWrapperProps as d, ArrayButtonProps as e, ArrayCollapseButtonProps as f, ArrayFieldLayoutProps as g, ArrayRowLayoutProps as h, ArrayWrapperProps as i, AutoFormConfig as j, FormMethods as k, FieldDependencyResult as l, DeepFieldValue as m, FormClassNames as n, CoercionMap as o, FormLabels as p, ArrayButtonSlots as q, FieldCondition as r, FieldMeta as s, FieldOverride as t, FieldType as u, FormWrapperProps as v, ResolvedArrayButtonSlots as w, SectionWrapperProps as x, SelectOption as y, SubmitButtonProps as z };
762
+ export type { AutoFormProps as A, SelectOption as B, ComponentRegistry as C, DeepKeysIndexed as D, SubmitButtonProps as E, FieldMetaBase as F, LayoutSlots as L, ObjectWrapperProps as O, PersistStorage as P, ResolvedLayoutSlots as R, SectionConfig as S, ValidationMessages as V, FieldConfig as a, AutoFormHandle as b, FieldProps as c, FieldWrapperProps as d, ArrayButtonProps as e, ArrayCollapseButtonProps as f, ArrayFieldLayoutProps as g, ArrayRowLayoutProps as h, ArrayWrapperProps as i, AutoFormConfig as j, FormMethods as k, DeepKeys as l, FieldDependencyResult as m, DeepFieldValue as n, ConditionValues as o, FormClassNames as p, CoercionMap as q, FormLabels as r, ArrayButtonSlots as s, FieldCondition as t, FieldMeta as u, FieldOverride as v, FieldType as w, FormWrapperProps as x, ResolvedArrayButtonSlots as y, SectionWrapperProps as z };
@@ -27,14 +27,45 @@ type ArrayItem<T> = T extends (infer U)[] ? U : never;
27
27
  type DeepKeys<T> = T extends object ? {
28
28
  [K in keyof T & string]: T[K] extends unknown[] ? ArrayItem<T[K]> extends object ? K | `${K}.${DeepKeys<ArrayItem<T[K]>>}` : K : T[K] extends object ? K | `${K}.${DeepKeys<T[K]>}` : K;
29
29
  }[keyof T & string] : never;
30
+ /**
31
+ * Extends `DeepKeys` with index-based array paths like `"items.0.name"`.
32
+ * Used by `setOnChange` to allow registering handlers for specific rows.
33
+ *
34
+ * @example
35
+ * // Given { tasks: { priority: string; notes: string }[] }
36
+ * // DeepKeysIndexed adds: "tasks.0.priority" | "tasks.0.notes" | "tasks.1.priority" | ...
37
+ * // (represented as template literal `tasks.${number}.priority`)
38
+ */
39
+ type DeepKeysIndexed<T> = T extends object ? {
40
+ [K in keyof T & string]: T[K] extends unknown[] ? ArrayItem<T[K]> extends object ? K | `${K}.${DeepKeys<ArrayItem<T[K]>>}` | `${K}.${number}.${DeepKeys<ArrayItem<T[K]>>}` : K : T[K] extends object ? K | `${K}.${DeepKeysIndexed<T[K]>}` : K;
41
+ }[keyof T & string] : never;
30
42
  /**
31
43
  * Resolves the value type at a dot-notated path within a type `T`.
32
44
  * Array fields use the unprefixed child path (matching `DeepKeys` convention).
45
+ * Also supports indexed paths like `"items.0.qty"` — the numeric segment is skipped.
33
46
  *
34
47
  * @example
35
48
  * // DeepFieldValue<{ name: string; items: { qty: number }[] }, 'items.qty'> → number
49
+ * // DeepFieldValue<{ items: { qty: number }[] }, 'items.0.qty'> → number
50
+ */
51
+ type DeepFieldValue<T, K extends string> = K extends keyof T ? T[K] : K extends `${infer Head}.${infer Tail}` ? Head extends keyof T ? T[Head] extends (infer Item)[] ? Tail extends `${number}.${infer Rest}` ? DeepFieldValue<NonNullable<Item>, Rest> : DeepFieldValue<NonNullable<Item>, Tail> : DeepFieldValue<NonNullable<T[Head]>, Tail> : unknown : unknown;
52
+ /**
53
+ * Resolves the values type that a `setCondition` predicate receives for a
54
+ * given field key `K` within form values type `TValues`.
55
+ *
56
+ * - **Array item fields** (e.g. `"tasks.note"`): the predicate receives the
57
+ * array item type (`{ title, priority, note }`), enabling row-local sibling
58
+ * conditions like `(row) => row.priority === 'high'`.
59
+ * - **All other fields**: the predicate receives the full form values type.
60
+ *
61
+ * @example
62
+ * // ConditionValues<{ name: string; tasks: { priority: string; note: string }[] }, 'tasks.note'>
63
+ * // → { priority: string; note: string }
64
+ *
65
+ * // ConditionValues<{ name: string; address: { street: string } }, 'address.street'>
66
+ * // → { name: string; address: { street: string } } (full form — not an array)
36
67
  */
37
- type DeepFieldValue<T, K extends string> = K extends keyof T ? T[K] : K extends `${infer Head}.${infer Tail}` ? Head extends keyof T ? T[Head] extends (infer Item)[] ? DeepFieldValue<NonNullable<Item>, Tail> : DeepFieldValue<NonNullable<T[Head]>, Tail> : unknown : unknown;
68
+ type ConditionValues<TValues, K extends string> = K extends `${infer Head}.${string}` ? Head extends keyof TValues ? TValues[Head] extends readonly (infer Item)[] ? Item : TValues : TValues : TValues;
38
69
 
39
70
  /**
40
71
  * A single option entry used in `select` / enum fields.
@@ -140,8 +171,8 @@ interface ArrayRowLayoutProps {
140
171
  moveDown: React$1.ReactNode | null;
141
172
  /** Button to duplicate the row, or `null` if at max items. */
142
173
  duplicate: React$1.ReactNode | null;
143
- /** Button to remove the row. */
144
- remove: React$1.ReactNode;
174
+ /** Button to remove the row, or `null` when omitted via layout slot. */
175
+ remove: React$1.ReactNode | null;
145
176
  /** Button to collapse/expand the row, or `null` if collapsing is disabled. */
146
177
  collapse: React$1.ReactNode | null;
147
178
  };
@@ -195,6 +226,7 @@ interface SubmitButtonProps {
195
226
  isSubmitting: boolean;
196
227
  label: string;
197
228
  }
229
+ type OptionalSlotComponent<TProps> = React$1.ComponentType<TProps> | null;
198
230
  /**
199
231
  * Per-section styling overrides forwarded to the `sectionWrapper` component.
200
232
  * Keys are section titles; values control how that section wrapper is styled.
@@ -212,35 +244,35 @@ type SectionConfig = {
212
244
  * itself falls back to a plain `<button>`.
213
245
  */
214
246
  type ArrayButtonSlots = {
215
- /** Used for every array button that has no specific override. */
216
- base?: React$1.ComponentType<ArrayButtonProps>;
217
- /** Override for the add-row button only. */
218
- add?: React$1.ComponentType<ArrayButtonProps>;
219
- /** Override for the remove-row button only. */
220
- remove?: React$1.ComponentType<ArrayButtonProps>;
221
- /** Override for the move-up button only. */
222
- moveUp?: React$1.ComponentType<ArrayButtonProps>;
223
- /** Override for the move-down button only. */
224
- moveDown?: React$1.ComponentType<ArrayButtonProps>;
225
- /** Override for the duplicate-row button only. */
226
- duplicate?: React$1.ComponentType<ArrayButtonProps>;
247
+ /** Used for every array button that has no specific override. Set to `null` to omit all unspecified buttons. */
248
+ base?: OptionalSlotComponent<ArrayButtonProps>;
249
+ /** Override for the add-row button only. Set to `null` to omit. */
250
+ add?: OptionalSlotComponent<ArrayButtonProps>;
251
+ /** Override for the remove-row button only. Set to `null` to omit. */
252
+ remove?: OptionalSlotComponent<ArrayButtonProps>;
253
+ /** Override for the move-up button only. Set to `null` to omit. */
254
+ moveUp?: OptionalSlotComponent<ArrayButtonProps>;
255
+ /** Override for the move-down button only. Set to `null` to omit. */
256
+ moveDown?: OptionalSlotComponent<ArrayButtonProps>;
257
+ /** Override for the duplicate-row button only. Set to `null` to omit. */
258
+ duplicate?: OptionalSlotComponent<ArrayButtonProps>;
227
259
  /**
228
260
  * Override for the collapse/expand toggle button only.
229
261
  * Receives `isCollapsed` in addition to standard `ArrayButtonProps`.
230
262
  */
231
- collapse?: React$1.ComponentType<ArrayCollapseButtonProps>;
263
+ collapse?: OptionalSlotComponent<ArrayCollapseButtonProps>;
232
264
  };
233
265
  /**
234
266
  * Resolved button slots where every entry is guaranteed to be defined.
235
267
  */
236
268
  type ResolvedArrayButtonSlots = {
237
- base: React$1.ComponentType<ArrayButtonProps>;
238
- add: React$1.ComponentType<ArrayButtonProps>;
239
- remove: React$1.ComponentType<ArrayButtonProps>;
240
- moveUp: React$1.ComponentType<ArrayButtonProps>;
241
- moveDown: React$1.ComponentType<ArrayButtonProps>;
242
- duplicate: React$1.ComponentType<ArrayButtonProps>;
243
- collapse: React$1.ComponentType<ArrayCollapseButtonProps>;
269
+ base: OptionalSlotComponent<ArrayButtonProps>;
270
+ add: OptionalSlotComponent<ArrayButtonProps>;
271
+ remove: OptionalSlotComponent<ArrayButtonProps>;
272
+ moveUp: OptionalSlotComponent<ArrayButtonProps>;
273
+ moveDown: OptionalSlotComponent<ArrayButtonProps>;
274
+ duplicate: OptionalSlotComponent<ArrayButtonProps>;
275
+ collapse: OptionalSlotComponent<ArrayCollapseButtonProps>;
244
276
  };
245
277
  /**
246
278
  * Optional layout slot overrides for top-level structural components of the
@@ -252,8 +284,8 @@ type LayoutSlots = {
252
284
  formWrapper?: React$1.ComponentType<FormWrapperProps>;
253
285
  /** Wrapper rendered around each named field section. */
254
286
  sectionWrapper?: React$1.ComponentType<SectionWrapperProps>;
255
- /** Custom submit button component. */
256
- submitButton?: React$1.ComponentType<SubmitButtonProps>;
287
+ /** Custom submit button component. Set to `null` to omit rendering it. */
288
+ submitButton?: OptionalSlotComponent<SubmitButtonProps>;
257
289
  /** Custom layout component for individual rows in array fields. */
258
290
  arrayRowLayout?: React$1.ComponentType<ArrayRowLayoutProps>;
259
291
  /**
@@ -295,7 +327,7 @@ type LayoutSlots = {
295
327
  type ResolvedLayoutSlots = {
296
328
  formWrapper: React$1.ComponentType<FormWrapperProps>;
297
329
  sectionWrapper: React$1.ComponentType<SectionWrapperProps>;
298
- submitButton: React$1.ComponentType<SubmitButtonProps>;
330
+ submitButton: OptionalSlotComponent<SubmitButtonProps>;
299
331
  arrayRowLayout: React$1.ComponentType<ArrayRowLayoutProps>;
300
332
  arrayFieldLayout: React$1.ComponentType<ArrayFieldLayoutProps>;
301
333
  objectWrapper: React$1.ComponentType<ObjectWrapperProps>;
@@ -680,13 +712,13 @@ type FieldConfig = FieldConfigBase & ({
680
712
  * value, change/blur handlers, and all resolved UI metadata needed to render
681
713
  * a single field.
682
714
  */
683
- interface FieldProps {
715
+ interface FieldProps<Value = unknown> {
684
716
  /** Dot-notated field path (e.g. `"address.street"`). */
685
717
  name: string;
686
718
  /** The current field value. */
687
- value: unknown;
719
+ value: Value;
688
720
  /** Callback to update the field value. */
689
- onChange: (value: unknown) => void;
721
+ onChange: (value: Value) => void;
690
722
  /** Callback fired when the field loses focus. */
691
723
  onBlur: () => void;
692
724
  /** Ref callback for registering the DOM element with `react-hook-form`. */
@@ -727,4 +759,4 @@ type FieldOverride<TSchema extends z.$ZodObject = z.$ZodObject, TValue = unknown
727
759
  [key: string]: unknown;
728
760
  };
729
761
 
730
- export type { AutoFormProps as A, ComponentRegistry as C, DeepKeys as D, FieldMetaBase as F, LayoutSlots as L, ObjectWrapperProps as O, PersistStorage as P, ResolvedLayoutSlots as R, SectionConfig as S, ValidationMessages as V, FieldConfig as a, AutoFormHandle as b, FieldProps as c, FieldWrapperProps as d, ArrayButtonProps as e, ArrayCollapseButtonProps as f, ArrayFieldLayoutProps as g, ArrayRowLayoutProps as h, ArrayWrapperProps as i, AutoFormConfig as j, FormMethods as k, FieldDependencyResult as l, DeepFieldValue as m, FormClassNames as n, CoercionMap as o, FormLabels as p, ArrayButtonSlots as q, FieldCondition as r, FieldMeta as s, FieldOverride as t, FieldType as u, FormWrapperProps as v, ResolvedArrayButtonSlots as w, SectionWrapperProps as x, SelectOption as y, SubmitButtonProps as z };
762
+ export type { AutoFormProps as A, SelectOption as B, ComponentRegistry as C, DeepKeysIndexed as D, SubmitButtonProps as E, FieldMetaBase as F, LayoutSlots as L, ObjectWrapperProps as O, PersistStorage as P, ResolvedLayoutSlots as R, SectionConfig as S, ValidationMessages as V, FieldConfig as a, AutoFormHandle as b, FieldProps as c, FieldWrapperProps as d, ArrayButtonProps as e, ArrayCollapseButtonProps as f, ArrayFieldLayoutProps as g, ArrayRowLayoutProps as h, ArrayWrapperProps as i, AutoFormConfig as j, FormMethods as k, DeepKeys as l, FieldDependencyResult as m, DeepFieldValue as n, ConditionValues as o, FormClassNames as p, CoercionMap as q, FormLabels as r, ArrayButtonSlots as s, FieldCondition as t, FieldMeta as u, FieldOverride as v, FieldType as w, FormWrapperProps as x, ResolvedArrayButtonSlots as y, SectionWrapperProps as z };
package/dist/index.d.mts CHANGED
@@ -1,8 +1,9 @@
1
- import { F as FieldMetaBase, a as FieldConfig, A as AutoFormProps, b as AutoFormHandle, c as FieldProps, d as FieldWrapperProps, e as ArrayButtonProps, f as ArrayCollapseButtonProps, g as ArrayFieldLayoutProps, h as ArrayRowLayoutProps, O as ObjectWrapperProps, i as ArrayWrapperProps, C as ComponentRegistry, j as AutoFormConfig, D as DeepKeys, k as FormMethods, l as FieldDependencyResult, m as DeepFieldValue, P as PersistStorage, R as ResolvedLayoutSlots, n as FormClassNames, o as CoercionMap$1, V as ValidationMessages, p as FormLabels } from './field-DPgaGkOL.mjs';
2
- export { q as ArrayButtonSlots, r as FieldCondition, s as FieldMeta, t as FieldOverride, u as FieldType, v as FormWrapperProps, L as LayoutSlots, w as ResolvedArrayButtonSlots, S as SectionConfig, x as SectionWrapperProps, y as SelectOption, z as SubmitButtonProps } from './field-DPgaGkOL.mjs';
1
+ import { F as FieldMetaBase, a as FieldConfig, A as AutoFormProps, b as AutoFormHandle, c as FieldProps, d as FieldWrapperProps, e as ArrayButtonProps, f as ArrayCollapseButtonProps, g as ArrayFieldLayoutProps, h as ArrayRowLayoutProps, O as ObjectWrapperProps, i as ArrayWrapperProps, C as ComponentRegistry, j as AutoFormConfig, D as DeepKeysIndexed, k as FormMethods, l as DeepKeys, m as FieldDependencyResult, n as DeepFieldValue, o as ConditionValues, P as PersistStorage, R as ResolvedLayoutSlots, p as FormClassNames, q as CoercionMap$1, V as ValidationMessages, r as FormLabels } from './field-Cogi7eQc.mjs';
2
+ export { s as ArrayButtonSlots, t as FieldCondition, u as FieldMeta, v as FieldOverride, w as FieldType, x as FormWrapperProps, L as LayoutSlots, y as ResolvedArrayButtonSlots, S as SectionConfig, z as SectionWrapperProps, B as SelectOption, E as SubmitButtonProps } from './field-Cogi7eQc.mjs';
3
3
  import * as z from 'zod/v4/core';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
  import * as React from 'react';
6
+ import * as react_hook_form from 'react-hook-form';
6
7
  import { Control } from 'react-hook-form';
7
8
 
8
9
  // zod@3.25+ — import from 'zod/v4'
@@ -192,7 +193,7 @@ type UniFormContext<TSchema extends z.$ZodObject = z.$ZodObject> = FormMethods<z
192
193
  setFieldMeta: <K extends DeepKeys<z.infer<TSchema>>>(field: K, meta: Partial<FieldDependencyResult>) => void;
193
194
  };
194
195
  type Handler<TSchema extends z.$ZodObject, TValue> = (value: TValue, ctx: UniFormContext<TSchema>) => void | Promise<void>;
195
- type Condition<TSchema extends z.$ZodObject> = (values: z.infer<TSchema>) => boolean;
196
+ type Condition = (values: unknown) => boolean;
196
197
  /**
197
198
  * A type-safe form definition that lives outside React components.
198
199
  * Wraps a Zod schema and lets you attach typed `setOnChange` callbacks that fire
@@ -226,22 +227,26 @@ declare class UniForm<TSchema extends z.$ZodObject, TRegistered extends string =
226
227
  * Replaces any previously registered handler for that field — only one
227
228
  * handler per field is kept. This prevents accidental handler accumulation
228
229
  * when called inside a React render cycle.
230
+ *
231
+ * Supports both generic array paths (`"tasks.priority"` — fires for all rows)
232
+ * and indexed paths (`"tasks.0.priority"` — fires only for row 0).
233
+ *
229
234
  * Returns `this` for fluent chaining.
230
235
  */
231
- setOnChange<K extends Exclude<DeepKeys<z.infer<TSchema>>, TRegistered>>(field: K, handler: Handler<TSchema, DeepFieldValue<z.infer<TSchema>, K>>): UniForm<TSchema, TRegistered | K>;
236
+ setOnChange<K extends Exclude<DeepKeysIndexed<z.infer<TSchema>>, TRegistered>>(field: K, handler: Handler<TSchema, DeepFieldValue<z.infer<TSchema>, K>>): UniForm<TSchema, TRegistered | K>;
232
237
  /**
233
238
  * Attach a typed condition for a specific field.
234
239
  * The field is shown when the predicate returns `true`, hidden when `false`.
235
240
  * Composes with any `condition` set via the `fields` prop (UniForm takes precedence).
236
241
  * Returns `this` for fluent chaining.
237
242
  */
238
- setCondition<K extends DeepKeys<z.infer<TSchema>>>(field: K, predicate: Condition<TSchema>): this;
243
+ setCondition<K extends DeepKeys<z.infer<TSchema>>>(field: K, predicate: (values: ConditionValues<z.infer<TSchema>, K>) => boolean): this;
239
244
  /** @internal Called by AutoForm to fire the handler registered for a field. */
240
245
  _fireHandler(field: string, value: unknown, ctx: UniFormContext<TSchema>): void | Promise<void>;
241
246
  /** @internal Returns all field names that have registered onChange handlers. */
242
247
  _getWatchedFields(): string[];
243
248
  /** @internal Returns a copy of the conditions map for AutoForm to inject into field meta. */
244
- _getConditions(): Map<string, Condition<TSchema>>;
249
+ _getConditions(): Map<string, Condition>;
245
250
  }
246
251
  /**
247
252
  * Creates a new `UniForm` instance for the given Zod object schema.
@@ -278,9 +283,12 @@ declare function createForm(schema: z.$ZodDiscriminatedUnion): UniForm<z.$ZodObj
278
283
  *
279
284
  * @param fields - The full list of field configs to filter and sort.
280
285
  * @param control - The RHF `control` object from the parent form.
286
+ * @param scopeName - When provided, conditions receive only the values at this
287
+ * path (e.g. `"tasks.0"` for an array row) rather than the full form values.
288
+ * This enables row-local sibling conditions inside arrays.
281
289
  * @returns The filtered and ordered subset of `fields`.
282
290
  */
283
- declare function useConditionalFields(fields: FieldConfig[], control: Control): FieldConfig[];
291
+ declare function useConditionalFields(fields: FieldConfig[], control: Control, scopeName?: string): FieldConfig[];
284
292
 
285
293
  type SectionGroup = {
286
294
  title: string | null;
@@ -324,8 +332,44 @@ declare function useFormPersistence(options: {
324
332
  clearPersistedData: () => void;
325
333
  };
326
334
 
335
+ /**
336
+ * Access the operations and reactive state of a named array field from
337
+ * anywhere inside an `<AutoForm>` tree.
338
+ *
339
+ * Useful for rendering action buttons (e.g. "Add Row") outside the array
340
+ * field's own wrapper — in a toolbar, section header, or custom form layout.
341
+ * `minItems` / `maxItems` are derived automatically from the Zod schema.
342
+ *
343
+ * @param fieldName - Dot-notated path to the array field (e.g. `"lineItems"`).
344
+ *
345
+ * @example
346
+ * function AddRowButton() {
347
+ * const { append, canAdd, rowCount } = useArrayField('lineItems')
348
+ * return (
349
+ * <button disabled={!canAdd} onClick={() => append({})}>
350
+ * Add Item ({rowCount})
351
+ * </button>
352
+ * )
353
+ * }
354
+ */
355
+ declare function useArrayField(fieldName: string): {
356
+ rowCount: number;
357
+ canAdd: boolean;
358
+ atMin: boolean;
359
+ swap: react_hook_form.UseFieldArraySwap;
360
+ move: react_hook_form.UseFieldArrayMove;
361
+ prepend: react_hook_form.UseFieldArrayPrepend<react_hook_form.FieldValues, never>;
362
+ append: react_hook_form.UseFieldArrayAppend<react_hook_form.FieldValues, never>;
363
+ remove: react_hook_form.UseFieldArrayRemove;
364
+ insert: react_hook_form.UseFieldArrayInsert<react_hook_form.FieldValues, never>;
365
+ update: react_hook_form.UseFieldArrayUpdate<react_hook_form.FieldValues, never>;
366
+ replace: react_hook_form.UseFieldArrayReplace<react_hook_form.FieldValues, never>;
367
+ fields: Record<"id", string>[];
368
+ };
369
+
327
370
  type AutoFormContextValue = {
328
371
  registry: ComponentRegistry;
372
+ fieldConfigs: FieldConfig[];
329
373
  fieldOverrides: Record<string, unknown>;
330
374
  fieldWrapper: React.ComponentType<FieldWrapperProps>;
331
375
  layout: ResolvedLayoutSlots;
@@ -335,7 +379,9 @@ type AutoFormContextValue = {
335
379
  messages?: ValidationMessages;
336
380
  labels: FormLabels;
337
381
  formMethods: FormMethods;
382
+ control: Control;
383
+ setDynamicMeta: React.Dispatch<React.SetStateAction<Record<string, Partial<FieldDependencyResult>>>>;
338
384
  };
339
385
  declare function useAutoFormContext(): AutoFormContextValue;
340
386
 
341
- export { ArrayButtonProps, ArrayCollapseButtonProps, ArrayFieldLayoutProps, ArrayRowLayoutProps, ArrayWrapperProps, AutoForm, AutoFormConfig, type AutoFormContextValue, AutoFormHandle, AutoFormProps, CoercionMap$1 as CoercionMap, ComponentRegistry, DefaultArrayButton, DefaultArrayCollapseButton, DefaultArrayFieldLayout, DefaultArrayRowLayout, DefaultArrayWrapper, DefaultCheckbox, DefaultFieldWrapper, DefaultInput, DefaultObjectWrapper, DefaultSelect, DefaultSubmitButton, FieldConfig, FieldDependencyResult, FieldMetaBase, FieldProps, FieldRenderer, FieldWrapperProps, FormClassNames, FormLabels, FormMethods, ObjectWrapperProps, PersistStorage, ResolvedLayoutSlots, type SectionGroup, UniForm, type UniFormContext, ValidationMessages, coerceValue, createAutoForm, createForm, defaultCoercionMap, defaultRegistry, introspectObjectSchema, introspectSchema, mergeRegistries, useAutoFormContext, useConditionalFields, useFormPersistence, useSectionGrouping };
387
+ export { ArrayButtonProps, ArrayCollapseButtonProps, ArrayFieldLayoutProps, ArrayRowLayoutProps, ArrayWrapperProps, AutoForm, AutoFormConfig, type AutoFormContextValue, AutoFormHandle, AutoFormProps, CoercionMap$1 as CoercionMap, ComponentRegistry, DefaultArrayButton, DefaultArrayCollapseButton, DefaultArrayFieldLayout, DefaultArrayRowLayout, DefaultArrayWrapper, DefaultCheckbox, DefaultFieldWrapper, DefaultInput, DefaultObjectWrapper, DefaultSelect, DefaultSubmitButton, FieldConfig, FieldDependencyResult, FieldMetaBase, FieldProps, FieldRenderer, FieldWrapperProps, FormClassNames, FormLabels, FormMethods, ObjectWrapperProps, PersistStorage, ResolvedLayoutSlots, type SectionGroup, UniForm, type UniFormContext, ValidationMessages, coerceValue, createAutoForm, createForm, defaultCoercionMap, defaultRegistry, introspectObjectSchema, introspectSchema, mergeRegistries, useArrayField, useAutoFormContext, useConditionalFields, useFormPersistence, useSectionGrouping };