@wix/headless-forms 0.0.2 → 0.0.4

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.
@@ -1,5 +1,9 @@
1
1
  import { forms } from '@wix/forms';
2
- import { type Signal } from '@wix/services-definitions/core-services/signals';
2
+ import { type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
3
+ /**
4
+ * Response type for form submission operations.
5
+ * Represents the different states a form submission can be in.
6
+ */
3
7
  export type SubmitResponse = {
4
8
  type: 'success';
5
9
  message?: string;
@@ -9,181 +13,78 @@ export type SubmitResponse = {
9
13
  } | {
10
14
  type: 'idle';
11
15
  };
16
+ /**
17
+ * API interface for the Form service, providing reactive form data management.
18
+ * This service handles loading and managing form data, loading state, and errors.
19
+ * It supports both pre-loaded form data and lazy loading with form IDs.
20
+ *
21
+ * @interface FormServiceAPI
22
+ */
12
23
  export interface FormServiceAPI {
13
- form: Signal<forms.Form | null>;
14
- isLoading: Signal<boolean>;
15
- error: Signal<string | null>;
16
- submitResponse: Signal<SubmitResponse>;
24
+ /** Reactive signal containing the current form data */
25
+ formSignal: ReadOnlySignal<forms.Form | null>;
26
+ /** Reactive signal indicating if a form is currently being loaded */
27
+ isLoadingSignal: ReadOnlySignal<boolean>;
28
+ /** Reactive signal containing any error message, or null if no error */
29
+ errorSignal: ReadOnlySignal<string | null>;
17
30
  }
31
+ /**
32
+ * Service definition for the Form service.
33
+ * This defines the contract that the FormService must implement.
34
+ *
35
+ * @constant
36
+ */
18
37
  export declare const FormServiceDefinition: string & {
19
38
  __api: FormServiceAPI;
20
39
  __config: {};
21
40
  isServiceDefinition?: boolean;
22
41
  } & FormServiceAPI;
23
42
  /**
24
- * Configuration object for initializing the Form service.
25
- *
26
- * The Form service supports two distinct patterns for providing form data:
27
- *
28
- * **Pattern 1: Pre-loaded Form Data (SSR/SSG)**
29
- * - Use when you have form data available at service initialization
30
- * - Recommended for server-side rendering and static site generation
31
- * - Provides immediate form availability with no loading states
32
- * - Better performance and SEO as form data is available immediately
43
+ * Configuration type for the Form service.
44
+ * Supports two distinct patterns for providing form data:
45
+ * - Pre-loaded form data (SSR/SSG scenarios)
46
+ * - Lazy loading with form ID (client-side routing)
33
47
  *
34
- * **Pattern 2: Lazy Loading with Form ID (Client-side)**
35
- * - Use when you only have a form ID and need to load form data asynchronously
36
- * - Ideal for client-side routing and dynamic form loading
37
- * - Service will automatically fetch form data using the provided formId
38
- * - Provides loading states and error handling during form fetch
39
- *
40
- * @interface FormServiceConfig
41
- * @property {forms.Form} [form] - Pre-loaded form data. When provided, the service uses this data immediately without any network requests. Recommended for SSR/SSG scenarios.
42
- * @property {string} [formId] - Form ID for lazy loading. When provided (and no form data), the service will fetch form data asynchronously from the Wix Forms API. Ideal for client-side routing.
43
- *
44
- * @example
45
- * ```tsx
46
- * // Pattern 1: Pre-loaded form data (SSR/SSG)
47
- * // Server-side: Load form data first
48
- * const formServiceConfigResult = await loadFormServiceConfig('form-123');
49
- * if (formServiceConfigResult.type === 'success') {
50
- * // Use pre-loaded form data
51
- * <Form.Root formServiceConfig={formServiceConfigResult.config} />
52
- * }
53
- *
54
- * // Or with direct form data
55
- * const config1: FormServiceConfig = { form: myForm };
56
- * <Form.Root formServiceConfig={config1} />
57
- * ```
58
- *
59
- * @example
60
- * ```tsx
61
- * // Pattern 2: Lazy loading with form ID (Client-side)
62
- * // Simple formId-based loading - service handles the rest
63
- * const config2: FormServiceConfig = { formId: 'form-123' };
64
- * <Form.Root formServiceConfig={config2} />
65
- *
66
- * // With loading and error handling
67
- * <Form.Root formServiceConfig={{ formId: 'form-123' }}>
68
- * <Form.Loading>
69
- * {({ isLoading }) => isLoading ? <div>Loading form...</div> : null}
70
- * </Form.Loading>
71
- * <Form.LoadingError>
72
- * {({ error, hasError }) => hasError ? <div>Error: {error}</div> : null}
73
- * </Form.LoadingError>
74
- * <Form.Fields fieldMap={FIELD_MAP} />
75
- * </Form.Root>
76
- * ```
77
- *
78
- * @example
79
- * ```tsx
80
- * // Pattern 3: Both provided (form takes precedence)
81
- * // The pre-loaded form data will be used, formId is ignored
82
- * const config3: FormServiceConfig = {
83
- * form: myForm,
84
- * formId: 'form-123' // This will be ignored
85
- * };
86
- * <Form.Root formServiceConfig={config3} />
87
- * ```
88
- *
89
- * @throws {Error} Throws an error if neither form nor formId is provided
48
+ * @type {FormServiceConfig}
90
49
  */
91
- export interface FormServiceConfig {
92
- form?: forms.Form;
93
- formId?: string;
94
- }
50
+ export type FormServiceConfig = {
51
+ formId: string;
52
+ } | {
53
+ form: forms.Form;
54
+ };
95
55
  /**
96
- * Form service implementation that supports both pre-loaded form data and lazy loading.
97
- *
98
- * This service provides reactive state management for form data, loading states, errors, and submission responses.
99
- * It automatically handles form loading when only a formId is provided, making it suitable for both SSR and client-side scenarios.
100
- *
101
- * ## Service Behavior
102
- *
103
- * **Configuration Resolution:**
104
- * - If `form` is provided: Uses pre-loaded form data immediately (SSR/SSG pattern)
105
- * - If only `formId` is provided: Loads form data asynchronously from Wix Forms API
106
- * - If both are provided: Uses pre-loaded `form` data and ignores `formId`
107
- * - If neither is provided: Throws an error during service initialization
108
- *
109
- * **Loading States:**
110
- * - `isLoading`: `true` when loading form data via `formId`, `false` otherwise
111
- * - `form`: `null` initially when using `formId`, populated after successful load
112
- * - `error`: `null` initially, populated if form loading fails
113
- * - `submitResponse`: `{ type: 'idle' }` initially, updated during form submission
114
- *
115
- * **Error Handling:**
116
- * - Network errors during form loading are caught and stored in the `error` signal
117
- * - "Form not found" errors are handled when the formId doesn't exist
118
- * - All errors are logged to console for debugging
56
+ * Implementation of the Form service that manages reactive form data.
57
+ * This service provides signals for form data, loading state, and error handling.
58
+ * It supports both pre-loaded form data and lazy loading with form IDs.
119
59
  *
120
60
  * @example
121
61
  * ```tsx
122
- * // Service automatically handles loading states
123
- * const service = useService(FormServiceDefinition);
62
+ * // Pre-loaded form data (SSR/SSG)
63
+ * const formService = FormService.withConfig({ form: formData });
124
64
  *
125
- * // Check loading state
126
- * const isLoading = service.isLoading.get();
127
- *
128
- * // Access form data (null during loading)
129
- * const form = service.form.get();
130
- *
131
- * // Check for errors
132
- * const error = service.error.get();
65
+ * // Lazy loading with form ID (client-side)
66
+ * const formService = FormService.withConfig({ formId: 'form-123' });
133
67
  * ```
134
- *
135
68
  */
136
69
  export declare const FormService: import("@wix/services-definitions").ServiceFactory<string & {
137
70
  __api: FormServiceAPI;
138
71
  __config: {};
139
72
  isServiceDefinition?: boolean;
140
73
  } & FormServiceAPI, FormServiceConfig>;
141
- export type FormServiceConfigResult = {
142
- type: 'success';
143
- config: FormServiceConfig;
144
- } | {
145
- type: 'notFound';
146
- };
147
74
  /**
148
- * Loads form service configuration by form ID.
149
- *
150
- * This function fetches form data from the Wix Forms API and returns a configuration
151
- * object that can be used to initialize the Form service. This is the recommended approach
152
- * for server-side rendering (SSR) and static site generation (SSG) scenarios.
153
- *
154
- * @param {string} id - The unique identifier of the form to load
155
- * @returns {Promise<FormServiceConfigResult>} A promise that resolves to either:
156
- * - `{ type: 'success', config: FormServiceConfig }` if the form is found and loaded successfully
157
- * - `{ type: 'notFound' }` if the form doesn't exist or an error occurs during loading
158
- *
159
- * @example
160
- * ```tsx
161
- * import { loadFormServiceConfig } from '@wix/headless-forms/services';
162
- *
163
- * // Server-side loading (Astro/SSR) - pre-loads form data
164
- * const formServiceConfigResult = await loadFormServiceConfig('form-id');
75
+ * Loads form service configuration from the Wix Forms API for SSR initialization.
76
+ * This function is designed to be used during Server-Side Rendering (SSR) to preload
77
+ * a specific form by ID that will be used to configure the FormService.
165
78
  *
166
- * if (formServiceConfigResult.type === 'notFound') {
167
- * return Astro.redirect('/404');
168
- * }
169
- *
170
- * // Use pre-loaded form data
171
- * const formServiceConfig = formServiceConfigResult.config;
172
- * <Form.Root formServiceConfig={formServiceConfig} />
173
- * ```
79
+ * @param {string} formId - The unique identifier of the form to load
80
+ * @returns {Promise<FormServiceConfig>} Configuration object with pre-loaded form data
81
+ * @throws {Error} When the form cannot be loaded
174
82
  *
175
83
  * @example
176
84
  * ```tsx
177
- * // Alternative: Client-side loading with formId
178
- * // No need to pre-load, service handles loading automatically
179
- * <Form.Root formServiceConfig={{ formId: 'form-id' }}>
180
- * <Form.Loading>
181
- * {({ isLoading }) => isLoading ? <div>Loading...</div> : null}
182
- * </Form.Loading>
183
- * <Form.Fields fieldMap={FIELD_MAP} />
184
- * </Form.Root>
85
+ * // In your SSR/SSG setup
86
+ * const formConfig = await loadFormServiceConfig('form-123');
87
+ * const formService = FormService.withConfig(formConfig);
185
88
  * ```
186
- *
187
- * @throws {Error} Logs errors to console but returns 'notFound' result instead of throwing
188
89
  */
189
- export declare function loadFormServiceConfig(id: string): Promise<FormServiceConfigResult>;
90
+ export declare function loadFormServiceConfig(formId: string): Promise<FormServiceConfig>;
@@ -5,137 +5,92 @@ exports.loadFormServiceConfig = loadFormServiceConfig;
5
5
  const forms_1 = require("@wix/forms");
6
6
  const services_definitions_1 = require("@wix/services-definitions");
7
7
  const signals_1 = require("@wix/services-definitions/core-services/signals");
8
- exports.FormServiceDefinition = (0, services_definitions_1.defineService)('formService');
9
8
  /**
10
- * Form service implementation that supports both pre-loaded form data and lazy loading.
11
- *
12
- * This service provides reactive state management for form data, loading states, errors, and submission responses.
13
- * It automatically handles form loading when only a formId is provided, making it suitable for both SSR and client-side scenarios.
14
- *
15
- * ## Service Behavior
16
- *
17
- * **Configuration Resolution:**
18
- * - If `form` is provided: Uses pre-loaded form data immediately (SSR/SSG pattern)
19
- * - If only `formId` is provided: Loads form data asynchronously from Wix Forms API
20
- * - If both are provided: Uses pre-loaded `form` data and ignores `formId`
21
- * - If neither is provided: Throws an error during service initialization
9
+ * Service definition for the Form service.
10
+ * This defines the contract that the FormService must implement.
22
11
  *
23
- * **Loading States:**
24
- * - `isLoading`: `true` when loading form data via `formId`, `false` otherwise
25
- * - `form`: `null` initially when using `formId`, populated after successful load
26
- * - `error`: `null` initially, populated if form loading fails
27
- * - `submitResponse`: `{ type: 'idle' }` initially, updated during form submission
28
- *
29
- * **Error Handling:**
30
- * - Network errors during form loading are caught and stored in the `error` signal
31
- * - "Form not found" errors are handled when the formId doesn't exist
32
- * - All errors are logged to console for debugging
12
+ * @constant
13
+ */
14
+ exports.FormServiceDefinition = (0, services_definitions_1.defineService)('formService');
15
+ /**
16
+ * Implementation of the Form service that manages reactive form data.
17
+ * This service provides signals for form data, loading state, and error handling.
18
+ * It supports both pre-loaded form data and lazy loading with form IDs.
33
19
  *
34
20
  * @example
35
21
  * ```tsx
36
- * // Service automatically handles loading states
37
- * const service = useService(FormServiceDefinition);
22
+ * // Pre-loaded form data (SSR/SSG)
23
+ * const formService = FormService.withConfig({ form: formData });
38
24
  *
39
- * // Check loading state
40
- * const isLoading = service.isLoading.get();
41
- *
42
- * // Access form data (null during loading)
43
- * const form = service.form.get();
44
- *
45
- * // Check for errors
46
- * const error = service.error.get();
25
+ * // Lazy loading with form ID (client-side)
26
+ * const formService = FormService.withConfig({ formId: 'form-123' });
47
27
  * ```
48
- *
49
28
  */
50
29
  exports.FormService = services_definitions_1.implementService.withConfig()(exports.FormServiceDefinition, ({ getService, config }) => {
51
30
  const signalsService = getService(signals_1.SignalsServiceDefinition);
52
- const { form: initialForm, formId } = config;
53
- // Validation: ensure either form or formId is provided
54
- if (!initialForm && !formId) {
55
- throw new Error('FormServiceConfig must provide either "form" or "formId"');
31
+ const isLoadingSignal = signalsService.signal(false);
32
+ const errorSignal = signalsService.signal(null);
33
+ const hasSchema = 'form' in config;
34
+ const formSignal = signalsService.signal(hasSchema ? config.form : null);
35
+ if (!hasSchema) {
36
+ loadForm(config.formId);
56
37
  }
57
- const form = signalsService.signal(initialForm || null);
58
- const isLoading = signalsService.signal(!!formId && !initialForm);
59
- const error = signalsService.signal(null);
60
- const submitResponse = signalsService.signal({ type: 'idle' });
61
- // Client-side form loading for formId case
62
- if (formId && !initialForm) {
63
- // Load form asynchronously
64
- forms_1.forms
65
- .getForm(formId)
66
- .then((loadedForm) => {
67
- if (loadedForm) {
68
- form.set(loadedForm);
69
- isLoading.set(false);
38
+ async function loadForm(id) {
39
+ isLoadingSignal.set(true);
40
+ errorSignal.set(null);
41
+ try {
42
+ const result = await fetchForm(id);
43
+ if (result) {
44
+ formSignal.set(result);
70
45
  }
71
46
  else {
72
- error.set('Form not found');
73
- isLoading.set(false);
47
+ errorSignal.set('Form not found');
74
48
  }
75
- })
76
- .catch((err) => {
77
- console.error('Failed to load form:', err);
78
- error.set('Failed to load form');
79
- isLoading.set(false);
80
- });
49
+ }
50
+ catch (err) {
51
+ errorSignal.set('Failed to load form');
52
+ throw err;
53
+ }
54
+ finally {
55
+ isLoadingSignal.set(false);
56
+ }
81
57
  }
82
- return { form, isLoading, error, submitResponse };
58
+ return {
59
+ formSignal: formSignal,
60
+ isLoadingSignal: isLoadingSignal,
61
+ errorSignal: errorSignal,
62
+ };
83
63
  });
64
+ async function fetchForm(id) {
65
+ try {
66
+ const result = await forms_1.forms.getForm(id);
67
+ if (!result) {
68
+ throw new Error(`Form ${id} not found`);
69
+ }
70
+ return result;
71
+ }
72
+ catch (err) {
73
+ console.error('Failed to load form:', id, err);
74
+ throw err;
75
+ }
76
+ }
84
77
  /**
85
- * Loads form service configuration by form ID.
86
- *
87
- * This function fetches form data from the Wix Forms API and returns a configuration
88
- * object that can be used to initialize the Form service. This is the recommended approach
89
- * for server-side rendering (SSR) and static site generation (SSG) scenarios.
90
- *
91
- * @param {string} id - The unique identifier of the form to load
92
- * @returns {Promise<FormServiceConfigResult>} A promise that resolves to either:
93
- * - `{ type: 'success', config: FormServiceConfig }` if the form is found and loaded successfully
94
- * - `{ type: 'notFound' }` if the form doesn't exist or an error occurs during loading
95
- *
96
- * @example
97
- * ```tsx
98
- * import { loadFormServiceConfig } from '@wix/headless-forms/services';
78
+ * Loads form service configuration from the Wix Forms API for SSR initialization.
79
+ * This function is designed to be used during Server-Side Rendering (SSR) to preload
80
+ * a specific form by ID that will be used to configure the FormService.
99
81
  *
100
- * // Server-side loading (Astro/SSR) - pre-loads form data
101
- * const formServiceConfigResult = await loadFormServiceConfig('form-id');
102
- *
103
- * if (formServiceConfigResult.type === 'notFound') {
104
- * return Astro.redirect('/404');
105
- * }
106
- *
107
- * // Use pre-loaded form data
108
- * const formServiceConfig = formServiceConfigResult.config;
109
- * <Form.Root formServiceConfig={formServiceConfig} />
110
- * ```
82
+ * @param {string} formId - The unique identifier of the form to load
83
+ * @returns {Promise<FormServiceConfig>} Configuration object with pre-loaded form data
84
+ * @throws {Error} When the form cannot be loaded
111
85
  *
112
86
  * @example
113
87
  * ```tsx
114
- * // Alternative: Client-side loading with formId
115
- * // No need to pre-load, service handles loading automatically
116
- * <Form.Root formServiceConfig={{ formId: 'form-id' }}>
117
- * <Form.Loading>
118
- * {({ isLoading }) => isLoading ? <div>Loading...</div> : null}
119
- * </Form.Loading>
120
- * <Form.Fields fieldMap={FIELD_MAP} />
121
- * </Form.Root>
88
+ * // In your SSR/SSG setup
89
+ * const formConfig = await loadFormServiceConfig('form-123');
90
+ * const formService = FormService.withConfig(formConfig);
122
91
  * ```
123
- *
124
- * @throws {Error} Logs errors to console but returns 'notFound' result instead of throwing
125
92
  */
126
- async function loadFormServiceConfig(id) {
127
- try {
128
- const form = await forms_1.forms.getForm(id);
129
- if (!form) {
130
- return { type: 'notFound' };
131
- }
132
- return {
133
- type: 'success',
134
- config: { form },
135
- };
136
- }
137
- catch (error) {
138
- console.error('Failed to load form:', error);
139
- }
140
- return { type: 'notFound' };
93
+ async function loadFormServiceConfig(formId) {
94
+ const form = await fetchForm(formId);
95
+ return { form };
141
96
  }
@@ -22,6 +22,7 @@ export interface RootProps {
22
22
  * @param {RootProps} props - The component props
23
23
  * @param {React.ReactNode} props.children - Child components that will have access to form context
24
24
  * @param {FormServiceConfig} props.formServiceConfig - Form service configuration object
25
+ * @param {boolean} [props.asChild] - Whether to render as a child component
25
26
  * @param {string} [props.className] - CSS classes to apply to the root element
26
27
  * @example
27
28
  * ```tsx
@@ -368,7 +369,7 @@ export declare const Submitted: React.ForwardRefExoticComponent<SubmittedProps &
368
369
  * };
369
370
  * ```
370
371
  */
371
- interface FieldMap {
372
+ export interface FieldMap {
372
373
  TEXT_INPUT: React.ComponentType<TextInputProps>;
373
374
  TEXT_AREA: React.ComponentType<TextAreaProps>;
374
375
  PHONE_INPUT: React.ComponentType<PhoneInputProps>;
@@ -416,7 +417,7 @@ interface FieldMap {
416
417
  * <Form.Fields fieldMap={FIELD_MAP} />
417
418
  * ```
418
419
  */
419
- export interface FieldsProps {
420
+ interface FieldsProps {
420
421
  fieldMap: FieldMap;
421
422
  }
422
423
  /**
@@ -564,69 +565,4 @@ export interface FieldsProps {
564
565
  * ```
565
566
  */
566
567
  export declare const Fields: React.ForwardRefExoticComponent<FieldsProps & React.RefAttributes<HTMLDivElement>>;
567
- /**
568
- * Main Form namespace containing all form components following the compound component pattern.
569
- * Provides a headless, flexible way to render and manage forms with custom field components.
570
- *
571
- * @namespace Form
572
- * @property {typeof Root} Root - Form root component that provides service context to all child components
573
- * @property {typeof Loading} Loading - Form loading state component that displays content during form loading
574
- * @property {typeof LoadingError} LoadingError - Form loading error state component for handling form loading errors
575
- * @property {typeof Error} Error - Form submit error state component for handling form submission errors
576
- * @property {typeof Submitted} Submitted - Form submitted state component for displaying success messages
577
- * @property {typeof Fields} Fields - Form fields component for rendering form fields with custom field renderers
578
- * @example
579
- * ```tsx
580
- * import { Form } from '@wix/headless-forms/react';
581
- * import { loadFormServiceConfig } from '@wix/headless-forms/services';
582
- * import { TextInput, TextArea, Checkbox } from './field-components';
583
- *
584
- * const FIELD_MAP = {
585
- * TEXT_INPUT: TextInput,
586
- * TEXT_AREA: TextArea,
587
- * CHECKBOX: Checkbox,
588
- * // ... other field components
589
- * };
590
- *
591
- * // Pattern 1: Pre-loaded form data (SSR/SSG)
592
- * function MyForm({ formServiceConfig }) {
593
- * return (
594
- * <Form.Root formServiceConfig={formServiceConfig}>
595
- * <Form.Loading className="flex justify-center p-4" />
596
- * <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
597
- * <Form.Fields fieldMap={FIELD_MAP} />
598
- * <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
599
- * <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
600
- * </Form.Root>
601
- * );
602
- * }
603
- *
604
- * // Pattern 2: Lazy loading with formId (Client-side)
605
- * function DynamicForm({ formId }) {
606
- * return (
607
- * <Form.Root formServiceConfig={{ formId }}>
608
- * <Form.Loading className="flex justify-center p-4" />
609
- * <Form.LoadingError className="text-destructive px-4 py-3 rounded mb-4" />
610
- * <Form.Fields fieldMap={FIELD_MAP} />
611
- * <Form.Error className="text-destructive p-4 rounded-lg mb-4" />
612
- * <Form.Submitted className="text-green-500 p-4 rounded-lg mb-4" />
613
- * </Form.Root>
614
- * );
615
- * }
616
- * ```
617
- */
618
- export declare const Form: {
619
- /** Form root component that provides service context */
620
- readonly Root: React.ForwardRefExoticComponent<RootProps & React.RefAttributes<HTMLDivElement>>;
621
- /** Form loading state component */
622
- readonly Loading: React.ForwardRefExoticComponent<LoadingProps & React.RefAttributes<HTMLElement>>;
623
- /** Form loading error state component */
624
- readonly LoadingError: React.ForwardRefExoticComponent<LoadingErrorProps & React.RefAttributes<HTMLElement>>;
625
- /** Form error state component */
626
- readonly Error: React.ForwardRefExoticComponent<ErrorProps & React.RefAttributes<HTMLElement>>;
627
- /** Form submitted state component */
628
- readonly Submitted: React.ForwardRefExoticComponent<SubmittedProps & React.RefAttributes<HTMLElement>>;
629
- /** Form fields component for rendering form fields */
630
- readonly Fields: React.ForwardRefExoticComponent<FieldsProps & React.RefAttributes<HTMLDivElement>>;
631
- };
632
568
  export {};