@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.
@@ -7,24 +7,14 @@
7
7
  *
8
8
  * When using a pre-loaded form, it must belong to the bookings namespace.
9
9
  */
10
- import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
11
- import type { FormValues } from '@wix/form-public';
10
+ import { type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
12
11
  import { forms } from '@wix/forms';
12
+ import { FormServiceConfig } from '@wix/headless-forms/services';
13
13
  /**
14
14
  * The required namespace for booking forms.
15
15
  * All booking forms must belong to this namespace.
16
16
  */
17
17
  export declare const BOOKING_FORM_NAMESPACE = "wix.bookings.v2.bookings";
18
- /**
19
- * Validation result type for form validation.
20
- * Fields with errors will be marked according to the validation failures.
21
- */
22
- export interface ValidationResult {
23
- /** Whether the form is valid */
24
- valid: boolean;
25
- /** Array of validation failure messages */
26
- validationFailures: string[];
27
- }
28
18
  /**
29
19
  * Error thrown when BookingFormService cannot be initialized due to missing configuration.
30
20
  * This occurs when:
@@ -61,7 +51,9 @@ export declare class BookingFormConfigurationError extends Error {
61
51
  export type BookingFormServiceConfig = {
62
52
  /** The form ID to load (client-side loading) */
63
53
  formId: string;
64
- /** Optional list of service IDs associated with this booking form */
54
+ /** Optional additional metadata to pass to FormService */
55
+ additionalMetadata?: Record<string, string | string[]>;
56
+ /** Optional list of service IDs to pass to FormService */
65
57
  serviceIds?: string[];
66
58
  } | {
67
59
  /** Pre-loaded form object (SSR/SSG) - must be from bookings namespace */
@@ -76,28 +68,11 @@ export type BookingFormServiceConfig = {
76
68
  * - SSR/SSG: Pre-loaded form is provided via config
77
69
  */
78
70
  export interface BookingFormServiceAPI {
79
- /** Signal containing the current form values from FormService (live state of form fields) */
80
- formValues: ReadOnlySignal<FormValues>;
81
- /** Signal containing form submission data (set before actual submission) */
82
- formSubmission: Signal<FormValues | null>;
83
- /** Submitted form ID (won't be supported on first phase) */
84
- formSubmissionId: ReadOnlySignal<string | null>;
85
- /** Signal containing the form ID (from config or extracted from form) */
86
- formId: ReadOnlySignal<string>;
87
- /** Signal containing the pre-loaded form (if provided via SSR) */
88
- form: ReadOnlySignal<forms.Form | null>;
89
- /** Action to store form submission data */
90
- setFormSubmission: (formValues: FormValues) => void;
91
- /** Action to validate form - internally delegates to Form.Fields ref */
92
- validateFormSubmission: () => Promise<ValidationResult>;
93
- }
94
- /**
95
- * Internal API interface that extends public API.
96
- * Contains methods for internal use by core components.
97
- */
98
- export interface BookingFormServiceInternalAPI extends BookingFormServiceAPI {
99
- /** Internal: Register validate callback (called by core component) */
100
- _setValidateCallback: (callback: () => Promise<ValidationResult>) => void;
71
+ /**
72
+ * Signal containing the form service config.
73
+ * Contains either a pre-loaded form (SSR/SSG) or formId with metadata (client-side).
74
+ */
75
+ formServiceConfig: ReadOnlySignal<FormServiceConfig | null>;
101
76
  }
102
77
  export { hasFormId, hasForm, extractFormIdFromForm, extractFormIdFromConfig, type FormIdConfig, } from './utils.js';
103
78
  /**
@@ -117,8 +92,16 @@ export declare const BookingFormServiceDefinition: string & {
117
92
  * 1. Client-side: formId is provided, form is loaded dynamically
118
93
  * 2. SSR/SSG: Pre-loaded form is provided (must be from bookings namespace)
119
94
  *
120
- * NOTE: The Form.Fields ref is managed internally - the core component passes
121
- * a validate callback that the service stores and calls when validateFormSubmission() is invoked.
95
+ * Initialization:
96
+ * - Requires either a config (formId or form) OR BookingService with selected services
97
+ * - If config is not provided, formId is extracted from selected services
98
+ * - For multiple services, uses DEFAULT_FORM_ID; for single service, uses service's form ID
99
+ * - Service IDs are extracted from config.serviceIds or from selected services
100
+ *
101
+ * Validation:
102
+ * - The Form.Fields core component registers a validation callback via setValidateCallback
103
+ * - The validateFormSubmission() method calls this registered callback
104
+ * - The callback may be null initially until the core component registers it
122
105
  *
123
106
  * @example
124
107
  * ```tsx
@@ -127,17 +110,27 @@ export declare const BookingFormServiceDefinition: string & {
127
110
  * BookingFormServiceDefinition.withConfig({ formId: 'form-123' })
128
111
  * );
129
112
  *
113
+ * // Pattern 1b: Client-side loading with service IDs
114
+ * const bookingFormService = useService(
115
+ * BookingFormServiceDefinition.withConfig({
116
+ * formId: 'form-123',
117
+ * serviceIds: ['service-1', 'service-2']
118
+ * })
119
+ * );
120
+ *
130
121
  * // Pattern 2: SSR with pre-loaded form
131
122
  * const bookingFormService = useService(
132
123
  * BookingFormServiceDefinition.withConfig({ form: preloadedForm })
133
124
  * );
134
125
  *
135
- * // Store form submission
136
- * bookingFormService.setFormSubmission(formValues);
126
+ * // Pattern 3: No config - formId extracted from BookingService
127
+ * const bookingFormService = useService(
128
+ * BookingFormServiceDefinition.withConfig(undefined)
129
+ * );
137
130
  *
138
- * // Validate form
139
- * const result = await bookingFormService.validateFormSubmission();
140
- * if (result.valid) {
131
+ * // Validate form (callback must be registered by core component first)
132
+ * const result = await bookingFormService.validateFormSubmission?.();
133
+ * if (result?.valid) {
141
134
  * // Proceed with booking
142
135
  * }
143
136
  * ```
@@ -9,9 +9,8 @@
9
9
  */
10
10
  import { defineService, implementService } from '@wix/services-definitions';
11
11
  import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
12
- import { FormServiceDefinition } from '@wix/headless-forms/services';
13
12
  import { BookingServiceDefinition, } from '../booking/booking.js';
14
- import { hasForm, extractFormIdFromConfig } from './utils.js';
13
+ import { hasForm, extractFormIdFromConfig, hasFormId } from './utils.js';
15
14
  // ============================================================================
16
15
  // Constants
17
16
  // ============================================================================
@@ -20,6 +19,7 @@ import { hasForm, extractFormIdFromConfig } from './utils.js';
20
19
  * All booking forms must belong to this namespace.
21
20
  */
22
21
  export const BOOKING_FORM_NAMESPACE = 'wix.bookings.v2.bookings';
22
+ const DEFAULT_FORM_ID = '00000000-0000-0000-0000-000000000000';
23
23
  /**
24
24
  * Error thrown when BookingFormService cannot be initialized due to missing configuration.
25
25
  * This occurs when:
@@ -54,32 +54,51 @@ export const BookingFormServiceDefinition = defineService('bookingForm');
54
54
  // ============================================================================
55
55
  /**
56
56
  * Validates that a form belongs to the bookings namespace.
57
- * @param form The form to validate
57
+ * @param config The service config that may contain a pre-loaded form
58
58
  * @throws Error if the form is not from the bookings namespace
59
59
  */
60
60
  function validateBookingFormNamespace(form) {
61
- // Check if form has namespace property and validate it
62
- // The namespace might be in different locations depending on the form structure
63
- const formNamespace = form.namespace ||
64
- form._namespace;
61
+ const formNamespace = form.namespace;
65
62
  if (formNamespace && formNamespace !== BOOKING_FORM_NAMESPACE) {
66
63
  throw new Error(`BookingFormService: Invalid form namespace. ` +
67
64
  `Expected "${BOOKING_FORM_NAMESPACE}", but received "${formNamespace}". ` +
68
65
  `Ensure the form is a booking form.`);
69
66
  }
70
67
  }
68
+ function validateBookingFormConfig({ config, services, }) {
69
+ if (!config) {
70
+ return;
71
+ }
72
+ if (hasForm(config)) {
73
+ validateBookingFormNamespace(config.form);
74
+ const servicesFormId = services.length > 1 ? DEFAULT_FORM_ID : services?.[0]?.form?._id;
75
+ if (servicesFormId !== config.form._id) {
76
+ throw new Error(`BookingFormService: Invalid form ID. ` +
77
+ `Expected "${config.form._id}", but received "${servicesFormId}". ` +
78
+ `Ensure the form is the same as the selected services form.`);
79
+ }
80
+ }
81
+ }
71
82
  /**
72
83
  * Extracts the form ID from either config type or from services.
84
+ *
85
+ * Extraction priority:
86
+ * 1. From config (if formId is provided directly or extracted from pre-loaded form)
87
+ * 2. From services:
88
+ * - If multiple services: use DEFAULT_FORM_ID
89
+ * - If single service: use the service's form ID
90
+ *
73
91
  * @param config The service config (optional)
74
92
  * @param services The list of services to extract formId from if config is not provided
75
93
  * @returns The form ID
94
+ * @throws BookingFormConfigurationError if no formId can be extracted
76
95
  */
77
96
  function extractFormId(config, services) {
78
97
  // Try to extract from config first
79
98
  let formId = extractFormIdFromConfig(config);
80
99
  // Fallback to extracting from services
81
100
  if (!formId) {
82
- formId = services.find((s) => s.form?._id)?.form?._id;
101
+ formId = services.length > 1 ? DEFAULT_FORM_ID : services?.[0]?.form?._id;
83
102
  }
84
103
  if (!formId) {
85
104
  throw new BookingFormConfigurationError('BookingFormService: no formId found in config or services. ' +
@@ -87,6 +106,23 @@ function extractFormId(config, services) {
87
106
  }
88
107
  return formId;
89
108
  }
109
+ /**
110
+ * Extracts service IDs from config or services.
111
+ *
112
+ * Extraction priority:
113
+ * 1. From config.serviceIds if config has formId and serviceIds are provided
114
+ * 2. From services array (extract _id from each service)
115
+ *
116
+ * @param config The service config (optional)
117
+ * @param services The list of services to extract IDs from if config doesn't provide them
118
+ * @returns Array of service IDs
119
+ */
120
+ function getServiceIds(config, services) {
121
+ if (hasFormId(config) && config.serviceIds) {
122
+ return config.serviceIds;
123
+ }
124
+ return services.map((s) => s._id).filter(Boolean);
125
+ }
90
126
  // ============================================================================
91
127
  // Service Implementation
92
128
  // ============================================================================
@@ -98,8 +134,16 @@ function extractFormId(config, services) {
98
134
  * 1. Client-side: formId is provided, form is loaded dynamically
99
135
  * 2. SSR/SSG: Pre-loaded form is provided (must be from bookings namespace)
100
136
  *
101
- * NOTE: The Form.Fields ref is managed internally - the core component passes
102
- * a validate callback that the service stores and calls when validateFormSubmission() is invoked.
137
+ * Initialization:
138
+ * - Requires either a config (formId or form) OR BookingService with selected services
139
+ * - If config is not provided, formId is extracted from selected services
140
+ * - For multiple services, uses DEFAULT_FORM_ID; for single service, uses service's form ID
141
+ * - Service IDs are extracted from config.serviceIds or from selected services
142
+ *
143
+ * Validation:
144
+ * - The Form.Fields core component registers a validation callback via setValidateCallback
145
+ * - The validateFormSubmission() method calls this registered callback
146
+ * - The callback may be null initially until the core component registers it
103
147
  *
104
148
  * @example
105
149
  * ```tsx
@@ -108,24 +152,33 @@ function extractFormId(config, services) {
108
152
  * BookingFormServiceDefinition.withConfig({ formId: 'form-123' })
109
153
  * );
110
154
  *
155
+ * // Pattern 1b: Client-side loading with service IDs
156
+ * const bookingFormService = useService(
157
+ * BookingFormServiceDefinition.withConfig({
158
+ * formId: 'form-123',
159
+ * serviceIds: ['service-1', 'service-2']
160
+ * })
161
+ * );
162
+ *
111
163
  * // Pattern 2: SSR with pre-loaded form
112
164
  * const bookingFormService = useService(
113
165
  * BookingFormServiceDefinition.withConfig({ form: preloadedForm })
114
166
  * );
115
167
  *
116
- * // Store form submission
117
- * bookingFormService.setFormSubmission(formValues);
168
+ * // Pattern 3: No config - formId extracted from BookingService
169
+ * const bookingFormService = useService(
170
+ * BookingFormServiceDefinition.withConfig(undefined)
171
+ * );
118
172
  *
119
- * // Validate form
120
- * const result = await bookingFormService.validateFormSubmission();
121
- * if (result.valid) {
173
+ * // Validate form (callback must be registered by core component first)
174
+ * const result = await bookingFormService.validateFormSubmission?.();
175
+ * if (result?.valid) {
122
176
  * // Proceed with booking
123
177
  * }
124
178
  * ```
125
179
  */
126
180
  export const BookingFormService = implementService.withConfig()(BookingFormServiceDefinition, ({ getService, config }) => {
127
181
  const signalsService = getService(SignalsServiceDefinition);
128
- const formService = getService(FormServiceDefinition);
129
182
  // BookingService is optional - may not be available if not wrapped with Booking.Root
130
183
  let bookingService = null;
131
184
  try {
@@ -135,6 +188,10 @@ export const BookingFormService = implementService.withConfig()(BookingFormServi
135
188
  // BookingService not available - that's ok if formId was provided explicitly
136
189
  bookingService = null;
137
190
  }
191
+ /**
192
+ * Helper to extract services from BookingService.
193
+ * Returns empty array if BookingService is not available.
194
+ */
138
195
  const getServices = () => {
139
196
  if (!bookingService) {
140
197
  return [];
@@ -150,64 +207,37 @@ export const BookingFormService = implementService.withConfig()(BookingFormServi
150
207
  if (!config && (!bookingService || !hasServiceSelections)) {
151
208
  throw new BookingFormConfigurationError();
152
209
  }
153
- // Determine if we have a pre-loaded form
154
- const hasPreloadedForm = hasForm(config);
155
- // If form is provided, validate namespace
156
- if (hasPreloadedForm) {
157
- validateBookingFormNamespace(config.form);
158
- }
159
- // Extract formId from config (either directly or from form object)
160
- const formIdValue = extractFormId(config, getServices());
161
- // Initialize formSubmission signal (stores form data to be submitted)
162
- const formSubmission = signalsService.signal(null);
163
- // Initialize submissionId signal (for future use)
164
- const formSubmissionId = signalsService.signal(null);
165
- // formId signal - readonly
166
- const formId = signalsService.signal(formIdValue);
167
- // form signal - contains pre-loaded form if provided
168
- const form = signalsService.signal(hasForm(config) ? config.form : null);
169
- // Internal: Store validate callback (set by core component, not exposed publicly)
170
- let _validateCallback = null;
171
- // Internal: Register validate callback (called by core component via closure)
172
- const _setValidateCallback = (callback) => {
173
- _validateCallback = callback;
174
- };
175
- // Action to set form submission data
176
- const setFormSubmission = (formValues) => {
177
- formSubmission.set(formValues);
178
- };
179
- // Action to validate form - delegates to internally registered callback
180
- const validateFormSubmission = async () => {
181
- if (!_validateCallback) {
182
- console.error('BookingFormService: Validate callback not registered. ' +
183
- 'Ensure BookingForm component is mounted.');
184
- return { valid: false, validationFailures: ['Form not initialized'] };
185
- }
186
- return _validateCallback();
187
- };
188
- // Auto-sync formValues to BookingService.formSubmission (client-side only)
189
- // This keeps Booking.Data.formSubmission updated as the user types
190
- // and ensures the book action gets the latest form values
191
- // Only set up sync if BookingService is available
192
- if (bookingService) {
193
- signalsService.effect(() => {
194
- const formValues = formService.formValuesSignal.get();
195
- if (formValues && Object.keys(formValues).length > 0) {
196
- bookingService.actions.setFormSubmission(formValues);
197
- }
210
+ const serviceSelections = getServices();
211
+ // Validate config
212
+ validateBookingFormConfig({ config, services: serviceSelections });
213
+ // Extract formId from config (either directly or from form object) or from services
214
+ const formIdValue = extractFormId(config, serviceSelections);
215
+ // Extract service IDs to pass to FormService (from config or services)
216
+ const serviceIds = getServiceIds(config, serviceSelections);
217
+ /**
218
+ * Form service config signal.
219
+ *
220
+ * If pre-loaded form is provided (SSR/SSG):
221
+ * - Creates config with the form object
222
+ *
223
+ * Otherwise (client-side loading):
224
+ * - Creates config with formId, namespace, and metadata
225
+ * - Includes serviceIds in additionalMetadata for FormService
226
+ */
227
+ const formServiceConfig = signalsService.signal(hasForm(config)
228
+ ? { form: config.form }
229
+ : {
230
+ formId: formIdValue,
231
+ namespace: BOOKING_FORM_NAMESPACE,
232
+ additionalMetadata: {
233
+ ...config?.additionalMetadata,
234
+ serviceIds,
235
+ },
198
236
  });
199
- }
200
- // Public API (what's exposed in BookingFormServiceAPI interface)
201
- const publicApi = {
202
- formValues: formService.formValuesSignal,
203
- formSubmission,
204
- formSubmissionId,
205
- formId,
206
- form,
207
- setFormSubmission,
208
- validateFormSubmission,
209
- // Internal method for core component access
210
- _setValidateCallback,
237
+ /**
238
+ * Public API implementation.
239
+ */
240
+ return {
241
+ formServiceConfig,
211
242
  };
212
- return publicApi;
213
243
  });
@@ -8,7 +8,7 @@ export { TimeSlotListService, TimeSlotListServiceDefinition, TimeSlotService, Ti
8
8
  export { ServiceListService, ServiceListServiceDefinition, type ServiceListServiceAPI, type ServiceListServiceConfig, type ServiceListActions, type QueryOptions, loadServiceListServiceInitialData, type SuccessServiceListServiceConfigResult, type ErrorServiceListServiceConfigResult, } from './service-list/service-list.js';
9
9
  export { SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from '../api/query-services/index.js';
10
10
  export { executeBookAction, canBook, buildBookingRequest, buildCheckoutRequest, type CanBookParams, type BookResult, type BookingError, type BookActionParams, type BookChildProps, type BookProps, type BuildCheckoutParams, } from './booking/book-action/index.js';
11
- export { BookingFormService, BookingFormServiceDefinition, type BookingFormServiceAPI, type BookingFormServiceConfig, type BookingFormServiceInternalAPI, type ValidationResult, } from './booking-form/booking-form.js';
11
+ export { BookingFormService, BookingFormServiceDefinition, type BookingFormServiceAPI, type BookingFormServiceConfig, } from './booking-form/booking-form.js';
12
12
  export { PaymentService, PaymentServiceDefinition, type PaymentServiceAPI, type PaymentServiceConfig, type PaymentDetails, type PricingServiceSelection, type LineItemAdditionalInfo, loadPaymentConfig, type SuccessPaymentConfigResult, type ErrorPaymentConfigResult, } from './payment/payment.js';
13
13
  export { LocationListService, LocationListServiceDefinition, LocationType, loadLocationListServiceInitialData, locationListServiceBinding, type LocationListServiceAPI, type LocationListServiceConfig, type LocationListActions, type Location, type LoadLocationListServiceResult, getLocationById, } from './location-list/location-list.js';
14
14
  export { StaffMemberListService, StaffMemberListServiceDefinition, loadStaffMemberListServiceInitialData, staffMemberListServiceBinding, type StaffMemberListServiceAPI, type StaffMemberListServiceConfig, type StaffMemberListActions, type StaffMemberData, type LoadStaffMemberListServiceResult, } from './staff-member-list/staff-member-list.js';
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import React from 'react';
6
6
  import { Form } from '@wix/headless-forms/react';
7
- import * as CoreBookingForm from '../core/booking-form/BookingForm.js';
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
- /** Child components - if not provided, Form.Fields is rendered with default field map */
27
- children?: React.ReactNode;
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
  */