@wix/headless-forms 0.0.8 → 0.0.9

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,6 @@
1
1
  import { forms } from '@wix/forms';
2
2
  import { type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
3
+ import { FormValues } from '../react/types.js';
3
4
  /**
4
5
  * Response type for form submission operations.
5
6
  * Represents the different states a form submission can be in.
@@ -15,7 +16,7 @@ export type SubmitResponse = {
15
16
  };
16
17
  /**
17
18
  * 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
+ * This service handles loading and managing form data, loading state, errors, and submissions.
19
20
  * It supports both pre-loaded form data and lazy loading with form IDs.
20
21
  *
21
22
  * @interface FormServiceAPI
@@ -27,6 +28,10 @@ export interface FormServiceAPI {
27
28
  isLoadingSignal: ReadOnlySignal<boolean>;
28
29
  /** Reactive signal containing any error message, or null if no error */
29
30
  errorSignal: ReadOnlySignal<string | null>;
31
+ /** Reactive signal containing submission response state */
32
+ submitResponseSignal: ReadOnlySignal<SubmitResponse>;
33
+ /** Function to submit form with current values */
34
+ submitForm: (formValues: FormValues) => Promise<void>;
30
35
  }
31
36
  /**
32
37
  * Service definition for the Form service.
@@ -39,23 +44,29 @@ export declare const FormServiceDefinition: string & {
39
44
  __config: {};
40
45
  isServiceDefinition?: boolean;
41
46
  } & FormServiceAPI;
47
+ type OnSubmit = (formId: string, formValues: FormValues) => Promise<SubmitResponse>;
42
48
  /**
43
49
  * Configuration type for the Form service.
44
50
  * Supports two distinct patterns for providing form data:
45
51
  * - Pre-loaded form data (SSR/SSG scenarios)
46
52
  * - Lazy loading with form ID (client-side routing)
47
53
  *
54
+ * Optionally accepts a custom submission handler to override default behavior.
55
+ *
48
56
  * @type {FormServiceConfig}
49
57
  */
50
58
  export type FormServiceConfig = {
51
59
  formId: string;
60
+ onSubmit?: OnSubmit;
52
61
  } | {
53
62
  form: forms.Form;
63
+ onSubmit?: OnSubmit;
54
64
  };
55
65
  /**
56
- * Implementation of the Form service that manages reactive form data.
57
- * This service provides signals for form data, loading state, and error handling.
66
+ * Implementation of the Form service that manages reactive form data and submissions.
67
+ * This service provides signals for form data, loading state, error handling, and submission state.
58
68
  * It supports both pre-loaded form data and lazy loading with form IDs.
69
+ * Consumers can provide a custom submission handler via config.
59
70
  *
60
71
  * @example
61
72
  * ```tsx
@@ -64,6 +75,16 @@ export type FormServiceConfig = {
64
75
  *
65
76
  * // Lazy loading with form ID (client-side)
66
77
  * const formService = FormService.withConfig({ formId: 'form-123' });
78
+ *
79
+ * // With custom submission handler
80
+ * const formService = FormService.withConfig({
81
+ * formId: 'form-123',
82
+ * onSubmit: async (formId, formValues) => {
83
+ * // Custom submission logic
84
+ * await fetch('/api/submit', { method: 'POST', body: JSON.stringify({ formId, ...formValues }) });
85
+ * return { type: 'success', message: 'Form submitted!' };
86
+ * }
87
+ * });
67
88
  * ```
68
89
  */
69
90
  export declare const FormService: import("@wix/services-definitions").ServiceFactory<string & {
@@ -88,3 +109,4 @@ export declare const FormService: import("@wix/services-definitions").ServiceFac
88
109
  * ```
89
110
  */
90
111
  export declare function loadFormServiceConfig(formId: string): Promise<FormServiceConfig>;
112
+ export {};
@@ -13,9 +13,10 @@ const signals_1 = require("@wix/services-definitions/core-services/signals");
13
13
  */
14
14
  exports.FormServiceDefinition = (0, services_definitions_1.defineService)('formService');
15
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.
16
+ * Implementation of the Form service that manages reactive form data and submissions.
17
+ * This service provides signals for form data, loading state, error handling, and submission state.
18
18
  * It supports both pre-loaded form data and lazy loading with form IDs.
19
+ * Consumers can provide a custom submission handler via config.
19
20
  *
20
21
  * @example
21
22
  * ```tsx
@@ -24,12 +25,25 @@ exports.FormServiceDefinition = (0, services_definitions_1.defineService)('formS
24
25
  *
25
26
  * // Lazy loading with form ID (client-side)
26
27
  * const formService = FormService.withConfig({ formId: 'form-123' });
28
+ *
29
+ * // With custom submission handler
30
+ * const formService = FormService.withConfig({
31
+ * formId: 'form-123',
32
+ * onSubmit: async (formId, formValues) => {
33
+ * // Custom submission logic
34
+ * await fetch('/api/submit', { method: 'POST', body: JSON.stringify({ formId, ...formValues }) });
35
+ * return { type: 'success', message: 'Form submitted!' };
36
+ * }
37
+ * });
27
38
  * ```
28
39
  */
29
40
  exports.FormService = services_definitions_1.implementService.withConfig()(exports.FormServiceDefinition, ({ getService, config }) => {
30
41
  const signalsService = getService(signals_1.SignalsServiceDefinition);
31
42
  const isLoadingSignal = signalsService.signal(false);
32
43
  const errorSignal = signalsService.signal(null);
44
+ const submitResponseSignal = signalsService.signal({
45
+ type: 'idle',
46
+ });
33
47
  const hasSchema = 'form' in config;
34
48
  const formSignal = signalsService.signal(hasSchema ? config.form : null);
35
49
  if (!hasSchema) {
@@ -55,10 +69,49 @@ exports.FormService = services_definitions_1.implementService.withConfig()(expor
55
69
  isLoadingSignal.set(false);
56
70
  }
57
71
  }
72
+ async function defaultSubmitHandler(formId, formValues) {
73
+ try {
74
+ await forms_1.submissions.createSubmission({ formId, ...formValues });
75
+ // TODO: add message
76
+ return { type: 'success' };
77
+ }
78
+ catch (error) {
79
+ console.error('Form submission failed:', error);
80
+ return { type: 'error', message: 'Failed to submit form' };
81
+ }
82
+ }
83
+ /**
84
+ * Submits the form with the provided values.
85
+ * Uses custom handler if provided in config, otherwise uses default submission.
86
+ */
87
+ async function submitForm(formValues) {
88
+ const form = formSignal.get();
89
+ if (!form) {
90
+ console.error('Cannot submit: form not loaded');
91
+ return;
92
+ }
93
+ // @ts-expect-error
94
+ const formId = form._id ? form._id : form.id;
95
+ submitResponseSignal.set({ type: 'idle' });
96
+ try {
97
+ const handler = config.onSubmit || defaultSubmitHandler;
98
+ const response = await handler(formId, formValues);
99
+ submitResponseSignal.set(response);
100
+ }
101
+ catch (error) {
102
+ console.error('Unexpected error during submission:', error);
103
+ submitResponseSignal.set({
104
+ type: 'error',
105
+ message: 'Unexpected error during submission',
106
+ });
107
+ }
108
+ }
58
109
  return {
59
110
  formSignal: formSignal,
60
111
  isLoadingSignal: isLoadingSignal,
61
112
  errorSignal: errorSignal,
113
+ submitResponseSignal: submitResponseSignal,
114
+ submitForm: submitForm,
62
115
  };
63
116
  });
64
117
  async function fetchForm(id) {
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
+ import { type CheckboxGroupProps, type CheckboxProps, type PhoneInputProps, type DateInputProps, type DatePickerProps, type DateTimeInputProps, type DropdownProps, type FileUploadProps, type MultilineAddressProps, type NumberInputProps, type RadioGroupProps, type RatingInputProps, type RichTextProps, type SignatureProps, type SubmitButtonProps, type TagsProps, type TextAreaProps, type TextInputProps, type TimeInputProps, type ProductListProps, type FixedPaymentProps, type PaymentInputProps, type DonationProps, type AppointmentProps, type ImageChoiceProps } from './types';
2
3
  import { type FormServiceConfig } from '../services/form-service';
3
- import { CheckboxGroupProps, CheckboxProps, PhoneInputProps, DateInputProps, DatePickerProps, DateTimeInputProps, DropdownProps, FileUploadProps, MultilineAddressProps, NumberInputProps, RadioGroupProps, RatingInputProps, RichTextProps, SignatureProps, SubmitButtonProps, TagsProps, TextAreaProps, TextInputProps, TimeInputProps, ProductListProps, FixedPaymentProps, PaymentInputProps, DonationProps, AppointmentProps, ImageChoiceProps } from './types.js';
4
4
  /**
5
5
  * Props for the Form root component following the documented API
6
6
  */
@@ -457,10 +457,9 @@ export const Fields = React.forwardRef((props, ref) => {
457
457
  const handleFormValidate = useCallback((errors) => {
458
458
  setFormErrors(errors);
459
459
  }, []);
460
- return (_jsx(CoreFields, { children: ({ form }) => {
461
- console.log('Fields form', form);
460
+ return (_jsx(CoreFields, { children: ({ form, submitForm }) => {
462
461
  if (!form)
463
462
  return null;
464
- return (_jsx("div", { ref: ref, children: _jsx(FormViewer, { form: form, values: formValues, onChange: handleFormChange, errors: formErrors, onValidate: handleFormValidate, fields: props.fieldMap }) }));
463
+ return (_jsx("div", { ref: ref, children: _jsx(FormViewer, { form: form, values: formValues, onChange: handleFormChange, errors: formErrors, onValidate: handleFormValidate, fields: props.fieldMap, submitForm: () => submitForm(formValues) }) }));
465
464
  } }));
466
465
  });
@@ -1,5 +1,6 @@
1
1
  import { forms } from '@wix/forms';
2
2
  import { FormServiceConfig } from '../../services/form-service.js';
3
+ import { FormValues } from '../types.js';
3
4
  /**
4
5
  * Props for Root headless component
5
6
  */
@@ -223,6 +224,7 @@ export declare function Submitted(props: FormSubmittedProps): import("react").Re
223
224
  */
224
225
  interface FieldsRenderProps {
225
226
  form: forms.Form | null;
227
+ submitForm: (formValues: FormValues) => Promise<void>;
226
228
  }
227
229
  /**
228
230
  * Props for Fields headless component
@@ -231,12 +233,13 @@ interface FieldsProps {
231
233
  children: (props: FieldsRenderProps) => React.ReactNode;
232
234
  }
233
235
  /**
234
- * Fields component that provides form data to its children.
235
- * This component accesses the form data from the service and passes it to children via render props.
236
+ * Fields component that provides form data and actions to its children.
237
+ * This component accesses the form data and submitForm action from the service
238
+ * and passes them to children via render props.
236
239
  *
237
240
  * @component
238
241
  * @param {FieldsProps} props - Component props
239
- * @param {FieldsProps['children']} props.children - Render prop function that receives form data
242
+ * @param {FieldsProps['children']} props.children - Render prop function that receives form data and actions
240
243
  * @example
241
244
  * ```tsx
242
245
  * import { Form } from '@wix/headless-forms/react';
@@ -244,11 +247,14 @@ interface FieldsProps {
244
247
  * function FormFields() {
245
248
  * return (
246
249
  * <Form.Fields>
247
- * {({ form }) => (
250
+ * {({ form, submitForm }) => (
248
251
  * form ? (
249
252
  * <div>
250
253
  * <h2>{form.name}</h2>
251
254
  * <p>{form.description}</p>
255
+ * <button onClick={() => submitForm({ field1: 'value' })}>
256
+ * Submit
257
+ * </button>
252
258
  * </div>
253
259
  * ) : null
254
260
  * )}
@@ -141,9 +141,10 @@ export function LoadingError(props) {
141
141
  * ```
142
142
  */
143
143
  export function Error(props) {
144
- // TODO: Implement submit response handling when submitResponseSignal is added to service
145
- const error = null;
146
- const hasError = false;
144
+ const { submitResponseSignal } = useService(FormServiceDefinition);
145
+ const submitResponse = submitResponseSignal.get();
146
+ const error = submitResponse.type === 'error' ? submitResponse.message : null;
147
+ const hasError = submitResponse.type === 'error';
147
148
  return props.children({
148
149
  error,
149
150
  hasError,
@@ -176,21 +177,25 @@ export function Error(props) {
176
177
  * ```
177
178
  */
178
179
  export function Submitted(props) {
179
- // TODO: Implement submit response handling when submitResponseSignal is added to service
180
- const isSubmitted = false;
181
- const message = DEFAULT_SUCCESS_MESSAGE;
180
+ const { submitResponseSignal } = useService(FormServiceDefinition);
181
+ const submitResponse = submitResponseSignal.get();
182
+ const isSubmitted = submitResponse.type === 'success';
183
+ const message = submitResponse.type === 'success'
184
+ ? submitResponse.message || DEFAULT_SUCCESS_MESSAGE
185
+ : DEFAULT_SUCCESS_MESSAGE;
182
186
  return props.children({
183
187
  isSubmitted,
184
188
  message,
185
189
  });
186
190
  }
187
191
  /**
188
- * Fields component that provides form data to its children.
189
- * This component accesses the form data from the service and passes it to children via render props.
192
+ * Fields component that provides form data and actions to its children.
193
+ * This component accesses the form data and submitForm action from the service
194
+ * and passes them to children via render props.
190
195
  *
191
196
  * @component
192
197
  * @param {FieldsProps} props - Component props
193
- * @param {FieldsProps['children']} props.children - Render prop function that receives form data
198
+ * @param {FieldsProps['children']} props.children - Render prop function that receives form data and actions
194
199
  * @example
195
200
  * ```tsx
196
201
  * import { Form } from '@wix/headless-forms/react';
@@ -198,11 +203,14 @@ export function Submitted(props) {
198
203
  * function FormFields() {
199
204
  * return (
200
205
  * <Form.Fields>
201
- * {({ form }) => (
206
+ * {({ form, submitForm }) => (
202
207
  * form ? (
203
208
  * <div>
204
209
  * <h2>{form.name}</h2>
205
210
  * <p>{form.description}</p>
211
+ * <button onClick={() => submitForm({ field1: 'value' })}>
212
+ * Submit
213
+ * </button>
206
214
  * </div>
207
215
  * ) : null
208
216
  * )}
@@ -212,9 +220,10 @@ export function Submitted(props) {
212
220
  * ```
213
221
  */
214
222
  export function Fields(props) {
215
- const { formSignal } = useService(FormServiceDefinition);
223
+ const { formSignal, submitForm } = useService(FormServiceDefinition);
216
224
  const form = formSignal.get();
217
225
  return props.children({
218
226
  form,
227
+ submitForm,
219
228
  });
220
229
  }