@wix/headless-bookings 0.0.96 → 0.0.97
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/booking-form/BookingForm.d.ts +30 -154
- package/cjs/dist/react/booking-form/BookingForm.js +34 -126
- package/cjs/dist/react/core/booking-form/BookingForm.d.ts +1 -51
- package/cjs/dist/react/core/booking-form/BookingForm.js +29 -140
- package/cjs/dist/services/booking-form/booking-form.d.ts +35 -42
- package/cjs/dist/services/booking-form/booking-form.js +104 -74
- package/cjs/dist/services/index.d.ts +1 -1
- package/dist/react/booking-form/BookingForm.d.ts +30 -154
- package/dist/react/booking-form/BookingForm.js +34 -126
- package/dist/react/core/booking-form/BookingForm.d.ts +1 -51
- package/dist/react/core/booking-form/BookingForm.js +29 -140
- package/dist/services/booking-form/booking-form.d.ts +35 -42
- package/dist/services/booking-form/booking-form.js +104 -74
- package/dist/services/index.d.ts +1 -1
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import { Form } from '@wix/headless-forms/react';
|
|
7
|
-
import
|
|
7
|
+
import type { BookingFormRenderProps } from '../core/booking-form/BookingForm.js';
|
|
8
8
|
type FieldMap = Parameters<typeof Form.Fields>[0]['fieldMap'];
|
|
9
9
|
export declare const TestIds: {
|
|
10
10
|
readonly bookingFormRoot: "booking-form-root";
|
|
@@ -23,8 +23,13 @@ export interface RootProps {
|
|
|
23
23
|
additionalMetadata?: Record<string, string | string[]>;
|
|
24
24
|
/** Field map to override default booking field components */
|
|
25
25
|
fieldMap: FieldMap;
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Child components - can be:
|
|
28
|
+
* - ReactNode for declarative usage
|
|
29
|
+
* - Render prop function for programmatic access to form data and actions
|
|
30
|
+
* - undefined to use default UI
|
|
31
|
+
*/
|
|
32
|
+
children?: React.ReactNode | ((data: BookingFormRenderProps) => React.ReactNode);
|
|
28
33
|
/** Whether to render as a child component */
|
|
29
34
|
asChild?: boolean;
|
|
30
35
|
/** CSS classes to apply to the root element */
|
|
@@ -78,6 +83,28 @@ export interface RootProps {
|
|
|
78
83
|
* <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
|
|
79
84
|
* <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
|
|
80
85
|
* </BookingForm.Root>
|
|
86
|
+
*
|
|
87
|
+
* // With render props for programmatic access
|
|
88
|
+
* <BookingForm.Root formId="your-form-id" fieldMap={...}>
|
|
89
|
+
* {({ fields, formValues, validateFormSubmission, setFormSubmission, formRef }) => (
|
|
90
|
+
* <div>
|
|
91
|
+
* <Form.Fields
|
|
92
|
+
* fieldMap={fields}
|
|
93
|
+
* formRef={formRef}
|
|
94
|
+
* rowGapClassname="gap-y-4"
|
|
95
|
+
* columnGapClassname="gap-x-2"
|
|
96
|
+
* />
|
|
97
|
+
* <button onClick={async () => {
|
|
98
|
+
* const isValid = await validateFormSubmission();
|
|
99
|
+
* if (isValid) {
|
|
100
|
+
* setFormSubmission(formValues);
|
|
101
|
+
* }
|
|
102
|
+
* }}>
|
|
103
|
+
* Validate & Submit
|
|
104
|
+
* </button>
|
|
105
|
+
* </div>
|
|
106
|
+
* )}
|
|
107
|
+
* </BookingForm.Root>
|
|
81
108
|
* ```
|
|
82
109
|
*/
|
|
83
110
|
export declare const Root: React.ForwardRefExoticComponent<RootProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -107,157 +134,6 @@ export interface FieldsProps {
|
|
|
107
134
|
* ```
|
|
108
135
|
*/
|
|
109
136
|
export declare const Fields: React.ForwardRefExoticComponent<FieldsProps & React.RefAttributes<HTMLDivElement>>;
|
|
110
|
-
/**
|
|
111
|
-
* Props for BookingForm.Actions.ValidateFormSubmission component
|
|
112
|
-
*/
|
|
113
|
-
export interface ValidateFormSubmissionProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'onClick'> {
|
|
114
|
-
asChild?: boolean;
|
|
115
|
-
children?: React.ReactNode | ((props: {
|
|
116
|
-
onClick: () => Promise<void>;
|
|
117
|
-
valid: boolean | null;
|
|
118
|
-
validationFailures: string[];
|
|
119
|
-
}) => React.ReactNode);
|
|
120
|
-
label?: string;
|
|
121
|
-
/** Callback when validation completes */
|
|
122
|
-
onValidationComplete?: (result: {
|
|
123
|
-
valid: boolean;
|
|
124
|
-
validationFailures: string[];
|
|
125
|
-
}) => void;
|
|
126
|
-
onClick?: (result: {
|
|
127
|
-
valid: boolean;
|
|
128
|
-
validationFailures: string[];
|
|
129
|
-
}) => void;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Button to validate the booking form submission.
|
|
133
|
-
* Must be used within BookingForm.Root or BookingForm.Data context.
|
|
134
|
-
* Default label is "Validate".
|
|
135
|
-
*
|
|
136
|
-
* @component
|
|
137
|
-
* @example
|
|
138
|
-
* ```tsx
|
|
139
|
-
* // Within BookingForm.Root
|
|
140
|
-
* <BookingForm.Root formId="your-form-id" fieldMap={...}>
|
|
141
|
-
* <BookingForm.Fields />
|
|
142
|
-
* <BookingForm.Actions.ValidateFormSubmission />
|
|
143
|
-
* </BookingForm.Root>
|
|
144
|
-
*
|
|
145
|
-
* // With custom label
|
|
146
|
-
* <BookingForm.Actions.ValidateFormSubmission label="Check Form" />
|
|
147
|
-
*
|
|
148
|
-
* // With asChild
|
|
149
|
-
* <BookingForm.Actions.ValidateFormSubmission asChild>
|
|
150
|
-
* <button className="btn-primary">Validate Booking</button>
|
|
151
|
-
* </BookingForm.Actions.ValidateFormSubmission>
|
|
152
|
-
*
|
|
153
|
-
* // Using render prop pattern with validation callback
|
|
154
|
-
* <BookingForm.Actions.ValidateFormSubmission
|
|
155
|
-
* onValidationComplete={(result) => {
|
|
156
|
-
* if (result.valid) {
|
|
157
|
-
* console.log('Form is valid!');
|
|
158
|
-
* } else {
|
|
159
|
-
* console.log('Validation errors:', result.validationFailures);
|
|
160
|
-
* }
|
|
161
|
-
* }}
|
|
162
|
-
* >
|
|
163
|
-
* {({ onClick, valid, validationFailures }) => (
|
|
164
|
-
* <button onClick={onClick}>
|
|
165
|
-
* {valid === null ? 'Validate' : valid ? 'Valid ✓' : 'Invalid ✗'}
|
|
166
|
-
* {validationFailures.length > 0 && (
|
|
167
|
-
* <span> ({validationFailures.length} errors)</span>
|
|
168
|
-
* )}
|
|
169
|
-
* </button>
|
|
170
|
-
* )}
|
|
171
|
-
* </BookingForm.Actions.ValidateFormSubmission>
|
|
172
|
-
* ```
|
|
173
|
-
*/
|
|
174
|
-
export declare const ValidateFormSubmission: React.ForwardRefExoticComponent<ValidateFormSubmissionProps & React.RefAttributes<HTMLButtonElement>>;
|
|
175
|
-
/**
|
|
176
|
-
* Actions namespace for BookingForm
|
|
177
|
-
*/
|
|
178
|
-
export declare const Actions: {
|
|
179
|
-
ValidateFormSubmission: React.ForwardRefExoticComponent<ValidateFormSubmissionProps & React.RefAttributes<HTMLButtonElement>>;
|
|
180
|
-
};
|
|
181
|
-
/**
|
|
182
|
-
* Render props for BookingForm.Data component
|
|
183
|
-
*/
|
|
184
|
-
export interface DataRenderProps {
|
|
185
|
-
/** The form ID being used */
|
|
186
|
-
formId: string;
|
|
187
|
-
/** Merged field map (DEFAULT_BOOKING_FIELD_MAP + user overrides) */
|
|
188
|
-
fields: FieldMap;
|
|
189
|
-
/** Current form values from FormService (live state of form fields) */
|
|
190
|
-
formValues: Record<string, unknown>;
|
|
191
|
-
/** Current form submission data */
|
|
192
|
-
formSubmission: Record<string, unknown> | null;
|
|
193
|
-
/** Action to store form submission data */
|
|
194
|
-
setFormSubmission: (formValues: Record<string, unknown>) => void;
|
|
195
|
-
/** Ref to access form methods imperatively (pass to Form.Fields) */
|
|
196
|
-
formRef: React.RefObject<CoreBookingForm.FormHandle | null>;
|
|
197
|
-
/** Action to validate the form fields. Returns true if valid, false if there are errors. */
|
|
198
|
-
validateFormSubmission: () => Promise<boolean>;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Props for BookingForm.Data component
|
|
202
|
-
*/
|
|
203
|
-
export interface DataProps {
|
|
204
|
-
/** The form ID to load (if not provided, the formId will be extracted from the selected service if available) */
|
|
205
|
-
formId?: string;
|
|
206
|
-
/** Optional service IDs to pass to FormService (will be merged into additionalMetadata) */
|
|
207
|
-
serviceIds?: string[];
|
|
208
|
-
/** Optional additional metadata to pass to FormService */
|
|
209
|
-
additionalMetadata?: Record<string, string | string[]>;
|
|
210
|
-
/** Field map to override default booking field components */
|
|
211
|
-
fieldMap: FieldMap;
|
|
212
|
-
/** Render prop function that receives form data and actions */
|
|
213
|
-
children: (data: DataRenderProps) => React.ReactNode;
|
|
214
|
-
/** Custom fallback UI for load errors. Can be a ReactNode or a function that receives the error. */
|
|
215
|
-
loadErrorFallback?: React.ReactNode | ((error: Error) => React.ReactNode);
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Data component that provides booking form data via render props.
|
|
219
|
-
* Use this when you need programmatic access to form state and actions.
|
|
220
|
-
*
|
|
221
|
-
* @component
|
|
222
|
-
* @example
|
|
223
|
-
* ```tsx
|
|
224
|
-
* <BookingForm.Data formId="your-form-id">
|
|
225
|
-
* {({ formId, fields, formValues, formRef, validateFormSubmission, setFormSubmission }) => (
|
|
226
|
-
* <div>
|
|
227
|
-
* <Form.Fields
|
|
228
|
-
* fieldMap={fields}
|
|
229
|
-
* rowGapClassname="gap-y-4"
|
|
230
|
-
* columnGapClassname="gap-x-2"
|
|
231
|
-
* formRef={formRef}
|
|
232
|
-
* />
|
|
233
|
-
* <button onClick={async () => {
|
|
234
|
-
* const isValid = await validateFormSubmission();
|
|
235
|
-
* if (isValid) {
|
|
236
|
-
* // Store current form values before submission
|
|
237
|
-
* setFormSubmission(formValues);
|
|
238
|
-
* }
|
|
239
|
-
* }}>
|
|
240
|
-
* Validate & Submit
|
|
241
|
-
* </button>
|
|
242
|
-
* </div>
|
|
243
|
-
* )}
|
|
244
|
-
* </BookingForm.Data>
|
|
245
|
-
*
|
|
246
|
-
* // With serviceIds
|
|
247
|
-
* <BookingForm.Data
|
|
248
|
-
* formId="your-form-id"
|
|
249
|
-
* serviceIds={['service-1', 'service-2']}
|
|
250
|
-
* >
|
|
251
|
-
* {({ fields, formRef }) => (
|
|
252
|
-
* <Form.Fields fieldMap={fields} formRef={formRef} />
|
|
253
|
-
* )}
|
|
254
|
-
* </BookingForm.Data>
|
|
255
|
-
* ```
|
|
256
|
-
*/
|
|
257
|
-
export declare function Data(props: DataProps): React.ReactNode;
|
|
258
|
-
export declare namespace Data {
|
|
259
|
-
var displayName: string;
|
|
260
|
-
}
|
|
261
137
|
/**
|
|
262
138
|
* Re-export LoadError component and utilities for error handling
|
|
263
139
|
*/
|
|
@@ -87,11 +87,44 @@ export const TestIds = {
|
|
|
87
87
|
* <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
|
|
88
88
|
* <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
|
|
89
89
|
* </BookingForm.Root>
|
|
90
|
+
*
|
|
91
|
+
* // With render props for programmatic access
|
|
92
|
+
* <BookingForm.Root formId="your-form-id" fieldMap={...}>
|
|
93
|
+
* {({ fields, formValues, validateFormSubmission, setFormSubmission, formRef }) => (
|
|
94
|
+
* <div>
|
|
95
|
+
* <Form.Fields
|
|
96
|
+
* fieldMap={fields}
|
|
97
|
+
* formRef={formRef}
|
|
98
|
+
* rowGapClassname="gap-y-4"
|
|
99
|
+
* columnGapClassname="gap-x-2"
|
|
100
|
+
* />
|
|
101
|
+
* <button onClick={async () => {
|
|
102
|
+
* const isValid = await validateFormSubmission();
|
|
103
|
+
* if (isValid) {
|
|
104
|
+
* setFormSubmission(formValues);
|
|
105
|
+
* }
|
|
106
|
+
* }}>
|
|
107
|
+
* Validate & Submit
|
|
108
|
+
* </button>
|
|
109
|
+
* </div>
|
|
110
|
+
* )}
|
|
111
|
+
* </BookingForm.Root>
|
|
90
112
|
* ```
|
|
91
113
|
*/
|
|
92
114
|
export const Root = React.forwardRef((props, ref) => {
|
|
93
115
|
const { children, asChild, className, formId, serviceIds, additionalMetadata, fieldMap, rowGapClassname = 'gap-y-4', columnGapClassname = 'gap-x-2', loadErrorFallback, ...otherProps } = props;
|
|
94
|
-
return (_jsx(BookingFormErrorBoundary, { fallback: loadErrorFallback, children: _jsx(CoreBookingForm.Root, { formId: formId, serviceIds: serviceIds, additionalMetadata: additionalMetadata, fieldMap: fieldMap, children: (
|
|
116
|
+
return (_jsx(BookingFormErrorBoundary, { fallback: loadErrorFallback, children: _jsx(CoreBookingForm.Root, { formId: formId, serviceIds: serviceIds, additionalMetadata: additionalMetadata, fieldMap: fieldMap, children: (renderProps) => {
|
|
117
|
+
// If children is a render prop function, call it with the render props
|
|
118
|
+
if (typeof children === 'function') {
|
|
119
|
+
return children(renderProps);
|
|
120
|
+
}
|
|
121
|
+
// Otherwise, use declarative pattern with context
|
|
122
|
+
return (_jsx(BookingFormContext.Provider, { value: {
|
|
123
|
+
fields: renderProps.fields,
|
|
124
|
+
rowGapClassname,
|
|
125
|
+
columnGapClassname,
|
|
126
|
+
}, children: _jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.bookingFormRoot, ...otherProps, children: _jsx("div", { children: children ?? (_jsxs(_Fragment, { children: [_jsx(Form.Loading, { className: "flex justify-center p-4 text-foreground" }), _jsx(Form.LoadingError, { className: "bg-destructive/10 border border-destructive/20 text-destructive px-4 py-3 rounded-lg mb-4" }), _jsx(Form.Fields, { fieldMap: renderProps.fields, rowGapClassname: rowGapClassname, columnGapClassname: columnGapClassname }), _jsx(Form.Error, { className: "mt-4 bg-destructive/10 border border-destructive/20 text-destructive px-4 py-3 rounded-lg" }), _jsx(Form.Submitted, { className: "mt-4 bg-green-500/10 border border-green-500/20 text-green-500 px-4 py-3 rounded-lg" })] })) }) }) }));
|
|
127
|
+
} }) }));
|
|
95
128
|
});
|
|
96
129
|
Root.displayName = 'BookingForm.Root';
|
|
97
130
|
const BookingFormContext = React.createContext(null);
|
|
@@ -126,131 +159,6 @@ export const Fields = React.forwardRef((props, ref) => {
|
|
|
126
159
|
return (_jsx("div", { ref: ref, className: className, "data-testid": TestIds.bookingFormFields, ...otherProps, children: _jsx(Form.Fields, { fieldMap: context.fields, rowGapClassname: rowGapClassname, columnGapClassname: columnGapClassname }) }));
|
|
127
160
|
});
|
|
128
161
|
Fields.displayName = 'BookingForm.Fields';
|
|
129
|
-
/**
|
|
130
|
-
* Button to validate the booking form submission.
|
|
131
|
-
* Must be used within BookingForm.Root or BookingForm.Data context.
|
|
132
|
-
* Default label is "Validate".
|
|
133
|
-
*
|
|
134
|
-
* @component
|
|
135
|
-
* @example
|
|
136
|
-
* ```tsx
|
|
137
|
-
* // Within BookingForm.Root
|
|
138
|
-
* <BookingForm.Root formId="your-form-id" fieldMap={...}>
|
|
139
|
-
* <BookingForm.Fields />
|
|
140
|
-
* <BookingForm.Actions.ValidateFormSubmission />
|
|
141
|
-
* </BookingForm.Root>
|
|
142
|
-
*
|
|
143
|
-
* // With custom label
|
|
144
|
-
* <BookingForm.Actions.ValidateFormSubmission label="Check Form" />
|
|
145
|
-
*
|
|
146
|
-
* // With asChild
|
|
147
|
-
* <BookingForm.Actions.ValidateFormSubmission asChild>
|
|
148
|
-
* <button className="btn-primary">Validate Booking</button>
|
|
149
|
-
* </BookingForm.Actions.ValidateFormSubmission>
|
|
150
|
-
*
|
|
151
|
-
* // Using render prop pattern with validation callback
|
|
152
|
-
* <BookingForm.Actions.ValidateFormSubmission
|
|
153
|
-
* onValidationComplete={(result) => {
|
|
154
|
-
* if (result.valid) {
|
|
155
|
-
* console.log('Form is valid!');
|
|
156
|
-
* } else {
|
|
157
|
-
* console.log('Validation errors:', result.validationFailures);
|
|
158
|
-
* }
|
|
159
|
-
* }}
|
|
160
|
-
* >
|
|
161
|
-
* {({ onClick, valid, validationFailures }) => (
|
|
162
|
-
* <button onClick={onClick}>
|
|
163
|
-
* {valid === null ? 'Validate' : valid ? 'Valid ✓' : 'Invalid ✗'}
|
|
164
|
-
* {validationFailures.length > 0 && (
|
|
165
|
-
* <span> ({validationFailures.length} errors)</span>
|
|
166
|
-
* )}
|
|
167
|
-
* </button>
|
|
168
|
-
* )}
|
|
169
|
-
* </BookingForm.Actions.ValidateFormSubmission>
|
|
170
|
-
* ```
|
|
171
|
-
*/
|
|
172
|
-
export const ValidateFormSubmission = React.forwardRef((props, ref) => {
|
|
173
|
-
const { asChild, children, label, onValidationComplete, onClick, ...rest } = props;
|
|
174
|
-
const [valid, setValid] = React.useState(null);
|
|
175
|
-
const [validationFailures, setValidationFailures] = React.useState([]);
|
|
176
|
-
return (_jsx(CoreBookingForm.Actions, { children: ({ validateFormSubmission }) => {
|
|
177
|
-
const handleClick = async () => {
|
|
178
|
-
const result = await validateFormSubmission();
|
|
179
|
-
setValid(result.valid);
|
|
180
|
-
setValidationFailures(result.validationFailures);
|
|
181
|
-
if (onValidationComplete) {
|
|
182
|
-
onValidationComplete(result);
|
|
183
|
-
}
|
|
184
|
-
onClick?.(result);
|
|
185
|
-
};
|
|
186
|
-
return (_jsx(AsChildSlot, { ref: ref, asChild: asChild, ...rest, "data-testid": TestIds.bookingFormActionValidateFormSubmission, "data-valid": valid, customElement: children, customElementProps: {
|
|
187
|
-
onClick: handleClick,
|
|
188
|
-
valid,
|
|
189
|
-
validationFailures,
|
|
190
|
-
}, children: _jsx("button", { onClick: handleClick, children: children || label }) }));
|
|
191
|
-
} }));
|
|
192
|
-
});
|
|
193
|
-
ValidateFormSubmission.displayName =
|
|
194
|
-
'BookingForm.Actions.ValidateFormSubmission';
|
|
195
|
-
/**
|
|
196
|
-
* Actions namespace for BookingForm
|
|
197
|
-
*/
|
|
198
|
-
export const Actions = {
|
|
199
|
-
ValidateFormSubmission,
|
|
200
|
-
};
|
|
201
|
-
/**
|
|
202
|
-
* Data component that provides booking form data via render props.
|
|
203
|
-
* Use this when you need programmatic access to form state and actions.
|
|
204
|
-
*
|
|
205
|
-
* @component
|
|
206
|
-
* @example
|
|
207
|
-
* ```tsx
|
|
208
|
-
* <BookingForm.Data formId="your-form-id">
|
|
209
|
-
* {({ formId, fields, formValues, formRef, validateFormSubmission, setFormSubmission }) => (
|
|
210
|
-
* <div>
|
|
211
|
-
* <Form.Fields
|
|
212
|
-
* fieldMap={fields}
|
|
213
|
-
* rowGapClassname="gap-y-4"
|
|
214
|
-
* columnGapClassname="gap-x-2"
|
|
215
|
-
* formRef={formRef}
|
|
216
|
-
* />
|
|
217
|
-
* <button onClick={async () => {
|
|
218
|
-
* const isValid = await validateFormSubmission();
|
|
219
|
-
* if (isValid) {
|
|
220
|
-
* // Store current form values before submission
|
|
221
|
-
* setFormSubmission(formValues);
|
|
222
|
-
* }
|
|
223
|
-
* }}>
|
|
224
|
-
* Validate & Submit
|
|
225
|
-
* </button>
|
|
226
|
-
* </div>
|
|
227
|
-
* )}
|
|
228
|
-
* </BookingForm.Data>
|
|
229
|
-
*
|
|
230
|
-
* // With serviceIds
|
|
231
|
-
* <BookingForm.Data
|
|
232
|
-
* formId="your-form-id"
|
|
233
|
-
* serviceIds={['service-1', 'service-2']}
|
|
234
|
-
* >
|
|
235
|
-
* {({ fields, formRef }) => (
|
|
236
|
-
* <Form.Fields fieldMap={fields} formRef={formRef} />
|
|
237
|
-
* )}
|
|
238
|
-
* </BookingForm.Data>
|
|
239
|
-
* ```
|
|
240
|
-
*/
|
|
241
|
-
export function Data(props) {
|
|
242
|
-
const { formId, serviceIds, additionalMetadata, fieldMap, children, loadErrorFallback, } = props;
|
|
243
|
-
return (_jsx(BookingFormErrorBoundary, { fallback: loadErrorFallback, children: _jsx(CoreBookingForm.Root, { formId: formId, serviceIds: serviceIds, additionalMetadata: additionalMetadata, fieldMap: fieldMap, children: ({ formId: id, fields, formValues, formSubmission, setFormSubmission, validateFormSubmission, formRef, }) => children({
|
|
244
|
-
formId: id,
|
|
245
|
-
fields,
|
|
246
|
-
formValues,
|
|
247
|
-
formSubmission,
|
|
248
|
-
setFormSubmission,
|
|
249
|
-
formRef,
|
|
250
|
-
validateFormSubmission,
|
|
251
|
-
}) }) }));
|
|
252
|
-
}
|
|
253
|
-
Data.displayName = 'BookingForm.Data';
|
|
254
162
|
// ============================================================================
|
|
255
163
|
// Error Handling
|
|
256
164
|
// ============================================================================
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import * as React from 'react';
|
|
12
12
|
import { type FormValues } from '@wix/form-public';
|
|
13
13
|
import { forms, submissions } from '@wix/forms';
|
|
14
|
-
import { BookingFormConfigurationError
|
|
14
|
+
import { BookingFormConfigurationError } from '../../../services/booking-form/booking-form.js';
|
|
15
15
|
import { Form } from '@wix/headless-forms/react';
|
|
16
16
|
type FieldMap = Parameters<typeof Form.Fields>[0]['fieldMap'];
|
|
17
17
|
export type Submit = () => Promise<submissions.GetSubmissionResponse['submission'] | undefined>;
|
|
@@ -25,14 +25,10 @@ export type FormHandle = {
|
|
|
25
25
|
* Render props data provided to BookingForm children
|
|
26
26
|
*/
|
|
27
27
|
export interface BookingFormRenderProps {
|
|
28
|
-
/** The form ID being used */
|
|
29
|
-
formId: string;
|
|
30
28
|
/** Current form values from FormService (live state of form fields) */
|
|
31
29
|
formValues: FormValues;
|
|
32
30
|
/** Current form submission data (values stored before submission) */
|
|
33
31
|
formSubmission: FormValues | null;
|
|
34
|
-
/** Action to store form submission data */
|
|
35
|
-
setFormSubmission: (formValues: FormValues) => void;
|
|
36
32
|
/** Action to validate the form fields. Returns true if valid, false if there are errors. */
|
|
37
33
|
validateFormSubmission: () => Promise<boolean>;
|
|
38
34
|
/** Merged field map (DEFAULT_BOOKING_FIELD_MAP + user overrides) */
|
|
@@ -148,52 +144,6 @@ export type BookingFormProps = BookingFormWithFormIdProps | BookingFormWithFormP
|
|
|
148
144
|
* ```
|
|
149
145
|
*/
|
|
150
146
|
export declare function Root(props: BookingFormProps): React.ReactNode;
|
|
151
|
-
/**
|
|
152
|
-
* Render props for BookingForm.Actions component
|
|
153
|
-
*/
|
|
154
|
-
export interface ActionsRenderProps {
|
|
155
|
-
/** Action to validate the form submission */
|
|
156
|
-
validateFormSubmission: () => Promise<ValidationResult>;
|
|
157
|
-
/** The form ID being used */
|
|
158
|
-
formId: string;
|
|
159
|
-
/** Current form values */
|
|
160
|
-
formValues: FormValues;
|
|
161
|
-
/** Current form submission data */
|
|
162
|
-
formSubmission: FormValues | null;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Props for BookingForm.Actions component
|
|
166
|
-
*/
|
|
167
|
-
export interface ActionsProps {
|
|
168
|
-
children: (data: ActionsRenderProps) => React.ReactNode;
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Core component that provides booking form actions via render props.
|
|
172
|
-
* Must be used within BookingForm.Root context.
|
|
173
|
-
*
|
|
174
|
-
* @component
|
|
175
|
-
* @example
|
|
176
|
-
* ```tsx
|
|
177
|
-
* <CoreBookingForm.Root formId="form-123">
|
|
178
|
-
* {({ fields }) => (
|
|
179
|
-
* <>
|
|
180
|
-
* <Form.Fields fieldMap={fields} />
|
|
181
|
-
* <CoreBookingForm.Actions>
|
|
182
|
-
* {({ validateFormSubmission, formId }) => (
|
|
183
|
-
* <button onClick={async () => {
|
|
184
|
-
* const result = await validateFormSubmission();
|
|
185
|
-
* console.log('Validation result:', result);
|
|
186
|
-
* }}>
|
|
187
|
-
* Validate Form
|
|
188
|
-
* </button>
|
|
189
|
-
* )}
|
|
190
|
-
* </CoreBookingForm.Actions>
|
|
191
|
-
* </>
|
|
192
|
-
* )}
|
|
193
|
-
* </CoreBookingForm.Root>
|
|
194
|
-
* ```
|
|
195
|
-
*/
|
|
196
|
-
export declare function Actions(props: ActionsProps): React.ReactNode;
|
|
197
147
|
/**
|
|
198
148
|
* Props for BookingForm.LoadError component
|
|
199
149
|
*/
|
|
@@ -10,33 +10,17 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
10
10
|
* 2. SSR/SSG: Provide a pre-loaded form object for server-side rendering
|
|
11
11
|
*/
|
|
12
12
|
import * as React from 'react';
|
|
13
|
-
import {
|
|
13
|
+
import { useRef, useCallback } from 'react';
|
|
14
14
|
import { WixServices, useService } from '@wix/services-manager-react';
|
|
15
15
|
import { createServicesMap } from '@wix/services-manager';
|
|
16
|
-
import { BookingFormServiceDefinition, BookingFormService, BookingFormConfigurationError,
|
|
16
|
+
import { BookingFormServiceDefinition, BookingFormService, BookingFormConfigurationError, } from '../../../services/booking-form/booking-form.js';
|
|
17
17
|
import { BookingServiceDefinition } from '../../../services/booking/booking.js';
|
|
18
18
|
import { Form } from '@wix/headless-forms/react';
|
|
19
|
+
import { FormServiceDefinition } from '@wix/headless-forms/services';
|
|
20
|
+
import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
|
|
19
21
|
// ============================================================================
|
|
20
22
|
// Component
|
|
21
23
|
// ============================================================================
|
|
22
|
-
/**
|
|
23
|
-
* Extracts the form ID from props (either directly or from form object)
|
|
24
|
-
* Returns undefined if formId should be extracted from services by the service layer
|
|
25
|
-
*/
|
|
26
|
-
function extractFormIdFromProps(props) {
|
|
27
|
-
// Convert props to config-like object for the utility
|
|
28
|
-
const config = props.form
|
|
29
|
-
? { form: props.form }
|
|
30
|
-
: props.formId
|
|
31
|
-
? { formId: props.formId }
|
|
32
|
-
: undefined;
|
|
33
|
-
const formId = extractFormIdFromConfig(config);
|
|
34
|
-
// If form was provided but we couldn't extract formId, that's an error
|
|
35
|
-
if (props.form && !formId) {
|
|
36
|
-
throw new Error('BookingForm: Could not extract form ID from provided form object.');
|
|
37
|
-
}
|
|
38
|
-
return formId;
|
|
39
|
-
}
|
|
40
24
|
/**
|
|
41
25
|
* Core component that provides booking form data via render props.
|
|
42
26
|
* Uses BookingFormService to manage form submission state.
|
|
@@ -107,78 +91,24 @@ function extractFormIdFromProps(props) {
|
|
|
107
91
|
*/
|
|
108
92
|
export function Root(props) {
|
|
109
93
|
const { serviceIds, additionalMetadata, fieldMap, children } = props;
|
|
110
|
-
// Determine if we have a pre-loaded form
|
|
111
|
-
const hasPreloadedForm = 'form' in props && props.form;
|
|
112
|
-
const preloadedForm = hasPreloadedForm ? props.form : null;
|
|
113
|
-
// Extract formId from props
|
|
114
|
-
const propsFormId = extractFormIdFromProps(props);
|
|
115
|
-
// Try to get BookingService if available (to extract formId and serviceIds from services)
|
|
116
|
-
let bookingService = null;
|
|
117
|
-
try {
|
|
118
|
-
bookingService = useService(BookingServiceDefinition);
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
// BookingService not available - that's ok if formId and serviceIds were provided explicitly
|
|
122
|
-
bookingService = null;
|
|
123
|
-
}
|
|
124
|
-
// Determine final formId: use props formId if provided, otherwise extract from services
|
|
125
|
-
let formId = propsFormId;
|
|
126
|
-
if (!formId && bookingService) {
|
|
127
|
-
const serviceSelections = bookingService.serviceSelections.get();
|
|
128
|
-
const services = serviceSelections.length
|
|
129
|
-
? serviceSelections.map((s) => s.service)
|
|
130
|
-
: [];
|
|
131
|
-
formId = services.find((s) => s.form?._id)?.form?._id;
|
|
132
|
-
}
|
|
133
|
-
// Determine final serviceIds: use props serviceIds if provided, otherwise extract from services
|
|
134
|
-
let resolvedServiceIds = serviceIds;
|
|
135
|
-
if (!resolvedServiceIds && bookingService) {
|
|
136
|
-
const serviceSelections = bookingService.serviceSelections.get();
|
|
137
|
-
resolvedServiceIds = serviceSelections
|
|
138
|
-
.map((s) => s.service._id)
|
|
139
|
-
.filter((id) => Boolean(id));
|
|
140
|
-
}
|
|
141
|
-
// Merge serviceIds into additionalMetadata
|
|
142
|
-
const mergedMetadata = {
|
|
143
|
-
...additionalMetadata,
|
|
144
|
-
...(resolvedServiceIds &&
|
|
145
|
-
resolvedServiceIds.length > 0 && { serviceIds: resolvedServiceIds }),
|
|
146
|
-
};
|
|
147
|
-
// Build formServiceConfig for FormService
|
|
148
|
-
// If we have a pre-loaded form, pass it directly
|
|
149
|
-
// If we have formId, pass it with namespace and metadata
|
|
150
|
-
const formServiceConfig = useMemo(() => {
|
|
151
|
-
if (preloadedForm) {
|
|
152
|
-
return { form: preloadedForm };
|
|
153
|
-
}
|
|
154
|
-
// Only provide config if formId is available
|
|
155
|
-
if (formId) {
|
|
156
|
-
return {
|
|
157
|
-
formId,
|
|
158
|
-
namespace: BOOKING_FORM_NAMESPACE,
|
|
159
|
-
...(Object.keys(mergedMetadata).length > 0 && {
|
|
160
|
-
additionalMetadata: mergedMetadata,
|
|
161
|
-
}),
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
}, [formId, mergedMetadata, preloadedForm]);
|
|
165
|
-
// Build bookingFormServiceConfig
|
|
166
|
-
const bookingFormServiceConfig = useMemo(() => {
|
|
167
|
-
if (preloadedForm) {
|
|
168
|
-
return { form: preloadedForm };
|
|
169
|
-
}
|
|
170
|
-
// formId is optional - service will extract from services if not provided
|
|
171
|
-
return formId ? { formId, serviceIds } : undefined;
|
|
172
|
-
}, [formId, preloadedForm, serviceIds]);
|
|
173
94
|
// Create formRef to pass to Form.Root and BookingFormContent
|
|
174
95
|
const formRef = useRef(null);
|
|
175
|
-
|
|
96
|
+
const bookingFormServiceConfig = props.form
|
|
97
|
+
? { form: props.form }
|
|
98
|
+
: props.formId
|
|
99
|
+
? { formId: props.formId, additionalMetadata, serviceIds }
|
|
100
|
+
: undefined;
|
|
101
|
+
// Use Form.Root to set up FormService, then nest WixServices for BookingFormService
|
|
102
|
+
// This allows BookingFormService to access FormService via getService internally
|
|
103
|
+
return (_jsx(WixServices, { servicesMap: createServicesMap().addService(BookingFormServiceDefinition, BookingFormService, bookingFormServiceConfig), disposeOnUnmount: true, children: _jsx(FormWrapper, { formRef: formRef, children: _jsx(BookingFormContent, { fields: fieldMap, formRef: formRef, children: children }) }) }));
|
|
104
|
+
}
|
|
105
|
+
function FormWrapper({ children, formRef, }) {
|
|
106
|
+
const bookingFormService = useService(BookingFormServiceDefinition);
|
|
107
|
+
const formServiceConfig = bookingFormService.formServiceConfig.get();
|
|
176
108
|
if (!formServiceConfig) {
|
|
177
109
|
return null;
|
|
178
110
|
}
|
|
179
|
-
|
|
180
|
-
// This allows BookingFormService to access FormService via getService internally
|
|
181
|
-
return (_jsx(Form.Root, { formServiceConfig: formServiceConfig, formRef: formRef, children: _jsx(WixServices, { servicesMap: createServicesMap().addService(BookingFormServiceDefinition, BookingFormService, bookingFormServiceConfig), disposeOnUnmount: true, children: _jsx(BookingFormContent, { fields: fieldMap, formRef: formRef, children: children }) }) }));
|
|
111
|
+
return (_jsx(Form.Root, { formServiceConfig: formServiceConfig, formRef: formRef, children: children }));
|
|
182
112
|
}
|
|
183
113
|
/**
|
|
184
114
|
* Internal component that consumes BookingFormService and provides render props.
|
|
@@ -205,72 +135,31 @@ function BookingFormContent({ fields, formRef, children, }) {
|
|
|
205
135
|
// For now, we'll always attempt to get it and let it throw if not available
|
|
206
136
|
// The user should wrap BookingForm with Booking.Root when using Booking.Actions.Book
|
|
207
137
|
const bookingService = useService(BookingServiceDefinition);
|
|
138
|
+
const formService = useService(FormServiceDefinition);
|
|
139
|
+
const signalsService = useService(SignalsServiceDefinition);
|
|
208
140
|
// Create validateFormSubmission action that uses the form ref
|
|
209
141
|
const validateFormSubmission = useCallback(() => {
|
|
210
142
|
return formRef.current?.validate() ?? Promise.resolve(false);
|
|
211
143
|
}, []);
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// This allows Booking.Actions.Book to work correctly
|
|
218
|
-
if (bookingService) {
|
|
144
|
+
const formValues = formService.formValuesSignal.get();
|
|
145
|
+
const formServiceConfig = bookingFormService.formServiceConfig.get();
|
|
146
|
+
signalsService.effect(() => {
|
|
147
|
+
const formValues = formService.formValuesSignal.get();
|
|
148
|
+
if (formValues && Object.values(formValues).filter(Boolean).length > 0) {
|
|
219
149
|
bookingService.actions.setFormSubmission(formValues);
|
|
220
150
|
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const resolvedFormId = bookingFormService.formId.get();
|
|
151
|
+
});
|
|
152
|
+
if (!formServiceConfig) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
226
155
|
return children({
|
|
227
|
-
formId: resolvedFormId,
|
|
228
156
|
formValues,
|
|
229
|
-
formSubmission,
|
|
230
|
-
setFormSubmission,
|
|
157
|
+
formSubmission: formService.formValuesSignal.get(),
|
|
231
158
|
validateFormSubmission,
|
|
232
159
|
formRef,
|
|
233
160
|
fields,
|
|
234
161
|
});
|
|
235
162
|
}
|
|
236
|
-
/**
|
|
237
|
-
* Core component that provides booking form actions via render props.
|
|
238
|
-
* Must be used within BookingForm.Root context.
|
|
239
|
-
*
|
|
240
|
-
* @component
|
|
241
|
-
* @example
|
|
242
|
-
* ```tsx
|
|
243
|
-
* <CoreBookingForm.Root formId="form-123">
|
|
244
|
-
* {({ fields }) => (
|
|
245
|
-
* <>
|
|
246
|
-
* <Form.Fields fieldMap={fields} />
|
|
247
|
-
* <CoreBookingForm.Actions>
|
|
248
|
-
* {({ validateFormSubmission, formId }) => (
|
|
249
|
-
* <button onClick={async () => {
|
|
250
|
-
* const result = await validateFormSubmission();
|
|
251
|
-
* console.log('Validation result:', result);
|
|
252
|
-
* }}>
|
|
253
|
-
* Validate Form
|
|
254
|
-
* </button>
|
|
255
|
-
* )}
|
|
256
|
-
* </CoreBookingForm.Actions>
|
|
257
|
-
* </>
|
|
258
|
-
* )}
|
|
259
|
-
* </CoreBookingForm.Root>
|
|
260
|
-
* ```
|
|
261
|
-
*/
|
|
262
|
-
export function Actions(props) {
|
|
263
|
-
const bookingFormService = useService(BookingFormServiceDefinition);
|
|
264
|
-
const formSubmission = bookingFormService.formSubmission.get();
|
|
265
|
-
const formValues = bookingFormService.formValues.get();
|
|
266
|
-
const formId = bookingFormService.formId.get();
|
|
267
|
-
return props.children({
|
|
268
|
-
validateFormSubmission: bookingFormService.validateFormSubmission,
|
|
269
|
-
formId,
|
|
270
|
-
formValues,
|
|
271
|
-
formSubmission,
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
163
|
/**
|
|
275
164
|
* Component to display when BookingForm fails to load due to configuration errors.
|
|
276
165
|
* This component should be used in an error boundary to catch BookingFormConfigurationError.
|