rn-inputforge 0.3.1 → 0.4.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/README.md +224 -19
- package/lib/commonjs/components/FieldFlowCheckbox/FieldFlowCheckbox.js +131 -0
- package/lib/commonjs/components/FieldFlowCheckbox/FieldFlowCheckbox.js.map +1 -0
- package/lib/commonjs/components/FieldFlowCheckbox/index.js +13 -0
- package/lib/commonjs/components/FieldFlowCheckbox/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js +536 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js.map +1 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/index.js +13 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/types.js +6 -0
- package/lib/commonjs/components/FieldFlowDateTimePicker/types.js.map +1 -0
- package/lib/commonjs/components/FieldFlowDropdown/FieldFlowDropdown.js +24 -9
- package/lib/commonjs/components/FieldFlowDropdown/FieldFlowDropdown.js.map +1 -1
- package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js +55 -24
- package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js.map +1 -1
- package/lib/commonjs/components/FieldFlowInput/styles.js +32 -11
- package/lib/commonjs/components/FieldFlowInput/styles.js.map +1 -1
- package/lib/commonjs/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js +66 -0
- package/lib/commonjs/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js.map +1 -0
- package/lib/commonjs/components/FieldFlowMaskedInput/index.js +13 -0
- package/lib/commonjs/components/FieldFlowMaskedInput/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowMaskedInput/types.js +6 -0
- package/lib/commonjs/components/FieldFlowMaskedInput/types.js.map +1 -0
- package/lib/commonjs/components/FieldFlowOTP/FieldFlowOTP.js +255 -0
- package/lib/commonjs/components/FieldFlowOTP/FieldFlowOTP.js.map +1 -0
- package/lib/commonjs/components/FieldFlowOTP/index.js +13 -0
- package/lib/commonjs/components/FieldFlowOTP/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowOTP/types.js +6 -0
- package/lib/commonjs/components/FieldFlowOTP/types.js.map +1 -0
- package/lib/commonjs/components/FieldFlowRadio/FieldFlowRadio.js +164 -0
- package/lib/commonjs/components/FieldFlowRadio/FieldFlowRadio.js.map +1 -0
- package/lib/commonjs/components/FieldFlowRadio/index.js +13 -0
- package/lib/commonjs/components/FieldFlowRadio/index.js.map +1 -0
- package/lib/commonjs/components/FieldFlowSwitch/FieldFlowSwitch.js +152 -0
- package/lib/commonjs/components/FieldFlowSwitch/FieldFlowSwitch.js.map +1 -0
- package/lib/commonjs/components/FieldFlowSwitch/index.js +13 -0
- package/lib/commonjs/components/FieldFlowSwitch/index.js.map +1 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useFieldFlowForm.js +146 -31
- package/lib/commonjs/hooks/useFieldFlowForm.js.map +1 -1
- package/lib/commonjs/icons/Icon.js +719 -30
- package/lib/commonjs/icons/Icon.js.map +1 -1
- package/lib/commonjs/icons/index.js +7 -0
- package/lib/commonjs/icons/index.js.map +1 -1
- package/lib/commonjs/icons/renderIcon.js +24 -0
- package/lib/commonjs/icons/renderIcon.js.map +1 -0
- package/lib/commonjs/index.js +127 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/masks.js +130 -0
- package/lib/commonjs/utils/masks.js.map +1 -0
- package/lib/commonjs/validation/index.js +25 -0
- package/lib/commonjs/validation/index.js.map +1 -1
- package/lib/commonjs/validation/zodAdapter.js +91 -0
- package/lib/commonjs/validation/zodAdapter.js.map +1 -0
- package/lib/module/components/FieldFlowCheckbox/FieldFlowCheckbox.js +126 -0
- package/lib/module/components/FieldFlowCheckbox/FieldFlowCheckbox.js.map +1 -0
- package/lib/module/components/FieldFlowCheckbox/index.js +4 -0
- package/lib/module/components/FieldFlowCheckbox/index.js.map +1 -0
- package/lib/module/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js +531 -0
- package/lib/module/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.js.map +1 -0
- package/lib/module/components/FieldFlowDateTimePicker/index.js +4 -0
- package/lib/module/components/FieldFlowDateTimePicker/index.js.map +1 -0
- package/lib/module/components/FieldFlowDateTimePicker/types.js +4 -0
- package/lib/module/components/FieldFlowDateTimePicker/types.js.map +1 -0
- package/lib/module/components/FieldFlowDropdown/FieldFlowDropdown.js +24 -9
- package/lib/module/components/FieldFlowDropdown/FieldFlowDropdown.js.map +1 -1
- package/lib/module/components/FieldFlowInput/FieldFlowInput.js +59 -28
- package/lib/module/components/FieldFlowInput/FieldFlowInput.js.map +1 -1
- package/lib/module/components/FieldFlowInput/styles.js +32 -11
- package/lib/module/components/FieldFlowInput/styles.js.map +1 -1
- package/lib/module/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js +61 -0
- package/lib/module/components/FieldFlowMaskedInput/FieldFlowMaskedInput.js.map +1 -0
- package/lib/module/components/FieldFlowMaskedInput/index.js +4 -0
- package/lib/module/components/FieldFlowMaskedInput/index.js.map +1 -0
- package/lib/module/components/FieldFlowMaskedInput/types.js +4 -0
- package/lib/module/components/FieldFlowMaskedInput/types.js.map +1 -0
- package/lib/module/components/FieldFlowOTP/FieldFlowOTP.js +250 -0
- package/lib/module/components/FieldFlowOTP/FieldFlowOTP.js.map +1 -0
- package/lib/module/components/FieldFlowOTP/index.js +4 -0
- package/lib/module/components/FieldFlowOTP/index.js.map +1 -0
- package/lib/module/components/FieldFlowOTP/types.js +4 -0
- package/lib/module/components/FieldFlowOTP/types.js.map +1 -0
- package/lib/module/components/FieldFlowRadio/FieldFlowRadio.js +159 -0
- package/lib/module/components/FieldFlowRadio/FieldFlowRadio.js.map +1 -0
- package/lib/module/components/FieldFlowRadio/index.js +4 -0
- package/lib/module/components/FieldFlowRadio/index.js.map +1 -0
- package/lib/module/components/FieldFlowSwitch/FieldFlowSwitch.js +147 -0
- package/lib/module/components/FieldFlowSwitch/FieldFlowSwitch.js.map +1 -0
- package/lib/module/components/FieldFlowSwitch/index.js +4 -0
- package/lib/module/components/FieldFlowSwitch/index.js.map +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useFieldFlowForm.js +147 -32
- package/lib/module/hooks/useFieldFlowForm.js.map +1 -1
- package/lib/module/icons/Icon.js +720 -31
- package/lib/module/icons/Icon.js.map +1 -1
- package/lib/module/icons/index.js +1 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/icons/renderIcon.js +19 -0
- package/lib/module/icons/renderIcon.js.map +1 -0
- package/lib/module/index.js +11 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/masks.js +118 -0
- package/lib/module/utils/masks.js.map +1 -0
- package/lib/module/validation/index.js +1 -0
- package/lib/module/validation/index.js.map +1 -1
- package/lib/module/validation/zodAdapter.js +84 -0
- package/lib/module/validation/zodAdapter.js.map +1 -0
- package/lib/typescript/components/FieldFlowCheckbox/FieldFlowCheckbox.d.ts +36 -0
- package/lib/typescript/components/FieldFlowCheckbox/FieldFlowCheckbox.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowCheckbox/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowCheckbox/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.d.ts +4 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/types.d.ts +55 -0
- package/lib/typescript/components/FieldFlowDateTimePicker/types.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowDropdown/FieldFlowDropdown.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowDropdown/types.d.ts +7 -1
- package/lib/typescript/components/FieldFlowDropdown/types.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowInput/FieldFlowInput.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowInput/styles.d.ts +19 -2
- package/lib/typescript/components/FieldFlowInput/styles.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowInput/types.d.ts +24 -5
- package/lib/typescript/components/FieldFlowInput/types.d.ts.map +1 -1
- package/lib/typescript/components/FieldFlowMaskedInput/FieldFlowMaskedInput.d.ts +5 -0
- package/lib/typescript/components/FieldFlowMaskedInput/FieldFlowMaskedInput.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowMaskedInput/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowMaskedInput/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowMaskedInput/types.d.ts +20 -0
- package/lib/typescript/components/FieldFlowMaskedInput/types.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowOTP/FieldFlowOTP.d.ts +4 -0
- package/lib/typescript/components/FieldFlowOTP/FieldFlowOTP.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowOTP/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowOTP/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowOTP/types.d.ts +52 -0
- package/lib/typescript/components/FieldFlowOTP/types.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowRadio/FieldFlowRadio.d.ts +39 -0
- package/lib/typescript/components/FieldFlowRadio/FieldFlowRadio.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowRadio/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowRadio/index.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowSwitch/FieldFlowSwitch.d.ts +31 -0
- package/lib/typescript/components/FieldFlowSwitch/FieldFlowSwitch.d.ts.map +1 -0
- package/lib/typescript/components/FieldFlowSwitch/index.d.ts +3 -0
- package/lib/typescript/components/FieldFlowSwitch/index.d.ts.map +1 -0
- package/lib/typescript/hooks/index.d.ts +1 -1
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useFieldFlowForm.d.ts +46 -7
- package/lib/typescript/hooks/useFieldFlowForm.d.ts.map +1 -1
- package/lib/typescript/icons/Icon.d.ts +2 -2
- package/lib/typescript/icons/Icon.d.ts.map +1 -1
- package/lib/typescript/icons/index.d.ts +2 -0
- package/lib/typescript/icons/index.d.ts.map +1 -1
- package/lib/typescript/icons/renderIcon.d.ts +6 -0
- package/lib/typescript/icons/renderIcon.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +20 -6
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types/index.d.ts +4 -0
- package/lib/typescript/types/index.d.ts.map +1 -1
- package/lib/typescript/utils/masks.d.ts +37 -0
- package/lib/typescript/utils/masks.d.ts.map +1 -0
- package/lib/typescript/validation/index.d.ts +2 -0
- package/lib/typescript/validation/index.d.ts.map +1 -1
- package/lib/typescript/validation/zodAdapter.d.ts +53 -0
- package/lib/typescript/validation/zodAdapter.d.ts.map +1 -0
- package/package.json +17 -3
- package/src/components/FieldFlowCheckbox/FieldFlowCheckbox.tsx +174 -0
- package/src/components/FieldFlowCheckbox/index.ts +2 -0
- package/src/components/FieldFlowDateTimePicker/FieldFlowDateTimePicker.tsx +585 -0
- package/src/components/FieldFlowDateTimePicker/index.ts +2 -0
- package/src/components/FieldFlowDateTimePicker/types.ts +57 -0
- package/src/components/FieldFlowDropdown/FieldFlowDropdown.tsx +17 -7
- package/src/components/FieldFlowDropdown/types.ts +8 -1
- package/src/components/FieldFlowInput/FieldFlowInput.tsx +69 -26
- package/src/components/FieldFlowInput/styles.ts +41 -6
- package/src/components/FieldFlowInput/types.ts +24 -5
- package/src/components/FieldFlowMaskedInput/FieldFlowMaskedInput.tsx +76 -0
- package/src/components/FieldFlowMaskedInput/index.ts +2 -0
- package/src/components/FieldFlowMaskedInput/types.ts +22 -0
- package/src/components/FieldFlowOTP/FieldFlowOTP.tsx +305 -0
- package/src/components/FieldFlowOTP/index.ts +2 -0
- package/src/components/FieldFlowOTP/types.ts +57 -0
- package/src/components/FieldFlowRadio/FieldFlowRadio.tsx +215 -0
- package/src/components/FieldFlowRadio/index.ts +2 -0
- package/src/components/FieldFlowSwitch/FieldFlowSwitch.tsx +172 -0
- package/src/components/FieldFlowSwitch/index.ts +2 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useFieldFlowForm.ts +209 -33
- package/src/icons/Icon.tsx +451 -5
- package/src/icons/index.ts +2 -0
- package/src/icons/renderIcon.tsx +17 -0
- package/src/index.ts +47 -4
- package/src/types/index.ts +5 -0
- package/src/utils/masks.ts +137 -0
- package/src/validation/index.ts +7 -0
- package/src/validation/zodAdapter.ts +103 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export type FieldFlowMaskType = 'phone' | 'card' | 'currency' | 'date' | 'custom';
|
|
2
|
+
|
|
3
|
+
export interface MaskResult {
|
|
4
|
+
/** Display value shown in the input. */
|
|
5
|
+
formatted: string;
|
|
6
|
+
/** Digits / raw value without mask characters (currency keeps decimal point). */
|
|
7
|
+
raw: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ApplyMaskOptions {
|
|
11
|
+
/** Currency symbol prefix, e.g. "$" or "Rs ". Default: "$". */
|
|
12
|
+
currencySymbol?: string;
|
|
13
|
+
/** Max decimal places for currency. Default: 2. */
|
|
14
|
+
currencyDecimals?: number;
|
|
15
|
+
/** Custom mask pattern using `#` for digits. E.g. `(###) ###-####`. */
|
|
16
|
+
pattern?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Strip everything except digits. */
|
|
20
|
+
export function digitsOnly(value: string): string {
|
|
21
|
+
return value.replace(/\D/g, '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Apply a digit pattern where `#` is a digit slot and any other char is literal.
|
|
26
|
+
* Extra digits beyond the pattern are ignored.
|
|
27
|
+
*/
|
|
28
|
+
export function applyPattern(rawDigits: string, pattern: string): MaskResult {
|
|
29
|
+
let di = 0;
|
|
30
|
+
let formatted = '';
|
|
31
|
+
for (let i = 0; i < pattern.length && di < rawDigits.length; i++) {
|
|
32
|
+
const ch = pattern[i];
|
|
33
|
+
if (ch === '#') {
|
|
34
|
+
formatted += rawDigits[di++];
|
|
35
|
+
} else {
|
|
36
|
+
formatted += ch;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { formatted, raw: rawDigits.slice(0, di) };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function applyPhoneMask(value: string): MaskResult {
|
|
43
|
+
const digits = digitsOnly(value).slice(0, 15);
|
|
44
|
+
// US-style when 10 digits or less; otherwise keep a loose international grouping
|
|
45
|
+
if (digits.length <= 10) {
|
|
46
|
+
return applyPattern(digits, '(###) ###-####');
|
|
47
|
+
}
|
|
48
|
+
// +X (XXX) XXX-XXXX… style for longer numbers
|
|
49
|
+
const country = digits.slice(0, digits.length - 10);
|
|
50
|
+
const rest = applyPattern(digits.slice(-10), '(###) ###-####').formatted;
|
|
51
|
+
return { formatted: `+${country} ${rest}`, raw: digits };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function applyCardMask(value: string): MaskResult {
|
|
55
|
+
const digits = digitsOnly(value).slice(0, 19);
|
|
56
|
+
const groups = digits.match(/.{1,4}/g) ?? [];
|
|
57
|
+
return { formatted: groups.join(' '), raw: digits };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function applyDateMask(value: string): MaskResult {
|
|
61
|
+
// MM/DD/YYYY
|
|
62
|
+
return applyPattern(digitsOnly(value).slice(0, 8), '##/##/####');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function applyCurrencyMask(
|
|
66
|
+
value: string,
|
|
67
|
+
symbol = '$',
|
|
68
|
+
decimals = 2,
|
|
69
|
+
): MaskResult {
|
|
70
|
+
// Keep only digits and at most one decimal point
|
|
71
|
+
let cleaned = value.replace(/[^0-9.]/g, '');
|
|
72
|
+
const firstDot = cleaned.indexOf('.');
|
|
73
|
+
if (firstDot !== -1) {
|
|
74
|
+
cleaned =
|
|
75
|
+
cleaned.slice(0, firstDot + 1) + cleaned.slice(firstDot + 1).replace(/\./g, '');
|
|
76
|
+
const [intPart, frac = ''] = cleaned.split('.');
|
|
77
|
+
cleaned = `${intPart}.${frac.slice(0, decimals)}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!cleaned || cleaned === '.') {
|
|
81
|
+
return { formatted: '', raw: '' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const [intRaw, fracRaw] = cleaned.split('.');
|
|
85
|
+
const intFormatted = (intRaw || '0').replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
86
|
+
const formatted =
|
|
87
|
+
fracRaw !== undefined ? `${symbol}${intFormatted}.${fracRaw}` : `${symbol}${intFormatted}`;
|
|
88
|
+
|
|
89
|
+
return { formatted, raw: cleaned };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Format a value with a built-in or custom mask.
|
|
94
|
+
* Pass the previous formatted value's raw length context via the incoming `value`
|
|
95
|
+
* (the function always re-derives from the full string the user typed).
|
|
96
|
+
*/
|
|
97
|
+
export function applyMask(
|
|
98
|
+
value: string,
|
|
99
|
+
type: FieldFlowMaskType,
|
|
100
|
+
options: ApplyMaskOptions = {},
|
|
101
|
+
): MaskResult {
|
|
102
|
+
switch (type) {
|
|
103
|
+
case 'phone':
|
|
104
|
+
return applyPhoneMask(value);
|
|
105
|
+
case 'card':
|
|
106
|
+
return applyCardMask(value);
|
|
107
|
+
case 'date':
|
|
108
|
+
return applyDateMask(value);
|
|
109
|
+
case 'currency':
|
|
110
|
+
return applyCurrencyMask(value, options.currencySymbol ?? '$', options.currencyDecimals ?? 2);
|
|
111
|
+
case 'custom':
|
|
112
|
+
if (!options.pattern) {
|
|
113
|
+
return { formatted: value, raw: value };
|
|
114
|
+
}
|
|
115
|
+
return applyPattern(digitsOnly(value), options.pattern);
|
|
116
|
+
default:
|
|
117
|
+
return { formatted: value, raw: value };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Convenience: return only the formatted display string. */
|
|
122
|
+
export function formatMasked(
|
|
123
|
+
value: string,
|
|
124
|
+
type: FieldFlowMaskType,
|
|
125
|
+
options?: ApplyMaskOptions,
|
|
126
|
+
): string {
|
|
127
|
+
return applyMask(value, type, options).formatted;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Convenience: return only the raw (unmasked) value. */
|
|
131
|
+
export function unmask(
|
|
132
|
+
value: string,
|
|
133
|
+
type: FieldFlowMaskType,
|
|
134
|
+
options?: ApplyMaskOptions,
|
|
135
|
+
): string {
|
|
136
|
+
return applyMask(value, type, options).raw;
|
|
137
|
+
}
|
package/src/validation/index.ts
CHANGED
|
@@ -13,3 +13,10 @@ export {
|
|
|
13
13
|
} from './validators';
|
|
14
14
|
export { useValidation } from './useValidation';
|
|
15
15
|
export type { UseValidationResult } from './useValidation';
|
|
16
|
+
export {
|
|
17
|
+
zodToFormErrors,
|
|
18
|
+
zodFormValidate,
|
|
19
|
+
createZodSubmitGuard,
|
|
20
|
+
fieldRulesFromZodString,
|
|
21
|
+
} from './zodAdapter';
|
|
22
|
+
export type { ZodSchemaLike, ZodIssueLike, ZodErrorLike } from './zodAdapter';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { FieldFlowRules } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural types so consumers can pass a Zod schema without
|
|
5
|
+
* rn-inputforge taking a hard dependency on `zod`.
|
|
6
|
+
*/
|
|
7
|
+
export interface ZodIssueLike {
|
|
8
|
+
path: (string | number)[];
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ZodErrorLike {
|
|
13
|
+
issues: ZodIssueLike[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ZodSchemaLike<T = unknown> {
|
|
17
|
+
safeParse: (data: unknown) => { success: true; data: T } | { success: false; error: ZodErrorLike };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert a Zod schema parse failure into FieldFlow form errors
|
|
22
|
+
* (first message per top-level field).
|
|
23
|
+
*/
|
|
24
|
+
export function zodToFormErrors<T extends Record<string, string>>(
|
|
25
|
+
schema: ZodSchemaLike<T>,
|
|
26
|
+
values: T,
|
|
27
|
+
): Partial<Record<keyof T, string>> {
|
|
28
|
+
const result = schema.safeParse(values);
|
|
29
|
+
if (result.success) return {};
|
|
30
|
+
|
|
31
|
+
const errors: Partial<Record<keyof T, string>> = {};
|
|
32
|
+
for (const issue of result.error.issues) {
|
|
33
|
+
const key = issue.path[0];
|
|
34
|
+
if (typeof key === 'string' && errors[key as keyof T] === undefined) {
|
|
35
|
+
errors[key as keyof T] = issue.message;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return errors;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Drop into `useFieldFlowForm({ formValidate: zodFormValidate(schema) })`.
|
|
43
|
+
*/
|
|
44
|
+
export function zodFormValidate<T extends Record<string, string>>(schema: ZodSchemaLike<T>) {
|
|
45
|
+
return (values: T) => zodToFormErrors(schema, values);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Wrap an `onSubmit` so it only runs when Zod passes.
|
|
50
|
+
* Useful if you keep FieldFlow rules for UX and Zod as the final gate.
|
|
51
|
+
*/
|
|
52
|
+
export function createZodSubmitGuard<T extends Record<string, string>>(
|
|
53
|
+
schema: ZodSchemaLike<T>,
|
|
54
|
+
onValid: (values: T) => void | Promise<void>,
|
|
55
|
+
onInvalid?: (errors: Partial<Record<keyof T, string>>) => void,
|
|
56
|
+
) {
|
|
57
|
+
return async (values: T) => {
|
|
58
|
+
const errors = zodToFormErrors(schema, values);
|
|
59
|
+
if (Object.keys(errors).length > 0) {
|
|
60
|
+
onInvalid?.(errors);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await onValid(values);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Best-effort map of a Zod string schema onto declarative `FieldFlowRules`.
|
|
69
|
+
* Supports required / email / min / max / regex. For refinements and objects,
|
|
70
|
+
* use `zodFormValidate` instead.
|
|
71
|
+
*/
|
|
72
|
+
export function fieldRulesFromZodString(zodStringSchema: {
|
|
73
|
+
_def?: {
|
|
74
|
+
typeName?: string;
|
|
75
|
+
checks?: Array<{ kind: string; value?: unknown; regex?: RegExp; message?: string }>;
|
|
76
|
+
};
|
|
77
|
+
isOptional?: () => boolean;
|
|
78
|
+
}): FieldFlowRules {
|
|
79
|
+
const rules: FieldFlowRules = {};
|
|
80
|
+
const def = zodStringSchema._def;
|
|
81
|
+
if (!def) return rules;
|
|
82
|
+
|
|
83
|
+
const optional = typeof zodStringSchema.isOptional === 'function' ? zodStringSchema.isOptional() : false;
|
|
84
|
+
if (!optional) rules.required = true;
|
|
85
|
+
|
|
86
|
+
for (const check of def.checks ?? []) {
|
|
87
|
+
if (check.kind === 'email') {
|
|
88
|
+
rules.email = check.message ?? true;
|
|
89
|
+
} else if (check.kind === 'min' && typeof check.value === 'number') {
|
|
90
|
+
if (check.value === 1 && !optional) {
|
|
91
|
+
rules.required = check.message ?? true;
|
|
92
|
+
} else {
|
|
93
|
+
rules.minLength = { value: check.value, message: check.message };
|
|
94
|
+
}
|
|
95
|
+
} else if (check.kind === 'max' && typeof check.value === 'number') {
|
|
96
|
+
rules.maxLength = { value: check.value, message: check.message };
|
|
97
|
+
} else if (check.kind === 'regex' && check.regex) {
|
|
98
|
+
rules.pattern = { value: check.regex, message: check.message };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return rules;
|
|
103
|
+
}
|