@wix/headless-forms 0.0.10 → 0.0.12
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 +235 -18
- package/cjs/dist/react/Form.js +251 -19
- package/cjs/dist/react/context/FieldContext.d.ts +12 -0
- package/cjs/dist/react/context/FieldContext.js +16 -0
- package/cjs/dist/react/context/FieldLayoutContext.d.ts +12 -0
- package/cjs/dist/react/context/FieldLayoutContext.js +21 -0
- 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 +235 -18
- package/dist/react/Form.js +252 -20
- package/dist/react/context/FieldContext.d.ts +12 -0
- package/dist/react/context/FieldContext.js +9 -0
- package/dist/react/context/FieldLayoutContext.d.ts +12 -0
- package/dist/react/context/FieldLayoutContext.js +13 -0
- 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/dist/react/Form.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React, { useState, useCallback } from 'react';
|
|
3
3
|
import { AsChildSlot } from '@wix/headless-utils/react';
|
|
4
|
-
import {
|
|
5
|
-
import { Root as CoreRoot, Loading as CoreLoading, LoadingError as CoreLoadingError, Error as CoreError, Submitted as CoreSubmitted, Fields as CoreFields, } from './core/Form.js';
|
|
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';
|
|
6
8
|
var TestIds;
|
|
7
9
|
(function (TestIds) {
|
|
8
10
|
TestIds["formRoot"] = "form-root";
|
|
@@ -11,6 +13,11 @@ var TestIds;
|
|
|
11
13
|
TestIds["formLoadingError"] = "form-loading-error";
|
|
12
14
|
TestIds["formError"] = "form-error";
|
|
13
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";
|
|
14
21
|
})(TestIds || (TestIds = {}));
|
|
15
22
|
/**
|
|
16
23
|
* Root component that provides all necessary service contexts for a complete form experience.
|
|
@@ -312,6 +319,8 @@ export const Submitted = React.forwardRef((props, ref) => {
|
|
|
312
319
|
* @component
|
|
313
320
|
* @param {FieldsProps} props - Component props
|
|
314
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
|
|
315
324
|
* @example
|
|
316
325
|
* ```tsx
|
|
317
326
|
* import { Form } from '@wix/headless-forms/react';
|
|
@@ -330,7 +339,11 @@ export const Submitted = React.forwardRef((props, ref) => {
|
|
|
330
339
|
* <Form.Root formServiceConfig={formServiceConfig}>
|
|
331
340
|
* <Form.Loading className="flex justify-center p-4" />
|
|
332
341
|
* <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
|
|
333
|
-
* <Form.Fields
|
|
342
|
+
* <Form.Fields
|
|
343
|
+
* fieldMap={FIELD_MAP}
|
|
344
|
+
* rowGapClassname="gap-y-4"
|
|
345
|
+
* columnGapClassname="gap-x-2"
|
|
346
|
+
* />
|
|
334
347
|
* </Form.Root>
|
|
335
348
|
* );
|
|
336
349
|
* }
|
|
@@ -345,12 +358,15 @@ export const Submitted = React.forwardRef((props, ref) => {
|
|
|
345
358
|
* - Field validation and error display
|
|
346
359
|
* - Form state management
|
|
347
360
|
* - Field value updates
|
|
361
|
+
* - Grid layout with configurable row and column gaps
|
|
348
362
|
*
|
|
349
363
|
* Must be used within Form.Root to access form context.
|
|
350
364
|
*
|
|
351
365
|
* @component
|
|
352
366
|
* @param {FieldsProps} props - The component props
|
|
353
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
|
|
354
370
|
*
|
|
355
371
|
* @example
|
|
356
372
|
* ```tsx
|
|
@@ -418,7 +434,11 @@ export const Submitted = React.forwardRef((props, ref) => {
|
|
|
418
434
|
* <Form.Root formServiceConfig={formServiceConfig}>
|
|
419
435
|
* <Form.Loading className="flex justify-center p-4" />
|
|
420
436
|
* <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
|
|
421
|
-
* <Form.Fields
|
|
437
|
+
* <Form.Fields
|
|
438
|
+
* fieldMap={FIELD_MAP}
|
|
439
|
+
* rowGapClassname="gap-y-4"
|
|
440
|
+
* columnGapClassname="gap-x-2"
|
|
441
|
+
* />
|
|
422
442
|
* <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
|
|
423
443
|
* <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
|
|
424
444
|
* </Form.Root>
|
|
@@ -428,23 +448,41 @@ export const Submitted = React.forwardRef((props, ref) => {
|
|
|
428
448
|
*
|
|
429
449
|
* @example
|
|
430
450
|
* ```tsx
|
|
431
|
-
* //
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
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
|
+
* };
|
|
444
482
|
*
|
|
445
483
|
* const FIELD_MAP = {
|
|
446
|
-
* TEXT_INPUT:
|
|
447
|
-
* // ... other field components
|
|
484
|
+
* TEXT_INPUT: TextInput,
|
|
485
|
+
* // ... all other field components must also use Form.Field
|
|
448
486
|
* };
|
|
449
487
|
* ```
|
|
450
488
|
*/
|
|
@@ -460,6 +498,200 @@ export const Fields = React.forwardRef((props, ref) => {
|
|
|
460
498
|
return (_jsx(CoreFields, { children: ({ form, submitForm }) => {
|
|
461
499
|
if (!form)
|
|
462
500
|
return null;
|
|
463
|
-
return (_jsx("div", { ref: ref, children: _jsx(
|
|
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 }) }));
|
|
464
572
|
} }));
|
|
465
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">Email Address</label>
|
|
588
|
+
* </Form.Field.Label>
|
|
589
|
+
* <Form.Field.InputWrapper>
|
|
590
|
+
* <Form.Field.Input>
|
|
591
|
+
* <input type="email" className="bg-background border-foreground text-foreground" />
|
|
592
|
+
* </Form.Field.Input>
|
|
593
|
+
* </Form.Field.InputWrapper>
|
|
594
|
+
* </Form.Field>
|
|
595
|
+
* ```
|
|
596
|
+
*/
|
|
597
|
+
export const FieldLabel = React.forwardRef((props, ref) => {
|
|
598
|
+
const { children, asChild, className, ...otherProps } = props;
|
|
599
|
+
const { gridStyles } = useFieldContext();
|
|
600
|
+
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 }) }));
|
|
601
|
+
});
|
|
602
|
+
FieldLabel.displayName = 'Form.Field.Label';
|
|
603
|
+
/**
|
|
604
|
+
* InputWrapper component that wraps input and error elements with grid positioning.
|
|
605
|
+
* Must be used within a Form.Field component.
|
|
606
|
+
* This wrapper applies the grid positioning styles to contain both the input and error.
|
|
607
|
+
*
|
|
608
|
+
* @component
|
|
609
|
+
* @example
|
|
610
|
+
* ```tsx
|
|
611
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
612
|
+
*
|
|
613
|
+
* <Form.Field id="email">
|
|
614
|
+
* <Form.Field.Label>
|
|
615
|
+
* <label className="text-foreground font-paragraph">Email Address</label>
|
|
616
|
+
* </Form.Field.Label>
|
|
617
|
+
* <Form.Field.InputWrapper>
|
|
618
|
+
* <Form.Field.Input>
|
|
619
|
+
* <input type="email" className="bg-background border-foreground text-foreground" />
|
|
620
|
+
* </Form.Field.Input>
|
|
621
|
+
* <Form.Field.Error>
|
|
622
|
+
* <span className="text-destructive text-sm font-paragraph">Please enter a valid email</span>
|
|
623
|
+
* </Form.Field.Error>
|
|
624
|
+
* </Form.Field.InputWrapper>
|
|
625
|
+
* </Form.Field>
|
|
626
|
+
* ```
|
|
627
|
+
*/
|
|
628
|
+
export const FieldInputWrapper = React.forwardRef((props, ref) => {
|
|
629
|
+
const { children, asChild, className, ...otherProps } = props;
|
|
630
|
+
const { gridStyles } = useFieldContext();
|
|
631
|
+
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 }) }));
|
|
632
|
+
});
|
|
633
|
+
FieldInputWrapper.displayName = 'Form.Field.InputWrapper';
|
|
634
|
+
/**
|
|
635
|
+
* Input component for a form field.
|
|
636
|
+
* Must be used within a Form.Field.InputWrapper component.
|
|
637
|
+
* Renders the actual input element without grid positioning.
|
|
638
|
+
*
|
|
639
|
+
* @component
|
|
640
|
+
* @example
|
|
641
|
+
* ```tsx
|
|
642
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
643
|
+
*
|
|
644
|
+
* <Form.Field id="password">
|
|
645
|
+
* <Form.Field.Label>
|
|
646
|
+
* <label className="text-foreground font-paragraph">Password</label>
|
|
647
|
+
* </Form.Field.Label>
|
|
648
|
+
* <Form.Field.InputWrapper>
|
|
649
|
+
* <Form.Field.Input description={<span className="text-secondary-foreground">Min 8 characters</span>}>
|
|
650
|
+
* <input type="password" className="bg-background border-foreground text-foreground" />
|
|
651
|
+
* </Form.Field.Input>
|
|
652
|
+
* </Form.Field.InputWrapper>
|
|
653
|
+
* </Form.Field>
|
|
654
|
+
* ```
|
|
655
|
+
*/
|
|
656
|
+
export const FieldInput = React.forwardRef((props, ref) => {
|
|
657
|
+
const { children, description, asChild, className, ...otherProps } = props;
|
|
658
|
+
return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.fieldInput, customElement: children, customElementProps: {}, ...otherProps, children: _jsx("div", { children: children }) }));
|
|
659
|
+
});
|
|
660
|
+
FieldInput.displayName = 'Form.Field.Input';
|
|
661
|
+
/**
|
|
662
|
+
* Error component for displaying field-level validation errors.
|
|
663
|
+
* Must be used within a Form.Field.InputWrapper component.
|
|
664
|
+
* Only renders when there is an error for the current field.
|
|
665
|
+
*
|
|
666
|
+
* @component
|
|
667
|
+
* @example
|
|
668
|
+
* ```tsx
|
|
669
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
670
|
+
*
|
|
671
|
+
* <Form.Field id="email">
|
|
672
|
+
* <Form.Field.Label>
|
|
673
|
+
* <label className="text-foreground font-paragraph">Email Address</label>
|
|
674
|
+
* </Form.Field.Label>
|
|
675
|
+
* <Form.Field.InputWrapper>
|
|
676
|
+
* <Form.Field.Input>
|
|
677
|
+
* <input type="email" className="bg-background border-foreground text-foreground" />
|
|
678
|
+
* </Form.Field.Input>
|
|
679
|
+
* <Form.Field.Error path="email">
|
|
680
|
+
* <span className="text-destructive text-sm font-paragraph">Please enter a valid email address</span>
|
|
681
|
+
* </Form.Field.Error>
|
|
682
|
+
* </Form.Field.InputWrapper>
|
|
683
|
+
* </Form.Field>
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
export const FieldError = React.forwardRef((props, ref) => {
|
|
687
|
+
const { errorMessage, asChild, className, children, ...otherProps } = props;
|
|
688
|
+
if (!errorMessage && !children)
|
|
689
|
+
return null;
|
|
690
|
+
return (_jsx(AsChildSlot, { "data-testid": TestIds.fieldError, ref: ref, asChild: asChild, className: className, ...otherProps, children: children || errorMessage }));
|
|
691
|
+
});
|
|
692
|
+
FieldError.displayName = 'Form.Field.Error';
|
|
693
|
+
export const Field = FieldRoot;
|
|
694
|
+
Field.Label = FieldLabel;
|
|
695
|
+
Field.InputWrapper = FieldInputWrapper;
|
|
696
|
+
Field.Input = FieldInput;
|
|
697
|
+
Field.Error = FieldError;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Layout } from '../core/Form.js';
|
|
3
|
+
export interface FieldContextValue {
|
|
4
|
+
id: string;
|
|
5
|
+
layout: Layout;
|
|
6
|
+
gridStyles: {
|
|
7
|
+
label: React.CSSProperties;
|
|
8
|
+
input: React.CSSProperties;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export declare const FieldContext: React.Context<FieldContextValue | null>;
|
|
12
|
+
export declare function useFieldContext(): FieldContextValue;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export const FieldContext = React.createContext(null);
|
|
3
|
+
export function useFieldContext() {
|
|
4
|
+
const context = React.useContext(FieldContext);
|
|
5
|
+
if (!context) {
|
|
6
|
+
throw new Error('Field components must be used within a Form.Field component');
|
|
7
|
+
}
|
|
8
|
+
return context;
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Layout } from '../core/Form.js';
|
|
3
|
+
export interface FieldLayoutMap {
|
|
4
|
+
[fieldId: string]: Layout;
|
|
5
|
+
}
|
|
6
|
+
export declare const FieldLayoutContext: React.Context<FieldLayoutMap | null>;
|
|
7
|
+
export interface FieldLayoutProviderProps {
|
|
8
|
+
value: FieldLayoutMap;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
export declare const FieldLayoutProvider: React.FC<FieldLayoutProviderProps>;
|
|
12
|
+
export declare function useFieldLayout(fieldId: string): Layout | null;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export const FieldLayoutContext = React.createContext(null);
|
|
4
|
+
export const FieldLayoutProvider = ({ value, children, }) => {
|
|
5
|
+
return (_jsx(FieldLayoutContext.Provider, { value: value, children: children }));
|
|
6
|
+
};
|
|
7
|
+
export function useFieldLayout(fieldId) {
|
|
8
|
+
const layoutMap = React.useContext(FieldLayoutContext);
|
|
9
|
+
if (!layoutMap) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return layoutMap[fieldId] || null;
|
|
13
|
+
}
|
|
@@ -135,14 +135,14 @@ export interface FormErrorRenderProps {
|
|
|
135
135
|
*/
|
|
136
136
|
export declare function LoadingError(props: FormErrorProps): import("react").ReactNode;
|
|
137
137
|
/**
|
|
138
|
-
* Props for Form Error headless component
|
|
138
|
+
* Props for Form Submit Error headless component
|
|
139
139
|
*/
|
|
140
140
|
export interface FormSubmitErrorProps {
|
|
141
141
|
/** Render prop function that receives submit error state data */
|
|
142
142
|
children: (props: FormSubmitErrorRenderProps) => React.ReactNode;
|
|
143
143
|
}
|
|
144
144
|
/**
|
|
145
|
-
* Render props for Form Error component
|
|
145
|
+
* Render props for Form Submit Error component
|
|
146
146
|
*/
|
|
147
147
|
export interface FormSubmitErrorRenderProps {
|
|
148
148
|
/** Submit error message */
|
|
@@ -222,14 +222,17 @@ export declare function Submitted(props: FormSubmittedProps): import("react").Re
|
|
|
222
222
|
/**
|
|
223
223
|
* Render props for Fields component
|
|
224
224
|
*/
|
|
225
|
-
interface FieldsRenderProps {
|
|
225
|
+
export interface FieldsRenderProps {
|
|
226
|
+
/** The form data, or null if not loaded */
|
|
226
227
|
form: forms.Form | null;
|
|
228
|
+
/** Function to submit the form with values */
|
|
227
229
|
submitForm: (formValues: FormValues) => Promise<void>;
|
|
228
230
|
}
|
|
229
231
|
/**
|
|
230
232
|
* Props for Fields headless component
|
|
231
233
|
*/
|
|
232
|
-
interface FieldsProps {
|
|
234
|
+
export interface FieldsProps {
|
|
235
|
+
/** Render prop function that receives form data and submit handler */
|
|
233
236
|
children: (props: FieldsRenderProps) => React.ReactNode;
|
|
234
237
|
}
|
|
235
238
|
/**
|
|
@@ -264,4 +267,76 @@ interface FieldsProps {
|
|
|
264
267
|
* ```
|
|
265
268
|
*/
|
|
266
269
|
export declare function Fields(props: FieldsProps): import("react").ReactNode;
|
|
267
|
-
|
|
270
|
+
/**
|
|
271
|
+
* Form view interface containing field definitions
|
|
272
|
+
*/
|
|
273
|
+
export interface FormView {
|
|
274
|
+
fields: FieldDefinition[];
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Field layout configuration
|
|
278
|
+
*/
|
|
279
|
+
export interface Layout {
|
|
280
|
+
column: number;
|
|
281
|
+
row: number;
|
|
282
|
+
height: number;
|
|
283
|
+
width: number;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Field definition including layout information
|
|
287
|
+
*/
|
|
288
|
+
export interface FieldDefinition {
|
|
289
|
+
id: string;
|
|
290
|
+
layout: Layout;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Render props for Field component
|
|
294
|
+
*/
|
|
295
|
+
export interface FieldRenderProps {
|
|
296
|
+
/** The field ID */
|
|
297
|
+
id: string;
|
|
298
|
+
/** The field layout configuration */
|
|
299
|
+
layout: Layout;
|
|
300
|
+
/** Grid styles for container */
|
|
301
|
+
gridStyles: {
|
|
302
|
+
label: React.CSSProperties;
|
|
303
|
+
input: React.CSSProperties;
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Props for Field headless component
|
|
308
|
+
*/
|
|
309
|
+
export interface FieldProps {
|
|
310
|
+
/** The unique identifier for this field */
|
|
311
|
+
id: string;
|
|
312
|
+
/** The field layout configuration */
|
|
313
|
+
layout: Layout;
|
|
314
|
+
/** Render prop function that receives field layout data */
|
|
315
|
+
children: (props: FieldRenderProps) => React.ReactNode;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Headless Field component that provides field layout data and grid styles.
|
|
319
|
+
* This component accesses field configuration and calculates grid positioning.
|
|
320
|
+
*
|
|
321
|
+
* @component
|
|
322
|
+
* @param {FieldProps} props - Component props
|
|
323
|
+
* @param {FieldProps['children']} props.children - Render prop function that receives field layout data
|
|
324
|
+
* @example
|
|
325
|
+
* ```tsx
|
|
326
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
327
|
+
*
|
|
328
|
+
* function CustomField({ id, layout }) {
|
|
329
|
+
* return (
|
|
330
|
+
* <Form.Field id={id} layout={layout}>
|
|
331
|
+
* {({ id, layout, gridStyles }) => (
|
|
332
|
+
* <div data-field-id={id}>
|
|
333
|
+
* <div style={gridStyles.label}>Label</div>
|
|
334
|
+
* <div style={gridStyles.input}>Input</div>
|
|
335
|
+
* </div>
|
|
336
|
+
* )}
|
|
337
|
+
* </Form.Field>
|
|
338
|
+
* );
|
|
339
|
+
* }
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
export declare function Field(props: FieldProps): import("react").ReactNode;
|
package/dist/react/core/Form.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useService, WixServices } from '@wix/services-manager-react';
|
|
3
3
|
import { createServicesMap } from '@wix/services-manager';
|
|
4
4
|
import { FormServiceDefinition, FormService, } from '../../services/form-service.js';
|
|
5
|
+
import { calculateGridStyles } from '../utils.js';
|
|
5
6
|
const DEFAULT_SUCCESS_MESSAGE = 'Your form has been submitted successfully.';
|
|
6
7
|
/**
|
|
7
8
|
* Root component that provides the Form service context to its children.
|
|
@@ -227,3 +228,42 @@ export function Fields(props) {
|
|
|
227
228
|
submitForm,
|
|
228
229
|
});
|
|
229
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Headless Field component that provides field layout data and grid styles.
|
|
233
|
+
* This component accesses field configuration and calculates grid positioning.
|
|
234
|
+
*
|
|
235
|
+
* @component
|
|
236
|
+
* @param {FieldProps} props - Component props
|
|
237
|
+
* @param {FieldProps['children']} props.children - Render prop function that receives field layout data
|
|
238
|
+
* @example
|
|
239
|
+
* ```tsx
|
|
240
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
241
|
+
*
|
|
242
|
+
* function CustomField({ id, layout }) {
|
|
243
|
+
* return (
|
|
244
|
+
* <Form.Field id={id} layout={layout}>
|
|
245
|
+
* {({ id, layout, gridStyles }) => (
|
|
246
|
+
* <div data-field-id={id}>
|
|
247
|
+
* <div style={gridStyles.label}>Label</div>
|
|
248
|
+
* <div style={gridStyles.input}>Input</div>
|
|
249
|
+
* </div>
|
|
250
|
+
* )}
|
|
251
|
+
* </Form.Field>
|
|
252
|
+
* );
|
|
253
|
+
* }
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
export function Field(props) {
|
|
257
|
+
const { id, children, layout } = props;
|
|
258
|
+
const { formSignal } = useService(FormServiceDefinition);
|
|
259
|
+
const form = formSignal.get();
|
|
260
|
+
if (!form) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
const gridStyles = calculateGridStyles(layout);
|
|
264
|
+
return children({
|
|
265
|
+
id,
|
|
266
|
+
layout,
|
|
267
|
+
gridStyles,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Layout } from './core/Form';
|
|
2
|
+
export declare function calculateGridStyles(layout: Layout): {
|
|
3
|
+
label: {
|
|
4
|
+
gridRow: string;
|
|
5
|
+
gridColumn: string;
|
|
6
|
+
display: string;
|
|
7
|
+
alignItems: string;
|
|
8
|
+
};
|
|
9
|
+
input: {
|
|
10
|
+
gridRow: string;
|
|
11
|
+
gridColumn: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function calculateGridStyles(layout) {
|
|
2
|
+
const labelRow = 1;
|
|
3
|
+
const inputRow = 2;
|
|
4
|
+
const gridColumn = `${layout.column + 1} / span ${layout.width}`;
|
|
5
|
+
return {
|
|
6
|
+
label: {
|
|
7
|
+
gridRow: `${labelRow} / span 1`,
|
|
8
|
+
gridColumn,
|
|
9
|
+
display: 'flex',
|
|
10
|
+
alignItems: 'flex-end',
|
|
11
|
+
},
|
|
12
|
+
input: {
|
|
13
|
+
gridRow: `${inputRow} / span 1`,
|
|
14
|
+
gridColumn,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -67,7 +67,10 @@ export const FormService = implementService.withConfig()(FormServiceDefinition,
|
|
|
67
67
|
}
|
|
68
68
|
async function defaultSubmitHandler(formId, formValues) {
|
|
69
69
|
try {
|
|
70
|
-
await submissions.createSubmission({
|
|
70
|
+
await submissions.createSubmission({
|
|
71
|
+
formId,
|
|
72
|
+
submissions: formValues,
|
|
73
|
+
});
|
|
71
74
|
// TODO: add message
|
|
72
75
|
return { type: 'success' };
|
|
73
76
|
}
|
|
@@ -88,7 +91,7 @@ export const FormService = implementService.withConfig()(FormServiceDefinition,
|
|
|
88
91
|
}
|
|
89
92
|
// @ts-expect-error
|
|
90
93
|
const formId = form._id ? form._id : form.id;
|
|
91
|
-
submitResponseSignal.set({ type: '
|
|
94
|
+
submitResponseSignal.set({ type: 'loading' });
|
|
92
95
|
try {
|
|
93
96
|
const handler = config.onSubmit || defaultSubmitHandler;
|
|
94
97
|
const response = await handler(formId, formValues);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-forms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "npm run build:esm && npm run build:cjs",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"vitest": "^3.1.4"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@wix/form-public": "^0.
|
|
45
|
-
"@wix/forms": "^1.0.
|
|
44
|
+
"@wix/form-public": "^0.53.0",
|
|
45
|
+
"@wix/forms": "^1.0.331",
|
|
46
46
|
"@wix/headless-utils": "0.0.4",
|
|
47
47
|
"@wix/services-definitions": "^0.1.4",
|
|
48
48
|
"@wix/services-manager-react": "^0.1.26"
|