@viveksinghind/narrative-form-core 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.
@@ -0,0 +1,484 @@
1
+ interface NarrativeErrorDisplay {
2
+ mode?: "inline" | "toast" | "shake" | "inline+shake" | "tooltip";
3
+ position?: "below" | "above";
4
+ icon?: boolean;
5
+ iconChar?: string;
6
+ animateIn?: "fadeUp" | "slideDown" | "none";
7
+ clearOn?: "onChange" | "onFocus";
8
+ }
9
+ interface NarrativeField {
10
+ key: string;
11
+ type: NarrativeFieldType;
12
+ prefix: string;
13
+ suffix?: string;
14
+ placeholder?: string;
15
+ order?: number;
16
+ options?: string[];
17
+ animate?: boolean;
18
+ autoAdvance?: boolean;
19
+ lockPrevious?: boolean;
20
+ showIf?: (values: NarrativeFieldValues) => boolean;
21
+ className?: string;
22
+ inputClassName?: string;
23
+ editable?: boolean;
24
+ defaultValue?: string;
25
+ validation?: NarrativeValidation;
26
+ sanitise?: (value: string) => string;
27
+ format?: (value: string) => string;
28
+ otpLength?: number;
29
+ onRequest?: () => void | Promise<void>;
30
+ onVerify?: (otp: string) => void | Promise<void>;
31
+ resendLabel?: string;
32
+ resendDelay?: number;
33
+ }
34
+ type NarrativeFieldType = "text" | "tel" | "email" | "password" | "number" | "select" | "chips" | "multi-chips" | "date" | "otp";
35
+ interface NarrativeValidation {
36
+ required?: boolean;
37
+ requiredMessage?: string;
38
+ minLength?: number;
39
+ minLengthMessage?: string;
40
+ maxLength?: number;
41
+ maxLengthMessage?: string;
42
+ exactLength?: number;
43
+ exactLengthMessage?: string;
44
+ pattern?: RegExp;
45
+ patternMessage?: string;
46
+ isEmail?: boolean;
47
+ isEmailMessage?: string;
48
+ min?: number;
49
+ minMessage?: string;
50
+ max?: number;
51
+ maxMessage?: string;
52
+ custom?: (value: string, allValues: NarrativeFieldValues) => boolean | string | Promise<boolean | string>;
53
+ rules?: NarrativeValidationRule[];
54
+ mode?: "bail" | "all";
55
+ trigger?: "onChange" | "onBlur" | "onSubmit" | "onConfirm" | "debounced";
56
+ debounceMs?: number;
57
+ errorDisplay?: NarrativeErrorDisplay;
58
+ use?: string | string[];
59
+ serverValidate?: NarrativeServerValidation;
60
+ loadingText?: string;
61
+ successIndicator?: boolean;
62
+ }
63
+ interface NarrativeValidationRule {
64
+ name: string;
65
+ validate: (value: string, allValues: NarrativeFieldValues) => boolean | string | Promise<boolean | string>;
66
+ message?: string;
67
+ async?: boolean;
68
+ debounce?: number;
69
+ }
70
+ interface NarrativeServerValidation {
71
+ url: string;
72
+ method?: "GET" | "POST";
73
+ headers?: Record<string, string>;
74
+ debounceMs?: number;
75
+ timeout?: number;
76
+ timeoutMessage?: string;
77
+ responsePath: string;
78
+ errorPath: string;
79
+ }
80
+ interface NarrativeTheme {
81
+ background?: string;
82
+ textColor?: string;
83
+ inputBorderColor?: string;
84
+ placeholderColor?: string;
85
+ errorColor?: string;
86
+ filledColor?: string;
87
+ cursorColor?: string;
88
+ successColor?: string;
89
+ loadingColor?: string;
90
+ fontFamily?: string;
91
+ uiFontFamily?: string;
92
+ fontSize?: string;
93
+ mobileFontSize?: string;
94
+ inputFontStyle?: string;
95
+ lineGap?: string;
96
+ pagePadding?: string;
97
+ buttonRadius?: string;
98
+ buttonBackground?: string;
99
+ buttonColor?: string;
100
+ enterBtnSize?: string;
101
+ chipBorderRadius?: string;
102
+ chipBorderColor?: string;
103
+ chipActiveBackground?: string;
104
+ chipActiveColor?: string;
105
+ chipFontStyle?: string;
106
+ mode?: "light" | "dark" | "auto";
107
+ dark?: Partial<NarrativeTheme>;
108
+ }
109
+ interface NarrativeTypewriter {
110
+ enabled?: boolean;
111
+ speed?: number;
112
+ cursor?: boolean;
113
+ cursorChar?: string;
114
+ pauseAfter?: number;
115
+ }
116
+ interface NarrativeWelcome {
117
+ show?: boolean;
118
+ heading?: string;
119
+ subtext?: string;
120
+ ctaLabel?: string;
121
+ }
122
+ interface NarrativeDone {
123
+ show?: boolean;
124
+ message?: string | ((values: NarrativeFieldValues) => string);
125
+ ctaLabel?: string;
126
+ onSubmit?: (values: NarrativeFieldValues, meta: NarrativeMeta) => void | Promise<void>;
127
+ }
128
+ interface NarrativeFormConfig {
129
+ form: {
130
+ id: string;
131
+ name: string;
132
+ version: number;
133
+ };
134
+ welcome?: NarrativeWelcome;
135
+ fields: NarrativeField[];
136
+ theme?: NarrativeTheme;
137
+ done?: NarrativeDone;
138
+ }
139
+ interface NarrativeMeta {
140
+ formId?: string;
141
+ formVersion?: number;
142
+ totalTimeMs: number;
143
+ fieldTimings: Record<string, number>;
144
+ }
145
+ interface NarrativeFieldValues {
146
+ [key: string]: string | string[];
147
+ }
148
+ interface NarrativeI18n {
149
+ editLabel?: string;
150
+ requiredMessage?: string;
151
+ otpResend?: string;
152
+ otpTimer?: string;
153
+ submitLabel?: string;
154
+ loadingLabel?: string;
155
+ successLabel?: string;
156
+ retryLabel?: string;
157
+ fetchErrorMessage?: string;
158
+ }
159
+ interface NarrativeRefHandle {
160
+ next: () => void;
161
+ getValues: () => NarrativeFieldValues;
162
+ reset: () => void;
163
+ focusField: (key: string) => void;
164
+ }
165
+ /** Cross-field validation rule that validates across multiple fields. */
166
+ interface NarrativeCrossFieldValidator {
167
+ /** Fields this validator watches. */
168
+ fields: string[];
169
+ /** Field key where the error should be displayed. */
170
+ errorField: string;
171
+ /** Validation function receiving all values. */
172
+ validate: (values: NarrativeFieldValues) => boolean | string | Promise<boolean | string>;
173
+ /** Fallback error message. */
174
+ message?: string;
175
+ }
176
+ interface NarrativeCallbacks {
177
+ onChange?: (key: string, value: string) => void;
178
+ onFieldFocus?: (key: string) => void;
179
+ onFieldBlur?: (key: string, value: string) => void;
180
+ onFieldComplete?: (key: string, value: string, timeSpentMs: number) => void;
181
+ onEdit?: (key: string) => void;
182
+ onError?: (key: string, message: string) => void;
183
+ onDropOff?: (lastFieldKey: string) => void;
184
+ onComplete?: (values: NarrativeFieldValues, meta: NarrativeMeta) => void;
185
+ }
186
+
187
+ /**
188
+ * Framework-agnostic form state manager for narrative-form.
189
+ * Manages field lifecycle, values, and timing metadata.
190
+ *
191
+ * @remarks
192
+ * This module has zero framework dependencies — it is pure TypeScript logic
193
+ * consumed by React/Vue/Angular wrappers.
194
+ */
195
+
196
+ /** Possible statuses for a single field in the form flow. */
197
+ type FieldStatus = "idle" | "typing" | "active" | "confirmed" | "editing";
198
+ /** Snapshot of the entire form's state at a point in time. */
199
+ interface FormStateSnapshot {
200
+ /** Ordered list of field configurations. */
201
+ readonly fields: readonly NarrativeField[];
202
+ /** Index of the currently active field (-1 if none are active yet). */
203
+ readonly activeIndex: number;
204
+ /** Map of field key → current value. */
205
+ readonly values: Readonly<NarrativeFieldValues>;
206
+ /** Map of field key → current lifecycle status. */
207
+ readonly statuses: Readonly<Record<string, FieldStatus>>;
208
+ /** Whether every field has been confirmed. */
209
+ readonly isComplete: boolean;
210
+ }
211
+ /**
212
+ * Pure form state engine for narrative-form.
213
+ *
214
+ * Tracks which field is active, all confirmed values, per-field statuses,
215
+ * and timing metadata for analytics. Framework wrappers subscribe to
216
+ * state changes via the `onChange` callback.
217
+ */
218
+ declare class FormStateEngine {
219
+ private fields;
220
+ private activeIndex;
221
+ private values;
222
+ private statuses;
223
+ private fieldTimings;
224
+ private fieldStartTimes;
225
+ private formStartTime;
226
+ private onChange;
227
+ /**
228
+ * Create a new FormStateEngine.
229
+ *
230
+ * @param fields - Ordered array of field configurations
231
+ * @param onChange - Optional callback invoked on every state mutation
232
+ */
233
+ constructor(fields: readonly NarrativeField[], onChange?: () => void);
234
+ /** Returns a readonly snapshot of the current form state. */
235
+ getSnapshot(): FormStateSnapshot;
236
+ /** Returns a copy of all confirmed field values. */
237
+ getValues(): NarrativeFieldValues;
238
+ /**
239
+ * Returns analytics metadata about the form session.
240
+ *
241
+ * @param formId - Optional form identifier
242
+ * @param formVersion - Optional form version number
243
+ */
244
+ getMeta(formId?: string, formVersion?: number): NarrativeMeta;
245
+ /**
246
+ * Start typing animation for a specific field.
247
+ * Transitions the field from `idle` to `typing`.
248
+ *
249
+ * @param key - The field key to begin typing
250
+ */
251
+ startTyping(key: string): void;
252
+ /**
253
+ * Mark a field as active (typewriter finished, input is now visible).
254
+ * Starts the timing clock for this field.
255
+ *
256
+ * @param key - The field key to activate
257
+ */
258
+ activateField(key: string): void;
259
+ /**
260
+ * Confirm a field with a value.
261
+ * Records the time spent on the field and advances to the next one.
262
+ *
263
+ * @param key - The field key to confirm
264
+ * @param value - The confirmed value
265
+ */
266
+ confirmField(key: string, value: string | string[]): void;
267
+ /**
268
+ * Reopen a confirmed field for editing.
269
+ * The field transitions to `editing` status with its current value preserved.
270
+ *
271
+ * @param key - The field key to edit
272
+ */
273
+ editField(key: string): void;
274
+ /**
275
+ * Re-confirm a field after editing.
276
+ *
277
+ * @param key - The field key to re-confirm
278
+ * @param value - The new confirmed value
279
+ */
280
+ reconfirmField(key: string, value: string | string[]): void;
281
+ /**
282
+ * Move to the next field without confirming the current one.
283
+ * Useful for programmatic navigation via ref API.
284
+ */
285
+ next(): void;
286
+ /**
287
+ * Focus a specific field by key.
288
+ * Only works for confirmed fields (triggers edit) or the current active field.
289
+ *
290
+ * @param key - The field key to focus
291
+ */
292
+ focusField(key: string): void;
293
+ /**
294
+ * Reset all form state to initial values.
295
+ * Clears all values, statuses, and timings.
296
+ */
297
+ reset(): void;
298
+ /**
299
+ * Update the onChange listener.
300
+ * Used by framework wrappers to trigger re-renders.
301
+ *
302
+ * @param fn - Callback to invoke on state changes
303
+ */
304
+ setOnChange(fn: () => void): void;
305
+ private findFieldIndex;
306
+ private findNextUnconfirmedIndex;
307
+ private notify;
308
+ }
309
+
310
+ /**
311
+ * Validation engine for narrative-form.
312
+ *
313
+ * @remarks
314
+ * Runs validation rules in a strict priority order defined in SPEC.md.
315
+ * Supports both `bail` (stop at first failure) and `all` (collect all errors) modes.
316
+ *
317
+ * **Priority order:**
318
+ * 1. required
319
+ * 2. minLength / maxLength
320
+ * 3. exactLength
321
+ * 4. min / max
322
+ * 5. pattern / isEmail
323
+ * 6. use (registered plugin validators) — in array order
324
+ * 7. Sync custom rules — in array order
325
+ * 8. Async custom rules — in array order
326
+ * 9. serverValidate URL call
327
+ * 10. Global cross-field validators (handled externally)
328
+ *
329
+ * Async rules only run if all sync rules pass first.
330
+ */
331
+
332
+ /** Result of running the validation engine on a single field. */
333
+ interface ValidationResult {
334
+ /** Whether the field value is valid. */
335
+ valid: boolean;
336
+ /** Array of error messages (empty if valid). */
337
+ errors: string[];
338
+ }
339
+ /** Result of async validation — extends sync with an abort handle. */
340
+ interface AsyncValidationHandle {
341
+ /** Promise that resolves with the validation result. */
342
+ promise: Promise<ValidationResult>;
343
+ /** Abort the in-flight async validation. */
344
+ abort: () => void;
345
+ }
346
+ /**
347
+ * Validate a single field value synchronously.
348
+ *
349
+ * Runs rules in strict priority order. In `bail` mode (default),
350
+ * stops at the first failure. In `all` mode, collects every error.
351
+ *
352
+ * @param value - The field value to validate
353
+ * @param validation - The validation configuration for this field
354
+ * @param allValues - All confirmed field values (for cross-field checks)
355
+ * @returns Validation result with valid flag and error messages
356
+ */
357
+ declare function validateField(value: string, validation: NarrativeValidation | undefined, allValues?: NarrativeFieldValues): ValidationResult;
358
+ /**
359
+ * Check if a field has any async validation rules that need to run.
360
+ *
361
+ * @param validation - The validation configuration to check
362
+ * @returns Whether async validation is needed
363
+ */
364
+ declare function hasAsyncValidation(validation: NarrativeValidation | undefined): boolean;
365
+ /**
366
+ * Run async validation rules on a field value.
367
+ *
368
+ * Only runs if all sync rules pass first. Returns an abort handle
369
+ * so callers can cancel in-flight requests on new input.
370
+ *
371
+ * @param value - The field value to validate
372
+ * @param validation - The validation configuration
373
+ * @param allValues - All confirmed field values
374
+ * @returns An abort handle with a promise and abort method
375
+ */
376
+ declare function validateFieldAsync(value: string, validation: NarrativeValidation | undefined, allValues?: NarrativeFieldValues): AsyncValidationHandle;
377
+
378
+ /**
379
+ * Validator plugin registry for narrative-form.
380
+ *
381
+ * @remarks
382
+ * Allows developers to register reusable named validators globally at app level.
383
+ * Fields can then reference them by name via the `use` property.
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * registerValidator("indianPhone", (value) => {
388
+ * return /^[6-9]\d{9}$/.test(value) || "Enter a valid Indian phone number";
389
+ * });
390
+ *
391
+ * // In field config:
392
+ * { key: "phone", type: "tel", prefix: "My phone is", validation: { use: "indianPhone" } }
393
+ * ```
394
+ */
395
+
396
+ /** A validator function that returns true on pass or an error string on fail. */
397
+ type ValidatorFn = (value: string, allValues: NarrativeFieldValues) => boolean | string | Promise<boolean | string>;
398
+ /**
399
+ * Register a reusable named validator.
400
+ *
401
+ * @param name - Unique validator name (e.g., "indianPhone", "pan")
402
+ * @param fn - Validator function returning true on pass or error string on fail
403
+ */
404
+ declare function registerValidator(name: string, fn: ValidatorFn): void;
405
+ /**
406
+ * Retrieve a registered validator by name.
407
+ *
408
+ * @param name - The validator name to look up
409
+ * @returns The validator function, or undefined if not found
410
+ */
411
+ declare function getValidator(name: string): ValidatorFn | undefined;
412
+ /**
413
+ * Check if a validator is registered.
414
+ *
415
+ * @param name - The validator name to check
416
+ */
417
+ declare function hasValidator(name: string): boolean;
418
+ /**
419
+ * Remove a registered validator.
420
+ *
421
+ * @param name - The validator name to remove
422
+ */
423
+ declare function unregisterValidator(name: string): void;
424
+ /**
425
+ * Clear all registered validators.
426
+ * Useful for testing or hot-reload scenarios.
427
+ */
428
+ declare function clearValidators(): void;
429
+ /**
430
+ * Get all registered validator names.
431
+ * Useful for debugging.
432
+ */
433
+ declare function getRegisteredValidatorNames(): string[];
434
+
435
+ /**
436
+ * Built-in validators shipped with narrative-form.
437
+ *
438
+ * @remarks
439
+ * These cover common Indian regulatory formats and universal patterns.
440
+ * Call `registerBuiltinValidators()` once at app startup to register them all.
441
+ *
442
+ * **India-specific:** indianPhone, indianPincode, aadhaar, pan, gst, ifsc
443
+ * **Universal:** email, url, strongPassword, alphanumeric, noSpaces,
444
+ * futureDate, pastDate, minAge
445
+ *
446
+ * @example
447
+ * ```ts
448
+ * import { registerBuiltinValidators } from "@viveksinghind/narrative-form-core";
449
+ * registerBuiltinValidators(); // Call once at app startup
450
+ * ```
451
+ */
452
+ /**
453
+ * Register all built-in validators.
454
+ * Safe to call multiple times — subsequent calls are no-ops.
455
+ */
456
+ declare function registerBuiltinValidators(): void;
457
+
458
+ /**
459
+ * Default internationalisation strings for narrative-form.
460
+ *
461
+ * @remarks
462
+ * All user-facing copy lives here. Consumers override via the `i18n` prop,
463
+ * which is deep-merged with these defaults using {@link mergeStrings}.
464
+ */
465
+
466
+ /** Default English strings shipped with the package. */
467
+ declare const defaultStrings: Readonly<Required<NarrativeI18n>>;
468
+ /**
469
+ * Deep-merge user-provided i18n overrides with the default strings.
470
+ *
471
+ * @param custom - Partial i18n overrides from the consumer
472
+ * @returns A complete i18n object with all keys guaranteed to be present
473
+ */
474
+ declare function mergeStrings(custom?: Partial<NarrativeI18n>): Required<NarrativeI18n>;
475
+
476
+ interface FetchConfigOptions {
477
+ headers?: Record<string, string>;
478
+ }
479
+ declare class ConfigFetchError extends Error {
480
+ constructor(message: string);
481
+ }
482
+ declare function fetchFormConfig(url: string, options?: FetchConfigOptions): Promise<NarrativeFormConfig>;
483
+
484
+ export { type AsyncValidationHandle, ConfigFetchError, type FetchConfigOptions, type FieldStatus, FormStateEngine, type FormStateSnapshot, type NarrativeCallbacks, type NarrativeCrossFieldValidator, type NarrativeDone, type NarrativeErrorDisplay, type NarrativeField, type NarrativeFieldType, type NarrativeFieldValues, type NarrativeFormConfig, type NarrativeI18n, type NarrativeMeta, type NarrativeRefHandle, type NarrativeServerValidation, type NarrativeTheme, type NarrativeTypewriter, type NarrativeValidation, type NarrativeValidationRule, type NarrativeWelcome, type ValidationResult, type ValidatorFn, clearValidators, defaultStrings, fetchFormConfig, getRegisteredValidatorNames, getValidator, hasAsyncValidation, hasValidator, mergeStrings, registerBuiltinValidators, registerValidator, unregisterValidator, validateField, validateFieldAsync };