@viveksinghind/narrative-form-react 1.0.0

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.
package/dist/index.js ADDED
@@ -0,0 +1,1889 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var narrativeFormCore = require('@viveksinghind/narrative-form-core');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React__default = /*#__PURE__*/_interopDefault(React);
10
+
11
+ // src/NarrativeForm.tsx
12
+ function useFormState(fields) {
13
+ const [, setTick] = React.useState(0);
14
+ const engineRef = React.useRef(null);
15
+ if (engineRef.current === null) {
16
+ engineRef.current = new narrativeFormCore.FormStateEngine(fields, () => {
17
+ setTick((t) => t + 1);
18
+ });
19
+ }
20
+ const engine = engineRef.current;
21
+ const startTyping = React.useCallback((key) => engine.startTyping(key), [engine]);
22
+ const activateField = React.useCallback((key) => engine.activateField(key), [engine]);
23
+ const confirmField = React.useCallback(
24
+ (key, value) => engine.confirmField(key, value),
25
+ [engine]
26
+ );
27
+ const editField = React.useCallback((key) => engine.editField(key), [engine]);
28
+ const reconfirmField = React.useCallback(
29
+ (key, value) => engine.reconfirmField(key, value),
30
+ [engine]
31
+ );
32
+ const next = React.useCallback(() => engine.next(), [engine]);
33
+ const focusField = React.useCallback((key) => engine.focusField(key), [engine]);
34
+ const reset = React.useCallback(() => engine.reset(), [engine]);
35
+ const getValues = React.useCallback(() => engine.getValues(), [engine]);
36
+ const getMeta = React.useCallback(
37
+ (formId, formVersion) => engine.getMeta(formId, formVersion),
38
+ [engine]
39
+ );
40
+ const snapshot = engine.getSnapshot();
41
+ return React.useMemo(
42
+ () => ({
43
+ snapshot,
44
+ startTyping,
45
+ activateField,
46
+ confirmField,
47
+ editField,
48
+ reconfirmField,
49
+ next,
50
+ focusField,
51
+ reset,
52
+ getValues,
53
+ getMeta
54
+ }),
55
+ // snapshot changes every tick, which is what we want
56
+ // eslint-disable-next-line react-hooks/exhaustive-deps
57
+ [snapshot]
58
+ );
59
+ }
60
+ function useDynamicForm({
61
+ fieldsUrl,
62
+ fieldsUrlHeaders,
63
+ formConfig,
64
+ onFetchError
65
+ }) {
66
+ const [config, setConfig] = React.useState(formConfig != null ? formConfig : null);
67
+ const [loading, setLoading] = React.useState(!!fieldsUrl && !formConfig);
68
+ const [error, setError] = React.useState(null);
69
+ const [retryCount, setRetryCount] = React.useState(0);
70
+ React.useEffect(() => {
71
+ if (formConfig) {
72
+ setConfig(formConfig);
73
+ setLoading(false);
74
+ setError(null);
75
+ return;
76
+ }
77
+ if (!fieldsUrl) {
78
+ return;
79
+ }
80
+ let isMounted = true;
81
+ setLoading(true);
82
+ setError(null);
83
+ narrativeFormCore.fetchFormConfig(fieldsUrl, { headers: fieldsUrlHeaders }).then((data) => {
84
+ if (isMounted) {
85
+ setConfig(data);
86
+ setLoading(false);
87
+ }
88
+ }).catch((err) => {
89
+ if (isMounted) {
90
+ setError(err);
91
+ setLoading(false);
92
+ onFetchError == null ? void 0 : onFetchError(err);
93
+ }
94
+ });
95
+ return () => {
96
+ isMounted = false;
97
+ };
98
+ }, [fieldsUrl, fieldsUrlHeaders, formConfig, retryCount, onFetchError]);
99
+ const retry = React.useCallback(() => {
100
+ if (fieldsUrl && !formConfig) {
101
+ setRetryCount((c) => c + 1);
102
+ }
103
+ }, [fieldsUrl, formConfig]);
104
+ return { config, loading, error, retry };
105
+ }
106
+ function prefersReducedMotion() {
107
+ if (typeof window === "undefined") return false;
108
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
109
+ }
110
+ function useTypewriter(options) {
111
+ const { text, speed = 38, enabled = true, pauseAfter = 100, onComplete } = options;
112
+ const [charIndex, setCharIndex] = React.useState(0);
113
+ const [isComplete, setIsComplete] = React.useState(false);
114
+ const intervalRef = React.useRef(null);
115
+ const pauseTimeoutRef = React.useRef(null);
116
+ const onCompleteRef = React.useRef(onComplete);
117
+ onCompleteRef.current = onComplete;
118
+ const cleanup = React.useCallback(() => {
119
+ if (intervalRef.current !== null) {
120
+ clearInterval(intervalRef.current);
121
+ intervalRef.current = null;
122
+ }
123
+ if (pauseTimeoutRef.current !== null) {
124
+ clearTimeout(pauseTimeoutRef.current);
125
+ pauseTimeoutRef.current = null;
126
+ }
127
+ }, []);
128
+ React.useEffect(() => {
129
+ var _a;
130
+ if (!enabled || prefersReducedMotion() || text.length === 0) {
131
+ setCharIndex(text.length);
132
+ setIsComplete(true);
133
+ (_a = onCompleteRef.current) == null ? void 0 : _a.call(onCompleteRef);
134
+ return;
135
+ }
136
+ setCharIndex(0);
137
+ setIsComplete(false);
138
+ intervalRef.current = setInterval(() => {
139
+ setCharIndex((prev) => {
140
+ const next = prev + 1;
141
+ if (next >= text.length) {
142
+ if (intervalRef.current !== null) {
143
+ clearInterval(intervalRef.current);
144
+ intervalRef.current = null;
145
+ }
146
+ pauseTimeoutRef.current = setTimeout(() => {
147
+ var _a2;
148
+ setIsComplete(true);
149
+ (_a2 = onCompleteRef.current) == null ? void 0 : _a2.call(onCompleteRef);
150
+ }, pauseAfter);
151
+ return text.length;
152
+ }
153
+ return next;
154
+ });
155
+ }, speed);
156
+ return cleanup;
157
+ }, [text, speed, enabled, pauseAfter, cleanup]);
158
+ return {
159
+ displayedText: text.slice(0, charIndex),
160
+ isTyping: charIndex < text.length && !isComplete,
161
+ isComplete
162
+ };
163
+ }
164
+ var Cursor = React__default.default.memo(function Cursor2({
165
+ cursorChar = "|",
166
+ className
167
+ }) {
168
+ const classes = ["ns-cursor", className].filter(Boolean).join(" ");
169
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: classes, "aria-hidden": "true", children: cursorChar });
170
+ });
171
+ var Prose = React__default.default.memo(function Prose2({
172
+ text,
173
+ animate = true,
174
+ speed = 38,
175
+ cursor = true,
176
+ cursorChar = "|",
177
+ pauseAfter = 100,
178
+ onComplete,
179
+ className
180
+ }) {
181
+ const { displayedText, isTyping, isComplete } = useTypewriter({
182
+ text,
183
+ speed,
184
+ enabled: animate,
185
+ pauseAfter,
186
+ onComplete
187
+ });
188
+ const classes = [
189
+ "ns-prose",
190
+ isTyping ? "ns-prose--typing" : void 0,
191
+ className
192
+ ].filter(Boolean).join(" ");
193
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: classes, children: [
194
+ displayedText,
195
+ cursor && isTyping && /* @__PURE__ */ jsxRuntime.jsx(Cursor, { cursorChar })
196
+ ] });
197
+ });
198
+ var EnterButton = React__default.default.memo(function EnterButton2({
199
+ onConfirm,
200
+ label = "\u21B5",
201
+ className
202
+ }) {
203
+ const classes = ["ns-enter-btn", className].filter(Boolean).join(" ");
204
+ return /* @__PURE__ */ jsxRuntime.jsx(
205
+ "button",
206
+ {
207
+ type: "button",
208
+ className: classes,
209
+ onClick: onConfirm,
210
+ "aria-label": "Confirm",
211
+ children: label
212
+ }
213
+ );
214
+ });
215
+ var InlineInput = function InlineInput2({
216
+ fieldKey,
217
+ type = "text",
218
+ placeholder,
219
+ defaultValue = "",
220
+ suffix,
221
+ sanitise,
222
+ onConfirm,
223
+ onChange,
224
+ onFocus,
225
+ onBlur,
226
+ onEscape,
227
+ inputClassName,
228
+ className
229
+ }) {
230
+ const [value, setValue] = React.useState(defaultValue);
231
+ const [isFocused, setIsFocused] = React.useState(false);
232
+ const inputRef = React.useRef(null);
233
+ React.useEffect(() => {
234
+ var _a;
235
+ (_a = inputRef.current) == null ? void 0 : _a.focus();
236
+ }, []);
237
+ const applyValue = React.useCallback(
238
+ (raw) => {
239
+ const cleaned = sanitise ? sanitise(raw) : raw;
240
+ setValue(cleaned);
241
+ onChange == null ? void 0 : onChange(cleaned);
242
+ },
243
+ [sanitise, onChange]
244
+ );
245
+ const handleChange = (e) => {
246
+ applyValue(e.target.value);
247
+ };
248
+ const handlePaste = React.useCallback(
249
+ (e) => {
250
+ if (!sanitise) return;
251
+ e.preventDefault();
252
+ const pasted = e.clipboardData.getData("text");
253
+ applyValue(pasted);
254
+ },
255
+ [sanitise, applyValue]
256
+ );
257
+ const handleKeyDown = (e) => {
258
+ if (e.key === "Enter") {
259
+ e.preventDefault();
260
+ onConfirm(value);
261
+ } else if (e.key === "Escape") {
262
+ e.preventDefault();
263
+ onEscape == null ? void 0 : onEscape();
264
+ }
265
+ };
266
+ const handleFocus = () => {
267
+ setIsFocused(true);
268
+ onFocus == null ? void 0 : onFocus();
269
+ };
270
+ const handleBlur = () => {
271
+ setIsFocused(false);
272
+ onBlur == null ? void 0 : onBlur(value);
273
+ };
274
+ const handleConfirmClick = () => {
275
+ onConfirm(value);
276
+ };
277
+ const wrapClasses = ["ns-input-wrap", className].filter(Boolean).join(" ");
278
+ const inputClasses = [
279
+ "ns-input",
280
+ `ns-input--${type}`,
281
+ isFocused ? "ns-input--focused" : void 0,
282
+ inputClassName
283
+ ].filter(Boolean).join(" ");
284
+ const inputMode = (() => {
285
+ switch (type) {
286
+ case "tel":
287
+ return "tel";
288
+ case "email":
289
+ return "email";
290
+ case "number":
291
+ return "numeric";
292
+ default:
293
+ return void 0;
294
+ }
295
+ })();
296
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: wrapClasses, children: [
297
+ /* @__PURE__ */ jsxRuntime.jsx(
298
+ "input",
299
+ {
300
+ ref: inputRef,
301
+ id: `ns-field-${fieldKey}`,
302
+ className: inputClasses,
303
+ type: type === "number" ? "text" : type,
304
+ inputMode,
305
+ value,
306
+ placeholder,
307
+ onChange: handleChange,
308
+ onKeyDown: handleKeyDown,
309
+ onFocus: handleFocus,
310
+ onBlur: handleBlur,
311
+ onPaste: handlePaste,
312
+ autoComplete: "off",
313
+ "aria-label": fieldKey,
314
+ "aria-invalid": void 0
315
+ }
316
+ ),
317
+ /* @__PURE__ */ jsxRuntime.jsx(EnterButton, { onConfirm: handleConfirmClick }),
318
+ suffix && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ns-suffix", children: suffix })
319
+ ] });
320
+ };
321
+ var EditIcon = React__default.default.memo(function EditIcon2({
322
+ onEdit,
323
+ label = "Edit",
324
+ className
325
+ }) {
326
+ const classes = ["ns-edit-btn", className].filter(Boolean).join(" ");
327
+ return /* @__PURE__ */ jsxRuntime.jsx(
328
+ "button",
329
+ {
330
+ type: "button",
331
+ className: classes,
332
+ onClick: onEdit,
333
+ "aria-label": label,
334
+ title: label,
335
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
336
+ "svg",
337
+ {
338
+ width: "14",
339
+ height: "14",
340
+ viewBox: "0 0 24 24",
341
+ fill: "none",
342
+ stroke: "currentColor",
343
+ strokeWidth: "2",
344
+ strokeLinecap: "round",
345
+ strokeLinejoin: "round",
346
+ "aria-hidden": "true",
347
+ children: [
348
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }),
349
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m15 5 4 4" })
350
+ ]
351
+ }
352
+ )
353
+ }
354
+ );
355
+ });
356
+ var FilledValue = React__default.default.memo(function FilledValue2({
357
+ value,
358
+ suffix,
359
+ editable = true,
360
+ onEdit,
361
+ editLabel = "Edit",
362
+ className
363
+ }) {
364
+ const classes = ["ns-filled-wrap", className].filter(Boolean).join(" ");
365
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: classes, children: [
366
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ns-filled-value", children: value }),
367
+ suffix && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ns-suffix", children: suffix }),
368
+ editable && onEdit && /* @__PURE__ */ jsxRuntime.jsx(EditIcon, { onEdit, label: editLabel })
369
+ ] });
370
+ });
371
+ var ErrorMessage = React__default.default.memo(function ErrorMessage2({
372
+ message,
373
+ display = {},
374
+ className
375
+ }) {
376
+ var _a, _b, _c, _d, _e;
377
+ const mode = (_a = display.mode) != null ? _a : "inline";
378
+ if (mode === "toast" || mode === "shake") {
379
+ return null;
380
+ }
381
+ const position = (_b = display.position) != null ? _b : "below";
382
+ const animateIn = (_c = display.animateIn) != null ? _c : "fadeUp";
383
+ const showIcon = (_d = display.icon) != null ? _d : false;
384
+ const iconChar = (_e = display.iconChar) != null ? _e : "\u26A0";
385
+ const isTooltip = mode === "tooltip";
386
+ const isInlineShake = mode === "inline+shake";
387
+ const wrapClasses = [
388
+ "ns-error-wrap",
389
+ isTooltip ? "ns-error-wrap--tooltip" : "ns-error-wrap--inline",
390
+ `ns-error-wrap--${position}`,
391
+ animateIn === "fadeUp" ? "ns-animate-fade-up" : void 0,
392
+ animateIn === "slideDown" ? "ns-animate-slide-down" : void 0,
393
+ className
394
+ ].filter(Boolean).join(" ");
395
+ const textClasses = [
396
+ "ns-error-text",
397
+ isInlineShake ? "ns-error-text--shake" : void 0
398
+ ].filter(Boolean).join(" ");
399
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: wrapClasses, role: "alert", "aria-live": "assertive", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: textClasses, children: [
400
+ showIcon && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ns-error-icon", "aria-hidden": "true", children: [
401
+ iconChar,
402
+ " "
403
+ ] }),
404
+ message
405
+ ] }) });
406
+ });
407
+ var ChipsField = function ChipsField2({
408
+ fieldKey,
409
+ options,
410
+ defaultValue,
411
+ autoAdvance = false,
412
+ onConfirm,
413
+ onChange,
414
+ className
415
+ }) {
416
+ const [selected, setSelected] = React.useState(defaultValue != null ? defaultValue : null);
417
+ const [focusedIndex, setFocusedIndex] = React.useState(0);
418
+ const chipsRef = React.useRef([]);
419
+ const handleSelect = React.useCallback(
420
+ (option) => {
421
+ setSelected(option);
422
+ onChange == null ? void 0 : onChange(option);
423
+ if (autoAdvance) {
424
+ onConfirm(option);
425
+ }
426
+ },
427
+ [autoAdvance, onConfirm, onChange]
428
+ );
429
+ const handleConfirm = React.useCallback(() => {
430
+ if (selected !== null) {
431
+ onConfirm(selected);
432
+ }
433
+ }, [selected, onConfirm]);
434
+ const handleKeyDown = React.useCallback(
435
+ (e, index) => {
436
+ var _a, _b;
437
+ switch (e.key) {
438
+ case "Enter":
439
+ case " ": {
440
+ e.preventDefault();
441
+ const option = options[index];
442
+ if (option) handleSelect(option);
443
+ break;
444
+ }
445
+ case "ArrowRight":
446
+ case "ArrowDown": {
447
+ e.preventDefault();
448
+ const nextIndex = (index + 1) % options.length;
449
+ setFocusedIndex(nextIndex);
450
+ (_a = chipsRef.current[nextIndex]) == null ? void 0 : _a.focus();
451
+ break;
452
+ }
453
+ case "ArrowLeft":
454
+ case "ArrowUp": {
455
+ e.preventDefault();
456
+ const prevIndex = (index - 1 + options.length) % options.length;
457
+ setFocusedIndex(prevIndex);
458
+ (_b = chipsRef.current[prevIndex]) == null ? void 0 : _b.focus();
459
+ break;
460
+ }
461
+ }
462
+ },
463
+ [handleSelect, options]
464
+ );
465
+ const wrapClasses = ["ns-chips-wrap", className].filter(Boolean).join(" ");
466
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: wrapClasses, role: "listbox", "aria-label": fieldKey, children: [
467
+ options.map((option, index) => {
468
+ const chipClasses = [
469
+ "ns-chip",
470
+ selected === option ? "ns-chip--active" : void 0
471
+ ].filter(Boolean).join(" ");
472
+ return /* @__PURE__ */ jsxRuntime.jsx(
473
+ "button",
474
+ {
475
+ ref: (el) => {
476
+ chipsRef.current[index] = el;
477
+ },
478
+ type: "button",
479
+ className: chipClasses,
480
+ onClick: () => handleSelect(option),
481
+ onKeyDown: (e) => handleKeyDown(e, index),
482
+ "aria-selected": selected === option,
483
+ role: "option",
484
+ tabIndex: index === focusedIndex ? 0 : -1,
485
+ children: option
486
+ },
487
+ option
488
+ );
489
+ }),
490
+ !autoAdvance && selected !== null && /* @__PURE__ */ jsxRuntime.jsx(
491
+ "button",
492
+ {
493
+ type: "button",
494
+ className: "ns-enter-btn",
495
+ onClick: handleConfirm,
496
+ "aria-label": "Confirm",
497
+ style: { opacity: 1, transform: "none" },
498
+ children: "\u21B5"
499
+ }
500
+ )
501
+ ] });
502
+ };
503
+ var MultiChipsField = function MultiChipsField2({
504
+ fieldKey,
505
+ options,
506
+ defaultValue,
507
+ onConfirm,
508
+ onChange,
509
+ className
510
+ }) {
511
+ const [selected, setSelected] = React.useState(
512
+ new Set(defaultValue != null ? defaultValue : [])
513
+ );
514
+ const [hoveredIndex, setHoveredIndex] = React.useState(null);
515
+ const handleToggle = React.useCallback(
516
+ (option) => {
517
+ setSelected((prev) => {
518
+ const next = new Set(prev);
519
+ if (next.has(option)) {
520
+ next.delete(option);
521
+ } else {
522
+ next.add(option);
523
+ }
524
+ const value = Array.from(next).join(", ");
525
+ onChange == null ? void 0 : onChange(value);
526
+ return next;
527
+ });
528
+ },
529
+ [onChange]
530
+ );
531
+ const handleConfirm = React.useCallback(() => {
532
+ if (selected.size > 0) {
533
+ onConfirm(Array.from(selected).join(", "));
534
+ }
535
+ }, [selected, onConfirm]);
536
+ const handleKeyDown = React.useCallback(
537
+ (e, option) => {
538
+ if (e.key === "Enter" && selected.size > 0) {
539
+ e.preventDefault();
540
+ handleConfirm();
541
+ } else if (e.key === " ") {
542
+ e.preventDefault();
543
+ handleToggle(option);
544
+ }
545
+ },
546
+ [handleToggle, handleConfirm, selected.size]
547
+ );
548
+ const wrapClasses = ["ns-chips-wrap", className].filter(Boolean).join(" ");
549
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: wrapClasses, children: [
550
+ options.map((option, index) => {
551
+ const isSelected = selected.has(option);
552
+ const chipClasses = [
553
+ "ns-chip",
554
+ isSelected ? "ns-chip--active" : void 0,
555
+ hoveredIndex === index ? "ns-chip--hover" : void 0
556
+ ].filter(Boolean).join(" ");
557
+ return /* @__PURE__ */ jsxRuntime.jsx(
558
+ "button",
559
+ {
560
+ type: "button",
561
+ className: chipClasses,
562
+ onClick: () => handleToggle(option),
563
+ onKeyDown: (e) => handleKeyDown(e, option),
564
+ onMouseEnter: () => setHoveredIndex(index),
565
+ onMouseLeave: () => setHoveredIndex(null),
566
+ "aria-pressed": isSelected,
567
+ role: "option",
568
+ children: option
569
+ },
570
+ option
571
+ );
572
+ }),
573
+ selected.size > 0 && /* @__PURE__ */ jsxRuntime.jsx(
574
+ "button",
575
+ {
576
+ type: "button",
577
+ className: "ns-enter-btn",
578
+ onClick: handleConfirm,
579
+ "aria-label": "Confirm",
580
+ children: "\u21B5"
581
+ }
582
+ )
583
+ ] });
584
+ };
585
+ var SelectField = function SelectField2({
586
+ fieldKey,
587
+ options,
588
+ placeholder = "Select\u2026",
589
+ defaultValue = "",
590
+ autoAdvance = false,
591
+ onConfirm,
592
+ onChange,
593
+ onFocus,
594
+ onBlur,
595
+ inputClassName,
596
+ className
597
+ }) {
598
+ const [value, setValue] = React.useState(defaultValue);
599
+ const selectRef = React.useRef(null);
600
+ React.useEffect(() => {
601
+ var _a;
602
+ (_a = selectRef.current) == null ? void 0 : _a.focus();
603
+ }, []);
604
+ const handleChange = React.useCallback(
605
+ (e) => {
606
+ const newValue = e.target.value;
607
+ setValue(newValue);
608
+ onChange == null ? void 0 : onChange(newValue);
609
+ if (autoAdvance && newValue !== "") {
610
+ onConfirm(newValue);
611
+ }
612
+ },
613
+ [autoAdvance, onConfirm, onChange]
614
+ );
615
+ const handleKeyDown = React.useCallback(
616
+ (e) => {
617
+ if (e.key === "Enter" && value !== "") {
618
+ e.preventDefault();
619
+ onConfirm(value);
620
+ }
621
+ },
622
+ [value, onConfirm]
623
+ );
624
+ const handleConfirmClick = React.useCallback(() => {
625
+ if (value !== "") {
626
+ onConfirm(value);
627
+ }
628
+ }, [value, onConfirm]);
629
+ const wrapClasses = ["ns-select-wrap", className].filter(Boolean).join(" ");
630
+ const selectClasses = ["ns-select", inputClassName].filter(Boolean).join(" ");
631
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: wrapClasses, children: [
632
+ /* @__PURE__ */ jsxRuntime.jsxs(
633
+ "select",
634
+ {
635
+ ref: selectRef,
636
+ id: `ns-field-${fieldKey}`,
637
+ className: selectClasses,
638
+ value,
639
+ onChange: handleChange,
640
+ onKeyDown: handleKeyDown,
641
+ onFocus: () => onFocus == null ? void 0 : onFocus(),
642
+ onBlur: () => onBlur == null ? void 0 : onBlur(value),
643
+ "aria-label": fieldKey,
644
+ children: [
645
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }),
646
+ options.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option, children: option }, option))
647
+ ]
648
+ }
649
+ ),
650
+ !autoAdvance && value !== "" && /* @__PURE__ */ jsxRuntime.jsx(
651
+ "button",
652
+ {
653
+ type: "button",
654
+ className: "ns-enter-btn",
655
+ onClick: handleConfirmClick,
656
+ "aria-label": "Confirm",
657
+ children: "\u21B5"
658
+ }
659
+ )
660
+ ] });
661
+ };
662
+ var OtpField = function OtpField2({
663
+ fieldKey,
664
+ otpLength = 6,
665
+ autoAdvance = true,
666
+ onRequest,
667
+ onVerify,
668
+ onConfirm,
669
+ onChange,
670
+ resendLabel = "Resend code",
671
+ resendDelay = 30,
672
+ className
673
+ }) {
674
+ const [digits, setDigits] = React.useState(Array.from({ length: otpLength }, () => ""));
675
+ const [activeIndex, setActiveIndex] = React.useState(0);
676
+ const [timer, setTimer] = React.useState(resendDelay);
677
+ const [canResend, setCanResend] = React.useState(false);
678
+ const inputRefs = React.useRef([]);
679
+ const timerRef = React.useRef(null);
680
+ const hasRequestedRef = React.useRef(false);
681
+ React.useEffect(() => {
682
+ if (!hasRequestedRef.current) {
683
+ hasRequestedRef.current = true;
684
+ onRequest == null ? void 0 : onRequest();
685
+ }
686
+ }, [onRequest]);
687
+ React.useEffect(() => {
688
+ if (resendDelay <= 0) {
689
+ setCanResend(true);
690
+ return;
691
+ }
692
+ setTimer(resendDelay);
693
+ setCanResend(false);
694
+ timerRef.current = setInterval(() => {
695
+ setTimer((prev) => {
696
+ if (prev <= 1) {
697
+ if (timerRef.current !== null) {
698
+ clearInterval(timerRef.current);
699
+ timerRef.current = null;
700
+ }
701
+ setCanResend(true);
702
+ return 0;
703
+ }
704
+ return prev - 1;
705
+ });
706
+ }, 1e3);
707
+ return () => {
708
+ if (timerRef.current !== null) {
709
+ clearInterval(timerRef.current);
710
+ timerRef.current = null;
711
+ }
712
+ };
713
+ }, [resendDelay]);
714
+ React.useEffect(() => {
715
+ var _a;
716
+ (_a = inputRefs.current[0]) == null ? void 0 : _a.focus();
717
+ }, []);
718
+ const getOtpString = React.useCallback(
719
+ (d) => d.join(""),
720
+ []
721
+ );
722
+ const handleDigitChange = React.useCallback(
723
+ (index, value) => {
724
+ const digit = value.replace(/\D/g, "").slice(-1);
725
+ setDigits((prev) => {
726
+ var _a;
727
+ const next = [...prev];
728
+ next[index] = digit;
729
+ const otpString = getOtpString(next);
730
+ onChange == null ? void 0 : onChange(otpString);
731
+ if (digit !== "" && index < otpLength - 1) {
732
+ (_a = inputRefs.current[index + 1]) == null ? void 0 : _a.focus();
733
+ setActiveIndex(index + 1);
734
+ }
735
+ if (next.every((d) => d !== "")) {
736
+ onVerify == null ? void 0 : onVerify(otpString);
737
+ if (autoAdvance) {
738
+ setTimeout(() => onConfirm(otpString), 0);
739
+ }
740
+ }
741
+ return next;
742
+ });
743
+ },
744
+ [otpLength, autoAdvance, onConfirm, onVerify, onChange, getOtpString]
745
+ );
746
+ const handleKeyDown = React.useCallback(
747
+ (index, e) => {
748
+ var _a, _b;
749
+ if (e.key === "Backspace") {
750
+ e.preventDefault();
751
+ setDigits((prev) => {
752
+ var _a2;
753
+ const next = [...prev];
754
+ if (next[index] !== "") {
755
+ next[index] = "";
756
+ onChange == null ? void 0 : onChange(getOtpString(next));
757
+ } else if (index > 0) {
758
+ next[index - 1] = "";
759
+ (_a2 = inputRefs.current[index - 1]) == null ? void 0 : _a2.focus();
760
+ setActiveIndex(index - 1);
761
+ onChange == null ? void 0 : onChange(getOtpString(next));
762
+ }
763
+ return next;
764
+ });
765
+ } else if (e.key === "Enter") {
766
+ e.preventDefault();
767
+ const otpString = getOtpString(digits);
768
+ if (digits.every((d) => d !== "")) {
769
+ onConfirm(otpString);
770
+ }
771
+ } else if (e.key === "ArrowLeft" && index > 0) {
772
+ e.preventDefault();
773
+ (_a = inputRefs.current[index - 1]) == null ? void 0 : _a.focus();
774
+ setActiveIndex(index - 1);
775
+ } else if (e.key === "ArrowRight" && index < otpLength - 1) {
776
+ e.preventDefault();
777
+ (_b = inputRefs.current[index + 1]) == null ? void 0 : _b.focus();
778
+ setActiveIndex(index + 1);
779
+ }
780
+ },
781
+ [digits, otpLength, onConfirm, onChange, getOtpString]
782
+ );
783
+ const handlePaste = React.useCallback(
784
+ (e) => {
785
+ e.preventDefault();
786
+ const pastedData = e.clipboardData.getData("text").replace(/\D/g, "").slice(0, otpLength);
787
+ if (pastedData.length === 0) return;
788
+ setDigits((prev) => {
789
+ var _a;
790
+ const next = [...prev];
791
+ for (let i = 0; i < pastedData.length; i++) {
792
+ const char = pastedData[i];
793
+ if (char !== void 0) {
794
+ next[i] = char;
795
+ }
796
+ }
797
+ const otpString = getOtpString(next);
798
+ onChange == null ? void 0 : onChange(otpString);
799
+ const nextEmptyIndex = next.findIndex((d) => d === "");
800
+ const focusIndex = nextEmptyIndex === -1 ? otpLength - 1 : nextEmptyIndex;
801
+ (_a = inputRefs.current[focusIndex]) == null ? void 0 : _a.focus();
802
+ setActiveIndex(focusIndex);
803
+ if (next.every((d) => d !== "")) {
804
+ onVerify == null ? void 0 : onVerify(otpString);
805
+ if (autoAdvance) {
806
+ setTimeout(() => onConfirm(otpString), 0);
807
+ }
808
+ }
809
+ return next;
810
+ });
811
+ },
812
+ [otpLength, autoAdvance, onConfirm, onVerify, onChange, getOtpString]
813
+ );
814
+ const handleResend = React.useCallback(() => {
815
+ var _a;
816
+ if (!canResend) return;
817
+ setCanResend(false);
818
+ setTimer(resendDelay);
819
+ setDigits(Array.from({ length: otpLength }, () => ""));
820
+ setActiveIndex(0);
821
+ (_a = inputRefs.current[0]) == null ? void 0 : _a.focus();
822
+ onRequest == null ? void 0 : onRequest();
823
+ timerRef.current = setInterval(() => {
824
+ setTimer((prev) => {
825
+ if (prev <= 1) {
826
+ if (timerRef.current !== null) {
827
+ clearInterval(timerRef.current);
828
+ timerRef.current = null;
829
+ }
830
+ setCanResend(true);
831
+ return 0;
832
+ }
833
+ return prev - 1;
834
+ });
835
+ }, 1e3);
836
+ }, [canResend, resendDelay, otpLength, onRequest]);
837
+ const handleFocus = React.useCallback((index) => {
838
+ setActiveIndex(index);
839
+ }, []);
840
+ const wrapClasses = ["ns-otp-wrap", className].filter(Boolean).join(" ");
841
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: wrapClasses, children: [
842
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ns-otp-boxes", children: digits.map((digit, index) => {
843
+ const boxClasses = [
844
+ "ns-otp-box",
845
+ digit !== "" ? "ns-otp-box--filled" : void 0,
846
+ activeIndex === index ? "ns-otp-box--active" : void 0
847
+ ].filter(Boolean).join(" ");
848
+ return /* @__PURE__ */ jsxRuntime.jsx(
849
+ "input",
850
+ {
851
+ ref: (el) => {
852
+ inputRefs.current[index] = el;
853
+ },
854
+ className: boxClasses,
855
+ type: "text",
856
+ inputMode: "numeric",
857
+ maxLength: 1,
858
+ value: digit,
859
+ onChange: (e) => handleDigitChange(index, e.target.value),
860
+ onKeyDown: (e) => handleKeyDown(index, e),
861
+ onPaste: handlePaste,
862
+ onFocus: () => handleFocus(index),
863
+ autoComplete: "one-time-code",
864
+ "aria-label": `Digit ${String(index + 1)}`
865
+ },
866
+ index
867
+ );
868
+ }) }),
869
+ !autoAdvance && digits.every((d) => d !== "") && /* @__PURE__ */ jsxRuntime.jsx(
870
+ "button",
871
+ {
872
+ type: "button",
873
+ className: "ns-enter-btn",
874
+ onClick: () => onConfirm(getOtpString(digits)),
875
+ "aria-label": "Confirm",
876
+ children: "\u21B5"
877
+ }
878
+ ),
879
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ns-otp-resend-wrap", children: canResend ? /* @__PURE__ */ jsxRuntime.jsx(
880
+ "button",
881
+ {
882
+ type: "button",
883
+ className: "ns-otp-resend",
884
+ onClick: handleResend,
885
+ children: resendLabel
886
+ }
887
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ns-otp-resend ns-otp-resend--disabled", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ns-otp-timer", children: [
888
+ "Resend in ",
889
+ String(timer),
890
+ "s"
891
+ ] }) }) })
892
+ ] });
893
+ };
894
+ var PasswordField = function PasswordField2({
895
+ showToggle = true,
896
+ inputClassName,
897
+ ...props
898
+ }) {
899
+ const [visible, setVisible] = React.useState(false);
900
+ const toggleVisibility = () => {
901
+ setVisible((prev) => !prev);
902
+ };
903
+ const combinedClassName = [
904
+ inputClassName,
905
+ "ns-input--password"
906
+ ].filter(Boolean).join(" ");
907
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ns-password-wrap", children: [
908
+ /* @__PURE__ */ jsxRuntime.jsx(
909
+ InlineInput,
910
+ {
911
+ ...props,
912
+ type: visible ? "text" : "password",
913
+ inputClassName: combinedClassName
914
+ }
915
+ ),
916
+ showToggle && /* @__PURE__ */ jsxRuntime.jsx(
917
+ "button",
918
+ {
919
+ type: "button",
920
+ className: "ns-password-toggle",
921
+ onClick: toggleVisibility,
922
+ "aria-label": visible ? "Hide password" : "Show password",
923
+ tabIndex: -1,
924
+ children: visible ? "Hide" : "Show"
925
+ }
926
+ )
927
+ ] });
928
+ };
929
+ var DateField = function DateField2({
930
+ fieldKey,
931
+ placeholder,
932
+ defaultValue,
933
+ suffix,
934
+ onConfirm,
935
+ onChange,
936
+ onFocus,
937
+ onBlur,
938
+ inputClassName,
939
+ className
940
+ }) {
941
+ return /* @__PURE__ */ jsxRuntime.jsx(
942
+ InlineInput,
943
+ {
944
+ fieldKey,
945
+ type: "date",
946
+ placeholder,
947
+ defaultValue,
948
+ suffix,
949
+ onConfirm,
950
+ onChange,
951
+ onFocus,
952
+ onBlur,
953
+ inputClassName,
954
+ className
955
+ }
956
+ );
957
+ };
958
+ var ToastContext = React.createContext(null);
959
+ var useToast = () => {
960
+ const context = React.useContext(ToastContext);
961
+ if (!context) {
962
+ throw new Error("useToast must be used within a ToastProvider");
963
+ }
964
+ return context;
965
+ };
966
+ var ToastProvider = ({ children }) => {
967
+ const [toasts, setToasts] = React.useState([]);
968
+ const showToast = React.useCallback((message, icon = false, iconChar = "\u26A0") => {
969
+ const id = Math.random().toString(36).substring(2, 9);
970
+ setToasts((prev) => [...prev, { id, message, icon, iconChar }]);
971
+ setTimeout(() => {
972
+ setToasts((prev) => prev.filter((t) => t.id !== id));
973
+ }, 3e3);
974
+ }, []);
975
+ const hideToast = React.useCallback((id) => {
976
+ setToasts((prev) => prev.filter((t) => t.id !== id));
977
+ }, []);
978
+ return /* @__PURE__ */ jsxRuntime.jsxs(ToastContext.Provider, { value: { showToast, hideToast }, children: [
979
+ children,
980
+ toasts.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ns-toast-container", "aria-live": "polite", children: toasts.map((toast) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ns-toast ns-animate-fade-up", children: [
981
+ toast.icon && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ns-toast-icon", children: [
982
+ toast.iconChar,
983
+ " "
984
+ ] }),
985
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ns-toast-message", children: toast.message }),
986
+ /* @__PURE__ */ jsxRuntime.jsx(
987
+ "button",
988
+ {
989
+ type: "button",
990
+ className: "ns-toast-close",
991
+ onClick: () => hideToast(toast.id),
992
+ "aria-label": "Close",
993
+ children: "\xD7"
994
+ }
995
+ )
996
+ ] }, toast.id)) })
997
+ ] });
998
+ };
999
+ function renderFieldInput({
1000
+ field,
1001
+ status,
1002
+ value,
1003
+ handleConfirm,
1004
+ handleChange,
1005
+ handleFocus,
1006
+ handleBlur
1007
+ }) {
1008
+ var _a, _b, _c, _d;
1009
+ const editValue = status === "editing" && value !== void 0 ? String(value) : field.defaultValue;
1010
+ switch (field.type) {
1011
+ case "chips":
1012
+ return /* @__PURE__ */ jsxRuntime.jsx(
1013
+ ChipsField,
1014
+ {
1015
+ fieldKey: field.key,
1016
+ options: (_a = field.options) != null ? _a : [],
1017
+ defaultValue: editValue,
1018
+ autoAdvance: field.autoAdvance,
1019
+ onConfirm: handleConfirm,
1020
+ onChange: handleChange,
1021
+ className: field.inputClassName
1022
+ }
1023
+ );
1024
+ case "multi-chips":
1025
+ return /* @__PURE__ */ jsxRuntime.jsx(
1026
+ MultiChipsField,
1027
+ {
1028
+ fieldKey: field.key,
1029
+ options: (_b = field.options) != null ? _b : [],
1030
+ defaultValue: status === "editing" && Array.isArray(value) ? value : editValue == null ? void 0 : editValue.split(", ").filter(Boolean),
1031
+ onConfirm: handleConfirm,
1032
+ onChange: handleChange,
1033
+ className: field.inputClassName
1034
+ }
1035
+ );
1036
+ case "select":
1037
+ return /* @__PURE__ */ jsxRuntime.jsx(
1038
+ SelectField,
1039
+ {
1040
+ fieldKey: field.key,
1041
+ options: (_c = field.options) != null ? _c : [],
1042
+ placeholder: field.placeholder,
1043
+ defaultValue: editValue,
1044
+ autoAdvance: field.autoAdvance,
1045
+ onConfirm: handleConfirm,
1046
+ onChange: handleChange,
1047
+ onFocus: handleFocus,
1048
+ onBlur: handleBlur,
1049
+ inputClassName: field.inputClassName
1050
+ }
1051
+ );
1052
+ case "otp":
1053
+ return /* @__PURE__ */ jsxRuntime.jsx(
1054
+ OtpField,
1055
+ {
1056
+ fieldKey: field.key,
1057
+ otpLength: field.otpLength,
1058
+ autoAdvance: (_d = field.autoAdvance) != null ? _d : true,
1059
+ onRequest: field.onRequest,
1060
+ onVerify: field.onVerify,
1061
+ onConfirm: handleConfirm,
1062
+ onChange: handleChange,
1063
+ resendLabel: field.resendLabel,
1064
+ resendDelay: field.resendDelay,
1065
+ className: field.inputClassName
1066
+ }
1067
+ );
1068
+ case "password":
1069
+ return /* @__PURE__ */ jsxRuntime.jsx(
1070
+ PasswordField,
1071
+ {
1072
+ fieldKey: field.key,
1073
+ placeholder: field.placeholder,
1074
+ defaultValue: editValue,
1075
+ suffix: field.suffix,
1076
+ onConfirm: handleConfirm,
1077
+ onChange: handleChange,
1078
+ onFocus: handleFocus,
1079
+ onBlur: handleBlur,
1080
+ inputClassName: field.inputClassName
1081
+ }
1082
+ );
1083
+ case "date":
1084
+ return /* @__PURE__ */ jsxRuntime.jsx(
1085
+ DateField,
1086
+ {
1087
+ fieldKey: field.key,
1088
+ placeholder: field.placeholder,
1089
+ defaultValue: editValue,
1090
+ suffix: field.suffix,
1091
+ onConfirm: handleConfirm,
1092
+ onChange: handleChange,
1093
+ onFocus: handleFocus,
1094
+ onBlur: handleBlur,
1095
+ inputClassName: field.inputClassName
1096
+ }
1097
+ );
1098
+ // text, tel, email, number — all use InlineInput with the appropriate type
1099
+ default:
1100
+ return /* @__PURE__ */ jsxRuntime.jsx(
1101
+ InlineInput,
1102
+ {
1103
+ fieldKey: field.key,
1104
+ type: field.type,
1105
+ placeholder: field.placeholder,
1106
+ defaultValue: editValue,
1107
+ suffix: field.suffix,
1108
+ sanitise: field.sanitise,
1109
+ onConfirm: handleConfirm,
1110
+ onChange: handleChange,
1111
+ onFocus: handleFocus,
1112
+ onBlur: handleBlur,
1113
+ inputClassName: field.inputClassName
1114
+ }
1115
+ );
1116
+ }
1117
+ }
1118
+ var Line = React__default.default.memo(function Line2({
1119
+ field,
1120
+ status,
1121
+ value,
1122
+ allValues = {},
1123
+ typewriter,
1124
+ editable = true,
1125
+ locked = false,
1126
+ editLabel = "Edit",
1127
+ onTypingComplete,
1128
+ onConfirm,
1129
+ onEdit,
1130
+ onError,
1131
+ onChange,
1132
+ onFocus,
1133
+ onBlur
1134
+ }) {
1135
+ var _a, _b, _c, _d, _e;
1136
+ const [error, setError] = React.useState(null);
1137
+ const [shake, setShake] = React.useState(false);
1138
+ const [asyncState, setAsyncState] = React.useState("idle");
1139
+ const abortRef = React.useRef(null);
1140
+ const { showToast } = useToast();
1141
+ const isFieldEditable = field.editable !== false && editable && !locked;
1142
+ const shouldAnimate = field.animate !== false && (typewriter == null ? void 0 : typewriter.enabled) !== false;
1143
+ const handleTypingComplete = React.useCallback(() => {
1144
+ onTypingComplete(field.key);
1145
+ }, [field.key, onTypingComplete]);
1146
+ const handleConfirm = React.useCallback(
1147
+ (val) => {
1148
+ var _a2, _b2, _c2, _d2;
1149
+ (_a2 = abortRef.current) == null ? void 0 : _a2.call(abortRef);
1150
+ abortRef.current = null;
1151
+ const result = narrativeFormCore.validateField(val, field.validation, allValues);
1152
+ if (!result.valid) {
1153
+ const firstError = (_b2 = result.errors[0]) != null ? _b2 : "Validation failed";
1154
+ setError(firstError);
1155
+ setAsyncState("idle");
1156
+ onError == null ? void 0 : onError(field.key, firstError);
1157
+ const display = (_c2 = field.validation) == null ? void 0 : _c2.errorDisplay;
1158
+ const mode = (_d2 = display == null ? void 0 : display.mode) != null ? _d2 : "inline";
1159
+ if (mode === "shake" || mode === "inline+shake") {
1160
+ setShake(false);
1161
+ setTimeout(() => setShake(true), 10);
1162
+ }
1163
+ if (mode === "toast") {
1164
+ showToast(firstError, display == null ? void 0 : display.icon, display == null ? void 0 : display.iconChar);
1165
+ }
1166
+ return;
1167
+ }
1168
+ if (narrativeFormCore.hasAsyncValidation(field.validation)) {
1169
+ setAsyncState("validating");
1170
+ setError(null);
1171
+ const handle = narrativeFormCore.validateFieldAsync(val, field.validation, allValues);
1172
+ abortRef.current = handle.abort;
1173
+ handle.promise.then((asyncResult) => {
1174
+ var _a3;
1175
+ if (!asyncResult.valid) {
1176
+ const firstError = (_a3 = asyncResult.errors[0]) != null ? _a3 : "Validation failed";
1177
+ setError(firstError);
1178
+ setAsyncState("invalid");
1179
+ onError == null ? void 0 : onError(field.key, firstError);
1180
+ } else {
1181
+ setAsyncState("valid");
1182
+ setError(null);
1183
+ setTimeout(() => {
1184
+ setAsyncState("idle");
1185
+ onConfirm(field.key, val);
1186
+ }, 300);
1187
+ }
1188
+ }).catch(() => {
1189
+ setAsyncState("idle");
1190
+ });
1191
+ return;
1192
+ }
1193
+ setError(null);
1194
+ setShake(false);
1195
+ setAsyncState("idle");
1196
+ onConfirm(field.key, val);
1197
+ },
1198
+ [field.key, field.validation, allValues, onConfirm, onError, showToast]
1199
+ );
1200
+ const handleEdit = React.useCallback(() => {
1201
+ var _a2;
1202
+ setError(null);
1203
+ setShake(false);
1204
+ setAsyncState("idle");
1205
+ (_a2 = abortRef.current) == null ? void 0 : _a2.call(abortRef);
1206
+ onEdit(field.key);
1207
+ }, [field.key, onEdit]);
1208
+ const handleChange = React.useCallback(
1209
+ (val) => {
1210
+ var _a2, _b2, _c2;
1211
+ const clearOn = (_c2 = (_b2 = (_a2 = field.validation) == null ? void 0 : _a2.errorDisplay) == null ? void 0 : _b2.clearOn) != null ? _c2 : "onChange";
1212
+ if (clearOn === "onChange" && error !== null) {
1213
+ setError(null);
1214
+ setShake(false);
1215
+ setAsyncState("idle");
1216
+ }
1217
+ onChange == null ? void 0 : onChange(field.key, val);
1218
+ },
1219
+ [field.key, onChange, error, (_b = (_a = field.validation) == null ? void 0 : _a.errorDisplay) == null ? void 0 : _b.clearOn]
1220
+ );
1221
+ const handleFocus = React.useCallback(() => {
1222
+ var _a2, _b2, _c2;
1223
+ const clearOn = (_c2 = (_b2 = (_a2 = field.validation) == null ? void 0 : _a2.errorDisplay) == null ? void 0 : _b2.clearOn) != null ? _c2 : "onChange";
1224
+ if (clearOn === "onFocus" && error !== null) {
1225
+ setError(null);
1226
+ setShake(false);
1227
+ setAsyncState("idle");
1228
+ }
1229
+ onFocus == null ? void 0 : onFocus(field.key);
1230
+ }, [field.key, onFocus, error, (_d = (_c = field.validation) == null ? void 0 : _c.errorDisplay) == null ? void 0 : _d.clearOn]);
1231
+ const handleBlur = React.useCallback(
1232
+ (val) => {
1233
+ onBlur == null ? void 0 : onBlur(field.key, val);
1234
+ },
1235
+ [field.key, onBlur]
1236
+ );
1237
+ const lineClasses = [
1238
+ "ns-line",
1239
+ `ns-line-${field.key}`,
1240
+ status === "active" || status === "editing" ? "ns-line--active" : void 0,
1241
+ status === "confirmed" ? "ns-line--confirmed" : void 0,
1242
+ status === "editing" ? "ns-line--editing" : void 0,
1243
+ error !== null ? "ns-line--error" : void 0,
1244
+ shake ? "ns-line--shake" : void 0,
1245
+ field.className
1246
+ ].filter(Boolean).join(" ");
1247
+ const showInput = status === "active" || status === "editing";
1248
+ const showFilled = status === "confirmed";
1249
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lineClasses, role: "group", "aria-label": field.prefix, children: [
1250
+ /* @__PURE__ */ jsxRuntime.jsx(
1251
+ Prose,
1252
+ {
1253
+ text: field.prefix,
1254
+ animate: status === "typing" && shouldAnimate,
1255
+ speed: typewriter == null ? void 0 : typewriter.speed,
1256
+ cursor: typewriter == null ? void 0 : typewriter.cursor,
1257
+ cursorChar: typewriter == null ? void 0 : typewriter.cursorChar,
1258
+ pauseAfter: typewriter == null ? void 0 : typewriter.pauseAfter,
1259
+ onComplete: status === "typing" ? handleTypingComplete : void 0
1260
+ }
1261
+ ),
1262
+ showInput && renderFieldInput({
1263
+ field,
1264
+ status,
1265
+ value,
1266
+ handleConfirm,
1267
+ handleChange,
1268
+ handleFocus,
1269
+ handleBlur
1270
+ }),
1271
+ showInput && asyncState === "validating" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ns-loading-indicator", "aria-label": "Validating" }),
1272
+ showInput && asyncState === "valid" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ns-success-indicator", "aria-label": "Valid", children: "\u2713" }),
1273
+ showFilled && /* @__PURE__ */ jsxRuntime.jsx(
1274
+ FilledValue,
1275
+ {
1276
+ value: String(value != null ? value : ""),
1277
+ suffix: field.suffix,
1278
+ editable: isFieldEditable,
1279
+ onEdit: handleEdit,
1280
+ editLabel
1281
+ }
1282
+ ),
1283
+ error !== null && showInput && /* @__PURE__ */ jsxRuntime.jsx(
1284
+ ErrorMessage,
1285
+ {
1286
+ message: error,
1287
+ display: (_e = field.validation) == null ? void 0 : _e.errorDisplay
1288
+ }
1289
+ )
1290
+ ] });
1291
+ });
1292
+ var ThemeContext = React.createContext({
1293
+ theme: {},
1294
+ isDark: false
1295
+ });
1296
+ function useTheme() {
1297
+ return React.useContext(ThemeContext);
1298
+ }
1299
+ var TOKEN_TO_CSS_VAR = {
1300
+ background: "--ns-bg",
1301
+ textColor: "--ns-text",
1302
+ inputBorderColor: "--ns-border",
1303
+ placeholderColor: "--ns-placeholder-color",
1304
+ errorColor: "--ns-error",
1305
+ filledColor: "--ns-filled-color",
1306
+ cursorColor: "--ns-cursor-color",
1307
+ successColor: "--ns-success-color",
1308
+ loadingColor: "--ns-loading-color",
1309
+ fontFamily: "--ns-font-family",
1310
+ uiFontFamily: "--ns-ui-font",
1311
+ fontSize: "--ns-font-size",
1312
+ mobileFontSize: "--ns-mobile-font-size",
1313
+ inputFontStyle: "--ns-input-font-style",
1314
+ lineGap: "--ns-line-gap",
1315
+ pagePadding: "--ns-page-padding",
1316
+ buttonRadius: "--ns-btn-radius",
1317
+ buttonBackground: "--ns-btn-bg",
1318
+ buttonColor: "--ns-btn-color",
1319
+ enterBtnSize: "--ns-enter-size",
1320
+ chipBorderRadius: "--ns-chip-radius",
1321
+ chipBorderColor: "--ns-chip-border",
1322
+ chipActiveBackground: "--ns-chip-active-bg",
1323
+ chipActiveColor: "--ns-chip-active-color",
1324
+ chipFontStyle: "--ns-chip-font-style"
1325
+ };
1326
+ function themeToCssVars(theme) {
1327
+ const style = {};
1328
+ for (const [tokenName, cssVar] of Object.entries(TOKEN_TO_CSS_VAR)) {
1329
+ const value = theme[tokenName];
1330
+ if (typeof value === "string" && value.length > 0) {
1331
+ style[cssVar] = value;
1332
+ }
1333
+ }
1334
+ return style;
1335
+ }
1336
+ function usePrefersDark() {
1337
+ const [prefersDark, setPrefersDark] = React.useState(() => {
1338
+ if (typeof window === "undefined") return false;
1339
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
1340
+ });
1341
+ React.useEffect(() => {
1342
+ if (typeof window === "undefined") return;
1343
+ const mql = window.matchMedia("(prefers-color-scheme: dark)");
1344
+ const handler = (e) => {
1345
+ setPrefersDark(e.matches);
1346
+ };
1347
+ mql.addEventListener("change", handler);
1348
+ return () => mql.removeEventListener("change", handler);
1349
+ }, []);
1350
+ return prefersDark;
1351
+ }
1352
+ var ThemeProvider = function ThemeProvider2({
1353
+ theme = {},
1354
+ children
1355
+ }) {
1356
+ const systemPrefersDark = usePrefersDark();
1357
+ const isDark = React.useMemo(() => {
1358
+ if (theme.mode === "dark") return true;
1359
+ if (theme.mode === "auto") return systemPrefersDark;
1360
+ return false;
1361
+ }, [theme.mode, systemPrefersDark]);
1362
+ const resolvedTheme = React.useMemo(() => {
1363
+ if (isDark && theme.dark) {
1364
+ return { ...theme, ...theme.dark };
1365
+ }
1366
+ return theme;
1367
+ }, [theme, isDark]);
1368
+ const cssVars = React.useMemo(() => themeToCssVars(resolvedTheme), [resolvedTheme]);
1369
+ const contextValue = React.useMemo(
1370
+ () => ({ theme: resolvedTheme, isDark }),
1371
+ [resolvedTheme, isDark]
1372
+ );
1373
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: cssVars, className: isDark ? "ns-root--dark" : void 0, children }) });
1374
+ };
1375
+ var WelcomeScreen = React__default.default.memo(
1376
+ function WelcomeScreen2({ welcome, typewriter, onStart, className }) {
1377
+ var _a, _b;
1378
+ const heading = (_a = welcome.heading) != null ? _a : "Welcome";
1379
+ const subtext = welcome.subtext;
1380
+ const ctaLabel = (_b = welcome.ctaLabel) != null ? _b : "Let\u2019s go \u2192";
1381
+ const shouldAnimate = (typewriter == null ? void 0 : typewriter.enabled) !== false;
1382
+ const handleClick = React.useCallback(() => {
1383
+ onStart();
1384
+ }, [onStart]);
1385
+ const wrapperClasses = ["ns-welcome", className].filter(Boolean).join(" ");
1386
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClasses, children: [
1387
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "ns-welcome-heading", children: /* @__PURE__ */ jsxRuntime.jsx(
1388
+ Prose,
1389
+ {
1390
+ text: heading,
1391
+ animate: shouldAnimate,
1392
+ speed: typewriter == null ? void 0 : typewriter.speed,
1393
+ cursor: typewriter == null ? void 0 : typewriter.cursor,
1394
+ cursorChar: typewriter == null ? void 0 : typewriter.cursorChar
1395
+ }
1396
+ ) }),
1397
+ subtext && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ns-welcome-subtext", children: subtext }),
1398
+ /* @__PURE__ */ jsxRuntime.jsx(
1399
+ "button",
1400
+ {
1401
+ type: "button",
1402
+ className: "ns-welcome-cta",
1403
+ onClick: handleClick,
1404
+ children: ctaLabel
1405
+ }
1406
+ )
1407
+ ] });
1408
+ }
1409
+ );
1410
+ function interpolateMessage(template, values) {
1411
+ return template.replace(/\{(\w+)\}/g, (_match, key) => {
1412
+ const val = values[key];
1413
+ if (Array.isArray(val)) return val.join(", ");
1414
+ return typeof val === "string" ? val : `{${key}}`;
1415
+ });
1416
+ }
1417
+ var DoneScreen = React__default.default.memo(
1418
+ function DoneScreen2({ done, values, meta, typewriter, className }) {
1419
+ var _a;
1420
+ const [submitState, setSubmitState] = React.useState("default");
1421
+ const [errorMessage, setErrorMessage] = React.useState(null);
1422
+ const ctaLabel = (_a = done.ctaLabel) != null ? _a : "Continue \u2192";
1423
+ const shouldAnimate = (typewriter == null ? void 0 : typewriter.enabled) !== false;
1424
+ const resolvedMessage = (() => {
1425
+ if (typeof done.message === "function") {
1426
+ return done.message(values);
1427
+ }
1428
+ if (typeof done.message === "string") {
1429
+ return interpolateMessage(done.message, values);
1430
+ }
1431
+ return "You\u2019re all set!";
1432
+ })();
1433
+ const handleSubmit = React.useCallback(async () => {
1434
+ if (!done.onSubmit || submitState === "loading" || submitState === "success") {
1435
+ return;
1436
+ }
1437
+ setSubmitState("loading");
1438
+ setErrorMessage(null);
1439
+ try {
1440
+ await done.onSubmit(values, meta);
1441
+ setSubmitState("success");
1442
+ } catch (err) {
1443
+ const msg = err instanceof Error ? err.message : "Something went wrong. Please try again.";
1444
+ setErrorMessage(msg);
1445
+ setSubmitState("error");
1446
+ }
1447
+ }, [done, values, meta, submitState]);
1448
+ const wrapperClasses = ["ns-done", className].filter(Boolean).join(" ");
1449
+ const ctaClasses = [
1450
+ "ns-done-cta",
1451
+ submitState === "loading" ? "ns-done-cta--loading" : void 0,
1452
+ submitState === "success" ? "ns-done-cta--success" : void 0,
1453
+ submitState === "error" ? "ns-done-cta--error" : void 0
1454
+ ].filter(Boolean).join(" ");
1455
+ const buttonLabel = (() => {
1456
+ switch (submitState) {
1457
+ case "loading":
1458
+ return "Please wait\u2026";
1459
+ case "success":
1460
+ return "\u2713 Done";
1461
+ case "error":
1462
+ return "Try again";
1463
+ default:
1464
+ return ctaLabel;
1465
+ }
1466
+ })();
1467
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClasses, children: [
1468
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ns-done-message", children: /* @__PURE__ */ jsxRuntime.jsx(
1469
+ Prose,
1470
+ {
1471
+ text: resolvedMessage,
1472
+ animate: shouldAnimate,
1473
+ speed: typewriter == null ? void 0 : typewriter.speed,
1474
+ cursor: typewriter == null ? void 0 : typewriter.cursor,
1475
+ cursorChar: typewriter == null ? void 0 : typewriter.cursorChar
1476
+ }
1477
+ ) }),
1478
+ done.onSubmit && /* @__PURE__ */ jsxRuntime.jsxs(
1479
+ "button",
1480
+ {
1481
+ type: "button",
1482
+ className: ctaClasses,
1483
+ onClick: handleSubmit,
1484
+ disabled: submitState === "loading" || submitState === "success",
1485
+ children: [
1486
+ submitState === "loading" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ns-loading-indicator", "aria-hidden": "true" }),
1487
+ buttonLabel
1488
+ ]
1489
+ }
1490
+ ),
1491
+ errorMessage !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ns-done-error", children: errorMessage })
1492
+ ] });
1493
+ }
1494
+ );
1495
+ var NarrativeFormInner = function NarrativeFormInner2({
1496
+ fields: staticFields,
1497
+ fieldsUrl,
1498
+ fieldsUrlHeaders,
1499
+ formConfig,
1500
+ typewriter,
1501
+ welcome: welcomeProp,
1502
+ done: doneProp,
1503
+ editable = true,
1504
+ editLabel,
1505
+ className,
1506
+ callbacks,
1507
+ formRef,
1508
+ defaultValues,
1509
+ values: controlledValues,
1510
+ strings: stringsProp,
1511
+ locale,
1512
+ direction: directionProp,
1513
+ crossFieldValidators,
1514
+ loadingComponent,
1515
+ errorComponent,
1516
+ onFetchError,
1517
+ retryLabel,
1518
+ reducedMotion
1519
+ }) {
1520
+ var _a;
1521
+ const { isDark } = useTheme();
1522
+ const i18n = React.useMemo(() => narrativeFormCore.mergeStrings(stringsProp), [stringsProp]);
1523
+ const direction = React.useMemo(() => {
1524
+ var _a2;
1525
+ if (directionProp) return directionProp;
1526
+ if (locale) {
1527
+ const rtlLocales = ["ar", "he", "fa", "ur"];
1528
+ const lang = (_a2 = locale.split("-")[0]) == null ? void 0 : _a2.toLowerCase();
1529
+ if (lang && rtlLocales.includes(lang)) return "rtl";
1530
+ }
1531
+ return "ltr";
1532
+ }, [directionProp, locale]);
1533
+ const { config: dynamicConfig, loading: dynamicLoading, error: dynamicError, retry } = useDynamicForm({ fieldsUrl, fieldsUrlHeaders, onFetchError });
1534
+ const resolvedConfig = React.useMemo(() => {
1535
+ if (staticFields) return { fields: staticFields };
1536
+ if (formConfig) return formConfig;
1537
+ if (dynamicConfig) return dynamicConfig;
1538
+ return null;
1539
+ }, [staticFields, formConfig, dynamicConfig]);
1540
+ const fields = (_a = resolvedConfig == null ? void 0 : resolvedConfig.fields) != null ? _a : [];
1541
+ const welcome = welcomeProp != null ? welcomeProp : resolvedConfig && "welcome" in resolvedConfig ? resolvedConfig.welcome : void 0;
1542
+ const done = doneProp != null ? doneProp : resolvedConfig && "done" in resolvedConfig ? resolvedConfig.done : void 0;
1543
+ const effectiveTypewriter = React.useMemo(() => {
1544
+ var _a2;
1545
+ return {
1546
+ ...typewriter,
1547
+ enabled: reducedMotion ? false : (_a2 = typewriter == null ? void 0 : typewriter.enabled) != null ? _a2 : true
1548
+ };
1549
+ }, [typewriter, reducedMotion]);
1550
+ const {
1551
+ snapshot,
1552
+ startTyping,
1553
+ activateField,
1554
+ confirmField,
1555
+ editField,
1556
+ reconfirmField,
1557
+ next,
1558
+ focusField,
1559
+ reset,
1560
+ getValues,
1561
+ getMeta
1562
+ } = useFormState(fields);
1563
+ const showWelcome = (welcome == null ? void 0 : welcome.show) !== false && welcome !== void 0;
1564
+ const [welcomeDismissed, setWelcomeDismissed] = React.useState(!showWelcome);
1565
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
1566
+ const hasStartedRef = React.useRef(false);
1567
+ const defaultsAppliedRef = React.useRef(false);
1568
+ React.useEffect(() => {
1569
+ if (defaultsAppliedRef.current || !defaultValues) return;
1570
+ defaultsAppliedRef.current = true;
1571
+ for (const field of fields) {
1572
+ const val = defaultValues[field.key];
1573
+ if (val !== void 0) {
1574
+ startTyping(field.key);
1575
+ activateField(field.key);
1576
+ confirmField(field.key, val);
1577
+ }
1578
+ }
1579
+ }, [defaultValues, fields, startTyping, activateField, confirmField]);
1580
+ React.useEffect(() => {
1581
+ if (!welcomeDismissed) return;
1582
+ if (hasStartedRef.current) return;
1583
+ if (fields.length === 0) return;
1584
+ hasStartedRef.current = true;
1585
+ const firstField = fields.find((f) => snapshot.statuses[f.key] !== "confirmed");
1586
+ if (firstField) {
1587
+ startTyping(firstField.key);
1588
+ }
1589
+ }, [welcomeDismissed, fields, startTyping, snapshot.statuses]);
1590
+ React.useEffect(() => {
1591
+ if (!formRef) return;
1592
+ const handle = {
1593
+ next,
1594
+ getValues,
1595
+ reset: () => {
1596
+ setWelcomeDismissed(!showWelcome);
1597
+ hasStartedRef.current = false;
1598
+ defaultsAppliedRef.current = false;
1599
+ reset();
1600
+ },
1601
+ focusField
1602
+ };
1603
+ if (typeof formRef === "function") {
1604
+ formRef(handle);
1605
+ } else if (formRef && "current" in formRef) {
1606
+ formRef.current = handle;
1607
+ }
1608
+ }, [formRef, next, getValues, reset, focusField, showWelcome]);
1609
+ const handleWelcomeStart = React.useCallback(() => {
1610
+ setWelcomeDismissed(true);
1611
+ }, []);
1612
+ const handleTypingComplete = React.useCallback(
1613
+ (key) => {
1614
+ activateField(key);
1615
+ },
1616
+ [activateField]
1617
+ );
1618
+ const handleConfirm = React.useCallback(
1619
+ (key, value) => {
1620
+ var _a2, _b;
1621
+ const status = snapshot.statuses[key];
1622
+ const finalValue = (controlledValues == null ? void 0 : controlledValues[key]) !== void 0 ? String(controlledValues[key]) : value;
1623
+ if (status === "editing") {
1624
+ reconfirmField(key, finalValue);
1625
+ } else {
1626
+ confirmField(key, finalValue);
1627
+ }
1628
+ (_a2 = callbacks == null ? void 0 : callbacks.onFieldComplete) == null ? void 0 : _a2.call(callbacks, key, finalValue, 0);
1629
+ const updatedValues = { ...snapshot.values, [key]: finalValue };
1630
+ const visibleFieldKeys = fields.filter((f) => {
1631
+ if (!f.showIf) return true;
1632
+ return f.showIf(updatedValues);
1633
+ }).map((f) => f.key);
1634
+ const allConfirmed = visibleFieldKeys.every(
1635
+ (k) => k === key || snapshot.statuses[k] === "confirmed"
1636
+ );
1637
+ if (allConfirmed) {
1638
+ const finalValues = {};
1639
+ for (const k of visibleFieldKeys) {
1640
+ const v = k === key ? finalValue : snapshot.values[k];
1641
+ if (v !== void 0) finalValues[k] = v;
1642
+ }
1643
+ (_b = callbacks == null ? void 0 : callbacks.onComplete) == null ? void 0 : _b.call(callbacks, finalValues, getMeta());
1644
+ } else {
1645
+ const currentIndex = fields.findIndex((f) => f.key === key);
1646
+ for (let i = currentIndex + 1; i < fields.length; i++) {
1647
+ const nextField = fields[i];
1648
+ if (!nextField) continue;
1649
+ if (snapshot.statuses[nextField.key] === "confirmed") continue;
1650
+ const nextUpdatedValues = { ...snapshot.values, [key]: finalValue };
1651
+ if (nextField.showIf && !nextField.showIf(nextUpdatedValues)) continue;
1652
+ startTyping(nextField.key);
1653
+ break;
1654
+ }
1655
+ }
1656
+ },
1657
+ [snapshot, fields, confirmField, reconfirmField, startTyping, callbacks, getMeta, controlledValues]
1658
+ );
1659
+ const handleEdit = React.useCallback(
1660
+ (key) => {
1661
+ var _a2;
1662
+ editField(key);
1663
+ (_a2 = callbacks == null ? void 0 : callbacks.onEdit) == null ? void 0 : _a2.call(callbacks, key);
1664
+ },
1665
+ [editField, callbacks]
1666
+ );
1667
+ const handleError = React.useCallback(
1668
+ (key, message) => {
1669
+ var _a2;
1670
+ (_a2 = callbacks == null ? void 0 : callbacks.onError) == null ? void 0 : _a2.call(callbacks, key, message);
1671
+ },
1672
+ [callbacks]
1673
+ );
1674
+ const handleChange = React.useCallback(
1675
+ (key, value) => {
1676
+ var _a2;
1677
+ (_a2 = callbacks == null ? void 0 : callbacks.onChange) == null ? void 0 : _a2.call(callbacks, key, value);
1678
+ },
1679
+ [callbacks]
1680
+ );
1681
+ const handleFocus = React.useCallback(
1682
+ (key) => {
1683
+ var _a2;
1684
+ (_a2 = callbacks == null ? void 0 : callbacks.onFieldFocus) == null ? void 0 : _a2.call(callbacks, key);
1685
+ },
1686
+ [callbacks]
1687
+ );
1688
+ const handleBlur = React.useCallback(
1689
+ (key, value) => {
1690
+ var _a2;
1691
+ (_a2 = callbacks == null ? void 0 : callbacks.onFieldBlur) == null ? void 0 : _a2.call(callbacks, key, value);
1692
+ },
1693
+ [callbacks]
1694
+ );
1695
+ React.useEffect(() => {
1696
+ const onDropOff = callbacks == null ? void 0 : callbacks.onDropOff;
1697
+ if (!onDropOff) return;
1698
+ if (typeof window === "undefined") return;
1699
+ const handleVisibility = () => {
1700
+ if (document.visibilityState === "hidden" && !snapshot.isComplete) {
1701
+ const activeField = fields.find(
1702
+ (f) => snapshot.statuses[f.key] === "active" || snapshot.statuses[f.key] === "editing"
1703
+ );
1704
+ if (activeField) {
1705
+ onDropOff(activeField.key);
1706
+ }
1707
+ }
1708
+ };
1709
+ document.addEventListener("visibilitychange", handleVisibility);
1710
+ return () => document.removeEventListener("visibilitychange", handleVisibility);
1711
+ }, [callbacks, snapshot, fields]);
1712
+ const visibleFields = React.useMemo(() => {
1713
+ return fields.filter((field) => {
1714
+ const status = snapshot.statuses[field.key];
1715
+ const isStarted = status === "typing" || status === "active" || status === "confirmed" || status === "editing";
1716
+ if (!isStarted) return false;
1717
+ if (field.showIf && !field.showIf(snapshot.values)) return false;
1718
+ return true;
1719
+ });
1720
+ }, [fields, snapshot.statuses, snapshot.values]);
1721
+ const lockedFields = React.useMemo(() => {
1722
+ const locked = /* @__PURE__ */ new Set();
1723
+ for (let i = 0; i < fields.length; i++) {
1724
+ const field = fields[i];
1725
+ if (!field) continue;
1726
+ if (field.lockPrevious && (snapshot.statuses[field.key] === "active" || snapshot.statuses[field.key] === "typing" || snapshot.statuses[field.key] === "editing" || snapshot.statuses[field.key] === "confirmed")) {
1727
+ for (let j = 0; j < i; j++) {
1728
+ const prev = fields[j];
1729
+ if (prev) locked.add(prev.key);
1730
+ }
1731
+ }
1732
+ }
1733
+ return locked;
1734
+ }, [fields, snapshot.statuses]);
1735
+ const showDone = (done == null ? void 0 : done.show) !== false && done !== void 0 && snapshot.isComplete;
1736
+ if (fieldsUrl && dynamicLoading) {
1737
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ns-root", children: loadingComponent != null ? loadingComponent : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ns-loading-indicator", "aria-label": "Loading form" }) });
1738
+ }
1739
+ if (fieldsUrl && dynamicError) {
1740
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ns-root", children: errorComponent != null ? errorComponent : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ns-done", children: [
1741
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ns-error-text", children: i18n.fetchErrorMessage }),
1742
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ns-done-cta", onClick: retry, children: retryLabel != null ? retryLabel : i18n.retryLabel })
1743
+ ] }) });
1744
+ }
1745
+ const rootClasses = [
1746
+ "ns-root",
1747
+ snapshot.isComplete ? "ns-root--complete" : void 0,
1748
+ isDark ? "ns-root--dark" : void 0,
1749
+ isSubmitting ? "ns-root--submitting" : void 0,
1750
+ className
1751
+ ].filter(Boolean).join(" ");
1752
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rootClasses, dir: direction, lang: locale, children: [
1753
+ !welcomeDismissed && welcome && /* @__PURE__ */ jsxRuntime.jsx(
1754
+ WelcomeScreen,
1755
+ {
1756
+ welcome,
1757
+ typewriter: effectiveTypewriter,
1758
+ onStart: handleWelcomeStart
1759
+ }
1760
+ ),
1761
+ welcomeDismissed && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ns-letter", role: "form", "aria-label": "Narrative form", children: visibleFields.map((field) => {
1762
+ var _a2, _b;
1763
+ return /* @__PURE__ */ jsxRuntime.jsx(
1764
+ Line,
1765
+ {
1766
+ field,
1767
+ status: (_a2 = snapshot.statuses[field.key]) != null ? _a2 : "idle",
1768
+ value: (_b = controlledValues == null ? void 0 : controlledValues[field.key]) != null ? _b : snapshot.values[field.key],
1769
+ allValues: snapshot.values,
1770
+ typewriter: effectiveTypewriter,
1771
+ editable,
1772
+ locked: lockedFields.has(field.key),
1773
+ editLabel: editLabel != null ? editLabel : i18n.editLabel,
1774
+ onTypingComplete: handleTypingComplete,
1775
+ onConfirm: handleConfirm,
1776
+ onEdit: handleEdit,
1777
+ onError: handleError,
1778
+ onChange: handleChange,
1779
+ onFocus: handleFocus,
1780
+ onBlur: handleBlur
1781
+ },
1782
+ field.key
1783
+ );
1784
+ }) }),
1785
+ showDone && done && /* @__PURE__ */ jsxRuntime.jsx(
1786
+ DoneScreen,
1787
+ {
1788
+ done,
1789
+ values: snapshot.values,
1790
+ meta: getMeta(),
1791
+ typewriter: effectiveTypewriter
1792
+ }
1793
+ )
1794
+ ] });
1795
+ };
1796
+ var NarrativeForm = function NarrativeForm2(props) {
1797
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeProvider, { theme: props.theme, children: /* @__PURE__ */ jsxRuntime.jsx(ToastProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(NarrativeFormInner, { ...props }) }) });
1798
+ };
1799
+ var TextField = function TextField2(props) {
1800
+ return /* @__PURE__ */ jsxRuntime.jsx(InlineInput, { ...props, type: "text" });
1801
+ };
1802
+ var TelField = function TelField2(props) {
1803
+ return /* @__PURE__ */ jsxRuntime.jsx(InlineInput, { ...props, type: "tel" });
1804
+ };
1805
+ var EmailField = function EmailField2(props) {
1806
+ return /* @__PURE__ */ jsxRuntime.jsx(InlineInput, { ...props, type: "email" });
1807
+ };
1808
+ var NumberField = function NumberField2(props) {
1809
+ return /* @__PURE__ */ jsxRuntime.jsx(InlineInput, { ...props, type: "number" });
1810
+ };
1811
+
1812
+ Object.defineProperty(exports, "FormStateEngine", {
1813
+ enumerable: true,
1814
+ get: function () { return narrativeFormCore.FormStateEngine; }
1815
+ });
1816
+ Object.defineProperty(exports, "clearValidators", {
1817
+ enumerable: true,
1818
+ get: function () { return narrativeFormCore.clearValidators; }
1819
+ });
1820
+ Object.defineProperty(exports, "defaultStrings", {
1821
+ enumerable: true,
1822
+ get: function () { return narrativeFormCore.defaultStrings; }
1823
+ });
1824
+ Object.defineProperty(exports, "getRegisteredValidatorNames", {
1825
+ enumerable: true,
1826
+ get: function () { return narrativeFormCore.getRegisteredValidatorNames; }
1827
+ });
1828
+ Object.defineProperty(exports, "getValidator", {
1829
+ enumerable: true,
1830
+ get: function () { return narrativeFormCore.getValidator; }
1831
+ });
1832
+ Object.defineProperty(exports, "hasAsyncValidation", {
1833
+ enumerable: true,
1834
+ get: function () { return narrativeFormCore.hasAsyncValidation; }
1835
+ });
1836
+ Object.defineProperty(exports, "hasValidator", {
1837
+ enumerable: true,
1838
+ get: function () { return narrativeFormCore.hasValidator; }
1839
+ });
1840
+ Object.defineProperty(exports, "mergeStrings", {
1841
+ enumerable: true,
1842
+ get: function () { return narrativeFormCore.mergeStrings; }
1843
+ });
1844
+ Object.defineProperty(exports, "registerBuiltinValidators", {
1845
+ enumerable: true,
1846
+ get: function () { return narrativeFormCore.registerBuiltinValidators; }
1847
+ });
1848
+ Object.defineProperty(exports, "registerValidator", {
1849
+ enumerable: true,
1850
+ get: function () { return narrativeFormCore.registerValidator; }
1851
+ });
1852
+ Object.defineProperty(exports, "unregisterValidator", {
1853
+ enumerable: true,
1854
+ get: function () { return narrativeFormCore.unregisterValidator; }
1855
+ });
1856
+ Object.defineProperty(exports, "validateField", {
1857
+ enumerable: true,
1858
+ get: function () { return narrativeFormCore.validateField; }
1859
+ });
1860
+ Object.defineProperty(exports, "validateFieldAsync", {
1861
+ enumerable: true,
1862
+ get: function () { return narrativeFormCore.validateFieldAsync; }
1863
+ });
1864
+ exports.ChipsField = ChipsField;
1865
+ exports.Cursor = Cursor;
1866
+ exports.DoneScreen = DoneScreen;
1867
+ exports.EditIcon = EditIcon;
1868
+ exports.EmailField = EmailField;
1869
+ exports.EnterButton = EnterButton;
1870
+ exports.ErrorMessage = ErrorMessage;
1871
+ exports.FilledValue = FilledValue;
1872
+ exports.InlineInput = InlineInput;
1873
+ exports.Line = Line;
1874
+ exports.MultiChipsField = MultiChipsField;
1875
+ exports.NarrativeForm = NarrativeForm;
1876
+ exports.NumberField = NumberField;
1877
+ exports.OtpField = OtpField;
1878
+ exports.PasswordField = PasswordField;
1879
+ exports.Prose = Prose;
1880
+ exports.SelectField = SelectField;
1881
+ exports.TelField = TelField;
1882
+ exports.TextField = TextField;
1883
+ exports.ThemeProvider = ThemeProvider;
1884
+ exports.WelcomeScreen = WelcomeScreen;
1885
+ exports.useFormState = useFormState;
1886
+ exports.useTheme = useTheme;
1887
+ exports.useTypewriter = useTypewriter;
1888
+ //# sourceMappingURL=index.js.map
1889
+ //# sourceMappingURL=index.js.map