@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.
@@ -0,0 +1,140 @@
1
+ /**
2
+ * BookingForm Service - Manages form submission state for booking flows
3
+ *
4
+ * This service supports two modes:
5
+ * 1. Client-side: Provide formId to load the form dynamically
6
+ * 2. SSR/SSG: Provide a pre-loaded form object for server-side rendering
7
+ *
8
+ * When using a pre-loaded form, it must belong to the bookings namespace.
9
+ */
10
+ import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
11
+ import type { FormValues } from '@wix/form-public';
12
+ import { forms } from '@wix/forms';
13
+ /**
14
+ * The required namespace for booking forms.
15
+ * All booking forms must belong to this namespace.
16
+ */
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
+ /**
29
+ * Configuration for BookingFormService.
30
+ * Supports two modes:
31
+ * 1. Client-side loading: Provide formId to fetch the form dynamically
32
+ * 2. SSR/SSG: Provide a pre-loaded form object (must be from bookings namespace)
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * // Pattern 1: Client-side loading
37
+ * const config = { formId: 'form-123' };
38
+ *
39
+ * // Pattern 2: Pre-loaded form (SSR/SSG)
40
+ * const config = { form: preloadedForm };
41
+ * ```
42
+ */
43
+ export type BookingFormServiceConfig = {
44
+ /** The form ID to load (client-side loading) */
45
+ formId: string;
46
+ } | {
47
+ /** Pre-loaded form object (SSR/SSG) - must be from bookings namespace */
48
+ form: forms.Form;
49
+ };
50
+ /**
51
+ * API interface for the BookingForm service.
52
+ * Manages form submission state and validation for booking forms.
53
+ *
54
+ * Supports two modes:
55
+ * - Client-side: formId is provided via config, form is loaded dynamically
56
+ * - SSR/SSG: Pre-loaded form is provided via config
57
+ */
58
+ export interface BookingFormServiceAPI {
59
+ /** Signal containing the current form values from FormService (live state of form fields) */
60
+ formValues: ReadOnlySignal<FormValues>;
61
+ /** Signal containing form submission data (set before actual submission) */
62
+ formSubmission: Signal<FormValues | null>;
63
+ /** Submitted form ID (won't be supported on first phase) */
64
+ formSubmissionId: ReadOnlySignal<string | null>;
65
+ /** Signal containing the form ID (from config or extracted from form) */
66
+ formId: ReadOnlySignal<string>;
67
+ /** Signal containing the pre-loaded form (if provided via SSR) */
68
+ form: ReadOnlySignal<forms.Form | null>;
69
+ /** Action to store form submission data */
70
+ setFormSubmission: (formValues: FormValues) => void;
71
+ /** Action to validate form - internally delegates to Form.Fields ref */
72
+ validateFormSubmission: () => Promise<ValidationResult>;
73
+ }
74
+ /**
75
+ * Internal API interface that extends public API.
76
+ * Contains methods for internal use by core components.
77
+ */
78
+ export interface BookingFormServiceInternalAPI extends BookingFormServiceAPI {
79
+ /** Internal: Register validate callback (called by core component) */
80
+ _setValidateCallback: (callback: () => Promise<ValidationResult>) => void;
81
+ }
82
+ /**
83
+ * Type guard to check if config has formId
84
+ */
85
+ export declare function hasFormId(config: BookingFormServiceConfig): config is {
86
+ formId: string;
87
+ };
88
+ /**
89
+ * Type guard to check if config has pre-loaded form
90
+ */
91
+ export declare function hasForm(config: BookingFormServiceConfig): config is {
92
+ form: forms.Form;
93
+ };
94
+ /**
95
+ * Service definition for BookingForm.
96
+ * Defines the contract that the BookingFormService must implement.
97
+ */
98
+ export declare const BookingFormServiceDefinition: string & {
99
+ __api: BookingFormServiceAPI;
100
+ __config: {};
101
+ isServiceDefinition?: boolean;
102
+ } & BookingFormServiceAPI;
103
+ /**
104
+ * Implementation of BookingFormService.
105
+ *
106
+ * This service manages form submission state for booking flows.
107
+ * Supports two modes:
108
+ * 1. Client-side: formId is provided, form is loaded dynamically
109
+ * 2. SSR/SSG: Pre-loaded form is provided (must be from bookings namespace)
110
+ *
111
+ * NOTE: The Form.Fields ref is managed internally - the core component passes
112
+ * a validate callback that the service stores and calls when validateFormSubmission() is invoked.
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * // Pattern 1: Client-side loading
117
+ * const bookingFormService = useService(
118
+ * BookingFormServiceDefinition.withConfig({ formId: 'form-123' })
119
+ * );
120
+ *
121
+ * // Pattern 2: SSR with pre-loaded form
122
+ * const bookingFormService = useService(
123
+ * BookingFormServiceDefinition.withConfig({ form: preloadedForm })
124
+ * );
125
+ *
126
+ * // Store form submission
127
+ * bookingFormService.setFormSubmission(formValues);
128
+ *
129
+ * // Validate form
130
+ * const result = await bookingFormService.validateFormSubmission();
131
+ * if (result.valid) {
132
+ * // Proceed with booking
133
+ * }
134
+ * ```
135
+ */
136
+ export declare const BookingFormService: import("@wix/services-definitions").ServiceFactory<string & {
137
+ __api: BookingFormServiceAPI;
138
+ __config: {};
139
+ isServiceDefinition?: boolean;
140
+ } & BookingFormServiceAPI, BookingFormServiceConfig>;
@@ -0,0 +1,171 @@
1
+ /**
2
+ * BookingForm Service - Manages form submission state for booking flows
3
+ *
4
+ * This service supports two modes:
5
+ * 1. Client-side: Provide formId to load the form dynamically
6
+ * 2. SSR/SSG: Provide a pre-loaded form object for server-side rendering
7
+ *
8
+ * When using a pre-loaded form, it must belong to the bookings namespace.
9
+ */
10
+ import { defineService, implementService } from '@wix/services-definitions';
11
+ import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
12
+ import { FormServiceDefinition } from '@wix/headless-forms/services';
13
+ // ============================================================================
14
+ // Constants
15
+ // ============================================================================
16
+ /**
17
+ * The required namespace for booking forms.
18
+ * All booking forms must belong to this namespace.
19
+ */
20
+ export const BOOKING_FORM_NAMESPACE = 'wix.bookings.v2.bookings';
21
+ // ============================================================================
22
+ // Helper Types
23
+ // ============================================================================
24
+ /**
25
+ * Type guard to check if config has formId
26
+ */
27
+ export function hasFormId(config) {
28
+ return 'formId' in config;
29
+ }
30
+ /**
31
+ * Type guard to check if config has pre-loaded form
32
+ */
33
+ export function hasForm(config) {
34
+ return 'form' in config;
35
+ }
36
+ // ============================================================================
37
+ // Service Definition
38
+ // ============================================================================
39
+ /**
40
+ * Service definition for BookingForm.
41
+ * Defines the contract that the BookingFormService must implement.
42
+ */
43
+ export const BookingFormServiceDefinition = defineService('bookingForm');
44
+ // ============================================================================
45
+ // Service Implementation
46
+ // ============================================================================
47
+ // ============================================================================
48
+ // Validation Helpers
49
+ // ============================================================================
50
+ /**
51
+ * Validates that a form belongs to the bookings namespace.
52
+ * @param form The form to validate
53
+ * @throws Error if the form is not from the bookings namespace
54
+ */
55
+ function validateBookingFormNamespace(form) {
56
+ // Check if form has namespace property and validate it
57
+ // The namespace might be in different locations depending on the form structure
58
+ const formNamespace = form.namespace ||
59
+ form._namespace;
60
+ if (formNamespace && formNamespace !== BOOKING_FORM_NAMESPACE) {
61
+ throw new Error(`BookingFormService: Invalid form namespace. ` +
62
+ `Expected "${BOOKING_FORM_NAMESPACE}", but received "${formNamespace}". ` +
63
+ `Ensure the form is a booking form.`);
64
+ }
65
+ }
66
+ /**
67
+ * Extracts the form ID from either config type.
68
+ * @param config The service config
69
+ * @returns The form ID
70
+ */
71
+ function extractFormId(config) {
72
+ if ('formId' in config) {
73
+ return config.formId;
74
+ }
75
+ // Extract from form object - forms typically have an 'id' or '_id' property
76
+ const form = config.form;
77
+ const formId = form.id ||
78
+ form._id;
79
+ if (!formId) {
80
+ throw new Error('BookingFormService: Could not extract form ID from provided form object.');
81
+ }
82
+ return formId;
83
+ }
84
+ // ============================================================================
85
+ // Service Implementation
86
+ // ============================================================================
87
+ /**
88
+ * Implementation of BookingFormService.
89
+ *
90
+ * This service manages form submission state for booking flows.
91
+ * Supports two modes:
92
+ * 1. Client-side: formId is provided, form is loaded dynamically
93
+ * 2. SSR/SSG: Pre-loaded form is provided (must be from bookings namespace)
94
+ *
95
+ * NOTE: The Form.Fields ref is managed internally - the core component passes
96
+ * a validate callback that the service stores and calls when validateFormSubmission() is invoked.
97
+ *
98
+ * @example
99
+ * ```tsx
100
+ * // Pattern 1: Client-side loading
101
+ * const bookingFormService = useService(
102
+ * BookingFormServiceDefinition.withConfig({ formId: 'form-123' })
103
+ * );
104
+ *
105
+ * // Pattern 2: SSR with pre-loaded form
106
+ * const bookingFormService = useService(
107
+ * BookingFormServiceDefinition.withConfig({ form: preloadedForm })
108
+ * );
109
+ *
110
+ * // Store form submission
111
+ * bookingFormService.setFormSubmission(formValues);
112
+ *
113
+ * // Validate form
114
+ * const result = await bookingFormService.validateFormSubmission();
115
+ * if (result.valid) {
116
+ * // Proceed with booking
117
+ * }
118
+ * ```
119
+ */
120
+ export const BookingFormService = implementService.withConfig()(BookingFormServiceDefinition, ({ getService, config }) => {
121
+ const signalsService = getService(SignalsServiceDefinition);
122
+ const formService = getService(FormServiceDefinition);
123
+ // Determine if we have a pre-loaded form
124
+ const hasPreloadedForm = 'form' in config;
125
+ // If form is provided, validate namespace
126
+ if (hasPreloadedForm) {
127
+ validateBookingFormNamespace(config.form);
128
+ }
129
+ // Extract formId from config (either directly or from form object)
130
+ const formIdValue = extractFormId(config);
131
+ // Initialize formSubmission signal (stores form data to be submitted)
132
+ const formSubmission = signalsService.signal(null);
133
+ // Initialize submissionId signal (for future use)
134
+ const formSubmissionId = signalsService.signal(null);
135
+ // formId signal - readonly
136
+ const formId = signalsService.signal(formIdValue);
137
+ // form signal - contains pre-loaded form if provided
138
+ const form = signalsService.signal(hasPreloadedForm ? config.form : null);
139
+ // Internal: Store validate callback (set by core component, not exposed publicly)
140
+ let _validateCallback = null;
141
+ // Internal: Register validate callback (called by core component via closure)
142
+ const _setValidateCallback = (callback) => {
143
+ _validateCallback = callback;
144
+ };
145
+ // Action to set form submission data
146
+ const setFormSubmission = (formValues) => {
147
+ formSubmission.set(formValues);
148
+ };
149
+ // Action to validate form - delegates to internally registered callback
150
+ const validateFormSubmission = async () => {
151
+ if (!_validateCallback) {
152
+ console.error('BookingFormService: Validate callback not registered. ' +
153
+ 'Ensure BookingForm component is mounted.');
154
+ return { valid: false, validationFailures: ['Form not initialized'] };
155
+ }
156
+ return _validateCallback();
157
+ };
158
+ // Public API (what's exposed in BookingFormServiceAPI interface)
159
+ const publicApi = {
160
+ formValues: formService.formValuesSignal,
161
+ formSubmission,
162
+ formSubmissionId,
163
+ formId,
164
+ form,
165
+ setFormSubmission,
166
+ validateFormSubmission,
167
+ // Internal method for core component access
168
+ _setValidateCallback,
169
+ };
170
+ return publicApi;
171
+ });
@@ -6,3 +6,4 @@ export { ServiceService, ServiceServiceDefinition, type ServiceServiceAPI, type
6
6
  export { BookingService, BookingServiceDefinition, type BookingServiceConfig, type Location, type BookingServiceActions, type ServiceSelection, type ServiceSelectionTimeSlot, } from './booking/booking.js';
7
7
  export { TimeSlotListService, TimeSlotListServiceDefinition, ViewMode, loadTimeSlotListConfig, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type SlotSelection, type TimeSlot, type LoadTimeSlotListConfigOptions, type LoadTimeSlotListConfigResult, } from './time-slot-list/index.js';
8
8
  export { ServiceListService, ServiceListServiceDefinition, type ServiceListServiceAPI, type ServiceListServiceConfig, type ServiceListFilters, type ServiceListActions, type QueryOptions, loadServiceListServiceInitialData, type SuccessServiceListServiceConfigResult, type ErrorServiceListServiceConfigResult, } from './service-list/service-list.js';
9
+ export { BookingFormService, BookingFormServiceDefinition, type BookingFormServiceAPI, type BookingFormServiceConfig, type BookingFormServiceInternalAPI, type ValidationResult, } from './booking-form/booking-form.js';
@@ -6,3 +6,4 @@ export { ServiceService, ServiceServiceDefinition, loadServiceServiceInitialData
6
6
  export { BookingService, BookingServiceDefinition, } from './booking/booking.js';
7
7
  export { TimeSlotListService, TimeSlotListServiceDefinition, ViewMode, loadTimeSlotListConfig, } from './time-slot-list/index.js';
8
8
  export { ServiceListService, ServiceListServiceDefinition, loadServiceListServiceInitialData, } from './service-list/service-list.js';
9
+ export { BookingFormService, BookingFormServiceDefinition, } from './booking-form/booking-form.js';
@@ -185,7 +185,10 @@ export const ServiceService = implementService.withConfig()(ServiceServiceDefini
185
185
  if (!bookableSignal.get() || !bookingService)
186
186
  return;
187
187
  const currentService = serviceSignal.get();
188
- bookingService.actions.updateItem({ service: currentService });
188
+ bookingService.actions.setItem({
189
+ service: currentService,
190
+ resources: [],
191
+ });
189
192
  },
190
193
  add: () => {
191
194
  if (!bookableSignal.get() || !bookingService)
@@ -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';