@wix/headless-forms 0.0.9 → 0.0.11
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/cjs/dist/react/Form.d.ts +145 -20
- package/cjs/dist/react/Form.js +218 -26
- package/cjs/dist/react/core/Form.d.ts +80 -5
- package/cjs/dist/react/core/Form.js +41 -0
- package/cjs/dist/react/utils.d.ts +13 -0
- package/cjs/dist/react/utils.js +20 -0
- package/cjs/dist/services/form-service.d.ts +2 -0
- package/cjs/dist/services/form-service.js +5 -2
- package/dist/react/Form.d.ts +145 -20
- package/dist/react/Form.js +212 -20
- package/dist/react/core/Form.d.ts +80 -5
- package/dist/react/core/Form.js +40 -0
- package/dist/react/utils.d.ts +13 -0
- package/dist/react/utils.js +17 -0
- package/dist/services/form-service.d.ts +2 -0
- package/dist/services/form-service.js +5 -2
- package/package.json +3 -3
package/cjs/dist/react/Form.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { type CheckboxGroupProps, type CheckboxProps, type PhoneInputProps, type DateInputProps, type DatePickerProps, type DateTimeInputProps, type DropdownProps, type FileUploadProps, type MultilineAddressProps, type NumberInputProps, type RadioGroupProps, type RatingInputProps, type RichTextProps, type SignatureProps, type SubmitButtonProps, type TagsProps, type TextAreaProps, type TextInputProps, type TimeInputProps, type ProductListProps, type FixedPaymentProps, type PaymentInputProps, type DonationProps, type AppointmentProps, type ImageChoiceProps } from './types';
|
|
3
|
-
import { type FormServiceConfig } from '../services/form-service';
|
|
2
|
+
import { type CheckboxGroupProps, type CheckboxProps, type PhoneInputProps, type DateInputProps, type DatePickerProps, type DateTimeInputProps, type DropdownProps, type FileUploadProps, type MultilineAddressProps, type NumberInputProps, type RadioGroupProps, type RatingInputProps, type RichTextProps, type SignatureProps, type SubmitButtonProps, type TagsProps, type TextAreaProps, type TextInputProps, type TimeInputProps, type ProductListProps, type FixedPaymentProps, type PaymentInputProps, type DonationProps, type AppointmentProps, type ImageChoiceProps } from './types.js';
|
|
3
|
+
import { type FormServiceConfig } from '../services/form-service.js';
|
|
4
4
|
/**
|
|
5
5
|
* Props for the Form root component following the documented API
|
|
6
6
|
*/
|
|
@@ -303,6 +303,9 @@ export declare const Submitted: React.ForwardRefExoticComponent<SubmittedProps &
|
|
|
303
303
|
/**
|
|
304
304
|
* Mapping of form field types to their corresponding React components.
|
|
305
305
|
*
|
|
306
|
+
* ALL field components in this map MUST use Form.Field for proper
|
|
307
|
+
* grid layout positioning.
|
|
308
|
+
*
|
|
306
309
|
* Each key represents a field type identifier that matches the field types defined
|
|
307
310
|
* in the form configuration, and each value is a React component that will receive
|
|
308
311
|
* the field's props and render the appropriate UI element.
|
|
@@ -401,6 +404,8 @@ export interface FieldMap {
|
|
|
401
404
|
*
|
|
402
405
|
* @interface FieldsProps
|
|
403
406
|
* @property {FieldMap} fieldMap - A mapping of field types to their corresponding React components
|
|
407
|
+
* @property {string} rowGapClassname - CSS class name for gap between rows
|
|
408
|
+
* @property {string} columnGapClassname - CSS class name for gap between columns
|
|
404
409
|
* @example
|
|
405
410
|
* ```tsx
|
|
406
411
|
* const FIELD_MAP = {
|
|
@@ -414,11 +419,13 @@ export interface FieldMap {
|
|
|
414
419
|
* // ... remaining field components
|
|
415
420
|
* };
|
|
416
421
|
*
|
|
417
|
-
* <Form.Fields fieldMap={FIELD_MAP} />
|
|
422
|
+
* <Form.Fields fieldMap={FIELD_MAP} rowGapClassname="gap-y-4" columnGapClassname="gap-x-2" />
|
|
418
423
|
* ```
|
|
419
424
|
*/
|
|
420
425
|
interface FieldsProps {
|
|
421
426
|
fieldMap: FieldMap;
|
|
427
|
+
rowGapClassname: string;
|
|
428
|
+
columnGapClassname: string;
|
|
422
429
|
}
|
|
423
430
|
/**
|
|
424
431
|
* Fields component for rendering a form with custom field renderers.
|
|
@@ -428,6 +435,8 @@ interface FieldsProps {
|
|
|
428
435
|
* @component
|
|
429
436
|
* @param {FieldsProps} props - Component props
|
|
430
437
|
* @param {FieldMap} props.fieldMap - A mapping of field types to their corresponding React components
|
|
438
|
+
* @param {string} props.rowGapClassname - CSS class name for gap between rows
|
|
439
|
+
* @param {string} props.columnGapClassname - CSS class name for gap between columns
|
|
431
440
|
* @example
|
|
432
441
|
* ```tsx
|
|
433
442
|
* import { Form } from '@wix/headless-forms/react';
|
|
@@ -446,7 +455,11 @@ interface FieldsProps {
|
|
|
446
455
|
* <Form.Root formServiceConfig={formServiceConfig}>
|
|
447
456
|
* <Form.Loading className="flex justify-center p-4" />
|
|
448
457
|
* <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
|
|
449
|
-
* <Form.Fields
|
|
458
|
+
* <Form.Fields
|
|
459
|
+
* fieldMap={FIELD_MAP}
|
|
460
|
+
* rowGapClassname="gap-y-4"
|
|
461
|
+
* columnGapClassname="gap-x-2"
|
|
462
|
+
* />
|
|
450
463
|
* </Form.Root>
|
|
451
464
|
* );
|
|
452
465
|
* }
|
|
@@ -461,12 +474,15 @@ interface FieldsProps {
|
|
|
461
474
|
* - Field validation and error display
|
|
462
475
|
* - Form state management
|
|
463
476
|
* - Field value updates
|
|
477
|
+
* - Grid layout with configurable row and column gaps
|
|
464
478
|
*
|
|
465
479
|
* Must be used within Form.Root to access form context.
|
|
466
480
|
*
|
|
467
481
|
* @component
|
|
468
482
|
* @param {FieldsProps} props - The component props
|
|
469
483
|
* @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.
|
|
484
|
+
* @param {string} props.rowGapClassname - CSS class name for gap between form rows
|
|
485
|
+
* @param {string} props.columnGapClassname - CSS class name for gap between form columns
|
|
470
486
|
*
|
|
471
487
|
* @example
|
|
472
488
|
* ```tsx
|
|
@@ -534,7 +550,11 @@ interface FieldsProps {
|
|
|
534
550
|
* <Form.Root formServiceConfig={formServiceConfig}>
|
|
535
551
|
* <Form.Loading className="flex justify-center p-4" />
|
|
536
552
|
* <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
|
|
537
|
-
* <Form.Fields
|
|
553
|
+
* <Form.Fields
|
|
554
|
+
* fieldMap={FIELD_MAP}
|
|
555
|
+
* rowGapClassname="gap-y-4"
|
|
556
|
+
* columnGapClassname="gap-x-2"
|
|
557
|
+
* />
|
|
538
558
|
* <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
|
|
539
559
|
* <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
|
|
540
560
|
* </Form.Root>
|
|
@@ -544,25 +564,130 @@ interface FieldsProps {
|
|
|
544
564
|
*
|
|
545
565
|
* @example
|
|
546
566
|
* ```tsx
|
|
547
|
-
* //
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
567
|
+
* // Creating custom field components - ALL field components MUST use Form.Field
|
|
568
|
+
* // This example shows the REQUIRED structure for a TEXT_INPUT component
|
|
569
|
+
* import { Form, type TextInputProps } from '@wix/headless-forms/react';
|
|
570
|
+
*
|
|
571
|
+
* const TextInput = (props: TextInputProps) => {
|
|
572
|
+
* const { id, value, onChange, label, error, required, ...inputProps } = props;
|
|
573
|
+
*
|
|
574
|
+
* // Form.Field provides automatic grid layout positioning
|
|
575
|
+
* return (
|
|
576
|
+
* <Form.Field id={id}>
|
|
577
|
+
* <Form.Field.Label>
|
|
578
|
+
* <label className="text-foreground font-paragraph">
|
|
579
|
+
* {label}
|
|
580
|
+
* {required && <span className="text-destructive ml-1">*</span>}
|
|
581
|
+
* </label>
|
|
582
|
+
* </Form.Field.Label>
|
|
583
|
+
* <Form.Field.Input
|
|
584
|
+
* description={error && <span className="text-destructive text-sm">{error}</span>}
|
|
585
|
+
* >
|
|
586
|
+
* <input
|
|
587
|
+
* type="text"
|
|
588
|
+
* value={value || ''}
|
|
589
|
+
* onChange={(e) => onChange(e.target.value)}
|
|
590
|
+
* className="bg-background border-foreground text-foreground"
|
|
591
|
+
* aria-invalid={!!error}
|
|
592
|
+
* {...inputProps}
|
|
593
|
+
* />
|
|
594
|
+
* </Form.Field.Input>
|
|
595
|
+
* </Form.Field>
|
|
596
|
+
* );
|
|
597
|
+
* };
|
|
560
598
|
*
|
|
561
599
|
* const FIELD_MAP = {
|
|
562
|
-
* TEXT_INPUT:
|
|
563
|
-
* // ... other field components
|
|
600
|
+
* TEXT_INPUT: TextInput,
|
|
601
|
+
* // ... all other field components must also use Form.Field
|
|
564
602
|
* };
|
|
565
603
|
* ```
|
|
566
604
|
*/
|
|
567
605
|
export declare const Fields: React.ForwardRefExoticComponent<FieldsProps & React.RefAttributes<HTMLDivElement>>;
|
|
606
|
+
/**
|
|
607
|
+
* Props for Field container component
|
|
608
|
+
*/
|
|
609
|
+
export interface FieldProps {
|
|
610
|
+
/** The unique identifier for this field */
|
|
611
|
+
id: string;
|
|
612
|
+
/** Child components (Field.Label, Field.Input, etc.) */
|
|
613
|
+
children: React.ReactNode;
|
|
614
|
+
/** Whether to render as a child component */
|
|
615
|
+
asChild?: boolean;
|
|
616
|
+
/** CSS classes to apply to the root element */
|
|
617
|
+
className?: string;
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Field component with sub-components
|
|
621
|
+
*/
|
|
622
|
+
interface FieldComponent extends React.ForwardRefExoticComponent<FieldProps & React.RefAttributes<HTMLDivElement>> {
|
|
623
|
+
Label: typeof FieldLabel;
|
|
624
|
+
Input: typeof FieldInput;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Props for Field.Label component
|
|
628
|
+
*/
|
|
629
|
+
export interface FieldLabelProps {
|
|
630
|
+
/** Label content to display */
|
|
631
|
+
children: React.ReactNode;
|
|
632
|
+
/** Whether to render as a child component */
|
|
633
|
+
asChild?: boolean;
|
|
634
|
+
/** CSS classes to apply to the label element */
|
|
635
|
+
className?: string;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Props for Field.Input component
|
|
639
|
+
*/
|
|
640
|
+
export interface FieldInputProps {
|
|
641
|
+
/** Input element to render */
|
|
642
|
+
children: React.ReactNode;
|
|
643
|
+
/** Whether to render as a child component */
|
|
644
|
+
asChild?: boolean;
|
|
645
|
+
/** CSS classes to apply to the input element */
|
|
646
|
+
className?: string;
|
|
647
|
+
/** Description text to display below the input */
|
|
648
|
+
description?: React.ReactNode;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Label component for a form field with automatic grid positioning.
|
|
652
|
+
* Must be used within a Form.Field component.
|
|
653
|
+
* Renders in the label row of the field's grid layout.
|
|
654
|
+
*
|
|
655
|
+
* @component
|
|
656
|
+
* @example
|
|
657
|
+
* ```tsx
|
|
658
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
659
|
+
*
|
|
660
|
+
* <Form.Field id="email">
|
|
661
|
+
* <Form.Field.Label>
|
|
662
|
+
* <label className="text-foreground font-paragraph">Email Address</label>
|
|
663
|
+
* </Form.Field.Label>
|
|
664
|
+
* <Form.Field.Input>
|
|
665
|
+
* <input type="email" className="bg-background border-foreground" />
|
|
666
|
+
* </Form.Field.Input>
|
|
667
|
+
* </Form.Field>
|
|
668
|
+
* ```
|
|
669
|
+
*/
|
|
670
|
+
export declare const FieldLabel: React.ForwardRefExoticComponent<FieldLabelProps & React.RefAttributes<HTMLDivElement>>;
|
|
671
|
+
/**
|
|
672
|
+
* Input component for a form field with automatic grid positioning.
|
|
673
|
+
* Must be used within a Form.Field component.
|
|
674
|
+
* Renders in the input row of the field's grid layout with optional description.
|
|
675
|
+
*
|
|
676
|
+
* @component
|
|
677
|
+
* @example
|
|
678
|
+
* ```tsx
|
|
679
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
680
|
+
*
|
|
681
|
+
* <Form.Field id="password">
|
|
682
|
+
* <Form.Field.Label>
|
|
683
|
+
* <label className="text-foreground font-paragraph">Password</label>
|
|
684
|
+
* </Form.Field.Label>
|
|
685
|
+
* <Form.Field.Input description={<span className="text-secondary-foreground">Min 8 characters</span>}>
|
|
686
|
+
* <input type="password" className="bg-background border-foreground text-foreground" />
|
|
687
|
+
* </Form.Field.Input>
|
|
688
|
+
* </Form.Field>
|
|
689
|
+
* ```
|
|
690
|
+
*/
|
|
691
|
+
export declare const FieldInput: React.ForwardRefExoticComponent<FieldInputProps & React.RefAttributes<HTMLDivElement>>;
|
|
692
|
+
export declare const Field: FieldComponent;
|
|
568
693
|
export {};
|
package/cjs/dist/react/Form.js
CHANGED
|
@@ -33,12 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Fields = exports.Submitted = exports.Error = exports.LoadingError = exports.Loading = exports.Root = void 0;
|
|
36
|
+
exports.Field = exports.FieldInput = exports.FieldLabel = exports.Fields = exports.Submitted = exports.Error = exports.LoadingError = exports.Loading = exports.Root = void 0;
|
|
37
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
38
|
const react_1 = __importStar(require("react"));
|
|
39
39
|
const react_2 = require("@wix/headless-utils/react");
|
|
40
40
|
const form_public_1 = require("@wix/form-public");
|
|
41
|
-
const
|
|
41
|
+
const Form_js_1 = require("./core/Form.js");
|
|
42
42
|
var TestIds;
|
|
43
43
|
(function (TestIds) {
|
|
44
44
|
TestIds["formRoot"] = "form-root";
|
|
@@ -47,6 +47,9 @@ var TestIds;
|
|
|
47
47
|
TestIds["formLoadingError"] = "form-loading-error";
|
|
48
48
|
TestIds["formError"] = "form-error";
|
|
49
49
|
TestIds["formSubmitted"] = "form-submitted";
|
|
50
|
+
TestIds["fieldRoot"] = "field-root";
|
|
51
|
+
TestIds["fieldLabel"] = "field-label";
|
|
52
|
+
TestIds["fieldInput"] = "field-input";
|
|
50
53
|
})(TestIds || (TestIds = {}));
|
|
51
54
|
/**
|
|
52
55
|
* Root component that provides all necessary service contexts for a complete form experience.
|
|
@@ -100,7 +103,7 @@ var TestIds;
|
|
|
100
103
|
*/
|
|
101
104
|
exports.Root = react_1.default.forwardRef((props, ref) => {
|
|
102
105
|
const { children, formServiceConfig, asChild, ...otherProps } = props;
|
|
103
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
106
|
+
return ((0, jsx_runtime_1.jsx)(Form_js_1.Root, { formServiceConfig: formServiceConfig, children: (0, jsx_runtime_1.jsx)(RootContent, { asChild: asChild, ref: ref, ...otherProps, children: children }) }));
|
|
104
107
|
});
|
|
105
108
|
/**
|
|
106
109
|
* Internal component to handle the Root content with service access.
|
|
@@ -164,7 +167,7 @@ const RootContent = react_1.default.forwardRef((props, ref) => {
|
|
|
164
167
|
*/
|
|
165
168
|
exports.Loading = react_1.default.forwardRef((props, ref) => {
|
|
166
169
|
const { asChild, children, className, ...otherProps } = props;
|
|
167
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
170
|
+
return ((0, jsx_runtime_1.jsx)(Form_js_1.Loading, { children: ({ isLoading }) => {
|
|
168
171
|
if (!isLoading)
|
|
169
172
|
return null;
|
|
170
173
|
return ((0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { "data-testid": TestIds.formLoading, ref: ref, asChild: asChild, className: className, customElement: children, content: "Loading form...", ...otherProps, children: (0, jsx_runtime_1.jsx)("div", { children: "Loading form..." }) }));
|
|
@@ -221,7 +224,7 @@ exports.Loading = react_1.default.forwardRef((props, ref) => {
|
|
|
221
224
|
*/
|
|
222
225
|
exports.LoadingError = react_1.default.forwardRef((props, ref) => {
|
|
223
226
|
const { asChild, children, className, ...otherProps } = props;
|
|
224
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
227
|
+
return ((0, jsx_runtime_1.jsx)(Form_js_1.LoadingError, { children: ({ error, hasError }) => {
|
|
225
228
|
if (!hasError)
|
|
226
229
|
return null;
|
|
227
230
|
const errorData = { error, hasError };
|
|
@@ -277,7 +280,7 @@ exports.LoadingError = react_1.default.forwardRef((props, ref) => {
|
|
|
277
280
|
*/
|
|
278
281
|
exports.Error = react_1.default.forwardRef((props, ref) => {
|
|
279
282
|
const { asChild, children, className, ...otherProps } = props;
|
|
280
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
283
|
+
return ((0, jsx_runtime_1.jsx)(Form_js_1.Error, { children: ({ error, hasError }) => {
|
|
281
284
|
if (!hasError)
|
|
282
285
|
return null;
|
|
283
286
|
const errorData = { error, hasError };
|
|
@@ -333,7 +336,7 @@ exports.Error = react_1.default.forwardRef((props, ref) => {
|
|
|
333
336
|
*/
|
|
334
337
|
exports.Submitted = react_1.default.forwardRef((props, ref) => {
|
|
335
338
|
const { asChild, children, className, ...otherProps } = props;
|
|
336
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
339
|
+
return ((0, jsx_runtime_1.jsx)(Form_js_1.Submitted, { children: ({ isSubmitted, message }) => {
|
|
337
340
|
if (!isSubmitted)
|
|
338
341
|
return null;
|
|
339
342
|
const submittedData = { isSubmitted, message };
|
|
@@ -348,6 +351,8 @@ exports.Submitted = react_1.default.forwardRef((props, ref) => {
|
|
|
348
351
|
* @component
|
|
349
352
|
* @param {FieldsProps} props - Component props
|
|
350
353
|
* @param {FieldMap} props.fieldMap - A mapping of field types to their corresponding React components
|
|
354
|
+
* @param {string} props.rowGapClassname - CSS class name for gap between rows
|
|
355
|
+
* @param {string} props.columnGapClassname - CSS class name for gap between columns
|
|
351
356
|
* @example
|
|
352
357
|
* ```tsx
|
|
353
358
|
* import { Form } from '@wix/headless-forms/react';
|
|
@@ -366,7 +371,11 @@ exports.Submitted = react_1.default.forwardRef((props, ref) => {
|
|
|
366
371
|
* <Form.Root formServiceConfig={formServiceConfig}>
|
|
367
372
|
* <Form.Loading className="flex justify-center p-4" />
|
|
368
373
|
* <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
|
|
369
|
-
* <Form.Fields
|
|
374
|
+
* <Form.Fields
|
|
375
|
+
* fieldMap={FIELD_MAP}
|
|
376
|
+
* rowGapClassname="gap-y-4"
|
|
377
|
+
* columnGapClassname="gap-x-2"
|
|
378
|
+
* />
|
|
370
379
|
* </Form.Root>
|
|
371
380
|
* );
|
|
372
381
|
* }
|
|
@@ -381,12 +390,15 @@ exports.Submitted = react_1.default.forwardRef((props, ref) => {
|
|
|
381
390
|
* - Field validation and error display
|
|
382
391
|
* - Form state management
|
|
383
392
|
* - Field value updates
|
|
393
|
+
* - Grid layout with configurable row and column gaps
|
|
384
394
|
*
|
|
385
395
|
* Must be used within Form.Root to access form context.
|
|
386
396
|
*
|
|
387
397
|
* @component
|
|
388
398
|
* @param {FieldsProps} props - The component props
|
|
389
399
|
* @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.
|
|
400
|
+
* @param {string} props.rowGapClassname - CSS class name for gap between form rows
|
|
401
|
+
* @param {string} props.columnGapClassname - CSS class name for gap between form columns
|
|
390
402
|
*
|
|
391
403
|
* @example
|
|
392
404
|
* ```tsx
|
|
@@ -454,7 +466,11 @@ exports.Submitted = react_1.default.forwardRef((props, ref) => {
|
|
|
454
466
|
* <Form.Root formServiceConfig={formServiceConfig}>
|
|
455
467
|
* <Form.Loading className="flex justify-center p-4" />
|
|
456
468
|
* <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
|
|
457
|
-
* <Form.Fields
|
|
469
|
+
* <Form.Fields
|
|
470
|
+
* fieldMap={FIELD_MAP}
|
|
471
|
+
* rowGapClassname="gap-y-4"
|
|
472
|
+
* columnGapClassname="gap-x-2"
|
|
473
|
+
* />
|
|
458
474
|
* <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
|
|
459
475
|
* <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
|
|
460
476
|
* </Form.Root>
|
|
@@ -464,23 +480,41 @@ exports.Submitted = react_1.default.forwardRef((props, ref) => {
|
|
|
464
480
|
*
|
|
465
481
|
* @example
|
|
466
482
|
* ```tsx
|
|
467
|
-
* //
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
483
|
+
* // Creating custom field components - ALL field components MUST use Form.Field
|
|
484
|
+
* // This example shows the REQUIRED structure for a TEXT_INPUT component
|
|
485
|
+
* import { Form, type TextInputProps } from '@wix/headless-forms/react';
|
|
486
|
+
*
|
|
487
|
+
* const TextInput = (props: TextInputProps) => {
|
|
488
|
+
* const { id, value, onChange, label, error, required, ...inputProps } = props;
|
|
489
|
+
*
|
|
490
|
+
* // Form.Field provides automatic grid layout positioning
|
|
491
|
+
* return (
|
|
492
|
+
* <Form.Field id={id}>
|
|
493
|
+
* <Form.Field.Label>
|
|
494
|
+
* <label className="text-foreground font-paragraph">
|
|
495
|
+
* {label}
|
|
496
|
+
* {required && <span className="text-destructive ml-1">*</span>}
|
|
497
|
+
* </label>
|
|
498
|
+
* </Form.Field.Label>
|
|
499
|
+
* <Form.Field.Input
|
|
500
|
+
* description={error && <span className="text-destructive text-sm">{error}</span>}
|
|
501
|
+
* >
|
|
502
|
+
* <input
|
|
503
|
+
* type="text"
|
|
504
|
+
* value={value || ''}
|
|
505
|
+
* onChange={(e) => onChange(e.target.value)}
|
|
506
|
+
* className="bg-background border-foreground text-foreground"
|
|
507
|
+
* aria-invalid={!!error}
|
|
508
|
+
* {...inputProps}
|
|
509
|
+
* />
|
|
510
|
+
* </Form.Field.Input>
|
|
511
|
+
* </Form.Field>
|
|
512
|
+
* );
|
|
513
|
+
* };
|
|
480
514
|
*
|
|
481
515
|
* const FIELD_MAP = {
|
|
482
|
-
* TEXT_INPUT:
|
|
483
|
-
* // ... other field components
|
|
516
|
+
* TEXT_INPUT: TextInput,
|
|
517
|
+
* // ... all other field components must also use Form.Field
|
|
484
518
|
* };
|
|
485
519
|
* ```
|
|
486
520
|
*/
|
|
@@ -493,9 +527,167 @@ exports.Fields = react_1.default.forwardRef((props, ref) => {
|
|
|
493
527
|
const handleFormValidate = (0, react_1.useCallback)((errors) => {
|
|
494
528
|
setFormErrors(errors);
|
|
495
529
|
}, []);
|
|
496
|
-
return ((0, jsx_runtime_1.jsx)(
|
|
530
|
+
return ((0, jsx_runtime_1.jsx)(Form_js_1.Fields, { children: ({ form, submitForm }) => {
|
|
497
531
|
if (!form)
|
|
498
532
|
return null;
|
|
499
|
-
return ((0, jsx_runtime_1.jsx)("div", { ref: ref, children: (0, jsx_runtime_1.jsx)(form_public_1.
|
|
533
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: ref, children: (0, jsx_runtime_1.jsx)(form_public_1.FormProvider, { children: (0, jsx_runtime_1.jsx)(FieldsWithForm, { form: form, values: formValues, onChange: handleFormChange, errors: formErrors, onValidate: handleFormValidate, fields: props.fieldMap, submitForm: () => submitForm(formValues), rowGapClassname: props.rowGapClassname, columnGapClassname: props.columnGapClassname }) }) }));
|
|
500
534
|
} }));
|
|
501
535
|
});
|
|
536
|
+
const FieldsWithForm = ({ form, submitForm, values, onChange, errors, onValidate, fields: fieldMap, rowGapClassname, columnGapClassname, }) => {
|
|
537
|
+
const formData = (0, form_public_1.useForm)({
|
|
538
|
+
form,
|
|
539
|
+
values,
|
|
540
|
+
onChange,
|
|
541
|
+
errors,
|
|
542
|
+
onValidate,
|
|
543
|
+
submitForm,
|
|
544
|
+
fieldMap,
|
|
545
|
+
});
|
|
546
|
+
if (!formData)
|
|
547
|
+
return null;
|
|
548
|
+
console.log('formData', formData);
|
|
549
|
+
const { columnCount, fieldElements, fieldsLayout } = formData;
|
|
550
|
+
return (
|
|
551
|
+
// TODO: use readOnly, isDisabled
|
|
552
|
+
// TODO: step title a11y support
|
|
553
|
+
// TODO: mobile support?
|
|
554
|
+
(0, jsx_runtime_1.jsx)(FieldLayoutProvider, { value: fieldsLayout, children: (0, jsx_runtime_1.jsx)("form", { onSubmit: (e) => e.preventDefault(), children: (0, jsx_runtime_1.jsx)("fieldset", { style: { display: 'flex', flexDirection: 'column' }, className: rowGapClassname, children: fieldElements.map((rowElements, index) => {
|
|
555
|
+
return ((0, jsx_runtime_1.jsx)("div", { style: {
|
|
556
|
+
display: 'grid',
|
|
557
|
+
width: '100%',
|
|
558
|
+
gridTemplateColumns: `repeat(${columnCount}, 1fr)`,
|
|
559
|
+
gridAutoRows: 'minmax(min-content, max-content)',
|
|
560
|
+
}, className: columnGapClassname, children: rowElements }, index));
|
|
561
|
+
}) }) }) }));
|
|
562
|
+
};
|
|
563
|
+
/**
|
|
564
|
+
* Context for sharing field layout data across the form
|
|
565
|
+
* @internal
|
|
566
|
+
*/
|
|
567
|
+
const FieldLayoutContext = react_1.default.createContext(null);
|
|
568
|
+
/**
|
|
569
|
+
* Provider component that makes field layout data available to child components
|
|
570
|
+
* @internal
|
|
571
|
+
*/
|
|
572
|
+
const FieldLayoutProvider = ({ value, children, }) => {
|
|
573
|
+
return ((0, jsx_runtime_1.jsx)(FieldLayoutContext.Provider, { value: value, children: children }));
|
|
574
|
+
};
|
|
575
|
+
/**
|
|
576
|
+
* Hook to access layout configuration for a specific field
|
|
577
|
+
* @internal
|
|
578
|
+
* @param {string} fieldId - The unique identifier of the field
|
|
579
|
+
* @returns {Layout | null} The layout configuration for the field, or null if not found
|
|
580
|
+
*/
|
|
581
|
+
function useFieldLayout(fieldId) {
|
|
582
|
+
const layoutMap = react_1.default.useContext(FieldLayoutContext);
|
|
583
|
+
if (!layoutMap) {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
return layoutMap[fieldId] || null;
|
|
587
|
+
}
|
|
588
|
+
const FieldContext = react_1.default.createContext(null);
|
|
589
|
+
/**
|
|
590
|
+
* Hook to access field context
|
|
591
|
+
*/
|
|
592
|
+
function useFieldContext() {
|
|
593
|
+
const context = react_1.default.useContext(FieldContext);
|
|
594
|
+
if (!context) {
|
|
595
|
+
throw new globalThis.Error('Field components must be used within a Form.Field component');
|
|
596
|
+
}
|
|
597
|
+
return context;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Container component for a form field with grid layout support.
|
|
601
|
+
* Provides context to Field.Label and Field.Input child components.
|
|
602
|
+
* Based on the default-field-layout functionality.
|
|
603
|
+
*
|
|
604
|
+
* @component
|
|
605
|
+
* @example
|
|
606
|
+
* ```tsx
|
|
607
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
608
|
+
*
|
|
609
|
+
* function FormFields() {
|
|
610
|
+
* return (
|
|
611
|
+
* <Form.Field id="username">
|
|
612
|
+
* <Form.Field.Label>
|
|
613
|
+
* <label className="text-foreground font-paragraph">Username</label>
|
|
614
|
+
* </Form.Field.Label>
|
|
615
|
+
* <Form.Field.Input description={<span className="text-secondary-foreground">Required</span>}>
|
|
616
|
+
* <input className="bg-background border-foreground text-foreground" />
|
|
617
|
+
* </Form.Field.Input>
|
|
618
|
+
* </Form.Field>
|
|
619
|
+
* );
|
|
620
|
+
* }
|
|
621
|
+
* ```
|
|
622
|
+
*/
|
|
623
|
+
const FieldRoot = react_1.default.forwardRef((props, ref) => {
|
|
624
|
+
const { id, children, asChild, className, ...otherProps } = props;
|
|
625
|
+
const layout = useFieldLayout(id);
|
|
626
|
+
if (!layout) {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
return ((0, jsx_runtime_1.jsx)(Form_js_1.Field, { id: id, layout: layout, children: (fieldData) => {
|
|
630
|
+
const contextValue = {
|
|
631
|
+
id,
|
|
632
|
+
layout: fieldData.layout,
|
|
633
|
+
gridStyles: fieldData.gridStyles,
|
|
634
|
+
};
|
|
635
|
+
return ((0, jsx_runtime_1.jsx)(FieldContext.Provider, { value: contextValue, children: (0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.fieldRoot, customElement: children, customElementProps: {}, ...otherProps, children: children }) }));
|
|
636
|
+
} }));
|
|
637
|
+
});
|
|
638
|
+
FieldRoot.displayName = 'Form.Field';
|
|
639
|
+
/**
|
|
640
|
+
* Label component for a form field with automatic grid positioning.
|
|
641
|
+
* Must be used within a Form.Field component.
|
|
642
|
+
* Renders in the label row of the field's grid layout.
|
|
643
|
+
*
|
|
644
|
+
* @component
|
|
645
|
+
* @example
|
|
646
|
+
* ```tsx
|
|
647
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
648
|
+
*
|
|
649
|
+
* <Form.Field id="email">
|
|
650
|
+
* <Form.Field.Label>
|
|
651
|
+
* <label className="text-foreground font-paragraph">Email Address</label>
|
|
652
|
+
* </Form.Field.Label>
|
|
653
|
+
* <Form.Field.Input>
|
|
654
|
+
* <input type="email" className="bg-background border-foreground" />
|
|
655
|
+
* </Form.Field.Input>
|
|
656
|
+
* </Form.Field>
|
|
657
|
+
* ```
|
|
658
|
+
*/
|
|
659
|
+
exports.FieldLabel = react_1.default.forwardRef((props, ref) => {
|
|
660
|
+
const { children, asChild, className, ...otherProps } = props;
|
|
661
|
+
const { gridStyles } = useFieldContext();
|
|
662
|
+
return ((0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { ref: ref, asChild: asChild, className: className, style: gridStyles.label, "data-testid": TestIds.fieldLabel, customElement: children, customElementProps: {}, ...otherProps, children: (0, jsx_runtime_1.jsx)("div", { children: children }) }));
|
|
663
|
+
});
|
|
664
|
+
exports.FieldLabel.displayName = 'Form.Field.Label';
|
|
665
|
+
/**
|
|
666
|
+
* Input component for a form field with automatic grid positioning.
|
|
667
|
+
* Must be used within a Form.Field component.
|
|
668
|
+
* Renders in the input row of the field's grid layout with optional description.
|
|
669
|
+
*
|
|
670
|
+
* @component
|
|
671
|
+
* @example
|
|
672
|
+
* ```tsx
|
|
673
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
674
|
+
*
|
|
675
|
+
* <Form.Field id="password">
|
|
676
|
+
* <Form.Field.Label>
|
|
677
|
+
* <label className="text-foreground font-paragraph">Password</label>
|
|
678
|
+
* </Form.Field.Label>
|
|
679
|
+
* <Form.Field.Input description={<span className="text-secondary-foreground">Min 8 characters</span>}>
|
|
680
|
+
* <input type="password" className="bg-background border-foreground text-foreground" />
|
|
681
|
+
* </Form.Field.Input>
|
|
682
|
+
* </Form.Field>
|
|
683
|
+
* ```
|
|
684
|
+
*/
|
|
685
|
+
exports.FieldInput = react_1.default.forwardRef((props, ref) => {
|
|
686
|
+
const { children, description, asChild, className, ...otherProps } = props;
|
|
687
|
+
const { gridStyles } = useFieldContext();
|
|
688
|
+
return ((0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { ref: ref, asChild: asChild, className: className, style: gridStyles.input, "data-testid": TestIds.fieldInput, customElement: children, customElementProps: {}, ...otherProps, children: (0, jsx_runtime_1.jsx)("div", { children: children }) }));
|
|
689
|
+
});
|
|
690
|
+
exports.FieldInput.displayName = 'Form.Field.Input';
|
|
691
|
+
exports.Field = FieldRoot;
|
|
692
|
+
exports.Field.Label = exports.FieldLabel;
|
|
693
|
+
exports.Field.Input = exports.FieldInput;
|