@buildnbuzz/buzzform 0.1.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.
@@ -0,0 +1,1136 @@
1
+ import { ReactNode, ComponentType, FormEvent } from 'react';
2
+ import { ZodSchema } from 'zod';
3
+
4
+ /**
5
+ * Context passed to validation functions.
6
+ */
7
+ interface ValidationContext<TData = Record<string, unknown>> {
8
+ /** Complete form data */
9
+ data: TData;
10
+ /** Sibling field data at the same level */
11
+ siblingData: Record<string, unknown>;
12
+ /** Path segments to this field */
13
+ path: string[];
14
+ }
15
+ /**
16
+ * Validation function return type.
17
+ * - `true` = valid
18
+ * - `string` = error message
19
+ */
20
+ type ValidationResult = true | string | Promise<true | string>;
21
+ /**
22
+ * Custom validation function.
23
+ */
24
+ type ValidationFn<TValue = unknown, TData = Record<string, unknown>> = (value: TValue, context: ValidationContext<TData>) => ValidationResult;
25
+ /**
26
+ * Context for conditional field display.
27
+ */
28
+ interface ConditionContext {
29
+ /** Current operation */
30
+ operation: 'create' | 'update' | 'read';
31
+ /** Path segments to this field */
32
+ path: string[];
33
+ }
34
+ /**
35
+ * Condition function to control field visibility.
36
+ */
37
+ type FieldCondition<TData = Record<string, unknown>> = (data: TData, siblingData: Record<string, unknown>, context: ConditionContext) => boolean;
38
+ /**
39
+ * Props passed to a full custom field component.
40
+ * Use this when you want complete control over the field rendering.
41
+ */
42
+ interface FieldComponentProps<TValue = unknown, TField = Field> {
43
+ /** The field configuration */
44
+ field: TField;
45
+ /** Field path (e.g., "email", "address.city") */
46
+ path: string;
47
+ /** Generated HTML id for the field input */
48
+ id: string;
49
+ /** Form adapter instance */
50
+ form: FormAdapter;
51
+ /** Current field value */
52
+ value: TValue;
53
+ /** Update the field value */
54
+ onChange: (value: TValue) => void;
55
+ /** Mark field as touched */
56
+ onBlur: () => void;
57
+ /** Whether the field is disabled */
58
+ disabled: boolean;
59
+ /** Whether the field is read-only */
60
+ readOnly: boolean;
61
+ /** First error message, if any */
62
+ error?: string;
63
+ /** Whether the field should auto-focus */
64
+ autoFocus?: boolean;
65
+ }
66
+ /**
67
+ * Props passed to a custom input component.
68
+ * Use this when you only want to customize the input, keeping the standard wrapper.
69
+ */
70
+ interface FieldInputProps<TValue = unknown, TField = Field> {
71
+ /** The field configuration (access min, max, placeholder, etc.) */
72
+ field: TField;
73
+ /** Field path (e.g., "email", "address.city") */
74
+ path: string;
75
+ /** Generated HTML id (must be used for label association) */
76
+ id: string;
77
+ /** Field name for HTML form/autofill (e.g., "email", "password") */
78
+ name: string;
79
+ /** Current field value */
80
+ value: TValue;
81
+ /** Update the field value */
82
+ onChange: (value: TValue) => void;
83
+ /** Mark field as touched */
84
+ onBlur: () => void;
85
+ /** Whether the field is disabled */
86
+ disabled: boolean;
87
+ /** Whether the field is read-only */
88
+ readOnly: boolean;
89
+ /** First error message, if any */
90
+ error?: string;
91
+ /** Whether the field should auto-focus */
92
+ autoFocus?: boolean;
93
+ /** Live validation state (if enabled) */
94
+ validation?: {
95
+ /** Whether validation is currently running */
96
+ isChecking: boolean;
97
+ /** Whether the field passes validation */
98
+ isValid: boolean;
99
+ /** Validation message */
100
+ message?: string;
101
+ };
102
+ }
103
+ /**
104
+ * Custom input render function.
105
+ */
106
+ type FieldInputRenderFn<TValue = unknown, TField = Field> = (props: FieldInputProps<TValue, TField>) => ReactNode;
107
+ /**
108
+ * Styling options for a field.
109
+ */
110
+ interface FieldStyle {
111
+ /** Additional CSS class for the field wrapper */
112
+ className?: string;
113
+ /** Field width (e.g., '50%', '200px', 200) */
114
+ width?: string | number;
115
+ }
116
+ /**
117
+ * Base field interface. All field types extend this.
118
+ */
119
+ interface BaseField<TValue = unknown, TData = Record<string, unknown>> {
120
+ /** Field name - becomes the key in form data */
121
+ name: string;
122
+ /** Explicit HTML id override (defaults to path-based generation) */
123
+ id?: string;
124
+ /** Display label (false to hide, ReactNode for custom) */
125
+ label?: string | ReactNode | false;
126
+ /** Help text shown below the field */
127
+ description?: string | ReactNode;
128
+ /** Placeholder text */
129
+ placeholder?: string;
130
+ /** Whether the field is required */
131
+ required?: boolean;
132
+ /**
133
+ * Disable user interaction.
134
+ * Can be a boolean or a function for conditional disabling.
135
+ * @example disabled: (data) => !data.country // Disable until country is selected
136
+ */
137
+ disabled?: boolean | ((data: TData, siblingData: Record<string, unknown>) => boolean);
138
+ /** Hide the field from the form UI (static or conditional) */
139
+ hidden?: boolean | ((data: TData, siblingData: Record<string, unknown>) => boolean);
140
+ /**
141
+ * Make field read-only.
142
+ * Can be a boolean or a function for conditional read-only state.
143
+ * @example readOnly: (data) => data.status === 'published'
144
+ */
145
+ readOnly?: boolean | ((data: TData, siblingData: Record<string, unknown>) => boolean);
146
+ /** Default value (static only, async handled at form level) */
147
+ defaultValue?: TValue;
148
+ /** Direct Zod schema (overrides auto-generated) */
149
+ schema?: ZodSchema<TValue>;
150
+ /** Custom validation function */
151
+ validate?: ValidationFn<TValue, TData>;
152
+ /** Condition function to show/hide the field */
153
+ condition?: FieldCondition<TData>;
154
+ /** Styling options */
155
+ style?: FieldStyle;
156
+ /** Full custom component (replaces entire field) */
157
+ component?: ComponentType<FieldComponentProps<TValue>>;
158
+ /** Custom input only (library handles wrapper/label/error) */
159
+ input?: ComponentType<FieldInputProps<TValue>> | FieldInputRenderFn<TValue>;
160
+ /** HTML autocomplete attribute */
161
+ autoComplete?: string;
162
+ /** Custom metadata */
163
+ meta?: Record<string, unknown>;
164
+ }
165
+ /**
166
+ * Text field
167
+ */
168
+ interface TextField extends BaseField<string> {
169
+ type: 'text';
170
+ /** Minimum length */
171
+ minLength?: number;
172
+ /** Maximum length */
173
+ maxLength?: number;
174
+ /** Pattern to match (string or RegExp) */
175
+ pattern?: string | RegExp;
176
+ /** Trim whitespace */
177
+ trim?: boolean;
178
+ /** UI options */
179
+ ui?: {
180
+ /** Show copy button to copy value to clipboard */
181
+ copyable?: boolean;
182
+ };
183
+ }
184
+ /**
185
+ * Email field
186
+ */
187
+ interface EmailField extends BaseField<string> {
188
+ type: 'email';
189
+ /** Minimum length */
190
+ minLength?: number;
191
+ /** Maximum length */
192
+ maxLength?: number;
193
+ /** Pattern to match (string or RegExp) */
194
+ pattern?: string | RegExp;
195
+ /** Trim whitespace */
196
+ trim?: boolean;
197
+ /** UI options */
198
+ ui?: {
199
+ /** Show copy button to copy value to clipboard */
200
+ copyable?: boolean;
201
+ };
202
+ }
203
+ /**
204
+ * Password field
205
+ */
206
+ interface PasswordField extends BaseField<string> {
207
+ type: 'password';
208
+ /** Minimum length (default: 8) */
209
+ minLength?: number;
210
+ /** Maximum length */
211
+ maxLength?: number;
212
+ /** Password strength criteria */
213
+ criteria?: {
214
+ requireUppercase?: boolean;
215
+ requireLowercase?: boolean;
216
+ requireNumber?: boolean;
217
+ requireSpecial?: boolean;
218
+ };
219
+ /** UI options */
220
+ ui?: {
221
+ /** Show strength indicator */
222
+ strengthIndicator?: boolean;
223
+ /** Show requirements checklist */
224
+ showRequirements?: boolean;
225
+ /** Allow password generation */
226
+ allowGenerate?: boolean;
227
+ /** Show copy button to copy value to clipboard */
228
+ copyable?: boolean;
229
+ };
230
+ }
231
+ /**
232
+ * Textarea field
233
+ */
234
+ interface TextareaField extends BaseField<string> {
235
+ type: 'textarea';
236
+ /** Minimum length */
237
+ minLength?: number;
238
+ /** Maximum length */
239
+ maxLength?: number;
240
+ /** Number of visible rows */
241
+ rows?: number;
242
+ /** Auto-resize based on content */
243
+ autoResize?: boolean;
244
+ /** UI options */
245
+ ui?: {
246
+ /** Show copy button to copy value to clipboard */
247
+ copyable?: boolean;
248
+ };
249
+ }
250
+ /**
251
+ * Number field
252
+ */
253
+ interface NumberField extends BaseField<number> {
254
+ type: 'number';
255
+ /** Minimum value */
256
+ min?: number;
257
+ /** Maximum value */
258
+ max?: number;
259
+ /** Decimal precision */
260
+ precision?: number;
261
+ /** UI options */
262
+ ui?: {
263
+ /** Step increment */
264
+ step?: number;
265
+ /** Visual variant */
266
+ variant?: 'default' | 'stacked' | 'pill' | 'plain';
267
+ /** Prefix (e.g., "$") */
268
+ prefix?: string;
269
+ /** Suffix (e.g., "%", "kg") */
270
+ suffix?: string;
271
+ /** Use thousand separator */
272
+ thousandSeparator?: boolean | string;
273
+ /** Hide stepper buttons */
274
+ hideSteppers?: boolean;
275
+ /** Show copy button to copy value to clipboard */
276
+ copyable?: boolean;
277
+ };
278
+ }
279
+ /**
280
+ * Date field
281
+ */
282
+ interface DateField extends BaseField<Date> {
283
+ type: 'date';
284
+ /** Minimum date */
285
+ minDate?: Date | string;
286
+ /** Maximum date */
287
+ maxDate?: Date | string;
288
+ /** UI options */
289
+ ui?: {
290
+ /** Display format (date-fns format) */
291
+ format?: string;
292
+ /** Manual input format */
293
+ inputFormat?: string;
294
+ /** Quick date presets */
295
+ presets?: boolean | Array<{
296
+ label: string;
297
+ value: Date | (() => Date);
298
+ }>;
299
+ };
300
+ }
301
+ /**
302
+ * Datetime field
303
+ */
304
+ interface DatetimeField extends BaseField<Date> {
305
+ type: 'datetime';
306
+ /** Minimum date */
307
+ minDate?: Date | string;
308
+ /** Maximum date */
309
+ maxDate?: Date | string;
310
+ /** UI options */
311
+ ui?: {
312
+ /** Display format (date-fns format) */
313
+ format?: string;
314
+ /** Manual input format */
315
+ inputFormat?: string;
316
+ /** Time picker config */
317
+ timePicker?: boolean | {
318
+ interval?: number;
319
+ use24hr?: boolean;
320
+ includeSeconds?: boolean;
321
+ };
322
+ /** Quick date presets */
323
+ presets?: boolean | Array<{
324
+ label: string;
325
+ value: Date | (() => Date);
326
+ }>;
327
+ };
328
+ }
329
+ /**
330
+ * Select option
331
+ */
332
+ interface SelectOption {
333
+ /** Display label */
334
+ label: string | ReactNode;
335
+ /** Option value (supports string, number, or boolean for API compatibility) */
336
+ value: string | number | boolean;
337
+ /** Optional description */
338
+ description?: string | ReactNode;
339
+ /** Optional icon */
340
+ icon?: ReactNode;
341
+ /** Optional badge */
342
+ badge?: string;
343
+ /** Whether disabled */
344
+ disabled?: boolean;
345
+ }
346
+ /**
347
+ * Select field
348
+ */
349
+ interface SelectField extends BaseField<string | string[] | number | number[] | boolean> {
350
+ type: 'select';
351
+ /** Options (static, string array, or async with context for dependent dropdowns) */
352
+ options: SelectOption[] | string[] | ((context: ValidationContext) => Promise<SelectOption[]>);
353
+ /**
354
+ * Field paths that trigger options refetch when changed.
355
+ * Required when using async options that depend on other field values.
356
+ * @example dependencies: ['country', 'state'] // Refetch when country or state changes
357
+ */
358
+ dependencies?: string[];
359
+ /** Allow multiple selections */
360
+ hasMany?: boolean;
361
+ /** UI options */
362
+ ui?: {
363
+ /** Enable search */
364
+ isSearchable?: boolean;
365
+ /** Show clear button */
366
+ isClearable?: boolean;
367
+ /** Max visible chips (for hasMany) */
368
+ maxVisibleChips?: number;
369
+ /** Empty state message */
370
+ emptyMessage?: string;
371
+ /** Loading message */
372
+ loadingMessage?: string;
373
+ };
374
+ }
375
+ /**
376
+ * Checkbox field
377
+ */
378
+ interface CheckboxField extends BaseField<boolean> {
379
+ type: 'checkbox';
380
+ }
381
+ /**
382
+ * Switch field
383
+ */
384
+ interface SwitchField extends BaseField<boolean> {
385
+ type: 'switch';
386
+ /** UI options */
387
+ ui?: {
388
+ /**
389
+ * Switch position relative to label.
390
+ * - 'between': Label on left, switch on right with full-width spacing (default)
391
+ * - 'start': Switch on left, label on right
392
+ * - 'end': Label on left, switch on right
393
+ */
394
+ alignment?: 'start' | 'end' | 'between';
395
+ };
396
+ }
397
+ /**
398
+ * Radio field - single selection from a group of options
399
+ */
400
+ interface RadioField extends BaseField<string | number | boolean> {
401
+ type: 'radio';
402
+ /** Static array or async function for options */
403
+ options: SelectOption[] | string[] | ((context: ValidationContext) => Promise<SelectOption[]>);
404
+ /** Paths that trigger options refetch when changed */
405
+ dependencies?: string[];
406
+ ui?: {
407
+ /** Visual variant ('default' or 'card') */
408
+ variant?: 'default' | 'card';
409
+ /** Layout direction for 'default' variant */
410
+ direction?: 'vertical' | 'horizontal';
411
+ /** Grid columns (responsive, 1 on mobile) */
412
+ columns?: 1 | 2 | 3 | 4;
413
+ /** Card settings (for variant: 'card') */
414
+ card?: {
415
+ /** Size preset ('sm', 'md', 'lg') */
416
+ size?: 'sm' | 'md' | 'lg';
417
+ /** Show border around cards */
418
+ bordered?: boolean;
419
+ };
420
+ };
421
+ }
422
+ /**
423
+ * Tags field - chip-based multi-value string input
424
+ */
425
+ interface TagsField extends BaseField<string[]> {
426
+ type: 'tags';
427
+ /** Minimum number of tags */
428
+ minTags?: number;
429
+ /** Maximum number of tags */
430
+ maxTags?: number;
431
+ /** Maximum character length per tag */
432
+ maxTagLength?: number;
433
+ /** Allow duplicate tag values (default: false) */
434
+ allowDuplicates?: boolean;
435
+ /** UI options */
436
+ ui?: {
437
+ /** Keys that create a new tag (default: ['enter']) */
438
+ delimiters?: ('enter' | 'comma' | 'space' | 'tab')[];
439
+ /** Visual variant */
440
+ variant?: 'chips' | 'pills' | 'inline';
441
+ /** Show copy button to copy all tags */
442
+ copyable?: boolean;
443
+ };
444
+ }
445
+ /**
446
+ * Upload field
447
+ */
448
+ interface UploadField extends BaseField<File | File[] | string | string[]> {
449
+ type: 'upload';
450
+ /** Allow multiple files */
451
+ hasMany?: boolean;
452
+ /** Minimum files (when hasMany) */
453
+ minFiles?: number;
454
+ /** Maximum files (when hasMany) */
455
+ maxFiles?: number;
456
+ /** Maximum file size in bytes */
457
+ maxSize?: number;
458
+ /** UI options */
459
+ ui?: {
460
+ /** MIME type filter */
461
+ accept?: string;
462
+ /** Visual variant */
463
+ variant?: 'dropzone' | 'avatar' | 'inline' | 'gallery';
464
+ /** Shape (for avatar) */
465
+ shape?: 'circle' | 'square' | 'rounded';
466
+ /** Size preset */
467
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
468
+ /** Enable cropping */
469
+ crop?: boolean | {
470
+ aspectRatio?: number;
471
+ circular?: boolean;
472
+ };
473
+ /** Show progress */
474
+ showProgress?: boolean;
475
+ /** Dropzone text */
476
+ dropzoneText?: string;
477
+ };
478
+ }
479
+ /**
480
+ * Group field - wraps fields in a named object
481
+ */
482
+ interface GroupField extends BaseField<Record<string, unknown>> {
483
+ type: 'group';
484
+ /** Nested fields */
485
+ fields: Field[];
486
+ /** UI options */
487
+ ui?: {
488
+ /** Visual variant ('card', 'flat', 'ghost', 'bordered') */
489
+ variant?: 'card' | 'flat' | 'ghost' | 'bordered';
490
+ /** Spacing between fields ('sm', 'md', 'lg') */
491
+ spacing?: 'sm' | 'md' | 'lg';
492
+ /** Start in collapsed state */
493
+ collapsed?: boolean;
494
+ /** Show error badge */
495
+ showErrorBadge?: boolean;
496
+ };
497
+ }
498
+ /**
499
+ * Array field - repeatable fields
500
+ */
501
+ interface ArrayField extends BaseField<unknown[]> {
502
+ type: 'array';
503
+ /** Fields for each array item */
504
+ fields: Field[];
505
+ /** Minimum items */
506
+ minRows?: number;
507
+ /** Maximum items */
508
+ maxRows?: number;
509
+ /** UI options */
510
+ ui?: {
511
+ /** Allow reordering via drag & drop (default: true) */
512
+ isSortable?: boolean;
513
+ /** Add button label (default: "Add Item") */
514
+ addLabel?: string;
515
+ /** Field name to use for row labels (falls back to first named field) */
516
+ rowLabelField?: string;
517
+ /** Start the array container collapsed */
518
+ collapsed?: boolean;
519
+ /** Start individual rows collapsed */
520
+ rowsCollapsed?: boolean;
521
+ /** Show confirmation dialog before deleting all items */
522
+ confirmDelete?: boolean;
523
+ /** Custom empty state message */
524
+ emptyMessage?: string;
525
+ /** Show error count badge in header (default: true) */
526
+ showErrorBadge?: boolean;
527
+ };
528
+ }
529
+ /**
530
+ * Row field - horizontal layout container
531
+ */
532
+ interface RowField {
533
+ type: 'row';
534
+ /** Fields to display in a row */
535
+ fields: Field[];
536
+ /** Layout options */
537
+ ui?: {
538
+ /** Gap between fields (e.g., 4, '1rem', '16px') */
539
+ gap?: number | string;
540
+ /** Vertical alignment */
541
+ align?: 'start' | 'center' | 'end' | 'stretch';
542
+ /** Horizontal distribution */
543
+ justify?: 'start' | 'center' | 'end' | 'between';
544
+ /** Allow wrapping to next line */
545
+ wrap?: boolean;
546
+ /** Responsive behavior: 'stack' collapses to vertical on mobile */
547
+ responsive?: boolean | 'stack';
548
+ };
549
+ }
550
+ /**
551
+ * Tab configuration
552
+ */
553
+ interface Tab {
554
+ /** Tab name (if provided, creates nested object) */
555
+ name?: string;
556
+ /** Tab label */
557
+ label: string | ReactNode;
558
+ /** Fields in this tab */
559
+ fields: Field[];
560
+ /** Tab description */
561
+ description?: string | ReactNode;
562
+ /** Tab icon */
563
+ icon?: ReactNode;
564
+ /** Whether this tab is disabled */
565
+ disabled?: boolean;
566
+ }
567
+ /**
568
+ * Tabs field - tabbed container
569
+ */
570
+ interface TabsField {
571
+ type: 'tabs';
572
+ /** Tab definitions */
573
+ tabs: Tab[];
574
+ /** UI options */
575
+ ui?: {
576
+ /** Default active tab (index or name) */
577
+ defaultTab?: number | string;
578
+ /** Show error badge on tabs with validation errors */
579
+ showErrorBadge?: boolean;
580
+ /** Visual variant */
581
+ variant?: 'default' | 'line';
582
+ /** Spacing between fields within tabs */
583
+ spacing?: 'sm' | 'md' | 'lg';
584
+ };
585
+ }
586
+ /**
587
+ * Collapsible field - expandable container
588
+ */
589
+ interface CollapsibleField {
590
+ type: 'collapsible';
591
+ /** Container label */
592
+ label: string;
593
+ /** Nested fields */
594
+ fields: Field[];
595
+ /** Start collapsed */
596
+ collapsed?: boolean;
597
+ /** UI options */
598
+ ui?: {
599
+ /** Visual variant ('card', 'flat', 'ghost', 'bordered') */
600
+ variant?: 'card' | 'flat' | 'ghost' | 'bordered';
601
+ /** Spacing between fields ('sm', 'md', 'lg') */
602
+ spacing?: 'sm' | 'md' | 'lg';
603
+ /** Show error badge */
604
+ showErrorBadge?: boolean;
605
+ /** Optional description */
606
+ description?: string;
607
+ /** Optional icon */
608
+ icon?: ReactNode;
609
+ };
610
+ /** Styling options */
611
+ style?: FieldStyle;
612
+ }
613
+ /**
614
+ * Union of all field types.
615
+ */
616
+ type Field = TextField | EmailField | PasswordField | TextareaField | NumberField | DateField | DatetimeField | SelectField | CheckboxField | SwitchField | RadioField | TagsField | UploadField | GroupField | ArrayField | RowField | TabsField | CollapsibleField;
617
+ /**
618
+ * Extract field type from a Field.
619
+ */
620
+ type FieldType = Field['type'];
621
+ /**
622
+ * Data fields (fields that hold values).
623
+ */
624
+ type DataField = TextField | EmailField | PasswordField | TextareaField | NumberField | DateField | DatetimeField | SelectField | CheckboxField | SwitchField | RadioField | TagsField | UploadField | GroupField | ArrayField;
625
+ /**
626
+ * Layout fields (fields that organize other fields).
627
+ */
628
+ type LayoutField = RowField | TabsField | CollapsibleField;
629
+
630
+ /**
631
+ * A Zod schema with field definitions attached.
632
+ * Created by `createSchema([...])` for type inference and field rendering.
633
+ */
634
+ type BuzzFormSchema<TData = unknown, TFields extends readonly Field[] = Field[]> = ZodSchema<TData> & {
635
+ fields: TFields;
636
+ };
637
+ /**
638
+ * Form-level behavior settings.
639
+ * These control how the form behaves during user interaction.
640
+ */
641
+ interface FormSettings {
642
+ /**
643
+ * Prevent submission when form has no changes.
644
+ * When true, handleSubmit is a no-op if formState.isDirty is false.
645
+ * @default false
646
+ */
647
+ submitOnlyWhenDirty?: boolean;
648
+ /**
649
+ * Auto-focus first visible, enabled field on mount.
650
+ * Note: This is a hint to the renderer component.
651
+ * @default false
652
+ */
653
+ autoFocus?: boolean;
654
+ }
655
+ /**
656
+ * Global form configuration set at the provider level.
657
+ * These defaults apply to all forms unless overridden.
658
+ *
659
+ * @example
660
+ * <FormProvider
661
+ * adapter={useRhfAdapter}
662
+ * resolver={zodResolver}
663
+ * mode="onBlur"
664
+ * >
665
+ * <App />
666
+ * </FormProvider>
667
+ */
668
+ interface FormConfig {
669
+ /**
670
+ * Adapter factory function.
671
+ * Determines which form library handles state management.
672
+ *
673
+ * @example
674
+ * import { useRhfAdapter } from '@buildnbuzz/buzzform/rhf';
675
+ * adapter: useRhfAdapter
676
+ */
677
+ adapter: AdapterFactory;
678
+ /**
679
+ * Validation resolver factory.
680
+ * Converts a Zod schema into a form-compatible resolver.
681
+ * @default zodResolver
682
+ *
683
+ * @example
684
+ * import { zodResolver } from '@buildnbuzz/buzzform/resolvers/zod';
685
+ * resolver: zodResolver
686
+ */
687
+ resolver?: <T>(schema: ZodSchema<T>) => Resolver<T>;
688
+ /**
689
+ * Default validation mode for all forms.
690
+ * - 'onChange': Validate on every change (default)
691
+ * - 'onBlur': Validate when fields lose focus
692
+ * - 'onSubmit': Validate only on submit
693
+ * @default 'onChange'
694
+ */
695
+ mode?: 'onChange' | 'onBlur' | 'onSubmit';
696
+ /**
697
+ * When to re-validate after initial validation error.
698
+ * - 'onChange': Re-validate on every change (default)
699
+ * - 'onBlur': Re-validate when fields lose focus
700
+ * - 'onSubmit': Re-validate only on submit
701
+ * @default 'onChange'
702
+ */
703
+ reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit';
704
+ }
705
+ /**
706
+ * Options passed to useForm hook.
707
+ *
708
+ * @example
709
+ * const loginSchema = createSchema([
710
+ * { type: 'email', name: 'email', required: true },
711
+ * { type: 'password', name: 'password', required: true },
712
+ * ]);
713
+ *
714
+ * const form = useForm({
715
+ * schema: loginSchema,
716
+ * onSubmit: async (data) => {
717
+ * await auth.login(data);
718
+ * },
719
+ * });
720
+ */
721
+ interface UseFormOptions<TData = Record<string, unknown>> {
722
+ /**
723
+ * Zod schema for validation.
724
+ * - Use `createSchema([...])` for schema with fields attached (recommended)
725
+ * - Use raw Zod schema for validation-only (manual field rendering)
726
+ */
727
+ schema: ZodSchema<TData> | BuzzFormSchema<TData>;
728
+ /**
729
+ * Default values for the form.
730
+ * If not provided and schema has fields, extracted from field definitions.
731
+ * Can be static, sync function, or async function.
732
+ */
733
+ defaultValues?: TData | (() => TData) | (() => Promise<TData>);
734
+ /**
735
+ * Form submission handler.
736
+ * Called with validated data after all validation passes.
737
+ */
738
+ onSubmit?: (data: TData) => Promise<void> | void;
739
+ /**
740
+ * Override the adapter for this specific form.
741
+ * Uses provider's adapter if not specified.
742
+ */
743
+ adapter?: AdapterFactory;
744
+ /**
745
+ * Override the validation mode for this form.
746
+ * Uses provider's mode if not specified.
747
+ */
748
+ mode?: 'onChange' | 'onBlur' | 'onSubmit';
749
+ /**
750
+ * Override the re-validation mode for this form.
751
+ * Uses provider's reValidateMode if not specified.
752
+ */
753
+ reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit';
754
+ /**
755
+ * Form behavior settings.
756
+ */
757
+ settings?: FormSettings;
758
+ }
759
+
760
+ /**
761
+ * Represents the current reactive state of a form.
762
+ * Adapters must ensure this triggers re-renders when values change.
763
+ *
764
+ * This is the MINIMUM state required for BuzzForm to work.
765
+ * Custom adapters must provide all these properties.
766
+ */
767
+ interface FormState {
768
+ /** True while the form is being submitted */
769
+ isSubmitting: boolean;
770
+ /** True while validation is running (async validators) */
771
+ isValidating: boolean;
772
+ /** True if any field has been modified from its default value */
773
+ isDirty: boolean;
774
+ /** True if all validations pass (no errors) */
775
+ isValid: boolean;
776
+ /** True while async default values are being resolved */
777
+ isLoading: boolean;
778
+ /**
779
+ * Field-level errors.
780
+ * Key is the field path (e.g., "email", "address.city", "items.0.name")
781
+ * Value is the error message(s)
782
+ *
783
+ * NOTE: Path format uses dot notation. Array indices use numbers (items.0.name).
784
+ */
785
+ errors: Record<string, string | string[] | undefined>;
786
+ /** Map of field paths to whether they've been modified */
787
+ dirtyFields: Record<string, boolean>;
788
+ /** Map of field paths to whether they've been touched (blurred) */
789
+ touchedFields: Record<string, boolean>;
790
+ /** Number of times the form has been submitted */
791
+ submitCount: number;
792
+ }
793
+ /**
794
+ * Options when programmatically setting a field value.
795
+ */
796
+ interface SetValueOptions {
797
+ /** Run validation after setting the value (default: adapter-specific) */
798
+ shouldValidate?: boolean;
799
+ /** Mark the field as dirty (default: true) */
800
+ shouldDirty?: boolean;
801
+ /** Mark the field as touched (default: false) */
802
+ shouldTouch?: boolean;
803
+ }
804
+ /**
805
+ * Represents a field-level error.
806
+ */
807
+ interface FieldError {
808
+ /** Error type (e.g., 'required', 'pattern', 'server', 'custom') */
809
+ type?: string;
810
+ /** Human-readable error message */
811
+ message: string;
812
+ }
813
+ /**
814
+ * Result from a validation resolver.
815
+ */
816
+ interface ResolverResult<TData> {
817
+ /** Parsed/transformed values (if validation passes) */
818
+ values?: TData;
819
+ /** Field errors (if validation fails) */
820
+ errors?: Record<string, FieldError>;
821
+ }
822
+ /**
823
+ * A validation resolver function.
824
+ * Adapters use this to validate form values against a schema.
825
+ *
826
+ * @example
827
+ * // Zod resolver
828
+ * const zodResolver = (schema) => async (values) => {
829
+ * const result = schema.safeParse(values);
830
+ * if (result.success) return { values: result.data };
831
+ * return { errors: mapZodErrors(result.error) };
832
+ * };
833
+ */
834
+ type Resolver<TData> = (values: TData) => Promise<ResolverResult<TData>> | ResolverResult<TData>;
835
+ /**
836
+ * Helper methods for manipulating array fields.
837
+ * All methods operate on a field path (e.g., "items", "users.0.tags").
838
+ *
839
+ * This is REQUIRED for ArrayField to work. If your custom adapter doesn't
840
+ * support arrays, you can implement these as no-ops or throw errors.
841
+ */
842
+ interface ArrayHelpers {
843
+ /**
844
+ * Get array items with stable IDs for React keys.
845
+ * @param path - Field path to the array
846
+ * @returns Array of items, each with an `id` property for React keys
847
+ */
848
+ fields: <T = unknown>(path: string) => Array<T & {
849
+ id: string;
850
+ }>;
851
+ /**
852
+ * Add an item to the end of the array.
853
+ */
854
+ append: (path: string, value: unknown) => void;
855
+ /**
856
+ * Add an item to the beginning of the array.
857
+ */
858
+ prepend: (path: string, value: unknown) => void;
859
+ /**
860
+ * Insert an item at a specific index.
861
+ */
862
+ insert: (path: string, index: number, value: unknown) => void;
863
+ /**
864
+ * Remove an item at a specific index.
865
+ */
866
+ remove: (path: string, index: number) => void;
867
+ /**
868
+ * Move an item from one index to another.
869
+ * Used for drag-and-drop reordering.
870
+ */
871
+ move: (path: string, from: number, to: number) => void;
872
+ /**
873
+ * Swap two items by their indices.
874
+ */
875
+ swap: (path: string, indexA: number, indexB: number) => void;
876
+ /**
877
+ * Replace the entire array with new values.
878
+ */
879
+ replace: (path: string, values: unknown[]) => void;
880
+ /**
881
+ * Update an item at a specific index.
882
+ */
883
+ update: (path: string, index: number, value: unknown) => void;
884
+ }
885
+ /**
886
+ * Base options passed to any adapter when creating a form instance.
887
+ * Adapters can extend this with library-specific options.
888
+ *
889
+ * @typeParam TData - The shape of form data
890
+ */
891
+ interface AdapterOptions<TData = Record<string, unknown>> {
892
+ /**
893
+ * Initial values for the form.
894
+ * Can be:
895
+ * - A static object
896
+ * - A sync function returning values
897
+ * - An async function returning values (form shows loading state)
898
+ */
899
+ defaultValues?: TData | (() => TData) | (() => Promise<TData>);
900
+ /**
901
+ * Controlled values - when provided, form becomes controlled.
902
+ * Changes to this prop will update form values.
903
+ */
904
+ values?: TData;
905
+ /**
906
+ * Validation resolver.
907
+ * Called to validate form values before submission and optionally on change/blur.
908
+ */
909
+ resolver?: Resolver<TData>;
910
+ /**
911
+ * When to run validation.
912
+ * - 'onChange': Validate on every value change
913
+ * - 'onBlur': Validate when fields lose focus
914
+ * - 'onSubmit': Validate only on submit
915
+ * - 'all': Validate on all events
916
+ *
917
+ * NOTE: Not all adapters support all modes. Check adapter documentation.
918
+ */
919
+ mode?: 'onChange' | 'onBlur' | 'onSubmit' | 'all';
920
+ /**
921
+ * When to re-validate after initial error.
922
+ * NOTE: This is optional. Some form libraries don't have this concept.
923
+ */
924
+ reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit';
925
+ /**
926
+ * Callback when form is submitted (after validation passes).
927
+ */
928
+ onSubmit?: (values: TData) => Promise<void> | void;
929
+ }
930
+ /**
931
+ * The contract any form adapter must fulfill.
932
+ *
933
+ * ## Required vs Optional
934
+ *
935
+ * **Required methods** are the building blocks that BuzzForm needs to function.
936
+ * If any are missing, forms will break.
937
+ *
938
+ * **Optional methods** (marked with `?`) provide enhanced functionality but
939
+ * are not required. BuzzForm will gracefully degrade without them.
940
+ *
941
+ * ## Creating a Custom Adapter
942
+ *
943
+ * To create a custom adapter (e.g., for useActionState, Formik, or vanilla React):
944
+ *
945
+ * 1. Implement all required properties and methods
946
+ * 2. Optionally implement enhanced features
947
+ * 3. Return the adapter from a hook (factory function)
948
+ *
949
+ * @example
950
+ * // Minimal custom adapter skeleton
951
+ * function useMyAdapter<T>(options: AdapterOptions<T>): FormAdapter<T> {
952
+ * const [values, setValues] = useState(options.defaultValues ?? {});
953
+ * const [errors, setErrors] = useState({});
954
+ * const [isSubmitting, setIsSubmitting] = useState(false);
955
+ *
956
+ * return {
957
+ * control: null, // Your state/context
958
+ * get formState() { return { ... } },
959
+ * handleSubmit: async (e) => { ... },
960
+ * getValues: () => values,
961
+ * setValue: (name, value) => { ... },
962
+ * reset: (vals) => setValues(vals ?? {}),
963
+ * watch: (name) => name ? values[name] : values,
964
+ * validate: async () => true,
965
+ * setError: (name, error) => { ... },
966
+ * clearErrors: () => setErrors({}),
967
+ * array: createArrayHelpers(...),
968
+ * };
969
+ * }
970
+ *
971
+ * @typeParam TData - The shape of form data
972
+ */
973
+ interface FormAdapter<TData = Record<string, unknown>> {
974
+ /**
975
+ * Form-level behavior settings.
976
+ * Set by useForm after applying FormSettings.
977
+ */
978
+ settings?: FormSettings;
979
+ /**
980
+ * The underlying form library's control/instance.
981
+ *
982
+ * This is passed to field components that need direct access to the form
983
+ * library (e.g., for React Hook Form's Controller).
984
+ *
985
+ * For custom adapters, this can be:
986
+ * - Your state object
987
+ * - A context value
988
+ * - null (if not needed)
989
+ */
990
+ control: unknown;
991
+ /**
992
+ * Current form state.
993
+ * MUST be implemented as a getter to ensure reactivity.
994
+ *
995
+ * @example
996
+ * get formState() {
997
+ * return {
998
+ * isSubmitting: this._isSubmitting,
999
+ * isValidating: false,
1000
+ * isDirty: Object.keys(this._touched).length > 0,
1001
+ * isValid: Object.keys(this._errors).length === 0,
1002
+ * isLoading: false,
1003
+ * errors: this._errors,
1004
+ * dirtyFields: this._dirty,
1005
+ * touchedFields: this._touched,
1006
+ * submitCount: this._submitCount,
1007
+ * };
1008
+ * }
1009
+ */
1010
+ formState: FormState;
1011
+ /**
1012
+ * Submit handler to attach to a form element.
1013
+ * Should prevent default, run validation, and call onSubmit if valid.
1014
+ *
1015
+ * @example
1016
+ * <form onSubmit={adapter.handleSubmit}>
1017
+ */
1018
+ handleSubmit: (e?: FormEvent) => Promise<void> | void;
1019
+ /**
1020
+ * Get all current form values.
1021
+ */
1022
+ getValues: () => TData;
1023
+ /**
1024
+ * Set a single field's value.
1025
+ *
1026
+ * @param name - Field path (e.g., "email", "address.city", "items.0.name")
1027
+ * @param value - New value
1028
+ * @param options - Additional options
1029
+ */
1030
+ setValue: (name: string, value: unknown, options?: SetValueOptions) => void;
1031
+ /**
1032
+ * Reset form to default values or provided values.
1033
+ *
1034
+ * @param values - Optional new values to reset to
1035
+ */
1036
+ reset: (values?: Partial<TData>) => void;
1037
+ /**
1038
+ * Watch one or more field values reactively.
1039
+ * Returns current value(s) and causes re-render when they change.
1040
+ *
1041
+ * @param name - Field path to watch, or undefined for all values
1042
+ * @returns Current value(s)
1043
+ */
1044
+ watch: <T = unknown>(name?: string) => T;
1045
+ /**
1046
+ * Manually trigger validation.
1047
+ *
1048
+ * @param name - Field(s) to validate, or undefined for entire form
1049
+ * @returns True if validation passes
1050
+ */
1051
+ validate: (name?: string | string[]) => Promise<boolean>;
1052
+ /**
1053
+ * Set a field error programmatically.
1054
+ * Useful for server-side validation errors.
1055
+ *
1056
+ * @param name - Field path
1057
+ * @param error - Error details
1058
+ */
1059
+ setError: (name: string, error: FieldError) => void;
1060
+ /**
1061
+ * Clear validation errors.
1062
+ *
1063
+ * @param name - Field(s) to clear, or undefined for all errors
1064
+ */
1065
+ clearErrors: (name?: string | string[]) => void;
1066
+ /**
1067
+ * Helpers for manipulating array fields.
1068
+ * Required if you use ArrayField component.
1069
+ */
1070
+ array: ArrayHelpers;
1071
+ /**
1072
+ * Handle field blur event.
1073
+ * Triggers validation if mode is 'onBlur'.
1074
+ *
1075
+ * If not provided, BuzzForm will not trigger blur-based validation.
1076
+ *
1077
+ * @param name - Field path
1078
+ */
1079
+ onBlur?: (name: string) => void;
1080
+ /**
1081
+ * Get the state of a specific field.
1082
+ * More efficient than accessing the entire formState for single fields.
1083
+ *
1084
+ * @param name - Field path
1085
+ */
1086
+ getFieldState?: (name: string) => {
1087
+ isDirty: boolean;
1088
+ isTouched: boolean;
1089
+ invalid: boolean;
1090
+ error?: string;
1091
+ };
1092
+ /**
1093
+ * Programmatically focus a field.
1094
+ * Useful for accessibility and after adding array items.
1095
+ *
1096
+ * @param name - Field path
1097
+ * @param options - Focus options
1098
+ */
1099
+ setFocus?: (name: string, options?: {
1100
+ shouldSelect?: boolean;
1101
+ }) => void;
1102
+ /**
1103
+ * Unregister a field from the form.
1104
+ * Called when conditional fields are hidden.
1105
+ *
1106
+ * If not provided, hidden fields will retain their values.
1107
+ *
1108
+ * @param name - Field path(s) to unregister
1109
+ */
1110
+ unregister?: (name: string | string[]) => void;
1111
+ }
1112
+ /**
1113
+ * Type for an adapter factory function (hook).
1114
+ *
1115
+ * @example
1116
+ * // Using the adapter
1117
+ * function MyApp() {
1118
+ * return (
1119
+ * <FormProvider adapter={useRhfAdapter}>
1120
+ * <MyForm />
1121
+ * </FormProvider>
1122
+ * );
1123
+ * }
1124
+ */
1125
+ type AdapterFactory<TData = Record<string, unknown>> = (options: AdapterOptions<TData>) => FormAdapter<TData>;
1126
+ /**
1127
+ * Validates that a FormAdapter has all required methods.
1128
+ * Call this in development to catch missing implementations early.
1129
+ *
1130
+ * @param adapter - The adapter to validate
1131
+ * @param adapterName - Name for error messages
1132
+ * @throws Error if required methods are missing
1133
+ */
1134
+ declare function validateAdapter(adapter: FormAdapter, adapterName?: string): void;
1135
+
1136
+ export { type ArrayHelpers as A, type BaseField as B, type ConditionContext as C, type DateField as D, type EmailField as E, type Field as F, type GroupField as G, type Tab as H, type TabsField as I, type CollapsibleField as J, type FieldType as K, type DataField as L, type LayoutField as M, type NumberField as N, type BuzzFormSchema as O, type PasswordField as P, type FormSettings as Q, type ResolverResult as R, type SetValueOptions as S, type TextField as T, type UseFormOptions as U, type ValidationContext as V, type FormConfig as a, type FormAdapter as b, type FormState as c, type FieldError as d, type Resolver as e, type AdapterOptions as f, type AdapterFactory as g, type ValidationResult as h, type ValidationFn as i, type FieldCondition as j, type FieldComponentProps as k, type FieldInputProps as l, type FieldInputRenderFn as m, type FieldStyle as n, type TextareaField as o, type DatetimeField as p, type SelectOption as q, type SelectField as r, type CheckboxField as s, type SwitchField as t, type RadioField as u, validateAdapter as v, type TagsField as w, type UploadField as x, type ArrayField as y, type RowField as z };