rn-inputforge 0.1.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.
Files changed (193) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +273 -0
  3. package/lib/commonjs/components/ChatInput/ChatInput.js +170 -0
  4. package/lib/commonjs/components/ChatInput/ChatInput.js.map +1 -0
  5. package/lib/commonjs/components/ChatInput/index.js +13 -0
  6. package/lib/commonjs/components/ChatInput/index.js.map +1 -0
  7. package/lib/commonjs/components/ChatInput/types.js +6 -0
  8. package/lib/commonjs/components/ChatInput/types.js.map +1 -0
  9. package/lib/commonjs/components/DescriptionField/DescriptionField.js +40 -0
  10. package/lib/commonjs/components/DescriptionField/DescriptionField.js.map +1 -0
  11. package/lib/commonjs/components/DescriptionField/index.js +13 -0
  12. package/lib/commonjs/components/DescriptionField/index.js.map +1 -0
  13. package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js +207 -0
  14. package/lib/commonjs/components/FieldFlowInput/FieldFlowInput.js.map +1 -0
  15. package/lib/commonjs/components/FieldFlowInput/index.js +13 -0
  16. package/lib/commonjs/components/FieldFlowInput/index.js.map +1 -0
  17. package/lib/commonjs/components/FieldFlowInput/styles.js +99 -0
  18. package/lib/commonjs/components/FieldFlowInput/styles.js.map +1 -0
  19. package/lib/commonjs/components/FieldFlowInput/types.js +6 -0
  20. package/lib/commonjs/components/FieldFlowInput/types.js.map +1 -0
  21. package/lib/commonjs/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js +44 -0
  22. package/lib/commonjs/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js.map +1 -0
  23. package/lib/commonjs/components/KeyboardAwareFormWrapper/index.js +13 -0
  24. package/lib/commonjs/components/KeyboardAwareFormWrapper/index.js.map +1 -0
  25. package/lib/commonjs/hooks/index.js +27 -0
  26. package/lib/commonjs/hooks/index.js.map +1 -0
  27. package/lib/commonjs/hooks/useFieldFlowForm.js +108 -0
  28. package/lib/commonjs/hooks/useFieldFlowForm.js.map +1 -0
  29. package/lib/commonjs/hooks/useFieldState.js +27 -0
  30. package/lib/commonjs/hooks/useFieldState.js.map +1 -0
  31. package/lib/commonjs/hooks/useKeyboardManager.js +68 -0
  32. package/lib/commonjs/hooks/useKeyboardManager.js.map +1 -0
  33. package/lib/commonjs/icons/Icon.js +388 -0
  34. package/lib/commonjs/icons/Icon.js.map +1 -0
  35. package/lib/commonjs/icons/index.js +13 -0
  36. package/lib/commonjs/icons/index.js.map +1 -0
  37. package/lib/commonjs/index.js +158 -0
  38. package/lib/commonjs/index.js.map +1 -0
  39. package/lib/commonjs/package.json +1 -0
  40. package/lib/commonjs/theme/ThemeProvider.js +48 -0
  41. package/lib/commonjs/theme/ThemeProvider.js.map +1 -0
  42. package/lib/commonjs/theme/defaultTheme.js +62 -0
  43. package/lib/commonjs/theme/defaultTheme.js.map +1 -0
  44. package/lib/commonjs/theme/index.js +38 -0
  45. package/lib/commonjs/theme/index.js.map +1 -0
  46. package/lib/commonjs/types/index.js +6 -0
  47. package/lib/commonjs/types/index.js.map +1 -0
  48. package/lib/commonjs/utils/formatters.js +25 -0
  49. package/lib/commonjs/utils/formatters.js.map +1 -0
  50. package/lib/commonjs/utils/keyboardTypes.js +103 -0
  51. package/lib/commonjs/utils/keyboardTypes.js.map +1 -0
  52. package/lib/commonjs/validation/index.js +80 -0
  53. package/lib/commonjs/validation/index.js.map +1 -0
  54. package/lib/commonjs/validation/useValidation.js +34 -0
  55. package/lib/commonjs/validation/useValidation.js.map +1 -0
  56. package/lib/commonjs/validation/validators.js +118 -0
  57. package/lib/commonjs/validation/validators.js.map +1 -0
  58. package/lib/module/components/ChatInput/ChatInput.js +165 -0
  59. package/lib/module/components/ChatInput/ChatInput.js.map +1 -0
  60. package/lib/module/components/ChatInput/index.js +4 -0
  61. package/lib/module/components/ChatInput/index.js.map +1 -0
  62. package/lib/module/components/ChatInput/types.js +4 -0
  63. package/lib/module/components/ChatInput/types.js.map +1 -0
  64. package/lib/module/components/DescriptionField/DescriptionField.js +35 -0
  65. package/lib/module/components/DescriptionField/DescriptionField.js.map +1 -0
  66. package/lib/module/components/DescriptionField/index.js +4 -0
  67. package/lib/module/components/DescriptionField/index.js.map +1 -0
  68. package/lib/module/components/FieldFlowInput/FieldFlowInput.js +202 -0
  69. package/lib/module/components/FieldFlowInput/FieldFlowInput.js.map +1 -0
  70. package/lib/module/components/FieldFlowInput/index.js +4 -0
  71. package/lib/module/components/FieldFlowInput/index.js.map +1 -0
  72. package/lib/module/components/FieldFlowInput/styles.js +92 -0
  73. package/lib/module/components/FieldFlowInput/styles.js.map +1 -0
  74. package/lib/module/components/FieldFlowInput/types.js +4 -0
  75. package/lib/module/components/FieldFlowInput/types.js.map +1 -0
  76. package/lib/module/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js +39 -0
  77. package/lib/module/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.js.map +1 -0
  78. package/lib/module/components/KeyboardAwareFormWrapper/index.js +4 -0
  79. package/lib/module/components/KeyboardAwareFormWrapper/index.js.map +1 -0
  80. package/lib/module/hooks/index.js +6 -0
  81. package/lib/module/hooks/index.js.map +1 -0
  82. package/lib/module/hooks/useFieldFlowForm.js +104 -0
  83. package/lib/module/hooks/useFieldFlowForm.js.map +1 -0
  84. package/lib/module/hooks/useFieldState.js +23 -0
  85. package/lib/module/hooks/useFieldState.js.map +1 -0
  86. package/lib/module/hooks/useKeyboardManager.js +64 -0
  87. package/lib/module/hooks/useKeyboardManager.js.map +1 -0
  88. package/lib/module/icons/Icon.js +383 -0
  89. package/lib/module/icons/Icon.js.map +1 -0
  90. package/lib/module/icons/index.js +4 -0
  91. package/lib/module/icons/index.js.map +1 -0
  92. package/lib/module/index.js +18 -0
  93. package/lib/module/index.js.map +1 -0
  94. package/lib/module/theme/ThemeProvider.js +42 -0
  95. package/lib/module/theme/ThemeProvider.js.map +1 -0
  96. package/lib/module/theme/defaultTheme.js +58 -0
  97. package/lib/module/theme/defaultTheme.js.map +1 -0
  98. package/lib/module/theme/index.js +5 -0
  99. package/lib/module/theme/index.js.map +1 -0
  100. package/lib/module/types/index.js +4 -0
  101. package/lib/module/types/index.js.map +1 -0
  102. package/lib/module/utils/formatters.js +19 -0
  103. package/lib/module/utils/formatters.js.map +1 -0
  104. package/lib/module/utils/keyboardTypes.js +98 -0
  105. package/lib/module/utils/keyboardTypes.js.map +1 -0
  106. package/lib/module/validation/index.js +5 -0
  107. package/lib/module/validation/index.js.map +1 -0
  108. package/lib/module/validation/useValidation.js +30 -0
  109. package/lib/module/validation/useValidation.js.map +1 -0
  110. package/lib/module/validation/validators.js +103 -0
  111. package/lib/module/validation/validators.js.map +1 -0
  112. package/lib/typescript/components/ChatInput/ChatInput.d.ts +10 -0
  113. package/lib/typescript/components/ChatInput/ChatInput.d.ts.map +1 -0
  114. package/lib/typescript/components/ChatInput/index.d.ts +3 -0
  115. package/lib/typescript/components/ChatInput/index.d.ts.map +1 -0
  116. package/lib/typescript/components/ChatInput/types.d.ts +30 -0
  117. package/lib/typescript/components/ChatInput/types.d.ts.map +1 -0
  118. package/lib/typescript/components/DescriptionField/DescriptionField.d.ts +16 -0
  119. package/lib/typescript/components/DescriptionField/DescriptionField.d.ts.map +1 -0
  120. package/lib/typescript/components/DescriptionField/index.d.ts +3 -0
  121. package/lib/typescript/components/DescriptionField/index.d.ts.map +1 -0
  122. package/lib/typescript/components/FieldFlowInput/FieldFlowInput.d.ts +5 -0
  123. package/lib/typescript/components/FieldFlowInput/FieldFlowInput.d.ts.map +1 -0
  124. package/lib/typescript/components/FieldFlowInput/index.d.ts +3 -0
  125. package/lib/typescript/components/FieldFlowInput/index.d.ts.map +1 -0
  126. package/lib/typescript/components/FieldFlowInput/styles.d.ts +71 -0
  127. package/lib/typescript/components/FieldFlowInput/styles.d.ts.map +1 -0
  128. package/lib/typescript/components/FieldFlowInput/types.d.ts +50 -0
  129. package/lib/typescript/components/FieldFlowInput/types.d.ts.map +1 -0
  130. package/lib/typescript/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.d.ts +17 -0
  131. package/lib/typescript/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.d.ts.map +1 -0
  132. package/lib/typescript/components/KeyboardAwareFormWrapper/index.d.ts +3 -0
  133. package/lib/typescript/components/KeyboardAwareFormWrapper/index.d.ts.map +1 -0
  134. package/lib/typescript/hooks/index.d.ts +7 -0
  135. package/lib/typescript/hooks/index.d.ts.map +1 -0
  136. package/lib/typescript/hooks/useFieldFlowForm.d.ts +31 -0
  137. package/lib/typescript/hooks/useFieldFlowForm.d.ts.map +1 -0
  138. package/lib/typescript/hooks/useFieldState.d.ts +12 -0
  139. package/lib/typescript/hooks/useFieldState.d.ts.map +1 -0
  140. package/lib/typescript/hooks/useKeyboardManager.d.ts +35 -0
  141. package/lib/typescript/hooks/useKeyboardManager.d.ts.map +1 -0
  142. package/lib/typescript/icons/Icon.d.ts +17 -0
  143. package/lib/typescript/icons/Icon.d.ts.map +1 -0
  144. package/lib/typescript/icons/index.d.ts +3 -0
  145. package/lib/typescript/icons/index.d.ts.map +1 -0
  146. package/lib/typescript/index.d.ts +18 -0
  147. package/lib/typescript/index.d.ts.map +1 -0
  148. package/lib/typescript/theme/ThemeProvider.d.ts +16 -0
  149. package/lib/typescript/theme/ThemeProvider.d.ts.map +1 -0
  150. package/lib/typescript/theme/defaultTheme.d.ts +4 -0
  151. package/lib/typescript/theme/defaultTheme.d.ts.map +1 -0
  152. package/lib/typescript/theme/index.d.ts +4 -0
  153. package/lib/typescript/theme/index.d.ts.map +1 -0
  154. package/lib/typescript/types/index.d.ts +79 -0
  155. package/lib/typescript/types/index.d.ts.map +1 -0
  156. package/lib/typescript/utils/formatters.d.ts +5 -0
  157. package/lib/typescript/utils/formatters.d.ts.map +1 -0
  158. package/lib/typescript/utils/keyboardTypes.d.ts +18 -0
  159. package/lib/typescript/utils/keyboardTypes.d.ts.map +1 -0
  160. package/lib/typescript/validation/index.d.ts +4 -0
  161. package/lib/typescript/validation/index.d.ts.map +1 -0
  162. package/lib/typescript/validation/useValidation.d.ts +14 -0
  163. package/lib/typescript/validation/useValidation.d.ts.map +1 -0
  164. package/lib/typescript/validation/validators.d.ts +17 -0
  165. package/lib/typescript/validation/validators.d.ts.map +1 -0
  166. package/package.json +80 -0
  167. package/src/components/ChatInput/ChatInput.tsx +178 -0
  168. package/src/components/ChatInput/index.ts +2 -0
  169. package/src/components/ChatInput/types.ts +35 -0
  170. package/src/components/DescriptionField/DescriptionField.tsx +39 -0
  171. package/src/components/DescriptionField/index.ts +2 -0
  172. package/src/components/FieldFlowInput/FieldFlowInput.tsx +240 -0
  173. package/src/components/FieldFlowInput/index.ts +2 -0
  174. package/src/components/FieldFlowInput/styles.ts +94 -0
  175. package/src/components/FieldFlowInput/types.ts +55 -0
  176. package/src/components/KeyboardAwareFormWrapper/KeyboardAwareFormWrapper.tsx +45 -0
  177. package/src/components/KeyboardAwareFormWrapper/index.ts +2 -0
  178. package/src/hooks/index.ts +12 -0
  179. package/src/hooks/useFieldFlowForm.ts +133 -0
  180. package/src/hooks/useFieldState.ts +31 -0
  181. package/src/hooks/useKeyboardManager.ts +105 -0
  182. package/src/icons/Icon.tsx +338 -0
  183. package/src/icons/index.ts +2 -0
  184. package/src/index.ts +63 -0
  185. package/src/theme/ThemeProvider.tsx +49 -0
  186. package/src/theme/defaultTheme.ts +58 -0
  187. package/src/theme/index.ts +3 -0
  188. package/src/types/index.ts +98 -0
  189. package/src/utils/formatters.ts +18 -0
  190. package/src/utils/keyboardTypes.ts +85 -0
  191. package/src/validation/index.ts +15 -0
  192. package/src/validation/useValidation.ts +37 -0
  193. package/src/validation/validators.ts +93 -0
package/src/index.ts ADDED
@@ -0,0 +1,63 @@
1
+ // Components
2
+ export { FieldFlowInput } from './components/FieldFlowInput';
3
+ export type { FieldFlowInputProps } from './components/FieldFlowInput';
4
+
5
+ export { DescriptionField } from './components/DescriptionField';
6
+ export type { DescriptionFieldProps } from './components/DescriptionField';
7
+
8
+ export { ChatInput } from './components/ChatInput';
9
+ export type { ChatInputProps, ChatAttachmentAction, CustomChatAction } from './components/ChatInput';
10
+
11
+ export { KeyboardAwareFormWrapper } from './components/KeyboardAwareFormWrapper';
12
+ export type { KeyboardAwareFormWrapperProps } from './components/KeyboardAwareFormWrapper';
13
+
14
+ // Theming
15
+ export { FieldFlowThemeProvider, useFieldFlowTheme, defaultTheme, darkTheme } from './theme';
16
+ export type { FieldFlowThemeProviderProps } from './theme';
17
+
18
+ // Hooks
19
+ export { useFieldFlowForm, useKeyboardManager, useFieldState } from './hooks';
20
+ export type {
21
+ UseFieldFlowFormOptions,
22
+ UseFieldFlowFormResult,
23
+ FieldFlowFormRules,
24
+ UseKeyboardManagerOptions,
25
+ UseKeyboardManagerResult,
26
+ UseFieldStateOptions,
27
+ } from './hooks';
28
+
29
+ // Validation
30
+ export {
31
+ useValidation,
32
+ runRules,
33
+ isValidEmail,
34
+ isValidNumber,
35
+ isValidPhone,
36
+ requiredValidator,
37
+ emailValidator,
38
+ numberValidator,
39
+ phoneValidator,
40
+ minLengthValidator,
41
+ maxLengthValidator,
42
+ patternValidator,
43
+ } from './validation';
44
+ export type { UseValidationResult } from './validation';
45
+
46
+ // Icons (in case consumers want to reuse FieldFlow's built-in icon set elsewhere)
47
+ export { FieldFlowIcon } from './icons';
48
+ export type { FieldFlowIconName, FieldFlowIconProps } from './icons';
49
+
50
+ // Types
51
+ export type {
52
+ FieldFlowFieldType,
53
+ FieldFlowState,
54
+ FieldFlowRules,
55
+ ValidationResult,
56
+ Validator,
57
+ FieldFlowColors,
58
+ FieldFlowSpacing,
59
+ FieldFlowTypography,
60
+ FieldFlowAnimationConfig,
61
+ FieldFlowTheme,
62
+ DeepPartial,
63
+ } from './types';
@@ -0,0 +1,49 @@
1
+ import React, { createContext, useContext, useMemo } from 'react';
2
+ import type { DeepPartial, FieldFlowTheme } from '../types';
3
+ import { defaultTheme } from './defaultTheme';
4
+
5
+ const FieldFlowThemeContext = createContext<FieldFlowTheme>(defaultTheme);
6
+
7
+ function mergeDeep<T extends Record<string, any>>(base: T, override?: DeepPartial<T>): T {
8
+ if (!override) return base;
9
+ const result: any = { ...base };
10
+ for (const key of Object.keys(override)) {
11
+ const overrideValue = (override as any)[key];
12
+ const baseValue = (base as any)[key];
13
+ if (
14
+ overrideValue &&
15
+ typeof overrideValue === 'object' &&
16
+ !Array.isArray(overrideValue) &&
17
+ baseValue &&
18
+ typeof baseValue === 'object'
19
+ ) {
20
+ result[key] = mergeDeep(baseValue, overrideValue);
21
+ } else if (overrideValue !== undefined) {
22
+ result[key] = overrideValue;
23
+ }
24
+ }
25
+ return result;
26
+ }
27
+
28
+ export interface FieldFlowThemeProviderProps {
29
+ theme?: DeepPartial<FieldFlowTheme>;
30
+ children: React.ReactNode;
31
+ }
32
+
33
+ /**
34
+ * Wrap your app (or a screen/form) in this provider to apply a custom theme.
35
+ * Any field not covered by your overrides falls back to FieldFlow's defaults,
36
+ * so you only need to specify what you want to change.
37
+ */
38
+ export function FieldFlowThemeProvider({ theme, children }: FieldFlowThemeProviderProps) {
39
+ const merged = useMemo(() => mergeDeep(defaultTheme, theme), [theme]);
40
+ return (
41
+ <FieldFlowThemeContext.Provider value={merged}>{children}</FieldFlowThemeContext.Provider>
42
+ );
43
+ }
44
+
45
+ export function useFieldFlowTheme(): FieldFlowTheme {
46
+ return useContext(FieldFlowThemeContext);
47
+ }
48
+
49
+ export { mergeDeep };
@@ -0,0 +1,58 @@
1
+ import type { FieldFlowTheme } from '../types';
2
+
3
+ export const defaultTheme: FieldFlowTheme = {
4
+ colors: {
5
+ background: '#FFFFFF',
6
+ backgroundDisabled: '#F2F3F5',
7
+ border: '#D8DCE2',
8
+ borderFocused: '#3B82F6',
9
+ borderError: '#EF4444',
10
+ borderSuccess: '#22C55E',
11
+ text: '#111827',
12
+ placeholder: '#9CA3AF',
13
+ label: '#374151',
14
+ labelFocused: '#3B82F6',
15
+ error: '#EF4444',
16
+ success: '#22C55E',
17
+ icon: '#6B7280',
18
+ iconFocused: '#3B82F6',
19
+ },
20
+ spacing: {
21
+ paddingHorizontal: 14,
22
+ paddingVertical: 12,
23
+ gap: 6,
24
+ borderRadius: 10,
25
+ borderWidth: 1,
26
+ borderWidthFocused: 1.5,
27
+ },
28
+ typography: {
29
+ fontSize: 15,
30
+ labelFontSize: 13,
31
+ helperFontSize: 12,
32
+ fontWeight: '400',
33
+ },
34
+ animation: {
35
+ duration: 160,
36
+ useNativeDriver: false,
37
+ },
38
+ };
39
+
40
+ export const darkTheme: FieldFlowTheme = {
41
+ ...defaultTheme,
42
+ colors: {
43
+ background: '#1F2329',
44
+ backgroundDisabled: '#15171B',
45
+ border: '#3A3F47',
46
+ borderFocused: '#60A5FA',
47
+ borderError: '#F87171',
48
+ borderSuccess: '#4ADE80',
49
+ text: '#F3F4F6',
50
+ placeholder: '#6B7280',
51
+ label: '#D1D5DB',
52
+ labelFocused: '#60A5FA',
53
+ error: '#F87171',
54
+ success: '#4ADE80',
55
+ icon: '#9CA3AF',
56
+ iconFocused: '#60A5FA',
57
+ },
58
+ };
@@ -0,0 +1,3 @@
1
+ export { defaultTheme, darkTheme } from './defaultTheme';
2
+ export { FieldFlowThemeProvider, useFieldFlowTheme, mergeDeep } from './ThemeProvider';
3
+ export type { FieldFlowThemeProviderProps } from './ThemeProvider';
@@ -0,0 +1,98 @@
1
+ import type { KeyboardTypeOptions, TextInputProps, TextStyle } from 'react-native';
2
+
3
+ /** Every field "flavor" the core input understands out of the box. */
4
+ export type FieldFlowFieldType =
5
+ | 'text'
6
+ | 'email'
7
+ | 'number'
8
+ | 'decimal'
9
+ | 'phone'
10
+ | 'password'
11
+ | 'search'
12
+ | 'multiline'
13
+ | 'description'
14
+ | 'chat';
15
+
16
+ export type FieldFlowState =
17
+ | 'idle'
18
+ | 'focused'
19
+ | 'error'
20
+ | 'success'
21
+ | 'disabled'
22
+ | 'readonly'
23
+ | 'loading';
24
+
25
+ export interface ValidationResult {
26
+ valid: boolean;
27
+ message?: string;
28
+ }
29
+
30
+ export type Validator = (value: string) => ValidationResult;
31
+
32
+ export interface FieldFlowRules {
33
+ required?: boolean | string;
34
+ minLength?: number | { value: number; message?: string };
35
+ maxLength?: number | { value: number; message?: string };
36
+ pattern?: RegExp | { value: RegExp; message?: string };
37
+ email?: boolean | string;
38
+ number?: boolean | string;
39
+ custom?: Validator | Validator[];
40
+ }
41
+
42
+ export interface FieldFlowColors {
43
+ background: string;
44
+ backgroundDisabled: string;
45
+ border: string;
46
+ borderFocused: string;
47
+ borderError: string;
48
+ borderSuccess: string;
49
+ text: string;
50
+ placeholder: string;
51
+ label: string;
52
+ labelFocused: string;
53
+ error: string;
54
+ success: string;
55
+ icon: string;
56
+ iconFocused: string;
57
+ }
58
+
59
+ export interface FieldFlowSpacing {
60
+ paddingHorizontal: number;
61
+ paddingVertical: number;
62
+ gap: number;
63
+ borderRadius: number;
64
+ borderWidth: number;
65
+ borderWidthFocused: number;
66
+ }
67
+
68
+ export interface FieldFlowTypography {
69
+ fontSize: number;
70
+ labelFontSize: number;
71
+ helperFontSize: number;
72
+ fontFamily?: string;
73
+ fontWeight: TextStyle['fontWeight'];
74
+ }
75
+
76
+ export interface FieldFlowAnimationConfig {
77
+ duration: number;
78
+ useNativeDriver: boolean;
79
+ }
80
+
81
+ export interface FieldFlowTheme {
82
+ colors: FieldFlowColors;
83
+ spacing: FieldFlowSpacing;
84
+ typography: FieldFlowTypography;
85
+ animation: FieldFlowAnimationConfig;
86
+ }
87
+
88
+ export type DeepPartial<T> = {
89
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
90
+ };
91
+
92
+ export interface FieldFlowFieldConfigByType {
93
+ keyboardType: KeyboardTypeOptions;
94
+ secureTextEntry: boolean;
95
+ autoCapitalize: TextInputProps['autoCapitalize'];
96
+ autoCorrect: boolean;
97
+ multiline: boolean;
98
+ }
@@ -0,0 +1,18 @@
1
+ /** Strips everything except digits, a single leading "+", and "." for decimals. */
2
+ export function sanitizeNumeric(value: string, allowDecimal = false): string {
3
+ if (allowDecimal) {
4
+ const cleaned = value.replace(/[^0-9.]/g, '');
5
+ const [head, ...rest] = cleaned.split('.');
6
+ return rest.length ? `${head}.${rest.join('')}` : head;
7
+ }
8
+ return value.replace(/[^0-9]/g, '');
9
+ }
10
+
11
+ export function sanitizePhone(value: string): string {
12
+ return value.replace(/[^0-9+()\-\s]/g, '');
13
+ }
14
+
15
+ export function truncate(value: string, maxLength?: number): string {
16
+ if (!maxLength) return value;
17
+ return value.length > maxLength ? value.slice(0, maxLength) : value;
18
+ }
@@ -0,0 +1,85 @@
1
+ import { Platform } from 'react-native';
2
+ import type { FieldFlowFieldConfigByType, FieldFlowFieldType } from '../types';
3
+
4
+ /**
5
+ * Central place that decides "if fieldType is X, the input should behave like Y".
6
+ * This is what powers the "just set type='email' and everything is handled" behavior.
7
+ */
8
+ export function resolveFieldConfig(type: FieldFlowFieldType): FieldFlowFieldConfigByType {
9
+ switch (type) {
10
+ case 'email':
11
+ return {
12
+ keyboardType: 'email-address',
13
+ secureTextEntry: false,
14
+ autoCapitalize: 'none',
15
+ autoCorrect: false,
16
+ multiline: false,
17
+ };
18
+ case 'number':
19
+ return {
20
+ keyboardType: Platform.select({ ios: 'number-pad', android: 'numeric', default: 'numeric' }),
21
+ secureTextEntry: false,
22
+ autoCapitalize: 'none',
23
+ autoCorrect: false,
24
+ multiline: false,
25
+ };
26
+ case 'decimal':
27
+ return {
28
+ keyboardType: Platform.select({ ios: 'decimal-pad', android: 'numeric', default: 'decimal-pad' }),
29
+ secureTextEntry: false,
30
+ autoCapitalize: 'none',
31
+ autoCorrect: false,
32
+ multiline: false,
33
+ };
34
+ case 'phone':
35
+ return {
36
+ keyboardType: 'phone-pad',
37
+ secureTextEntry: false,
38
+ autoCapitalize: 'none',
39
+ autoCorrect: false,
40
+ multiline: false,
41
+ };
42
+ case 'password':
43
+ return {
44
+ keyboardType: 'default',
45
+ secureTextEntry: true,
46
+ autoCapitalize: 'none',
47
+ autoCorrect: false,
48
+ multiline: false,
49
+ };
50
+ case 'search':
51
+ return {
52
+ keyboardType: 'default',
53
+ secureTextEntry: false,
54
+ autoCapitalize: 'none',
55
+ autoCorrect: false,
56
+ multiline: false,
57
+ };
58
+ case 'multiline':
59
+ case 'description':
60
+ case 'chat':
61
+ return {
62
+ keyboardType: 'default',
63
+ secureTextEntry: false,
64
+ autoCapitalize: 'sentences',
65
+ autoCorrect: true,
66
+ multiline: true,
67
+ };
68
+ case 'text':
69
+ default:
70
+ return {
71
+ keyboardType: 'default',
72
+ secureTextEntry: false,
73
+ autoCapitalize: 'sentences',
74
+ autoCorrect: true,
75
+ multiline: false,
76
+ };
77
+ }
78
+ }
79
+
80
+ /** Returns sensible default validation rules implied purely by the field type (before user overrides). */
81
+ export function implicitRulesForType(type: FieldFlowFieldType) {
82
+ if (type === 'email') return { email: true as const };
83
+ if (type === 'number' || type === 'decimal') return { number: true as const };
84
+ return {};
85
+ }
@@ -0,0 +1,15 @@
1
+ export {
2
+ isValidEmail,
3
+ isValidNumber,
4
+ isValidPhone,
5
+ requiredValidator,
6
+ emailValidator,
7
+ numberValidator,
8
+ phoneValidator,
9
+ minLengthValidator,
10
+ maxLengthValidator,
11
+ patternValidator,
12
+ runRules,
13
+ } from './validators';
14
+ export { useValidation } from './useValidation';
15
+ export type { UseValidationResult } from './useValidation';
@@ -0,0 +1,37 @@
1
+ import { useCallback, useState } from 'react';
2
+ import type { FieldFlowRules } from '../types';
3
+ import { runRules } from './validators';
4
+
5
+ export interface UseValidationResult {
6
+ error: string | null;
7
+ touched: boolean;
8
+ validate: (value: string) => boolean;
9
+ markTouched: () => void;
10
+ reset: () => void;
11
+ }
12
+
13
+ /**
14
+ * Lightweight per-field validation hook for use outside of `useFieldFlowForm`,
15
+ * e.g. when a single input is validated on its own.
16
+ */
17
+ export function useValidation(rules?: FieldFlowRules): UseValidationResult {
18
+ const [error, setError] = useState<string | null>(null);
19
+ const [touched, setTouched] = useState(false);
20
+
21
+ const validate = useCallback(
22
+ (value: string) => {
23
+ const result = runRules(value, rules);
24
+ setError(result.valid ? null : result.message ?? 'Invalid value');
25
+ return result.valid;
26
+ },
27
+ [rules],
28
+ );
29
+
30
+ const markTouched = useCallback(() => setTouched(true), []);
31
+ const reset = useCallback(() => {
32
+ setError(null);
33
+ setTouched(false);
34
+ }, []);
35
+
36
+ return { error, touched, validate, markTouched, reset };
37
+ }
@@ -0,0 +1,93 @@
1
+ import type { FieldFlowRules, ValidationResult, Validator } from '../types';
2
+
3
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
4
+ const NUMBER_REGEX = /^-?\d+(\.\d+)?$/;
5
+ const PHONE_REGEX = /^[+]?[\d\s()-]{7,20}$/;
6
+
7
+ export const isValidEmail = (value: string): boolean => EMAIL_REGEX.test(value.trim());
8
+ export const isValidNumber = (value: string): boolean => NUMBER_REGEX.test(value.trim());
9
+ export const isValidPhone = (value: string): boolean => PHONE_REGEX.test(value.trim());
10
+
11
+ export const requiredValidator =
12
+ (message = 'This field is required'): Validator =>
13
+ value =>
14
+ value !== undefined && value !== null && value.trim().length > 0
15
+ ? { valid: true }
16
+ : { valid: false, message };
17
+
18
+ export const emailValidator =
19
+ (message = 'Enter a valid email address'): Validator =>
20
+ value =>
21
+ value.length === 0 || isValidEmail(value) ? { valid: true } : { valid: false, message };
22
+
23
+ export const numberValidator =
24
+ (message = 'Enter a valid number'): Validator =>
25
+ value =>
26
+ value.length === 0 || isValidNumber(value) ? { valid: true } : { valid: false, message };
27
+
28
+ export const phoneValidator =
29
+ (message = 'Enter a valid phone number'): Validator =>
30
+ value =>
31
+ value.length === 0 || isValidPhone(value) ? { valid: true } : { valid: false, message };
32
+
33
+ export const minLengthValidator =
34
+ (min: number, message?: string): Validator =>
35
+ value =>
36
+ value.length === 0 || value.length >= min
37
+ ? { valid: true }
38
+ : { valid: false, message: message ?? `Must be at least ${min} characters` };
39
+
40
+ export const maxLengthValidator =
41
+ (max: number, message?: string): Validator =>
42
+ value =>
43
+ value.length <= max ? { valid: true } : { valid: false, message: message ?? `Must be at most ${max} characters` };
44
+
45
+ export const patternValidator =
46
+ (pattern: RegExp, message = 'Invalid format'): Validator =>
47
+ value =>
48
+ value.length === 0 || pattern.test(value) ? { valid: true } : { valid: false, message };
49
+
50
+ /**
51
+ * Turns a declarative `FieldFlowRules` object into an ordered list of validators
52
+ * and runs them against a value, stopping at (and returning) the first failure.
53
+ */
54
+ export function runRules(value: string, rules?: FieldFlowRules): ValidationResult {
55
+ if (!rules) return { valid: true };
56
+
57
+ const validators: Validator[] = [];
58
+
59
+ if (rules.required) {
60
+ const message = typeof rules.required === 'string' ? rules.required : undefined;
61
+ validators.push(requiredValidator(message));
62
+ }
63
+ if (rules.email) {
64
+ const message = typeof rules.email === 'string' ? rules.email : undefined;
65
+ validators.push(emailValidator(message));
66
+ }
67
+ if (rules.number) {
68
+ const message = typeof rules.number === 'string' ? rules.number : undefined;
69
+ validators.push(numberValidator(message));
70
+ }
71
+ if (rules.minLength !== undefined) {
72
+ const cfg = typeof rules.minLength === 'number' ? { value: rules.minLength } : rules.minLength;
73
+ validators.push(minLengthValidator(cfg.value, cfg.message));
74
+ }
75
+ if (rules.maxLength !== undefined) {
76
+ const cfg = typeof rules.maxLength === 'number' ? { value: rules.maxLength } : rules.maxLength;
77
+ validators.push(maxLengthValidator(cfg.value, cfg.message));
78
+ }
79
+ if (rules.pattern) {
80
+ const cfg = rules.pattern instanceof RegExp ? { value: rules.pattern } : rules.pattern;
81
+ validators.push(patternValidator(cfg.value, cfg.message));
82
+ }
83
+ if (rules.custom) {
84
+ const customs = Array.isArray(rules.custom) ? rules.custom : [rules.custom];
85
+ validators.push(...customs);
86
+ }
87
+
88
+ for (const validate of validators) {
89
+ const result = validate(value ?? '');
90
+ if (!result.valid) return result;
91
+ }
92
+ return { valid: true };
93
+ }