@otl-core/forms 1.1.19 → 1.1.21

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.
Files changed (43) hide show
  1. package/dist/form-context.cjs +99 -0
  2. package/dist/form-context.cjs.map +1 -0
  3. package/dist/form-context.d.cts +55 -0
  4. package/dist/form-context.d.ts +55 -0
  5. package/dist/form-context.js +80 -0
  6. package/dist/form-context.js.map +1 -0
  7. package/dist/index.cjs +25 -943
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +6 -84
  10. package/dist/index.d.ts +6 -84
  11. package/dist/index.js +3 -939
  12. package/dist/index.js.map +1 -1
  13. package/dist/use-form-action.cjs +281 -0
  14. package/dist/use-form-action.cjs.map +1 -0
  15. package/dist/use-form-action.d.cts +12 -0
  16. package/dist/use-form-action.d.ts +12 -0
  17. package/dist/use-form-action.js +264 -0
  18. package/dist/use-form-action.js.map +1 -0
  19. package/dist/use-form-field.cjs +181 -0
  20. package/dist/use-form-field.cjs.map +1 -0
  21. package/dist/use-form-field.d.cts +19 -0
  22. package/dist/use-form-field.d.ts +19 -0
  23. package/dist/use-form-field.js +160 -0
  24. package/dist/use-form-field.js.map +1 -0
  25. package/dist/utils/page.utils.cjs +76 -0
  26. package/dist/utils/page.utils.cjs.map +1 -0
  27. package/dist/utils/page.utils.d.cts +7 -0
  28. package/dist/utils/page.utils.d.ts +7 -0
  29. package/dist/utils/page.utils.js +50 -0
  30. package/dist/utils/page.utils.js.map +1 -0
  31. package/dist/utils/rule.utils.cjs +94 -0
  32. package/dist/utils/rule.utils.cjs.map +1 -0
  33. package/dist/utils/rule.utils.d.cts +6 -0
  34. package/dist/utils/rule.utils.d.ts +6 -0
  35. package/dist/utils/rule.utils.js +69 -0
  36. package/dist/utils/rule.utils.js.map +1 -0
  37. package/dist/utils/validation.utils.cjs +391 -0
  38. package/dist/utils/validation.utils.cjs.map +1 -0
  39. package/dist/utils/validation.utils.d.cts +15 -0
  40. package/dist/utils/validation.utils.d.ts +15 -0
  41. package/dist/utils/validation.utils.js +366 -0
  42. package/dist/utils/validation.utils.js.map +1 -0
  43. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,940 +1,4 @@
1
- "use client";
2
- import { createContext, useContext, useState, useMemo, useCallback } from 'react';
3
- import { jsx } from 'react/jsx-runtime';
4
-
5
- // src/form-context.tsx
6
- var FormContext = createContext(void 0);
7
- function useForm() {
8
- const context = useContext(FormContext);
9
- if (!context) {
10
- throw new Error("useForm must be used within a FormProvider");
11
- }
12
- return context;
13
- }
14
- function FormProvider({
15
- children,
16
- formId,
17
- document,
18
- settings,
19
- analyticsSettings,
20
- onAnalyticsEvent,
21
- formName,
22
- environmentVariantId,
23
- formVariantId,
24
- locale
25
- }) {
26
- const [currentPageId, setCurrentPageId] = useState(
27
- document.pages[0]?.id ?? ""
28
- );
29
- const [formValues, setFormValues] = useState({});
30
- const [loading, setLoading] = useState(false);
31
- const [errors, setErrors] = useState(null);
32
- const [globalError, setGlobalError] = useState(null);
33
- const [hasStarted, setHasStarted] = useState(false);
34
- const currentPage = useMemo(
35
- () => document.pages.find((p) => p.id === currentPageId) ?? document.pages[0],
36
- [document, currentPageId]
37
- );
38
- const setCurrentPage = useCallback(
39
- (page) => setCurrentPageId(page.id),
40
- []
41
- );
42
- return /* @__PURE__ */ jsx(
43
- FormContext.Provider,
44
- {
45
- value: {
46
- formId,
47
- document,
48
- settings,
49
- loading,
50
- setLoading,
51
- currentPage,
52
- setCurrentPage,
53
- formValues,
54
- setFormValues,
55
- errors,
56
- setErrors,
57
- globalError,
58
- setGlobalError,
59
- analyticsSettings,
60
- onAnalyticsEvent,
61
- formName,
62
- hasStarted,
63
- setHasStarted,
64
- environmentVariantId,
65
- formVariantId,
66
- locale
67
- },
68
- children
69
- }
70
- );
71
- }
72
-
73
- // src/utils/page.utils.ts
74
- function getPreviousPage(currentPage, formDocument) {
75
- const index = formDocument.pages.findIndex(
76
- (page) => page.id === currentPage.id
77
- );
78
- if (index === 0) {
79
- return null;
80
- }
81
- return formDocument.pages[index - 1];
82
- }
83
- function getNextPage(currentPage, formDocument) {
84
- const index = formDocument.pages.findIndex(
85
- (page) => page.id === currentPage.id
86
- );
87
- if (index === formDocument.pages.length - 1) {
88
- return null;
89
- }
90
- return formDocument.pages[index + 1];
91
- }
92
- function findBlockInDocument(blockId, document) {
93
- for (const page of document.pages) {
94
- const block = findBlockRecursive(blockId, page.blocks);
95
- if (block) return block;
96
- }
97
- return null;
98
- }
99
- function findBlockRecursive(blockId, blocks) {
100
- for (const block of blocks) {
101
- if (block.id === blockId) {
102
- return block;
103
- }
104
- if (block.config.blocks && Array.isArray(block.config.blocks)) {
105
- const nestedBlocks = block.config.blocks.map(
106
- (b) => ({
107
- id: b.id,
108
- type: b.type,
109
- config: b.config || {}
110
- })
111
- );
112
- const found = findBlockRecursive(blockId, nestedBlocks);
113
- if (found) return found;
114
- }
115
- }
116
- return null;
117
- }
118
-
119
- // src/utils/rule.utils.ts
120
- function evaluateCondition(operator, fieldValue, ruleValue) {
121
- switch (operator) {
122
- case "equals":
123
- return fieldValue === ruleValue;
124
- case "not_equals":
125
- return fieldValue !== ruleValue;
126
- case "contains":
127
- return String(fieldValue || "").includes(String(ruleValue));
128
- case "not_contains":
129
- return !String(fieldValue || "").includes(String(ruleValue));
130
- case "starts_with":
131
- return String(fieldValue || "").startsWith(String(ruleValue));
132
- case "ends_with":
133
- return String(fieldValue || "").endsWith(String(ruleValue));
134
- case "greater_than":
135
- return Number(fieldValue) > Number(ruleValue);
136
- case "less_than":
137
- return Number(fieldValue) < Number(ruleValue);
138
- case "greater_than_or_equal":
139
- return Number(fieldValue) >= Number(ruleValue);
140
- case "less_than_or_equal":
141
- return Number(fieldValue) <= Number(ruleValue);
142
- case "is_empty":
143
- return !fieldValue || fieldValue === "" || Array.isArray(fieldValue) && fieldValue.length === 0;
144
- case "is_not_empty":
145
- return !!fieldValue && fieldValue !== "" && (!Array.isArray(fieldValue) || fieldValue.length > 0);
146
- case "is_checked":
147
- return fieldValue === true;
148
- case "is_not_checked":
149
- return fieldValue !== true;
150
- case "includes":
151
- return Array.isArray(fieldValue) && fieldValue.includes(ruleValue);
152
- case "not_includes":
153
- return !Array.isArray(fieldValue) || !fieldValue.includes(ruleValue);
154
- default:
155
- return false;
156
- }
157
- }
158
- function evaluateVisibilityRules(rules, formValues) {
159
- const visibilityRules = rules.filter((r) => r.type === "visible_when");
160
- if (visibilityRules.length === 0) {
161
- return "block";
162
- }
163
- for (const rule of visibilityRules) {
164
- const fieldValue = formValues[rule.field];
165
- if (evaluateCondition(rule.operator, fieldValue, rule.value)) {
166
- return "block";
167
- }
168
- }
169
- return "none";
170
- }
171
- function evaluateDisabledRules(rules, formValues) {
172
- const disabledRules = rules.filter((r) => r.type === "disabled_when");
173
- if (disabledRules.length === 0) {
174
- return false;
175
- }
176
- for (const rule of disabledRules) {
177
- const fieldValue = formValues[rule.field];
178
- if (evaluateCondition(rule.operator, fieldValue, rule.value)) {
179
- return true;
180
- }
181
- }
182
- return false;
183
- }
184
-
185
- // src/utils/validation.utils.ts
186
- function isEmpty(value) {
187
- return value === void 0 || value === null || value === "";
188
- }
189
- function interpolateMessage(template, params) {
190
- return template.replace(/\{(\w+)\}/g, (_, key) => String(params[key] || ""));
191
- }
192
- function validateMinLength(value, minLength) {
193
- if (isEmpty(value)) return true;
194
- return typeof value === "string" && value.length >= parseInt(minLength);
195
- }
196
- function validateMaxLength(value, maxLength) {
197
- if (isEmpty(value)) return true;
198
- return typeof value === "string" && value.length <= parseInt(maxLength);
199
- }
200
- function validatePattern(value, pattern) {
201
- if (isEmpty(value)) return true;
202
- return typeof value === "string" && new RegExp(pattern).test(value);
203
- }
204
- function validateMinValue(value, min) {
205
- if (isEmpty(value)) return true;
206
- const numValue = typeof value === "number" ? value : parseFloat(value);
207
- const minValue = typeof min === "number" ? min : parseFloat(min);
208
- return !isNaN(numValue) && !isNaN(minValue) && numValue >= minValue;
209
- }
210
- function validateMaxValue(value, max) {
211
- if (isEmpty(value)) return true;
212
- const numValue = typeof value === "number" ? value : parseFloat(value);
213
- const maxValue = typeof max === "number" ? max : parseFloat(max);
214
- return !isNaN(numValue) && !isNaN(maxValue) && numValue <= maxValue;
215
- }
216
- function validateEmail(value) {
217
- if (isEmpty(value)) return true;
218
- if (typeof value !== "string") return false;
219
- const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
220
- return emailRegex.test(value);
221
- }
222
- function validateURL(value) {
223
- if (isEmpty(value)) return true;
224
- if (typeof value !== "string") return false;
225
- try {
226
- const url = new URL(value);
227
- return url.protocol === "http:" || url.protocol === "https:";
228
- } catch {
229
- return false;
230
- }
231
- }
232
- function validatePhoneNumber(value) {
233
- if (isEmpty(value)) return true;
234
- if (typeof value !== "string") return false;
235
- const phoneRegex = /^[\+]?[(]?[0-9]{1,4}[)]?[-\s\.]?[(]?[0-9]{1,4}[)]?[-\s\.]?[0-9]{1,4}[-\s\.]?[0-9]{1,9}$/;
236
- return phoneRegex.test(value.replace(/\s/g, ""));
237
- }
238
- function validateDate(value) {
239
- if (isEmpty(value)) return true;
240
- if (typeof value !== "string") return false;
241
- const date = new Date(value);
242
- return !isNaN(date.getTime());
243
- }
244
- function validateTime(value) {
245
- if (isEmpty(value)) return true;
246
- if (typeof value !== "string") return false;
247
- const timeRegex = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/;
248
- return timeRegex.test(value);
249
- }
250
- function validateColor(value) {
251
- if (isEmpty(value)) return true;
252
- if (typeof value !== "string") return false;
253
- const hexRegex = /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/;
254
- return hexRegex.test(value);
255
- }
256
- function validateZipCode(value) {
257
- if (isEmpty(value)) return true;
258
- if (typeof value !== "string") return false;
259
- const usZipRegex = /^\d{5}(-\d{4})?$/;
260
- const ukPostcodeRegex = /^[A-Z]{1,2}\d{1,2}[A-Z]?\s?\d[A-Z]{2}$/i;
261
- const dePostcodeRegex = /^\d{5}$/;
262
- return usZipRegex.test(value) || ukPostcodeRegex.test(value) || dePostcodeRegex.test(value);
263
- }
264
- function validateCreditCard(value) {
265
- if (isEmpty(value)) return true;
266
- if (typeof value !== "string") return false;
267
- const cleaned = value.replace(/[\s-]/g, "");
268
- if (!/^\d{13,19}$/.test(cleaned)) return false;
269
- let sum = 0;
270
- let isEven = false;
271
- for (let i = cleaned.length - 1; i >= 0; i--) {
272
- let digit = parseInt(cleaned.charAt(i), 10);
273
- if (isEven) {
274
- digit *= 2;
275
- if (digit > 9) {
276
- digit -= 9;
277
- }
278
- }
279
- sum += digit;
280
- isEven = !isEven;
281
- }
282
- return sum % 10 === 0;
283
- }
284
- function validateIPAddress(value) {
285
- if (isEmpty(value)) return true;
286
- if (typeof value !== "string") return false;
287
- const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
288
- const ipv6Regex = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/i;
289
- return ipv4Regex.test(value) || ipv6Regex.test(value);
290
- }
291
- var TYPE_ERROR_MESSAGES = {
292
- zipcode: "Please enter a valid ZIP/postal code",
293
- creditcard: "Please enter a valid credit card number",
294
- ip: "Please enter a valid IP address"
295
- };
296
- function getAllBlocksFromPage(blocks) {
297
- const allBlocks = [];
298
- for (const block of blocks) {
299
- allBlocks.push(block);
300
- if (block.config.blocks && Array.isArray(block.config.blocks)) {
301
- const nestedBlocks = block.config.blocks.map(
302
- (b) => ({
303
- id: b.id,
304
- type: b.type,
305
- config: b.config || {}
306
- })
307
- );
308
- allBlocks.push(...getAllBlocksFromPage(nestedBlocks));
309
- }
310
- }
311
- return allBlocks;
312
- }
313
- function validatePage(page, formValues, settings) {
314
- const errors = {};
315
- const allBlocks = getAllBlocksFromPage(page.blocks);
316
- const errorMessages = settings?.error_messages || {};
317
- for (const block of allBlocks) {
318
- if (block.type === "form-button" || block.type === "form-inline-group") {
319
- continue;
320
- }
321
- const fieldId = typeof block.config.field_id === "string" ? block.config.field_id : block.id;
322
- const value = formValues[fieldId];
323
- const inputType = typeof block.config.input_type === "string" ? block.config.input_type : "";
324
- if (block.config.required && isEmpty(value)) {
325
- errors[fieldId] = errorMessages.required || "This field is required";
326
- continue;
327
- }
328
- if (block.config.check_required && block.type === "form-checkbox") {
329
- const isChecked = value === true || Array.isArray(value) && value.length > 0;
330
- if (!isChecked) {
331
- errors[fieldId] = errorMessages.check_required || "You must check this box to continue";
332
- continue;
333
- }
334
- }
335
- if (block.type === "form-checkbox" && block.config.options) {
336
- const options = block.config.options;
337
- const checkedValues = Array.isArray(value) ? value : [];
338
- for (const option of options) {
339
- if (option.check_required && !checkedValues.includes(option.value)) {
340
- errors[fieldId] = `You must check "${option.label}" to continue`;
341
- break;
342
- }
343
- }
344
- if (errors[fieldId]) {
345
- continue;
346
- }
347
- }
348
- if (isEmpty(value)) {
349
- continue;
350
- }
351
- switch (inputType) {
352
- case "email":
353
- if (!validateEmail(value)) {
354
- errors[fieldId] = errorMessages.invalid_email || "Please enter a valid email address";
355
- continue;
356
- }
357
- break;
358
- case "url":
359
- if (!validateURL(value)) {
360
- errors[fieldId] = errorMessages.invalid_url || "Please enter a valid URL (e.g., https://example.com)";
361
- continue;
362
- }
363
- break;
364
- case "tel":
365
- if (!validatePhoneNumber(value)) {
366
- errors[fieldId] = errorMessages.invalid_phone || "Please enter a valid phone number";
367
- continue;
368
- }
369
- break;
370
- case "date":
371
- if (!validateDate(value)) {
372
- errors[fieldId] = errorMessages.invalid_date || "Please enter a valid date";
373
- continue;
374
- }
375
- break;
376
- case "time":
377
- if (!validateTime(value)) {
378
- errors[fieldId] = errorMessages.invalid_time || "Please enter a valid time";
379
- continue;
380
- }
381
- break;
382
- case "color":
383
- if (!validateColor(value)) {
384
- errors[fieldId] = errorMessages.invalid_color || "Please enter a valid color code (e.g., #FF0000)";
385
- continue;
386
- }
387
- break;
388
- }
389
- if (block.config.validation_type) {
390
- const validationType = block.config.validation_type;
391
- switch (validationType) {
392
- case "zipcode":
393
- if (!validateZipCode(value)) {
394
- errors[fieldId] = TYPE_ERROR_MESSAGES.zipcode;
395
- continue;
396
- }
397
- break;
398
- case "creditcard":
399
- if (!validateCreditCard(value)) {
400
- errors[fieldId] = TYPE_ERROR_MESSAGES.creditcard;
401
- continue;
402
- }
403
- break;
404
- case "ip":
405
- if (!validateIPAddress(value)) {
406
- errors[fieldId] = TYPE_ERROR_MESSAGES.ip;
407
- continue;
408
- }
409
- break;
410
- }
411
- }
412
- if (block.config.minLength && !validateMinLength(value, block.config.minLength)) {
413
- errors[fieldId] = errorMessages.min_length ? interpolateMessage(errorMessages.min_length, {
414
- min: block.config.minLength
415
- }) : "This field must be at least " + block.config.minLength + " characters long";
416
- continue;
417
- }
418
- if (block.config.maxLength && !validateMaxLength(value, block.config.maxLength)) {
419
- errors[fieldId] = errorMessages.max_length ? interpolateMessage(errorMessages.max_length, {
420
- max: block.config.maxLength
421
- }) : "This field must be less than " + block.config.maxLength + " characters long";
422
- continue;
423
- }
424
- if (block.config.min !== void 0 && !validateMinValue(value, block.config.min)) {
425
- errors[fieldId] = errorMessages.min_value ? interpolateMessage(errorMessages.min_value, { min: block.config.min }) : "Value must be at least " + block.config.min;
426
- continue;
427
- }
428
- if (block.config.max !== void 0 && !validateMaxValue(value, block.config.max)) {
429
- errors[fieldId] = errorMessages.max_value ? interpolateMessage(errorMessages.max_value, { max: block.config.max }) : "Value must be no more than " + block.config.max;
430
- continue;
431
- }
432
- if (block.config.pattern && !validatePattern(value, block.config.pattern)) {
433
- const patternMessage = block.config.pattern_message ? block.config.pattern_message : "This field has an invalid format";
434
- errors[fieldId] = patternMessage;
435
- continue;
436
- }
437
- }
438
- return Object.keys(errors).length > 0 ? errors : true;
439
- }
440
- function validateField(block, value, settings) {
441
- if (block.type === "form-button" || block.type === "form-inline-group") {
442
- return void 0;
443
- }
444
- const errorMessages = settings?.error_messages || {};
445
- const inputType = typeof block.config.input_type === "string" ? block.config.input_type : "";
446
- if (block.config.required && isEmpty(value)) {
447
- return errorMessages.required || "This field is required";
448
- }
449
- if (block.config.check_required && block.type === "form-checkbox") {
450
- const isChecked = value === true || Array.isArray(value) && value.length > 0;
451
- if (!isChecked) {
452
- return errorMessages.check_required || "You must check this box to continue";
453
- }
454
- }
455
- if (block.type === "form-checkbox" && block.config.options) {
456
- const options = block.config.options;
457
- const checkedValues = Array.isArray(value) ? value : [];
458
- for (const option of options) {
459
- if (option.check_required && !checkedValues.includes(option.value)) {
460
- return `You must check "${option.label}" to continue`;
461
- }
462
- }
463
- }
464
- if (isEmpty(value)) {
465
- return void 0;
466
- }
467
- switch (inputType) {
468
- case "email":
469
- if (!validateEmail(value)) {
470
- return errorMessages.invalid_email || "Please enter a valid email address";
471
- }
472
- break;
473
- case "url":
474
- if (!validateURL(value)) {
475
- return errorMessages.invalid_url || "Please enter a valid URL (e.g., https://example.com)";
476
- }
477
- break;
478
- case "tel":
479
- if (!validatePhoneNumber(value)) {
480
- return errorMessages.invalid_phone || "Please enter a valid phone number";
481
- }
482
- break;
483
- case "date":
484
- if (!validateDate(value)) {
485
- return errorMessages.invalid_date || "Please enter a valid date";
486
- }
487
- break;
488
- case "time":
489
- if (!validateTime(value)) {
490
- return errorMessages.invalid_time || "Please enter a valid time";
491
- }
492
- break;
493
- case "color":
494
- if (!validateColor(value)) {
495
- return errorMessages.invalid_color || "Please enter a valid color code (e.g., #FF0000)";
496
- }
497
- break;
498
- }
499
- if (block.config.validation_type) {
500
- const validationType = block.config.validation_type;
501
- switch (validationType) {
502
- case "zipcode":
503
- if (!validateZipCode(value)) {
504
- return TYPE_ERROR_MESSAGES.zipcode;
505
- }
506
- break;
507
- case "creditcard":
508
- if (!validateCreditCard(value)) {
509
- return TYPE_ERROR_MESSAGES.creditcard;
510
- }
511
- break;
512
- case "ip":
513
- if (!validateIPAddress(value)) {
514
- return TYPE_ERROR_MESSAGES.ip;
515
- }
516
- break;
517
- }
518
- }
519
- if (block.config.minLength && !validateMinLength(value, block.config.minLength)) {
520
- return errorMessages.min_length ? interpolateMessage(errorMessages.min_length, {
521
- min: block.config.minLength
522
- }) : "This field must be at least " + block.config.minLength + " characters long";
523
- }
524
- if (block.config.maxLength && !validateMaxLength(value, block.config.maxLength)) {
525
- return errorMessages.max_length ? interpolateMessage(errorMessages.max_length, {
526
- max: block.config.maxLength
527
- }) : "This field must be less than " + block.config.maxLength + " characters long";
528
- }
529
- if (block.config.min !== void 0 && !validateMinValue(value, block.config.min)) {
530
- return errorMessages.min_value ? interpolateMessage(errorMessages.min_value, { min: block.config.min }) : "Value must be at least " + block.config.min;
531
- }
532
- if (block.config.max !== void 0 && !validateMaxValue(value, block.config.max)) {
533
- return errorMessages.max_value ? interpolateMessage(errorMessages.max_value, { max: block.config.max }) : "Value must be no more than " + block.config.max;
534
- }
535
- if (block.config.pattern && !validatePattern(value, block.config.pattern)) {
536
- const patternMessage = block.config.pattern_message ? block.config.pattern_message : "This field has an invalid format";
537
- return patternMessage;
538
- }
539
- return void 0;
540
- }
541
-
542
- // src/use-form-action.tsx
543
- function useFormAction(id) {
544
- const {
545
- formId,
546
- document,
547
- formValues,
548
- setErrors,
549
- loading,
550
- currentPage,
551
- setCurrentPage,
552
- setFormValues,
553
- setGlobalError,
554
- settings,
555
- analyticsSettings,
556
- onAnalyticsEvent,
557
- formName,
558
- environmentVariantId,
559
- formVariantId,
560
- locale
561
- } = useForm();
562
- const block = useMemo(() => {
563
- return findBlockInDocument(id, document);
564
- }, [document, id]);
565
- const navigate = useCallback(
566
- (to) => {
567
- const currentPageValid = to === "first" || validatePage(currentPage, formValues, settings);
568
- if (currentPageValid !== true) {
569
- setErrors(currentPageValid);
570
- return;
571
- }
572
- setErrors(null);
573
- setGlobalError(null);
574
- let targetPage = null;
575
- switch (to) {
576
- case "prev":
577
- const previousPage = getPreviousPage(currentPage, document);
578
- if (previousPage) {
579
- targetPage = previousPage;
580
- setCurrentPage(previousPage);
581
- } else {
582
- console.error("[ERROR] No previous page found.");
583
- setGlobalError("Configuration Error: No previous page found.");
584
- }
585
- break;
586
- case "next":
587
- const nextPage = getNextPage(currentPage, document);
588
- if (nextPage) {
589
- targetPage = nextPage;
590
- setCurrentPage(nextPage);
591
- } else {
592
- console.error("[ERROR] No next page found.");
593
- setGlobalError("Configuration Error: No next page found.");
594
- }
595
- break;
596
- case "first":
597
- targetPage = document.pages[0];
598
- setCurrentPage(document.pages[0]);
599
- break;
600
- case "last":
601
- targetPage = document.pages[document.pages.length - 1];
602
- setCurrentPage(document.pages[document.pages.length - 1]);
603
- break;
604
- default:
605
- targetPage = document.pages.find((page) => page.id === to) || document.pages[0];
606
- setCurrentPage(targetPage);
607
- break;
608
- }
609
- if (targetPage && typeof window !== "undefined" && window.parent) {
610
- window.parent.postMessage(
611
- { type: "FORM_PAGE_CHANGED", pageId: targetPage.id },
612
- window.location.origin
613
- );
614
- }
615
- if (targetPage && analyticsSettings?.track_page_navigation && onAnalyticsEvent) {
616
- const targetIndex = document.pages.findIndex(
617
- (p) => p.id === targetPage.id
618
- );
619
- onAnalyticsEvent("form_page_change", {
620
- form_id: formId,
621
- form_name: formName,
622
- page_id: targetPage.id,
623
- page_index: targetIndex,
624
- total_pages: document.pages.length
625
- });
626
- }
627
- },
628
- [
629
- currentPage,
630
- setCurrentPage,
631
- document,
632
- formId,
633
- formValues,
634
- settings,
635
- setErrors,
636
- setGlobalError,
637
- analyticsSettings,
638
- onAnalyticsEvent,
639
- formName
640
- ]
641
- );
642
- const submit = useCallback(async () => {
643
- if (!block) {
644
- return;
645
- }
646
- let allErrors = {};
647
- let hasErrors = false;
648
- for (const page of document.pages) {
649
- const result = validatePage(page, formValues, settings);
650
- if (result !== true) {
651
- allErrors = { ...allErrors, ...result };
652
- hasErrors = true;
653
- }
654
- }
655
- if (hasErrors) {
656
- setErrors(allErrors);
657
- if (analyticsSettings?.track_form_error && onAnalyticsEvent) {
658
- onAnalyticsEvent("form_error", {
659
- form_id: formId,
660
- form_name: formName,
661
- error_count: Object.keys(allErrors).length,
662
- error_fields: Object.keys(allErrors)
663
- });
664
- }
665
- return;
666
- }
667
- setErrors(null);
668
- setGlobalError(null);
669
- try {
670
- const buttonTypeOverride = block && typeof block.config.submission_type === "string" && block.config.submission_type ? block.config.submission_type : void 0;
671
- const submissionData = {
672
- form_id: formId,
673
- type: buttonTypeOverride ?? settings?.form_type ?? "General",
674
- locale: locale ?? "en",
675
- data: formValues,
676
- environment_type: "page",
677
- environment_id: formId,
678
- environment_path: typeof window !== "undefined" ? window.location.pathname : void 0,
679
- form_variant_id: formVariantId ?? "",
680
- environment_variant_id: environmentVariantId ?? ""
681
- };
682
- const response = await fetch("/api/submission", {
683
- method: "POST",
684
- headers: {
685
- "Content-Type": "application/json"
686
- },
687
- body: JSON.stringify(submissionData)
688
- });
689
- const result = await response.json();
690
- if (!response.ok || !result.success) {
691
- setGlobalError(
692
- result.message || "Failed to submit form. Please try again."
693
- );
694
- return;
695
- }
696
- if (analyticsSettings?.track_form_submit && onAnalyticsEvent) {
697
- onAnalyticsEvent("form_submit", {
698
- form_id: formId,
699
- form_name: formName,
700
- total_pages: document.pages.length,
701
- custom_event_name: analyticsSettings.submit_event_name,
702
- target_providers: analyticsSettings.target_providers
703
- });
704
- }
705
- } catch (error) {
706
- setGlobalError("An error occurred while submitting the form.");
707
- }
708
- return Promise.resolve();
709
- }, [
710
- block,
711
- document,
712
- formId,
713
- formValues,
714
- setErrors,
715
- setGlobalError,
716
- settings,
717
- analyticsSettings,
718
- onAnalyticsEvent,
719
- formName,
720
- locale,
721
- formVariantId,
722
- environmentVariantId
723
- ]);
724
- const reset = useCallback(async () => {
725
- if (!block) {
726
- return;
727
- }
728
- setFormValues({});
729
- setErrors(null);
730
- setGlobalError(null);
731
- return Promise.resolve();
732
- }, [block, setFormValues, setErrors, setGlobalError]);
733
- const executeAction = useCallback(async () => {
734
- if (!block) {
735
- return;
736
- }
737
- const action = block.config.action;
738
- let to = String(block.config.navigate_to_page || "next");
739
- if (action === "reset") {
740
- await reset();
741
- to = "first";
742
- navigate(to);
743
- } else if (action === "submit") {
744
- await submit();
745
- navigate(to);
746
- } else if (action === "navigate") {
747
- navigate(to);
748
- }
749
- return Promise.resolve();
750
- }, [block, navigate, submit, reset]);
751
- const display = useMemo(() => {
752
- if (!block) {
753
- return "none";
754
- }
755
- const rules = block.config.advanced_options?.rules;
756
- if (!rules) {
757
- return "block";
758
- }
759
- return evaluateVisibilityRules(rules, formValues);
760
- }, [block, formValues]);
761
- const disabled = useMemo(() => {
762
- if (!block) {
763
- return false;
764
- }
765
- if (loading) {
766
- return true;
767
- }
768
- if (typeof block.config.disabled === "boolean" && block.config.disabled) {
769
- return block.config.disabled;
770
- }
771
- const rules = block.config.advanced_options?.rules;
772
- if (!rules) {
773
- return false;
774
- }
775
- return evaluateDisabledRules(rules, formValues);
776
- }, [block, formValues, loading]);
777
- if (!block) {
778
- return null;
779
- }
780
- return {
781
- variant: typeof block.config.variant === "string" ? block.config.variant : "primary",
782
- size: typeof block.config.size === "string" ? block.config.size : "md",
783
- disabled,
784
- loading,
785
- display,
786
- text: typeof block.config.text === "string" ? block.config.text : typeof block.config.label === "string" ? block.config.label : "",
787
- onClick: executeAction
788
- };
789
- }
790
- function useFormField(id) {
791
- const {
792
- formId,
793
- document,
794
- settings,
795
- loading,
796
- formValues,
797
- setFormValues,
798
- errors,
799
- setErrors,
800
- analyticsSettings,
801
- onAnalyticsEvent,
802
- formName,
803
- hasStarted,
804
- setHasStarted
805
- } = useForm();
806
- const block = useMemo(() => {
807
- return findBlockInDocument(id, document);
808
- }, [document, id]);
809
- const fieldId = useMemo(() => {
810
- if (!block) return null;
811
- return typeof block.config.field_id === "string" ? block.config.field_id : block.id;
812
- }, [block]);
813
- const initialValue = useMemo(() => {
814
- if (!block) {
815
- return void 0;
816
- }
817
- if (typeof block.config.initialValue === "string") {
818
- return String(block.config.initialValue);
819
- } else if (typeof block.config.initialValue === "number") {
820
- return Number(block.config.initialValue);
821
- } else if (typeof block.config.initialValue === "boolean") {
822
- return Boolean(block.config.initialValue);
823
- }
824
- if (block.config.initialValue !== void 0) {
825
- return block.config.initialValue;
826
- }
827
- return void 0;
828
- }, [block]);
829
- const value = useMemo(() => {
830
- if (!fieldId) {
831
- return void 0;
832
- }
833
- return formValues[fieldId] ?? initialValue;
834
- }, [fieldId, formValues, initialValue]);
835
- const setValue = useCallback(
836
- (value2) => {
837
- if (!fieldId) {
838
- return;
839
- }
840
- setFormValues((prev) => ({ ...prev, [fieldId]: value2 }));
841
- setErrors((prev) => {
842
- if (!prev || !prev[fieldId]) return prev;
843
- const { [fieldId]: _, ...rest } = prev;
844
- return Object.keys(rest).length > 0 ? rest : null;
845
- });
846
- if (!hasStarted && analyticsSettings?.track_form_start && onAnalyticsEvent) {
847
- setHasStarted(true);
848
- onAnalyticsEvent("form_start", {
849
- form_id: formId,
850
- form_name: formName
851
- });
852
- }
853
- },
854
- [
855
- fieldId,
856
- setFormValues,
857
- setErrors,
858
- hasStarted,
859
- setHasStarted,
860
- analyticsSettings,
861
- onAnalyticsEvent,
862
- formId,
863
- formName
864
- ]
865
- );
866
- const handleBlur = useCallback(() => {
867
- if (!(settings.validate_on_blur ?? false) || !block || !fieldId) {
868
- return;
869
- }
870
- const currentValue = formValues[fieldId] ?? initialValue;
871
- const error = validateField(block, currentValue, settings);
872
- if (error) {
873
- setErrors((prev) => ({ ...prev, [fieldId]: error }));
874
- } else {
875
- setErrors((prev) => {
876
- if (!prev || !prev[fieldId]) return prev;
877
- const { [fieldId]: _, ...rest } = prev;
878
- return Object.keys(rest).length > 0 ? rest : null;
879
- });
880
- }
881
- }, [settings, block, fieldId, formValues, initialValue, setErrors]);
882
- const display = useMemo(() => {
883
- if (!block) {
884
- return "none";
885
- }
886
- const rules = block.config.advanced_options?.rules;
887
- if (!rules) {
888
- return "block";
889
- }
890
- return evaluateVisibilityRules(rules, formValues);
891
- }, [block, formValues]);
892
- const disabled = useMemo(() => {
893
- if (!block) {
894
- return false;
895
- }
896
- if (loading) {
897
- return true;
898
- }
899
- if (typeof block.config.disabled === "boolean" && block.config.disabled) {
900
- return block.config.disabled;
901
- }
902
- const rules = block.config.advanced_options?.rules;
903
- if (!rules) {
904
- return false;
905
- }
906
- return evaluateDisabledRules(rules, formValues);
907
- }, [block, formValues, loading]);
908
- if (!block || !fieldId) {
909
- return null;
910
- }
911
- return {
912
- name: fieldId,
913
- label: typeof block.config.label === "string" ? block.config.label : "",
914
- type: typeof block.config.input_type === "string" ? block.config.input_type : typeof block.config.type === "string" ? block.config.type : "",
915
- helperText: typeof block.config.help_text === "string" ? block.config.help_text : typeof block.config.helperText === "string" ? block.config.helperText : "",
916
- placeholder: typeof block.config.placeholder === "string" ? block.config.placeholder : null,
917
- additionalSettings: {
918
- rows: typeof block.config.rows === "number" ? block.config.rows : void 0,
919
- options: block.config.options || void 0,
920
- multiple: typeof block.config.multiple === "boolean" ? block.config.multiple : void 0,
921
- min: typeof block.config.min === "number" ? block.config.min : void 0,
922
- max: typeof block.config.max === "number" ? block.config.max : void 0,
923
- step: typeof block.config.step === "number" ? block.config.step : void 0,
924
- blocks: block.config.blocks || void 0,
925
- gap: block.config.gap || void 0
926
- },
927
- loading,
928
- disabled,
929
- display,
930
- required: typeof block.config.required === "boolean" ? block.config.required : false,
931
- error: errors?.[fieldId] || errors?.[block.id] || void 0,
932
- value,
933
- onChange: setValue,
934
- onBlur: handleBlur
935
- };
936
- }
937
-
938
- export { FormProvider, useForm, useFormAction, useFormField };
939
- //# sourceMappingURL=index.js.map
1
+ export * from "./form-context";
2
+ export * from "./use-form-action";
3
+ export * from "./use-form-field";
940
4
  //# sourceMappingURL=index.js.map