@wix/headless-forms 0.0.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.
Files changed (49) hide show
  1. package/README.md +46 -0
  2. package/cjs/dist/react/Form.d.ts +802 -0
  3. package/cjs/dist/react/Form.js +776 -0
  4. package/cjs/dist/react/Phone.d.ts +47 -0
  5. package/cjs/dist/react/Phone.js +56 -0
  6. package/cjs/dist/react/constants/calling-country-codes.d.ts +242 -0
  7. package/cjs/dist/react/constants/calling-country-codes.js +242 -0
  8. package/cjs/dist/react/context/FieldContext.d.ts +12 -0
  9. package/cjs/dist/react/context/FieldContext.js +16 -0
  10. package/cjs/dist/react/context/FieldLayoutContext.d.ts +12 -0
  11. package/cjs/dist/react/context/FieldLayoutContext.js +21 -0
  12. package/cjs/dist/react/core/Form.d.ts +342 -0
  13. package/cjs/dist/react/core/Form.js +278 -0
  14. package/cjs/dist/react/index.d.ts +3 -0
  15. package/cjs/dist/react/index.js +42 -0
  16. package/cjs/dist/react/types.d.ts +3 -0
  17. package/cjs/dist/react/types.js +2 -0
  18. package/cjs/dist/react/utils.d.ts +13 -0
  19. package/cjs/dist/react/utils.js +20 -0
  20. package/cjs/dist/services/form-service.d.ts +114 -0
  21. package/cjs/dist/services/form-service.js +152 -0
  22. package/cjs/dist/services/index.d.ts +1 -0
  23. package/cjs/dist/services/index.js +17 -0
  24. package/cjs/package.json +3 -0
  25. package/dist/react/Form.d.ts +802 -0
  26. package/dist/react/Form.js +740 -0
  27. package/dist/react/Phone.d.ts +47 -0
  28. package/dist/react/Phone.js +50 -0
  29. package/dist/react/constants/calling-country-codes.d.ts +242 -0
  30. package/dist/react/constants/calling-country-codes.js +241 -0
  31. package/dist/react/context/FieldContext.d.ts +12 -0
  32. package/dist/react/context/FieldContext.js +9 -0
  33. package/dist/react/context/FieldLayoutContext.d.ts +12 -0
  34. package/dist/react/context/FieldLayoutContext.js +13 -0
  35. package/dist/react/core/Form.d.ts +342 -0
  36. package/dist/react/core/Form.js +269 -0
  37. package/dist/react/index.d.ts +3 -0
  38. package/dist/react/index.js +3 -0
  39. package/dist/react/types.d.ts +3 -0
  40. package/dist/react/types.js +1 -0
  41. package/dist/react/utils.d.ts +13 -0
  42. package/dist/react/utils.js +17 -0
  43. package/dist/services/form-service.d.ts +114 -0
  44. package/dist/services/form-service.js +148 -0
  45. package/dist/services/index.d.ts +1 -0
  46. package/dist/services/index.js +1 -0
  47. package/package.json +62 -0
  48. package/react/package.json +4 -0
  49. package/services/package.json +4 -0
@@ -0,0 +1,740 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { useState, useCallback } from 'react';
3
+ import { AsChildSlot } from '@wix/headless-utils/react';
4
+ import { useForm, FormProvider, } from '@wix/form-public';
5
+ import { Root as CoreRoot, Loading as CoreLoading, LoadingError as CoreLoadingError, Error as CoreError, Submitted as CoreSubmitted, Fields as CoreFields, Field as CoreField, } from './core/Form.js';
6
+ import { FieldContext, useFieldContext, } from './context/FieldContext.js';
7
+ import { FieldLayoutProvider, useFieldLayout, } from './context/FieldLayoutContext.js';
8
+ var TestIds;
9
+ (function (TestIds) {
10
+ TestIds["formRoot"] = "form-root";
11
+ TestIds["form"] = "form";
12
+ TestIds["formLoading"] = "form-loading";
13
+ TestIds["formLoadingError"] = "form-loading-error";
14
+ TestIds["formError"] = "form-error";
15
+ TestIds["formSubmitted"] = "form-submitted";
16
+ TestIds["fieldRoot"] = "field-root";
17
+ TestIds["fieldLabel"] = "field-label";
18
+ TestIds["fieldInputWrapper"] = "field-input-wrapper";
19
+ TestIds["fieldInput"] = "field-input";
20
+ TestIds["fieldError"] = "field-error";
21
+ })(TestIds || (TestIds = {}));
22
+ /**
23
+ * Root component that provides all necessary service contexts for a complete form experience.
24
+ * This component sets up the Form service and provides context to child components.
25
+ * Must be used as the top-level component for all form functionality.
26
+ *
27
+ * @component
28
+ * @param {RootProps} props - The component props
29
+ * @param {React.ReactNode} props.children - Child components that will have access to form context
30
+ * @param {FormServiceConfig} props.formServiceConfig - Form service configuration object
31
+ * @param {boolean} [props.asChild] - Whether to render as a child component
32
+ * @param {string} [props.className] - CSS classes to apply to the root element
33
+ * @example
34
+ * ```tsx
35
+ * import { Form } from '@wix/headless-forms/react';
36
+ * import { loadFormServiceConfig } from '@wix/headless-forms/services';
37
+ *
38
+ * const FIELD_MAP = {
39
+ * TEXT_INPUT: TextInput,
40
+ * TEXT_AREA: TextArea,
41
+ * CHECKBOX: Checkbox,
42
+ * // ... other field components
43
+ * };
44
+ *
45
+ * // Pattern 1: Pre-loaded form data (SSR/SSG)
46
+ * function FormPage({ formServiceConfig }) {
47
+ * return (
48
+ * <Form.Root formServiceConfig={formServiceConfig}>
49
+ * <Form.Loading className="flex justify-center p-4" />
50
+ * <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
51
+ * <Form.Fields fieldMap={FIELD_MAP} />
52
+ * <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
53
+ * <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
54
+ * </Form.Root>
55
+ * );
56
+ * }
57
+ *
58
+ * // Pattern 2: Lazy loading with formId (Client-side)
59
+ * function DynamicFormPage({ formId }) {
60
+ * return (
61
+ * <Form.Root formServiceConfig={{ formId }}>
62
+ * <Form.Loading className="flex justify-center p-4" />
63
+ * <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
64
+ * <Form.Fields fieldMap={FIELD_MAP} />
65
+ * <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
66
+ * <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
67
+ * </Form.Root>
68
+ * );
69
+ * }
70
+ * ```
71
+ */
72
+ export const Root = React.forwardRef((props, ref) => {
73
+ const { children, formServiceConfig, asChild, ...otherProps } = props;
74
+ return (_jsx(CoreRoot, { formServiceConfig: formServiceConfig, children: _jsx(RootContent, { asChild: asChild, ref: ref, ...otherProps, children: children }) }));
75
+ });
76
+ /**
77
+ * Internal component to handle the Root content with service access.
78
+ * This component wraps the children with the necessary div container and applies styling.
79
+ *
80
+ * @internal
81
+ * @param {RootContentProps} props - Component props
82
+ * @param {React.ReactNode} props.children - Child components to render
83
+ * @param {string} [props.className] - CSS classes to apply to the container
84
+ * @param {boolean} [props.asChild] - Whether to render as a child component
85
+ * @returns {JSX.Element} The wrapped content
86
+ */
87
+ const RootContent = React.forwardRef((props, ref) => {
88
+ const { asChild, children, className, ...otherProps } = props;
89
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.formRoot, customElement: children, customElementProps: {}, ...otherProps, children: _jsx("div", { children: children }) }));
90
+ });
91
+ /**
92
+ * Component that renders content during loading state.
93
+ * Only displays its children when the form is currently loading.
94
+ *
95
+ * @component
96
+ * @param {LoadingProps} props - The component props
97
+ * @param {boolean} [props.asChild] - Whether to render as a child component
98
+ * @param {React.ReactNode} [props.children] - Content to display during loading state
99
+ * @param {string} [props.className] - CSS classes to apply to the default element
100
+ * @example
101
+ * ```tsx
102
+ * import { Form } from '@wix/headless-forms/react';
103
+ *
104
+ * // Default usage with className
105
+ * function FormLoading() {
106
+ * return (
107
+ * <Form.Loading className="flex justify-center p-4" />
108
+ * );
109
+ * }
110
+ *
111
+ * // Custom content
112
+ * function CustomFormLoading() {
113
+ * return (
114
+ * <Form.Loading>
115
+ * <div className="flex justify-center items-center p-4">
116
+ * <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
117
+ * <span className="ml-2 text-foreground font-paragraph">Loading form...</span>
118
+ * </div>
119
+ * </Form.Loading>
120
+ * );
121
+ * }
122
+ *
123
+ * // With asChild for custom components
124
+ * function CustomFormLoadingAsChild() {
125
+ * return (
126
+ * <Form.Loading asChild>
127
+ * <div className="custom-loading-container">
128
+ * <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
129
+ * <span className="ml-2 text-foreground font-paragraph">Loading form...</span>
130
+ * </div>
131
+ * </Form.Loading>
132
+ * );
133
+ * }
134
+ * ```
135
+ */
136
+ export const Loading = React.forwardRef((props, ref) => {
137
+ const { asChild, children, className, ...otherProps } = props;
138
+ return (_jsx(CoreLoading, { children: ({ isLoading }) => {
139
+ if (!isLoading)
140
+ return null;
141
+ return (_jsx(AsChildSlot, { "data-testid": TestIds.formLoading, ref: ref, asChild: asChild, className: className, customElement: children, content: "Loading form...", ...otherProps, children: _jsx("div", { children: "Loading form..." }) }));
142
+ } }));
143
+ });
144
+ /**
145
+ * Component that renders content when there's an error loading the form.
146
+ * Only displays its children when an error has occurred.
147
+ *
148
+ * @component
149
+ * @param {LoadingErrorProps} props - The component props
150
+ * @param {boolean} [props.asChild] - Whether to render as a child component
151
+ * @param {React.ReactNode} [props.children] - Content to display during error state
152
+ * @param {string} [props.className] - CSS classes to apply to the default element
153
+ * @example
154
+ * ```tsx
155
+ * import { Form } from '@wix/headless-forms/react';
156
+ *
157
+ * // Default usage with className
158
+ * function FormLoadingError() {
159
+ * return (
160
+ * <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
161
+ * );
162
+ * }
163
+ *
164
+ * // Custom content
165
+ * function CustomLoadingError() {
166
+ * return (
167
+ * <Form.LoadingError>
168
+ * <div className="bg-destructive/10 border border-destructive text-destructive px-4 py-3 rounded mb-4">
169
+ * <h3 className="font-heading text-lg">Error loading form</h3>
170
+ * <p className="font-paragraph">Something went wrong. Please try again.</p>
171
+ * </div>
172
+ * </Form.LoadingError>
173
+ * );
174
+ * }
175
+ *
176
+ * // With asChild for custom components
177
+ * function CustomLoadingErrorAsChild() {
178
+ * return (
179
+ * <Form.LoadingError asChild>
180
+ * {React.forwardRef<HTMLDivElement, { error: string | null; hasError: boolean }>(
181
+ * ({ error }, ref) => (
182
+ * <div ref={ref} className="custom-error-container">
183
+ * <h3 className="font-heading">Error Loading Form</h3>
184
+ * <p className="font-paragraph">{error}</p>
185
+ * </div>
186
+ * )
187
+ * )}
188
+ * </Form.LoadingError>
189
+ * );
190
+ * }
191
+ * ```
192
+ */
193
+ export const LoadingError = React.forwardRef((props, ref) => {
194
+ const { asChild, children, className, ...otherProps } = props;
195
+ return (_jsx(CoreLoadingError, { children: ({ error, hasError }) => {
196
+ if (!hasError)
197
+ return null;
198
+ const errorData = { error, hasError };
199
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.formLoadingError, customElement: children, customElementProps: errorData, content: error, ...otherProps, children: _jsx("div", { children: error }) }));
200
+ } }));
201
+ });
202
+ /**
203
+ * Component that renders content when there's an error during form submission.
204
+ * Only displays its children when a submission error has occurred.
205
+ *
206
+ * @component
207
+ * @param {ErrorProps} props - The component props
208
+ * @param {boolean} [props.asChild] - Whether to render as a child component
209
+ * @param {React.ReactNode} [props.children] - Content to display during submit error state
210
+ * @param {string} [props.className] - CSS classes to apply to the default element
211
+ * @example
212
+ * ```tsx
213
+ * import { Form } from '@wix/headless-forms/react';
214
+ *
215
+ * // Default usage with className
216
+ * function FormError() {
217
+ * return <Form.Error className="text-destructive p-4 rounded-lg mb-4" />;
218
+ * }
219
+ *
220
+ * // Custom content
221
+ * function CustomFormError() {
222
+ * return (
223
+ * <Form.Error>
224
+ * <div className="bg-destructive/10 border border-destructive text-destructive p-4 rounded-lg mb-4">
225
+ * <h3 className="font-heading text-lg">Submission Failed</h3>
226
+ * <p className="font-paragraph">Please check your input and try again.</p>
227
+ * </div>
228
+ * </Form.Error>
229
+ * );
230
+ * }
231
+ *
232
+ * // With asChild for custom components
233
+ * function CustomFormErrorAsChild() {
234
+ * return (
235
+ * <Form.Error asChild>
236
+ * {React.forwardRef<HTMLDivElement, { error: string | null; hasError: boolean }>(
237
+ * ({ error }, ref) => (
238
+ * <div ref={ref} className="custom-error-container">
239
+ * <h3 className="font-heading">Submission Failed</h3>
240
+ * <p className="font-paragraph">{error}</p>
241
+ * </div>
242
+ * )
243
+ * )}
244
+ * </Form.Error>
245
+ * );
246
+ * }
247
+ * ```
248
+ */
249
+ export const Error = React.forwardRef((props, ref) => {
250
+ const { asChild, children, className, ...otherProps } = props;
251
+ return (_jsx(CoreError, { children: ({ error, hasError }) => {
252
+ if (!hasError)
253
+ return null;
254
+ const errorData = { error, hasError };
255
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.formError, customElement: children, customElementProps: errorData, content: error, ...otherProps, children: _jsx("div", { className: "text-destructive text-sm sm:text-base", children: error }) }));
256
+ } }));
257
+ });
258
+ /**
259
+ * Component that renders content after successful form submission.
260
+ * Only displays its children when the form has been successfully submitted.
261
+ *
262
+ * @component
263
+ * @param {SubmittedProps} props - The component props
264
+ * @param {boolean} [props.asChild] - Whether to render as a child component
265
+ * @param {React.ReactNode} [props.children] - Content to display after successful submission
266
+ * @param {string} [props.className] - CSS classes to apply to the default element
267
+ * @example
268
+ * ```tsx
269
+ * import { Form } from '@wix/headless-forms/react';
270
+ *
271
+ * // Default usage with className
272
+ * function FormSubmitted() {
273
+ * return <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />;
274
+ * }
275
+ *
276
+ * // Custom content
277
+ * function CustomFormSubmitted() {
278
+ * return (
279
+ * <Form.Submitted>
280
+ * <div className="bg-green-50 border border-green-200 text-green-800 p-6 rounded-lg mb-4">
281
+ * <h2 className="font-heading text-xl mb-2">Thank You!</h2>
282
+ * <p className="font-paragraph">Your form has been submitted successfully.</p>
283
+ * </div>
284
+ * </Form.Submitted>
285
+ * );
286
+ * }
287
+ *
288
+ * // With asChild for custom components
289
+ * function CustomFormSubmittedAsChild() {
290
+ * return (
291
+ * <Form.Submitted asChild>
292
+ * {React.forwardRef<HTMLDivElement, { isSubmitted: boolean; message: string }>(
293
+ * ({ message }, ref) => (
294
+ * <div ref={ref} className="custom-success-container">
295
+ * <h2 className="font-heading">Thank You!</h2>
296
+ * <p className="font-paragraph">{message}</p>
297
+ * </div>
298
+ * )
299
+ * )}
300
+ * </Form.Submitted>
301
+ * );
302
+ * }
303
+ * ```
304
+ */
305
+ export const Submitted = React.forwardRef((props, ref) => {
306
+ const { asChild, children, className, ...otherProps } = props;
307
+ return (_jsx(CoreSubmitted, { children: ({ isSubmitted, message }) => {
308
+ if (!isSubmitted)
309
+ return null;
310
+ const submittedData = { isSubmitted, message };
311
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.formSubmitted, customElement: children, customElementProps: submittedData, content: message, ...otherProps, children: _jsx("div", { className: "text-green-500 text-sm sm:text-base", children: message }) }));
312
+ } }));
313
+ });
314
+ /**
315
+ * Fields component for rendering a form with custom field renderers.
316
+ * This component handles the rendering of form fields based on the provided fieldMap.
317
+ * Must be used within Form.Root to access form context.
318
+ *
319
+ * @component
320
+ * @param {FieldsProps} props - Component props
321
+ * @param {FieldMap} props.fieldMap - A mapping of field types to their corresponding React components
322
+ * @param {string} props.rowGapClassname - CSS class name for gap between rows
323
+ * @param {string} props.columnGapClassname - CSS class name for gap between columns
324
+ * @example
325
+ * ```tsx
326
+ * import { Form } from '@wix/headless-forms/react';
327
+ * import { TextInput, TextArea, Checkbox } from './field-components';
328
+ *
329
+ * const FIELD_MAP = {
330
+ * TEXT_INPUT: TextInput,
331
+ * TEXT_AREA: TextArea,
332
+ * CHECKBOX: Checkbox,
333
+ * NUMBER_INPUT: NumberInput,
334
+ * // ... remaining field components
335
+ * };
336
+ *
337
+ * function ContactForm({ formServiceConfig }) {
338
+ * return (
339
+ * <Form.Root formServiceConfig={formServiceConfig}>
340
+ * <Form.Loading className="flex justify-center p-4" />
341
+ * <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
342
+ * <Form.Fields
343
+ * fieldMap={FIELD_MAP}
344
+ * rowGapClassname="gap-y-4"
345
+ * columnGapClassname="gap-x-2"
346
+ * />
347
+ * </Form.Root>
348
+ * );
349
+ * }
350
+ * ```
351
+ */
352
+ /**
353
+ * Fields component for rendering a form with custom field renderers.
354
+ * It maps each field type from the form configuration to its corresponding React component
355
+ * and renders them in the order and layout defined by the form structure.
356
+ *
357
+ * The component automatically handles:
358
+ * - Field validation and error display
359
+ * - Form state management
360
+ * - Field value updates
361
+ * - Grid layout with configurable row and column gaps
362
+ *
363
+ * Must be used within Form.Root to access form context.
364
+ *
365
+ * @component
366
+ * @param {FieldsProps} props - The component props
367
+ * @param {FieldMap} props.fieldMap - A mapping of field types to their corresponding React components. Each key represents a field type (e.g., 'TEXT_INPUT', 'CHECKBOX') and the value is the React component that should render that field type.
368
+ * @param {string} props.rowGapClassname - CSS class name for gap between form rows
369
+ * @param {string} props.columnGapClassname - CSS class name for gap between form columns
370
+ *
371
+ * @example
372
+ * ```tsx
373
+ * import { Form } from '@wix/headless-forms/react';
374
+ * import { loadFormServiceConfig } from '@wix/headless-forms/services';
375
+ * import {
376
+ * TextInput,
377
+ * TextArea,
378
+ * PhoneInput,
379
+ * MultilineAddress,
380
+ * DateInput,
381
+ * DatePicker,
382
+ * DateTimeInput,
383
+ * FileUpload,
384
+ * NumberInput,
385
+ * Checkbox,
386
+ * Signature,
387
+ * RatingInput,
388
+ * RadioGroup,
389
+ * CheckboxGroup,
390
+ * Dropdown,
391
+ * Tags,
392
+ * TimeInput,
393
+ * RichText,
394
+ * SubmitButton,
395
+ * ProductList,
396
+ * FixedPayment,
397
+ * PaymentInput,
398
+ * Donation,
399
+ * Appointment,
400
+ * ImageChoice
401
+ * } from './components';
402
+ *
403
+ * // Define your field mapping - this tells the Fields component which React component to use for each field type
404
+ * const FIELD_MAP = {
405
+ * TEXT_INPUT: TextInput,
406
+ * TEXT_AREA: TextArea,
407
+ * PHONE_INPUT: PhoneInput,
408
+ * MULTILINE_ADDRESS: MultilineAddress,
409
+ * DATE_INPUT: DateInput,
410
+ * DATE_PICKER: DatePicker,
411
+ * DATE_TIME_INPUT: DateTimeInput,
412
+ * FILE_UPLOAD: FileUpload,
413
+ * NUMBER_INPUT: NumberInput,
414
+ * CHECKBOX: Checkbox,
415
+ * SIGNATURE: Signature,
416
+ * RATING_INPUT: RatingInput,
417
+ * RADIO_GROUP: RadioGroup,
418
+ * CHECKBOX_GROUP: CheckboxGroup,
419
+ * DROPDOWN: Dropdown,
420
+ * TAGS: Tags,
421
+ * TIME_INPUT: TimeInput,
422
+ * TEXT: RichText,
423
+ * SUBMIT_BUTTON: SubmitButton,
424
+ * PRODUCT_LIST: ProductList,
425
+ * FIXED_PAYMENT: FixedPayment,
426
+ * PAYMENT_INPUT: PaymentInput,
427
+ * DONATION: Donation,
428
+ * APPOINTMENT: Appointment,
429
+ * IMAGE_CHOICE: ImageChoice,
430
+ * };
431
+ *
432
+ * function ContactForm({ formServiceConfig }) {
433
+ * return (
434
+ * <Form.Root formServiceConfig={formServiceConfig}>
435
+ * <Form.Loading className="flex justify-center p-4" />
436
+ * <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
437
+ * <Form.Fields
438
+ * fieldMap={FIELD_MAP}
439
+ * rowGapClassname="gap-y-4"
440
+ * columnGapClassname="gap-x-2"
441
+ * />
442
+ * <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
443
+ * <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
444
+ * </Form.Root>
445
+ * );
446
+ * }
447
+ * ```
448
+ *
449
+ * @example
450
+ * ```tsx
451
+ * // Creating custom field components - ALL field components MUST use Form.Field
452
+ * // This example shows the REQUIRED structure for a TEXT_INPUT component
453
+ * import { Form, type TextInputProps } from '@wix/headless-forms/react';
454
+ *
455
+ * const TextInput = (props: TextInputProps) => {
456
+ * const { id, value, onChange, label, error, required, ...inputProps } = props;
457
+ *
458
+ * // Form.Field provides automatic grid layout positioning
459
+ * return (
460
+ * <Form.Field id={id}>
461
+ * <Form.Field.Label>
462
+ * <label className="text-foreground font-paragraph">
463
+ * {label}
464
+ * {required && <span className="text-destructive ml-1">*</span>}
465
+ * </label>
466
+ * </Form.Field.Label>
467
+ * <Form.Field.Input
468
+ * description={error && <span className="text-destructive text-sm">{error}</span>}
469
+ * >
470
+ * <input
471
+ * type="text"
472
+ * value={value || ''}
473
+ * onChange={(e) => onChange(e.target.value)}
474
+ * className="bg-background border-foreground text-foreground"
475
+ * aria-invalid={!!error}
476
+ * {...inputProps}
477
+ * />
478
+ * </Form.Field.Input>
479
+ * </Form.Field>
480
+ * );
481
+ * };
482
+ *
483
+ * const FIELD_MAP = {
484
+ * TEXT_INPUT: TextInput,
485
+ * // ... all other field components must also use Form.Field
486
+ * };
487
+ * ```
488
+ */
489
+ export const Fields = React.forwardRef((props, ref) => {
490
+ const [formValues, setFormValues] = useState({});
491
+ const [formErrors, setFormErrors] = useState([]);
492
+ const handleFormChange = useCallback((values) => {
493
+ setFormValues(values);
494
+ }, []);
495
+ const handleFormValidate = useCallback((errors) => {
496
+ setFormErrors(errors);
497
+ }, []);
498
+ return (_jsx(CoreFields, { children: ({ form, submitForm }) => {
499
+ if (!form)
500
+ return null;
501
+ return (_jsx("div", { ref: ref, children: _jsx(FormProvider, { currency: 'USD', locale: 'en', children: _jsx(FieldsWithForm, { form: form, values: formValues, onChange: handleFormChange, errors: formErrors, onValidate: handleFormValidate, fields: props.fieldMap, submitForm: () => submitForm(formValues), rowGapClassname: props.rowGapClassname, columnGapClassname: props.columnGapClassname }) }) }));
502
+ } }));
503
+ });
504
+ const FieldsWithForm = ({ form, submitForm, values, onChange, errors, onValidate, fields: fieldMap, rowGapClassname, columnGapClassname, }) => {
505
+ const formData = useForm({
506
+ form,
507
+ values,
508
+ errors,
509
+ onChange,
510
+ onValidate,
511
+ submitForm,
512
+ fieldMap,
513
+ });
514
+ if (!formData)
515
+ return null;
516
+ const { columnCount, fieldElements, fieldsLayout } = formData;
517
+ return (
518
+ // TODO: use readOnly, isDisabled
519
+ // TODO: step title a11y support
520
+ // TODO: mobile support?
521
+ _jsx(FieldLayoutProvider, { value: fieldsLayout, children: _jsx("form", { onSubmit: (e) => e.preventDefault(), children: _jsx("fieldset", { style: { display: 'flex', flexDirection: 'column' }, className: rowGapClassname, children: fieldElements.map((rowElements, index) => {
522
+ return (_jsx("div", { style: {
523
+ display: 'grid',
524
+ width: '100%',
525
+ gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
526
+ gridAutoRows: 'minmax(min-content, max-content)',
527
+ }, className: columnGapClassname, children: rowElements }, index));
528
+ }) }) }) }));
529
+ };
530
+ /**
531
+ * Container component for a form field with grid layout support.
532
+ * Provides context to Field.Label, Field.InputWrapper, Field.Input, and Field.Error child components.
533
+ * Based on the default-field-layout functionality.
534
+ *
535
+ * @component
536
+ * @example
537
+ * ```tsx
538
+ * import { Form } from '@wix/headless-forms/react';
539
+ *
540
+ * function FormFields() {
541
+ * return (
542
+ * <Form.Field id="username">
543
+ * <Form.Field.Label>
544
+ * <label className="text-foreground font-paragraph">Username</label>
545
+ * </Form.Field.Label>
546
+ * <Form.Field.InputWrapper>
547
+ * <Form.Field.Input description={<span className="text-secondary-foreground">Required</span>}>
548
+ * <input className="bg-background border-foreground text-foreground" />
549
+ * </Form.Field.Input>
550
+ * <Form.Field.Error>
551
+ * <span className="text-destructive text-sm font-paragraph">Username is required</span>
552
+ * </Form.Field.Error>
553
+ * </Form.Field.InputWrapper>
554
+ * </Form.Field>
555
+ * );
556
+ * }
557
+ * ```
558
+ */
559
+ const FieldRoot = React.forwardRef((props, ref) => {
560
+ const { id, children, asChild, className, ...otherProps } = props;
561
+ const layout = useFieldLayout(id);
562
+ if (!layout) {
563
+ return null;
564
+ }
565
+ return (_jsx(CoreField, { id: id, layout: layout, children: (fieldData) => {
566
+ const contextValue = {
567
+ id,
568
+ layout: fieldData.layout,
569
+ gridStyles: fieldData.gridStyles,
570
+ };
571
+ return (_jsx(FieldContext.Provider, { value: contextValue, children: _jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.fieldRoot, customElement: children, customElementProps: {}, ...otherProps, children: children }) }));
572
+ } }));
573
+ });
574
+ FieldRoot.displayName = 'Form.Field';
575
+ /**
576
+ * Label component for a form field with automatic grid positioning.
577
+ * Must be used within a Form.Field component.
578
+ * Renders in the label row of the field's grid layout.
579
+ *
580
+ * @component
581
+ * @example
582
+ * ```tsx
583
+ * import { Form } from '@wix/headless-forms/react';
584
+ *
585
+ * <Form.Field id="email">
586
+ * <Form.Field.Label>
587
+ * <label className="text-foreground font-paragraph">
588
+ * Email Address
589
+ * <Form.Field.Label.Required required={true} />
590
+ * </label>
591
+ * </Form.Field.Label>
592
+ * <Form.Field.InputWrapper>
593
+ * <Form.Field.Input>
594
+ * <input type="email" className="bg-background border-foreground text-foreground" />
595
+ * </Form.Field.Input>
596
+ * </Form.Field.InputWrapper>
597
+ * </Form.Field>
598
+ * ```
599
+ */
600
+ const FieldLabelRoot = React.forwardRef((props, ref) => {
601
+ const { children, asChild, className, ...otherProps } = props;
602
+ const { gridStyles } = useFieldContext();
603
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, style: gridStyles.label, "data-testid": TestIds.fieldLabel, customElement: children, customElementProps: {}, ...otherProps, children: _jsx("div", { children: children }) }));
604
+ });
605
+ FieldLabelRoot.displayName = 'Form.Field.Label';
606
+ /**
607
+ * Required indicator component for form field labels.
608
+ * Must be used within a Form.Field.Label component.
609
+ *
610
+ * @component
611
+ * @example
612
+ * ```tsx
613
+ * import { Form } from '@wix/headless-forms/react';
614
+ *
615
+ * // Basic usage with required prop
616
+ * <Form.Field.Label>
617
+ * <label className="text-foreground font-paragraph">
618
+ * Email Address
619
+ * <Form.Field.Label.Required />
620
+ * </label>
621
+ * </Form.Field.Label>
622
+ *
623
+ * // Custom styling
624
+ * <Form.Field.Label>
625
+ * <label className="text-foreground font-paragraph">
626
+ * Username
627
+ * <Form.Field.Label.Required required={true} className="text-destructive ml-2" />
628
+ * </label>
629
+ * </Form.Field.Label>
630
+ */
631
+ export const FieldLabelRequired = React.forwardRef((props, ref) => {
632
+ const { required = false, children, asChild, className, ...otherProps } = props;
633
+ const requiredIndicator = 'asterisk';
634
+ // @ts-expect-error
635
+ if (!required || requiredIndicator === 'none')
636
+ return null;
637
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, customElement: children, ...otherProps, children: _jsx("span", { children: requiredIndicator === 'asterisk'
638
+ ? '*'
639
+ : requiredIndicator === 'text'
640
+ ? '(Required)'
641
+ : null }) }));
642
+ });
643
+ FieldLabelRequired.displayName = 'Form.Field.Label.Required';
644
+ export const FieldLabel = FieldLabelRoot;
645
+ FieldLabel.Required = FieldLabelRequired;
646
+ /**
647
+ * InputWrapper component that wraps input and error elements with grid positioning.
648
+ * Must be used within a Form.Field component.
649
+ * This wrapper applies the grid positioning styles to contain both the input and error.
650
+ *
651
+ * @component
652
+ * @example
653
+ * ```tsx
654
+ * import { Form } from '@wix/headless-forms/react';
655
+ *
656
+ * <Form.Field id="email">
657
+ * <Form.Field.Label>
658
+ * <label className="text-foreground font-paragraph">Email Address</label>
659
+ * </Form.Field.Label>
660
+ * <Form.Field.InputWrapper>
661
+ * <Form.Field.Input>
662
+ * <input type="email" className="bg-background border-foreground text-foreground" />
663
+ * </Form.Field.Input>
664
+ * <Form.Field.Error>
665
+ * <span className="text-destructive text-sm font-paragraph">Please enter a valid email</span>
666
+ * </Form.Field.Error>
667
+ * </Form.Field.InputWrapper>
668
+ * </Form.Field>
669
+ * ```
670
+ */
671
+ export const FieldInputWrapper = React.forwardRef((props, ref) => {
672
+ const { children, asChild, className, ...otherProps } = props;
673
+ const { gridStyles } = useFieldContext();
674
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, style: gridStyles.input, "data-testid": TestIds.fieldInputWrapper, customElement: children, customElementProps: {}, ...otherProps, children: _jsx("div", { children: children }) }));
675
+ });
676
+ FieldInputWrapper.displayName = 'Form.Field.InputWrapper';
677
+ /**
678
+ * Input component for a form field.
679
+ * Must be used within a Form.Field.InputWrapper component.
680
+ * Renders the actual input element without grid positioning.
681
+ *
682
+ * @component
683
+ * @example
684
+ * ```tsx
685
+ * import { Form } from '@wix/headless-forms/react';
686
+ *
687
+ * <Form.Field id="password">
688
+ * <Form.Field.Label>
689
+ * <label className="text-foreground font-paragraph">Password</label>
690
+ * </Form.Field.Label>
691
+ * <Form.Field.InputWrapper>
692
+ * <Form.Field.Input description={<span className="text-secondary-foreground">Min 8 characters</span>}>
693
+ * <input type="password" className="bg-background border-foreground text-foreground" />
694
+ * </Form.Field.Input>
695
+ * </Form.Field.InputWrapper>
696
+ * </Form.Field>
697
+ * ```
698
+ */
699
+ export const FieldInput = React.forwardRef((props, ref) => {
700
+ const { children, description, asChild, className, ...otherProps } = props;
701
+ return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.fieldInput, customElement: children, customElementProps: {}, ...otherProps, children: _jsx("div", { children: children }) }));
702
+ });
703
+ FieldInput.displayName = 'Form.Field.Input';
704
+ /**
705
+ * Error component for displaying field-level validation errors.
706
+ * Must be used within a Form.Field.InputWrapper component.
707
+ * Only renders when there is an error for the current field.
708
+ *
709
+ * @component
710
+ * @example
711
+ * ```tsx
712
+ * import { Form } from '@wix/headless-forms/react';
713
+ *
714
+ * <Form.Field id="email">
715
+ * <Form.Field.Label>
716
+ * <label className="text-foreground font-paragraph">Email Address</label>
717
+ * </Form.Field.Label>
718
+ * <Form.Field.InputWrapper>
719
+ * <Form.Field.Input>
720
+ * <input type="email" className="bg-background border-foreground text-foreground" />
721
+ * </Form.Field.Input>
722
+ * <Form.Field.Error path="email">
723
+ * <span className="text-destructive text-sm font-paragraph">Please enter a valid email address</span>
724
+ * </Form.Field.Error>
725
+ * </Form.Field.InputWrapper>
726
+ * </Form.Field>
727
+ * ```
728
+ */
729
+ export const FieldError = React.forwardRef((props, ref) => {
730
+ const { errorMessage, asChild, className, children, ...otherProps } = props;
731
+ if (!errorMessage && !children)
732
+ return null;
733
+ return (_jsx(AsChildSlot, { "data-testid": TestIds.fieldError, ref: ref, asChild: asChild, className: className, ...otherProps, children: children || errorMessage }));
734
+ });
735
+ FieldError.displayName = 'Form.Field.Error';
736
+ export const Field = FieldRoot;
737
+ Field.Label = FieldLabel;
738
+ Field.InputWrapper = FieldInputWrapper;
739
+ Field.Input = FieldInput;
740
+ Field.Error = FieldError;