@wix/headless-bookings 0.0.23 → 0.0.25

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,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';
@@ -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';
@@ -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';