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