@wix/headless-forms 0.0.12 → 0.0.14
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 +35 -18
- package/cjs/dist/react/Form.js +47 -4
- package/cjs/dist/react/Phone.d.ts +47 -0
- package/cjs/dist/react/Phone.js +56 -0
- package/cjs/dist/react/index.d.ts +1 -0
- package/cjs/dist/react/index.js +1 -0
- package/cjs/dist/services/form-service.d.ts +5 -1
- package/cjs/dist/services/form-service.js +41 -10
- package/dist/react/Form.d.ts +35 -18
- package/dist/react/Form.js +46 -3
- package/dist/react/Phone.d.ts +47 -0
- package/dist/react/Phone.js +50 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/services/form-service.d.ts +5 -1
- package/dist/services/form-service.js +42 -11
- package/package.json +16 -4
package/cjs/dist/react/Form.d.ts
CHANGED
|
@@ -637,9 +637,6 @@ export interface FieldLabelProps {
|
|
|
637
637
|
/** CSS classes to apply to the label element */
|
|
638
638
|
className?: string;
|
|
639
639
|
}
|
|
640
|
-
/**
|
|
641
|
-
* Props for Field.InputWrapper component
|
|
642
|
-
*/
|
|
643
640
|
export interface FieldInputWrapperProps {
|
|
644
641
|
/** Child components (typically Field.Input and Field.Error) */
|
|
645
642
|
children: React.ReactNode;
|
|
@@ -684,28 +681,48 @@ export interface FieldErrorProps {
|
|
|
684
681
|
children?: React.ReactNode;
|
|
685
682
|
}
|
|
686
683
|
/**
|
|
687
|
-
*
|
|
688
|
-
|
|
689
|
-
|
|
684
|
+
* Props for Field.Label.Required component
|
|
685
|
+
*/
|
|
686
|
+
export interface FieldLabelRequiredProps {
|
|
687
|
+
/** Whether to show the required indicator */
|
|
688
|
+
required?: boolean;
|
|
689
|
+
/** Custom content to display (defaults to red asterisk) */
|
|
690
|
+
children?: React.ReactNode;
|
|
691
|
+
/** Whether to render as a child component */
|
|
692
|
+
asChild?: boolean;
|
|
693
|
+
/** CSS classes to apply to the required indicator */
|
|
694
|
+
className?: string;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Required indicator component for form field labels.
|
|
698
|
+
* Must be used within a Form.Field.Label component.
|
|
690
699
|
*
|
|
691
700
|
* @component
|
|
692
701
|
* @example
|
|
693
702
|
* ```tsx
|
|
694
703
|
* import { Form } from '@wix/headless-forms/react';
|
|
695
704
|
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
705
|
+
* // Basic usage with required prop
|
|
706
|
+
* <Form.Field.Label>
|
|
707
|
+
* <label className="text-foreground font-paragraph">
|
|
708
|
+
* Email Address
|
|
709
|
+
* <Form.Field.Label.Required />
|
|
710
|
+
* </label>
|
|
711
|
+
* </Form.Field.Label>
|
|
712
|
+
*
|
|
713
|
+
* // Custom styling
|
|
714
|
+
* <Form.Field.Label>
|
|
715
|
+
* <label className="text-foreground font-paragraph">
|
|
716
|
+
* Username
|
|
717
|
+
* <Form.Field.Label.Required required={true} className="text-destructive ml-2" />
|
|
718
|
+
* </label>
|
|
719
|
+
* </Form.Field.Label>
|
|
707
720
|
*/
|
|
708
|
-
export declare const
|
|
721
|
+
export declare const FieldLabelRequired: React.ForwardRefExoticComponent<FieldLabelRequiredProps & React.RefAttributes<HTMLSpanElement>>;
|
|
722
|
+
interface FieldLabelComponent extends React.ForwardRefExoticComponent<FieldLabelProps & React.RefAttributes<HTMLDivElement>> {
|
|
723
|
+
Required: typeof FieldLabelRequired;
|
|
724
|
+
}
|
|
725
|
+
export declare const FieldLabel: FieldLabelComponent;
|
|
709
726
|
/**
|
|
710
727
|
* InputWrapper component that wraps input and error elements with grid positioning.
|
|
711
728
|
* Must be used within a Form.Field component.
|
package/cjs/dist/react/Form.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Field = exports.FieldError = exports.FieldInput = exports.FieldInputWrapper = exports.FieldLabel = exports.Fields = exports.Submitted = exports.Error = exports.LoadingError = exports.Loading = exports.Root = void 0;
|
|
36
|
+
exports.Field = exports.FieldError = exports.FieldInput = exports.FieldInputWrapper = exports.FieldLabel = exports.FieldLabelRequired = 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");
|
|
@@ -620,7 +620,10 @@ FieldRoot.displayName = 'Form.Field';
|
|
|
620
620
|
*
|
|
621
621
|
* <Form.Field id="email">
|
|
622
622
|
* <Form.Field.Label>
|
|
623
|
-
* <label className="text-foreground font-paragraph">
|
|
623
|
+
* <label className="text-foreground font-paragraph">
|
|
624
|
+
* Email Address
|
|
625
|
+
* <Form.Field.Label.Required required={true} />
|
|
626
|
+
* </label>
|
|
624
627
|
* </Form.Field.Label>
|
|
625
628
|
* <Form.Field.InputWrapper>
|
|
626
629
|
* <Form.Field.Input>
|
|
@@ -630,12 +633,52 @@ FieldRoot.displayName = 'Form.Field';
|
|
|
630
633
|
* </Form.Field>
|
|
631
634
|
* ```
|
|
632
635
|
*/
|
|
633
|
-
|
|
636
|
+
const FieldLabelRoot = react_1.default.forwardRef((props, ref) => {
|
|
634
637
|
const { children, asChild, className, ...otherProps } = props;
|
|
635
638
|
const { gridStyles } = (0, FieldContext_js_1.useFieldContext)();
|
|
636
639
|
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 }) }));
|
|
637
640
|
});
|
|
638
|
-
|
|
641
|
+
FieldLabelRoot.displayName = 'Form.Field.Label';
|
|
642
|
+
/**
|
|
643
|
+
* Required indicator component for form field labels.
|
|
644
|
+
* Must be used within a Form.Field.Label component.
|
|
645
|
+
*
|
|
646
|
+
* @component
|
|
647
|
+
* @example
|
|
648
|
+
* ```tsx
|
|
649
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
650
|
+
*
|
|
651
|
+
* // Basic usage with required prop
|
|
652
|
+
* <Form.Field.Label>
|
|
653
|
+
* <label className="text-foreground font-paragraph">
|
|
654
|
+
* Email Address
|
|
655
|
+
* <Form.Field.Label.Required />
|
|
656
|
+
* </label>
|
|
657
|
+
* </Form.Field.Label>
|
|
658
|
+
*
|
|
659
|
+
* // Custom styling
|
|
660
|
+
* <Form.Field.Label>
|
|
661
|
+
* <label className="text-foreground font-paragraph">
|
|
662
|
+
* Username
|
|
663
|
+
* <Form.Field.Label.Required required={true} className="text-destructive ml-2" />
|
|
664
|
+
* </label>
|
|
665
|
+
* </Form.Field.Label>
|
|
666
|
+
*/
|
|
667
|
+
exports.FieldLabelRequired = react_1.default.forwardRef((props, ref) => {
|
|
668
|
+
const { required = false, children, asChild, className, ...otherProps } = props;
|
|
669
|
+
const requiredIndicator = 'asterisk';
|
|
670
|
+
// @ts-expect-error
|
|
671
|
+
if (!required || requiredIndicator === 'none')
|
|
672
|
+
return null;
|
|
673
|
+
return ((0, jsx_runtime_1.jsx)(react_2.AsChildSlot, { ref: ref, asChild: asChild, className: className, customElement: children, ...otherProps, children: (0, jsx_runtime_1.jsx)("span", { children: requiredIndicator === 'asterisk'
|
|
674
|
+
? '*'
|
|
675
|
+
: requiredIndicator === 'text'
|
|
676
|
+
? '(Required)'
|
|
677
|
+
: null }) }));
|
|
678
|
+
});
|
|
679
|
+
exports.FieldLabelRequired.displayName = 'Form.Field.Label.Required';
|
|
680
|
+
exports.FieldLabel = FieldLabelRoot;
|
|
681
|
+
exports.FieldLabel.Required = exports.FieldLabelRequired;
|
|
639
682
|
/**
|
|
640
683
|
* InputWrapper component that wraps input and error elements with grid positioning.
|
|
641
684
|
* Must be used within a Form.Field component.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface PhoneFieldProps {
|
|
3
|
+
id: string;
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
asChild?: boolean;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PhoneLabelProps {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
asChild?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PhoneLabelRequiredProps {
|
|
14
|
+
required?: boolean;
|
|
15
|
+
children?: React.ReactNode;
|
|
16
|
+
asChild?: boolean;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const Required: React.ForwardRefExoticComponent<PhoneLabelRequiredProps & React.RefAttributes<HTMLSpanElement>>;
|
|
20
|
+
interface PhoneLabelComponent extends React.ForwardRefExoticComponent<PhoneLabelProps & React.RefAttributes<HTMLDivElement>> {
|
|
21
|
+
Required: typeof Required;
|
|
22
|
+
}
|
|
23
|
+
export declare const Label: PhoneLabelComponent;
|
|
24
|
+
export interface PhoneErrorProps {
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
asChild?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
declare const Error: React.ForwardRefExoticComponent<PhoneErrorProps & React.RefAttributes<HTMLDivElement>>;
|
|
30
|
+
export interface CountryCodeProps {
|
|
31
|
+
asChild?: boolean;
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
declare const CountryCode: React.ForwardRefExoticComponent<CountryCodeProps & React.RefAttributes<HTMLDivElement>>;
|
|
35
|
+
export interface PhoneFieldInputProps {
|
|
36
|
+
asChild?: boolean;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
declare const Input: React.ForwardRefExoticComponent<PhoneFieldInputProps & React.RefAttributes<HTMLDivElement>>;
|
|
40
|
+
interface PhoneFieldComponent extends React.ForwardRefExoticComponent<PhoneFieldProps & React.RefAttributes<HTMLDivElement>> {
|
|
41
|
+
Label: typeof Label;
|
|
42
|
+
Error: typeof Error;
|
|
43
|
+
Input: typeof Input;
|
|
44
|
+
CountryCode: typeof CountryCode;
|
|
45
|
+
}
|
|
46
|
+
export declare const PhoneField: PhoneFieldComponent;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PhoneField = exports.Label = exports.Required = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const react_1 = __importDefault(require("react"));
|
|
9
|
+
const react_2 = require("@wix/headless-forms/react");
|
|
10
|
+
const form_public_1 = require("@wix/form-public");
|
|
11
|
+
const Root = react_1.default.forwardRef((props, ref) => {
|
|
12
|
+
const { id, children, asChild, className } = props;
|
|
13
|
+
const { description } = (0, form_public_1.useFieldProps)();
|
|
14
|
+
return ((0, jsx_runtime_1.jsx)(react_2.Form.Field, { ref: ref, id: id, asChild: asChild, className: className, children: (0, jsx_runtime_1.jsx)(react_2.Form.Field.InputWrapper, { children: (0, jsx_runtime_1.jsx)(react_2.Form.Field.Input, { description: description, children: children }) }) }));
|
|
15
|
+
});
|
|
16
|
+
Root.displayName = 'PhoneField';
|
|
17
|
+
const LabelRoot = react_1.default.forwardRef((props, ref) => {
|
|
18
|
+
const { asChild, className, children } = props;
|
|
19
|
+
const { id, label, showLabel } = (0, form_public_1.useFieldProps)();
|
|
20
|
+
if (!showLabel) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return ((0, jsx_runtime_1.jsx)(react_2.Form.Field.Label, { ref: ref, asChild: asChild, className: className, children: (0, jsx_runtime_1.jsxs)("label", { htmlFor: id, children: [label, children] }) }));
|
|
24
|
+
});
|
|
25
|
+
LabelRoot.displayName = 'PhoneField.Label';
|
|
26
|
+
exports.Required = react_1.default.forwardRef((props, ref) => {
|
|
27
|
+
const { required } = (0, form_public_1.useFieldProps)();
|
|
28
|
+
return (0, jsx_runtime_1.jsx)(react_2.Form.Field.Label.Required, { ...props, ref: ref, required: required });
|
|
29
|
+
});
|
|
30
|
+
exports.Required.displayName = 'PhoneField.Label.Required';
|
|
31
|
+
exports.Label = LabelRoot;
|
|
32
|
+
exports.Label.Required = exports.Required;
|
|
33
|
+
const Error = react_1.default.forwardRef((props, ref) => {
|
|
34
|
+
const { children, ...rest } = props;
|
|
35
|
+
const { errorMessage } = (0, form_public_1.useFieldProps)();
|
|
36
|
+
return ((0, jsx_runtime_1.jsx)(react_2.Form.Field.Error, { ref: ref, ...rest, children: errorMessage }));
|
|
37
|
+
});
|
|
38
|
+
Error.displayName = 'PhoneField.Error';
|
|
39
|
+
const CountryCode = react_1.default.forwardRef((props, ref) => {
|
|
40
|
+
const { asChild, className, ...rest } = props;
|
|
41
|
+
const { id, countryCodes, defaultCountryCode, readOnly, onChange, onBlur, onFocus, } = (0, form_public_1.useFieldProps)();
|
|
42
|
+
return ((0, jsx_runtime_1.jsx)(react_2.Form.Field.Input, { ref: ref, asChild: asChild, className: className, ...rest, children: (0, jsx_runtime_1.jsx)("select", { id: `${id}-country`, defaultValue: defaultCountryCode || '', disabled: readOnly, onChange: (e) => onChange?.(e.target.value), onBlur: () => onBlur?.(), onFocus: () => onFocus?.(), children: countryCodes?.map((code) => ((0, jsx_runtime_1.jsx)("option", { value: code, children: code }, code))) }) }));
|
|
43
|
+
});
|
|
44
|
+
CountryCode.displayName = 'PhoneField.CountryCode';
|
|
45
|
+
const Input = react_1.default.forwardRef((props, ref) => {
|
|
46
|
+
const { asChild, className, ...rest } = props;
|
|
47
|
+
const { id, value, required, readOnly, placeholder, onChange, onBlur, onFocus, description, } = (0, form_public_1.useFieldProps)();
|
|
48
|
+
const descriptionId = description ? `${id}-description` : undefined;
|
|
49
|
+
return ((0, jsx_runtime_1.jsx)(react_2.Form.Field.Input, { ref: ref, asChild: asChild, className: className, ...rest, children: (0, jsx_runtime_1.jsx)("input", { id: id, type: "tel", value: value || '', required: required, readOnly: readOnly, placeholder: placeholder, "aria-describedby": descriptionId, "aria-invalid": !!(required && !value), "aria-required": required, onChange: (e) => onChange?.(e.target.value), onBlur: () => onBlur?.(), onFocus: () => onFocus?.() }) }));
|
|
50
|
+
});
|
|
51
|
+
Input.displayName = 'PhoneField.Input';
|
|
52
|
+
exports.PhoneField = Root;
|
|
53
|
+
exports.PhoneField.Label = exports.Label;
|
|
54
|
+
exports.PhoneField.Error = Error;
|
|
55
|
+
exports.PhoneField.Input = Input;
|
|
56
|
+
exports.PhoneField.CountryCode = CountryCode;
|
package/cjs/dist/react/index.js
CHANGED
|
@@ -60,6 +60,8 @@ type OnSubmit = (formId: string, formValues: FormValues) => Promise<SubmitRespon
|
|
|
60
60
|
export type FormServiceConfig = {
|
|
61
61
|
formId: string;
|
|
62
62
|
onSubmit?: OnSubmit;
|
|
63
|
+
namespace?: string;
|
|
64
|
+
additionalMetadata?: Record<string, string | string[]>;
|
|
63
65
|
} | {
|
|
64
66
|
form: forms.Form;
|
|
65
67
|
onSubmit?: OnSubmit;
|
|
@@ -100,6 +102,8 @@ export declare const FormService: import("@wix/services-definitions").ServiceFac
|
|
|
100
102
|
* a specific form by ID that will be used to configure the FormService.
|
|
101
103
|
*
|
|
102
104
|
* @param {string} formId - The unique identifier of the form to load
|
|
105
|
+
* @param {string} [namespace] - Optional namespace for the form
|
|
106
|
+
* @param {Record<string, string | string[]>} [additionalMetadata] - Optional additional metadata to pass to the API
|
|
103
107
|
* @returns {Promise<FormServiceConfig>} Configuration object with pre-loaded form data
|
|
104
108
|
* @throws {Error} When the form cannot be loaded
|
|
105
109
|
*
|
|
@@ -110,5 +114,5 @@ export declare const FormService: import("@wix/services-definitions").ServiceFac
|
|
|
110
114
|
* const formService = FormService.withConfig(formConfig);
|
|
111
115
|
* ```
|
|
112
116
|
*/
|
|
113
|
-
export declare function loadFormServiceConfig(formId: string): Promise<FormServiceConfig>;
|
|
117
|
+
export declare function loadFormServiceConfig(formId: string, namespace?: string, additionalMetadata?: Record<string, string | string[]>): Promise<FormServiceConfig>;
|
|
114
118
|
export {};
|
|
@@ -5,6 +5,7 @@ exports.loadFormServiceConfig = loadFormServiceConfig;
|
|
|
5
5
|
const forms_1 = require("@wix/forms");
|
|
6
6
|
const services_definitions_1 = require("@wix/services-definitions");
|
|
7
7
|
const signals_1 = require("@wix/services-definitions/core-services/signals");
|
|
8
|
+
const essentials_1 = require("@wix/essentials");
|
|
8
9
|
/**
|
|
9
10
|
* Service definition for the Form service.
|
|
10
11
|
* This defines the contract that the FormService must implement.
|
|
@@ -47,13 +48,13 @@ exports.FormService = services_definitions_1.implementService.withConfig()(expor
|
|
|
47
48
|
const hasSchema = 'form' in config;
|
|
48
49
|
const formSignal = signalsService.signal(hasSchema ? config.form : null);
|
|
49
50
|
if (!hasSchema) {
|
|
50
|
-
loadForm(config.formId);
|
|
51
|
+
loadForm(config.formId, config.namespace, config.additionalMetadata);
|
|
51
52
|
}
|
|
52
|
-
async function loadForm(id) {
|
|
53
|
+
async function loadForm(id, namespace, additionalMetadata) {
|
|
53
54
|
isLoadingSignal.set(true);
|
|
54
55
|
errorSignal.set(null);
|
|
55
56
|
try {
|
|
56
|
-
const result = await fetchForm(id);
|
|
57
|
+
const result = await fetchForm({ id, namespace, additionalMetadata });
|
|
57
58
|
if (result) {
|
|
58
59
|
formSignal.set(result);
|
|
59
60
|
}
|
|
@@ -117,13 +118,41 @@ exports.FormService = services_definitions_1.implementService.withConfig()(expor
|
|
|
117
118
|
submitForm: submitForm,
|
|
118
119
|
};
|
|
119
120
|
});
|
|
120
|
-
|
|
121
|
+
function buildFormFetchQueryParams({ id, namespace, additionalMetadata, }) {
|
|
122
|
+
const params = new URLSearchParams();
|
|
123
|
+
params.append('formId', id);
|
|
124
|
+
if (namespace) {
|
|
125
|
+
params.append('namespace', namespace);
|
|
126
|
+
}
|
|
127
|
+
if (additionalMetadata) {
|
|
128
|
+
Object.entries(additionalMetadata).forEach(([key, value]) => {
|
|
129
|
+
if (Array.isArray(value)) {
|
|
130
|
+
value.forEach((v) => params.append(key, v));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
params.append(key, value);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return params;
|
|
138
|
+
}
|
|
139
|
+
async function fetchForm({ id, namespace, additionalMetadata, }) {
|
|
121
140
|
try {
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
141
|
+
const params = buildFormFetchQueryParams({
|
|
142
|
+
id,
|
|
143
|
+
namespace,
|
|
144
|
+
additionalMetadata,
|
|
145
|
+
});
|
|
146
|
+
const url = `https://edge.wixapis.com/form-schema-service/v4/forms/${id}?${params.toString()}`;
|
|
147
|
+
const response = await essentials_1.httpClient.fetchWithAuth(url);
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw new Error(`Failed to fetch form: ${response.status} ${response.statusText}`);
|
|
150
|
+
}
|
|
151
|
+
const data = await response.json();
|
|
152
|
+
if (data && data.form) {
|
|
153
|
+
return data.form;
|
|
125
154
|
}
|
|
126
|
-
|
|
155
|
+
throw new Error('Invalid response format from Forms API');
|
|
127
156
|
}
|
|
128
157
|
catch (err) {
|
|
129
158
|
console.error('Failed to load form:', id, err);
|
|
@@ -136,6 +165,8 @@ async function fetchForm(id) {
|
|
|
136
165
|
* a specific form by ID that will be used to configure the FormService.
|
|
137
166
|
*
|
|
138
167
|
* @param {string} formId - The unique identifier of the form to load
|
|
168
|
+
* @param {string} [namespace] - Optional namespace for the form
|
|
169
|
+
* @param {Record<string, string | string[]>} [additionalMetadata] - Optional additional metadata to pass to the API
|
|
139
170
|
* @returns {Promise<FormServiceConfig>} Configuration object with pre-loaded form data
|
|
140
171
|
* @throws {Error} When the form cannot be loaded
|
|
141
172
|
*
|
|
@@ -146,7 +177,7 @@ async function fetchForm(id) {
|
|
|
146
177
|
* const formService = FormService.withConfig(formConfig);
|
|
147
178
|
* ```
|
|
148
179
|
*/
|
|
149
|
-
async function loadFormServiceConfig(formId) {
|
|
150
|
-
const form = await fetchForm(formId);
|
|
180
|
+
async function loadFormServiceConfig(formId, namespace, additionalMetadata) {
|
|
181
|
+
const form = await fetchForm({ id: formId, namespace, additionalMetadata });
|
|
151
182
|
return { form };
|
|
152
183
|
}
|
package/dist/react/Form.d.ts
CHANGED
|
@@ -637,9 +637,6 @@ export interface FieldLabelProps {
|
|
|
637
637
|
/** CSS classes to apply to the label element */
|
|
638
638
|
className?: string;
|
|
639
639
|
}
|
|
640
|
-
/**
|
|
641
|
-
* Props for Field.InputWrapper component
|
|
642
|
-
*/
|
|
643
640
|
export interface FieldInputWrapperProps {
|
|
644
641
|
/** Child components (typically Field.Input and Field.Error) */
|
|
645
642
|
children: React.ReactNode;
|
|
@@ -684,28 +681,48 @@ export interface FieldErrorProps {
|
|
|
684
681
|
children?: React.ReactNode;
|
|
685
682
|
}
|
|
686
683
|
/**
|
|
687
|
-
*
|
|
688
|
-
|
|
689
|
-
|
|
684
|
+
* Props for Field.Label.Required component
|
|
685
|
+
*/
|
|
686
|
+
export interface FieldLabelRequiredProps {
|
|
687
|
+
/** Whether to show the required indicator */
|
|
688
|
+
required?: boolean;
|
|
689
|
+
/** Custom content to display (defaults to red asterisk) */
|
|
690
|
+
children?: React.ReactNode;
|
|
691
|
+
/** Whether to render as a child component */
|
|
692
|
+
asChild?: boolean;
|
|
693
|
+
/** CSS classes to apply to the required indicator */
|
|
694
|
+
className?: string;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Required indicator component for form field labels.
|
|
698
|
+
* Must be used within a Form.Field.Label component.
|
|
690
699
|
*
|
|
691
700
|
* @component
|
|
692
701
|
* @example
|
|
693
702
|
* ```tsx
|
|
694
703
|
* import { Form } from '@wix/headless-forms/react';
|
|
695
704
|
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
705
|
+
* // Basic usage with required prop
|
|
706
|
+
* <Form.Field.Label>
|
|
707
|
+
* <label className="text-foreground font-paragraph">
|
|
708
|
+
* Email Address
|
|
709
|
+
* <Form.Field.Label.Required />
|
|
710
|
+
* </label>
|
|
711
|
+
* </Form.Field.Label>
|
|
712
|
+
*
|
|
713
|
+
* // Custom styling
|
|
714
|
+
* <Form.Field.Label>
|
|
715
|
+
* <label className="text-foreground font-paragraph">
|
|
716
|
+
* Username
|
|
717
|
+
* <Form.Field.Label.Required required={true} className="text-destructive ml-2" />
|
|
718
|
+
* </label>
|
|
719
|
+
* </Form.Field.Label>
|
|
707
720
|
*/
|
|
708
|
-
export declare const
|
|
721
|
+
export declare const FieldLabelRequired: React.ForwardRefExoticComponent<FieldLabelRequiredProps & React.RefAttributes<HTMLSpanElement>>;
|
|
722
|
+
interface FieldLabelComponent extends React.ForwardRefExoticComponent<FieldLabelProps & React.RefAttributes<HTMLDivElement>> {
|
|
723
|
+
Required: typeof FieldLabelRequired;
|
|
724
|
+
}
|
|
725
|
+
export declare const FieldLabel: FieldLabelComponent;
|
|
709
726
|
/**
|
|
710
727
|
* InputWrapper component that wraps input and error elements with grid positioning.
|
|
711
728
|
* Must be used within a Form.Field component.
|
package/dist/react/Form.js
CHANGED
|
@@ -584,7 +584,10 @@ FieldRoot.displayName = 'Form.Field';
|
|
|
584
584
|
*
|
|
585
585
|
* <Form.Field id="email">
|
|
586
586
|
* <Form.Field.Label>
|
|
587
|
-
* <label className="text-foreground font-paragraph">
|
|
587
|
+
* <label className="text-foreground font-paragraph">
|
|
588
|
+
* Email Address
|
|
589
|
+
* <Form.Field.Label.Required required={true} />
|
|
590
|
+
* </label>
|
|
588
591
|
* </Form.Field.Label>
|
|
589
592
|
* <Form.Field.InputWrapper>
|
|
590
593
|
* <Form.Field.Input>
|
|
@@ -594,12 +597,52 @@ FieldRoot.displayName = 'Form.Field';
|
|
|
594
597
|
* </Form.Field>
|
|
595
598
|
* ```
|
|
596
599
|
*/
|
|
597
|
-
|
|
600
|
+
const FieldLabelRoot = React.forwardRef((props, ref) => {
|
|
598
601
|
const { children, asChild, className, ...otherProps } = props;
|
|
599
602
|
const { gridStyles } = useFieldContext();
|
|
600
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 }) }));
|
|
601
604
|
});
|
|
602
|
-
|
|
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;
|
|
603
646
|
/**
|
|
604
647
|
* InputWrapper component that wraps input and error elements with grid positioning.
|
|
605
648
|
* Must be used within a Form.Field component.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface PhoneFieldProps {
|
|
3
|
+
id: string;
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
asChild?: boolean;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PhoneLabelProps {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
asChild?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface PhoneLabelRequiredProps {
|
|
14
|
+
required?: boolean;
|
|
15
|
+
children?: React.ReactNode;
|
|
16
|
+
asChild?: boolean;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const Required: React.ForwardRefExoticComponent<PhoneLabelRequiredProps & React.RefAttributes<HTMLSpanElement>>;
|
|
20
|
+
interface PhoneLabelComponent extends React.ForwardRefExoticComponent<PhoneLabelProps & React.RefAttributes<HTMLDivElement>> {
|
|
21
|
+
Required: typeof Required;
|
|
22
|
+
}
|
|
23
|
+
export declare const Label: PhoneLabelComponent;
|
|
24
|
+
export interface PhoneErrorProps {
|
|
25
|
+
children?: React.ReactNode;
|
|
26
|
+
asChild?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
declare const Error: React.ForwardRefExoticComponent<PhoneErrorProps & React.RefAttributes<HTMLDivElement>>;
|
|
30
|
+
export interface CountryCodeProps {
|
|
31
|
+
asChild?: boolean;
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
declare const CountryCode: React.ForwardRefExoticComponent<CountryCodeProps & React.RefAttributes<HTMLDivElement>>;
|
|
35
|
+
export interface PhoneFieldInputProps {
|
|
36
|
+
asChild?: boolean;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
declare const Input: React.ForwardRefExoticComponent<PhoneFieldInputProps & React.RefAttributes<HTMLDivElement>>;
|
|
40
|
+
interface PhoneFieldComponent extends React.ForwardRefExoticComponent<PhoneFieldProps & React.RefAttributes<HTMLDivElement>> {
|
|
41
|
+
Label: typeof Label;
|
|
42
|
+
Error: typeof Error;
|
|
43
|
+
Input: typeof Input;
|
|
44
|
+
CountryCode: typeof CountryCode;
|
|
45
|
+
}
|
|
46
|
+
export declare const PhoneField: PhoneFieldComponent;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Form } from '@wix/headless-forms/react';
|
|
4
|
+
import { useFieldProps } from '@wix/form-public';
|
|
5
|
+
const Root = React.forwardRef((props, ref) => {
|
|
6
|
+
const { id, children, asChild, className } = props;
|
|
7
|
+
const { description } = useFieldProps();
|
|
8
|
+
return (_jsx(Form.Field, { ref: ref, id: id, asChild: asChild, className: className, children: _jsx(Form.Field.InputWrapper, { children: _jsx(Form.Field.Input, { description: description, children: children }) }) }));
|
|
9
|
+
});
|
|
10
|
+
Root.displayName = 'PhoneField';
|
|
11
|
+
const LabelRoot = React.forwardRef((props, ref) => {
|
|
12
|
+
const { asChild, className, children } = props;
|
|
13
|
+
const { id, label, showLabel } = useFieldProps();
|
|
14
|
+
if (!showLabel) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return (_jsx(Form.Field.Label, { ref: ref, asChild: asChild, className: className, children: _jsxs("label", { htmlFor: id, children: [label, children] }) }));
|
|
18
|
+
});
|
|
19
|
+
LabelRoot.displayName = 'PhoneField.Label';
|
|
20
|
+
export const Required = React.forwardRef((props, ref) => {
|
|
21
|
+
const { required } = useFieldProps();
|
|
22
|
+
return _jsx(Form.Field.Label.Required, { ...props, ref: ref, required: required });
|
|
23
|
+
});
|
|
24
|
+
Required.displayName = 'PhoneField.Label.Required';
|
|
25
|
+
export const Label = LabelRoot;
|
|
26
|
+
Label.Required = Required;
|
|
27
|
+
const Error = React.forwardRef((props, ref) => {
|
|
28
|
+
const { children, ...rest } = props;
|
|
29
|
+
const { errorMessage } = useFieldProps();
|
|
30
|
+
return (_jsx(Form.Field.Error, { ref: ref, ...rest, children: errorMessage }));
|
|
31
|
+
});
|
|
32
|
+
Error.displayName = 'PhoneField.Error';
|
|
33
|
+
const CountryCode = React.forwardRef((props, ref) => {
|
|
34
|
+
const { asChild, className, ...rest } = props;
|
|
35
|
+
const { id, countryCodes, defaultCountryCode, readOnly, onChange, onBlur, onFocus, } = useFieldProps();
|
|
36
|
+
return (_jsx(Form.Field.Input, { ref: ref, asChild: asChild, className: className, ...rest, children: _jsx("select", { id: `${id}-country`, defaultValue: defaultCountryCode || '', disabled: readOnly, onChange: (e) => onChange?.(e.target.value), onBlur: () => onBlur?.(), onFocus: () => onFocus?.(), children: countryCodes?.map((code) => (_jsx("option", { value: code, children: code }, code))) }) }));
|
|
37
|
+
});
|
|
38
|
+
CountryCode.displayName = 'PhoneField.CountryCode';
|
|
39
|
+
const Input = React.forwardRef((props, ref) => {
|
|
40
|
+
const { asChild, className, ...rest } = props;
|
|
41
|
+
const { id, value, required, readOnly, placeholder, onChange, onBlur, onFocus, description, } = useFieldProps();
|
|
42
|
+
const descriptionId = description ? `${id}-description` : undefined;
|
|
43
|
+
return (_jsx(Form.Field.Input, { ref: ref, asChild: asChild, className: className, ...rest, children: _jsx("input", { id: id, type: "tel", value: value || '', required: required, readOnly: readOnly, placeholder: placeholder, "aria-describedby": descriptionId, "aria-invalid": !!(required && !value), "aria-required": required, onChange: (e) => onChange?.(e.target.value), onBlur: () => onBlur?.(), onFocus: () => onFocus?.() }) }));
|
|
44
|
+
});
|
|
45
|
+
Input.displayName = 'PhoneField.Input';
|
|
46
|
+
export const PhoneField = Root;
|
|
47
|
+
PhoneField.Label = Label;
|
|
48
|
+
PhoneField.Error = Error;
|
|
49
|
+
PhoneField.Input = Input;
|
|
50
|
+
PhoneField.CountryCode = CountryCode;
|
package/dist/react/index.d.ts
CHANGED
package/dist/react/index.js
CHANGED
|
@@ -60,6 +60,8 @@ type OnSubmit = (formId: string, formValues: FormValues) => Promise<SubmitRespon
|
|
|
60
60
|
export type FormServiceConfig = {
|
|
61
61
|
formId: string;
|
|
62
62
|
onSubmit?: OnSubmit;
|
|
63
|
+
namespace?: string;
|
|
64
|
+
additionalMetadata?: Record<string, string | string[]>;
|
|
63
65
|
} | {
|
|
64
66
|
form: forms.Form;
|
|
65
67
|
onSubmit?: OnSubmit;
|
|
@@ -100,6 +102,8 @@ export declare const FormService: import("@wix/services-definitions").ServiceFac
|
|
|
100
102
|
* a specific form by ID that will be used to configure the FormService.
|
|
101
103
|
*
|
|
102
104
|
* @param {string} formId - The unique identifier of the form to load
|
|
105
|
+
* @param {string} [namespace] - Optional namespace for the form
|
|
106
|
+
* @param {Record<string, string | string[]>} [additionalMetadata] - Optional additional metadata to pass to the API
|
|
103
107
|
* @returns {Promise<FormServiceConfig>} Configuration object with pre-loaded form data
|
|
104
108
|
* @throws {Error} When the form cannot be loaded
|
|
105
109
|
*
|
|
@@ -110,5 +114,5 @@ export declare const FormService: import("@wix/services-definitions").ServiceFac
|
|
|
110
114
|
* const formService = FormService.withConfig(formConfig);
|
|
111
115
|
* ```
|
|
112
116
|
*/
|
|
113
|
-
export declare function loadFormServiceConfig(formId: string): Promise<FormServiceConfig>;
|
|
117
|
+
export declare function loadFormServiceConfig(formId: string, namespace?: string, additionalMetadata?: Record<string, string | string[]>): Promise<FormServiceConfig>;
|
|
114
118
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { submissions } from '@wix/forms';
|
|
2
2
|
import { defineService, implementService } from '@wix/services-definitions';
|
|
3
3
|
import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
|
|
4
|
+
import { httpClient } from '@wix/essentials';
|
|
4
5
|
/**
|
|
5
6
|
* Service definition for the Form service.
|
|
6
7
|
* This defines the contract that the FormService must implement.
|
|
@@ -43,13 +44,13 @@ export const FormService = implementService.withConfig()(FormServiceDefinition,
|
|
|
43
44
|
const hasSchema = 'form' in config;
|
|
44
45
|
const formSignal = signalsService.signal(hasSchema ? config.form : null);
|
|
45
46
|
if (!hasSchema) {
|
|
46
|
-
loadForm(config.formId);
|
|
47
|
+
loadForm(config.formId, config.namespace, config.additionalMetadata);
|
|
47
48
|
}
|
|
48
|
-
async function loadForm(id) {
|
|
49
|
+
async function loadForm(id, namespace, additionalMetadata) {
|
|
49
50
|
isLoadingSignal.set(true);
|
|
50
51
|
errorSignal.set(null);
|
|
51
52
|
try {
|
|
52
|
-
const result = await fetchForm(id);
|
|
53
|
+
const result = await fetchForm({ id, namespace, additionalMetadata });
|
|
53
54
|
if (result) {
|
|
54
55
|
formSignal.set(result);
|
|
55
56
|
}
|
|
@@ -113,13 +114,41 @@ export const FormService = implementService.withConfig()(FormServiceDefinition,
|
|
|
113
114
|
submitForm: submitForm,
|
|
114
115
|
};
|
|
115
116
|
});
|
|
116
|
-
|
|
117
|
+
function buildFormFetchQueryParams({ id, namespace, additionalMetadata, }) {
|
|
118
|
+
const params = new URLSearchParams();
|
|
119
|
+
params.append('formId', id);
|
|
120
|
+
if (namespace) {
|
|
121
|
+
params.append('namespace', namespace);
|
|
122
|
+
}
|
|
123
|
+
if (additionalMetadata) {
|
|
124
|
+
Object.entries(additionalMetadata).forEach(([key, value]) => {
|
|
125
|
+
if (Array.isArray(value)) {
|
|
126
|
+
value.forEach((v) => params.append(key, v));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
params.append(key, value);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return params;
|
|
134
|
+
}
|
|
135
|
+
async function fetchForm({ id, namespace, additionalMetadata, }) {
|
|
117
136
|
try {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
137
|
+
const params = buildFormFetchQueryParams({
|
|
138
|
+
id,
|
|
139
|
+
namespace,
|
|
140
|
+
additionalMetadata,
|
|
141
|
+
});
|
|
142
|
+
const url = `https://edge.wixapis.com/form-schema-service/v4/forms/${id}?${params.toString()}`;
|
|
143
|
+
const response = await httpClient.fetchWithAuth(url);
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw new Error(`Failed to fetch form: ${response.status} ${response.statusText}`);
|
|
146
|
+
}
|
|
147
|
+
const data = await response.json();
|
|
148
|
+
if (data && data.form) {
|
|
149
|
+
return data.form;
|
|
121
150
|
}
|
|
122
|
-
|
|
151
|
+
throw new Error('Invalid response format from Forms API');
|
|
123
152
|
}
|
|
124
153
|
catch (err) {
|
|
125
154
|
console.error('Failed to load form:', id, err);
|
|
@@ -132,6 +161,8 @@ async function fetchForm(id) {
|
|
|
132
161
|
* a specific form by ID that will be used to configure the FormService.
|
|
133
162
|
*
|
|
134
163
|
* @param {string} formId - The unique identifier of the form to load
|
|
164
|
+
* @param {string} [namespace] - Optional namespace for the form
|
|
165
|
+
* @param {Record<string, string | string[]>} [additionalMetadata] - Optional additional metadata to pass to the API
|
|
135
166
|
* @returns {Promise<FormServiceConfig>} Configuration object with pre-loaded form data
|
|
136
167
|
* @throws {Error} When the form cannot be loaded
|
|
137
168
|
*
|
|
@@ -142,7 +173,7 @@ async function fetchForm(id) {
|
|
|
142
173
|
* const formService = FormService.withConfig(formConfig);
|
|
143
174
|
* ```
|
|
144
175
|
*/
|
|
145
|
-
export async function loadFormServiceConfig(formId) {
|
|
146
|
-
const form = await fetchForm(formId);
|
|
176
|
+
export async function loadFormServiceConfig(formId, namespace, additionalMetadata) {
|
|
177
|
+
const form = await fetchForm({ id: formId, namespace, additionalMetadata });
|
|
147
178
|
return { form };
|
|
148
179
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-forms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"scripts": {
|
|
6
7
|
"build": "npm run build:esm && npm run build:cjs",
|
|
@@ -41,10 +42,21 @@
|
|
|
41
42
|
"vitest": "^3.1.4"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@wix/form-public": "^0.
|
|
45
|
+
"@wix/form-public": "^0.55.0",
|
|
45
46
|
"@wix/forms": "^1.0.331",
|
|
46
|
-
"@wix/headless-utils": "0.0.
|
|
47
|
+
"@wix/headless-utils": "0.0.5",
|
|
47
48
|
"@wix/services-definitions": "^0.1.4",
|
|
48
49
|
"@wix/services-manager-react": "^0.1.26"
|
|
49
|
-
}
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"registry": "https://registry.npmjs.org/",
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"wix": {
|
|
56
|
+
"artifact": {
|
|
57
|
+
"artifactId": "headless-forms",
|
|
58
|
+
"groupId": "com.wixpress.headless-components"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"falconPackageHash": "d647857993c0f94b27c514b7f73c6118a7ae3c6ee5ee21c998802545"
|
|
50
62
|
}
|