@wix/headless-bookings 0.0.22 → 0.0.24
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 +156 -0
- package/cjs/dist/react/booking-form/BookingForm.js +126 -0
- package/cjs/dist/react/core/booking-form/BookingForm.d.ts +139 -0
- package/cjs/dist/react/core/booking-form/BookingForm.js +181 -0
- package/cjs/dist/react/index.d.ts +1 -0
- package/cjs/dist/react/index.js +1 -0
- package/cjs/dist/services/booking-form/booking-form.d.ts +140 -0
- package/cjs/dist/services/booking-form/booking-form.js +171 -0
- package/cjs/dist/services/index.d.ts +1 -0
- package/cjs/dist/services/index.js +1 -0
- package/cjs/dist/services/service/service.js +4 -1
- package/dist/react/booking-form/BookingForm.d.ts +156 -0
- package/dist/react/booking-form/BookingForm.js +126 -0
- package/dist/react/core/booking-form/BookingForm.d.ts +139 -0
- package/dist/react/core/booking-form/BookingForm.js +181 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/services/booking-form/booking-form.d.ts +140 -0
- package/dist/services/booking-form/booking-form.js +171 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/service/service.js +4 -1
- package/package.json +3 -2
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BookingForm - High-level component for rendering booking forms
|
|
3
|
+
* Provides components for displaying and managing booking form fields
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { Form } from '@wix/headless-forms/react';
|
|
7
|
+
type FieldMap = Parameters<typeof Form.Fields>[0]['fieldMap'];
|
|
8
|
+
/**
|
|
9
|
+
* Props for BookingForm.Root component
|
|
10
|
+
*/
|
|
11
|
+
export interface RootProps {
|
|
12
|
+
/** The form ID to load (required - standalone mode) */
|
|
13
|
+
formId: string;
|
|
14
|
+
/** Optional additional metadata to pass to FormService */
|
|
15
|
+
additionalMetadata?: Record<string, string | string[]>;
|
|
16
|
+
/** Optional partial field map to override default booking field components */
|
|
17
|
+
fieldMap?: Partial<FieldMap>;
|
|
18
|
+
/** Child components - if not provided, Form.Fields is rendered with default field map */
|
|
19
|
+
children?: React.ReactNode;
|
|
20
|
+
/** Whether to render as a child component */
|
|
21
|
+
asChild?: boolean;
|
|
22
|
+
/** CSS classes to apply to the root element */
|
|
23
|
+
className?: string;
|
|
24
|
+
/** Row gap class name for Form.Fields when using default rendering */
|
|
25
|
+
rowGapClassname?: string;
|
|
26
|
+
/** Column gap class name for Form.Fields when using default rendering */
|
|
27
|
+
columnGapClassname?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Root component for booking form that wraps CoreBookingForm.Root.
|
|
31
|
+
* Automatically loads form and handles submission in standalone mode.
|
|
32
|
+
* Uses DEFAULT_BOOKING_FIELD_MAP for field rendering with optional user overrides.
|
|
33
|
+
*
|
|
34
|
+
* @order 1
|
|
35
|
+
* @component
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* import { BookingForm } from '@wix/headless-bookings/react';
|
|
39
|
+
*
|
|
40
|
+
* // Minimal usage - uses default field components
|
|
41
|
+
* <BookingForm.Root formId="your-form-id" />
|
|
42
|
+
*
|
|
43
|
+
* // With custom styling
|
|
44
|
+
* <BookingForm.Root
|
|
45
|
+
* formId="your-form-id"
|
|
46
|
+
* className="my-form-container"
|
|
47
|
+
* rowGapClassname="gap-y-6"
|
|
48
|
+
* columnGapClassname="gap-x-4"
|
|
49
|
+
* />
|
|
50
|
+
*
|
|
51
|
+
* // With custom field overrides
|
|
52
|
+
* <BookingForm.Root
|
|
53
|
+
* formId="your-form-id"
|
|
54
|
+
* fieldMap={{ TEXT_INPUT: MyCustomTextInput }}
|
|
55
|
+
* />
|
|
56
|
+
*
|
|
57
|
+
* // With full control over form structure
|
|
58
|
+
* <BookingForm.Root formId="your-form-id">
|
|
59
|
+
* <Form.Loading className="flex justify-center p-4" />
|
|
60
|
+
* <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
|
|
61
|
+
* <BookingForm.Fields />
|
|
62
|
+
* <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
|
|
63
|
+
* <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
|
|
64
|
+
* </BookingForm.Root>
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare const Root: React.ForwardRefExoticComponent<RootProps & React.RefAttributes<HTMLDivElement>>;
|
|
68
|
+
/**
|
|
69
|
+
* Props for BookingForm.Fields component
|
|
70
|
+
*/
|
|
71
|
+
export interface FieldsProps {
|
|
72
|
+
/** Optional override for row gap class name */
|
|
73
|
+
rowGapClassname?: string;
|
|
74
|
+
/** Optional override for column gap class name */
|
|
75
|
+
columnGapClassname?: string;
|
|
76
|
+
/** CSS classes to apply */
|
|
77
|
+
className?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Fields component that renders Form.Fields with the merged field map.
|
|
81
|
+
* Must be used within BookingForm.Root.
|
|
82
|
+
*
|
|
83
|
+
* @component
|
|
84
|
+
* @example
|
|
85
|
+
* ```tsx
|
|
86
|
+
* <BookingForm.Root formId="your-form-id">
|
|
87
|
+
* <Form.Loading className="p-4" />
|
|
88
|
+
* <BookingForm.Fields rowGapClassname="gap-y-6" />
|
|
89
|
+
* <Form.Error className="text-destructive" />
|
|
90
|
+
* </BookingForm.Root>
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare const Fields: React.ForwardRefExoticComponent<FieldsProps & React.RefAttributes<HTMLDivElement>>;
|
|
94
|
+
/**
|
|
95
|
+
* Render props for BookingForm.Data component
|
|
96
|
+
*/
|
|
97
|
+
export interface DataRenderProps {
|
|
98
|
+
/** The form ID being used */
|
|
99
|
+
formId: string;
|
|
100
|
+
/** Merged field map (DEFAULT_BOOKING_FIELD_MAP + user overrides) */
|
|
101
|
+
fields: FieldMap;
|
|
102
|
+
/** Current form values from FormService (live state of form fields) */
|
|
103
|
+
formValues: Record<string, unknown>;
|
|
104
|
+
/** Current form submission data */
|
|
105
|
+
formSubmission: Record<string, unknown> | null;
|
|
106
|
+
/** Action to store form submission data */
|
|
107
|
+
setFormSubmission: (formValues: Record<string, unknown>) => void;
|
|
108
|
+
/** Action to validate the form */
|
|
109
|
+
validateFormSubmission: () => Promise<{
|
|
110
|
+
valid: boolean;
|
|
111
|
+
validationFailures: string[];
|
|
112
|
+
}>;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Props for BookingForm.Data component
|
|
116
|
+
*/
|
|
117
|
+
export interface DataProps {
|
|
118
|
+
/** The form ID to load (required - standalone mode) */
|
|
119
|
+
formId: string;
|
|
120
|
+
/** Optional additional metadata to pass to FormService */
|
|
121
|
+
additionalMetadata?: Record<string, string | string[]>;
|
|
122
|
+
/** Optional partial field map to override default booking field components */
|
|
123
|
+
fieldMap?: Partial<FieldMap>;
|
|
124
|
+
/** Render prop function that receives form data and actions */
|
|
125
|
+
children: (data: DataRenderProps) => React.ReactNode;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Data component that provides booking form data via render props.
|
|
129
|
+
* Use this when you need programmatic access to form state and actions.
|
|
130
|
+
*
|
|
131
|
+
* @component
|
|
132
|
+
* @example
|
|
133
|
+
* ```tsx
|
|
134
|
+
* <BookingForm.Data formId="your-form-id">
|
|
135
|
+
* {({ formId, fields, formValues, setFormSubmission, validateFormSubmission }) => (
|
|
136
|
+
* <div>
|
|
137
|
+
* <Form.Fields fieldMap={fields} rowGapClassname="gap-y-4" columnGapClassname="gap-x-2" />
|
|
138
|
+
* <button onClick={async () => {
|
|
139
|
+
* const result = await validateFormSubmission();
|
|
140
|
+
* if (result.valid) {
|
|
141
|
+
* // Store current form values before submission
|
|
142
|
+
* setFormSubmission(formValues);
|
|
143
|
+
* }
|
|
144
|
+
* }}>
|
|
145
|
+
* Validate & Submit
|
|
146
|
+
* </button>
|
|
147
|
+
* </div>
|
|
148
|
+
* )}
|
|
149
|
+
* </BookingForm.Data>
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export declare function Data(props: DataProps): React.ReactNode;
|
|
153
|
+
export declare namespace Data {
|
|
154
|
+
var displayName: string;
|
|
155
|
+
}
|
|
156
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* BookingForm - High-level component for rendering booking forms
|
|
4
|
+
* Provides components for displaying and managing booking form fields
|
|
5
|
+
*/
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { AsChildSlot } from '@wix/headless-utils/react';
|
|
8
|
+
import { Form } from '@wix/headless-forms/react';
|
|
9
|
+
import * as CoreBookingForm from '../core/booking-form/BookingForm.js';
|
|
10
|
+
var TestIds;
|
|
11
|
+
(function (TestIds) {
|
|
12
|
+
TestIds["bookingFormRoot"] = "booking-form-root";
|
|
13
|
+
TestIds["bookingFormFields"] = "booking-form-fields";
|
|
14
|
+
})(TestIds || (TestIds = {}));
|
|
15
|
+
/**
|
|
16
|
+
* Root component for booking form that wraps CoreBookingForm.Root.
|
|
17
|
+
* Automatically loads form and handles submission in standalone mode.
|
|
18
|
+
* Uses DEFAULT_BOOKING_FIELD_MAP for field rendering with optional user overrides.
|
|
19
|
+
*
|
|
20
|
+
* @order 1
|
|
21
|
+
* @component
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* import { BookingForm } from '@wix/headless-bookings/react';
|
|
25
|
+
*
|
|
26
|
+
* // Minimal usage - uses default field components
|
|
27
|
+
* <BookingForm.Root formId="your-form-id" />
|
|
28
|
+
*
|
|
29
|
+
* // With custom styling
|
|
30
|
+
* <BookingForm.Root
|
|
31
|
+
* formId="your-form-id"
|
|
32
|
+
* className="my-form-container"
|
|
33
|
+
* rowGapClassname="gap-y-6"
|
|
34
|
+
* columnGapClassname="gap-x-4"
|
|
35
|
+
* />
|
|
36
|
+
*
|
|
37
|
+
* // With custom field overrides
|
|
38
|
+
* <BookingForm.Root
|
|
39
|
+
* formId="your-form-id"
|
|
40
|
+
* fieldMap={{ TEXT_INPUT: MyCustomTextInput }}
|
|
41
|
+
* />
|
|
42
|
+
*
|
|
43
|
+
* // With full control over form structure
|
|
44
|
+
* <BookingForm.Root formId="your-form-id">
|
|
45
|
+
* <Form.Loading className="flex justify-center p-4" />
|
|
46
|
+
* <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
|
|
47
|
+
* <BookingForm.Fields />
|
|
48
|
+
* <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
|
|
49
|
+
* <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
|
|
50
|
+
* </BookingForm.Root>
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export const Root = React.forwardRef((props, ref) => {
|
|
54
|
+
const { children, asChild, className, formId, additionalMetadata, fieldMap, rowGapClassname = 'gap-y-4', columnGapClassname = 'gap-x-2', ...otherProps } = props;
|
|
55
|
+
return (_jsx(CoreBookingForm.Root, { formId: formId, additionalMetadata: additionalMetadata, fieldMap: fieldMap, children: ({ fields }) => (_jsx(BookingFormContext.Provider, { value: { fields, rowGapClassname, columnGapClassname }, 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: 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" })] })) }) }) })) }));
|
|
56
|
+
});
|
|
57
|
+
Root.displayName = 'BookingForm.Root';
|
|
58
|
+
const BookingFormContext = React.createContext(null);
|
|
59
|
+
/**
|
|
60
|
+
* Hook to access BookingForm context
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
function useBookingFormContext() {
|
|
64
|
+
const context = React.useContext(BookingFormContext);
|
|
65
|
+
if (!context) {
|
|
66
|
+
throw new Error('useBookingFormContext must be used within a BookingForm.Root component');
|
|
67
|
+
}
|
|
68
|
+
return context;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Fields component that renders Form.Fields with the merged field map.
|
|
72
|
+
* Must be used within BookingForm.Root.
|
|
73
|
+
*
|
|
74
|
+
* @component
|
|
75
|
+
* @example
|
|
76
|
+
* ```tsx
|
|
77
|
+
* <BookingForm.Root formId="your-form-id">
|
|
78
|
+
* <Form.Loading className="p-4" />
|
|
79
|
+
* <BookingForm.Fields rowGapClassname="gap-y-6" />
|
|
80
|
+
* <Form.Error className="text-destructive" />
|
|
81
|
+
* </BookingForm.Root>
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export const Fields = React.forwardRef((props, ref) => {
|
|
85
|
+
const context = useBookingFormContext();
|
|
86
|
+
const { rowGapClassname = context.rowGapClassname, columnGapClassname = context.columnGapClassname, className, ...otherProps } = props;
|
|
87
|
+
return (_jsx("div", { ref: ref, className: className, "data-testid": TestIds.bookingFormFields, ...otherProps, children: _jsx(Form.Fields, { fieldMap: context.fields, rowGapClassname: rowGapClassname, columnGapClassname: columnGapClassname }) }));
|
|
88
|
+
});
|
|
89
|
+
Fields.displayName = 'BookingForm.Fields';
|
|
90
|
+
/**
|
|
91
|
+
* Data component that provides booking form data via render props.
|
|
92
|
+
* Use this when you need programmatic access to form state and actions.
|
|
93
|
+
*
|
|
94
|
+
* @component
|
|
95
|
+
* @example
|
|
96
|
+
* ```tsx
|
|
97
|
+
* <BookingForm.Data formId="your-form-id">
|
|
98
|
+
* {({ formId, fields, formValues, setFormSubmission, validateFormSubmission }) => (
|
|
99
|
+
* <div>
|
|
100
|
+
* <Form.Fields fieldMap={fields} rowGapClassname="gap-y-4" columnGapClassname="gap-x-2" />
|
|
101
|
+
* <button onClick={async () => {
|
|
102
|
+
* const result = await validateFormSubmission();
|
|
103
|
+
* if (result.valid) {
|
|
104
|
+
* // Store current form values before submission
|
|
105
|
+
* setFormSubmission(formValues);
|
|
106
|
+
* }
|
|
107
|
+
* }}>
|
|
108
|
+
* Validate & Submit
|
|
109
|
+
* </button>
|
|
110
|
+
* </div>
|
|
111
|
+
* )}
|
|
112
|
+
* </BookingForm.Data>
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export function Data(props) {
|
|
116
|
+
const { formId, additionalMetadata, fieldMap, children } = props;
|
|
117
|
+
return (_jsx(CoreBookingForm.Root, { formId: formId, additionalMetadata: additionalMetadata, fieldMap: fieldMap, children: ({ formId: id, fields, formValues, formSubmission, setFormSubmission, validateFormSubmission, }) => children({
|
|
118
|
+
formId: id,
|
|
119
|
+
fields,
|
|
120
|
+
formValues,
|
|
121
|
+
formSubmission,
|
|
122
|
+
setFormSubmission,
|
|
123
|
+
validateFormSubmission,
|
|
124
|
+
}) }));
|
|
125
|
+
}
|
|
126
|
+
Data.displayName = 'BookingForm.Data';
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core BookingForm Component
|
|
3
|
+
*
|
|
4
|
+
* Provides low-level access to booking form functionality via render props.
|
|
5
|
+
* Wraps CoreForm.Root and provides merged field map with booking defaults.
|
|
6
|
+
*
|
|
7
|
+
* Supports two modes:
|
|
8
|
+
* 1. Client-side: Provide formId to load the form dynamically
|
|
9
|
+
* 2. SSR/SSG: Provide a pre-loaded form object for server-side rendering
|
|
10
|
+
*/
|
|
11
|
+
import * as React from 'react';
|
|
12
|
+
import type { FormValues } from '@wix/form-public';
|
|
13
|
+
import { forms } from '@wix/forms';
|
|
14
|
+
import { type ValidationResult } from '../../../services/booking-form/booking-form.js';
|
|
15
|
+
import { Form } from '@wix/headless-forms/react';
|
|
16
|
+
type FieldMap = Parameters<typeof Form.Fields>[0]['fieldMap'];
|
|
17
|
+
/**
|
|
18
|
+
* Render props data provided to BookingForm children
|
|
19
|
+
*/
|
|
20
|
+
export interface BookingFormRenderProps {
|
|
21
|
+
/** The form ID being used */
|
|
22
|
+
formId: string;
|
|
23
|
+
/** Current form values from FormService (live state of form fields) */
|
|
24
|
+
formValues: FormValues;
|
|
25
|
+
/** Current form submission data (values stored before submission) */
|
|
26
|
+
formSubmission: FormValues | null;
|
|
27
|
+
/** Action to store form submission data */
|
|
28
|
+
setFormSubmission: (formValues: FormValues) => void;
|
|
29
|
+
/** Action to validate the form */
|
|
30
|
+
validateFormSubmission: () => Promise<ValidationResult>;
|
|
31
|
+
/** Merged field map (DEFAULT_BOOKING_FIELD_MAP + user overrides) */
|
|
32
|
+
fields: FieldMap;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Base props shared by all BookingForm config patterns
|
|
36
|
+
*/
|
|
37
|
+
interface BookingFormBaseProps {
|
|
38
|
+
/** Optional additional metadata to pass to FormService */
|
|
39
|
+
additionalMetadata?: Record<string, string | string[]>;
|
|
40
|
+
/** Optional partial field map to override default booking field components */
|
|
41
|
+
fieldMap?: Partial<FieldMap>;
|
|
42
|
+
/** Render prop function that receives form data and actions */
|
|
43
|
+
children: (data: BookingFormRenderProps) => React.ReactNode;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Props for BookingForm with formId (client-side loading)
|
|
47
|
+
*/
|
|
48
|
+
interface BookingFormWithFormIdProps extends BookingFormBaseProps {
|
|
49
|
+
/** The form ID to load (client-side loading) */
|
|
50
|
+
formId: string;
|
|
51
|
+
/** Pre-loaded form - not used in this mode */
|
|
52
|
+
form?: never;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Props for BookingForm with pre-loaded form (SSR/SSG)
|
|
56
|
+
*/
|
|
57
|
+
interface BookingFormWithFormProps extends BookingFormBaseProps {
|
|
58
|
+
/** Pre-loaded form object (SSR/SSG) - must be from bookings namespace */
|
|
59
|
+
form: forms.Form;
|
|
60
|
+
/** Form ID - not used in this mode (extracted from form) */
|
|
61
|
+
formId?: never;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Props for BookingForm render prop component.
|
|
65
|
+
* Supports two patterns:
|
|
66
|
+
* 1. Client-side: Provide formId to load form dynamically
|
|
67
|
+
* 2. SSR/SSG: Provide pre-loaded form object
|
|
68
|
+
*/
|
|
69
|
+
export type BookingFormProps = BookingFormWithFormIdProps | BookingFormWithFormProps;
|
|
70
|
+
/**
|
|
71
|
+
* Core component that provides booking form data via render props.
|
|
72
|
+
* Uses BookingFormService to manage form submission state.
|
|
73
|
+
*
|
|
74
|
+
* Supports two modes:
|
|
75
|
+
* 1. Client-side: Provide formId to load the form dynamically
|
|
76
|
+
* 2. SSR/SSG: Provide a pre-loaded form object
|
|
77
|
+
*
|
|
78
|
+
* This component:
|
|
79
|
+
* - Sets up both FormService and BookingFormService in the same context
|
|
80
|
+
* - BookingFormService internally accesses FormService for formValues
|
|
81
|
+
* - Merges user's fieldMap with DEFAULT_BOOKING_FIELD_MAP
|
|
82
|
+
* - Provides form data and actions via render props
|
|
83
|
+
* - Form.* components (Form.Fields, Form.Loading, etc.) work because FormService is in context
|
|
84
|
+
*
|
|
85
|
+
* @component
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* // Pattern 1: Client-side loading
|
|
89
|
+
* import * as CoreBookingForm from '@wix/headless-bookings/react/core/booking-form';
|
|
90
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
91
|
+
*
|
|
92
|
+
* <CoreBookingForm.Root formId="form-123">
|
|
93
|
+
* {({ formId, setFormSubmission, fields }) => (
|
|
94
|
+
* <Form.Fields
|
|
95
|
+
* fieldMap={fields}
|
|
96
|
+
* rowGapClassname="gap-y-4"
|
|
97
|
+
* columnGapClassname="gap-x-2"
|
|
98
|
+
* />
|
|
99
|
+
* )}
|
|
100
|
+
* </CoreBookingForm.Root>
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```tsx
|
|
105
|
+
* // Pattern 2: SSR with pre-loaded form
|
|
106
|
+
* import * as CoreBookingForm from '@wix/headless-bookings/react/core/booking-form';
|
|
107
|
+
* import { loadBookingFormConfig } from '@wix/headless-bookings/services';
|
|
108
|
+
*
|
|
109
|
+
* // Server-side: Load form
|
|
110
|
+
* const { form } = await loadBookingFormConfig('form-123');
|
|
111
|
+
*
|
|
112
|
+
* // Client-side: Render with pre-loaded form
|
|
113
|
+
* <CoreBookingForm.Root form={form}>
|
|
114
|
+
* {({ formId, setFormSubmission, fields }) => (
|
|
115
|
+
* <Form.Fields fieldMap={fields} />
|
|
116
|
+
* )}
|
|
117
|
+
* </CoreBookingForm.Root>
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```tsx
|
|
122
|
+
* // With custom field overrides and saving form values
|
|
123
|
+
* <CoreBookingForm.Root
|
|
124
|
+
* formId="form-123"
|
|
125
|
+
* fieldMap={{ TEXT_INPUT: MyCustomTextInput }}
|
|
126
|
+
* >
|
|
127
|
+
* {({ fields, formValues, setFormSubmission }) => (
|
|
128
|
+
* <>
|
|
129
|
+
* <Form.Fields fieldMap={fields} rowGapClassname="gap-y-4" columnGapClassname="gap-x-2" />
|
|
130
|
+
* <button onClick={() => setFormSubmission(formValues)}>
|
|
131
|
+
* Save
|
|
132
|
+
* </button>
|
|
133
|
+
* </>
|
|
134
|
+
* )}
|
|
135
|
+
* </CoreBookingForm.Root>
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export declare function Root(props: BookingFormProps): React.ReactNode;
|
|
139
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { WixServices, useService } from '@wix/services-manager-react';
|
|
4
|
+
import { createServicesMap } from '@wix/services-manager';
|
|
5
|
+
import { BookingFormServiceDefinition, BookingFormService, BOOKING_FORM_NAMESPACE, } from '../../../services/booking-form/booking-form.js';
|
|
6
|
+
import { FormServiceDefinition, FormService, } from '@wix/headless-forms/services';
|
|
7
|
+
// TODO: remove this when form will be updated to support optional field map
|
|
8
|
+
const DEFAULT_BOOKING_FIELD_MAP = {
|
|
9
|
+
TEXT_INPUT: () => null,
|
|
10
|
+
TEXT_AREA: () => null,
|
|
11
|
+
PHONE_INPUT: () => null,
|
|
12
|
+
EMAIL_INPUT: () => null,
|
|
13
|
+
NUMBER_INPUT: () => null,
|
|
14
|
+
CHECKBOX: () => null,
|
|
15
|
+
DROPDOWN: () => null,
|
|
16
|
+
DATE_PICKER: () => null,
|
|
17
|
+
SUBMIT_BUTTON: () => null,
|
|
18
|
+
RADIO_GROUP: () => null,
|
|
19
|
+
CHECKBOX_GROUP: () => null,
|
|
20
|
+
DATE_INPUT: () => null,
|
|
21
|
+
DATE_TIME_INPUT: () => null,
|
|
22
|
+
TIME_INPUT: () => null,
|
|
23
|
+
FILE_UPLOAD: () => null,
|
|
24
|
+
SIGNATURE: () => null,
|
|
25
|
+
TEXT: () => null,
|
|
26
|
+
MULTILINE_ADDRESS: () => null,
|
|
27
|
+
RATING_INPUT: () => null,
|
|
28
|
+
TAGS: () => null,
|
|
29
|
+
PRODUCT_LIST: () => null,
|
|
30
|
+
FIXED_PAYMENT: () => null,
|
|
31
|
+
PAYMENT_INPUT: () => null,
|
|
32
|
+
DONATION: () => null,
|
|
33
|
+
APPOINTMENT: () => null,
|
|
34
|
+
IMAGE_CHOICE: () => null,
|
|
35
|
+
};
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Component
|
|
38
|
+
// ============================================================================
|
|
39
|
+
/**
|
|
40
|
+
* Extracts the form ID from props (either directly or from form object)
|
|
41
|
+
*/
|
|
42
|
+
function extractFormIdFromProps(props) {
|
|
43
|
+
if ('formId' in props && props.formId) {
|
|
44
|
+
return props.formId;
|
|
45
|
+
}
|
|
46
|
+
if ('form' in props && props.form) {
|
|
47
|
+
const form = props.form;
|
|
48
|
+
const formId = form.id ||
|
|
49
|
+
form._id;
|
|
50
|
+
if (!formId) {
|
|
51
|
+
throw new Error('BookingForm: Could not extract form ID from provided form object.');
|
|
52
|
+
}
|
|
53
|
+
return formId;
|
|
54
|
+
}
|
|
55
|
+
throw new Error('BookingForm: Either formId or form must be provided.');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Core component that provides booking form data via render props.
|
|
59
|
+
* Uses BookingFormService to manage form submission state.
|
|
60
|
+
*
|
|
61
|
+
* Supports two modes:
|
|
62
|
+
* 1. Client-side: Provide formId to load the form dynamically
|
|
63
|
+
* 2. SSR/SSG: Provide a pre-loaded form object
|
|
64
|
+
*
|
|
65
|
+
* This component:
|
|
66
|
+
* - Sets up both FormService and BookingFormService in the same context
|
|
67
|
+
* - BookingFormService internally accesses FormService for formValues
|
|
68
|
+
* - Merges user's fieldMap with DEFAULT_BOOKING_FIELD_MAP
|
|
69
|
+
* - Provides form data and actions via render props
|
|
70
|
+
* - Form.* components (Form.Fields, Form.Loading, etc.) work because FormService is in context
|
|
71
|
+
*
|
|
72
|
+
* @component
|
|
73
|
+
* @example
|
|
74
|
+
* ```tsx
|
|
75
|
+
* // Pattern 1: Client-side loading
|
|
76
|
+
* import * as CoreBookingForm from '@wix/headless-bookings/react/core/booking-form';
|
|
77
|
+
* import { Form } from '@wix/headless-forms/react';
|
|
78
|
+
*
|
|
79
|
+
* <CoreBookingForm.Root formId="form-123">
|
|
80
|
+
* {({ formId, setFormSubmission, fields }) => (
|
|
81
|
+
* <Form.Fields
|
|
82
|
+
* fieldMap={fields}
|
|
83
|
+
* rowGapClassname="gap-y-4"
|
|
84
|
+
* columnGapClassname="gap-x-2"
|
|
85
|
+
* />
|
|
86
|
+
* )}
|
|
87
|
+
* </CoreBookingForm.Root>
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```tsx
|
|
92
|
+
* // Pattern 2: SSR with pre-loaded form
|
|
93
|
+
* import * as CoreBookingForm from '@wix/headless-bookings/react/core/booking-form';
|
|
94
|
+
* import { loadBookingFormConfig } from '@wix/headless-bookings/services';
|
|
95
|
+
*
|
|
96
|
+
* // Server-side: Load form
|
|
97
|
+
* const { form } = await loadBookingFormConfig('form-123');
|
|
98
|
+
*
|
|
99
|
+
* // Client-side: Render with pre-loaded form
|
|
100
|
+
* <CoreBookingForm.Root form={form}>
|
|
101
|
+
* {({ formId, setFormSubmission, fields }) => (
|
|
102
|
+
* <Form.Fields fieldMap={fields} />
|
|
103
|
+
* )}
|
|
104
|
+
* </CoreBookingForm.Root>
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* // With custom field overrides and saving form values
|
|
110
|
+
* <CoreBookingForm.Root
|
|
111
|
+
* formId="form-123"
|
|
112
|
+
* fieldMap={{ TEXT_INPUT: MyCustomTextInput }}
|
|
113
|
+
* >
|
|
114
|
+
* {({ fields, formValues, setFormSubmission }) => (
|
|
115
|
+
* <>
|
|
116
|
+
* <Form.Fields fieldMap={fields} rowGapClassname="gap-y-4" columnGapClassname="gap-x-2" />
|
|
117
|
+
* <button onClick={() => setFormSubmission(formValues)}>
|
|
118
|
+
* Save
|
|
119
|
+
* </button>
|
|
120
|
+
* </>
|
|
121
|
+
* )}
|
|
122
|
+
* </CoreBookingForm.Root>
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export function Root(props) {
|
|
126
|
+
const { additionalMetadata, fieldMap: userFieldMap, children } = props;
|
|
127
|
+
// Determine if we have a pre-loaded form
|
|
128
|
+
const hasPreloadedForm = 'form' in props && props.form;
|
|
129
|
+
const preloadedForm = hasPreloadedForm ? props.form : null;
|
|
130
|
+
// Extract formId from props
|
|
131
|
+
const formId = extractFormIdFromProps(props);
|
|
132
|
+
// Merge user's partial field map with booking defaults
|
|
133
|
+
const fields = useMemo(() => ({
|
|
134
|
+
// ...DEFAULT_BOOKING_FIELD_MAP, // TODO: Add default field map
|
|
135
|
+
...userFieldMap,
|
|
136
|
+
}), [userFieldMap]);
|
|
137
|
+
// Build formServiceConfig for FormService
|
|
138
|
+
// If we have a pre-loaded form, pass it directly; otherwise pass formId
|
|
139
|
+
const formServiceConfig = useMemo(() => {
|
|
140
|
+
if (preloadedForm) {
|
|
141
|
+
return { form: preloadedForm };
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
formId,
|
|
145
|
+
namespace: BOOKING_FORM_NAMESPACE,
|
|
146
|
+
...(additionalMetadata && { additionalMetadata }),
|
|
147
|
+
};
|
|
148
|
+
}, [formId, additionalMetadata, preloadedForm]);
|
|
149
|
+
// Build bookingFormServiceConfig
|
|
150
|
+
const bookingFormServiceConfig = useMemo(() => {
|
|
151
|
+
if (preloadedForm) {
|
|
152
|
+
return { form: preloadedForm };
|
|
153
|
+
}
|
|
154
|
+
return { formId };
|
|
155
|
+
}, [formId, preloadedForm]);
|
|
156
|
+
// Both FormService and BookingFormService are added to the same WixServices context
|
|
157
|
+
// This allows BookingFormService to access FormService via getService internally
|
|
158
|
+
return (_jsx(WixServices, { servicesMap: createServicesMap()
|
|
159
|
+
.addService(FormServiceDefinition, FormService, formServiceConfig)
|
|
160
|
+
.addService(BookingFormServiceDefinition, BookingFormService, bookingFormServiceConfig), children: _jsx(BookingFormContent, { formId: formId, fields: { ...DEFAULT_BOOKING_FIELD_MAP, ...fields }, children: children }) }));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Internal component that consumes BookingFormService and provides render props.
|
|
164
|
+
* Separated to ensure useService is called within the WixServices context.
|
|
165
|
+
*
|
|
166
|
+
* Note: BookingFormService internally accesses FormService to get formValues,
|
|
167
|
+
* so we only need to consume BookingFormService here.
|
|
168
|
+
*/
|
|
169
|
+
function BookingFormContent({ formId, fields, children, }) {
|
|
170
|
+
const bookingFormService = useService(BookingFormServiceDefinition);
|
|
171
|
+
const formSubmission = bookingFormService.formSubmission.get();
|
|
172
|
+
const formValues = bookingFormService.formValues.get();
|
|
173
|
+
return children({
|
|
174
|
+
formId,
|
|
175
|
+
formValues,
|
|
176
|
+
formSubmission,
|
|
177
|
+
setFormSubmission: bookingFormService.setFormSubmission,
|
|
178
|
+
validateFormSubmission: bookingFormService.validateFormSubmission,
|
|
179
|
+
fields,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * as Booking from './booking/Booking.js';
|
|
2
|
+
export * as BookingForm from './booking-form/BookingForm.js';
|
|
2
3
|
export * as Service from './service/Service.js';
|
|
3
4
|
export * as TimeSlotList from './time-slot-list/TimeSlotList.js';
|
|
4
5
|
export * as ServiceList from './service-list/ServiceList.js';
|
package/cjs/dist/react/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * as Booking from './booking/Booking.js';
|
|
2
|
+
export * as BookingForm from './booking-form/BookingForm.js';
|
|
2
3
|
export * as Service from './service/Service.js';
|
|
3
4
|
export * as TimeSlotList from './time-slot-list/TimeSlotList.js';
|
|
4
5
|
export * as ServiceList from './service-list/ServiceList.js';
|