@classytic/formkit 1.3.1 → 1.5.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.d.mts CHANGED
@@ -8,7 +8,7 @@ import { ClassValue } from "clsx";
8
8
  * Field type identifier.
9
9
  * Can be built-in types or custom string identifiers.
10
10
  */
11
- type FieldType = "text" | "email" | "password" | "number" | "tel" | "url" | "textarea" | "select" | "checkbox" | "radio" | "switch" | "date" | "time" | "datetime" | "file" | "hidden" | "group" | "array" | "custom" | (string & {});
11
+ type FieldType = "text" | "email" | "password" | "number" | "tel" | "phone" | "url" | "textarea" | "slug" | "select" | "combobox" | "multiselect" | "dependentSelect" | "checkbox" | "radio" | "switch" | "date" | "time" | "datetime" | "rich-text" | "color" | "rating" | "tags" | "json" | "file" | "hidden" | "group" | "array" | "custom" | (string & {});
12
12
  /**
13
13
  * Layout type identifier.
14
14
  */
@@ -70,12 +70,55 @@ interface FieldOptionGroup<TValue = string> {
70
70
  /** Whether group is disabled */
71
71
  disabled?: boolean;
72
72
  }
73
+ /**
74
+ * Object form for length/range validation rules with a custom message.
75
+ */
76
+ interface ValidationRuleObject<TValue = number> {
77
+ value: TValue;
78
+ message: string;
79
+ }
80
+ /**
81
+ * Object form for pattern validation with a custom message.
82
+ */
83
+ interface PatternRuleObject {
84
+ /** Regex string (will be compiled with `new RegExp(regex)`) */
85
+ regex: string;
86
+ /** Custom error message */
87
+ message: string;
88
+ }
89
+ /**
90
+ * AI-friendly metadata attached to any field.
91
+ * Consumed by LLM coding assistants, documentation generators, and
92
+ * schema-introspection tools — never by the form renderer itself.
93
+ */
94
+ interface FieldMeta {
95
+ /** Human-readable description of what this field collects */
96
+ description?: string;
97
+ /** Representative example value (for documentation / AI context) */
98
+ example?: unknown;
99
+ /** Logical category for grouping fields in documentation or tooling */
100
+ category?: string;
101
+ /** Arbitrary tags for filtering, search, or feature-flagging */
102
+ tags?: string[];
103
+ /** Whether this field is considered PII */
104
+ pii?: boolean;
105
+ /** Arbitrary extension point — anything extra you want to carry */
106
+ [key: string]: unknown;
107
+ }
73
108
  /**
74
109
  * Base field configuration shared by all field types.
75
110
  * @template TFieldValues - Form field values type for type-safe field names
76
111
  */
77
112
  interface BaseField<TFieldValues extends FieldValues = FieldValues> {
78
- /** Field name (must be a valid path in form values) */
113
+ /**
114
+ * Field name — a path into TFieldValues (e.g. `"email"`, `"address.street"`).
115
+ *
116
+ * Accepts any string so that:
117
+ * - Namespaced sections can use relative names (`"street"` → prefixed to `"address.street"`)
118
+ * - Group/array `itemFields` can use relative names (`"email"` → prefixed at render time)
119
+ *
120
+ * Use `field.for<T>()` builder for call-site enforcement that names are valid paths.
121
+ */
79
122
  name: Path<TFieldValues> | (string & {});
80
123
  /** Field type identifier */
81
124
  type: FieldType;
@@ -95,6 +138,13 @@ interface BaseField<TFieldValues extends FieldValues = FieldValues> {
95
138
  variant?: Variant;
96
139
  /** Whether field should span full width in grid */
97
140
  fullWidth?: boolean;
141
+ /**
142
+ * Span N columns of the section grid (e.g. `2` of a 3-col section). `1` (or
143
+ * unset) is the default single cell; `fullWidth` still means "span the whole
144
+ * row" and takes precedence. Applied as an inline `grid-column: span N` so it
145
+ * works regardless of the host's Tailwind content scan.
146
+ */
147
+ colSpan?: number;
98
148
  /** Custom CSS class name */
99
149
  className?: string;
100
150
  /**
@@ -106,18 +156,41 @@ interface BaseField<TFieldValues extends FieldValues = FieldValues> {
106
156
  defaultValue?: unknown;
107
157
  /** Options for select/radio/checkbox fields */
108
158
  options?: (FieldOption | FieldOptionGroup)[];
109
- /** Minimum value (for number/date fields) */
110
- min?: number | string;
111
- /** Maximum value (for number/date fields) */
112
- max?: number | string;
159
+ /**
160
+ * Minimum value (for number/date fields).
161
+ * Pass an object `{ value, message }` to provide a custom error message.
162
+ */
163
+ min?: number | string | ValidationRuleObject<number | string>;
164
+ /**
165
+ * Maximum value (for number/date fields).
166
+ * Pass an object `{ value, message }` to provide a custom error message.
167
+ */
168
+ max?: number | string | ValidationRuleObject<number | string>;
113
169
  /** Step value (for number fields) */
114
170
  step?: number;
115
- /** Pattern for validation (regex string) */
116
- pattern?: string;
117
- /** Minimum length */
118
- minLength?: number;
119
- /** Maximum length */
120
- maxLength?: number;
171
+ /**
172
+ * Pattern for validation.
173
+ * Pass a regex string or `{ regex, message }` for a custom error message.
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * // simple
178
+ * pattern: "^[a-z]+$"
179
+ * // with custom message
180
+ * pattern: { regex: "^[a-z]+$", message: "Only lowercase letters allowed" }
181
+ * ```
182
+ */
183
+ pattern?: string | PatternRuleObject;
184
+ /**
185
+ * Minimum length.
186
+ * Pass an object `{ value, message }` to provide a custom error message.
187
+ */
188
+ minLength?: number | ValidationRuleObject<number>;
189
+ /**
190
+ * Maximum length.
191
+ * Pass an object `{ value, message }` to provide a custom error message.
192
+ */
193
+ maxLength?: number | ValidationRuleObject<number>;
121
194
  /** Number of rows (for textarea) */
122
195
  rows?: number;
123
196
  /** Multiple selection (for select/file) */
@@ -133,18 +206,41 @@ interface BaseField<TFieldValues extends FieldValues = FieldValues> {
133
206
  /**
134
207
  * Dynamic options loaded based on current form values.
135
208
  * Useful for dependent selects (e.g., state depends on country).
209
+ *
210
+ * Receives an `AbortSignal` in the second arg — forward it to `fetch` (or
211
+ * abort your own request on it) so a superseded / unmounted load is cancelled
212
+ * instead of racing to completion:
213
+ *
214
+ * ```ts
215
+ * loadOptions: (values, { signal }) =>
216
+ * fetch(`/api/cities?country=${values.country}`, { signal }).then(r => r.json())
217
+ * ```
136
218
  */
137
- loadOptions?: (formValues: Partial<TFieldValues>) => Promise<(FieldOption | FieldOptionGroup)[]> | (FieldOption | FieldOptionGroup)[];
219
+ loadOptions?: (formValues: Partial<TFieldValues>, options?: {
220
+ signal: AbortSignal;
221
+ }) => Promise<(FieldOption | FieldOptionGroup)[]> | (FieldOption | FieldOptionGroup)[];
138
222
  /**
139
223
  * Error callback for loadOptions failures.
140
224
  * Called when loadOptions rejects. Defaults to console.error.
141
225
  */
142
226
  onLoadError?: (error: unknown) => void;
143
227
  /**
144
- * Fields for array or object types.
145
- * Useful for 'array' or 'group' field types that need a sub-schema.
228
+ * Memoize async `loadOptions` results per set of watched values, so toggling
229
+ * a dependency back and forth (e.g. re-selecting a country) reuses the last
230
+ * fetch instead of hitting the API again. Off by default — enable only when
231
+ * the options are stable for a given input, since the cache lives for the
232
+ * field's lifetime and won't see server-side changes. Bounded (LRU-ish) so it
233
+ * can't grow without limit.
234
+ */
235
+ cacheOptions?: boolean;
236
+ /**
237
+ * Sub-fields for `group` and `array` field types.
238
+ *
239
+ * These fields use **relative** names (`"street"`, `"email"`) — FormGenerator
240
+ * prefixes them with the parent field name at render time (`"address.street"`).
241
+ * They are intentionally untyped to TFieldValues for this reason.
146
242
  */
147
- itemFields?: BaseField<TFieldValues>[];
243
+ itemFields?: BaseField[];
148
244
  /**
149
245
  * Custom render function to override the component registry for this specific field.
150
246
  * Completely bypasses the globally registered FieldComponent for this type.
@@ -153,15 +249,22 @@ interface BaseField<TFieldValues extends FieldValues = FieldValues> {
153
249
  /**
154
250
  * Cross-field validation function.
155
251
  * Receives the field value and all form values for cross-field checks.
156
- * Return `true` for valid, or a string error message for invalid.
252
+ * Return `true` for valid, a string error message for invalid, or a Promise of either
253
+ * for async validation (e.g., checking server-side uniqueness).
157
254
  *
158
255
  * @example
159
256
  * ```ts
160
257
  * validate: (value, formValues) =>
161
258
  * value > formValues.minPrice || "Must be greater than min price"
259
+ *
260
+ * // Async example
261
+ * validate: async (value) => {
262
+ * const taken = await checkUsernameAvailability(value as string);
263
+ * return taken ? "Username already taken" : true;
264
+ * }
162
265
  * ```
163
266
  */
164
- validate?: (value: unknown, formValues: Partial<TFieldValues>) => string | true;
267
+ validate?: (value: unknown, formValues: Partial<TFieldValues>) => string | true | Promise<string | true>;
165
268
  /**
166
269
  * Dependencies for optimizing conditionally rendered fields.
167
270
  * Allows specifying specific field names to watch, preventing full form re-renders.
@@ -169,6 +272,32 @@ interface BaseField<TFieldValues extends FieldValues = FieldValues> {
169
272
  watchNames?: Path<TFieldValues> | Path<TFieldValues>[];
170
273
  /** Additional field-specific props for custom components */
171
274
  customProps?: Record<string, unknown>;
275
+ /**
276
+ * AI / agent-friendly metadata for this field.
277
+ *
278
+ * Provides hints that help LLM coding assistants generate correct field
279
+ * configs without needing to inspect the schema at runtime.
280
+ *
281
+ * @example
282
+ * ```ts
283
+ * field.text("companyName", "Company", {
284
+ * meta: {
285
+ * description: "Legal entity name of the organization",
286
+ * example: "Acme Corp",
287
+ * category: "identity",
288
+ * tags: ["crm", "required-for-billing"],
289
+ * }
290
+ * })
291
+ * ```
292
+ */
293
+ meta?: FieldMeta;
294
+ /**
295
+ * Escape hatch for adapter-specific or custom props.
296
+ * Allows passing arbitrary props directly on the field object so they
297
+ * flow through the adapter spread (`{...field}`) without needing `customProps`.
298
+ * Intentionally broad to support diverse UI component libraries.
299
+ */
300
+ [key: string]: unknown;
172
301
  }
173
302
  /**
174
303
  * Props passed to field components.
@@ -214,13 +343,68 @@ interface FieldComponentProps<TFieldValues extends FieldValues = FieldValues> ex
214
343
  invalid: boolean;
215
344
  isDirty: boolean;
216
345
  isTouched: boolean;
217
- isValidating: boolean;
346
+ isValidating: boolean; /** True after the enclosing form's submit handler has been called at least once. */
347
+ isSubmitted: boolean;
218
348
  error?: FieldError;
219
349
  };
220
- /** Generated field ID for label-input association (e.g. `formkit-field-email`) */
350
+ /**
351
+ * Generated field ID for label-input association (e.g. `formkit-field-email`).
352
+ * Use as `id` on the input element and `htmlFor` on the `<label>`.
353
+ */
221
354
  fieldId: string;
355
+ /**
356
+ * Generated error container ID for ARIA association (e.g. `formkit-field-email-error`).
357
+ * Use as `id` on the error message element and as the value for `aria-errormessage`
358
+ * (preferred) or `aria-describedby` (broader support) on the input.
359
+ *
360
+ * @example
361
+ * ```tsx
362
+ * <input
363
+ * id={fieldId}
364
+ * aria-invalid={shouldShowError || undefined}
365
+ * aria-errormessage={shouldShowError ? errorId : undefined}
366
+ * />
367
+ * <p id={errorId} role="alert" aria-live="polite">
368
+ * {shouldShowError ? error?.message : null}
369
+ * </p>
370
+ * ```
371
+ */
372
+ errorId: string;
373
+ /**
374
+ * Whether to display the field error right now.
375
+ *
376
+ * Aligns with the CSS `:user-invalid` timing model: `true` only after the
377
+ * user has interacted with the field (blur) **or** the form has been
378
+ * submitted. This prevents premature "required" errors on untouched fields.
379
+ *
380
+ * Use this — not `!!error` — to drive `aria-invalid`, error message
381
+ * visibility, and destructive ring/border styles.
382
+ *
383
+ * @example
384
+ * ```tsx
385
+ * <input aria-invalid={shouldShowError || undefined} />
386
+ * {shouldShowError && <p id={errorId} role="alert">{error?.message}</p>}
387
+ * ```
388
+ */
389
+ shouldShowError: boolean;
222
390
  /** Whether dynamic options are currently loading */
223
391
  isLoading?: boolean;
392
+ /**
393
+ * Pre-computed react-hook-form validation rules for this field.
394
+ * Equivalent to calling `buildValidationRules(field)` — provided here so
395
+ * adapter components can pass `rules={rules}` directly to `<Controller>`
396
+ * without importing or calling `buildValidationRules` themselves.
397
+ *
398
+ * @example
399
+ * ```tsx
400
+ * function TextInput({ field, control, rules }: FieldComponentProps) {
401
+ * return (
402
+ * <Controller name={field.name} control={control} rules={rules} render={...} />
403
+ * );
404
+ * }
405
+ * ```
406
+ */
407
+ rules: ValidationRules;
224
408
  }
225
409
  /**
226
410
  * Field component type.
@@ -271,6 +455,17 @@ interface Section<TFieldValues extends FieldValues = FieldValues> {
271
455
  collapsible?: boolean;
272
456
  /** Default collapsed state */
273
457
  defaultCollapsed?: boolean;
458
+ /**
459
+ * Defer rendering of this section until it scrolls near the viewport.
460
+ * Applies `content-visibility: auto` + `contain-intrinsic-size` to the
461
+ * section container, skipping layout/paint work while off-screen.
462
+ *
463
+ * **Only use for sections that are below the initial fold.** Applying this
464
+ * to above-fold sections has no benefit and slightly increases overhead.
465
+ *
466
+ * @see https://developer.mozilla.org/en-US/docs/Web/CSS/content-visibility
467
+ */
468
+ deferRender?: boolean;
274
469
  }
275
470
  /**
276
471
  * Props passed to section render function.
@@ -298,6 +493,12 @@ interface SectionLayoutProps {
298
493
  collapsible?: boolean;
299
494
  /** Default collapsed state */
300
495
  defaultCollapsed?: boolean;
496
+ /**
497
+ * When true the section is below the initial fold and should defer
498
+ * browser layout/paint work until it nears the viewport.
499
+ * Layout components may apply `content-visibility: auto` here.
500
+ */
501
+ deferRender?: boolean;
301
502
  /** Children content */
302
503
  children: ReactNode;
303
504
  }
@@ -336,6 +537,12 @@ type LayoutComponent<TProps extends LayoutComponentProps = LayoutComponentProps>
336
537
  * @template TFieldValues - Form field values type for type-safe schemas
337
538
  */
338
539
  interface FormSchema<TFieldValues extends FieldValues = FieldValues> {
540
+ /** Optional schema identifier — useful for serialization, analytics, and AI context */
541
+ id?: string;
542
+ /** Human-readable form title (AI / documentation use) */
543
+ title?: string;
544
+ /** Human-readable description of the form's purpose */
545
+ description?: string;
339
546
  /** Form sections */
340
547
  sections: Section<TFieldValues>[];
341
548
  }
@@ -373,6 +580,8 @@ interface FormSystemContextValue {
373
580
  components: ComponentRegistry;
374
581
  /** Registered layout components */
375
582
  layouts: LayoutRegistry;
583
+ /** Notified when a field type has no registered component (see provider). */
584
+ onMissingComponent?: (type: string, variant?: string) => void;
376
585
  }
377
586
  /**
378
587
  * Form system provider props.
@@ -382,6 +591,13 @@ interface FormSystemProviderProps {
382
591
  components?: ComponentRegistry;
383
592
  /** Layout component registry */
384
593
  layouts?: LayoutRegistry;
594
+ /**
595
+ * Called (once per type) when a field's `type` has no registered component
596
+ * and the visible placeholder is shown instead. Use it to log/report the
597
+ * missing registration to your telemetry — the form still renders a
598
+ * placeholder rather than silently dropping the field.
599
+ */
600
+ onMissingComponent?: (type: string, variant?: string) => void;
385
601
  /** Children content */
386
602
  children: ReactNode;
387
603
  }
@@ -462,10 +678,12 @@ interface SectionRendererProps<TFieldValues extends FieldValues = FieldValues> {
462
678
  disabled?: boolean;
463
679
  variant?: Variant;
464
680
  }
681
+ declare function SectionRendererImpl<TFieldValues extends FieldValues = FieldValues>(props: SectionRendererProps<TFieldValues>): FormElement;
465
682
  /**
466
- * Renders a single section with its fields.
683
+ * Memoized section renderer. Re-renders only when section config or
684
+ * disabled/variant context changes — not on every form value change.
467
685
  */
468
- declare function SectionRenderer<TFieldValues extends FieldValues = FieldValues>(props: SectionRendererProps<TFieldValues>): FormElement;
686
+ declare const SectionRenderer: typeof SectionRendererImpl;
469
687
  interface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {
470
688
  fields?: BaseField<TFieldValues>[];
471
689
  cols?: number;
@@ -474,10 +692,7 @@ interface GridRendererProps<TFieldValues extends FieldValues = FieldValues> {
474
692
  disabled?: boolean;
475
693
  variant?: Variant;
476
694
  }
477
- /**
478
- * Renders a grid of fields with specified column layout.
479
- */
480
- declare function GridRenderer<TFieldValues extends FieldValues = FieldValues>({
695
+ declare function GridRendererImpl<TFieldValues extends FieldValues = FieldValues>({
481
696
  fields,
482
697
  cols,
483
698
  gap,
@@ -485,18 +700,22 @@ declare function GridRenderer<TFieldValues extends FieldValues = FieldValues>({
485
700
  disabled,
486
701
  variant
487
702
  }: GridRendererProps<TFieldValues>): FormElement;
703
+ /**
704
+ * Memoized grid renderer.
705
+ */
706
+ declare const GridRenderer: typeof GridRendererImpl;
488
707
  interface FieldWrapperProps<TFieldValues extends FieldValues = FieldValues> {
489
708
  field: BaseField<TFieldValues>;
490
709
  control?: Control<TFieldValues>;
491
710
  disabled?: boolean;
492
711
  variant?: Variant;
493
712
  }
713
+ declare function FieldWrapperImpl<TFieldValues extends FieldValues = FieldValues>(props: FieldWrapperProps<TFieldValues>): FormElement;
494
714
  /**
495
- * Wraps individual fields.
496
- * If the field requires conditional logic or dynamic options, it uses the Dynamic wrapper.
497
- * Otherwise, it uses the Static wrapper, vastly improving performance by skipping `useWatch`.
715
+ * Memoized field wrapper — the dispatch layer between schema fields and
716
+ * their renderer. Re-renders only when the field config itself changes.
498
717
  */
499
- declare function FieldWrapper<TFieldValues extends FieldValues = FieldValues>(props: FieldWrapperProps<TFieldValues>): FormElement;
718
+ declare const FieldWrapper: typeof FieldWrapperImpl;
500
719
  //#endregion
501
720
  //#region src/FormSystemContext.d.ts
502
721
  /**
@@ -535,6 +754,7 @@ declare function FieldWrapper<TFieldValues extends FieldValues = FieldValues>(pr
535
754
  declare function FormSystemProvider({
536
755
  components,
537
756
  layouts,
757
+ onMissingComponent,
538
758
  children
539
759
  }: FormSystemProviderProps): FormElement;
540
760
  /**
@@ -556,11 +776,16 @@ declare function useFormSystem(): FormSystemContextValue;
556
776
  * 1. Variant-specific component: `components[variant][type]`
557
777
  * 2. Type-specific component: `components[type]`
558
778
  * 3. Default component: `components["default"]`
559
- * 4. Text fallback: `components["text"]`
779
+ *
780
+ * There is intentionally NO "text" fallback: rendering a text input for an
781
+ * unregistered `date`/`switch`/etc. is a silent wrong-widget bug. A missing
782
+ * registration instead renders a visible placeholder (`MissingFieldComponent`)
783
+ * — in production too — and notifies `onMissingComponent`, so the gap is
784
+ * observable rather than a mystery empty box.
560
785
  *
561
786
  * @param type - Field type identifier
562
787
  * @param variant - Optional variant name
563
- * @returns Field component or fallback
788
+ * @returns Field component or the visible placeholder
564
789
  *
565
790
  * @internal
566
791
  */
@@ -645,6 +870,7 @@ declare function defineSection<TFieldValues extends FieldValues = FieldValues>(s
645
870
  /**
646
871
  * Extracts default values from a form schema.
647
872
  * Walks all sections and fields, respecting nameSpace prefixes and group nesting.
873
+ * Array fields default to `[]` when no explicit `defaultValue` is provided.
648
874
  *
649
875
  * @example
650
876
  * ```ts
@@ -653,11 +879,78 @@ declare function defineSection<TFieldValues extends FieldValues = FieldValues>(s
653
879
  * ```
654
880
  */
655
881
  declare function extractDefaultValues<TFieldValues extends FieldValues = FieldValues>(schema: FormSchema<TFieldValues>): Partial<TFieldValues>;
882
+ interface SchemaIssue {
883
+ /** Where the issue is, e.g. `sections[0].fields[2]` or `sections[1]`. */
884
+ path: string;
885
+ code: "missing-name" | "missing-type" | "duplicate-name" | "itemfields-on-noncontainer" | "empty-container" | "unknown-operator";
886
+ /** `error` = will misbehave at runtime; `warning` = suspicious but tolerated. */
887
+ severity: "error" | "warning";
888
+ message: string;
889
+ }
890
+ /**
891
+ * Structurally validate a form schema and return a list of issues (empty ⇒ OK).
892
+ * Server-safe (no hooks/DOM), so you can run it when a schema is loaded from a
893
+ * DB, in a test, or in a dev boot check. It validates SHAPE, not your component
894
+ * registry — an unknown field `type` is a registry concern, not a schema error.
895
+ *
896
+ * Checks: missing `name`/`type`, duplicate names (namespace-aware), `itemFields`
897
+ * on a non-container type, containers with no `itemFields`, and unknown DSL
898
+ * condition operators.
899
+ */
900
+ declare function validateSchema(schema: FormSchema): SchemaIssue[];
901
+ /**
902
+ * Build a default-value object for a flat list of fields — used to seed a new
903
+ * array item (or a group) from its `itemFields`.
904
+ *
905
+ * Recurses into nested `group` children (→ nested object) and seeds nested
906
+ * `array` children as `[]`, so appending an item never leaves a deep sub-field
907
+ * `undefined`. That matters because a missing deep field can trip a resolver
908
+ * (zod et al.) into a spurious "required" error the moment the row is added.
909
+ * Leaf fields without an explicit `defaultValue` seed to `""` (a controlled
910
+ * empty value RHF is happy with).
911
+ */
912
+ declare function buildFieldDefaults(fields: BaseField[] | undefined): Record<string, unknown>;
913
+ /**
914
+ * Deep-merge `override` onto `base` with default-values semantics: nested
915
+ * plain objects merge recursively; arrays, primitives, and class instances
916
+ * (Date, File, …) from `override` replace wholesale.
917
+ *
918
+ * This is the merge `useFormKit` applies between schema-extracted defaults and
919
+ * caller-provided `defaultValues`. Exported so wrappers that re-seed a form at
920
+ * runtime (e.g. an edit sheet swapping entities) can reproduce the exact same
921
+ * merge for `form.reset(...)`:
922
+ *
923
+ * @example
924
+ * ```ts
925
+ * const merged = mergeDefaultValues(extractDefaultValues(schema), entity);
926
+ * form.reset(merged);
927
+ * ```
928
+ */
929
+ declare function mergeDefaultValues(base: Record<string, unknown>, override: Record<string, unknown>): Record<string, unknown>;
930
+ /**
931
+ * Async version of `extractDefaultValues` for large schemas (50+ fields).
932
+ * Yields to the main thread after each section so that the browser can
933
+ * handle input events between chunks — keeping INP scores low.
934
+ *
935
+ * Use in `getDefaultValues` passed to `useForm` when the schema is known
936
+ * to be large:
937
+ *
938
+ * @example
939
+ * ```ts
940
+ * const form = useForm({
941
+ * defaultValues: async () => extractDefaultValuesAsync(schema),
942
+ * });
943
+ * ```
944
+ */
945
+ declare function extractDefaultValuesAsync<TFieldValues extends FieldValues = FieldValues>(schema: FormSchema<TFieldValues>): Promise<Partial<TFieldValues>>;
656
946
  /**
657
947
  * Generates react-hook-form `RegisterOptions`-compatible validation rules
658
948
  * from a field's schema props. Maps `required`, `min`, `max`, `minLength`,
659
949
  * `maxLength`, `pattern`, and `validate` to RHF rules.
660
950
  *
951
+ * Supports both shorthand scalars and `{ value, message }` objects for all
952
+ * numeric/length rules, and `{ regex, message }` for pattern.
953
+ *
661
954
  * @example
662
955
  * ```tsx
663
956
  * import { buildValidationRules } from '@classytic/formkit';
@@ -669,155 +962,174 @@ declare function extractDefaultValues<TFieldValues extends FieldValues = FieldVa
669
962
  * ```
670
963
  */
671
964
  declare function buildValidationRules<TFieldValues extends FieldValues = FieldValues>(field: BaseField<TFieldValues>): ValidationRules;
965
+ /** Returns true for fields that carry an `options` array (select, radio, etc.) */
966
+ declare function isChoiceField(field: BaseField): boolean;
967
+ /** Returns true for free-text input fields */
968
+ declare function isTextField(field: BaseField): boolean;
969
+ /** Returns true for numeric input fields */
970
+ declare function isNumericField(field: BaseField): boolean;
971
+ /** Returns true for date / time fields */
972
+ declare function isDateField(field: BaseField): boolean;
973
+ /** Returns true for structural fields that contain sub-fields (`itemFields`) */
974
+ declare function isContainerField(field: BaseField): boolean;
975
+ /** Returns true for array fields that render a repeatable list */
976
+ declare function isArrayField(field: BaseField): boolean;
977
+ /** Returns true for fields that load options asynchronously */
978
+ declare function isDynamicField(field: BaseField): boolean;
979
+ /** Returns true for fields with conditional rendering */
980
+ declare function isConditionalField(field: BaseField): boolean;
981
+ /**
982
+ * Merge two or more schemas into one, concatenating their sections.
983
+ *
984
+ * @example
985
+ * ```ts
986
+ * const full = mergeSchemas(personalSchema, addressSchema, billingSchema);
987
+ * ```
988
+ */
989
+ declare function mergeSchemas<TFieldValues extends FieldValues = FieldValues>(...schemas: FormSchema<TFieldValues>[]): FormSchema<TFieldValues>;
990
+ /**
991
+ * Add fields to a section identified by `sectionId`.
992
+ * Returns a new schema — the original is not mutated.
993
+ *
994
+ * @example
995
+ * ```ts
996
+ * const extended = extendSection(schema, "personal", [
997
+ * field.text("middleName", "Middle Name"),
998
+ * ]);
999
+ * ```
1000
+ */
1001
+ declare function extendSection<TFieldValues extends FieldValues = FieldValues>(schema: FormSchema<TFieldValues>, sectionId: string, fields: BaseField<TFieldValues>[], position?: "start" | "end"): FormSchema<TFieldValues>;
1002
+ /**
1003
+ * Create a new schema that includes only the named fields.
1004
+ *
1005
+ * @example
1006
+ * ```ts
1007
+ * const slim = pickFields(schema, ["email", "password"]);
1008
+ * ```
1009
+ */
1010
+ declare function pickFields<TFieldValues extends FieldValues = FieldValues>(schema: FormSchema<TFieldValues>, names: string[]): FormSchema<TFieldValues>;
1011
+ /**
1012
+ * Create a new schema that excludes the named fields.
1013
+ *
1014
+ * @example
1015
+ * ```ts
1016
+ * const withoutInternal = omitFields(schema, ["__id", "__createdAt"]);
1017
+ * ```
1018
+ */
1019
+ declare function omitFields<TFieldValues extends FieldValues = FieldValues>(schema: FormSchema<TFieldValues>, names: string[]): FormSchema<TFieldValues>;
1020
+ /**
1021
+ * Collect every field from every section into a flat array.
1022
+ * Useful for validation, documentation, and AI schema introspection.
1023
+ *
1024
+ * @example
1025
+ * ```ts
1026
+ * const allFields = flattenSchema(schema);
1027
+ * const required = allFields.filter(f => f.required);
1028
+ * ```
1029
+ */
1030
+ declare function flattenSchema<TFieldValues extends FieldValues = FieldValues>(schema: FormSchema<TFieldValues>): BaseField<TFieldValues>[];
1031
+ /**
1032
+ * Maps a server error response to react-hook-form field errors.
1033
+ *
1034
+ * Call this in your `onError` / `catch` handler after a failed API submission
1035
+ * to surface per-field server-side errors using the same UX as client validation.
1036
+ *
1037
+ * @param form - The `useForm` return value
1038
+ * @param errors - Map of field path → error message (dot-notation paths supported)
1039
+ *
1040
+ * @example
1041
+ * ```ts
1042
+ * async function onSubmit(data: FormValues) {
1043
+ * try {
1044
+ * await api.save(data);
1045
+ * } catch (err) {
1046
+ * if (err.fieldErrors) {
1047
+ * applyServerErrors(form, err.fieldErrors);
1048
+ * // { email: "Already taken", "address.zip": "Invalid ZIP" }
1049
+ * }
1050
+ * }
1051
+ * }
1052
+ * ```
1053
+ */
1054
+ declare function applyServerErrors<TFieldValues extends FieldValues = FieldValues>(form: UseFormReturn<TFieldValues>, errors: Record<string, string>): void;
672
1055
  //#endregion
673
1056
  //#region src/builders.d.ts
674
1057
  /**
675
- * Additional field props for builder helpers.
1058
+ * Additional field props accepted by builder helpers.
676
1059
  * Accepts all BaseField properties except `name`, `type`, and `label`
677
- * which are set by the builder method.
1060
+ * which are provided by the builder method directly.
678
1061
  */
679
- type FieldProps<TFieldValues extends FieldValues = FieldValues> = Omit<BaseField<TFieldValues>, "name" | "type" | "label"> & {
680
- /** Grid column class (e.g., "col-span-2") */gridColumn?: string; /** Icon for the left side of input */
681
- iconLeft?: ReactNode; /** Icon for the right side of input */
682
- iconRight?: ReactNode; /** Additional custom props */
683
- [key: string]: unknown;
684
- };
1062
+ type FieldProps<TFieldValues extends FieldValues = FieldValues> = Omit<BaseField<TFieldValues>, "name" | "type" | "label">;
685
1063
  /**
686
1064
  * Section configuration props.
687
1065
  */
688
1066
  interface SectionProps<TFieldValues extends FieldValues = FieldValues> extends Omit<Section<TFieldValues>, "id" | "title" | "fields" | "cols"> {
689
1067
  cols?: number;
690
1068
  }
691
- /**
692
- * Render function for custom field types.
693
- */
694
- type CustomRenderFn = (props: {
695
- control: Control<FieldValues>;
696
- disabled?: boolean;
697
- error?: FieldError;
698
- }) => ReactNode;
699
1069
  /**
700
1070
  * Type-safe field builder helpers for schema-driven forms.
701
1071
  *
702
- * Provides shorthand methods for common field types with sensible defaults,
703
- * reducing boilerplate while maintaining full type safety.
1072
+ * All methods are generic over TFieldValues, defaulting to FieldValues (any string)
1073
+ * when no type argument is provided. Specify the generic to enforce that field
1074
+ * names are valid paths in your form values type.
1075
+ *
1076
+ * For fully-typed schemas where every field name is checked, prefer
1077
+ * `field.for<MyForm>()` which fixes the generic once for the whole schema:
704
1078
  *
705
1079
  * @example
706
1080
  * ```ts
707
- * import { field, section } from '@classytic/formkit';
708
- *
709
- * const schema = {
710
- * sections: [
711
- * section("personal", "Personal Info", [
712
- * field.text("firstName", "First Name", { required: true }),
713
- * field.email("email", "Email"),
714
- * field.select("role", "Role", [
715
- * { label: "Admin", value: "admin" },
716
- * { label: "User", value: "user" },
717
- * ]),
718
- * ], { cols: 2 }),
719
- * ],
720
- * };
1081
+ * // Untyped any string accepted (backwards compatible)
1082
+ * field.text("email", "Email")
1083
+ *
1084
+ * // Per-call generic — name is checked against MyForm
1085
+ * field.text<MyForm>("email", "Email")
1086
+ *
1087
+ * // Typed factory — name checked on every call without repeating the generic
1088
+ * const f = field.for<MyForm>()
1089
+ * f.text("email", "Email") //
1090
+ * f.text("typo", "Email") // ✗ TypeScript error
721
1091
  * ```
722
1092
  */
723
1093
  declare const field: {
724
- /**
725
- * Text input field.
726
- */
727
- text: (name: string, label: string, props?: FieldProps) => BaseField;
728
- /**
729
- * Email input field with default placeholder.
730
- */
731
- email: (name: string, label: string, props?: FieldProps) => BaseField;
732
- /**
733
- * URL input field with default placeholder.
734
- */
735
- url: (name: string, label: string, props?: FieldProps) => BaseField;
736
- /**
737
- * Phone/tel input field with default placeholder.
738
- */
739
- tel: (name: string, label: string, props?: FieldProps) => BaseField;
740
- /**
741
- * Password input field.
742
- */
743
- password: (name: string, label: string, props?: FieldProps) => BaseField;
744
- /**
745
- * Number input field with min: 0 default.
746
- */
747
- number: (name: string, label: string, props?: FieldProps) => BaseField;
748
- /**
749
- * Textarea field with default 3 rows.
750
- */
751
- textarea: (name: string, label: string, props?: FieldProps) => BaseField;
752
- /**
753
- * Select dropdown field.
754
- */
755
- select: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
756
- /**
757
- * Searchable combobox field.
758
- */
759
- combobox: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
760
- /**
761
- * Multi-select field (tag choice).
762
- */
763
- multiselect: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
764
- /**
765
- * Dependent select field that reacts to parent field changes.
766
- */
767
- dependentSelect: (name: string, label: string, props?: FieldProps) => BaseField;
768
- /**
769
- * Switch/toggle field.
770
- */
771
- switch: (name: string, label: string, props?: FieldProps) => BaseField;
772
- /**
773
- * Boolean field (alias for switch).
774
- */
775
- boolean: (name: string, label: string, props?: FieldProps) => BaseField;
776
- /**
777
- * Checkbox field.
778
- */
779
- checkbox: (name: string, label: string, props?: FieldProps) => BaseField;
780
- /**
781
- * Radio button group field.
782
- */
783
- radio: (name: string, label: string, options: FieldOption[], props?: FieldProps) => BaseField;
784
- /**
785
- * Date picker field.
786
- */
787
- date: (name: string, label: string, props?: FieldProps) => BaseField;
788
- /**
789
- * Tag input field with default placeholder.
790
- */
791
- tags: (name: string, label: string, props?: FieldProps) => BaseField;
792
- /**
793
- * Slug field with auto-generation from source value.
794
- */
795
- slug: (name: string, label: string, props?: FieldProps) => BaseField;
796
- /**
797
- * File upload field.
798
- */
799
- file: (name: string, label: string, props?: FieldProps) => BaseField;
800
- /**
801
- * Hidden field (no UI).
802
- */
803
- hidden: (name: string, props?: FieldProps) => BaseField;
1094
+ /** Text input field. */text: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Email input field with default placeholder. */
1095
+ email: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** URL input field with default placeholder. */
1096
+ url: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Phone/tel input field with default placeholder. */
1097
+ tel: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Password input field. */
1098
+ password: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1099
+ /** Number input field. No implicit `min` — pass `{ min }` to add one, so the
1100
+ * builder never injects validation the author didn't write (signed
1101
+ * quantities like deltas / temperatures stay valid). */
1102
+ number: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Textarea field with default 3 rows. */
1103
+ textarea: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Select dropdown field. */
1104
+ select: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>; /** Searchable combobox field. */
1105
+ combobox: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>; /** Multi-select field. */
1106
+ multiselect: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>; /** Dependent select field that reacts to parent field changes. */
1107
+ dependentSelect: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Switch/toggle field. */
1108
+ switch: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Boolean field (alias for switch). */
1109
+ boolean: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Checkbox field. */
1110
+ checkbox: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Radio button group field. */
1111
+ radio: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, options: FieldOption[], props?: FieldProps<T>) => BaseField<T>; /** Date picker field. */
1112
+ date: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Tag input field. */
1113
+ tags: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Slug field. */
1114
+ slug: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** File upload field. */
1115
+ file: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Hidden field (no UI). */
1116
+ hidden: <T extends FieldValues = FieldValues>(name: Path<T>, props?: FieldProps<T>) => BaseField<T>;
804
1117
  /**
805
1118
  * Group field for nested objects.
806
- * Renders itemFields as a sub-grid within the form.
1119
+ * Renders itemFields as a sub-grid. Child names are relative (e.g. "street"),
1120
+ * FormGenerator prefixes them with the group name at render time.
807
1121
  *
808
1122
  * @example
809
1123
  * ```ts
810
1124
  * field.group("address", "Address", [
811
1125
  * field.text("street", "Street"),
812
1126
  * field.text("city", "City"),
813
- * field.text("zip", "ZIP Code"),
814
- * ], { cols: 3 })
1127
+ * ], { cols: 2 })
815
1128
  * ```
816
1129
  */
817
- group: (name: string, label: string, itemFields: BaseField[], props?: FieldProps) => BaseField;
1130
+ group: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, itemFields: BaseField[], props?: FieldProps<T>) => BaseField<T>;
818
1131
  /**
819
- * Array/repeatable field.
820
- * Renders a dynamic list of sub-forms using react-hook-form's useFieldArray.
1132
+ * Array/repeatable field backed by react-hook-form's useFieldArray.
821
1133
  *
822
1134
  * @example
823
1135
  * ```ts
@@ -827,42 +1139,109 @@ declare const field: {
827
1139
  * ])
828
1140
  * ```
829
1141
  */
830
- array: (name: string, label: string, itemFields: BaseField[], props?: FieldProps) => BaseField;
1142
+ array: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, itemFields: BaseField[], props?: FieldProps<T>) => BaseField<T>;
831
1143
  /**
832
1144
  * Custom field with a render function.
833
- * Bypasses the component registry entirely.
1145
+ * Bypasses the component registry — full control over rendering.
1146
+ *
1147
+ * The render callback receives the complete `FieldComponentProps` including
1148
+ * `fieldId`, `errorId`, `shouldShowError`, `error`, `rules`, and `control`.
1149
+ *
1150
+ * Use `shouldShowError` (not `!!error`) to drive `aria-invalid` and error
1151
+ * visibility so timing mirrors the CSS `:user-invalid` pseudo-class.
834
1152
  *
835
1153
  * @example
836
- * ```ts
837
- * field.custom("skills", "Skills", ({ control, disabled }) => (
838
- * <SkillSelector control={control} disabled={disabled} />
1154
+ * ```tsx
1155
+ * field.custom("skills", "Skills", ({ control, shouldShowError, errorId, error, fieldId }) => (
1156
+ * <div>
1157
+ * <SkillSelector
1158
+ * id={fieldId}
1159
+ * control={control}
1160
+ * aria-invalid={shouldShowError || undefined}
1161
+ * aria-errormessage={shouldShowError ? errorId : undefined}
1162
+ * />
1163
+ * {shouldShowError && (
1164
+ * <p id={errorId} role="alert" className="text-sm text-destructive">
1165
+ * {error?.message}
1166
+ * </p>
1167
+ * )}
1168
+ * </div>
839
1169
  * ))
840
1170
  * ```
841
1171
  */
842
- custom: (name: string, label: string, render: CustomRenderFn, props?: FieldProps) => BaseField;
1172
+ custom: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, render: (props: FieldComponentProps<T>) => ReactNode, props?: FieldProps<T>) => BaseField<T>;
1173
+ /**
1174
+ * Returns a typed field builder with `TFieldValues` fixed.
1175
+ * Every field name is validated against `Path<TFieldValues>` at the call site —
1176
+ * no need to repeat the generic on each individual builder call.
1177
+ *
1178
+ * @example
1179
+ * ```ts
1180
+ * interface ContactForm {
1181
+ * firstName: string;
1182
+ * email: string;
1183
+ * address: { street: string; city: string };
1184
+ * }
1185
+ *
1186
+ * const f = field.for<ContactForm>()
1187
+ *
1188
+ * const schema = defineSchema<ContactForm>({
1189
+ * sections: [{
1190
+ * fields: [
1191
+ * f.text("firstName", "First Name"), // ✓
1192
+ * f.email("email", "Email"), // ✓
1193
+ * f.text("typo", "Label"), // ✗ TypeScript error
1194
+ * ],
1195
+ * }],
1196
+ * })
1197
+ * ```
1198
+ */
1199
+ for: <T extends FieldValues>() => {
1200
+ text: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1201
+ email: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1202
+ url: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1203
+ tel: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1204
+ password: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1205
+ number: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1206
+ textarea: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1207
+ select: (name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>;
1208
+ combobox: (name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>;
1209
+ multiselect: (name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>;
1210
+ dependentSelect: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1211
+ switch: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1212
+ boolean: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1213
+ checkbox: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1214
+ radio: (name: Path<T>, label: string, options: FieldOption[], props?: FieldProps<T>) => BaseField<T>;
1215
+ date: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1216
+ tags: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1217
+ slug: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1218
+ file: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
1219
+ hidden: (name: Path<T>, props?: FieldProps<T>) => BaseField<T>;
1220
+ group: (name: Path<T>, label: string, itemFields: BaseField[], props?: FieldProps<T>) => BaseField<T>;
1221
+ array: (name: Path<T>, label: string, itemFields: BaseField[], props?: FieldProps<T>) => BaseField<T>;
1222
+ custom: (name: Path<T>, label: string, render: (props: FieldComponentProps<T>) => ReactNode, builderProps?: FieldProps<T>) => BaseField<T>;
1223
+ };
843
1224
  };
844
1225
  /**
845
1226
  * Create a section definition with sensible defaults.
846
1227
  *
847
- * @param id - Unique section identifier
848
- * @param title - Section title
849
- * @param fields - Array of field definitions
850
- * @param props - Additional section configuration
851
- *
852
1228
  * @example
853
1229
  * ```ts
854
1230
  * section("personal", "Personal Info", [
855
1231
  * field.text("name", "Name", { required: true }),
856
1232
  * field.email("email", "Email"),
857
- * ], { cols: 2, variant: "card" })
1233
+ * ], { cols: 2 })
858
1234
  * ```
859
1235
  */
860
1236
  declare function section<TFieldValues extends FieldValues = FieldValues>(id: string, title: string, fields: BaseField<TFieldValues>[], props?: SectionProps<TFieldValues>): Section<TFieldValues>;
861
1237
  /**
862
1238
  * Create a section without a title (transparent section).
863
1239
  * Useful for grouping fields without visual separation.
1240
+ *
1241
+ * Accepts `BaseField[]` (no generic) so mixed-type field arrays don't trigger
1242
+ * conflicting type inference across different field name generics.
864
1243
  */
865
- declare function sectionUntitled<TFieldValues extends FieldValues = FieldValues>(fields: BaseField<TFieldValues>[], props?: Omit<SectionProps<TFieldValues>, "variant">): Section<TFieldValues>;
1244
+ declare function sectionUntitled(fields: BaseField[], props?: Omit<SectionProps, "variant">): Section;
866
1245
  //#endregion
867
1246
  //#region src/useFormKit.d.ts
868
1247
  /**
@@ -880,6 +1259,14 @@ interface UseFormKitOptions<TFieldValues extends FieldValues = FieldValues> exte
880
1259
  variant?: Variant;
881
1260
  /** Root element className */
882
1261
  className?: string;
1262
+ /**
1263
+ * Re-seed the form with the schema's freshly-extracted defaults whenever the
1264
+ * `schema` identity changes (e.g. a schema fetched from a DB is swapped in).
1265
+ * Off by default because a reset discards in-progress edits — enable it only
1266
+ * when a schema swap should intentionally re-initialise the form. Skipped when
1267
+ * `defaultValues` is an async factory (the caller owns that reset).
1268
+ */
1269
+ resetOnSchemaChange?: boolean;
883
1270
  }
884
1271
  /**
885
1272
  * Return value from useFormKit.
@@ -910,4 +1297,30 @@ interface UseFormKitReturn<TFieldValues extends FieldValues = FieldValues> exten
910
1297
  */
911
1298
  declare function useFormKit<TFieldValues extends FieldValues = FieldValues>(options: UseFormKitOptions<TFieldValues>): UseFormKitReturn<TFieldValues>;
912
1299
  //#endregion
913
- export { type BaseField, type ClassValue, type ComponentRegistry, type Condition, type ConditionConfig, type ConditionRule, type DefaultLayoutProps, type DefineField, type FieldComponent, type FieldComponentProps, type FieldOption, type FieldOptionGroup, type FieldType, FieldWrapper, type FieldWrapperProps, type FormElement, FormGenerator, type FormGeneratorProps, type FormSchema, type FormSystemContextValue, FormSystemProvider, type FormSystemProviderProps, type GridLayoutProps, GridRenderer, type GridRendererProps, type InferSchemaValues, type LayoutComponent, type LayoutComponentProps, type LayoutRegistry, type LayoutType, type SchemaFieldNames, type Section, type SectionLayoutProps, type SectionRenderProps, SectionRenderer, type SectionRendererProps, type UseFormKitOptions, type UseFormKitReturn, type ValidationRules, type Variant, buildValidationRules, cn, defineField, defineSchema, defineSection, evaluateCondition, extractDefaultValues, extractWatchNames, field, section, sectionUntitled, shallowEqual, useFieldComponent, useFormKit, useFormSystem, useLayoutComponent };
1300
+ //#region src/focus.d.ts
1301
+ /**
1302
+ * Focus (and scroll to) the first field currently shown as invalid.
1303
+ *
1304
+ * It queries the rendered `[data-formkit-field][data-invalid]` wrapper — which
1305
+ * `FormGenerator` marks once errors are visible (after touch / submit) — so it
1306
+ * targets the first error in **visual order**, not react-hook-form's error-key
1307
+ * order (which isn't guaranteed to match the layout). Wire it into your submit's
1308
+ * invalid handler:
1309
+ *
1310
+ * ```ts
1311
+ * form.handleSubmit(onValid, () => focusFirstError());
1312
+ * ```
1313
+ *
1314
+ * **Timing:** react-hook-form calls the invalid handler *before* React commits
1315
+ * the re-render that sets `data-invalid` on the wrappers, so on the very first
1316
+ * invalid submit a synchronous query finds nothing. When the immediate attempt
1317
+ * misses, this retries on the next two animation frames (the commit lands
1318
+ * before the next paint), so the plain wiring above Just Works.
1319
+ *
1320
+ * @param root Optional container to search within (defaults to `document`).
1321
+ * @returns `true` if a field was focused immediately; `false` if not found yet
1322
+ * (a deferred retry may still focus it on the next frame).
1323
+ */
1324
+ declare function focusFirstError(root?: HTMLElement | Document | null): boolean;
1325
+ //#endregion
1326
+ export { type BaseField, type ClassValue, type ComponentRegistry, type Condition, type ConditionConfig, type ConditionRule, type DefaultLayoutProps, type DefineField, type FieldComponent, type FieldComponentProps, type FieldMeta, type FieldOption, type FieldOptionGroup, type FieldType, FieldWrapper, type FieldWrapperProps, type FormElement, FormGenerator, type FormGeneratorProps, type FormSchema, type FormSystemContextValue, FormSystemProvider, type FormSystemProviderProps, type GridLayoutProps, GridRenderer, type GridRendererProps, type InferSchemaValues, type LayoutComponent, type LayoutComponentProps, type LayoutRegistry, type LayoutType, type PatternRuleObject, type SchemaFieldNames, type SchemaIssue, type Section, type SectionLayoutProps, type SectionRenderProps, SectionRenderer, type SectionRendererProps, type UseFormKitOptions, type UseFormKitReturn, type ValidationRuleObject, type ValidationRules, type Variant, applyServerErrors, buildFieldDefaults, buildValidationRules, cn, defineField, defineSchema, defineSection, evaluateCondition, extendSection, extractDefaultValues, extractDefaultValuesAsync, extractWatchNames, field, flattenSchema, focusFirstError, isArrayField, isChoiceField, isConditionalField, isContainerField, isDateField, isDynamicField, isNumericField, isTextField, mergeDefaultValues, mergeSchemas, omitFields, pickFields, section, sectionUntitled, shallowEqual, useFieldComponent, useFormKit, useFormSystem, useLayoutComponent, validateSchema };