@classytic/formkit 1.3.0 → 1.4.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/CHANGELOG.md +12 -0
- package/README.md +31 -12
- package/dist/index.d.mts +447 -181
- package/dist/index.mjs +717 -219
- package/dist/server.d.mts +393 -170
- package/dist/server.mjs +392 -121
- package/package.json +115 -113
package/dist/server.d.mts
CHANGED
|
@@ -31,7 +31,7 @@ declare function cn(...inputs: ClassValue[]): string;
|
|
|
31
31
|
* Field type identifier.
|
|
32
32
|
* Can be built-in types or custom string identifiers.
|
|
33
33
|
*/
|
|
34
|
-
type FieldType = "text" | "email" | "password" | "number" | "tel" | "url" | "textarea" | "select" | "checkbox" | "radio" | "switch" | "date" | "time" | "datetime" | "file" | "hidden" | "group" | "array" | "custom" | (string & {});
|
|
34
|
+
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 & {});
|
|
35
35
|
/**
|
|
36
36
|
* Layout type identifier.
|
|
37
37
|
*/
|
|
@@ -93,12 +93,55 @@ interface FieldOptionGroup<TValue = string> {
|
|
|
93
93
|
/** Whether group is disabled */
|
|
94
94
|
disabled?: boolean;
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Object form for length/range validation rules with a custom message.
|
|
98
|
+
*/
|
|
99
|
+
interface ValidationRuleObject<TValue = number> {
|
|
100
|
+
value: TValue;
|
|
101
|
+
message: string;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Object form for pattern validation with a custom message.
|
|
105
|
+
*/
|
|
106
|
+
interface PatternRuleObject {
|
|
107
|
+
/** Regex string (will be compiled with `new RegExp(regex)`) */
|
|
108
|
+
regex: string;
|
|
109
|
+
/** Custom error message */
|
|
110
|
+
message: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* AI-friendly metadata attached to any field.
|
|
114
|
+
* Consumed by LLM coding assistants, documentation generators, and
|
|
115
|
+
* schema-introspection tools — never by the form renderer itself.
|
|
116
|
+
*/
|
|
117
|
+
interface FieldMeta {
|
|
118
|
+
/** Human-readable description of what this field collects */
|
|
119
|
+
description?: string;
|
|
120
|
+
/** Representative example value (for documentation / AI context) */
|
|
121
|
+
example?: unknown;
|
|
122
|
+
/** Logical category for grouping fields in documentation or tooling */
|
|
123
|
+
category?: string;
|
|
124
|
+
/** Arbitrary tags for filtering, search, or feature-flagging */
|
|
125
|
+
tags?: string[];
|
|
126
|
+
/** Whether this field is considered PII */
|
|
127
|
+
pii?: boolean;
|
|
128
|
+
/** Arbitrary extension point — anything extra you want to carry */
|
|
129
|
+
[key: string]: unknown;
|
|
130
|
+
}
|
|
96
131
|
/**
|
|
97
132
|
* Base field configuration shared by all field types.
|
|
98
133
|
* @template TFieldValues - Form field values type for type-safe field names
|
|
99
134
|
*/
|
|
100
135
|
interface BaseField<TFieldValues extends FieldValues = FieldValues> {
|
|
101
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* Field name — a path into TFieldValues (e.g. `"email"`, `"address.street"`).
|
|
138
|
+
*
|
|
139
|
+
* Accepts any string so that:
|
|
140
|
+
* - Namespaced sections can use relative names (`"street"` → prefixed to `"address.street"`)
|
|
141
|
+
* - Group/array `itemFields` can use relative names (`"email"` → prefixed at render time)
|
|
142
|
+
*
|
|
143
|
+
* Use `field.for<T>()` builder for call-site enforcement that names are valid paths.
|
|
144
|
+
*/
|
|
102
145
|
name: Path<TFieldValues> | (string & {});
|
|
103
146
|
/** Field type identifier */
|
|
104
147
|
type: FieldType;
|
|
@@ -129,18 +172,41 @@ interface BaseField<TFieldValues extends FieldValues = FieldValues> {
|
|
|
129
172
|
defaultValue?: unknown;
|
|
130
173
|
/** Options for select/radio/checkbox fields */
|
|
131
174
|
options?: (FieldOption | FieldOptionGroup)[];
|
|
132
|
-
/**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Minimum value (for number/date fields).
|
|
177
|
+
* Pass an object `{ value, message }` to provide a custom error message.
|
|
178
|
+
*/
|
|
179
|
+
min?: number | string | ValidationRuleObject<number | string>;
|
|
180
|
+
/**
|
|
181
|
+
* Maximum value (for number/date fields).
|
|
182
|
+
* Pass an object `{ value, message }` to provide a custom error message.
|
|
183
|
+
*/
|
|
184
|
+
max?: number | string | ValidationRuleObject<number | string>;
|
|
136
185
|
/** Step value (for number fields) */
|
|
137
186
|
step?: number;
|
|
138
|
-
/**
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Pattern for validation.
|
|
189
|
+
* Pass a regex string or `{ regex, message }` for a custom error message.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* // simple
|
|
194
|
+
* pattern: "^[a-z]+$"
|
|
195
|
+
* // with custom message
|
|
196
|
+
* pattern: { regex: "^[a-z]+$", message: "Only lowercase letters allowed" }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
pattern?: string | PatternRuleObject;
|
|
200
|
+
/**
|
|
201
|
+
* Minimum length.
|
|
202
|
+
* Pass an object `{ value, message }` to provide a custom error message.
|
|
203
|
+
*/
|
|
204
|
+
minLength?: number | ValidationRuleObject<number>;
|
|
205
|
+
/**
|
|
206
|
+
* Maximum length.
|
|
207
|
+
* Pass an object `{ value, message }` to provide a custom error message.
|
|
208
|
+
*/
|
|
209
|
+
maxLength?: number | ValidationRuleObject<number>;
|
|
144
210
|
/** Number of rows (for textarea) */
|
|
145
211
|
rows?: number;
|
|
146
212
|
/** Multiple selection (for select/file) */
|
|
@@ -164,10 +230,13 @@ interface BaseField<TFieldValues extends FieldValues = FieldValues> {
|
|
|
164
230
|
*/
|
|
165
231
|
onLoadError?: (error: unknown) => void;
|
|
166
232
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
233
|
+
* Sub-fields for `group` and `array` field types.
|
|
234
|
+
*
|
|
235
|
+
* These fields use **relative** names (`"street"`, `"email"`) — FormGenerator
|
|
236
|
+
* prefixes them with the parent field name at render time (`"address.street"`).
|
|
237
|
+
* They are intentionally untyped to TFieldValues for this reason.
|
|
169
238
|
*/
|
|
170
|
-
itemFields?: BaseField
|
|
239
|
+
itemFields?: BaseField[];
|
|
171
240
|
/**
|
|
172
241
|
* Custom render function to override the component registry for this specific field.
|
|
173
242
|
* Completely bypasses the globally registered FieldComponent for this type.
|
|
@@ -176,15 +245,22 @@ interface BaseField<TFieldValues extends FieldValues = FieldValues> {
|
|
|
176
245
|
/**
|
|
177
246
|
* Cross-field validation function.
|
|
178
247
|
* Receives the field value and all form values for cross-field checks.
|
|
179
|
-
* Return `true` for valid,
|
|
248
|
+
* Return `true` for valid, a string error message for invalid, or a Promise of either
|
|
249
|
+
* for async validation (e.g., checking server-side uniqueness).
|
|
180
250
|
*
|
|
181
251
|
* @example
|
|
182
252
|
* ```ts
|
|
183
253
|
* validate: (value, formValues) =>
|
|
184
254
|
* value > formValues.minPrice || "Must be greater than min price"
|
|
255
|
+
*
|
|
256
|
+
* // Async example
|
|
257
|
+
* validate: async (value) => {
|
|
258
|
+
* const taken = await checkUsernameAvailability(value as string);
|
|
259
|
+
* return taken ? "Username already taken" : true;
|
|
260
|
+
* }
|
|
185
261
|
* ```
|
|
186
262
|
*/
|
|
187
|
-
validate?: (value: unknown, formValues: Partial<TFieldValues>) => string | true
|
|
263
|
+
validate?: (value: unknown, formValues: Partial<TFieldValues>) => string | true | Promise<string | true>;
|
|
188
264
|
/**
|
|
189
265
|
* Dependencies for optimizing conditionally rendered fields.
|
|
190
266
|
* Allows specifying specific field names to watch, preventing full form re-renders.
|
|
@@ -192,6 +268,32 @@ interface BaseField<TFieldValues extends FieldValues = FieldValues> {
|
|
|
192
268
|
watchNames?: Path<TFieldValues> | Path<TFieldValues>[];
|
|
193
269
|
/** Additional field-specific props for custom components */
|
|
194
270
|
customProps?: Record<string, unknown>;
|
|
271
|
+
/**
|
|
272
|
+
* AI / agent-friendly metadata for this field.
|
|
273
|
+
*
|
|
274
|
+
* Provides hints that help LLM coding assistants generate correct field
|
|
275
|
+
* configs without needing to inspect the schema at runtime.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* ```ts
|
|
279
|
+
* field.text("companyName", "Company", {
|
|
280
|
+
* meta: {
|
|
281
|
+
* description: "Legal entity name of the organization",
|
|
282
|
+
* example: "Acme Corp",
|
|
283
|
+
* category: "identity",
|
|
284
|
+
* tags: ["crm", "required-for-billing"],
|
|
285
|
+
* }
|
|
286
|
+
* })
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
meta?: FieldMeta;
|
|
290
|
+
/**
|
|
291
|
+
* Escape hatch for adapter-specific or custom props.
|
|
292
|
+
* Allows passing arbitrary props directly on the field object so they
|
|
293
|
+
* flow through the adapter spread (`{...field}`) without needing `customProps`.
|
|
294
|
+
* Intentionally broad to support diverse UI component libraries.
|
|
295
|
+
*/
|
|
296
|
+
[key: string]: unknown;
|
|
195
297
|
}
|
|
196
298
|
/**
|
|
197
299
|
* Props passed to field components.
|
|
@@ -237,13 +339,68 @@ interface FieldComponentProps<TFieldValues extends FieldValues = FieldValues> ex
|
|
|
237
339
|
invalid: boolean;
|
|
238
340
|
isDirty: boolean;
|
|
239
341
|
isTouched: boolean;
|
|
240
|
-
isValidating: boolean;
|
|
342
|
+
isValidating: boolean; /** True after the enclosing form's submit handler has been called at least once. */
|
|
343
|
+
isSubmitted: boolean;
|
|
241
344
|
error?: FieldError;
|
|
242
345
|
};
|
|
243
|
-
/**
|
|
346
|
+
/**
|
|
347
|
+
* Generated field ID for label-input association (e.g. `formkit-field-email`).
|
|
348
|
+
* Use as `id` on the input element and `htmlFor` on the `<label>`.
|
|
349
|
+
*/
|
|
244
350
|
fieldId: string;
|
|
351
|
+
/**
|
|
352
|
+
* Generated error container ID for ARIA association (e.g. `formkit-field-email-error`).
|
|
353
|
+
* Use as `id` on the error message element and as the value for `aria-errormessage`
|
|
354
|
+
* (preferred) or `aria-describedby` (broader support) on the input.
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```tsx
|
|
358
|
+
* <input
|
|
359
|
+
* id={fieldId}
|
|
360
|
+
* aria-invalid={shouldShowError || undefined}
|
|
361
|
+
* aria-errormessage={shouldShowError ? errorId : undefined}
|
|
362
|
+
* />
|
|
363
|
+
* <p id={errorId} role="alert" aria-live="polite">
|
|
364
|
+
* {shouldShowError ? error?.message : null}
|
|
365
|
+
* </p>
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
errorId: string;
|
|
369
|
+
/**
|
|
370
|
+
* Whether to display the field error right now.
|
|
371
|
+
*
|
|
372
|
+
* Aligns with the CSS `:user-invalid` timing model: `true` only after the
|
|
373
|
+
* user has interacted with the field (blur) **or** the form has been
|
|
374
|
+
* submitted. This prevents premature "required" errors on untouched fields.
|
|
375
|
+
*
|
|
376
|
+
* Use this — not `!!error` — to drive `aria-invalid`, error message
|
|
377
|
+
* visibility, and destructive ring/border styles.
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* ```tsx
|
|
381
|
+
* <input aria-invalid={shouldShowError || undefined} />
|
|
382
|
+
* {shouldShowError && <p id={errorId} role="alert">{error?.message}</p>}
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
shouldShowError: boolean;
|
|
245
386
|
/** Whether dynamic options are currently loading */
|
|
246
387
|
isLoading?: boolean;
|
|
388
|
+
/**
|
|
389
|
+
* Pre-computed react-hook-form validation rules for this field.
|
|
390
|
+
* Equivalent to calling `buildValidationRules(field)` — provided here so
|
|
391
|
+
* adapter components can pass `rules={rules}` directly to `<Controller>`
|
|
392
|
+
* without importing or calling `buildValidationRules` themselves.
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```tsx
|
|
396
|
+
* function TextInput({ field, control, rules }: FieldComponentProps) {
|
|
397
|
+
* return (
|
|
398
|
+
* <Controller name={field.name} control={control} rules={rules} render={...} />
|
|
399
|
+
* );
|
|
400
|
+
* }
|
|
401
|
+
* ```
|
|
402
|
+
*/
|
|
403
|
+
rules: ValidationRules;
|
|
247
404
|
}
|
|
248
405
|
/**
|
|
249
406
|
* Validation rules compatible with react-hook-form's RegisterOptions.
|
|
@@ -289,6 +446,17 @@ interface Section<TFieldValues extends FieldValues = FieldValues> {
|
|
|
289
446
|
collapsible?: boolean;
|
|
290
447
|
/** Default collapsed state */
|
|
291
448
|
defaultCollapsed?: boolean;
|
|
449
|
+
/**
|
|
450
|
+
* Defer rendering of this section until it scrolls near the viewport.
|
|
451
|
+
* Applies `content-visibility: auto` + `contain-intrinsic-size` to the
|
|
452
|
+
* section container, skipping layout/paint work while off-screen.
|
|
453
|
+
*
|
|
454
|
+
* **Only use for sections that are below the initial fold.** Applying this
|
|
455
|
+
* to above-fold sections has no benefit and slightly increases overhead.
|
|
456
|
+
*
|
|
457
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/content-visibility
|
|
458
|
+
*/
|
|
459
|
+
deferRender?: boolean;
|
|
292
460
|
}
|
|
293
461
|
/**
|
|
294
462
|
* Props passed to section render function.
|
|
@@ -316,6 +484,12 @@ interface SectionLayoutProps {
|
|
|
316
484
|
collapsible?: boolean;
|
|
317
485
|
/** Default collapsed state */
|
|
318
486
|
defaultCollapsed?: boolean;
|
|
487
|
+
/**
|
|
488
|
+
* When true the section is below the initial fold and should defer
|
|
489
|
+
* browser layout/paint work until it nears the viewport.
|
|
490
|
+
* Layout components may apply `content-visibility: auto` here.
|
|
491
|
+
*/
|
|
492
|
+
deferRender?: boolean;
|
|
319
493
|
/** Children content */
|
|
320
494
|
children: ReactNode;
|
|
321
495
|
}
|
|
@@ -350,6 +524,12 @@ type LayoutComponentProps = SectionLayoutProps | GridLayoutProps | DefaultLayout
|
|
|
350
524
|
* @template TFieldValues - Form field values type for type-safe schemas
|
|
351
525
|
*/
|
|
352
526
|
interface FormSchema<TFieldValues extends FieldValues = FieldValues> {
|
|
527
|
+
/** Optional schema identifier — useful for serialization, analytics, and AI context */
|
|
528
|
+
id?: string;
|
|
529
|
+
/** Human-readable form title (AI / documentation use) */
|
|
530
|
+
title?: string;
|
|
531
|
+
/** Human-readable description of the form's purpose */
|
|
532
|
+
description?: string;
|
|
353
533
|
/** Form sections */
|
|
354
534
|
sections: Section<TFieldValues>[];
|
|
355
535
|
}
|
|
@@ -402,6 +582,7 @@ declare function defineSection<TFieldValues extends FieldValues = FieldValues>(s
|
|
|
402
582
|
/**
|
|
403
583
|
* Extracts default values from a form schema.
|
|
404
584
|
* Walks all sections and fields, respecting nameSpace prefixes and group nesting.
|
|
585
|
+
* Array fields default to `[]` when no explicit `defaultValue` is provided.
|
|
405
586
|
*
|
|
406
587
|
* @example
|
|
407
588
|
* ```ts
|
|
@@ -415,6 +596,9 @@ declare function extractDefaultValues<TFieldValues extends FieldValues = FieldVa
|
|
|
415
596
|
* from a field's schema props. Maps `required`, `min`, `max`, `minLength`,
|
|
416
597
|
* `maxLength`, `pattern`, and `validate` to RHF rules.
|
|
417
598
|
*
|
|
599
|
+
* Supports both shorthand scalars and `{ value, message }` objects for all
|
|
600
|
+
* numeric/length rules, and `{ regex, message }` for pattern.
|
|
601
|
+
*
|
|
418
602
|
* @example
|
|
419
603
|
* ```tsx
|
|
420
604
|
* import { buildValidationRules } from '@classytic/formkit';
|
|
@@ -426,175 +610,147 @@ declare function extractDefaultValues<TFieldValues extends FieldValues = FieldVa
|
|
|
426
610
|
* ```
|
|
427
611
|
*/
|
|
428
612
|
declare function buildValidationRules<TFieldValues extends FieldValues = FieldValues>(field: BaseField<TFieldValues>): ValidationRules;
|
|
613
|
+
/** Returns true for fields that carry an `options` array (select, radio, etc.) */
|
|
614
|
+
declare function isChoiceField(field: BaseField): boolean;
|
|
615
|
+
/** Returns true for free-text input fields */
|
|
616
|
+
declare function isTextField(field: BaseField): boolean;
|
|
617
|
+
/** Returns true for numeric input fields */
|
|
618
|
+
declare function isNumericField(field: BaseField): boolean;
|
|
619
|
+
/** Returns true for date / time fields */
|
|
620
|
+
declare function isDateField(field: BaseField): boolean;
|
|
621
|
+
/** Returns true for structural fields that contain sub-fields (`itemFields`) */
|
|
622
|
+
declare function isContainerField(field: BaseField): boolean;
|
|
623
|
+
/** Returns true for array fields that render a repeatable list */
|
|
624
|
+
declare function isArrayField(field: BaseField): boolean;
|
|
625
|
+
/** Returns true for fields that load options asynchronously */
|
|
626
|
+
declare function isDynamicField(field: BaseField): boolean;
|
|
627
|
+
/** Returns true for fields with conditional rendering */
|
|
628
|
+
declare function isConditionalField(field: BaseField): boolean;
|
|
629
|
+
/**
|
|
630
|
+
* Merge two or more schemas into one, concatenating their sections.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```ts
|
|
634
|
+
* const full = mergeSchemas(personalSchema, addressSchema, billingSchema);
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
637
|
+
declare function mergeSchemas<TFieldValues extends FieldValues = FieldValues>(...schemas: FormSchema<TFieldValues>[]): FormSchema<TFieldValues>;
|
|
638
|
+
/**
|
|
639
|
+
* Add fields to a section identified by `sectionId`.
|
|
640
|
+
* Returns a new schema — the original is not mutated.
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* ```ts
|
|
644
|
+
* const extended = extendSection(schema, "personal", [
|
|
645
|
+
* field.text("middleName", "Middle Name"),
|
|
646
|
+
* ]);
|
|
647
|
+
* ```
|
|
648
|
+
*/
|
|
649
|
+
declare function extendSection<TFieldValues extends FieldValues = FieldValues>(schema: FormSchema<TFieldValues>, sectionId: string, fields: BaseField<TFieldValues>[], position?: "start" | "end"): FormSchema<TFieldValues>;
|
|
650
|
+
/**
|
|
651
|
+
* Create a new schema that includes only the named fields.
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* ```ts
|
|
655
|
+
* const slim = pickFields(schema, ["email", "password"]);
|
|
656
|
+
* ```
|
|
657
|
+
*/
|
|
658
|
+
declare function pickFields<TFieldValues extends FieldValues = FieldValues>(schema: FormSchema<TFieldValues>, names: string[]): FormSchema<TFieldValues>;
|
|
659
|
+
/**
|
|
660
|
+
* Create a new schema that excludes the named fields.
|
|
661
|
+
*
|
|
662
|
+
* @example
|
|
663
|
+
* ```ts
|
|
664
|
+
* const withoutInternal = omitFields(schema, ["__id", "__createdAt"]);
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
declare function omitFields<TFieldValues extends FieldValues = FieldValues>(schema: FormSchema<TFieldValues>, names: string[]): FormSchema<TFieldValues>;
|
|
668
|
+
/**
|
|
669
|
+
* Collect every field from every section into a flat array.
|
|
670
|
+
* Useful for validation, documentation, and AI schema introspection.
|
|
671
|
+
*
|
|
672
|
+
* @example
|
|
673
|
+
* ```ts
|
|
674
|
+
* const allFields = flattenSchema(schema);
|
|
675
|
+
* const required = allFields.filter(f => f.required);
|
|
676
|
+
* ```
|
|
677
|
+
*/
|
|
678
|
+
declare function flattenSchema<TFieldValues extends FieldValues = FieldValues>(schema: FormSchema<TFieldValues>): BaseField<TFieldValues>[];
|
|
429
679
|
//#endregion
|
|
430
680
|
//#region src/builders.d.ts
|
|
431
681
|
/**
|
|
432
|
-
* Additional field props
|
|
682
|
+
* Additional field props accepted by builder helpers.
|
|
433
683
|
* Accepts all BaseField properties except `name`, `type`, and `label`
|
|
434
|
-
* which are
|
|
684
|
+
* which are provided by the builder method directly.
|
|
435
685
|
*/
|
|
436
|
-
type FieldProps<TFieldValues extends FieldValues = FieldValues> = Omit<BaseField<TFieldValues>, "name" | "type" | "label"
|
|
437
|
-
/** Grid column class (e.g., "col-span-2") */gridColumn?: string; /** Icon for the left side of input */
|
|
438
|
-
iconLeft?: ReactNode; /** Icon for the right side of input */
|
|
439
|
-
iconRight?: ReactNode; /** Additional custom props */
|
|
440
|
-
[key: string]: unknown;
|
|
441
|
-
};
|
|
686
|
+
type FieldProps<TFieldValues extends FieldValues = FieldValues> = Omit<BaseField<TFieldValues>, "name" | "type" | "label">;
|
|
442
687
|
/**
|
|
443
688
|
* Section configuration props.
|
|
444
689
|
*/
|
|
445
690
|
interface SectionProps<TFieldValues extends FieldValues = FieldValues> extends Omit<Section<TFieldValues>, "id" | "title" | "fields" | "cols"> {
|
|
446
691
|
cols?: number;
|
|
447
692
|
}
|
|
448
|
-
/**
|
|
449
|
-
* Render function for custom field types.
|
|
450
|
-
*/
|
|
451
|
-
type CustomRenderFn = (props: {
|
|
452
|
-
control: Control<FieldValues>;
|
|
453
|
-
disabled?: boolean;
|
|
454
|
-
error?: FieldError;
|
|
455
|
-
}) => ReactNode;
|
|
456
693
|
/**
|
|
457
694
|
* Type-safe field builder helpers for schema-driven forms.
|
|
458
695
|
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
696
|
+
* All methods are generic over TFieldValues, defaulting to FieldValues (any string)
|
|
697
|
+
* when no type argument is provided. Specify the generic to enforce that field
|
|
698
|
+
* names are valid paths in your form values type.
|
|
699
|
+
*
|
|
700
|
+
* For fully-typed schemas where every field name is checked, prefer
|
|
701
|
+
* `field.for<MyForm>()` which fixes the generic once for the whole schema:
|
|
461
702
|
*
|
|
462
703
|
* @example
|
|
463
704
|
* ```ts
|
|
464
|
-
*
|
|
705
|
+
* // Untyped — any string accepted (backwards compatible)
|
|
706
|
+
* field.text("email", "Email")
|
|
465
707
|
*
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
* { label: "User", value: "user" },
|
|
474
|
-
* ]),
|
|
475
|
-
* ], { cols: 2 }),
|
|
476
|
-
* ],
|
|
477
|
-
* };
|
|
708
|
+
* // Per-call generic — name is checked against MyForm
|
|
709
|
+
* field.text<MyForm>("email", "Email")
|
|
710
|
+
*
|
|
711
|
+
* // Typed factory — name checked on every call without repeating the generic
|
|
712
|
+
* const f = field.for<MyForm>()
|
|
713
|
+
* f.text("email", "Email") // ✓
|
|
714
|
+
* f.text("typo", "Email") // ✗ TypeScript error
|
|
478
715
|
* ```
|
|
479
716
|
*/
|
|
480
717
|
declare const field: {
|
|
481
|
-
/**
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Number input field with min: 0 default.
|
|
503
|
-
*/
|
|
504
|
-
number: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
505
|
-
/**
|
|
506
|
-
* Textarea field with default 3 rows.
|
|
507
|
-
*/
|
|
508
|
-
textarea: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
509
|
-
/**
|
|
510
|
-
* Select dropdown field.
|
|
511
|
-
*/
|
|
512
|
-
select: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
|
|
513
|
-
/**
|
|
514
|
-
* Searchable combobox field.
|
|
515
|
-
*/
|
|
516
|
-
combobox: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
|
|
517
|
-
/**
|
|
518
|
-
* Multi-select field (tag choice).
|
|
519
|
-
*/
|
|
520
|
-
multiselect: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
|
|
521
|
-
/**
|
|
522
|
-
* Tag choice field for selecting options as tags/chips.
|
|
523
|
-
*/
|
|
524
|
-
tagChoice: (name: string, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps) => BaseField;
|
|
525
|
-
/**
|
|
526
|
-
* Dependent select field that reacts to parent field changes.
|
|
527
|
-
*/
|
|
528
|
-
dependentSelect: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
529
|
-
/**
|
|
530
|
-
* Switch/toggle field.
|
|
531
|
-
*/
|
|
532
|
-
switch: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
533
|
-
/**
|
|
534
|
-
* Boolean field (alias for switch).
|
|
535
|
-
*/
|
|
536
|
-
boolean: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
537
|
-
/**
|
|
538
|
-
* Checkbox field.
|
|
539
|
-
*/
|
|
540
|
-
checkbox: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
541
|
-
/**
|
|
542
|
-
* Radio button group field.
|
|
543
|
-
*/
|
|
544
|
-
radio: (name: string, label: string, options: FieldOption[], props?: FieldProps) => BaseField;
|
|
545
|
-
/**
|
|
546
|
-
* Date picker field.
|
|
547
|
-
*/
|
|
548
|
-
date: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
549
|
-
/**
|
|
550
|
-
* Tag input field with default placeholder.
|
|
551
|
-
*/
|
|
552
|
-
tags: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
553
|
-
/**
|
|
554
|
-
* Slug field with auto-generation from source value.
|
|
555
|
-
*/
|
|
556
|
-
slug: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
557
|
-
/**
|
|
558
|
-
* File upload field.
|
|
559
|
-
*/
|
|
560
|
-
file: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
561
|
-
/**
|
|
562
|
-
* OTP/PIN input field.
|
|
563
|
-
*/
|
|
564
|
-
otp: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
565
|
-
/**
|
|
566
|
-
* Async searchable combobox with server-side search.
|
|
567
|
-
*/
|
|
568
|
-
asyncCombobox: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
569
|
-
/**
|
|
570
|
-
* Async searchable multi-select with server-side search.
|
|
571
|
-
*/
|
|
572
|
-
asyncMultiselect: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
573
|
-
/**
|
|
574
|
-
* Date and optional time picker field.
|
|
575
|
-
*/
|
|
576
|
-
dateTime: (name: string, label: string, props?: FieldProps) => BaseField;
|
|
577
|
-
/**
|
|
578
|
-
* Hidden field (no UI).
|
|
579
|
-
*/
|
|
580
|
-
hidden: (name: string, props?: FieldProps) => BaseField;
|
|
718
|
+
/** Text input field. */text: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Email input field with default placeholder. */
|
|
719
|
+
email: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** URL input field with default placeholder. */
|
|
720
|
+
url: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Phone/tel input field with default placeholder. */
|
|
721
|
+
tel: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Password input field. */
|
|
722
|
+
password: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Number input field with min: 0 default (overrideable via props). */
|
|
723
|
+
number: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Textarea field with default 3 rows. */
|
|
724
|
+
textarea: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Select dropdown field. */
|
|
725
|
+
select: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>; /** Searchable combobox field. */
|
|
726
|
+
combobox: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>; /** Multi-select field. */
|
|
727
|
+
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. */
|
|
728
|
+
dependentSelect: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Switch/toggle field. */
|
|
729
|
+
switch: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Boolean field (alias for switch). */
|
|
730
|
+
boolean: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Checkbox field. */
|
|
731
|
+
checkbox: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Radio button group field. */
|
|
732
|
+
radio: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, options: FieldOption[], props?: FieldProps<T>) => BaseField<T>; /** Date picker field. */
|
|
733
|
+
date: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Tag input field. */
|
|
734
|
+
tags: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Slug field. */
|
|
735
|
+
slug: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** File upload field. */
|
|
736
|
+
file: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>; /** Hidden field (no UI). */
|
|
737
|
+
hidden: <T extends FieldValues = FieldValues>(name: Path<T>, props?: FieldProps<T>) => BaseField<T>;
|
|
581
738
|
/**
|
|
582
739
|
* Group field for nested objects.
|
|
583
|
-
* Renders itemFields as a sub-grid
|
|
740
|
+
* Renders itemFields as a sub-grid. Child names are relative (e.g. "street"),
|
|
741
|
+
* FormGenerator prefixes them with the group name at render time.
|
|
584
742
|
*
|
|
585
743
|
* @example
|
|
586
744
|
* ```ts
|
|
587
745
|
* field.group("address", "Address", [
|
|
588
746
|
* field.text("street", "Street"),
|
|
589
747
|
* field.text("city", "City"),
|
|
590
|
-
*
|
|
591
|
-
* ], { cols: 3 })
|
|
748
|
+
* ], { cols: 2 })
|
|
592
749
|
* ```
|
|
593
750
|
*/
|
|
594
|
-
group: (name:
|
|
751
|
+
group: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, itemFields: BaseField[], props?: FieldProps<T>) => BaseField<T>;
|
|
595
752
|
/**
|
|
596
|
-
* Array/repeatable field.
|
|
597
|
-
* Renders a dynamic list of sub-forms using react-hook-form's useFieldArray.
|
|
753
|
+
* Array/repeatable field backed by react-hook-form's useFieldArray.
|
|
598
754
|
*
|
|
599
755
|
* @example
|
|
600
756
|
* ```ts
|
|
@@ -604,41 +760,108 @@ declare const field: {
|
|
|
604
760
|
* ])
|
|
605
761
|
* ```
|
|
606
762
|
*/
|
|
607
|
-
array: (name:
|
|
763
|
+
array: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, itemFields: BaseField[], props?: FieldProps<T>) => BaseField<T>;
|
|
608
764
|
/**
|
|
609
765
|
* Custom field with a render function.
|
|
610
|
-
* Bypasses the component registry
|
|
766
|
+
* Bypasses the component registry — full control over rendering.
|
|
767
|
+
*
|
|
768
|
+
* The render callback receives the complete `FieldComponentProps` including
|
|
769
|
+
* `fieldId`, `errorId`, `shouldShowError`, `error`, `rules`, and `control`.
|
|
770
|
+
*
|
|
771
|
+
* Use `shouldShowError` (not `!!error`) to drive `aria-invalid` and error
|
|
772
|
+
* visibility so timing mirrors the CSS `:user-invalid` pseudo-class.
|
|
611
773
|
*
|
|
612
774
|
* @example
|
|
613
|
-
* ```
|
|
614
|
-
* field.custom("skills", "Skills", ({ control,
|
|
615
|
-
* <
|
|
775
|
+
* ```tsx
|
|
776
|
+
* field.custom("skills", "Skills", ({ control, shouldShowError, errorId, error, fieldId }) => (
|
|
777
|
+
* <div>
|
|
778
|
+
* <SkillSelector
|
|
779
|
+
* id={fieldId}
|
|
780
|
+
* control={control}
|
|
781
|
+
* aria-invalid={shouldShowError || undefined}
|
|
782
|
+
* aria-errormessage={shouldShowError ? errorId : undefined}
|
|
783
|
+
* />
|
|
784
|
+
* {shouldShowError && (
|
|
785
|
+
* <p id={errorId} role="alert" className="text-sm text-destructive">
|
|
786
|
+
* {error?.message}
|
|
787
|
+
* </p>
|
|
788
|
+
* )}
|
|
789
|
+
* </div>
|
|
616
790
|
* ))
|
|
617
791
|
* ```
|
|
618
792
|
*/
|
|
619
|
-
custom: (name:
|
|
793
|
+
custom: <T extends FieldValues = FieldValues>(name: Path<T>, label: string, render: (props: FieldComponentProps<T>) => ReactNode, props?: FieldProps<T>) => BaseField<T>;
|
|
794
|
+
/**
|
|
795
|
+
* Returns a typed field builder with `TFieldValues` fixed.
|
|
796
|
+
* Every field name is validated against `Path<TFieldValues>` at the call site —
|
|
797
|
+
* no need to repeat the generic on each individual builder call.
|
|
798
|
+
*
|
|
799
|
+
* @example
|
|
800
|
+
* ```ts
|
|
801
|
+
* interface ContactForm {
|
|
802
|
+
* firstName: string;
|
|
803
|
+
* email: string;
|
|
804
|
+
* address: { street: string; city: string };
|
|
805
|
+
* }
|
|
806
|
+
*
|
|
807
|
+
* const f = field.for<ContactForm>()
|
|
808
|
+
*
|
|
809
|
+
* const schema = defineSchema<ContactForm>({
|
|
810
|
+
* sections: [{
|
|
811
|
+
* fields: [
|
|
812
|
+
* f.text("firstName", "First Name"), // ✓
|
|
813
|
+
* f.email("email", "Email"), // ✓
|
|
814
|
+
* f.text("typo", "Label"), // ✗ TypeScript error
|
|
815
|
+
* ],
|
|
816
|
+
* }],
|
|
817
|
+
* })
|
|
818
|
+
* ```
|
|
819
|
+
*/
|
|
820
|
+
for: <T extends FieldValues>() => {
|
|
821
|
+
text: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
822
|
+
email: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
823
|
+
url: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
824
|
+
tel: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
825
|
+
password: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
826
|
+
number: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
827
|
+
textarea: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
828
|
+
select: (name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>;
|
|
829
|
+
combobox: (name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>;
|
|
830
|
+
multiselect: (name: Path<T>, label: string, options: (FieldOption | FieldOptionGroup)[], props?: FieldProps<T>) => BaseField<T>;
|
|
831
|
+
dependentSelect: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
832
|
+
switch: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
833
|
+
boolean: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
834
|
+
checkbox: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
835
|
+
radio: (name: Path<T>, label: string, options: FieldOption[], props?: FieldProps<T>) => BaseField<T>;
|
|
836
|
+
date: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
837
|
+
tags: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
838
|
+
slug: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
839
|
+
file: (name: Path<T>, label: string, props?: FieldProps<T>) => BaseField<T>;
|
|
840
|
+
hidden: (name: Path<T>, props?: FieldProps<T>) => BaseField<T>;
|
|
841
|
+
group: (name: Path<T>, label: string, itemFields: BaseField[], props?: FieldProps<T>) => BaseField<T>;
|
|
842
|
+
array: (name: Path<T>, label: string, itemFields: BaseField[], props?: FieldProps<T>) => BaseField<T>;
|
|
843
|
+
custom: (name: Path<T>, label: string, render: (props: FieldComponentProps<T>) => ReactNode, builderProps?: FieldProps<T>) => BaseField<T>;
|
|
844
|
+
};
|
|
620
845
|
};
|
|
621
846
|
/**
|
|
622
847
|
* Create a section definition with sensible defaults.
|
|
623
848
|
*
|
|
624
|
-
* @param id - Unique section identifier
|
|
625
|
-
* @param title - Section title
|
|
626
|
-
* @param fields - Array of field definitions
|
|
627
|
-
* @param props - Additional section configuration
|
|
628
|
-
*
|
|
629
849
|
* @example
|
|
630
850
|
* ```ts
|
|
631
851
|
* section("personal", "Personal Info", [
|
|
632
852
|
* field.text("name", "Name", { required: true }),
|
|
633
853
|
* field.email("email", "Email"),
|
|
634
|
-
* ], { cols: 2
|
|
854
|
+
* ], { cols: 2 })
|
|
635
855
|
* ```
|
|
636
856
|
*/
|
|
637
857
|
declare function section<TFieldValues extends FieldValues = FieldValues>(id: string, title: string, fields: BaseField<TFieldValues>[], props?: SectionProps<TFieldValues>): Section<TFieldValues>;
|
|
638
858
|
/**
|
|
639
859
|
* Create a section without a title (transparent section).
|
|
640
860
|
* Useful for grouping fields without visual separation.
|
|
861
|
+
*
|
|
862
|
+
* Accepts `BaseField[]` (no generic) so mixed-type field arrays don't trigger
|
|
863
|
+
* conflicting type inference across different field name generics.
|
|
641
864
|
*/
|
|
642
|
-
declare function sectionUntitled
|
|
865
|
+
declare function sectionUntitled(fields: BaseField[], props?: Omit<SectionProps, "variant">): Section;
|
|
643
866
|
//#endregion
|
|
644
|
-
export { type BaseField, type ClassValue, type Condition, type ConditionConfig, type ConditionRule, type DefaultLayoutProps, type DefineField, type FieldOption, type FieldOptionGroup, type FieldType, type FormSchema, type GridLayoutProps, type InferSchemaValues, type LayoutComponentProps, type LayoutType, type SchemaFieldNames, type Section, type SectionLayoutProps, type SectionRenderProps, type Variant, buildValidationRules, cn, defineField, defineSchema, defineSection, evaluateCondition, extractDefaultValues, extractWatchNames, field, section, sectionUntitled };
|
|
867
|
+
export { type BaseField, type ClassValue, type Condition, type ConditionConfig, type ConditionRule, type DefaultLayoutProps, type DefineField, type FieldMeta, type FieldOption, type FieldOptionGroup, type FieldType, type FormSchema, type GridLayoutProps, type InferSchemaValues, type LayoutComponentProps, type LayoutType, type PatternRuleObject, type SchemaFieldNames, type Section, type SectionLayoutProps, type SectionRenderProps, type ValidationRuleObject, type Variant, buildValidationRules, cn, defineField, defineSchema, defineSection, evaluateCondition, extendSection, extractDefaultValues, extractWatchNames, field, flattenSchema, isArrayField, isChoiceField, isConditionalField, isContainerField, isDateField, isDynamicField, isNumericField, isTextField, mergeSchemas, omitFields, pickFields, section, sectionUntitled };
|