@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.
- package/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/adapter-BT9v2OVg.d.mts +1136 -0
- package/dist/adapter-BT9v2OVg.d.ts +1136 -0
- package/dist/chunk-DDDGBPVU.mjs +273 -0
- package/dist/chunk-DDDGBPVU.mjs.map +1 -0
- package/dist/chunk-K42S5YX3.mjs +599 -0
- package/dist/chunk-K42S5YX3.mjs.map +1 -0
- package/dist/index.d.mts +270 -0
- package/dist/index.d.ts +270 -0
- package/dist/index.js +1028 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +194 -0
- package/dist/index.mjs.map +1 -0
- package/dist/rhf.d.mts +70 -0
- package/dist/rhf.d.ts +70 -0
- package/dist/rhf.js +293 -0
- package/dist/rhf.js.map +1 -0
- package/dist/rhf.mjs +175 -0
- package/dist/rhf.mjs.map +1 -0
- package/dist/schema.d.mts +72 -0
- package/dist/schema.d.ts +72 -0
- package/dist/schema.js +768 -0
- package/dist/schema.js.map +1 -0
- package/dist/schema.mjs +63 -0
- package/dist/schema.mjs.map +1 -0
- package/dist/utils-BgwyUFGB.d.mts +233 -0
- package/dist/utils-DVLpbOoW.d.ts +233 -0
- package/dist/zod.d.mts +32 -0
- package/dist/zod.d.ts +32 -0
- package/dist/zod.js +88 -0
- package/dist/zod.js.map +1 -0
- package/dist/zod.mjs +62 -0
- package/dist/zod.mjs.map +1 -0
- package/package.json +109 -0
|
@@ -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 };
|