rn-inputforge 0.5.0 → 0.5.1
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/README.md +236 -0
- package/lib/commonjs/a11y/audit.js +72 -0
- package/lib/commonjs/a11y/audit.js.map +1 -0
- package/lib/commonjs/a11y/index.js +13 -0
- package/lib/commonjs/a11y/index.js.map +1 -0
- package/lib/commonjs/autofill/index.js +25 -0
- package/lib/commonjs/autofill/index.js.map +1 -0
- package/lib/commonjs/autofill/profiles.js +199 -0
- package/lib/commonjs/autofill/profiles.js.map +1 -0
- package/lib/commonjs/components/FieldFlowA11yAudit/FieldFlowA11yAudit.js +99 -0
- package/lib/commonjs/components/FieldFlowA11yAudit/FieldFlowA11yAudit.js.map +1 -0
- package/lib/commonjs/components/FieldFlowA11yAudit/index.js +13 -0
- package/lib/commonjs/components/FieldFlowA11yAudit/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowAuthActions/FieldFlowAuthActions.js +130 -0
- package/lib/commonjs/components/FieldFlowAuthActions/FieldFlowAuthActions.js.map +1 -0
- package/lib/commonjs/components/FieldFlowAuthActions/index.js +13 -0
- package/lib/commonjs/components/FieldFlowAuthActions/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowCompleteness/FieldFlowCompleteness.js +100 -0
- package/lib/commonjs/components/FieldFlowCompleteness/FieldFlowCompleteness.js.map +1 -0
- package/lib/commonjs/components/FieldFlowCompleteness/index.js +13 -0
- package/lib/commonjs/components/FieldFlowCompleteness/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowFormHeader/FieldFlowFormHeader.js +102 -0
- package/lib/commonjs/components/FieldFlowFormHeader/FieldFlowFormHeader.js.map +1 -0
- package/lib/commonjs/components/FieldFlowFormHeader/index.js +13 -0
- package/lib/commonjs/components/FieldFlowFormHeader/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowInfoTip/FieldFlowInfoTip.js +121 -0
- package/lib/commonjs/components/FieldFlowInfoTip/FieldFlowInfoTip.js.map +1 -0
- package/lib/commonjs/components/FieldFlowInfoTip/index.js +13 -0
- package/lib/commonjs/components/FieldFlowInfoTip/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowInlineAlert/FieldFlowInlineAlert.js +142 -0
- package/lib/commonjs/components/FieldFlowInlineAlert/FieldFlowInlineAlert.js.map +1 -0
- package/lib/commonjs/components/FieldFlowInlineAlert/index.js +13 -0
- package/lib/commonjs/components/FieldFlowInlineAlert/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js +44 -18
- package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js.map +1 -1
- package/lib/commonjs/components/FieldFlowInput/styles.js +49 -13
- package/lib/commonjs/components/FieldFlowInput/styles.js.map +1 -1
- package/lib/commonjs/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.js +137 -0
- package/lib/commonjs/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.js.map +1 -0
- package/lib/commonjs/components/FieldFlowKeyboardToolbar/index.js +19 -0
- package/lib/commonjs/components/FieldFlowKeyboardToolbar/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowSkeleton/FieldFlowSkeleton.js +101 -0
- package/lib/commonjs/components/FieldFlowSkeleton/FieldFlowSkeleton.js.map +1 -0
- package/lib/commonjs/components/FieldFlowSkeleton/index.js +13 -0
- package/lib/commonjs/components/FieldFlowSkeleton/index.js.map +1 -0
- package/lib/commonjs/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js +6 -4
- package/lib/commonjs/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js.map +1 -1
- package/lib/commonjs/hooks/index.js +21 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useFieldFlowCompleteness.js +46 -0
- package/lib/commonjs/hooks/useFieldFlowCompleteness.js.map +1 -0
- package/lib/commonjs/hooks/useFieldFlowSubmitQueue.js +145 -0
- package/lib/commonjs/hooks/useFieldFlowSubmitQueue.js.map +1 -0
- package/lib/commonjs/hooks/useKeyboardManager.js +15 -0
- package/lib/commonjs/hooks/useKeyboardManager.js.map +1 -1
- package/lib/commonjs/hooks/useSmartPaste.js +27 -0
- package/lib/commonjs/hooks/useSmartPaste.js.map +1 -0
- package/lib/commonjs/index.js +144 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/paste/index.js +19 -0
- package/lib/commonjs/paste/index.js.map +1 -0
- package/lib/commonjs/paste/smartPaste.js +113 -0
- package/lib/commonjs/paste/smartPaste.js.map +1 -0
- package/lib/commonjs/schema/FieldFlowSchemaForm.js +219 -0
- package/lib/commonjs/schema/FieldFlowSchemaForm.js.map +1 -0
- package/lib/commonjs/schema/index.js +13 -0
- package/lib/commonjs/schema/index.js.map +1 -0
- package/lib/commonjs/schema/types.js +6 -0
- package/lib/commonjs/schema/types.js.map +1 -0
- package/lib/commonjs/theme/ThemeProvider.js +65 -4
- package/lib/commonjs/theme/ThemeProvider.js.map +1 -1
- package/lib/commonjs/theme/appearance.js +93 -0
- package/lib/commonjs/theme/appearance.js.map +1 -0
- package/lib/commonjs/theme/defaultTheme.js +17 -2
- package/lib/commonjs/theme/defaultTheme.js.map +1 -1
- package/lib/commonjs/theme/index.js +19 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/commonjs/types/globals.d.js +2 -0
- package/lib/commonjs/types/globals.d.js.map +1 -0
- package/lib/module/a11y/audit.js +68 -0
- package/lib/module/a11y/audit.js.map +1 -0
- package/lib/module/a11y/index.js +4 -0
- package/lib/module/a11y/index.js.map +1 -0
- package/lib/module/autofill/index.js +4 -0
- package/lib/module/autofill/index.js.map +1 -0
- package/lib/module/autofill/profiles.js +193 -0
- package/lib/module/autofill/profiles.js.map +1 -0
- package/lib/module/components/FieldFlowA11yAudit/FieldFlowA11yAudit.js +94 -0
- package/lib/module/components/FieldFlowA11yAudit/FieldFlowA11yAudit.js.map +1 -0
- package/lib/module/components/FieldFlowA11yAudit/index.js +4 -0
- package/lib/module/components/FieldFlowA11yAudit/index.js.map +1 -0
- package/lib/module/components/FieldFlowAuthActions/FieldFlowAuthActions.js +125 -0
- package/lib/module/components/FieldFlowAuthActions/FieldFlowAuthActions.js.map +1 -0
- package/lib/module/components/FieldFlowAuthActions/index.js +4 -0
- package/lib/module/components/FieldFlowAuthActions/index.js.map +1 -0
- package/lib/module/components/FieldFlowCompleteness/FieldFlowCompleteness.js +95 -0
- package/lib/module/components/FieldFlowCompleteness/FieldFlowCompleteness.js.map +1 -0
- package/lib/module/components/FieldFlowCompleteness/index.js +4 -0
- package/lib/module/components/FieldFlowCompleteness/index.js.map +1 -0
- package/lib/module/components/FieldFlowFormHeader/FieldFlowFormHeader.js +97 -0
- package/lib/module/components/FieldFlowFormHeader/FieldFlowFormHeader.js.map +1 -0
- package/lib/module/components/FieldFlowFormHeader/index.js +4 -0
- package/lib/module/components/FieldFlowFormHeader/index.js.map +1 -0
- package/lib/module/components/FieldFlowInfoTip/FieldFlowInfoTip.js +116 -0
- package/lib/module/components/FieldFlowInfoTip/FieldFlowInfoTip.js.map +1 -0
- package/lib/module/components/FieldFlowInfoTip/index.js +4 -0
- package/lib/module/components/FieldFlowInfoTip/index.js.map +1 -0
- package/lib/module/components/FieldFlowInlineAlert/FieldFlowInlineAlert.js +137 -0
- package/lib/module/components/FieldFlowInlineAlert/FieldFlowInlineAlert.js.map +1 -0
- package/lib/module/components/FieldFlowInlineAlert/index.js +4 -0
- package/lib/module/components/FieldFlowInlineAlert/index.js.map +1 -0
- package/lib/module/components/FieldFlowInput/FieldFlowInput.js +44 -18
- package/lib/module/components/FieldFlowInput/FieldFlowInput.js.map +1 -1
- package/lib/module/components/FieldFlowInput/styles.js +49 -13
- package/lib/module/components/FieldFlowInput/styles.js.map +1 -1
- package/lib/module/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.js +131 -0
- package/lib/module/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.js.map +1 -0
- package/lib/module/components/FieldFlowKeyboardToolbar/index.js +4 -0
- package/lib/module/components/FieldFlowKeyboardToolbar/index.js.map +1 -0
- package/lib/module/components/FieldFlowSkeleton/FieldFlowSkeleton.js +96 -0
- package/lib/module/components/FieldFlowSkeleton/FieldFlowSkeleton.js.map +1 -0
- package/lib/module/components/FieldFlowSkeleton/index.js +4 -0
- package/lib/module/components/FieldFlowSkeleton/index.js.map +1 -0
- package/lib/module/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js +5 -3
- package/lib/module/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js.map +1 -1
- package/lib/module/hooks/index.js +3 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useFieldFlowCompleteness.js +42 -0
- package/lib/module/hooks/useFieldFlowCompleteness.js.map +1 -0
- package/lib/module/hooks/useFieldFlowSubmitQueue.js +141 -0
- package/lib/module/hooks/useFieldFlowSubmitQueue.js.map +1 -0
- package/lib/module/hooks/useKeyboardManager.js +15 -0
- package/lib/module/hooks/useKeyboardManager.js.map +1 -1
- package/lib/module/hooks/useSmartPaste.js +23 -0
- package/lib/module/hooks/useSmartPaste.js.map +1 -0
- package/lib/module/index.js +17 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/paste/index.js +4 -0
- package/lib/module/paste/index.js.map +1 -0
- package/lib/module/paste/smartPaste.js +108 -0
- package/lib/module/paste/smartPaste.js.map +1 -0
- package/lib/module/schema/FieldFlowSchemaForm.js +214 -0
- package/lib/module/schema/FieldFlowSchemaForm.js.map +1 -0
- package/lib/module/schema/index.js +4 -0
- package/lib/module/schema/index.js.map +1 -0
- package/lib/module/schema/types.js +4 -0
- package/lib/module/schema/types.js.map +1 -0
- package/lib/module/theme/ThemeProvider.js +66 -5
- package/lib/module/theme/ThemeProvider.js.map +1 -1
- package/lib/module/theme/appearance.js +87 -0
- package/lib/module/theme/appearance.js.map +1 -0
- package/lib/module/theme/defaultTheme.js +17 -2
- package/lib/module/theme/defaultTheme.js.map +1 -1
- package/lib/module/theme/index.js +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/types/globals.d.js +2 -0
- package/lib/module/types/globals.d.js.map +1 -0
- package/lib/typescript/a11y/audit.d.ts +32 -0
- package/lib/typescript/a11y/audit.d.ts.map +1 -0
- package/lib/typescript/a11y/index.d.ts +3 -0
- package/lib/typescript/a11y/index.d.ts.map +1 -0
- package/lib/typescript/autofill/index.d.ts +3 -0
- package/lib/typescript/autofill/index.d.ts.map +1 -0
- package/lib/typescript/autofill/profiles.d.ts +48 -0
- package/lib/typescript/autofill/profiles.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowA11yAudit/FieldFlowA11yAudit.d.ts +21 -0
- package/lib/typescript/components/FieldFlowA11yAudit/FieldFlowA11yAudit.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowA11yAudit/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowA11yAudit/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowAuthActions/FieldFlowAuthActions.d.ts +29 -0
- package/lib/typescript/components/FieldFlowAuthActions/FieldFlowAuthActions.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowAuthActions/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowAuthActions/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowCompleteness/FieldFlowCompleteness.d.ts +25 -0
- package/lib/typescript/components/FieldFlowCompleteness/FieldFlowCompleteness.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowCompleteness/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowCompleteness/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowFormHeader/FieldFlowFormHeader.d.ts +23 -0
- package/lib/typescript/components/FieldFlowFormHeader/FieldFlowFormHeader.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowFormHeader/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowFormHeader/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowInfoTip/FieldFlowInfoTip.d.ts +19 -0
- package/lib/typescript/components/FieldFlowInfoTip/FieldFlowInfoTip.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowInfoTip/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowInfoTip/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowInlineAlert/FieldFlowInlineAlert.d.ts +22 -0
- package/lib/typescript/components/FieldFlowInlineAlert/FieldFlowInlineAlert.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowInlineAlert/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowInlineAlert/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowInput/FieldFlowInput.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowInput/styles.d.ts +36 -1
- package/lib/typescript/components/FieldFlowInput/styles.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowInput/types.d.ts +18 -0
- package/lib/typescript/components/FieldFlowInput/types.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.d.ts +30 -0
- package/lib/typescript/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowKeyboardToolbar/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowKeyboardToolbar/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowSkeleton/FieldFlowSkeleton.d.ts +21 -0
- package/lib/typescript/components/FieldFlowSkeleton/FieldFlowSkeleton.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowSkeleton/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowSkeleton/index.d.ts.map +1 -0
- package/lib/typescript/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.d.ts +3 -2
- package/lib/typescript/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.d.ts.map +1 -1
- package/lib/typescript/hooks/index.d.ts +6 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useFieldFlowCompleteness.d.ts +28 -0
- package/lib/typescript/hooks/useFieldFlowCompleteness.d.ts.map +1 -0
- package/lib/typescript/hooks/useFieldFlowSubmitQueue.d.ts +45 -0
- package/lib/typescript/hooks/useFieldFlowSubmitQueue.d.ts.map +1 -0
- package/lib/typescript/hooks/useKeyboardManager.d.ts +6 -0
- package/lib/typescript/hooks/useKeyboardManager.d.ts.map +1 -1
- package/lib/typescript/hooks/useSmartPaste.d.ts +14 -0
- package/lib/typescript/hooks/useSmartPaste.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +28 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/paste/index.d.ts +3 -0
- package/lib/typescript/paste/index.d.ts.map +1 -0
- package/lib/typescript/paste/smartPaste.d.ts +32 -0
- package/lib/typescript/paste/smartPaste.d.ts.map +1 -0
- package/lib/typescript/schema/FieldFlowSchemaForm.d.ts +28 -0
- package/lib/typescript/schema/FieldFlowSchemaForm.d.ts.map +1 -0
- package/lib/typescript/schema/index.d.ts +4 -0
- package/lib/typescript/schema/index.d.ts.map +1 -0
- package/lib/typescript/schema/types.d.ts +43 -0
- package/lib/typescript/schema/types.d.ts.map +1 -0
- package/lib/typescript/theme/ThemeProvider.d.ts +18 -4
- package/lib/typescript/theme/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/theme/appearance.d.ts +19 -0
- package/lib/typescript/theme/appearance.d.ts.map +1 -0
- package/lib/typescript/theme/defaultTheme.d.ts.map +1 -1
- package/lib/typescript/theme/index.d.ts +1 -0
- package/lib/typescript/theme/index.d.ts.map +1 -1
- package/lib/typescript/types/index.d.ts +26 -0
- package/lib/typescript/types/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/a11y/audit.ts +99 -0
- package/src/a11y/index.ts +7 -0
- package/src/autofill/index.ts +10 -0
- package/src/autofill/profiles.ts +224 -0
- package/src/components/FieldFlowA11yAudit/FieldFlowA11yAudit.tsx +112 -0
- package/src/components/FieldFlowA11yAudit/index.ts +2 -0
- package/src/components/FieldFlowAuthActions/FieldFlowAuthActions.tsx +146 -0
- package/src/components/FieldFlowAuthActions/index.ts +6 -0
- package/src/components/FieldFlowCompleteness/FieldFlowCompleteness.tsx +109 -0
- package/src/components/FieldFlowCompleteness/index.ts +2 -0
- package/src/components/FieldFlowFormHeader/FieldFlowFormHeader.tsx +106 -0
- package/src/components/FieldFlowFormHeader/index.ts +2 -0
- package/src/components/FieldFlowInfoTip/FieldFlowInfoTip.tsx +120 -0
- package/src/components/FieldFlowInfoTip/index.ts +2 -0
- package/src/components/FieldFlowInlineAlert/FieldFlowInlineAlert.tsx +134 -0
- package/src/components/FieldFlowInlineAlert/index.ts +2 -0
- package/src/components/FieldFlowInput/FieldFlowInput.tsx +71 -13
- package/src/components/FieldFlowInput/styles.ts +58 -16
- package/src/components/FieldFlowInput/types.ts +21 -0
- package/src/components/FieldFlowKeyboardToolbar/FieldFlowKeyboardToolbar.tsx +150 -0
- package/src/components/FieldFlowKeyboardToolbar/index.ts +5 -0
- package/src/components/FieldFlowSkeleton/FieldFlowSkeleton.tsx +105 -0
- package/src/components/FieldFlowSkeleton/index.ts +2 -0
- package/src/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.tsx +8 -4
- package/src/hooks/index.ts +17 -0
- package/src/hooks/useFieldFlowCompleteness.ts +69 -0
- package/src/hooks/useFieldFlowSubmitQueue.ts +206 -0
- package/src/hooks/useKeyboardManager.ts +26 -0
- package/src/hooks/useSmartPaste.ts +33 -0
- package/src/index.ts +90 -0
- package/src/paste/index.ts +7 -0
- package/src/paste/smartPaste.ts +132 -0
- package/src/schema/FieldFlowSchemaForm.tsx +274 -0
- package/src/schema/index.ts +8 -0
- package/src/schema/types.ts +63 -0
- package/src/theme/ThemeProvider.tsx +96 -7
- package/src/theme/appearance.ts +105 -0
- package/src/theme/defaultTheme.ts +11 -0
- package/src/theme/index.ts +1 -0
- package/src/types/globals.d.ts +2 -0
- package/src/types/index.ts +28 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface UseFieldFlowCompletenessOptions<T extends Record<string, string>> {
|
|
4
|
+
values: T;
|
|
5
|
+
/** Required field keys (or dynamic list). */
|
|
6
|
+
requiredFields: (keyof T)[];
|
|
7
|
+
/** Optional: treat as filled when custom predicate returns true. */
|
|
8
|
+
isFilled?: (field: keyof T, value: string, values: T) => boolean;
|
|
9
|
+
/** Seconds users typically need per empty required field (for estimate). Default: 8. */
|
|
10
|
+
secondsPerField?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UseFieldFlowCompletenessResult<T extends Record<string, string>> {
|
|
14
|
+
total: number;
|
|
15
|
+
filled: number;
|
|
16
|
+
remaining: number;
|
|
17
|
+
/** 0–1 */
|
|
18
|
+
progress: number;
|
|
19
|
+
/** 0–100 */
|
|
20
|
+
percent: number;
|
|
21
|
+
remainingFields: (keyof T)[];
|
|
22
|
+
filledFields: (keyof T)[];
|
|
23
|
+
isComplete: boolean;
|
|
24
|
+
/** Rough seconds left based on empty required fields. */
|
|
25
|
+
estimatedSecondsLeft: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function defaultFilled(value: string): boolean {
|
|
29
|
+
return value.trim().length > 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Live form completeness for required fields — powers `FieldFlowCompleteness`.
|
|
34
|
+
*/
|
|
35
|
+
export function useFieldFlowCompleteness<T extends Record<string, string>>({
|
|
36
|
+
values,
|
|
37
|
+
requiredFields,
|
|
38
|
+
isFilled,
|
|
39
|
+
secondsPerField = 8,
|
|
40
|
+
}: UseFieldFlowCompletenessOptions<T>): UseFieldFlowCompletenessResult<T> {
|
|
41
|
+
return useMemo(() => {
|
|
42
|
+
const filledFields: (keyof T)[] = [];
|
|
43
|
+
const remainingFields: (keyof T)[] = [];
|
|
44
|
+
|
|
45
|
+
for (const field of requiredFields) {
|
|
46
|
+
const value = values[field] ?? '';
|
|
47
|
+
const ok = isFilled ? isFilled(field, value, values) : defaultFilled(value);
|
|
48
|
+
if (ok) filledFields.push(field);
|
|
49
|
+
else remainingFields.push(field);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const total = requiredFields.length;
|
|
53
|
+
const filled = filledFields.length;
|
|
54
|
+
const remaining = remainingFields.length;
|
|
55
|
+
const progress = total === 0 ? 1 : filled / total;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
total,
|
|
59
|
+
filled,
|
|
60
|
+
remaining,
|
|
61
|
+
progress,
|
|
62
|
+
percent: Math.round(progress * 100),
|
|
63
|
+
remainingFields,
|
|
64
|
+
filledFields,
|
|
65
|
+
isComplete: remaining === 0,
|
|
66
|
+
estimatedSecondsLeft: remaining * secondsPerField,
|
|
67
|
+
};
|
|
68
|
+
}, [values, requiredFields, isFilled, secondsPerField]);
|
|
69
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FieldFlowSubmitJob<T = unknown> {
|
|
4
|
+
id: string;
|
|
5
|
+
payload: T;
|
|
6
|
+
createdAt: number;
|
|
7
|
+
attempts: number;
|
|
8
|
+
lastError?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FieldFlowSubmitQueueStorage {
|
|
12
|
+
getItem: (key: string) => Promise<string | null> | string | null;
|
|
13
|
+
setItem: (key: string, value: string) => Promise<void> | void;
|
|
14
|
+
removeItem: (key: string) => Promise<void> | void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UseFieldFlowSubmitQueueOptions<T> {
|
|
18
|
+
/** Unique storage key. */
|
|
19
|
+
key: string;
|
|
20
|
+
/** Persist queue (AsyncStorage / MMKV). Optional — in-memory if omitted. */
|
|
21
|
+
storage?: FieldFlowSubmitQueueStorage;
|
|
22
|
+
/** Actually send the payload. Throw / return false to keep in queue. */
|
|
23
|
+
submit: (payload: T) => Promise<void | boolean>;
|
|
24
|
+
/** Max attempts before parking as failed. Default: 5. */
|
|
25
|
+
maxAttempts?: number;
|
|
26
|
+
/** Auto-flush when online. Default: true. */
|
|
27
|
+
flushOnOnline?: boolean;
|
|
28
|
+
/** Called after a successful flush of one job. */
|
|
29
|
+
onSuccess?: (job: FieldFlowSubmitJob<T>) => void;
|
|
30
|
+
/** Called when a job exceeds maxAttempts. */
|
|
31
|
+
onGiveUp?: (job: FieldFlowSubmitJob<T>) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UseFieldFlowSubmitQueueResult<T> {
|
|
35
|
+
queue: FieldFlowSubmitJob<T>[];
|
|
36
|
+
pendingCount: number;
|
|
37
|
+
isFlushing: boolean;
|
|
38
|
+
isOnline: boolean;
|
|
39
|
+
/** Enqueue a payload (and try flush if online). */
|
|
40
|
+
enqueue: (payload: T) => Promise<string>;
|
|
41
|
+
/** Process the queue now. */
|
|
42
|
+
flush: () => Promise<void>;
|
|
43
|
+
clear: () => Promise<void>;
|
|
44
|
+
remove: (id: string) => Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createId(): string {
|
|
48
|
+
return `ffq_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Offline-friendly submit queue — persist failed / offline form posts and retry.
|
|
53
|
+
*/
|
|
54
|
+
export function useFieldFlowSubmitQueue<T>({
|
|
55
|
+
key,
|
|
56
|
+
storage,
|
|
57
|
+
submit,
|
|
58
|
+
maxAttempts = 5,
|
|
59
|
+
flushOnOnline = true,
|
|
60
|
+
onSuccess,
|
|
61
|
+
onGiveUp,
|
|
62
|
+
}: UseFieldFlowSubmitQueueOptions<T>): UseFieldFlowSubmitQueueResult<T> {
|
|
63
|
+
const [queue, setQueue] = useState<FieldFlowSubmitJob<T>[]>([]);
|
|
64
|
+
const [isFlushing, setIsFlushing] = useState(false);
|
|
65
|
+
const [isOnline, setIsOnline] = useState(true);
|
|
66
|
+
const queueRef = useRef(queue);
|
|
67
|
+
queueRef.current = queue;
|
|
68
|
+
const hydrated = useRef(false);
|
|
69
|
+
|
|
70
|
+
const persist = useCallback(
|
|
71
|
+
async (next: FieldFlowSubmitJob<T>[]) => {
|
|
72
|
+
setQueue(next);
|
|
73
|
+
queueRef.current = next;
|
|
74
|
+
if (!storage) return;
|
|
75
|
+
if (next.length === 0) await storage.removeItem(key);
|
|
76
|
+
else await storage.setItem(key, JSON.stringify(next));
|
|
77
|
+
},
|
|
78
|
+
[storage, key],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
let cancelled = false;
|
|
83
|
+
(async () => {
|
|
84
|
+
if (!storage) {
|
|
85
|
+
hydrated.current = true;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const raw = await storage.getItem(key);
|
|
90
|
+
if (cancelled || !raw) {
|
|
91
|
+
hydrated.current = true;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const parsed = JSON.parse(raw) as FieldFlowSubmitJob<T>[];
|
|
95
|
+
if (!cancelled && Array.isArray(parsed)) {
|
|
96
|
+
setQueue(parsed);
|
|
97
|
+
queueRef.current = parsed;
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
/* ignore corrupt */
|
|
101
|
+
} finally {
|
|
102
|
+
hydrated.current = true;
|
|
103
|
+
}
|
|
104
|
+
})();
|
|
105
|
+
return () => {
|
|
106
|
+
cancelled = true;
|
|
107
|
+
};
|
|
108
|
+
}, [storage, key]);
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
// NetInfo-free: use RN global if present, else assume online
|
|
112
|
+
type NetInfoLike = {
|
|
113
|
+
addEventListener?: (cb: (s: { isConnected?: boolean | null }) => void) => { remove: () => void };
|
|
114
|
+
fetch?: () => Promise<{ isConnected?: boolean | null }>;
|
|
115
|
+
};
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
+
const NetInfo = (globalThis as any).NetInfo as NetInfoLike | undefined;
|
|
118
|
+
if (!NetInfo?.addEventListener) return;
|
|
119
|
+
|
|
120
|
+
const sub = NetInfo.addEventListener(state => {
|
|
121
|
+
const online = state.isConnected !== false;
|
|
122
|
+
setIsOnline(online);
|
|
123
|
+
});
|
|
124
|
+
NetInfo.fetch?.().then(s => setIsOnline(s.isConnected !== false)).catch(() => undefined);
|
|
125
|
+
return () => sub?.remove();
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
const flush = useCallback(async () => {
|
|
129
|
+
if (isFlushing) return;
|
|
130
|
+
setIsFlushing(true);
|
|
131
|
+
try {
|
|
132
|
+
let next = [...queueRef.current];
|
|
133
|
+
while (next.length > 0) {
|
|
134
|
+
const job = next[0];
|
|
135
|
+
try {
|
|
136
|
+
const result = await submit(job.payload);
|
|
137
|
+
if (result === false) throw new Error('Submit returned false');
|
|
138
|
+
onSuccess?.(job);
|
|
139
|
+
next = next.slice(1);
|
|
140
|
+
await persist(next);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
const message = err instanceof Error ? err.message : 'Submit failed';
|
|
143
|
+
const updated: FieldFlowSubmitJob<T> = {
|
|
144
|
+
...job,
|
|
145
|
+
attempts: job.attempts + 1,
|
|
146
|
+
lastError: message,
|
|
147
|
+
};
|
|
148
|
+
if (updated.attempts >= maxAttempts) {
|
|
149
|
+
onGiveUp?.(updated);
|
|
150
|
+
next = next.slice(1);
|
|
151
|
+
} else {
|
|
152
|
+
next = [updated, ...next.slice(1)];
|
|
153
|
+
}
|
|
154
|
+
await persist(next);
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} finally {
|
|
159
|
+
setIsFlushing(false);
|
|
160
|
+
}
|
|
161
|
+
}, [isFlushing, submit, persist, maxAttempts, onSuccess, onGiveUp]);
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (!flushOnOnline || !isOnline || !hydrated.current) return;
|
|
165
|
+
if (queueRef.current.length === 0) return;
|
|
166
|
+
void flush();
|
|
167
|
+
}, [isOnline, flushOnOnline, flush]);
|
|
168
|
+
|
|
169
|
+
const enqueue = useCallback(
|
|
170
|
+
async (payload: T) => {
|
|
171
|
+
const id = createId();
|
|
172
|
+
const job: FieldFlowSubmitJob<T> = {
|
|
173
|
+
id,
|
|
174
|
+
payload,
|
|
175
|
+
createdAt: Date.now(),
|
|
176
|
+
attempts: 0,
|
|
177
|
+
};
|
|
178
|
+
await persist([...queueRef.current, job]);
|
|
179
|
+
if (isOnline) void flush();
|
|
180
|
+
return id;
|
|
181
|
+
},
|
|
182
|
+
[persist, isOnline, flush],
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const clear = useCallback(async () => {
|
|
186
|
+
await persist([]);
|
|
187
|
+
}, [persist]);
|
|
188
|
+
|
|
189
|
+
const remove = useCallback(
|
|
190
|
+
async (id: string) => {
|
|
191
|
+
await persist(queueRef.current.filter(j => j.id !== id));
|
|
192
|
+
},
|
|
193
|
+
[persist],
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
queue,
|
|
198
|
+
pendingCount: queue.length,
|
|
199
|
+
isFlushing,
|
|
200
|
+
isOnline,
|
|
201
|
+
enqueue,
|
|
202
|
+
flush,
|
|
203
|
+
clear,
|
|
204
|
+
remove,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -13,7 +13,13 @@ export interface UseKeyboardManagerResult {
|
|
|
13
13
|
unregisterField: (id: string) => void;
|
|
14
14
|
/** Call from a field's onSubmitEditing to move focus to the next registered field, or submit if it's last. */
|
|
15
15
|
focusNext: (id: string) => void;
|
|
16
|
+
/** Move focus to the previous field in `order` (no-op on first). */
|
|
17
|
+
focusPrevious: (id: string) => void;
|
|
16
18
|
focusField: (id: string) => void;
|
|
19
|
+
/** Index helpers for keyboard toolbar enablement. */
|
|
20
|
+
getFieldIndex: (id: string) => number;
|
|
21
|
+
canGoPrevious: (id: string) => boolean;
|
|
22
|
+
canGoNext: (id: string) => boolean;
|
|
17
23
|
/** Attach to the surrounding ScrollView so FieldFlow can scroll the active field into view. */
|
|
18
24
|
scrollViewRef: React.RefObject<ScrollView | null>;
|
|
19
25
|
/** returnKeyType to use for a field given its position ("next" for all but the last, "done" for the last). */
|
|
@@ -85,6 +91,22 @@ export function useKeyboardManager({
|
|
|
85
91
|
[order, focusField, onSubmit],
|
|
86
92
|
);
|
|
87
93
|
|
|
94
|
+
const focusPrevious = useCallback(
|
|
95
|
+
(id: string) => {
|
|
96
|
+
const currentIndex = order.indexOf(id);
|
|
97
|
+
const prevId = order[currentIndex - 1];
|
|
98
|
+
if (prevId) focusField(prevId);
|
|
99
|
+
},
|
|
100
|
+
[order, focusField],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const getFieldIndex = useCallback((id: string) => order.indexOf(id), [order]);
|
|
104
|
+
const canGoPrevious = useCallback((id: string) => order.indexOf(id) > 0, [order]);
|
|
105
|
+
const canGoNext = useCallback((id: string) => {
|
|
106
|
+
const i = order.indexOf(id);
|
|
107
|
+
return i >= 0 && i < order.length - 1;
|
|
108
|
+
}, [order]);
|
|
109
|
+
|
|
88
110
|
const getReturnKeyType = useCallback(
|
|
89
111
|
(id: string): 'next' | 'done' => {
|
|
90
112
|
const isLast = order.indexOf(id) === order.length - 1;
|
|
@@ -97,7 +119,11 @@ export function useKeyboardManager({
|
|
|
97
119
|
registerField,
|
|
98
120
|
unregisterField,
|
|
99
121
|
focusNext,
|
|
122
|
+
focusPrevious,
|
|
100
123
|
focusField,
|
|
124
|
+
getFieldIndex,
|
|
125
|
+
canGoPrevious,
|
|
126
|
+
canGoNext,
|
|
101
127
|
scrollViewRef,
|
|
102
128
|
getReturnKeyType,
|
|
103
129
|
onSubmit: () => onSubmit?.(),
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
applySmartPaste,
|
|
4
|
+
routeSmartPaste,
|
|
5
|
+
type FieldFlowPasteResult,
|
|
6
|
+
type FieldFlowPasteTargets,
|
|
7
|
+
} from '../paste/smartPaste';
|
|
8
|
+
|
|
9
|
+
export interface UseSmartPasteOptions {
|
|
10
|
+
targets: FieldFlowPasteTargets;
|
|
11
|
+
minConfidence?: number;
|
|
12
|
+
onRouted?: (result: FieldFlowPasteResult) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Hook helper for smart paste — call `handlePaste(text)` from Clipboard or a paste event.
|
|
17
|
+
*/
|
|
18
|
+
export function useSmartPaste({
|
|
19
|
+
targets,
|
|
20
|
+
minConfidence = 0.7,
|
|
21
|
+
onRouted,
|
|
22
|
+
}: UseSmartPasteOptions) {
|
|
23
|
+
const handlePaste = useCallback(
|
|
24
|
+
(text: string) => {
|
|
25
|
+
const result = applySmartPaste(text, targets, minConfidence);
|
|
26
|
+
onRouted?.(result);
|
|
27
|
+
return result;
|
|
28
|
+
},
|
|
29
|
+
[targets, minConfidence, onRouted],
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return { handlePaste, routeSmartPaste };
|
|
33
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -94,6 +94,47 @@ export type {
|
|
|
94
94
|
FieldFlowPoint,
|
|
95
95
|
} from './components/FieldFlowSignature';
|
|
96
96
|
|
|
97
|
+
export { FieldFlowCompleteness } from './components/FieldFlowCompleteness';
|
|
98
|
+
export type { FieldFlowCompletenessProps } from './components/FieldFlowCompleteness';
|
|
99
|
+
|
|
100
|
+
export { FieldFlowAuthActions } from './components/FieldFlowAuthActions';
|
|
101
|
+
export type {
|
|
102
|
+
FieldFlowAuthActionsProps,
|
|
103
|
+
FieldFlowAuthAction,
|
|
104
|
+
FieldFlowAuthProvider,
|
|
105
|
+
} from './components/FieldFlowAuthActions';
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
FieldFlowKeyboardToolbar,
|
|
109
|
+
FIELD_FLOW_KEYBOARD_TOOLBAR_ID,
|
|
110
|
+
} from './components/FieldFlowKeyboardToolbar';
|
|
111
|
+
export type { FieldFlowKeyboardToolbarProps } from './components/FieldFlowKeyboardToolbar';
|
|
112
|
+
|
|
113
|
+
export { FieldFlowA11yAudit } from './components/FieldFlowA11yAudit';
|
|
114
|
+
export type { FieldFlowA11yAuditProps } from './components/FieldFlowA11yAudit';
|
|
115
|
+
|
|
116
|
+
export { FieldFlowInlineAlert } from './components/FieldFlowInlineAlert';
|
|
117
|
+
export type { FieldFlowInlineAlertProps, FieldFlowAlertTone } from './components/FieldFlowInlineAlert';
|
|
118
|
+
|
|
119
|
+
export { FieldFlowSkeleton } from './components/FieldFlowSkeleton';
|
|
120
|
+
export type { FieldFlowSkeletonProps } from './components/FieldFlowSkeleton';
|
|
121
|
+
|
|
122
|
+
export { FieldFlowFormHeader } from './components/FieldFlowFormHeader';
|
|
123
|
+
export type { FieldFlowFormHeaderProps } from './components/FieldFlowFormHeader';
|
|
124
|
+
|
|
125
|
+
export { FieldFlowInfoTip } from './components/FieldFlowInfoTip';
|
|
126
|
+
export type { FieldFlowInfoTipProps } from './components/FieldFlowInfoTip';
|
|
127
|
+
|
|
128
|
+
export { FieldFlowSchemaForm } from './schema';
|
|
129
|
+
export type {
|
|
130
|
+
FieldFlowSchemaFormProps,
|
|
131
|
+
FieldFlowSchemaShowWhenFn,
|
|
132
|
+
FieldFlowFormSchema,
|
|
133
|
+
FieldFlowSchemaField,
|
|
134
|
+
FieldFlowSchemaFieldType,
|
|
135
|
+
FieldFlowSchemaSection,
|
|
136
|
+
} from './schema';
|
|
137
|
+
|
|
97
138
|
// Adapters (optional peers: react-hook-form)
|
|
98
139
|
export {
|
|
99
140
|
FieldFlowController,
|
|
@@ -113,6 +154,9 @@ export {
|
|
|
113
154
|
darkTheme,
|
|
114
155
|
themePresets,
|
|
115
156
|
getThemePreset,
|
|
157
|
+
applyAppearance,
|
|
158
|
+
defaultAppearance,
|
|
159
|
+
surfaceShadow,
|
|
116
160
|
} from './theme';
|
|
117
161
|
export type { FieldFlowThemeProviderProps, FieldFlowThemePresetName } from './theme';
|
|
118
162
|
|
|
@@ -130,6 +174,9 @@ export {
|
|
|
130
174
|
useFieldFlowDraft,
|
|
131
175
|
useFieldFlowSteps,
|
|
132
176
|
useFieldFlowDirtyGuard,
|
|
177
|
+
useFieldFlowCompleteness,
|
|
178
|
+
useFieldFlowSubmitQueue,
|
|
179
|
+
useSmartPaste,
|
|
133
180
|
} from './hooks';
|
|
134
181
|
export type {
|
|
135
182
|
UseFieldFlowFormOptions,
|
|
@@ -157,8 +204,45 @@ export type {
|
|
|
157
204
|
UseFieldFlowStepsResult,
|
|
158
205
|
UseFieldFlowDirtyGuardOptions,
|
|
159
206
|
UseFieldFlowDirtyGuardResult,
|
|
207
|
+
UseFieldFlowCompletenessOptions,
|
|
208
|
+
UseFieldFlowCompletenessResult,
|
|
209
|
+
FieldFlowSubmitJob,
|
|
210
|
+
FieldFlowSubmitQueueStorage,
|
|
211
|
+
UseFieldFlowSubmitQueueOptions,
|
|
212
|
+
UseFieldFlowSubmitQueueResult,
|
|
213
|
+
UseSmartPasteOptions,
|
|
160
214
|
} from './hooks';
|
|
161
215
|
|
|
216
|
+
// Autofill
|
|
217
|
+
export {
|
|
218
|
+
getAutofillProps,
|
|
219
|
+
autofillRoleFromFieldType,
|
|
220
|
+
autofillProfiles,
|
|
221
|
+
} from './autofill';
|
|
222
|
+
export type {
|
|
223
|
+
FieldFlowAutofillRole,
|
|
224
|
+
FieldFlowAutofillProps,
|
|
225
|
+
FieldFlowAutofillProfileName,
|
|
226
|
+
} from './autofill';
|
|
227
|
+
|
|
228
|
+
// Smart paste
|
|
229
|
+
export { routeSmartPaste, applySmartPaste } from './paste';
|
|
230
|
+
export type {
|
|
231
|
+
FieldFlowPasteKind,
|
|
232
|
+
FieldFlowPasteResult,
|
|
233
|
+
FieldFlowAddressPaste,
|
|
234
|
+
FieldFlowPasteTargets,
|
|
235
|
+
} from './paste';
|
|
236
|
+
|
|
237
|
+
// A11y audit
|
|
238
|
+
export { runA11yAudit } from './a11y';
|
|
239
|
+
export type {
|
|
240
|
+
FieldFlowA11ySeverity,
|
|
241
|
+
FieldFlowA11yIssue,
|
|
242
|
+
FieldFlowA11yFieldSnapshot,
|
|
243
|
+
FieldFlowA11yAuditInput,
|
|
244
|
+
} from './a11y';
|
|
245
|
+
|
|
162
246
|
// i18n
|
|
163
247
|
export {
|
|
164
248
|
validationMessageCatalogs,
|
|
@@ -269,5 +353,11 @@ export type {
|
|
|
269
353
|
FieldFlowTypography,
|
|
270
354
|
FieldFlowAnimationConfig,
|
|
271
355
|
FieldFlowTheme,
|
|
356
|
+
FieldFlowAppearance,
|
|
357
|
+
FieldFlowDensity,
|
|
358
|
+
FieldFlowControlSize,
|
|
359
|
+
FieldFlowSurface,
|
|
360
|
+
FieldFlowShape,
|
|
361
|
+
FieldFlowFocusRing,
|
|
272
362
|
DeepPartial,
|
|
273
363
|
} from './types';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
export type FieldFlowPasteKind =
|
|
2
|
+
| 'otp'
|
|
3
|
+
| 'card'
|
|
4
|
+
| 'email'
|
|
5
|
+
| 'phone'
|
|
6
|
+
| 'address'
|
|
7
|
+
| 'url'
|
|
8
|
+
| 'unknown';
|
|
9
|
+
|
|
10
|
+
export interface FieldFlowAddressPaste {
|
|
11
|
+
street?: string;
|
|
12
|
+
city?: string;
|
|
13
|
+
state?: string;
|
|
14
|
+
postalCode?: string;
|
|
15
|
+
country?: string;
|
|
16
|
+
raw: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FieldFlowPasteResult {
|
|
20
|
+
kind: FieldFlowPasteKind;
|
|
21
|
+
/** Primary value for a single target field. */
|
|
22
|
+
value: string;
|
|
23
|
+
/** Digits-only for OTP / card / phone when applicable. */
|
|
24
|
+
raw?: string;
|
|
25
|
+
address?: FieldFlowAddressPaste;
|
|
26
|
+
confidence: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function digitsOnly(s: string): string {
|
|
30
|
+
return s.replace(/\D/g, '');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Classify clipboard / pasted text and extract a structured value.
|
|
35
|
+
* Pure — no Clipboard permission required; call from `onPaste` or after reading clipboard.
|
|
36
|
+
*/
|
|
37
|
+
export function routeSmartPaste(text: string): FieldFlowPasteResult {
|
|
38
|
+
const trimmed = text.trim();
|
|
39
|
+
if (!trimmed) {
|
|
40
|
+
return { kind: 'unknown', value: '', confidence: 0 };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const digits = digitsOnly(trimmed);
|
|
44
|
+
|
|
45
|
+
// OTP: 4–8 digits, little else
|
|
46
|
+
if (/^\d{4,8}$/.test(digits) && digits.length === trimmed.replace(/\s/g, '').length) {
|
|
47
|
+
return { kind: 'otp', value: digits, raw: digits, confidence: 0.95 };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Card: 13–19 digits (spaces/dashes ok)
|
|
51
|
+
if (digits.length >= 13 && digits.length <= 19 && /^[\d\s-]+$/.test(trimmed)) {
|
|
52
|
+
return { kind: 'card', value: digits, raw: digits, confidence: 0.9 };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Email
|
|
56
|
+
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
|
|
57
|
+
return { kind: 'email', value: trimmed.toLowerCase(), confidence: 0.98 };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// URL
|
|
61
|
+
if (/^https?:\/\//i.test(trimmed) || /^www\./i.test(trimmed)) {
|
|
62
|
+
return {
|
|
63
|
+
kind: 'url',
|
|
64
|
+
value: trimmed.startsWith('http') ? trimmed : `https://${trimmed}`,
|
|
65
|
+
confidence: 0.9,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Phone: 7–15 digits with optional + and punctuation
|
|
70
|
+
if (
|
|
71
|
+
digits.length >= 7 &&
|
|
72
|
+
digits.length <= 15 &&
|
|
73
|
+
/^[+]?[\d\s().-]{7,22}$/.test(trimmed) &&
|
|
74
|
+
!/[a-zA-Z]{3,}/.test(trimmed)
|
|
75
|
+
) {
|
|
76
|
+
return { kind: 'phone', value: trimmed, raw: digits, confidence: 0.85 };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Multi-line address-ish
|
|
80
|
+
const lines = trimmed
|
|
81
|
+
.split(/\r?\n/)
|
|
82
|
+
.map(l => l.trim())
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
if (lines.length >= 2) {
|
|
85
|
+
const postalMatch = trimmed.match(/\b([A-Z]{1,2}\d[A-Z\d]?\s*\d[A-Z]{2}|\d{5}(?:-\d{4})?)\b/i);
|
|
86
|
+
const address: FieldFlowAddressPaste = {
|
|
87
|
+
street: lines[0],
|
|
88
|
+
city: lines[1]?.replace(/,.*$/, '').trim(),
|
|
89
|
+
state: lines[1]?.includes(',')
|
|
90
|
+
? lines[1].split(',')[1]?.trim().split(/\s+/)[0]
|
|
91
|
+
: undefined,
|
|
92
|
+
postalCode: postalMatch?.[1],
|
|
93
|
+
raw: trimmed,
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
kind: 'address',
|
|
97
|
+
value: lines[0],
|
|
98
|
+
address,
|
|
99
|
+
confidence: 0.7,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { kind: 'unknown', value: trimmed, confidence: 0.2 };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type FieldFlowPasteTargets = Partial<
|
|
107
|
+
Record<FieldFlowPasteKind, (result: FieldFlowPasteResult) => void>
|
|
108
|
+
> & {
|
|
109
|
+
/** Called when kind is address and you want field-level writes. */
|
|
110
|
+
addressFields?: (address: FieldFlowAddressPaste) => void;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Route paste into field setters. Returns true if a handler ran with confidence ≥ minConfidence.
|
|
115
|
+
*/
|
|
116
|
+
export function applySmartPaste(
|
|
117
|
+
text: string,
|
|
118
|
+
targets: FieldFlowPasteTargets,
|
|
119
|
+
minConfidence = 0.7,
|
|
120
|
+
): FieldFlowPasteResult {
|
|
121
|
+
const result = routeSmartPaste(text);
|
|
122
|
+
if (result.confidence < minConfidence) return result;
|
|
123
|
+
|
|
124
|
+
if (result.kind === 'address' && result.address && targets.addressFields) {
|
|
125
|
+
targets.addressFields(result.address);
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const handler = targets[result.kind];
|
|
130
|
+
if (handler) handler(result);
|
|
131
|
+
return result;
|
|
132
|
+
}
|