@viveksinghind/narrative-form-native 1.0.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/dist/components/DoneScreen.d.ts +10 -0
  4. package/dist/components/DoneScreen.d.ts.map +1 -0
  5. package/dist/components/DoneScreen.js +33 -0
  6. package/dist/components/DoneScreen.js.map +1 -0
  7. package/dist/components/ErrorMessage.d.ts +8 -0
  8. package/dist/components/ErrorMessage.d.ts.map +1 -0
  9. package/dist/components/ErrorMessage.js +40 -0
  10. package/dist/components/ErrorMessage.js.map +1 -0
  11. package/dist/components/Line.d.ts +21 -0
  12. package/dist/components/Line.d.ts.map +1 -0
  13. package/dist/components/Line.js +130 -0
  14. package/dist/components/Line.js.map +1 -0
  15. package/dist/components/NarrativeForm.d.ts +25 -0
  16. package/dist/components/NarrativeForm.d.ts.map +1 -0
  17. package/dist/components/NarrativeForm.js +113 -0
  18. package/dist/components/NarrativeForm.js.map +1 -0
  19. package/dist/components/ThemeProvider.d.ts +13 -0
  20. package/dist/components/ThemeProvider.d.ts.map +1 -0
  21. package/dist/components/ThemeProvider.js +19 -0
  22. package/dist/components/ThemeProvider.js.map +1 -0
  23. package/dist/components/ToastProvider.d.ts +9 -0
  24. package/dist/components/ToastProvider.d.ts.map +1 -0
  25. package/dist/components/ToastProvider.js +41 -0
  26. package/dist/components/ToastProvider.js.map +1 -0
  27. package/dist/components/WelcomeScreen.d.ts +9 -0
  28. package/dist/components/WelcomeScreen.d.ts.map +1 -0
  29. package/dist/components/WelcomeScreen.js +63 -0
  30. package/dist/components/WelcomeScreen.js.map +1 -0
  31. package/dist/hooks/useDynamicForm.d.ts +18 -0
  32. package/dist/hooks/useDynamicForm.d.ts.map +1 -0
  33. package/dist/hooks/useDynamicForm.js +50 -0
  34. package/dist/hooks/useDynamicForm.js.map +1 -0
  35. package/dist/hooks/useFormState.d.ts +43 -0
  36. package/dist/hooks/useFormState.d.ts.map +1 -0
  37. package/dist/hooks/useFormState.js +57 -0
  38. package/dist/hooks/useFormState.js.map +1 -0
  39. package/dist/hooks/useTypewriter.d.ts +46 -0
  40. package/dist/hooks/useTypewriter.d.ts.map +1 -0
  41. package/dist/hooks/useTypewriter.js +94 -0
  42. package/dist/hooks/useTypewriter.js.map +1 -0
  43. package/dist/index.d.ts +10 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +10 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/index.mjs +621 -0
  48. package/dist/index.mjs.map +1 -0
  49. package/package.json +53 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,621 @@
1
+ import { createContext, useState, useRef, useCallback, useMemo, useEffect, useContext } from 'react';
2
+ import { StyleSheet, Animated, Text, Keyboard, View, TouchableOpacity, TextInput, ScrollView } from 'react-native';
3
+ import { FormStateEngine, fetchFormConfig, validateField, hasAsyncValidation, validateFieldAsync, mergeStrings } from '@viveksinghind/narrative-form-core';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ // src/components/NarrativeForm.tsx
7
+ function useFormState(fields) {
8
+ const [, setTick] = useState(0);
9
+ const engineRef = useRef(null);
10
+ if (engineRef.current === null) {
11
+ engineRef.current = new FormStateEngine(fields, () => {
12
+ setTick((t) => t + 1);
13
+ });
14
+ }
15
+ const engine = engineRef.current;
16
+ const startTyping = useCallback((key) => engine.startTyping(key), [engine]);
17
+ const activateField = useCallback((key) => engine.activateField(key), [engine]);
18
+ const confirmField = useCallback(
19
+ (key, value) => engine.confirmField(key, value),
20
+ [engine]
21
+ );
22
+ const editField = useCallback((key) => engine.editField(key), [engine]);
23
+ const reconfirmField = useCallback(
24
+ (key, value) => engine.reconfirmField(key, value),
25
+ [engine]
26
+ );
27
+ const next = useCallback(() => engine.next(), [engine]);
28
+ const focusField = useCallback((key) => engine.focusField(key), [engine]);
29
+ const reset = useCallback(() => engine.reset(), [engine]);
30
+ const getValues = useCallback(() => engine.getValues(), [engine]);
31
+ const getMeta = useCallback(
32
+ (formId, formVersion) => engine.getMeta(formId, formVersion),
33
+ [engine]
34
+ );
35
+ const snapshot = engine.getSnapshot();
36
+ return useMemo(
37
+ () => ({
38
+ snapshot,
39
+ startTyping,
40
+ activateField,
41
+ confirmField,
42
+ editField,
43
+ reconfirmField,
44
+ next,
45
+ focusField,
46
+ reset,
47
+ getValues,
48
+ getMeta
49
+ }),
50
+ // snapshot changes every tick, which is what we want
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
+ [snapshot]
53
+ );
54
+ }
55
+ function useDynamicForm({
56
+ fieldsUrl,
57
+ fieldsUrlHeaders,
58
+ formConfig,
59
+ onFetchError
60
+ }) {
61
+ const [config, setConfig] = useState(formConfig != null ? formConfig : null);
62
+ const [loading, setLoading] = useState(!!fieldsUrl && !formConfig);
63
+ const [error, setError] = useState(null);
64
+ const [retryCount, setRetryCount] = useState(0);
65
+ useEffect(() => {
66
+ if (formConfig) {
67
+ setConfig(formConfig);
68
+ setLoading(false);
69
+ setError(null);
70
+ return;
71
+ }
72
+ if (!fieldsUrl) {
73
+ return;
74
+ }
75
+ let isMounted = true;
76
+ setLoading(true);
77
+ setError(null);
78
+ fetchFormConfig(fieldsUrl, { headers: fieldsUrlHeaders }).then((data) => {
79
+ if (isMounted) {
80
+ setConfig(data);
81
+ setLoading(false);
82
+ }
83
+ }).catch((err) => {
84
+ if (isMounted) {
85
+ setError(err);
86
+ setLoading(false);
87
+ onFetchError == null ? void 0 : onFetchError(err);
88
+ }
89
+ });
90
+ return () => {
91
+ isMounted = false;
92
+ };
93
+ }, [fieldsUrl, fieldsUrlHeaders, formConfig, retryCount, onFetchError]);
94
+ const retry = useCallback(() => {
95
+ if (fieldsUrl && !formConfig) {
96
+ setRetryCount((c) => c + 1);
97
+ }
98
+ }, [fieldsUrl, formConfig]);
99
+ return { config, loading, error, retry };
100
+ }
101
+ var ThemeContext = createContext({
102
+ theme: void 0,
103
+ isDark: false
104
+ });
105
+ var useTheme = () => useContext(ThemeContext);
106
+ var ThemeProvider = ({ theme, children }) => {
107
+ const value = useMemo(() => {
108
+ return {
109
+ theme,
110
+ isDark: !!(theme == null ? void 0 : theme.mode) && theme.mode === "dark"
111
+ };
112
+ }, [theme]);
113
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
114
+ };
115
+ var ErrorMessage = ({ message, config }) => {
116
+ var _a;
117
+ const { theme } = useTheme();
118
+ const opacity = useRef(new Animated.Value(0)).current;
119
+ const translateY = useRef(new Animated.Value(-10)).current;
120
+ useEffect(() => {
121
+ if (message) {
122
+ Animated.parallel([
123
+ Animated.timing(opacity, {
124
+ toValue: 1,
125
+ duration: 200,
126
+ useNativeDriver: true
127
+ }),
128
+ Animated.timing(translateY, {
129
+ toValue: 0,
130
+ duration: 200,
131
+ useNativeDriver: true
132
+ })
133
+ ]).start();
134
+ } else {
135
+ opacity.setValue(0);
136
+ translateY.setValue(-10);
137
+ }
138
+ }, [message, opacity, translateY]);
139
+ if (!message) return null;
140
+ return /* @__PURE__ */ jsx(Animated.View, { style: { opacity, transform: [{ translateY }] }, children: /* @__PURE__ */ jsx(Text, { style: [styles.errorText, { color: ((_a = theme == null ? void 0 : theme.colors) == null ? void 0 : _a.error) || "#d32f2f" }], children: message }) });
141
+ };
142
+ var styles = StyleSheet.create({
143
+ errorText: {
144
+ fontSize: 14,
145
+ marginTop: 4,
146
+ marginBottom: 8
147
+ }
148
+ });
149
+ var Line = ({
150
+ field,
151
+ status,
152
+ value,
153
+ allValues,
154
+ typewriter,
155
+ editable,
156
+ locked,
157
+ editLabel,
158
+ onTypingComplete,
159
+ onConfirm,
160
+ onEdit,
161
+ onError,
162
+ onChange,
163
+ onFocus,
164
+ onBlur
165
+ }) => {
166
+ var _a, _b;
167
+ const { theme, isDark } = useTheme();
168
+ const [typedPrompt, setTypedPrompt] = useState("");
169
+ const [localValue, setLocalValue] = useState(value ? String(value) : "");
170
+ const [errorMsg, setErrorMsg] = useState(null);
171
+ const [isValidating, setIsValidating] = useState(false);
172
+ const inputRef = useRef(null);
173
+ const opacity = useRef(new Animated.Value(0)).current;
174
+ useEffect(() => {
175
+ Animated.timing(opacity, {
176
+ toValue: 1,
177
+ duration: 300,
178
+ useNativeDriver: true
179
+ }).start();
180
+ }, [opacity]);
181
+ useEffect(() => {
182
+ if (value !== void 0 && String(value) !== localValue) {
183
+ setLocalValue(String(value));
184
+ }
185
+ }, [value]);
186
+ useEffect(() => {
187
+ var _a2;
188
+ if (status !== "typing") return;
189
+ if (!typewriter.enabled) {
190
+ setTypedPrompt(field.prompt);
191
+ onTypingComplete(field.key);
192
+ return;
193
+ }
194
+ let i = 0;
195
+ const interval = setInterval(() => {
196
+ setTypedPrompt(field.prompt.slice(0, i + 1));
197
+ i++;
198
+ if (i >= field.prompt.length) {
199
+ clearInterval(interval);
200
+ onTypingComplete(field.key);
201
+ }
202
+ }, (_a2 = typewriter.delayMs) != null ? _a2 : 30);
203
+ return () => clearInterval(interval);
204
+ }, [status, field.prompt, field.key, typewriter, onTypingComplete]);
205
+ useEffect(() => {
206
+ if (status === "active" || status === "editing") {
207
+ setTimeout(() => {
208
+ var _a2;
209
+ (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
210
+ }, 50);
211
+ }
212
+ }, [status]);
213
+ const handleConfirm = useCallback(async () => {
214
+ const syncResult = validateField(field, localValue);
215
+ if (!syncResult.valid) {
216
+ setErrorMsg(syncResult.error || "Invalid input");
217
+ onError(field.key, syncResult.error || "Invalid input");
218
+ return;
219
+ }
220
+ if (hasAsyncValidation(field)) {
221
+ setIsValidating(true);
222
+ const asyncResult = await validateFieldAsync(field, localValue);
223
+ setIsValidating(false);
224
+ if (!asyncResult.valid) {
225
+ setErrorMsg(asyncResult.error || "Invalid input");
226
+ onError(field.key, asyncResult.error || "Invalid input");
227
+ return;
228
+ }
229
+ }
230
+ setErrorMsg(null);
231
+ Keyboard.dismiss();
232
+ onConfirm(field.key, localValue);
233
+ }, [field, localValue, onError, onConfirm]);
234
+ const textColor = ((_a = theme == null ? void 0 : theme.colors) == null ? void 0 : _a.text) || (isDark ? "#fff" : "#000");
235
+ const primaryColor = ((_b = theme == null ? void 0 : theme.colors) == null ? void 0 : _b.primary) || "#007bff";
236
+ const isConfirmed = status === "confirmed";
237
+ const isActive = status === "active" || status === "editing";
238
+ return /* @__PURE__ */ jsxs(Animated.View, { style: [styles2.container, { opacity }], children: [
239
+ /* @__PURE__ */ jsxs(View, { style: styles2.promptRow, children: [
240
+ /* @__PURE__ */ jsx(Text, { style: [styles2.prompt, { color: textColor }], children: status === "typing" ? typedPrompt : field.prompt }),
241
+ isConfirmed && /* @__PURE__ */ jsx(Text, { style: [styles2.value, { color: primaryColor }], children: localValue }),
242
+ isConfirmed && editable && !locked && /* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => onEdit(field.key), style: styles2.editBtn, children: /* @__PURE__ */ jsx(Text, { style: [styles2.editBtnText, { color: primaryColor }], children: editLabel }) })
243
+ ] }),
244
+ isActive && /* @__PURE__ */ jsx(View, { style: styles2.inputRow, children: /* @__PURE__ */ jsx(
245
+ TextInput,
246
+ {
247
+ ref: inputRef,
248
+ style: [
249
+ styles2.input,
250
+ { color: textColor, borderBottomColor: isValidating ? "#ccc" : primaryColor }
251
+ ],
252
+ value: localValue,
253
+ onChangeText: (text) => {
254
+ setLocalValue(text);
255
+ onChange(field.key, text);
256
+ if (errorMsg) setErrorMsg(null);
257
+ },
258
+ onFocus: () => onFocus(field.key),
259
+ onBlur: () => onBlur(field.key, localValue),
260
+ onSubmitEditing: handleConfirm,
261
+ keyboardType: field.type === "email" ? "email-address" : "default",
262
+ secureTextEntry: field.type === "password",
263
+ returnKeyType: "next",
264
+ editable: !isValidating
265
+ }
266
+ ) }),
267
+ /* @__PURE__ */ jsx(ErrorMessage, { message: errorMsg })
268
+ ] });
269
+ };
270
+ var styles2 = StyleSheet.create({
271
+ container: {
272
+ marginVertical: 12
273
+ },
274
+ promptRow: {
275
+ flexDirection: "row",
276
+ alignItems: "center",
277
+ flexWrap: "wrap"
278
+ },
279
+ prompt: {
280
+ fontSize: 18,
281
+ marginRight: 8
282
+ },
283
+ value: {
284
+ fontSize: 18,
285
+ fontWeight: "600",
286
+ marginRight: 8
287
+ },
288
+ editBtn: {
289
+ marginLeft: 8,
290
+ paddingHorizontal: 8,
291
+ paddingVertical: 4,
292
+ borderRadius: 4,
293
+ backgroundColor: "rgba(0,0,0,0.05)"
294
+ },
295
+ editBtnText: {
296
+ fontSize: 12,
297
+ fontWeight: "bold"
298
+ },
299
+ inputRow: {
300
+ marginTop: 8
301
+ },
302
+ input: {
303
+ fontSize: 18,
304
+ paddingVertical: 8,
305
+ borderBottomWidth: 2
306
+ }
307
+ });
308
+ var ToastContext = createContext({
309
+ showToast: () => {
310
+ }
311
+ });
312
+ var useToast = () => useContext(ToastContext);
313
+ var ToastProvider = ({ children }) => {
314
+ const [toast, setToast] = useState(null);
315
+ const showToast = useCallback((message, type = "info") => {
316
+ setToast({ message, type });
317
+ setTimeout(() => setToast(null), 3e3);
318
+ }, []);
319
+ return /* @__PURE__ */ jsx(ToastContext.Provider, { value: { showToast }, children: /* @__PURE__ */ jsxs(View, { style: styles3.container, children: [
320
+ children,
321
+ toast && /* @__PURE__ */ jsx(View, { style: [styles3.toast, toast.type === "error" ? styles3.toastError : styles3.toastSuccess], children: /* @__PURE__ */ jsx(Text, { style: styles3.toastText, children: toast.message }) })
322
+ ] }) });
323
+ };
324
+ var styles3 = StyleSheet.create({
325
+ container: {
326
+ flex: 1
327
+ },
328
+ toast: {
329
+ position: "absolute",
330
+ bottom: 40,
331
+ left: 20,
332
+ right: 20,
333
+ padding: 16,
334
+ borderRadius: 8,
335
+ backgroundColor: "#333",
336
+ alignItems: "center"
337
+ },
338
+ toastError: {
339
+ backgroundColor: "#d32f2f"
340
+ },
341
+ toastSuccess: {
342
+ backgroundColor: "#2e7d32"
343
+ },
344
+ toastText: {
345
+ color: "#fff",
346
+ fontSize: 14
347
+ }
348
+ });
349
+ var WelcomeScreen = ({ welcome, typewriter, onStart }) => {
350
+ var _a, _b;
351
+ const { theme, isDark } = useTheme();
352
+ const opacity = useRef(new Animated.Value(0)).current;
353
+ const [typedTitle, setTypedTitle] = useState("");
354
+ useEffect(() => {
355
+ Animated.timing(opacity, {
356
+ toValue: 1,
357
+ duration: 500,
358
+ useNativeDriver: true
359
+ }).start();
360
+ }, [opacity]);
361
+ useEffect(() => {
362
+ var _a2;
363
+ if (!typewriter.enabled) {
364
+ setTypedTitle(welcome.title);
365
+ return;
366
+ }
367
+ let i = 0;
368
+ const interval = setInterval(() => {
369
+ setTypedTitle(welcome.title.slice(0, i + 1));
370
+ i++;
371
+ if (i >= welcome.title.length) {
372
+ clearInterval(interval);
373
+ }
374
+ }, (_a2 = typewriter.delayMs) != null ? _a2 : 30);
375
+ return () => clearInterval(interval);
376
+ }, [welcome.title, typewriter]);
377
+ const textColor = ((_a = theme == null ? void 0 : theme.colors) == null ? void 0 : _a.text) || (isDark ? "#fff" : "#000");
378
+ const primaryColor = ((_b = theme == null ? void 0 : theme.colors) == null ? void 0 : _b.primary) || "#007bff";
379
+ return /* @__PURE__ */ jsxs(Animated.View, { style: [styles4.container, { opacity }], children: [
380
+ /* @__PURE__ */ jsx(Text, { style: [styles4.title, { color: textColor }], children: typedTitle }),
381
+ welcome.subtitle && /* @__PURE__ */ jsx(Text, { style: [styles4.subtitle, { color: textColor, opacity: 0.7 }], children: welcome.subtitle }),
382
+ /* @__PURE__ */ jsx(
383
+ TouchableOpacity,
384
+ {
385
+ style: [styles4.button, { backgroundColor: primaryColor }],
386
+ onPress: onStart,
387
+ accessibilityRole: "button",
388
+ children: /* @__PURE__ */ jsx(Text, { style: styles4.buttonText, children: welcome.startLabel || "Start" })
389
+ }
390
+ )
391
+ ] });
392
+ };
393
+ var styles4 = StyleSheet.create({
394
+ container: {
395
+ flex: 1,
396
+ justifyContent: "center",
397
+ alignItems: "flex-start",
398
+ padding: 20
399
+ },
400
+ title: {
401
+ fontSize: 28,
402
+ fontWeight: "bold",
403
+ marginBottom: 8
404
+ },
405
+ subtitle: {
406
+ fontSize: 16,
407
+ marginBottom: 24
408
+ },
409
+ button: {
410
+ paddingHorizontal: 24,
411
+ paddingVertical: 12,
412
+ borderRadius: 8
413
+ },
414
+ buttonText: {
415
+ color: "#fff",
416
+ fontSize: 16,
417
+ fontWeight: "600"
418
+ }
419
+ });
420
+ var DoneScreen = ({ done, values, meta }) => {
421
+ var _a;
422
+ const { theme, isDark } = useTheme();
423
+ const opacity = useRef(new Animated.Value(0)).current;
424
+ useEffect(() => {
425
+ Animated.timing(opacity, {
426
+ toValue: 1,
427
+ duration: 500,
428
+ useNativeDriver: true
429
+ }).start();
430
+ }, [opacity]);
431
+ const textColor = ((_a = theme == null ? void 0 : theme.colors) == null ? void 0 : _a.text) || (isDark ? "#fff" : "#000");
432
+ return /* @__PURE__ */ jsxs(Animated.View, { style: [styles5.container, { opacity }], children: [
433
+ /* @__PURE__ */ jsx(Text, { style: [styles5.title, { color: textColor }], children: done.title }),
434
+ done.subtitle && /* @__PURE__ */ jsx(Text, { style: [styles5.subtitle, { color: textColor, opacity: 0.7 }], children: done.subtitle })
435
+ ] });
436
+ };
437
+ var styles5 = StyleSheet.create({
438
+ container: {
439
+ padding: 20,
440
+ marginTop: 20
441
+ },
442
+ title: {
443
+ fontSize: 24,
444
+ fontWeight: "bold",
445
+ marginBottom: 8
446
+ },
447
+ subtitle: {
448
+ fontSize: 16
449
+ }
450
+ });
451
+ var NarrativeFormInner = (props) => {
452
+ var _a, _b, _c;
453
+ const { isDark } = useTheme();
454
+ const i18n = useMemo(() => mergeStrings(props.strings), [props.strings]);
455
+ const { config: dynamicConfig, loading, error, retry } = useDynamicForm({ fieldsUrl: props.fieldsUrl, fieldsUrlHeaders: props.fieldsUrlHeaders });
456
+ const resolvedConfig = useMemo(() => {
457
+ if (props.fields) return { fields: props.fields };
458
+ if (props.formConfig) return props.formConfig;
459
+ if (dynamicConfig) return dynamicConfig;
460
+ return null;
461
+ }, [props.fields, props.formConfig, dynamicConfig]);
462
+ const fields = (_a = resolvedConfig == null ? void 0 : resolvedConfig.fields) != null ? _a : [];
463
+ const welcome = (_b = props.welcome) != null ? _b : resolvedConfig && "welcome" in resolvedConfig ? resolvedConfig.welcome : void 0;
464
+ const done = (_c = props.done) != null ? _c : resolvedConfig && "done" in resolvedConfig ? resolvedConfig.done : void 0;
465
+ const effectiveTypewriter = useMemo(() => {
466
+ var _a2, _b2;
467
+ return {
468
+ ...props.typewriter,
469
+ enabled: props.reducedMotion ? false : (_b2 = (_a2 = props.typewriter) == null ? void 0 : _a2.enabled) != null ? _b2 : true
470
+ };
471
+ }, [props.typewriter, props.reducedMotion]);
472
+ const {
473
+ snapshot,
474
+ startTyping,
475
+ activateField,
476
+ confirmField,
477
+ editField,
478
+ reconfirmField,
479
+ next,
480
+ focusField,
481
+ reset,
482
+ getValues,
483
+ getMeta
484
+ } = useFormState(fields);
485
+ const showWelcome = (welcome == null ? void 0 : welcome.show) !== false && welcome !== void 0;
486
+ const [welcomeDismissed, setWelcomeDismissed] = useState(!showWelcome);
487
+ const hasStartedRef = useRef(false);
488
+ useEffect(() => {
489
+ if (!welcomeDismissed || hasStartedRef.current || fields.length === 0) return;
490
+ hasStartedRef.current = true;
491
+ const firstField = fields.find((f) => snapshot.statuses[f.key] !== "confirmed");
492
+ if (firstField) {
493
+ startTyping(firstField.key);
494
+ }
495
+ }, [welcomeDismissed, fields, startTyping, snapshot.statuses]);
496
+ const handleConfirm = useCallback((key, value) => {
497
+ var _a2, _b2;
498
+ const status = snapshot.statuses[key];
499
+ if (status === "editing") {
500
+ reconfirmField(key, value);
501
+ } else {
502
+ confirmField(key, value);
503
+ }
504
+ (_b2 = (_a2 = props.callbacks) == null ? void 0 : _a2.onFieldComplete) == null ? void 0 : _b2.call(_a2, key, value, 0);
505
+ const currentIndex = fields.findIndex((f) => f.key === key);
506
+ for (let i = currentIndex + 1; i < fields.length; i++) {
507
+ const nextField = fields[i];
508
+ if (!nextField || snapshot.statuses[nextField.key] === "confirmed") continue;
509
+ startTyping(nextField.key);
510
+ break;
511
+ }
512
+ }, [snapshot, fields, confirmField, reconfirmField, startTyping, props.callbacks]);
513
+ const visibleFields = useMemo(() => {
514
+ return fields.filter((field) => {
515
+ const status = snapshot.statuses[field.key];
516
+ return status === "typing" || status === "active" || status === "confirmed" || status === "editing";
517
+ });
518
+ }, [fields, snapshot.statuses]);
519
+ const scrollViewRef = useRef(null);
520
+ useEffect(() => {
521
+ if (visibleFields.length > 0) {
522
+ setTimeout(() => {
523
+ var _a2;
524
+ (_a2 = scrollViewRef.current) == null ? void 0 : _a2.scrollToEnd({ animated: true });
525
+ }, 100);
526
+ }
527
+ }, [visibleFields.length]);
528
+ return /* @__PURE__ */ jsxs(
529
+ ScrollView,
530
+ {
531
+ ref: scrollViewRef,
532
+ style: [styles6.container, isDark ? styles6.darkContainer : styles6.lightContainer],
533
+ contentContainerStyle: styles6.content,
534
+ keyboardShouldPersistTaps: "handled",
535
+ children: [
536
+ !welcomeDismissed && welcome && /* @__PURE__ */ jsx(
537
+ WelcomeScreen,
538
+ {
539
+ welcome,
540
+ typewriter: effectiveTypewriter,
541
+ onStart: () => setWelcomeDismissed(true)
542
+ }
543
+ ),
544
+ welcomeDismissed && /* @__PURE__ */ jsx(View, { style: styles6.formBody, children: visibleFields.map((field) => {
545
+ var _a2, _b2, _c2, _d, _e;
546
+ return /* @__PURE__ */ jsx(
547
+ Line,
548
+ {
549
+ field,
550
+ status: (_a2 = snapshot.statuses[field.key]) != null ? _a2 : "idle",
551
+ value: (_c2 = (_b2 = props.values) == null ? void 0 : _b2[field.key]) != null ? _c2 : snapshot.values[field.key],
552
+ allValues: snapshot.values,
553
+ typewriter: effectiveTypewriter,
554
+ editable: (_d = props.editable) != null ? _d : true,
555
+ locked: false,
556
+ editLabel: (_e = props.editLabel) != null ? _e : i18n.editLabel,
557
+ onTypingComplete: (k) => activateField(k),
558
+ onConfirm: handleConfirm,
559
+ onEdit: (k) => {
560
+ var _a3, _b3;
561
+ editField(k);
562
+ (_b3 = (_a3 = props.callbacks) == null ? void 0 : _a3.onEdit) == null ? void 0 : _b3.call(_a3, k);
563
+ },
564
+ onError: (k, err) => {
565
+ var _a3, _b3;
566
+ return (_b3 = (_a3 = props.callbacks) == null ? void 0 : _a3.onError) == null ? void 0 : _b3.call(_a3, k, err);
567
+ },
568
+ onChange: (k, v) => {
569
+ var _a3, _b3;
570
+ return (_b3 = (_a3 = props.callbacks) == null ? void 0 : _a3.onChange) == null ? void 0 : _b3.call(_a3, k, v);
571
+ },
572
+ onFocus: (k) => {
573
+ var _a3, _b3;
574
+ return (_b3 = (_a3 = props.callbacks) == null ? void 0 : _a3.onFieldFocus) == null ? void 0 : _b3.call(_a3, k);
575
+ },
576
+ onBlur: (k, v) => {
577
+ var _a3, _b3;
578
+ return (_b3 = (_a3 = props.callbacks) == null ? void 0 : _a3.onFieldBlur) == null ? void 0 : _b3.call(_a3, k, v);
579
+ }
580
+ },
581
+ field.key
582
+ );
583
+ }) }),
584
+ snapshot.isComplete && done && /* @__PURE__ */ jsx(
585
+ DoneScreen,
586
+ {
587
+ done,
588
+ values: snapshot.values,
589
+ meta: getMeta(),
590
+ typewriter: effectiveTypewriter
591
+ }
592
+ )
593
+ ]
594
+ }
595
+ );
596
+ };
597
+ var NarrativeForm = (props) => {
598
+ return /* @__PURE__ */ jsx(ThemeProvider, { theme: props.theme, children: /* @__PURE__ */ jsx(ToastProvider, { children: /* @__PURE__ */ jsx(NarrativeFormInner, { ...props }) }) });
599
+ };
600
+ var styles6 = StyleSheet.create({
601
+ container: {
602
+ flex: 1
603
+ },
604
+ lightContainer: {
605
+ backgroundColor: "#ffffff"
606
+ },
607
+ darkContainer: {
608
+ backgroundColor: "#121212"
609
+ },
610
+ content: {
611
+ padding: 20,
612
+ flexGrow: 1
613
+ },
614
+ formBody: {
615
+ flex: 1
616
+ }
617
+ });
618
+
619
+ export { DoneScreen, ErrorMessage, Line, NarrativeForm, ThemeProvider, ToastProvider, WelcomeScreen, useDynamicForm, useFormState, useTheme, useToast };
620
+ //# sourceMappingURL=index.mjs.map
621
+ //# sourceMappingURL=index.mjs.map