@maestro-js/form 1.0.0-alpha.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/README.md ADDED
@@ -0,0 +1,386 @@
1
+ # @maestro-js/form
2
+
3
+ Headless React form primitives with client and server validation, hidden inputs for custom components, controlled/uncontrolled state management, and Standard Schema (Zod) FormData parsing with automatic type coercion.
4
+
5
+ ## Quick Setup
6
+
7
+ ```tsx
8
+ import { HeadlessForm } from '@maestro-js/form'
9
+
10
+ // 1. Wrap your app with GlobalProvider to enable server error integration
11
+ function App() {
12
+ return (
13
+ <HeadlessForm.GlobalProvider useServerErrors={useMyServerErrors}>
14
+ <MyPage />
15
+ </HeadlessForm.GlobalProvider>
16
+ )
17
+ }
18
+
19
+ // 2. Use HeadlessForm as your form element
20
+ function LoginForm() {
21
+ return (
22
+ <HeadlessForm as="form" method="post" action="/login">
23
+ <input name="email" type="email" required />
24
+ <button type="submit">Log in</button>
25
+ </HeadlessForm>
26
+ )
27
+ }
28
+ ```
29
+
30
+ ## Core Concepts
31
+
32
+ ### Validation Flow
33
+
34
+ Custom validation via `validate` functions runs in realtime, setting `setCustomValidity()` on the referenced native element. Errors are committed (shown to the user) on input `change` or form `submit`. Server errors from `GlobalProvider` are shown immediately and cleared when the user edits the field. The first invalid input auto-focuses on submit.
35
+
36
+ ### Hidden Inputs for Custom Components
37
+
38
+ Custom UI components (dropdowns, file pickers, date pickers) can't natively participate in HTML forms. `HiddenInput`, `HiddenFileInput`, and `HiddenSelect` bridge this gap with visually-hidden native elements that carry the value and participate in constraint validation.
39
+
40
+ ### FormData Parsing
41
+
42
+ `parseFormData` and `parseFormDataClientSide` convert native FormData into typed objects using a Standard Schema (Zod). They handle type coercion (strings to numbers/booleans), nested objects via dot notation (`address.city`), arrays, and file uploads.
43
+
44
+ ## API Reference
45
+
46
+ ### HeadlessForm (Component)
47
+
48
+ ```tsx
49
+ <HeadlessForm as="form" method="post" id="profile-form">
50
+ {children}
51
+ </HeadlessForm>
52
+ ```
53
+
54
+ Root form component. Wraps a native `<form>` with validation and server error support.
55
+
56
+ - `as` -- Component to render (must resolve to `HTMLFormElement`)
57
+ - `method` -- `'get'` | `'post'` | `'put'` | `'patch'` | `'delete'`
58
+ - `id` -- Optional (auto-generated via `useId()` if omitted)
59
+ - Plus all standard form element props
60
+
61
+ GET forms validate on submit and prevent submission if invalid. Non-GET forms inject a hidden `__formId` input for server-side form identification.
62
+
63
+ ### GlobalProvider
64
+
65
+ ```tsx
66
+ <HeadlessForm.GlobalProvider useServerErrors={useMyServerErrors}>
67
+ {children}
68
+ </HeadlessForm.GlobalProvider>
69
+ ```
70
+
71
+ Wraps your app to provide server error resolution to all forms. The `useServerErrors` prop is a hook with signature:
72
+
73
+ ```ts
74
+ (formId: string) => { path: string; message: string }[] | null
75
+ ```
76
+
77
+ ### useValidation
78
+
79
+ ```ts
80
+ HeadlessForm.useValidation<T>(
81
+ props: {
82
+ name: string | string[]
83
+ value: T
84
+ form: string
85
+ validate?: ValidationFunction<T>
86
+ isInvalid?: boolean
87
+ isRequired?: boolean
88
+ focus?: () => void
89
+ },
90
+ ref: RefObject<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
91
+ ): {
92
+ isInvalid: boolean
93
+ validationErrors: string[]
94
+ validationDetails: ValidityState
95
+ validationMessage: string
96
+ commitValidation(): void
97
+ }
98
+ ```
99
+
100
+ Connects custom validation to the native Constraint Validation API. Use inside custom input components.
101
+
102
+ - Runs `validate(value)` in realtime, sets `setCustomValidity()` on the referenced element
103
+ - Commits errors on `change` event or form submit (`invalid` event)
104
+ - Auto-focuses the first invalid input on submission
105
+ - Integrates server errors from `GlobalProvider` -- clears when user edits
106
+ - Resets on form `reset` event
107
+
108
+ ### useWarningState
109
+
110
+ ```ts
111
+ HeadlessForm.useWarningState<T>(
112
+ props: { value: T; validate?: ValidationFunction<T>; isInvalid?: boolean }
113
+ ): {
114
+ realtimeValidation: ValidationResult
115
+ displayValidation: ValidationResult
116
+ warningMessage: string
117
+ commitValidation(): void
118
+ resetValidation(): void
119
+ updateValidation(result: ValidationResult): void
120
+ }
121
+ ```
122
+
123
+ Like `useValidation` but for non-blocking warnings. Does not integrate with native constraint validation or server errors. Use for soft validation hints that don't prevent submission.
124
+
125
+ ### useControlledState
126
+
127
+ ```ts
128
+ HeadlessForm.useControlledState<T>(
129
+ value: T | undefined,
130
+ defaultValue: T,
131
+ onChange?: (v: T) => void
132
+ ): [T, (value: T) => void]
133
+ ```
134
+
135
+ Manages controlled/uncontrolled state for custom form components. When `value` is defined, the component is controlled; when `undefined`, it uses `defaultValue` and manages its own state.
136
+
137
+ ### useReset
138
+
139
+ ```ts
140
+ HeadlessForm.useReset(
141
+ ref: RefObject<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
142
+ initialValue: T,
143
+ onReset: (value: T) => void
144
+ )
145
+ ```
146
+
147
+ Calls `onReset` with the initial value when the parent form fires a `reset` event.
148
+
149
+ ### useServerErrors
150
+
151
+ ```ts
152
+ HeadlessForm.useServerErrors(formId?: string): { path: string; message: string }[]
153
+ ```
154
+
155
+ Returns server errors for the given form ID (or the current form's ID from context if omitted). Requires `GlobalProvider` in the tree.
156
+
157
+ ### useTopLevelFormValidation
158
+
159
+ ```ts
160
+ HeadlessForm.useTopLevelFormValidation(
161
+ formElement: HTMLFormElement | string
162
+ ): { revalidate(field: string | null): void }
163
+ ```
164
+
165
+ Programmatically re-triggers validation on form fields. Pass `null` to revalidate all fields, or a field name prefix to revalidate only matching fields.
166
+
167
+ ### composeValidators
168
+
169
+ ```ts
170
+ HeadlessForm.composeValidators<T>(
171
+ ...fns: (ValidationFunction<T> | undefined | null)[]
172
+ ): ValidationFunction<T>
173
+ ```
174
+
175
+ Combines multiple validation functions. Each is called with the value; all returned errors are collected into a single array.
176
+
177
+ ```ts
178
+ const required = (v: string) => (!v ? 'Required' : null)
179
+ const minLen = (min: number) => (v: string) => (v.length < min ? `At least ${min} chars` : null)
180
+
181
+ const validate = HeadlessForm.composeValidators(required, minLen(5))
182
+ validate('') // ['Required', 'At least 5 chars']
183
+ ```
184
+
185
+ ### Hidden Input Components
186
+
187
+ #### HiddenInput
188
+
189
+ Visually hidden `<input>` that participates in form submission. Use inside custom components.
190
+
191
+ ```tsx
192
+ <HeadlessForm.HiddenInput ref={inputRef} name="country" value={selectedCountry} form={formId} isRequired />
193
+ ```
194
+
195
+ Props: `value`, `name`, `form`, `isRequired`, `type`, `min`, `max`
196
+
197
+ #### HiddenFileInput
198
+
199
+ Visually hidden `<input type="file">` with programmatic file assignment via `DataTransfer`.
200
+
201
+ ```tsx
202
+ <HeadlessForm.HiddenFileInput ref={fileRef} name="avatar" value={selectedFile} form={formId} isRequired />
203
+ ```
204
+
205
+ Props: `value` (`File | File[] | null`), `name`, `form`, `isRequired`
206
+
207
+ #### HiddenSelect
208
+
209
+ Visually hidden `<select>` for custom dropdown components.
210
+
211
+ ```tsx
212
+ <HeadlessForm.HiddenSelect ref={selectRef} name="role" value={selectedRole} form={formId} isRequired>
213
+ <option value="admin">Admin</option>
214
+ <option value="viewer">Viewer</option>
215
+ </HeadlessForm.HiddenSelect>
216
+ ```
217
+
218
+ Props: `value`, `name`, `form`, `isRequired`, `children`, `autoComplete`, `onChange`, `label`
219
+
220
+ ### FormData Parsing
221
+
222
+ #### parseFormData
223
+
224
+ ```ts
225
+ HeadlessForm.parseFormData<T>(
226
+ schema: StandardSchema<T>,
227
+ formData: FormData | Promise<FormData>,
228
+ options?: {
229
+ handleFile?(file: File, path: string, data: T): Promise<string>
230
+ libraryOptions?: Record<string, unknown>
231
+ }
232
+ ): Promise<ParseFormDataResult<T>>
233
+ ```
234
+
235
+ Server-side FormData parser. Converts raw FormData into a typed object validated against a Standard Schema (Zod).
236
+
237
+ ```ts
238
+ const schema = z.object({ name: z.string(), age: z.number(), avatar: z.string() })
239
+
240
+ const result = await HeadlessForm.parseFormData(schema, request.formData(), {
241
+ handleFile: async (file) => await uploadToS3(file)
242
+ })
243
+
244
+ if (result.success) {
245
+ result.data // { name: 'Alice', age: 30, avatar: 'https://cdn.example.com/avatar.png' }
246
+ }
247
+ ```
248
+
249
+ #### parseFormDataClientSide
250
+
251
+ ```ts
252
+ HeadlessForm.parseFormDataClientSide<T>(
253
+ schema: StandardSchema<T>,
254
+ formData: FormData,
255
+ options?: ParseFormDataOptions<T>
256
+ ): ParseFormDataResult<T> & { fileEntries: FoundFile[] }
257
+ ```
258
+
259
+ Synchronous client-side variant. Files are replaced with placeholder URLs for validation; actual file entries are returned separately in `fileEntries`.
260
+
261
+ ### Coercion Rules
262
+
263
+ The parser uses the schema's JSON Schema representation to coerce FormData string values:
264
+
265
+ | Schema Type | FormData Value | Result |
266
+ | --- | --- | --- |
267
+ | `string` | `'Alice'` | `'Alice'` |
268
+ | `string` (nullable) | `''` | `null` |
269
+ | `number` / `integer` | `'42'` | `42` |
270
+ | `number` (empty) | `''` | `null` |
271
+ | `boolean` | `'true'` / `'false'` | `true` / `false` |
272
+ | `boolean` (absent) | *(missing)* | `false` |
273
+ | `array` | multiple values | `['a', 'b']` |
274
+ | `array` | JSON string `'[1,2]'` | `[1, 2]` |
275
+ | `array` (empty) | `''` | `[]` |
276
+ | nested object | `'addr.city'` key | `{ addr: { city: ... } }` |
277
+ | array of objects | `'items.0.name'` key | `[{ name: ... }]` |
278
+
279
+ ### Types
280
+
281
+ ```ts
282
+ type ValidationFunction<T> = (value: T) => string | string[] | null | undefined
283
+
284
+ interface ValidationResult {
285
+ isInvalid: boolean
286
+ validationDetails: ValidityState
287
+ validationErrors: string[]
288
+ }
289
+
290
+ type ParseFormDataResult<T> =
291
+ | { success: true; data: T; formData: FormData; formId: string | null; issues: undefined }
292
+ | { success: false; data: any; formData: FormData; formId: string | null; issues: readonly StandardSchemaV1.Issue[] }
293
+ ```
294
+
295
+ ## Common Patterns
296
+
297
+ ### Custom Input with Validation
298
+
299
+ ```tsx
300
+ function PhoneInput({ name, form }: { name: string; form: string }) {
301
+ const inputRef = useRef<HTMLInputElement>(null)
302
+ const [value, setValue] = HeadlessForm.useControlledState<string>(undefined, '')
303
+
304
+ const validation = HeadlessForm.useValidation(
305
+ {
306
+ name,
307
+ form,
308
+ value,
309
+ validate: (v) => (v && !/^\d{10}$/.test(v) ? 'Enter a 10-digit phone number' : null)
310
+ },
311
+ inputRef
312
+ )
313
+
314
+ HeadlessForm.useReset(inputRef, '', setValue)
315
+
316
+ return (
317
+ <div>
318
+ <input value={value} onChange={(e) => setValue(e.target.value)} />
319
+ <HeadlessForm.HiddenInput ref={inputRef} name={name} value={value} form={form} />
320
+ {validation.isInvalid && <span>{validation.validationMessage}</span>}
321
+ </div>
322
+ )
323
+ }
324
+ ```
325
+
326
+ ### Server-Side Form Processing
327
+
328
+ ```ts
329
+ async function handleProfileUpdate(formData: FormData) {
330
+ const schema = z.object({
331
+ name: z.string().min(1),
332
+ bio: z.string().nullable(),
333
+ age: z.number().min(13),
334
+ avatar: z.string(),
335
+ tags: z.array(z.string())
336
+ })
337
+
338
+ const result = await HeadlessForm.parseFormData(schema, formData, {
339
+ handleFile: async (file) => uploadToStorage(file)
340
+ })
341
+
342
+ if (!result.success) {
343
+ return { errors: result.issues.map((i) => ({ path: i.path?.join('.') ?? '', message: i.message })) }
344
+ }
345
+
346
+ await db.updateUser(result.data)
347
+ }
348
+ ```
349
+
350
+ ### Custom Select with Hidden Native Element
351
+
352
+ ```tsx
353
+ function RoleSelect({ name, form }: { name: string; form: string }) {
354
+ const selectRef = useRef<HTMLSelectElement>(null)
355
+ const [value, setValue] = HeadlessForm.useControlledState<string>(undefined, '')
356
+
357
+ const validation = HeadlessForm.useValidation({ name, form, value, isRequired: true }, selectRef)
358
+ HeadlessForm.useReset(selectRef, '', setValue)
359
+
360
+ return (
361
+ <div>
362
+ <CustomDropdown value={value} onChange={setValue} options={['admin', 'editor', 'viewer']} />
363
+ <HeadlessForm.HiddenSelect ref={selectRef} name={name} value={value} form={form} isRequired>
364
+ <option value="admin">Admin</option>
365
+ <option value="editor">Editor</option>
366
+ <option value="viewer">Viewer</option>
367
+ </HeadlessForm.HiddenSelect>
368
+ {validation.isInvalid && <span>{validation.validationMessage}</span>}
369
+ </div>
370
+ )
371
+ }
372
+ ```
373
+
374
+ ## Warnings and Gotchas
375
+
376
+ > **Warning:** `GlobalProvider` must wrap your app before any `HeadlessForm` is rendered. Without it, `useServerErrors` throws: `'GlobalContext not found. Please wrap your site with GlobalProvider'`.
377
+
378
+ > **Warning:** Do not use async validation with `parseFormData` / `parseFormDataClientSide` -- they throw `'Async validation not supported'`. All schema validation must be synchronous.
379
+
380
+ > **Note:** Non-GET forms inject a hidden `__formId` field. `parseFormData` strips this automatically. If you parse FormData manually, filter out `__formId`.
381
+
382
+ > **Note:** `parseFormData` requires a `handleFile` option when the form contains file uploads. Without it, it throws. The client-side variant replaces files with placeholder URLs instead.
383
+
384
+ > **Warning:** Unchecked boolean checkboxes are absent from FormData. The parser defaults these to `false` based on the schema. If the boolean field isn't declared in the schema, the absent value won't be coerced.
385
+
386
+ > **Note:** `useControlledState` warns in the console if a component switches between controlled and uncontrolled mode. Always consistently pass either a value or `undefined`.
@@ -0,0 +1,183 @@
1
+ import * as React$1 from 'react';
2
+ import React__default from 'react';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { StandardJSONSchemaV1, StandardSchemaV1, StandardTypedV1 } from '@standard-schema/spec';
5
+
6
+ type HeadlessFormProps<As extends React__default.ElementType> = {
7
+ id?: string;
8
+ children?: React__default.ReactNode;
9
+ as: RefType<As> extends HTMLFormElement ? As : never;
10
+ method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
11
+ } & React__default.ComponentProps<As>;
12
+ type RefType<T extends React__default.ElementType<any>> = React__default.ComponentPropsWithRef<T>['ref'] extends React__default.Ref<infer I> | undefined ? I : never;
13
+ declare function GlobalProvider({ useServerErrors, children, csrfToken }: GlobalContextValue): react_jsx_runtime.JSX.Element;
14
+ interface GlobalContextValue {
15
+ useServerErrors(formId: string): {
16
+ path: string;
17
+ message: string;
18
+ }[] | null;
19
+ csrfToken?: string;
20
+ children?: React__default.ReactNode;
21
+ }
22
+ declare function useServerErrors(formId: string | undefined): {
23
+ path: string;
24
+ message: string;
25
+ }[];
26
+
27
+ declare function useControlledState<T, C = T>(value: Exclude<T, undefined>, defaultValue: Exclude<T, undefined> | undefined, onChange?: (v: C, ...args: any[]) => void): [T, (value: T) => void];
28
+ declare function useControlledState<T, C = T>(value: Exclude<T, undefined> | undefined, defaultValue: Exclude<T, undefined>, onChange?: (v: C, ...args: any[]) => void): [T, (value: T) => void];
29
+
30
+ declare function useTopLevelFormValidation(formElement: HTMLFormElement | string): {
31
+ revalidate: (field: string | null) => void;
32
+ };
33
+
34
+ type ValidatableElement$1 = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
35
+ declare function useReset<T>(ref: React__default.RefObject<ValidatableElement$1 | null>, initialValue: T, onReset: (value: T) => void): void;
36
+
37
+ interface Validation<T> {
38
+ /** Whether user input is required on the input before form submission. */
39
+ isRequired?: boolean;
40
+ /** Whether the input value is invalid. */
41
+ isInvalid?: boolean;
42
+ /**
43
+ * A function that returns an error message if a given value is invalid.
44
+ * Validation errors are displayed to the user when the form is submitted
45
+ * if `validationBehavior="native"`. For realtime validation, use the `isInvalid`
46
+ * prop instead.
47
+ */
48
+ validate?: ValidationFunction<T>;
49
+ }
50
+ interface ValidationResult {
51
+ isInvalid: boolean;
52
+ validationDetails: ValidityState;
53
+ validationErrors: string[];
54
+ }
55
+ type ValidationFunction<T> = (value: T) => string | string[] | null | undefined;
56
+
57
+ type ValidatableElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
58
+ interface FormValidationHookOptions<T> extends Validation<T> {
59
+ focus?: () => void;
60
+ name: string | string[] | undefined;
61
+ value: T;
62
+ form: string | undefined;
63
+ }
64
+ declare function useValidation<T>(props: FormValidationHookOptions<T>, ref: React__default.RefObject<ValidatableElement | null> | undefined): {
65
+ validationMessage: string;
66
+ isInvalid: boolean;
67
+ validationDetails: ValidityState;
68
+ validationErrors: string[];
69
+ commitValidation: () => void;
70
+ };
71
+
72
+ interface FormValidationProps<T> extends Validation<T> {
73
+ value: T;
74
+ }
75
+ interface FormWarningState {
76
+ /** Realtime validation results, updated as the user edits the value. */
77
+ realtimeValidation: ValidationResult;
78
+ /** Currently displayed validation results, updated when the user commits their changes. */
79
+ displayValidation: ValidationResult;
80
+ /** Updates the current validation result. Not displayed to the user until `commitValidation` is called. */
81
+ updateValidation(result: ValidationResult): void;
82
+ /** Resets the displayed validation state to valid when the user resets the form. */
83
+ resetValidation(): void;
84
+ /** Commits the realtime validation so it is displayed to the user. */
85
+ commitValidation(): void;
86
+ warningMessage: string;
87
+ }
88
+ declare function useWarningState<T>(props: FormValidationProps<T>): FormWarningState;
89
+
90
+ type StandardSchema<Input = unknown, Output = Input> = StandardJSONSchemaV1<Input, Output> & StandardSchemaV1<Input, Output> & StandardTypedV1<Input, Output>;
91
+ type ParseFormDataResult<T> = {
92
+ success: false;
93
+ issues: {
94
+ /** The error message of the issue. */
95
+ message: string;
96
+ /** The path of the issue, if any. */
97
+ path?: Array<PropertyKey | {
98
+ key: string | number | symbol;
99
+ }> | undefined;
100
+ }[];
101
+ data: any;
102
+ formData: FormData;
103
+ formId: string | null;
104
+ } | {
105
+ success: true;
106
+ issues: undefined;
107
+ data: T;
108
+ formData: FormData;
109
+ formId: string | null;
110
+ };
111
+ interface ParseFormDataOptions<T> {
112
+ handleFile?(file: File, path: string, data: T): Promise<string>;
113
+ libraryOptions?: Record<string, unknown> | undefined;
114
+ }
115
+ declare function parseFormDataClientSide<T>(schema: StandardSchema<T>, formData: FormData, options?: ParseFormDataOptions<T>): ParseFormDataResult<T> & {
116
+ fileEntries: FoundFile[];
117
+ };
118
+ declare function parseFormData<T>(schema: StandardSchema<T>, formData: FormData | Promise<FormData>, options?: ParseFormDataOptions<T>): Promise<ParseFormDataResult<T>>;
119
+ interface FoundFile {
120
+ path: (string | number)[];
121
+ value: File;
122
+ }
123
+
124
+ declare function composeValidators<T>(...fns: (ValidationFunction<T> | undefined | null)[]): ValidationFunction<T>;
125
+ /**
126
+ * Headless React form primitives with validation, hidden inputs, and FormData parsing.
127
+ * Wrap your app with `HeadlessForm.GlobalProvider`, use `HeadlessForm` as the form root,
128
+ * and build custom inputs with `useValidation`, `useControlledState`, and hidden input components.
129
+ *
130
+ * @example
131
+ * ```tsx
132
+ * <HeadlessForm.GlobalProvider useServerErrors={useMyServerErrors}>
133
+ * <HeadlessForm as="form" method="post">
134
+ * <PhoneInput name="phone" form={formId} />
135
+ * <button type="submit">Submit</button>
136
+ * </HeadlessForm>
137
+ * </HeadlessForm.GlobalProvider>
138
+ * ```
139
+ */
140
+ declare const HeadlessForm: (<As extends React.ElementType = "form">(props: {
141
+ id?: string;
142
+ children?: React.ReactNode;
143
+ as: (React$1.ComponentPropsWithRef<As>["ref"] extends React$1.Ref<infer I> | undefined ? I : never) extends HTMLFormElement ? As : never;
144
+ method?: "get" | "post" | "put" | "patch" | "delete" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
145
+ } & React$1.ComponentProps<As> & React$1.RefAttributes<HTMLFormElement>) => React.ReactElement | null) & {
146
+ GlobalProvider: typeof GlobalProvider;
147
+ useValidation: typeof useValidation;
148
+ useWarningState: typeof useWarningState;
149
+ useReset: typeof useReset;
150
+ useControlledState: typeof useControlledState;
151
+ HiddenInput: React$1.ForwardRefExoticComponent<{
152
+ value: string | number | readonly string[] | undefined;
153
+ name: string | undefined;
154
+ form: string | undefined;
155
+ isRequired?: boolean;
156
+ min?: number;
157
+ max?: number;
158
+ type?: React$1.HTMLInputTypeAttribute;
159
+ } & React$1.RefAttributes<HTMLInputElement>>;
160
+ HiddenFileInput: React$1.ForwardRefExoticComponent<{
161
+ value: File | File[] | null | undefined;
162
+ name: string | undefined;
163
+ form?: string | undefined;
164
+ isRequired?: boolean;
165
+ } & React$1.RefAttributes<HTMLInputElement>>;
166
+ HiddenSelect: React$1.ForwardRefExoticComponent<{
167
+ value: string | number | readonly string[] | undefined;
168
+ name: string | undefined;
169
+ form: string | undefined;
170
+ isRequired?: boolean;
171
+ children?: React.ReactNode;
172
+ autoComplete?: string;
173
+ onChange?: (e: React.ChangeEvent<HTMLSelectElement>) => unknown;
174
+ label?: React.ReactNode;
175
+ } & React$1.RefAttributes<HTMLSelectElement>>;
176
+ useServerErrors: typeof useServerErrors;
177
+ parseFormDataClientSide: typeof parseFormDataClientSide;
178
+ parseFormData: typeof parseFormData;
179
+ useTopLevelFormValidation: typeof useTopLevelFormValidation;
180
+ composeValidators: typeof composeValidators;
181
+ };
182
+
183
+ export { HeadlessForm, type HeadlessFormProps, type ValidationFunction, type ValidationResult };