@landform.io/sdk 0.1.2

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 (63) hide show
  1. package/dist/api/index.cjs +17 -0
  2. package/dist/api/index.cjs.map +1 -0
  3. package/dist/api/index.d.cts +48 -0
  4. package/dist/api/index.d.ts +48 -0
  5. package/dist/api/index.js +4 -0
  6. package/dist/api/index.js.map +1 -0
  7. package/dist/chunk-52W6RI6C.cjs +4 -0
  8. package/dist/chunk-52W6RI6C.cjs.map +1 -0
  9. package/dist/chunk-DASQI7IA.cjs +4 -0
  10. package/dist/chunk-DASQI7IA.cjs.map +1 -0
  11. package/dist/chunk-EL7YGOTY.js +3 -0
  12. package/dist/chunk-EL7YGOTY.js.map +1 -0
  13. package/dist/chunk-GVOTY5NG.js +3 -0
  14. package/dist/chunk-GVOTY5NG.js.map +1 -0
  15. package/dist/chunk-HUIMGLDC.js +488 -0
  16. package/dist/chunk-HUIMGLDC.js.map +1 -0
  17. package/dist/chunk-I6L5OCM3.js +1657 -0
  18. package/dist/chunk-I6L5OCM3.js.map +1 -0
  19. package/dist/chunk-IQXKFO6Q.cjs +55 -0
  20. package/dist/chunk-IQXKFO6Q.cjs.map +1 -0
  21. package/dist/chunk-P2GUKJHH.cjs +117 -0
  22. package/dist/chunk-P2GUKJHH.cjs.map +1 -0
  23. package/dist/chunk-PDUJU32P.js +687 -0
  24. package/dist/chunk-PDUJU32P.js.map +1 -0
  25. package/dist/chunk-PKHTPGWQ.js +114 -0
  26. package/dist/chunk-PKHTPGWQ.js.map +1 -0
  27. package/dist/chunk-V7WAYO2Q.js +48 -0
  28. package/dist/chunk-V7WAYO2Q.js.map +1 -0
  29. package/dist/chunk-WHV333XL.cjs +1684 -0
  30. package/dist/chunk-WHV333XL.cjs.map +1 -0
  31. package/dist/chunk-WIFNU3FA.cjs +497 -0
  32. package/dist/chunk-WIFNU3FA.cjs.map +1 -0
  33. package/dist/chunk-ZLOP4BTK.cjs +695 -0
  34. package/dist/chunk-ZLOP4BTK.cjs.map +1 -0
  35. package/dist/components/index.cjs +99 -0
  36. package/dist/components/index.cjs.map +1 -0
  37. package/dist/components/index.d.cts +166 -0
  38. package/dist/components/index.d.ts +166 -0
  39. package/dist/components/index.js +6 -0
  40. package/dist/components/index.js.map +1 -0
  41. package/dist/hooks/index.cjs +35 -0
  42. package/dist/hooks/index.cjs.map +1 -0
  43. package/dist/hooks/index.d.cts +75 -0
  44. package/dist/hooks/index.d.ts +75 -0
  45. package/dist/hooks/index.js +6 -0
  46. package/dist/hooks/index.js.map +1 -0
  47. package/dist/index-DoRZImTl.d.cts +590 -0
  48. package/dist/index-DoRZImTl.d.ts +590 -0
  49. package/dist/index.cjs +186 -0
  50. package/dist/index.cjs.map +1 -0
  51. package/dist/index.d.cts +8 -0
  52. package/dist/index.d.ts +8 -0
  53. package/dist/index.js +9 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/theme/index.cjs +61 -0
  56. package/dist/theme/index.cjs.map +1 -0
  57. package/dist/theme/index.d.cts +65 -0
  58. package/dist/theme/index.d.ts +65 -0
  59. package/dist/theme/index.js +4 -0
  60. package/dist/theme/index.js.map +1 -0
  61. package/dist/useForm-D1mB6REv.d.ts +48 -0
  62. package/dist/useForm-kF8xK1mJ.d.cts +48 -0
  63. package/package.json +101 -0
@@ -0,0 +1,497 @@
1
+ 'use strict';
2
+
3
+ var chunkZLOP4BTK_cjs = require('./chunk-ZLOP4BTK.cjs');
4
+ var chunkP2GUKJHH_cjs = require('./chunk-P2GUKJHH.cjs');
5
+ var react = require('react');
6
+ var nanoid = require('nanoid');
7
+
8
+ // src/utils/storage.ts
9
+ var AUTOSAVE_PREFIX = "lf-autosave-";
10
+ var SUBMITTED_PREFIX = "lf-submitted-";
11
+ var COOKIE_CONSENT_KEY = "lf-cookie-consent";
12
+ function saveProgress(projectId, answers, currentIndex, responseId) {
13
+ if (typeof window === "undefined") return;
14
+ try {
15
+ const data = {
16
+ answers,
17
+ currentIndex,
18
+ responseId,
19
+ timestamp: Date.now()
20
+ };
21
+ localStorage.setItem(AUTOSAVE_PREFIX + projectId, JSON.stringify(data));
22
+ } catch (error) {
23
+ console.error("Error saving form progress:", error);
24
+ }
25
+ }
26
+ function loadProgress(projectId) {
27
+ if (typeof window === "undefined") return null;
28
+ try {
29
+ const stored = localStorage.getItem(AUTOSAVE_PREFIX + projectId);
30
+ if (!stored) return null;
31
+ const data = JSON.parse(stored);
32
+ const maxAge = 7 * 24 * 60 * 60 * 1e3;
33
+ if (Date.now() - data.timestamp > maxAge) {
34
+ clearProgress(projectId);
35
+ return null;
36
+ }
37
+ return data;
38
+ } catch (error) {
39
+ console.error("Error loading form progress:", error);
40
+ return null;
41
+ }
42
+ }
43
+ function clearProgress(projectId) {
44
+ if (typeof window === "undefined") return;
45
+ try {
46
+ localStorage.removeItem(AUTOSAVE_PREFIX + projectId);
47
+ } catch (error) {
48
+ console.error("Error clearing form progress:", error);
49
+ }
50
+ }
51
+ function markAsSubmitted(projectId) {
52
+ if (typeof window === "undefined") return;
53
+ try {
54
+ localStorage.setItem(SUBMITTED_PREFIX + projectId, String(Date.now()));
55
+ } catch (error) {
56
+ console.error("Error marking form as submitted:", error);
57
+ }
58
+ }
59
+ function hasSubmitted(projectId) {
60
+ if (typeof window === "undefined") return false;
61
+ try {
62
+ return localStorage.getItem(SUBMITTED_PREFIX + projectId) !== null;
63
+ } catch (error) {
64
+ console.error("Error checking submission status:", error);
65
+ return false;
66
+ }
67
+ }
68
+ function hasCookieConsent() {
69
+ if (typeof window === "undefined") return false;
70
+ try {
71
+ return localStorage.getItem(COOKIE_CONSENT_KEY) === "true";
72
+ } catch (error) {
73
+ console.error("Error checking cookie consent:", error);
74
+ return false;
75
+ }
76
+ }
77
+ function setCookieConsent(consented) {
78
+ if (typeof window === "undefined") return;
79
+ try {
80
+ if (consented) {
81
+ localStorage.setItem(COOKIE_CONSENT_KEY, "true");
82
+ } else {
83
+ localStorage.removeItem(COOKIE_CONSENT_KEY);
84
+ }
85
+ } catch (error) {
86
+ console.error("Error setting cookie consent:", error);
87
+ }
88
+ }
89
+
90
+ // src/hooks/useForm.ts
91
+ function validateFieldValue(field, value) {
92
+ if (!field.validations?.required) {
93
+ return true;
94
+ }
95
+ if (value === void 0 || value === null || value === "") {
96
+ return false;
97
+ }
98
+ if (Array.isArray(value) && value.length === 0) {
99
+ return false;
100
+ }
101
+ return true;
102
+ }
103
+ function useForm(options) {
104
+ const {
105
+ projectId,
106
+ content,
107
+ settings,
108
+ baseUrl = "",
109
+ initialAnswers = {},
110
+ onComplete,
111
+ onError
112
+ } = options;
113
+ const [responseId, setResponseId] = react.useState(null);
114
+ const [sessionId] = react.useState(() => nanoid.nanoid());
115
+ const [currentIndex, setCurrentIndex] = react.useState(0);
116
+ const [answers, setAnswers] = react.useState(initialAnswers);
117
+ const [errors, setErrors] = react.useState({});
118
+ const [isStarted, setIsStarted] = react.useState(false);
119
+ const [isCompleted, setIsCompleted] = react.useState(false);
120
+ const [isSubmitting, setIsSubmitting] = react.useState(false);
121
+ const [isDuplicateSubmission, setIsDuplicateSubmission] = react.useState(false);
122
+ const [captchaToken, setCaptchaToken] = react.useState(null);
123
+ const [showCaptcha, setShowCaptcha] = react.useState(false);
124
+ const client = react.useMemo(
125
+ () => new chunkP2GUKJHH_cjs.LandformClient({ baseUrl, projectId, onError }),
126
+ [baseUrl, projectId, onError]
127
+ );
128
+ const hasTrackedView = react.useRef(false);
129
+ const hasLoadedProgress = react.useRef(false);
130
+ const autosaveTimeoutRef = react.useRef(null);
131
+ react.useEffect(() => {
132
+ if (!hasTrackedView.current) {
133
+ hasTrackedView.current = true;
134
+ client.trackEvent({ event: "view", sessionId });
135
+ }
136
+ }, [client, sessionId]);
137
+ react.useEffect(() => {
138
+ if (settings.duplicatePrevention && hasSubmitted(projectId)) {
139
+ setIsDuplicateSubmission(true);
140
+ }
141
+ }, [projectId, settings.duplicatePrevention]);
142
+ react.useEffect(() => {
143
+ if (hasLoadedProgress.current || !settings.autosaveProgress) return;
144
+ hasLoadedProgress.current = true;
145
+ const savedProgress = loadProgress(projectId);
146
+ if (savedProgress) {
147
+ setAnswers(savedProgress.answers);
148
+ setCurrentIndex(savedProgress.currentIndex);
149
+ if (savedProgress.responseId) {
150
+ setResponseId(savedProgress.responseId);
151
+ setIsStarted(true);
152
+ }
153
+ }
154
+ }, [projectId, settings.autosaveProgress]);
155
+ react.useEffect(() => {
156
+ if (!settings.autosaveProgress || isCompleted) return;
157
+ if (autosaveTimeoutRef.current) {
158
+ clearTimeout(autosaveTimeoutRef.current);
159
+ }
160
+ autosaveTimeoutRef.current = setTimeout(() => {
161
+ saveProgress(projectId, answers, currentIndex, responseId);
162
+ }, 500);
163
+ return () => {
164
+ if (autosaveTimeoutRef.current) {
165
+ clearTimeout(autosaveTimeoutRef.current);
166
+ }
167
+ };
168
+ }, [projectId, answers, currentIndex, responseId, settings.autosaveProgress, isCompleted]);
169
+ const sequence = react.useMemo(() => {
170
+ const items = [];
171
+ for (const screen of content.welcomeScreens) {
172
+ items.push({ type: "welcome", screen });
173
+ }
174
+ content.fields.forEach((field, index) => {
175
+ items.push({ type: "field", field, index });
176
+ });
177
+ for (const screen of content.thankYouScreens) {
178
+ items.push({ type: "thankYou", screen });
179
+ }
180
+ return items;
181
+ }, [content]);
182
+ const currentItem = sequence[currentIndex] || null;
183
+ const totalFields = content.fields.length;
184
+ const answeredCount = Object.keys(answers).length;
185
+ const progress = totalFields > 0 ? answeredCount / totalFields * 100 : 0;
186
+ const firstFieldIndex = content.welcomeScreens.length;
187
+ const lastFieldIndex = firstFieldIndex + content.fields.length - 1;
188
+ const canGoBack = currentIndex > firstFieldIndex;
189
+ const canGoNext = currentIndex < sequence.length - 1;
190
+ const isOnWelcome = currentItem?.type === "welcome";
191
+ const isOnThankYou = currentItem?.type === "thankYou";
192
+ const isOnField = currentItem?.type === "field";
193
+ const start = react.useCallback(async () => {
194
+ try {
195
+ const result = await client.startResponse({ sessionId });
196
+ setResponseId(result.id);
197
+ setIsStarted(true);
198
+ client.trackEvent({ event: "start", sessionId });
199
+ if (currentItem?.type === "welcome") {
200
+ setCurrentIndex((prev) => prev + 1);
201
+ }
202
+ } catch (error) {
203
+ onError?.(error);
204
+ }
205
+ }, [client, sessionId, currentItem, onError]);
206
+ const validateCurrentWithValue = react.useCallback((pendingValue) => {
207
+ if (currentItem?.type !== "field") return true;
208
+ const field = currentItem.field;
209
+ const value = pendingValue !== void 0 ? pendingValue : answers[field.ref];
210
+ const isValid = validateFieldValue(field, value);
211
+ if (!isValid) {
212
+ const requiredText = settings.systemMessages?.requiredText || "This field is required";
213
+ setErrors((prev) => ({
214
+ ...prev,
215
+ [field.ref]: requiredText
216
+ }));
217
+ }
218
+ return isValid;
219
+ }, [currentItem, answers, settings.systemMessages?.requiredText]);
220
+ const validateCurrent = react.useCallback(() => {
221
+ return validateCurrentWithValue();
222
+ }, [validateCurrentWithValue]);
223
+ const submit = react.useCallback(async () => {
224
+ if (!responseId) return;
225
+ if (settings.captchaEnabled && !captchaToken) {
226
+ setShowCaptcha(true);
227
+ return;
228
+ }
229
+ setIsSubmitting(true);
230
+ try {
231
+ await client.completeResponse({
232
+ responseId,
233
+ sessionId,
234
+ answers,
235
+ captchaToken: captchaToken || void 0
236
+ });
237
+ setIsCompleted(true);
238
+ setCurrentIndex(sequence.length - 1);
239
+ clearProgress(projectId);
240
+ if (settings.duplicatePrevention) {
241
+ markAsSubmitted(projectId);
242
+ }
243
+ onComplete?.(answers);
244
+ } catch (error) {
245
+ onError?.(error);
246
+ } finally {
247
+ setIsSubmitting(false);
248
+ setShowCaptcha(false);
249
+ }
250
+ }, [responseId, sessionId, answers, captchaToken, client, sequence.length, projectId, settings.captchaEnabled, settings.duplicatePrevention, onComplete, onError]);
251
+ const next = react.useCallback(async (pendingValue) => {
252
+ if (currentItem?.type === "field") {
253
+ const isValid = validateCurrentWithValue(pendingValue);
254
+ if (!isValid) {
255
+ return;
256
+ }
257
+ if (currentIndex === lastFieldIndex) {
258
+ if (pendingValue !== void 0) {
259
+ setAnswers((prev) => ({ ...prev, [currentItem.field.ref]: pendingValue }));
260
+ }
261
+ await submit();
262
+ return;
263
+ }
264
+ const answersToSave = pendingValue !== void 0 ? { ...answers, [currentItem.field.ref]: pendingValue } : answers;
265
+ if (responseId) {
266
+ client.updateAnswers({
267
+ responseId,
268
+ sessionId,
269
+ answers: answersToSave,
270
+ lastFieldRef: currentItem.field.ref
271
+ }).catch((error) => {
272
+ onError?.(error);
273
+ });
274
+ }
275
+ }
276
+ setCurrentIndex((prev) => Math.min(prev + 1, sequence.length - 1));
277
+ }, [
278
+ currentItem,
279
+ currentIndex,
280
+ lastFieldIndex,
281
+ sequence.length,
282
+ responseId,
283
+ sessionId,
284
+ answers,
285
+ client,
286
+ onError,
287
+ validateCurrentWithValue,
288
+ submit
289
+ ]);
290
+ const previous = react.useCallback(() => {
291
+ setCurrentIndex((prev) => Math.max(prev - 1, firstFieldIndex));
292
+ }, [firstFieldIndex]);
293
+ const goTo = react.useCallback(
294
+ (index) => {
295
+ if (index >= 0 && index < sequence.length) {
296
+ setCurrentIndex(index);
297
+ }
298
+ },
299
+ [sequence.length]
300
+ );
301
+ const setAnswer = react.useCallback((fieldRef, value) => {
302
+ setAnswers((prev) => ({ ...prev, [fieldRef]: value }));
303
+ setErrors((prev) => {
304
+ const newErrors = { ...prev };
305
+ delete newErrors[fieldRef];
306
+ return newErrors;
307
+ });
308
+ }, []);
309
+ const reset = react.useCallback(() => {
310
+ setResponseId(null);
311
+ setCurrentIndex(0);
312
+ setAnswers(initialAnswers);
313
+ setErrors({});
314
+ setIsStarted(false);
315
+ setIsCompleted(false);
316
+ setIsSubmitting(false);
317
+ }, [initialAnswers]);
318
+ react.useEffect(() => {
319
+ if (content.welcomeScreens.length === 0 && !isStarted) {
320
+ start();
321
+ }
322
+ }, [content.welcomeScreens.length, isStarted, start]);
323
+ return {
324
+ // State
325
+ currentIndex,
326
+ currentItem,
327
+ answers,
328
+ errors,
329
+ isStarted,
330
+ isCompleted,
331
+ isSubmitting,
332
+ responseId,
333
+ sessionId,
334
+ isDuplicateSubmission,
335
+ captchaToken,
336
+ showCaptcha,
337
+ // Computed
338
+ sequence,
339
+ totalFields,
340
+ answeredCount,
341
+ progress,
342
+ canGoBack,
343
+ canGoNext,
344
+ isOnWelcome,
345
+ isOnThankYou,
346
+ isOnField,
347
+ // Actions
348
+ start,
349
+ next,
350
+ previous,
351
+ goTo,
352
+ setAnswer,
353
+ validateCurrent,
354
+ submit,
355
+ reset,
356
+ setCaptchaToken
357
+ };
358
+ }
359
+ var ThemeContext = react.createContext(null);
360
+ function useThemeContext() {
361
+ const theme = react.useContext(ThemeContext);
362
+ if (!theme) {
363
+ throw new Error("useThemeContext must be used within a ThemeProvider");
364
+ }
365
+ return theme;
366
+ }
367
+ function useTheme(options) {
368
+ const { theme, inject = true } = options;
369
+ const cssVariables = react.useMemo(() => chunkZLOP4BTK_cjs.generateThemeCSS(theme), [theme]);
370
+ const componentStyles = react.useMemo(() => chunkZLOP4BTK_cjs.generateComponentStyles(), []);
371
+ react.useEffect(() => {
372
+ if (inject && chunkZLOP4BTK_cjs.isBrowser()) {
373
+ const cleanup = chunkZLOP4BTK_cjs.injectThemeCSS(theme);
374
+ return cleanup;
375
+ }
376
+ }, [theme, inject]);
377
+ const backgroundStyle = react.useMemo(() => {
378
+ const bg = theme.background;
379
+ switch (bg.type) {
380
+ case "gradient":
381
+ return { background: bg.value };
382
+ case "image":
383
+ return {
384
+ backgroundImage: `url(${bg.value})`,
385
+ backgroundSize: "cover",
386
+ backgroundPosition: bg.position || "center"
387
+ };
388
+ case "video":
389
+ return {};
390
+ default:
391
+ return {};
392
+ }
393
+ }, [theme.background]);
394
+ return {
395
+ theme,
396
+ cssVariables,
397
+ componentStyles,
398
+ backgroundStyle
399
+ };
400
+ }
401
+ function useField(options) {
402
+ const { field, value, error, onChange, onNext } = options;
403
+ const isEmpty = value === void 0 || value === "" || Array.isArray(value) && value.length === 0;
404
+ const onKeyDown = react.useCallback(
405
+ (e) => {
406
+ if (e.key === "Enter" && !e.shiftKey && field.type !== "long_text") {
407
+ e.preventDefault();
408
+ onNext?.();
409
+ }
410
+ },
411
+ [field.type, onNext]
412
+ );
413
+ const placeholder = react.useMemo(() => {
414
+ if ("properties" in field && field.properties) {
415
+ const props = field.properties;
416
+ if ("placeholder" in props) {
417
+ return props.placeholder;
418
+ }
419
+ }
420
+ return void 0;
421
+ }, [field]);
422
+ const getInputProps = react.useCallback(() => {
423
+ return {
424
+ value: value || "",
425
+ onChange: (e) => {
426
+ onChange(e.target.value);
427
+ },
428
+ onKeyDown,
429
+ placeholder,
430
+ "aria-invalid": !!error,
431
+ "aria-describedby": error ? `${field.ref}-error` : void 0
432
+ };
433
+ }, [value, onChange, onKeyDown, field.ref, error, placeholder]);
434
+ const getTextareaProps = react.useCallback(() => {
435
+ return {
436
+ value: value || "",
437
+ onChange: (e) => {
438
+ onChange(e.target.value);
439
+ },
440
+ placeholder,
441
+ "aria-invalid": !!error,
442
+ "aria-describedby": error ? `${field.ref}-error` : void 0
443
+ };
444
+ }, [value, onChange, field.ref, error, placeholder]);
445
+ return {
446
+ // Field info
447
+ ref: field.ref,
448
+ type: field.type,
449
+ title: field.title,
450
+ description: field.description,
451
+ required: field.validations?.required || false,
452
+ // State
453
+ value,
454
+ error,
455
+ isEmpty,
456
+ // Handlers
457
+ onChange,
458
+ onKeyDown,
459
+ // Helpers
460
+ getInputProps,
461
+ getTextareaProps
462
+ };
463
+ }
464
+ function useProgress(options) {
465
+ const { content, answers, currentIndex } = options;
466
+ return react.useMemo(() => {
467
+ const totalFields = content.fields.length;
468
+ const answeredCount = Object.keys(answers).length;
469
+ const welcomeCount = content.welcomeScreens.length;
470
+ const currentFieldIndex = Math.max(0, currentIndex - welcomeCount);
471
+ const percentage = totalFields > 0 ? answeredCount / totalFields * 100 : 0;
472
+ const segments = content.fields.map((field, index) => ({
473
+ index,
474
+ fieldRef: field.ref,
475
+ completed: !!answers[field.ref],
476
+ current: index === currentFieldIndex
477
+ }));
478
+ return {
479
+ totalFields,
480
+ answeredCount,
481
+ currentFieldIndex,
482
+ percentage,
483
+ segments
484
+ };
485
+ }, [content, answers, currentIndex]);
486
+ }
487
+
488
+ exports.ThemeContext = ThemeContext;
489
+ exports.hasCookieConsent = hasCookieConsent;
490
+ exports.setCookieConsent = setCookieConsent;
491
+ exports.useField = useField;
492
+ exports.useForm = useForm;
493
+ exports.useProgress = useProgress;
494
+ exports.useTheme = useTheme;
495
+ exports.useThemeContext = useThemeContext;
496
+ //# sourceMappingURL=chunk-WIFNU3FA.cjs.map
497
+ //# sourceMappingURL=chunk-WIFNU3FA.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/storage.ts","../src/hooks/useForm.ts","../src/hooks/useTheme.ts","../src/hooks/useField.ts","../src/hooks/useProgress.ts"],"names":["useState","nanoid","useMemo","LandformClient","useRef","useEffect","useCallback","createContext","useContext","generateThemeCSS","generateComponentStyles","isBrowser","injectThemeCSS"],"mappings":";;;;;;;;AAEA,IAAM,eAAA,GAAkB,cAAA;AACxB,IAAM,gBAAA,GAAmB,eAAA;AACzB,IAAM,kBAAA,GAAqB,mBAAA;AAYpB,SAAS,YAAA,CACf,SAAA,EACA,OAAA,EACA,YAAA,EACA,UAAA,EACO;AACP,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,EAAA,IAAI;AACH,IAAA,MAAM,IAAA,GAAqB;AAAA,MAC1B,OAAA;AAAA,MACA,YAAA;AAAA,MACA,UAAA;AAAA,MACA,SAAA,EAAW,KAAK,GAAA;AAAI,KACrB;AACA,IAAA,YAAA,CAAa,QAAQ,eAAA,GAAkB,SAAA,EAAW,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,CAAA;AAAA,EACvE,SAAS,KAAA,EAAO;AACf,IAAA,OAAA,CAAQ,KAAA,CAAM,+BAA+B,KAAK,CAAA;AAAA,EACnD;AACD;AAMO,SAAS,aAAa,SAAA,EAAwC;AACpE,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAE1C,EAAA,IAAI;AACH,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,eAAA,GAAkB,SAAS,CAAA;AAC/D,IAAA,IAAI,CAAC,QAAQ,OAAO,IAAA;AAEpB,IAAA,MAAM,IAAA,GAAqB,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AAG5C,IAAA,MAAM,MAAA,GAAS,CAAA,GAAI,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,GAAA;AAClC,IAAA,IAAI,IAAA,CAAK,GAAA,EAAI,GAAI,IAAA,CAAK,YAAY,MAAA,EAAQ;AACzC,MAAA,aAAA,CAAc,SAAS,CAAA;AACvB,MAAA,OAAO,IAAA;AAAA,IACR;AAEA,IAAA,OAAO,IAAA;AAAA,EACR,SAAS,KAAA,EAAO;AACf,IAAA,OAAA,CAAQ,KAAA,CAAM,gCAAgC,KAAK,CAAA;AACnD,IAAA,OAAO,IAAA;AAAA,EACR;AACD;AAKO,SAAS,cAAc,SAAA,EAAyB;AACtD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,EAAA,IAAI;AACH,IAAA,YAAA,CAAa,UAAA,CAAW,kBAAkB,SAAS,CAAA;AAAA,EACpD,SAAS,KAAA,EAAO;AACf,IAAA,OAAA,CAAQ,KAAA,CAAM,iCAAiC,KAAK,CAAA;AAAA,EACrD;AACD;AAKO,SAAS,gBAAgB,SAAA,EAAyB;AACxD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,EAAA,IAAI;AACH,IAAA,YAAA,CAAa,QAAQ,gBAAA,GAAmB,SAAA,EAAW,OAAO,IAAA,CAAK,GAAA,EAAK,CAAC,CAAA;AAAA,EACtE,SAAS,KAAA,EAAO;AACf,IAAA,OAAA,CAAQ,KAAA,CAAM,oCAAoC,KAAK,CAAA;AAAA,EACxD;AACD;AAKO,SAAS,aAAa,SAAA,EAA4B;AACxD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAE1C,EAAA,IAAI;AACH,IAAA,OAAO,YAAA,CAAa,OAAA,CAAQ,gBAAA,GAAmB,SAAS,CAAA,KAAM,IAAA;AAAA,EAC/D,SAAS,KAAA,EAAO;AACf,IAAA,OAAA,CAAQ,KAAA,CAAM,qCAAqC,KAAK,CAAA;AACxD,IAAA,OAAO,KAAA;AAAA,EACR;AACD;AAKO,SAAS,gBAAA,GAA4B;AAC3C,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAE1C,EAAA,IAAI;AACH,IAAA,OAAO,YAAA,CAAa,OAAA,CAAQ,kBAAkB,CAAA,KAAM,MAAA;AAAA,EACrD,SAAS,KAAA,EAAO;AACf,IAAA,OAAA,CAAQ,KAAA,CAAM,kCAAkC,KAAK,CAAA;AACrD,IAAA,OAAO,KAAA;AAAA,EACR;AACD;AAKO,SAAS,iBAAiB,SAAA,EAA0B;AAC1D,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,EAAA,IAAI;AACH,IAAA,IAAI,SAAA,EAAW;AACd,MAAA,YAAA,CAAa,OAAA,CAAQ,oBAAoB,MAAM,CAAA;AAAA,IAChD,CAAA,MAAO;AACN,MAAA,YAAA,CAAa,WAAW,kBAAkB,CAAA;AAAA,IAC3C;AAAA,EACD,SAAS,KAAA,EAAO;AACf,IAAA,OAAA,CAAQ,KAAA,CAAM,iCAAiC,KAAK,CAAA;AAAA,EACrD;AACD;;;AC3DA,SAAS,kBAAA,CAAmB,OAAkB,KAAA,EAAyC;AACtF,EAAA,IAAI,CAAC,KAAA,CAAM,WAAA,EAAa,QAAA,EAAU;AACjC,IAAA,OAAO,IAAA;AAAA,EACR;AAEA,EAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,IAAQ,UAAU,EAAA,EAAI;AAC1D,IAAA,OAAO,KAAA;AAAA,EACR;AAEA,EAAA,IAAI,MAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,WAAW,CAAA,EAAG;AAC/C,IAAA,OAAO,KAAA;AAAA,EACR;AAEA,EAAA,OAAO,IAAA;AACR;AAEO,SAAS,QAAQ,OAAA,EAAwC;AAC/D,EAAA,MAAM;AAAA,IACL,SAAA;AAAA,IACA,OAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA,GAAU,EAAA;AAAA,IACV,iBAAiB,EAAC;AAAA,IAClB,UAAA;AAAA,IACA;AAAA,GACD,GAAI,OAAA;AAGJ,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIA,eAAwB,IAAI,CAAA;AAChE,EAAA,MAAM,CAAC,SAAS,CAAA,GAAIA,cAAA,CAAS,MAAMC,eAAQ,CAAA;AAC3C,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAID,eAAS,CAAC,CAAA;AAClD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,eAA0B,cAAc,CAAA;AACtE,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIA,cAAA,CAAiC,EAAE,CAAA;AAC/D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,eAAS,KAAK,CAAA;AAChD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAS,KAAK,CAAA;AACpD,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAIA,eAAS,KAAK,CAAA;AACtD,EAAA,MAAM,CAAC,qBAAA,EAAuB,wBAAwB,CAAA,GAAIA,eAAS,KAAK,CAAA;AACxE,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAIA,eAAwB,IAAI,CAAA;AACpE,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAS,KAAK,CAAA;AAGpD,EAAA,MAAM,MAAA,GAASE,aAAA;AAAA,IACd,MAAM,IAAIC,gCAAA,CAAe,EAAE,OAAA,EAAS,SAAA,EAAW,SAAS,CAAA;AAAA,IACxD,CAAC,OAAA,EAAS,SAAA,EAAW,OAAO;AAAA,GAC7B;AAGA,EAAA,MAAM,cAAA,GAAiBC,aAAO,KAAK,CAAA;AACnC,EAAA,MAAM,iBAAA,GAAoBA,aAAO,KAAK,CAAA;AACtC,EAAA,MAAM,kBAAA,GAAqBA,aAA8B,IAAI,CAAA;AAG7D,EAAAC,eAAA,CAAU,MAAM;AACf,IAAA,IAAI,CAAC,eAAe,OAAA,EAAS;AAC5B,MAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AACzB,MAAA,MAAA,CAAO,UAAA,CAAW,EAAE,KAAA,EAAO,MAAA,EAAQ,WAAW,CAAA;AAAA,IAC/C;AAAA,EACD,CAAA,EAAG,CAAC,MAAA,EAAQ,SAAS,CAAC,CAAA;AAGtB,EAAAA,eAAA,CAAU,MAAM;AACf,IAAA,IAAI,QAAA,CAAS,mBAAA,IAAuB,YAAA,CAAa,SAAS,CAAA,EAAG;AAC5D,MAAA,wBAAA,CAAyB,IAAI,CAAA;AAAA,IAC9B;AAAA,EACD,CAAA,EAAG,CAAC,SAAA,EAAW,QAAA,CAAS,mBAAmB,CAAC,CAAA;AAG5C,EAAAA,eAAA,CAAU,MAAM;AACf,IAAA,IAAI,iBAAA,CAAkB,OAAA,IAAW,CAAC,QAAA,CAAS,gBAAA,EAAkB;AAC7D,IAAA,iBAAA,CAAkB,OAAA,GAAU,IAAA;AAE5B,IAAA,MAAM,aAAA,GAAgB,aAAa,SAAS,CAAA;AAC5C,IAAA,IAAI,aAAA,EAAe;AAClB,MAAA,UAAA,CAAW,cAAc,OAAO,CAAA;AAChC,MAAA,eAAA,CAAgB,cAAc,YAAY,CAAA;AAC1C,MAAA,IAAI,cAAc,UAAA,EAAY;AAC7B,QAAA,aAAA,CAAc,cAAc,UAAU,CAAA;AACtC,QAAA,YAAA,CAAa,IAAI,CAAA;AAAA,MAClB;AAAA,IACD;AAAA,EACD,CAAA,EAAG,CAAC,SAAA,EAAW,QAAA,CAAS,gBAAgB,CAAC,CAAA;AAGzC,EAAAA,eAAA,CAAU,MAAM;AACf,IAAA,IAAI,CAAC,QAAA,CAAS,gBAAA,IAAoB,WAAA,EAAa;AAG/C,IAAA,IAAI,mBAAmB,OAAA,EAAS;AAC/B,MAAA,YAAA,CAAa,mBAAmB,OAAO,CAAA;AAAA,IACxC;AAEA,IAAA,kBAAA,CAAmB,OAAA,GAAU,WAAW,MAAM;AAC7C,MAAA,YAAA,CAAa,SAAA,EAAW,OAAA,EAAS,YAAA,EAAc,UAAU,CAAA;AAAA,IAC1D,GAAG,GAAG,CAAA;AAEN,IAAA,OAAO,MAAM;AACZ,MAAA,IAAI,mBAAmB,OAAA,EAAS;AAC/B,QAAA,YAAA,CAAa,mBAAmB,OAAO,CAAA;AAAA,MACxC;AAAA,IACD,CAAA;AAAA,EACD,CAAA,EAAG,CAAC,SAAA,EAAW,OAAA,EAAS,cAAc,UAAA,EAAY,QAAA,CAAS,gBAAA,EAAkB,WAAW,CAAC,CAAA;AAGzF,EAAA,MAAM,QAAA,GAAWH,cAAsB,MAAM;AAC5C,IAAA,MAAM,QAAsB,EAAC;AAE7B,IAAA,KAAA,MAAW,MAAA,IAAU,QAAQ,cAAA,EAAgB;AAC5C,MAAA,KAAA,CAAM,IAAA,CAAK,EAAE,IAAA,EAAM,SAAA,EAAW,QAAQ,CAAA;AAAA,IACvC;AAEA,IAAA,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,CAAC,KAAA,EAAO,KAAA,KAAU;AACxC,MAAA,KAAA,CAAM,KAAK,EAAE,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,OAAO,CAAA;AAAA,IAC3C,CAAC,CAAA;AAED,IAAA,KAAA,MAAW,MAAA,IAAU,QAAQ,eAAA,EAAiB;AAC7C,MAAA,KAAA,CAAM,IAAA,CAAK,EAAE,IAAA,EAAM,UAAA,EAAY,QAAQ,CAAA;AAAA,IACxC;AAEA,IAAA,OAAO,KAAA;AAAA,EACR,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAGZ,EAAA,MAAM,WAAA,GAAc,QAAA,CAAS,YAAY,CAAA,IAAK,IAAA;AAG9C,EAAA,MAAM,WAAA,GAAc,QAAQ,MAAA,CAAO,MAAA;AACnC,EAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,CAAE,MAAA;AAC3C,EAAA,MAAM,QAAA,GAAW,WAAA,GAAc,CAAA,GAAK,aAAA,GAAgB,cAAe,GAAA,GAAM,CAAA;AACzE,EAAA,MAAM,eAAA,GAAkB,QAAQ,cAAA,CAAe,MAAA;AAC/C,EAAA,MAAM,cAAA,GAAiB,eAAA,GAAkB,OAAA,CAAQ,MAAA,CAAO,MAAA,GAAS,CAAA;AACjE,EAAA,MAAM,YAAY,YAAA,GAAe,eAAA;AACjC,EAAA,MAAM,SAAA,GAAY,YAAA,GAAe,QAAA,CAAS,MAAA,GAAS,CAAA;AACnD,EAAA,MAAM,WAAA,GAAc,aAAa,IAAA,KAAS,SAAA;AAC1C,EAAA,MAAM,YAAA,GAAe,aAAa,IAAA,KAAS,UAAA;AAC3C,EAAA,MAAM,SAAA,GAAY,aAAa,IAAA,KAAS,OAAA;AAGxC,EAAA,MAAM,KAAA,GAAQI,kBAAY,YAAY;AACrC,IAAA,IAAI;AACH,MAAA,MAAM,SAAS,MAAM,MAAA,CAAO,aAAA,CAAc,EAAE,WAAW,CAAA;AACvD,MAAA,aAAA,CAAc,OAAO,EAAE,CAAA;AACvB,MAAA,YAAA,CAAa,IAAI,CAAA;AAGjB,MAAA,MAAA,CAAO,UAAA,CAAW,EAAE,KAAA,EAAO,OAAA,EAAS,WAAW,CAAA;AAG/C,MAAA,IAAI,WAAA,EAAa,SAAS,SAAA,EAAW;AACpC,QAAA,eAAA,CAAgB,CAAC,IAAA,KAAS,IAAA,GAAO,CAAC,CAAA;AAAA,MACnC;AAAA,IACD,SAAS,KAAA,EAAO;AACf,MAAA,OAAA,GAAU,KAAc,CAAA;AAAA,IACzB;AAAA,EACD,GAAG,CAAC,MAAA,EAAQ,SAAA,EAAW,WAAA,EAAa,OAAO,CAAC,CAAA;AAG5C,EAAA,MAAM,wBAAA,GAA2BA,iBAAA,CAAY,CAAC,YAAA,KAAwC;AACrF,IAAA,IAAI,WAAA,EAAa,IAAA,KAAS,OAAA,EAAS,OAAO,IAAA;AAE1C,IAAA,MAAM,QAAQ,WAAA,CAAY,KAAA;AAE1B,IAAA,MAAM,QAAQ,YAAA,KAAiB,MAAA,GAAY,YAAA,GAAe,OAAA,CAAQ,MAAM,GAAG,CAAA;AAC3E,IAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,KAAA,EAAO,KAAK,CAAA;AAE/C,IAAA,IAAI,CAAC,OAAA,EAAS;AACb,MAAA,MAAM,YAAA,GACL,QAAA,CAAS,cAAA,EAAgB,YAAA,IAAgB,wBAAA;AAC1C,MAAA,SAAA,CAAU,CAAC,IAAA,MAAU;AAAA,QACpB,GAAG,IAAA;AAAA,QACH,CAAC,KAAA,CAAM,GAAG,GAAG;AAAA,OACd,CAAE,CAAA;AAAA,IACH;AAEA,IAAA,OAAO,OAAA;AAAA,EACR,GAAG,CAAC,WAAA,EAAa,SAAS,QAAA,CAAS,cAAA,EAAgB,YAAY,CAAC,CAAA;AAEhE,EAAA,MAAM,eAAA,GAAkBA,kBAAY,MAAe;AAClD,IAAA,OAAO,wBAAA,EAAyB;AAAA,EACjC,CAAA,EAAG,CAAC,wBAAwB,CAAC,CAAA;AAG7B,EAAA,MAAM,MAAA,GAASA,kBAAY,YAAY;AACtC,IAAA,IAAI,CAAC,UAAA,EAAY;AAGjB,IAAA,IAAI,QAAA,CAAS,cAAA,IAAkB,CAAC,YAAA,EAAc;AAC7C,MAAA,cAAA,CAAe,IAAI,CAAA;AACnB,MAAA;AAAA,IACD;AAEA,IAAA,eAAA,CAAgB,IAAI,CAAA;AACpB,IAAA,IAAI;AACH,MAAA,MAAM,OAAO,gBAAA,CAAiB;AAAA,QAC7B,UAAA;AAAA,QACA,SAAA;AAAA,QACA,OAAA;AAAA,QACA,cAAc,YAAA,IAAgB,KAAA;AAAA,OAC9B,CAAA;AACD,MAAA,cAAA,CAAe,IAAI,CAAA;AACnB,MAAA,eAAA,CAAgB,QAAA,CAAS,SAAS,CAAC,CAAA;AAGnC,MAAA,aAAA,CAAc,SAAS,CAAA;AACvB,MAAA,IAAI,SAAS,mBAAA,EAAqB;AACjC,QAAA,eAAA,CAAgB,SAAS,CAAA;AAAA,MAC1B;AAEA,MAAA,UAAA,GAAa,OAAO,CAAA;AAAA,IACrB,SAAS,KAAA,EAAO;AACf,MAAA,OAAA,GAAU,KAAc,CAAA;AAAA,IACzB,CAAA,SAAE;AACD,MAAA,eAAA,CAAgB,KAAK,CAAA;AACrB,MAAA,cAAA,CAAe,KAAK,CAAA;AAAA,IACrB;AAAA,EACD,GAAG,CAAC,UAAA,EAAY,SAAA,EAAW,OAAA,EAAS,cAAc,MAAA,EAAQ,QAAA,CAAS,MAAA,EAAQ,SAAA,EAAW,SAAS,cAAA,EAAgB,QAAA,CAAS,mBAAA,EAAqB,UAAA,EAAY,OAAO,CAAC,CAAA;AAGjK,EAAA,MAAM,IAAA,GAAOA,iBAAA,CAAY,OAAO,YAAA,KAA+B;AAE9D,IAAA,IAAI,WAAA,EAAa,SAAS,OAAA,EAAS;AAClC,MAAA,MAAM,OAAA,GAAU,yBAAyB,YAAY,CAAA;AACrD,MAAA,IAAI,CAAC,OAAA,EAAS;AACb,QAAA;AAAA,MACD;AAGA,MAAA,IAAI,iBAAiB,cAAA,EAAgB;AAEpC,QAAA,IAAI,iBAAiB,MAAA,EAAW;AAC/B,UAAA,UAAA,CAAW,CAAC,IAAA,MAAU,EAAE,GAAG,IAAA,EAAM,CAAC,WAAA,CAAY,KAAA,CAAM,GAAG,GAAG,YAAA,EAAa,CAAE,CAAA;AAAA,QAC1E;AACA,QAAA,MAAM,MAAA,EAAO;AACb,QAAA;AAAA,MACD;AAGA,MAAA,MAAM,aAAA,GAAgB,YAAA,KAAiB,MAAA,GACpC,EAAE,GAAG,OAAA,EAAS,CAAC,WAAA,CAAY,KAAA,CAAM,GAAG,GAAG,YAAA,EAAa,GACpD,OAAA;AAGH,MAAA,IAAI,UAAA,EAAY;AACf,QAAA,MAAA,CAAO,aAAA,CAAc;AAAA,UACpB,UAAA;AAAA,UACA,SAAA;AAAA,UACA,OAAA,EAAS,aAAA;AAAA,UACT,YAAA,EAAc,YAAY,KAAA,CAAM;AAAA,SAChC,CAAA,CAAE,KAAA,CAAM,CAAC,KAAA,KAAU;AACnB,UAAA,OAAA,GAAU,KAAc,CAAA;AAAA,QACzB,CAAC,CAAA;AAAA,MACF;AAAA,IACD;AAEA,IAAA,eAAA,CAAgB,CAAC,SAAS,IAAA,CAAK,GAAA,CAAI,OAAO,CAAA,EAAG,QAAA,CAAS,MAAA,GAAS,CAAC,CAAC,CAAA;AAAA,EAClE,CAAA,EAAG;AAAA,IACF,WAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA;AAAA,IACA,QAAA,CAAS,MAAA;AAAA,IACT,UAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,wBAAA;AAAA,IACA;AAAA,GACA,CAAA;AAGD,EAAA,MAAM,QAAA,GAAWA,kBAAY,MAAM;AAClC,IAAA,eAAA,CAAgB,CAAC,IAAA,KAAS,IAAA,CAAK,IAAI,IAAA,GAAO,CAAA,EAAG,eAAe,CAAC,CAAA;AAAA,EAC9D,CAAA,EAAG,CAAC,eAAe,CAAC,CAAA;AAGpB,EAAA,MAAM,IAAA,GAAOA,iBAAA;AAAA,IACZ,CAAC,KAAA,KAAkB;AAClB,MAAA,IAAI,KAAA,IAAS,CAAA,IAAK,KAAA,GAAQ,QAAA,CAAS,MAAA,EAAQ;AAC1C,QAAA,eAAA,CAAgB,KAAK,CAAA;AAAA,MACtB;AAAA,IACD,CAAA;AAAA,IACA,CAAC,SAAS,MAAM;AAAA,GACjB;AAGA,EAAA,MAAM,SAAA,GAAYA,iBAAA,CAAY,CAAC,QAAA,EAAkB,KAAA,KAAuB;AACvE,IAAA,UAAA,CAAW,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,QAAQ,GAAG,KAAA,EAAM,CAAE,CAAA;AAErD,IAAA,SAAA,CAAU,CAAC,IAAA,KAAS;AACnB,MAAA,MAAM,SAAA,GAAY,EAAE,GAAG,IAAA,EAAK;AAC5B,MAAA,OAAO,UAAU,QAAQ,CAAA;AACzB,MAAA,OAAO,SAAA;AAAA,IACR,CAAC,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,MAAM,KAAA,GAAQA,kBAAY,MAAM;AAC/B,IAAA,aAAA,CAAc,IAAI,CAAA;AAClB,IAAA,eAAA,CAAgB,CAAC,CAAA;AACjB,IAAA,UAAA,CAAW,cAAc,CAAA;AACzB,IAAA,SAAA,CAAU,EAAE,CAAA;AACZ,IAAA,YAAA,CAAa,KAAK,CAAA;AAClB,IAAA,cAAA,CAAe,KAAK,CAAA;AACpB,IAAA,eAAA,CAAgB,KAAK,CAAA;AAAA,EACtB,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAGnB,EAAAD,eAAA,CAAU,MAAM;AACf,IAAA,IAAI,OAAA,CAAQ,cAAA,CAAe,MAAA,KAAW,CAAA,IAAK,CAAC,SAAA,EAAW;AACtD,MAAA,KAAA,EAAM;AAAA,IACP;AAAA,EACD,GAAG,CAAC,OAAA,CAAQ,eAAe,MAAA,EAAQ,SAAA,EAAW,KAAK,CAAC,CAAA;AAEpD,EAAA,OAAO;AAAA;AAAA,IAEN,YAAA;AAAA,IACA,WAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA,YAAA;AAAA,IACA,UAAA;AAAA,IACA,SAAA;AAAA,IACA,qBAAA;AAAA,IACA,YAAA;AAAA,IACA,WAAA;AAAA;AAAA,IAGA,QAAA;AAAA,IACA,WAAA;AAAA,IACA,aAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAA;AAAA,IACA,WAAA;AAAA,IACA,YAAA;AAAA,IACA,SAAA;AAAA;AAAA,IAGA,KAAA;AAAA,IACA,IAAA;AAAA,IACA,QAAA;AAAA,IACA,IAAA;AAAA,IACA,SAAA;AAAA,IACA,eAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,GACD;AACD;AChaO,IAAM,YAAA,GAAeE,oBAAgC,IAAI;AAEzD,SAAS,eAAA,GAA6B;AAC5C,EAAA,MAAM,KAAA,GAAQC,iBAAW,YAAY,CAAA;AACrC,EAAA,IAAI,CAAC,KAAA,EAAO;AACX,IAAA,MAAM,IAAI,MAAM,qDAAqD,CAAA;AAAA,EACtE;AACA,EAAA,OAAO,KAAA;AACR;AAcO,SAAS,SAAS,OAAA,EAA0C;AAClE,EAAA,MAAM,EAAE,KAAA,EAAO,MAAA,GAAS,IAAA,EAAK,GAAI,OAAA;AAGjC,EAAA,MAAM,YAAA,GAAeN,cAAQ,MAAMO,kCAAA,CAAiB,KAAK,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AACnE,EAAA,MAAM,kBAAkBP,aAAAA,CAAQ,MAAMQ,yCAAA,EAAwB,EAAG,EAAE,CAAA;AAGnE,EAAAL,gBAAU,MAAM;AACf,IAAA,IAAI,MAAA,IAAUM,6BAAU,EAAG;AAC1B,MAAA,MAAM,OAAA,GAAUC,iCAAe,KAAK,CAAA;AACpC,MAAA,OAAO,OAAA;AAAA,IACR;AAAA,EACD,CAAA,EAAG,CAAC,KAAA,EAAO,MAAM,CAAC,CAAA;AAGlB,EAAA,MAAM,eAAA,GAAkBV,cAA6B,MAAM;AAC1D,IAAA,MAAM,KAAK,KAAA,CAAM,UAAA;AACjB,IAAA,QAAQ,GAAG,IAAA;AAAM,MAChB,KAAK,UAAA;AACJ,QAAA,OAAO,EAAE,UAAA,EAAY,EAAA,CAAG,KAAA,EAAM;AAAA,MAC/B,KAAK,OAAA;AACJ,QAAA,OAAO;AAAA,UACN,eAAA,EAAiB,CAAA,IAAA,EAAO,EAAA,CAAG,KAAK,CAAA,CAAA,CAAA;AAAA,UAChC,cAAA,EAAgB,OAAA;AAAA,UAChB,kBAAA,EAAoB,GAAG,QAAA,IAAY;AAAA,SACpC;AAAA,MACD,KAAK,OAAA;AAEJ,QAAA,OAAO,EAAC;AAAA,MACT;AACC,QAAA,OAAO,EAAC;AAAA;AACV,EACD,CAAA,EAAG,CAAC,KAAA,CAAM,UAAU,CAAC,CAAA;AAErB,EAAA,OAAO;AAAA,IACN,KAAA;AAAA,IACA,YAAA;AAAA,IACA,eAAA;AAAA,IACA;AAAA,GACD;AACD;ACnBO,SAAS,SAAS,OAAA,EAA0C;AAClE,EAAA,MAAM,EAAE,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,QAAA,EAAU,QAAO,GAAI,OAAA;AAElD,EAAA,MAAM,OAAA,GACL,KAAA,KAAU,MAAA,IACV,KAAA,KAAU,EAAA,IACT,MAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,MAAA,KAAW,CAAA;AAG3C,EAAA,MAAM,SAAA,GAAYI,iBAAAA;AAAA,IACjB,CAAC,CAAA,KAA2B;AAC3B,MAAA,IAAI,CAAA,CAAE,QAAQ,OAAA,IAAW,CAAC,EAAE,QAAA,IAAY,KAAA,CAAM,SAAS,WAAA,EAAa;AACnE,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,MAAA,IAAS;AAAA,MACV;AAAA,IACD,CAAA;AAAA,IACA,CAAC,KAAA,CAAM,IAAA,EAAM,MAAM;AAAA,GACpB;AAGA,EAAA,MAAM,WAAA,GAAcJ,cAAQ,MAAM;AACjC,IAAA,IAAI,YAAA,IAAgB,KAAA,IAAS,KAAA,CAAM,UAAA,EAAY;AAC9C,MAAA,MAAM,QAAQ,KAAA,CAAM,UAAA;AACpB,MAAA,IAAI,iBAAiB,KAAA,EAAO;AAC3B,QAAA,OAAO,KAAA,CAAM,WAAA;AAAA,MACd;AAAA,IACD;AACA,IAAA,OAAO,MAAA;AAAA,EACR,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAGV,EAAA,MAAM,aAAA,GAAgBI,kBAAY,MAAM;AACvC,IAAA,OAAO;AAAA,MACN,OAAQ,KAAA,IAAoB,EAAA;AAAA,MAC5B,QAAA,EAAU,CACT,CAAA,KAGI;AACJ,QAAA,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,MACxB,CAAA;AAAA,MACA,SAAA;AAAA,MACA,WAAA;AAAA,MACA,cAAA,EAAgB,CAAC,CAAC,KAAA;AAAA,MAClB,kBAAA,EAAoB,KAAA,GAAQ,CAAA,EAAG,KAAA,CAAM,GAAG,CAAA,MAAA,CAAA,GAAW;AAAA,KACpD;AAAA,EACD,CAAA,EAAG,CAAC,KAAA,EAAO,QAAA,EAAU,WAAW,KAAA,CAAM,GAAA,EAAK,KAAA,EAAO,WAAW,CAAC,CAAA;AAG9D,EAAA,MAAM,gBAAA,GAAmBA,kBAAY,MAAM;AAC1C,IAAA,OAAO;AAAA,MACN,OAAQ,KAAA,IAAoB,EAAA;AAAA,MAC5B,QAAA,EAAU,CAAC,CAAA,KAA8C;AACxD,QAAA,QAAA,CAAS,CAAA,CAAE,OAAO,KAAK,CAAA;AAAA,MACxB,CAAA;AAAA,MACA,WAAA;AAAA,MACA,cAAA,EAAgB,CAAC,CAAC,KAAA;AAAA,MAClB,kBAAA,EAAoB,KAAA,GAAQ,CAAA,EAAG,KAAA,CAAM,GAAG,CAAA,MAAA,CAAA,GAAW;AAAA,KACpD;AAAA,EACD,CAAA,EAAG,CAAC,KAAA,EAAO,QAAA,EAAU,MAAM,GAAA,EAAK,KAAA,EAAO,WAAW,CAAC,CAAA;AAEnD,EAAA,OAAO;AAAA;AAAA,IAEN,KAAK,KAAA,CAAM,GAAA;AAAA,IACX,MAAM,KAAA,CAAM,IAAA;AAAA,IACZ,OAAO,KAAA,CAAM,KAAA;AAAA,IACb,aAAa,KAAA,CAAM,WAAA;AAAA,IACnB,QAAA,EAAU,KAAA,CAAM,WAAA,EAAa,QAAA,IAAY,KAAA;AAAA;AAAA,IAGzC,KAAA;AAAA,IACA,KAAA;AAAA,IACA,OAAA;AAAA;AAAA,IAGA,QAAA;AAAA,IACA,SAAA;AAAA;AAAA,IAGA,aAAA;AAAA,IACA;AAAA,GACD;AACD;AC7GO,SAAS,YAAY,OAAA,EAAgD;AAC3E,EAAA,MAAM,EAAE,OAAA,EAAS,OAAA,EAAS,YAAA,EAAa,GAAI,OAAA;AAE3C,EAAA,OAAOJ,cAAQ,MAAM;AACpB,IAAA,MAAM,WAAA,GAAc,QAAQ,MAAA,CAAO,MAAA;AACnC,IAAA,MAAM,aAAA,GAAgB,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,CAAE,MAAA;AAG3C,IAAA,MAAM,YAAA,GAAe,QAAQ,cAAA,CAAe,MAAA;AAC5C,IAAA,MAAM,iBAAA,GAAoB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,eAAe,YAAY,CAAA;AAEjE,IAAA,MAAM,UAAA,GAAa,WAAA,GAAc,CAAA,GAAK,aAAA,GAAgB,cAAe,GAAA,GAAM,CAAA;AAG3E,IAAA,MAAM,WAA8B,OAAA,CAAQ,MAAA,CAAO,GAAA,CAAI,CAAC,OAAO,KAAA,MAAW;AAAA,MACzE,KAAA;AAAA,MACA,UAAU,KAAA,CAAM,GAAA;AAAA,MAChB,SAAA,EAAW,CAAC,CAAC,OAAA,CAAQ,MAAM,GAAG,CAAA;AAAA,MAC9B,SAAS,KAAA,KAAU;AAAA,KACpB,CAAE,CAAA;AAEF,IAAA,OAAO;AAAA,MACN,WAAA;AAAA,MACA,aAAA;AAAA,MACA,iBAAA;AAAA,MACA,UAAA;AAAA,MACA;AAAA,KACD;AAAA,EACD,CAAA,EAAG,CAAC,OAAA,EAAS,OAAA,EAAS,YAAY,CAAC,CAAA;AACpC","file":"chunk-WIFNU3FA.cjs","sourcesContent":["import type { ResponseAnswers } from \"../types\";\n\nconst AUTOSAVE_PREFIX = \"lf-autosave-\";\nconst SUBMITTED_PREFIX = \"lf-submitted-\";\nconst COOKIE_CONSENT_KEY = \"lf-cookie-consent\";\n\ninterface AutosaveData {\n\tanswers: ResponseAnswers;\n\tcurrentIndex: number;\n\tresponseId: string | null;\n\ttimestamp: number;\n}\n\n/**\n * Save form progress to localStorage\n */\nexport function saveProgress(\n\tprojectId: string,\n\tanswers: ResponseAnswers,\n\tcurrentIndex: number,\n\tresponseId: string | null\n): void {\n\tif (typeof window === \"undefined\") return;\n\n\ttry {\n\t\tconst data: AutosaveData = {\n\t\t\tanswers,\n\t\t\tcurrentIndex,\n\t\t\tresponseId,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t\tlocalStorage.setItem(AUTOSAVE_PREFIX + projectId, JSON.stringify(data));\n\t} catch (error) {\n\t\tconsole.error(\"Error saving form progress:\", error);\n\t}\n}\n\n/**\n * Load form progress from localStorage\n * Returns null if no saved progress or if progress is older than 7 days\n */\nexport function loadProgress(projectId: string): AutosaveData | null {\n\tif (typeof window === \"undefined\") return null;\n\n\ttry {\n\t\tconst stored = localStorage.getItem(AUTOSAVE_PREFIX + projectId);\n\t\tif (!stored) return null;\n\n\t\tconst data: AutosaveData = JSON.parse(stored);\n\n\t\t// Expire after 7 days\n\t\tconst maxAge = 7 * 24 * 60 * 60 * 1000; // 7 days in ms\n\t\tif (Date.now() - data.timestamp > maxAge) {\n\t\t\tclearProgress(projectId);\n\t\t\treturn null;\n\t\t}\n\n\t\treturn data;\n\t} catch (error) {\n\t\tconsole.error(\"Error loading form progress:\", error);\n\t\treturn null;\n\t}\n}\n\n/**\n * Clear saved form progress\n */\nexport function clearProgress(projectId: string): void {\n\tif (typeof window === \"undefined\") return;\n\n\ttry {\n\t\tlocalStorage.removeItem(AUTOSAVE_PREFIX + projectId);\n\t} catch (error) {\n\t\tconsole.error(\"Error clearing form progress:\", error);\n\t}\n}\n\n/**\n * Mark a form as submitted (for duplicate prevention)\n */\nexport function markAsSubmitted(projectId: string): void {\n\tif (typeof window === \"undefined\") return;\n\n\ttry {\n\t\tlocalStorage.setItem(SUBMITTED_PREFIX + projectId, String(Date.now()));\n\t} catch (error) {\n\t\tconsole.error(\"Error marking form as submitted:\", error);\n\t}\n}\n\n/**\n * Check if a form has already been submitted\n */\nexport function hasSubmitted(projectId: string): boolean {\n\tif (typeof window === \"undefined\") return false;\n\n\ttry {\n\t\treturn localStorage.getItem(SUBMITTED_PREFIX + projectId) !== null;\n\t} catch (error) {\n\t\tconsole.error(\"Error checking submission status:\", error);\n\t\treturn false;\n\t}\n}\n\n/**\n * Check if user has given cookie consent\n */\nexport function hasCookieConsent(): boolean {\n\tif (typeof window === \"undefined\") return false;\n\n\ttry {\n\t\treturn localStorage.getItem(COOKIE_CONSENT_KEY) === \"true\";\n\t} catch (error) {\n\t\tconsole.error(\"Error checking cookie consent:\", error);\n\t\treturn false;\n\t}\n}\n\n/**\n * Set cookie consent\n */\nexport function setCookieConsent(consented: boolean): void {\n\tif (typeof window === \"undefined\") return;\n\n\ttry {\n\t\tif (consented) {\n\t\t\tlocalStorage.setItem(COOKIE_CONSENT_KEY, \"true\");\n\t\t} else {\n\t\t\tlocalStorage.removeItem(COOKIE_CONSENT_KEY);\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"Error setting cookie consent:\", error);\n\t}\n}\n","import { useState, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { nanoid } from \"nanoid\";\nimport type {\n\tFormContent,\n\tFormTheme,\n\tFormSettings,\n\tFormField,\n\tWelcomeScreen,\n\tThankYouScreen,\n\tResponseAnswers,\n\tFieldAnswer,\n\tScreenItem,\n} from \"../types\";\nimport { LandformClient } from \"../api/client\";\nimport {\n\tsaveProgress,\n\tloadProgress,\n\tclearProgress,\n\tmarkAsSubmitted,\n\thasSubmitted,\n} from \"../utils/storage\";\n\nexport interface UseFormOptions {\n\tprojectId: string;\n\tcontent: FormContent;\n\ttheme: FormTheme;\n\tsettings: FormSettings;\n\tbaseUrl?: string;\n\tinitialAnswers?: ResponseAnswers;\n\tturnstileSiteKey?: string;\n\tonComplete?: (answers: ResponseAnswers) => void;\n\tonError?: (error: Error) => void;\n}\n\nexport interface UseFormReturn {\n\t// State\n\tcurrentIndex: number;\n\tcurrentItem: ScreenItem | null;\n\tanswers: ResponseAnswers;\n\terrors: Record<string, string>;\n\tisStarted: boolean;\n\tisCompleted: boolean;\n\tisSubmitting: boolean;\n\tresponseId: string | null;\n\tsessionId: string;\n\tisDuplicateSubmission: boolean;\n\tcaptchaToken: string | null;\n\tshowCaptcha: boolean;\n\n\t// CAPTCHA Actions\n\tsetCaptchaToken: (token: string) => void;\n\n\t// Computed\n\tsequence: ScreenItem[];\n\ttotalFields: number;\n\tansweredCount: number;\n\tprogress: number;\n\tcanGoBack: boolean;\n\tcanGoNext: boolean;\n\tisOnWelcome: boolean;\n\tisOnThankYou: boolean;\n\tisOnField: boolean;\n\n\t// Actions\n\tstart: () => Promise<void>;\n\tnext: (pendingValue?: FieldAnswer) => Promise<void>;\n\tprevious: () => void;\n\tgoTo: (index: number) => void;\n\tsetAnswer: (fieldRef: string, value: FieldAnswer) => void;\n\tvalidateCurrent: () => boolean;\n\tsubmit: () => Promise<void>;\n\treset: () => void;\n}\n\nfunction validateFieldValue(field: FormField, value: FieldAnswer | undefined): boolean {\n\tif (!field.validations?.required) {\n\t\treturn true;\n\t}\n\n\tif (value === undefined || value === null || value === \"\") {\n\t\treturn false;\n\t}\n\n\tif (Array.isArray(value) && value.length === 0) {\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nexport function useForm(options: UseFormOptions): UseFormReturn {\n\tconst {\n\t\tprojectId,\n\t\tcontent,\n\t\tsettings,\n\t\tbaseUrl = \"\",\n\t\tinitialAnswers = {},\n\t\tonComplete,\n\t\tonError,\n\t} = options;\n\n\t// Core state\n\tconst [responseId, setResponseId] = useState<string | null>(null);\n\tconst [sessionId] = useState(() => nanoid());\n\tconst [currentIndex, setCurrentIndex] = useState(0);\n\tconst [answers, setAnswers] = useState<ResponseAnswers>(initialAnswers);\n\tconst [errors, setErrors] = useState<Record<string, string>>({});\n\tconst [isStarted, setIsStarted] = useState(false);\n\tconst [isCompleted, setIsCompleted] = useState(false);\n\tconst [isSubmitting, setIsSubmitting] = useState(false);\n\tconst [isDuplicateSubmission, setIsDuplicateSubmission] = useState(false);\n\tconst [captchaToken, setCaptchaToken] = useState<string | null>(null);\n\tconst [showCaptcha, setShowCaptcha] = useState(false);\n\n\t// API client\n\tconst client = useMemo(\n\t\t() => new LandformClient({ baseUrl, projectId, onError }),\n\t\t[baseUrl, projectId, onError]\n\t);\n\n\t// Track if view event has been sent (prevent duplicates on re-renders)\n\tconst hasTrackedView = useRef(false);\n\tconst hasLoadedProgress = useRef(false);\n\tconst autosaveTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n\t// Track view on mount\n\tuseEffect(() => {\n\t\tif (!hasTrackedView.current) {\n\t\t\thasTrackedView.current = true;\n\t\t\tclient.trackEvent({ event: \"view\", sessionId });\n\t\t}\n\t}, [client, sessionId]);\n\n\t// Check for duplicate submission on mount\n\tuseEffect(() => {\n\t\tif (settings.duplicatePrevention && hasSubmitted(projectId)) {\n\t\t\tsetIsDuplicateSubmission(true);\n\t\t}\n\t}, [projectId, settings.duplicatePrevention]);\n\n\t// Load autosaved progress on mount\n\tuseEffect(() => {\n\t\tif (hasLoadedProgress.current || !settings.autosaveProgress) return;\n\t\thasLoadedProgress.current = true;\n\n\t\tconst savedProgress = loadProgress(projectId);\n\t\tif (savedProgress) {\n\t\t\tsetAnswers(savedProgress.answers);\n\t\t\tsetCurrentIndex(savedProgress.currentIndex);\n\t\t\tif (savedProgress.responseId) {\n\t\t\t\tsetResponseId(savedProgress.responseId);\n\t\t\t\tsetIsStarted(true);\n\t\t\t}\n\t\t}\n\t}, [projectId, settings.autosaveProgress]);\n\n\t// Autosave progress when answers or currentIndex change\n\tuseEffect(() => {\n\t\tif (!settings.autosaveProgress || isCompleted) return;\n\n\t\t// Debounce autosave\n\t\tif (autosaveTimeoutRef.current) {\n\t\t\tclearTimeout(autosaveTimeoutRef.current);\n\t\t}\n\n\t\tautosaveTimeoutRef.current = setTimeout(() => {\n\t\t\tsaveProgress(projectId, answers, currentIndex, responseId);\n\t\t}, 500);\n\n\t\treturn () => {\n\t\t\tif (autosaveTimeoutRef.current) {\n\t\t\t\tclearTimeout(autosaveTimeoutRef.current);\n\t\t\t}\n\t\t};\n\t}, [projectId, answers, currentIndex, responseId, settings.autosaveProgress, isCompleted]);\n\n\t// Build sequence of screens/fields\n\tconst sequence = useMemo<ScreenItem[]>(() => {\n\t\tconst items: ScreenItem[] = [];\n\n\t\tfor (const screen of content.welcomeScreens) {\n\t\t\titems.push({ type: \"welcome\", screen });\n\t\t}\n\n\t\tcontent.fields.forEach((field, index) => {\n\t\t\titems.push({ type: \"field\", field, index });\n\t\t});\n\n\t\tfor (const screen of content.thankYouScreens) {\n\t\t\titems.push({ type: \"thankYou\", screen });\n\t\t}\n\n\t\treturn items;\n\t}, [content]);\n\n\t// Current item\n\tconst currentItem = sequence[currentIndex] || null;\n\n\t// Computed values\n\tconst totalFields = content.fields.length;\n\tconst answeredCount = Object.keys(answers).length;\n\tconst progress = totalFields > 0 ? (answeredCount / totalFields) * 100 : 0;\n\tconst firstFieldIndex = content.welcomeScreens.length;\n\tconst lastFieldIndex = firstFieldIndex + content.fields.length - 1;\n\tconst canGoBack = currentIndex > firstFieldIndex;\n\tconst canGoNext = currentIndex < sequence.length - 1;\n\tconst isOnWelcome = currentItem?.type === \"welcome\";\n\tconst isOnThankYou = currentItem?.type === \"thankYou\";\n\tconst isOnField = currentItem?.type === \"field\";\n\n\t// Start response\n\tconst start = useCallback(async () => {\n\t\ttry {\n\t\t\tconst result = await client.startResponse({ sessionId });\n\t\t\tsetResponseId(result.id);\n\t\t\tsetIsStarted(true);\n\n\t\t\t// Track start event\n\t\t\tclient.trackEvent({ event: \"start\", sessionId });\n\n\t\t\t// Move past welcome screen if on one\n\t\t\tif (currentItem?.type === \"welcome\") {\n\t\t\t\tsetCurrentIndex((prev) => prev + 1);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tonError?.(error as Error);\n\t\t}\n\t}, [client, sessionId, currentItem, onError]);\n\n\t// Validate current field (pendingValue allows validating before state updates)\n\tconst validateCurrentWithValue = useCallback((pendingValue?: FieldAnswer): boolean => {\n\t\tif (currentItem?.type !== \"field\") return true;\n\n\t\tconst field = currentItem.field;\n\t\t// Use pending value if provided, otherwise use state\n\t\tconst value = pendingValue !== undefined ? pendingValue : answers[field.ref];\n\t\tconst isValid = validateFieldValue(field, value);\n\n\t\tif (!isValid) {\n\t\t\tconst requiredText =\n\t\t\t\tsettings.systemMessages?.requiredText || \"This field is required\";\n\t\t\tsetErrors((prev) => ({\n\t\t\t\t...prev,\n\t\t\t\t[field.ref]: requiredText,\n\t\t\t}));\n\t\t}\n\n\t\treturn isValid;\n\t}, [currentItem, answers, settings.systemMessages?.requiredText]);\n\n\tconst validateCurrent = useCallback((): boolean => {\n\t\treturn validateCurrentWithValue();\n\t}, [validateCurrentWithValue]);\n\n\t// Submit response\n\tconst submit = useCallback(async () => {\n\t\tif (!responseId) return;\n\n\t\t// If CAPTCHA is enabled but no token, show CAPTCHA first\n\t\tif (settings.captchaEnabled && !captchaToken) {\n\t\t\tsetShowCaptcha(true);\n\t\t\treturn;\n\t\t}\n\n\t\tsetIsSubmitting(true);\n\t\ttry {\n\t\t\tawait client.completeResponse({\n\t\t\t\tresponseId,\n\t\t\t\tsessionId,\n\t\t\t\tanswers,\n\t\t\t\tcaptchaToken: captchaToken || undefined,\n\t\t\t});\n\t\t\tsetIsCompleted(true);\n\t\t\tsetCurrentIndex(sequence.length - 1); // Go to thank you screen\n\n\t\t\t// Clear autosaved progress and mark as submitted for duplicate prevention\n\t\t\tclearProgress(projectId);\n\t\t\tif (settings.duplicatePrevention) {\n\t\t\t\tmarkAsSubmitted(projectId);\n\t\t\t}\n\n\t\t\tonComplete?.(answers);\n\t\t} catch (error) {\n\t\t\tonError?.(error as Error);\n\t\t} finally {\n\t\t\tsetIsSubmitting(false);\n\t\t\tsetShowCaptcha(false);\n\t\t}\n\t}, [responseId, sessionId, answers, captchaToken, client, sequence.length, projectId, settings.captchaEnabled, settings.duplicatePrevention, onComplete, onError]);\n\n\t// Navigate next (pendingValue allows validation before state updates)\n\tconst next = useCallback(async (pendingValue?: FieldAnswer) => {\n\t\t// Validate current field if applicable\n\t\tif (currentItem?.type === \"field\") {\n\t\t\tconst isValid = validateCurrentWithValue(pendingValue);\n\t\t\tif (!isValid) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Check if last field - complete the response (this should await)\n\t\t\tif (currentIndex === lastFieldIndex) {\n\t\t\t\t// For final submission, merge pending value\n\t\t\t\tif (pendingValue !== undefined) {\n\t\t\t\t\tsetAnswers((prev) => ({ ...prev, [currentItem.field.ref]: pendingValue }));\n\t\t\t\t}\n\t\t\t\tawait submit();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Merge pending value with answers for API call\n\t\t\tconst answersToSave = pendingValue !== undefined\n\t\t\t\t? { ...answers, [currentItem.field.ref]: pendingValue }\n\t\t\t\t: answers;\n\n\t\t\t// Save answer in background (don't await - keeps UI responsive)\n\t\t\tif (responseId) {\n\t\t\t\tclient.updateAnswers({\n\t\t\t\t\tresponseId,\n\t\t\t\t\tsessionId,\n\t\t\t\t\tanswers: answersToSave,\n\t\t\t\t\tlastFieldRef: currentItem.field.ref,\n\t\t\t\t}).catch((error) => {\n\t\t\t\t\tonError?.(error as Error);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tsetCurrentIndex((prev) => Math.min(prev + 1, sequence.length - 1));\n\t}, [\n\t\tcurrentItem,\n\t\tcurrentIndex,\n\t\tlastFieldIndex,\n\t\tsequence.length,\n\t\tresponseId,\n\t\tsessionId,\n\t\tanswers,\n\t\tclient,\n\t\tonError,\n\t\tvalidateCurrentWithValue,\n\t\tsubmit,\n\t]);\n\n\t// Navigate previous\n\tconst previous = useCallback(() => {\n\t\tsetCurrentIndex((prev) => Math.max(prev - 1, firstFieldIndex));\n\t}, [firstFieldIndex]);\n\n\t// Go to specific index\n\tconst goTo = useCallback(\n\t\t(index: number) => {\n\t\t\tif (index >= 0 && index < sequence.length) {\n\t\t\t\tsetCurrentIndex(index);\n\t\t\t}\n\t\t},\n\t\t[sequence.length]\n\t);\n\n\t// Set answer\n\tconst setAnswer = useCallback((fieldRef: string, value: FieldAnswer) => {\n\t\tsetAnswers((prev) => ({ ...prev, [fieldRef]: value }));\n\t\t// Clear error when user provides input\n\t\tsetErrors((prev) => {\n\t\t\tconst newErrors = { ...prev };\n\t\t\tdelete newErrors[fieldRef];\n\t\t\treturn newErrors;\n\t\t});\n\t}, []);\n\n\t// Reset form\n\tconst reset = useCallback(() => {\n\t\tsetResponseId(null);\n\t\tsetCurrentIndex(0);\n\t\tsetAnswers(initialAnswers);\n\t\tsetErrors({});\n\t\tsetIsStarted(false);\n\t\tsetIsCompleted(false);\n\t\tsetIsSubmitting(false);\n\t}, [initialAnswers]);\n\n\t// Auto-start if no welcome screens\n\tuseEffect(() => {\n\t\tif (content.welcomeScreens.length === 0 && !isStarted) {\n\t\t\tstart();\n\t\t}\n\t}, [content.welcomeScreens.length, isStarted, start]);\n\n\treturn {\n\t\t// State\n\t\tcurrentIndex,\n\t\tcurrentItem,\n\t\tanswers,\n\t\terrors,\n\t\tisStarted,\n\t\tisCompleted,\n\t\tisSubmitting,\n\t\tresponseId,\n\t\tsessionId,\n\t\tisDuplicateSubmission,\n\t\tcaptchaToken,\n\t\tshowCaptcha,\n\n\t\t// Computed\n\t\tsequence,\n\t\ttotalFields,\n\t\tansweredCount,\n\t\tprogress,\n\t\tcanGoBack,\n\t\tcanGoNext,\n\t\tisOnWelcome,\n\t\tisOnThankYou,\n\t\tisOnField,\n\n\t\t// Actions\n\t\tstart,\n\t\tnext,\n\t\tprevious,\n\t\tgoTo,\n\t\tsetAnswer,\n\t\tvalidateCurrent,\n\t\tsubmit,\n\t\treset,\n\t\tsetCaptchaToken,\n\t};\n}\n","import { useEffect, useMemo, useContext, createContext } from \"react\";\nimport type { FormTheme } from \"../types\";\nimport type React from \"react\";\nimport { generateThemeCSS, generateComponentStyles } from \"../theme/generateCSS\";\nimport { injectThemeCSS, isBrowser } from \"../theme/injection\";\n\n// Theme context for nested components\nexport const ThemeContext = createContext<FormTheme | null>(null);\n\nexport function useThemeContext(): FormTheme {\n\tconst theme = useContext(ThemeContext);\n\tif (!theme) {\n\t\tthrow new Error(\"useThemeContext must be used within a ThemeProvider\");\n\t}\n\treturn theme;\n}\n\nexport interface UseThemeOptions {\n\ttheme: FormTheme;\n\tinject?: boolean; // Whether to inject CSS (default: true on client)\n}\n\nexport interface UseThemeReturn {\n\ttheme: FormTheme;\n\tcssVariables: string;\n\tcomponentStyles: string;\n\tbackgroundStyle: React.CSSProperties;\n}\n\nexport function useTheme(options: UseThemeOptions): UseThemeReturn {\n\tconst { theme, inject = true } = options;\n\n\t// Generate CSS\n\tconst cssVariables = useMemo(() => generateThemeCSS(theme), [theme]);\n\tconst componentStyles = useMemo(() => generateComponentStyles(), []);\n\n\t// Inject CSS on client\n\tuseEffect(() => {\n\t\tif (inject && isBrowser()) {\n\t\t\tconst cleanup = injectThemeCSS(theme);\n\t\t\treturn cleanup;\n\t\t}\n\t}, [theme, inject]);\n\n\t// Background styles\n\tconst backgroundStyle = useMemo<React.CSSProperties>(() => {\n\t\tconst bg = theme.background;\n\t\tswitch (bg.type) {\n\t\t\tcase \"gradient\":\n\t\t\t\treturn { background: bg.value };\n\t\t\tcase \"image\":\n\t\t\t\treturn {\n\t\t\t\t\tbackgroundImage: `url(${bg.value})`,\n\t\t\t\t\tbackgroundSize: \"cover\",\n\t\t\t\t\tbackgroundPosition: bg.position || \"center\",\n\t\t\t\t};\n\t\t\tcase \"video\":\n\t\t\t\t// Video backgrounds need to be handled differently (with a video element)\n\t\t\t\treturn {};\n\t\t\tdefault:\n\t\t\t\treturn {};\n\t\t}\n\t}, [theme.background]);\n\n\treturn {\n\t\ttheme,\n\t\tcssVariables,\n\t\tcomponentStyles,\n\t\tbackgroundStyle,\n\t};\n}\n","import { useCallback, useMemo } from \"react\";\nimport type { FormField, FieldAnswer } from \"../types\";\n\nexport interface UseFieldOptions {\n\tfield: FormField;\n\tvalue: FieldAnswer | undefined;\n\terror: string | null;\n\tonChange: (value: FieldAnswer) => void;\n\tonNext?: () => void;\n}\n\nexport interface UseFieldReturn {\n\t// Field info\n\tref: string;\n\ttype: FormField[\"type\"];\n\ttitle: string;\n\tdescription?: string;\n\trequired: boolean;\n\n\t// State\n\tvalue: FieldAnswer | undefined;\n\terror: string | null;\n\tisEmpty: boolean;\n\n\t// Handlers\n\tonChange: (value: FieldAnswer) => void;\n\tonKeyDown: (e: React.KeyboardEvent) => void;\n\n\t// Helpers\n\tgetInputProps: () => {\n\t\tvalue: string;\n\t\tonChange: (\n\t\t\te: React.ChangeEvent<\n\t\t\t\tHTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\n\t\t\t>\n\t\t) => void;\n\t\tonKeyDown: (e: React.KeyboardEvent) => void;\n\t\tplaceholder?: string;\n\t\t\"aria-invalid\": boolean;\n\t\t\"aria-describedby\"?: string;\n\t};\n\n\tgetTextareaProps: () => {\n\t\tvalue: string;\n\t\tonChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;\n\t\tplaceholder?: string;\n\t\t\"aria-invalid\": boolean;\n\t\t\"aria-describedby\"?: string;\n\t};\n}\n\nexport function useField(options: UseFieldOptions): UseFieldReturn {\n\tconst { field, value, error, onChange, onNext } = options;\n\n\tconst isEmpty =\n\t\tvalue === undefined ||\n\t\tvalue === \"\" ||\n\t\t(Array.isArray(value) && value.length === 0);\n\n\t// Handle keyboard navigation\n\tconst onKeyDown = useCallback(\n\t\t(e: React.KeyboardEvent) => {\n\t\t\tif (e.key === \"Enter\" && !e.shiftKey && field.type !== \"long_text\") {\n\t\t\t\te.preventDefault();\n\t\t\t\tonNext?.();\n\t\t\t}\n\t\t},\n\t\t[field.type, onNext]\n\t);\n\n\t// Get placeholder from field properties\n\tconst placeholder = useMemo(() => {\n\t\tif (\"properties\" in field && field.properties) {\n\t\t\tconst props = field.properties as Record<string, unknown>;\n\t\t\tif (\"placeholder\" in props) {\n\t\t\t\treturn props.placeholder as string | undefined;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}, [field]);\n\n\t// Generic input props helper\n\tconst getInputProps = useCallback(() => {\n\t\treturn {\n\t\t\tvalue: (value as string) || \"\",\n\t\t\tonChange: (\n\t\t\t\te: React.ChangeEvent<\n\t\t\t\t\tHTMLInputElement | HTMLTextAreaElement | HTMLSelectElement\n\t\t\t\t>\n\t\t\t) => {\n\t\t\t\tonChange(e.target.value);\n\t\t\t},\n\t\t\tonKeyDown,\n\t\t\tplaceholder,\n\t\t\t\"aria-invalid\": !!error,\n\t\t\t\"aria-describedby\": error ? `${field.ref}-error` : undefined,\n\t\t};\n\t}, [value, onChange, onKeyDown, field.ref, error, placeholder]);\n\n\t// Textarea props helper (without onKeyDown to allow Enter for newlines)\n\tconst getTextareaProps = useCallback(() => {\n\t\treturn {\n\t\t\tvalue: (value as string) || \"\",\n\t\t\tonChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n\t\t\t\tonChange(e.target.value);\n\t\t\t},\n\t\t\tplaceholder,\n\t\t\t\"aria-invalid\": !!error,\n\t\t\t\"aria-describedby\": error ? `${field.ref}-error` : undefined,\n\t\t};\n\t}, [value, onChange, field.ref, error, placeholder]);\n\n\treturn {\n\t\t// Field info\n\t\tref: field.ref,\n\t\ttype: field.type,\n\t\ttitle: field.title,\n\t\tdescription: field.description,\n\t\trequired: field.validations?.required || false,\n\n\t\t// State\n\t\tvalue,\n\t\terror,\n\t\tisEmpty,\n\n\t\t// Handlers\n\t\tonChange,\n\t\tonKeyDown,\n\n\t\t// Helpers\n\t\tgetInputProps,\n\t\tgetTextareaProps,\n\t};\n}\n","import { useMemo } from \"react\";\nimport type { FormContent, ResponseAnswers } from \"../types\";\n\nexport interface UseProgressOptions {\n\tcontent: FormContent;\n\tanswers: ResponseAnswers;\n\tcurrentIndex: number;\n}\n\nexport interface ProgressSegment {\n\tindex: number;\n\tfieldRef: string;\n\tcompleted: boolean;\n\tcurrent: boolean;\n}\n\nexport interface UseProgressReturn {\n\ttotalFields: number;\n\tansweredCount: number;\n\tcurrentFieldIndex: number;\n\tpercentage: number;\n\tsegments: ProgressSegment[];\n}\n\nexport function useProgress(options: UseProgressOptions): UseProgressReturn {\n\tconst { content, answers, currentIndex } = options;\n\n\treturn useMemo(() => {\n\t\tconst totalFields = content.fields.length;\n\t\tconst answeredCount = Object.keys(answers).length;\n\n\t\t// Calculate current field index (accounting for welcome screens)\n\t\tconst welcomeCount = content.welcomeScreens.length;\n\t\tconst currentFieldIndex = Math.max(0, currentIndex - welcomeCount);\n\n\t\tconst percentage = totalFields > 0 ? (answeredCount / totalFields) * 100 : 0;\n\n\t\t// Generate segments for segmented progress bar\n\t\tconst segments: ProgressSegment[] = content.fields.map((field, index) => ({\n\t\t\tindex,\n\t\t\tfieldRef: field.ref,\n\t\t\tcompleted: !!answers[field.ref],\n\t\t\tcurrent: index === currentFieldIndex,\n\t\t}));\n\n\t\treturn {\n\t\t\ttotalFields,\n\t\t\tansweredCount,\n\t\t\tcurrentFieldIndex,\n\t\t\tpercentage,\n\t\t\tsegments,\n\t\t};\n\t}, [content, answers, currentIndex]);\n}\n"]}