react-native-mantine 0.7.0 → 0.9.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 +37 -5
- package/lib/commonjs/components/Group/index.js.map +1 -1
- package/lib/commonjs/components/Radio/index.js +62 -6
- package/lib/commonjs/components/Radio/index.js.map +1 -1
- package/lib/commonjs/components/Switch/index.js +6 -2
- package/lib/commonjs/components/Switch/index.js.map +1 -1
- package/lib/commonjs/hooks/index.js +28 -0
- package/lib/commonjs/hooks/index.js.map +1 -0
- package/lib/commonjs/hooks/use-form/index.js +40 -0
- package/lib/commonjs/hooks/use-form/index.js.map +1 -0
- package/lib/commonjs/hooks/use-form/types.js +2 -0
- package/lib/commonjs/hooks/use-form/types.js.map +1 -0
- package/lib/commonjs/hooks/use-form/useForm.js +418 -0
- package/lib/commonjs/hooks/use-form/useForm.js.map +1 -0
- package/lib/commonjs/hooks/use-form/validators.js +135 -0
- package/lib/commonjs/hooks/use-form/validators.js.map +1 -0
- package/lib/commonjs/index.js +11 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/components/Group/index.js.map +1 -1
- package/lib/module/components/Radio/index.js +64 -8
- package/lib/module/components/Radio/index.js.map +1 -1
- package/lib/module/components/Switch/index.js +6 -2
- package/lib/module/components/Switch/index.js.map +1 -1
- package/lib/module/hooks/index.js +5 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/use-form/index.js +6 -0
- package/lib/module/hooks/use-form/index.js.map +1 -0
- package/lib/module/hooks/use-form/types.js +2 -0
- package/lib/module/hooks/use-form/types.js.map +1 -0
- package/lib/module/hooks/use-form/useForm.js +414 -0
- package/lib/module/hooks/use-form/useForm.js.map +1 -0
- package/lib/module/hooks/use-form/validators.js +122 -0
- package/lib/module/hooks/use-form/validators.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/components/Group/index.d.ts +1 -0
- package/lib/typescript/commonjs/src/components/Group/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Radio/index.d.ts +22 -1
- package/lib/typescript/commonjs/src/components/Radio/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/components/Switch/index.d.ts +3 -3
- package/lib/typescript/commonjs/src/components/Switch/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/hooks/index.d.ts +3 -0
- package/lib/typescript/commonjs/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/use-form/index.d.ts +4 -0
- package/lib/typescript/commonjs/src/hooks/use-form/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/use-form/types.d.ts +119 -0
- package/lib/typescript/commonjs/src/hooks/use-form/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/use-form/useForm.d.ts +30 -0
- package/lib/typescript/commonjs/src/hooks/use-form/useForm.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/hooks/use-form/validators.d.ts +41 -0
- package/lib/typescript/commonjs/src/hooks/use-form/validators.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +1 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Group/index.d.ts +1 -0
- package/lib/typescript/module/src/components/Group/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Radio/index.d.ts +22 -1
- package/lib/typescript/module/src/components/Radio/index.d.ts.map +1 -1
- package/lib/typescript/module/src/components/Switch/index.d.ts +3 -3
- package/lib/typescript/module/src/components/Switch/index.d.ts.map +1 -1
- package/lib/typescript/module/src/hooks/index.d.ts +3 -0
- package/lib/typescript/module/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/use-form/index.d.ts +4 -0
- package/lib/typescript/module/src/hooks/use-form/index.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/use-form/types.d.ts +119 -0
- package/lib/typescript/module/src/hooks/use-form/types.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/use-form/useForm.d.ts +30 -0
- package/lib/typescript/module/src/hooks/use-form/useForm.d.ts.map +1 -0
- package/lib/typescript/module/src/hooks/use-form/validators.d.ts +41 -0
- package/lib/typescript/module/src/hooks/use-form/validators.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +1 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Group/index.tsx +1 -0
- package/src/components/Radio/index.tsx +99 -8
- package/src/components/Switch/index.tsx +10 -6
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-form/index.ts +3 -0
- package/src/hooks/use-form/types.ts +169 -0
- package/src/hooks/use-form/useForm.ts +436 -0
- package/src/hooks/use-form/validators.ts +143 -0
- package/src/index.tsx +1 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
FormErrors,
|
|
4
|
+
FormFieldStatus,
|
|
5
|
+
FormInputProps,
|
|
6
|
+
FormSubmitHandler,
|
|
7
|
+
FormTouched,
|
|
8
|
+
FormDirty,
|
|
9
|
+
FormValidationRules,
|
|
10
|
+
UseFormInput,
|
|
11
|
+
UseFormReturnType,
|
|
12
|
+
} from './types';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validates a single field value against validation rules
|
|
16
|
+
*/
|
|
17
|
+
function validateFieldValue<Values>(
|
|
18
|
+
field: keyof Values,
|
|
19
|
+
value: any,
|
|
20
|
+
rules?: FormValidationRules<Values>
|
|
21
|
+
): string | null {
|
|
22
|
+
if (!rules || !rules[field]) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const validators = rules[field];
|
|
27
|
+
if (!validators) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle single validator
|
|
32
|
+
if (typeof validators === 'function') {
|
|
33
|
+
return validators(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Handle array of validators
|
|
37
|
+
if (Array.isArray(validators)) {
|
|
38
|
+
for (const validator of validators) {
|
|
39
|
+
const error = validator(value);
|
|
40
|
+
if (error) {
|
|
41
|
+
return error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validates all form values
|
|
51
|
+
*/
|
|
52
|
+
function validateAllValues<Values>(
|
|
53
|
+
values: Values,
|
|
54
|
+
rules?: FormValidationRules<Values>
|
|
55
|
+
): FormErrors<Values> {
|
|
56
|
+
if (!rules) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const errors: FormErrors<Values> = {};
|
|
61
|
+
|
|
62
|
+
for (const field in rules) {
|
|
63
|
+
const error = validateFieldValue(field, values[field], rules);
|
|
64
|
+
if (error) {
|
|
65
|
+
errors[field] = error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return errors;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Form state management hook for React Native
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```tsx
|
|
77
|
+
* const form = useForm({
|
|
78
|
+
* initialValues: {
|
|
79
|
+
* email: '',
|
|
80
|
+
* password: '',
|
|
81
|
+
* },
|
|
82
|
+
* validate: {
|
|
83
|
+
* email: isEmail('Invalid email'),
|
|
84
|
+
* password: minLength(6, 'Password must be at least 6 characters'),
|
|
85
|
+
* },
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* return (
|
|
89
|
+
* <View>
|
|
90
|
+
* <TextInput {...form.getInputProps('email')} />
|
|
91
|
+
* <TextInput {...form.getInputProps('password')} />
|
|
92
|
+
* <Button onPress={form.onSubmit((values) => console.log(values))}>
|
|
93
|
+
* Submit
|
|
94
|
+
* </Button>
|
|
95
|
+
* </View>
|
|
96
|
+
* );
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function useForm<Values extends Record<string, any> = Record<string, any>>({
|
|
100
|
+
initialValues = {} as Values,
|
|
101
|
+
initialErrors = {},
|
|
102
|
+
initialTouched = {},
|
|
103
|
+
initialDirty = {},
|
|
104
|
+
validate: validationRules,
|
|
105
|
+
clearInputErrorOnChange = true,
|
|
106
|
+
validateInputOnChange = false,
|
|
107
|
+
validateInputOnBlur = false,
|
|
108
|
+
transformValues = (values: Values) => values,
|
|
109
|
+
}: UseFormInput<Values> = {}): UseFormReturnType<Values> {
|
|
110
|
+
// Form state
|
|
111
|
+
const [values, setValuesState] = useState<Values>(initialValues);
|
|
112
|
+
const [errors, setErrorsState] = useState<FormErrors<Values>>(initialErrors);
|
|
113
|
+
const [touched, setTouchedState] = useState<FormTouched<Values>>(initialTouched);
|
|
114
|
+
const [dirty, setDirtyState] = useState<FormDirty<Values>>(initialDirty);
|
|
115
|
+
|
|
116
|
+
// Keep track of initial values for reset
|
|
117
|
+
const initialValuesRef = useRef<Values>(initialValues);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Set a single field value
|
|
121
|
+
*/
|
|
122
|
+
const setFieldValue = useCallback(
|
|
123
|
+
<K extends keyof Values>(field: K, value: Values[K]) => {
|
|
124
|
+
setValuesState((prev) => ({ ...prev, [field]: value }));
|
|
125
|
+
setDirtyState((prev) => ({ ...prev, [field]: true }));
|
|
126
|
+
|
|
127
|
+
// Validate on change if enabled
|
|
128
|
+
if (validateInputOnChange && validationRules) {
|
|
129
|
+
const error = validateFieldValue(field, value, validationRules);
|
|
130
|
+
if (error) {
|
|
131
|
+
setErrorsState((prev) => ({ ...prev, [field]: error }));
|
|
132
|
+
} else {
|
|
133
|
+
setErrorsState((prev) => {
|
|
134
|
+
const newErrors = { ...prev };
|
|
135
|
+
delete newErrors[field];
|
|
136
|
+
return newErrors;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
} else if (clearInputErrorOnChange) {
|
|
140
|
+
// Clear error on change if enabled
|
|
141
|
+
setErrorsState((prev) => {
|
|
142
|
+
const newErrors = { ...prev };
|
|
143
|
+
delete newErrors[field];
|
|
144
|
+
return newErrors;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
[validateInputOnChange, clearInputErrorOnChange, validationRules]
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Set multiple field values
|
|
153
|
+
*/
|
|
154
|
+
const setValues = useCallback((newValues: Partial<Values>) => {
|
|
155
|
+
setValuesState((prev) => ({ ...prev, ...newValues }));
|
|
156
|
+
|
|
157
|
+
// Mark all updated fields as dirty
|
|
158
|
+
const dirtyFields: FormDirty<Values> = {};
|
|
159
|
+
for (const key in newValues) {
|
|
160
|
+
dirtyFields[key] = true;
|
|
161
|
+
}
|
|
162
|
+
setDirtyState((prev) => ({ ...prev, ...dirtyFields }));
|
|
163
|
+
|
|
164
|
+
if (clearInputErrorOnChange) {
|
|
165
|
+
setErrorsState((prev) => {
|
|
166
|
+
const newErrors = { ...prev };
|
|
167
|
+
for (const key in newValues) {
|
|
168
|
+
delete newErrors[key];
|
|
169
|
+
}
|
|
170
|
+
return newErrors;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}, [clearInputErrorOnChange]);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Set a single field error
|
|
177
|
+
*/
|
|
178
|
+
const setFieldError = useCallback(
|
|
179
|
+
<K extends keyof Values>(field: K, error: string | null) => {
|
|
180
|
+
if (error === null) {
|
|
181
|
+
setErrorsState((prev) => {
|
|
182
|
+
const newErrors = { ...prev };
|
|
183
|
+
delete newErrors[field];
|
|
184
|
+
return newErrors;
|
|
185
|
+
});
|
|
186
|
+
} else {
|
|
187
|
+
setErrorsState((prev) => ({ ...prev, [field]: error }));
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
[]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Set multiple errors
|
|
195
|
+
*/
|
|
196
|
+
const setErrors = useCallback((newErrors: FormErrors<Values>) => {
|
|
197
|
+
setErrorsState(newErrors);
|
|
198
|
+
}, []);
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Clear a specific field error
|
|
202
|
+
*/
|
|
203
|
+
const clearFieldError = useCallback(<K extends keyof Values>(field: K) => {
|
|
204
|
+
setErrorsState((prev) => {
|
|
205
|
+
const newErrors = { ...prev };
|
|
206
|
+
delete newErrors[field];
|
|
207
|
+
return newErrors;
|
|
208
|
+
});
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Clear all errors
|
|
213
|
+
*/
|
|
214
|
+
const clearErrors = useCallback(() => {
|
|
215
|
+
setErrorsState({});
|
|
216
|
+
}, []);
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Reset form to initial values
|
|
220
|
+
*/
|
|
221
|
+
const reset = useCallback(() => {
|
|
222
|
+
setValuesState(initialValuesRef.current);
|
|
223
|
+
setErrorsState({});
|
|
224
|
+
setTouchedState({});
|
|
225
|
+
setDirtyState({});
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Validate a specific field
|
|
230
|
+
*/
|
|
231
|
+
const validateField = useCallback(
|
|
232
|
+
<K extends keyof Values>(field: K): boolean => {
|
|
233
|
+
if (!validationRules) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const error = validateFieldValue(field, values[field], validationRules);
|
|
238
|
+
if (error) {
|
|
239
|
+
setFieldError(field, error);
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
clearFieldError(field);
|
|
244
|
+
return true;
|
|
245
|
+
},
|
|
246
|
+
[values, validationRules, setFieldError, clearFieldError]
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Validate entire form
|
|
251
|
+
*/
|
|
252
|
+
const validate = useCallback((): boolean => {
|
|
253
|
+
if (!validationRules) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const newErrors = validateAllValues(values, validationRules);
|
|
258
|
+
setErrorsState(newErrors);
|
|
259
|
+
return Object.keys(newErrors).length === 0;
|
|
260
|
+
}, [values, validationRules]);
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Check if form is valid
|
|
264
|
+
*/
|
|
265
|
+
const isValid = useCallback((): boolean => {
|
|
266
|
+
if (!validationRules) {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const validationErrors = validateAllValues(values, validationRules);
|
|
271
|
+
return Object.keys(validationErrors).length === 0;
|
|
272
|
+
}, [values, validationRules]);
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Check if form is dirty
|
|
276
|
+
*/
|
|
277
|
+
const isDirty = useCallback((): boolean => {
|
|
278
|
+
return Object.values(dirty).some((isDirty) => isDirty === true);
|
|
279
|
+
}, [dirty]);
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get field status
|
|
283
|
+
*/
|
|
284
|
+
const getFieldStatus = useCallback(
|
|
285
|
+
<K extends keyof Values>(field: K): FormFieldStatus => {
|
|
286
|
+
return {
|
|
287
|
+
hasError: !!errors[field],
|
|
288
|
+
isTouched: !!touched[field],
|
|
289
|
+
isDirty: !!dirty[field],
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
[errors, touched, dirty]
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Mark field as touched
|
|
297
|
+
*/
|
|
298
|
+
const setFieldTouched = useCallback(
|
|
299
|
+
<K extends keyof Values>(field: K, isTouched = true) => {
|
|
300
|
+
setTouchedState((prev) => ({ ...prev, [field]: isTouched }));
|
|
301
|
+
|
|
302
|
+
// Validate on blur if enabled
|
|
303
|
+
if (isTouched && validateInputOnBlur && validationRules) {
|
|
304
|
+
const error = validateFieldValue(field, values[field], validationRules);
|
|
305
|
+
if (error) {
|
|
306
|
+
setFieldError(field, error);
|
|
307
|
+
} else {
|
|
308
|
+
clearFieldError(field);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
[validateInputOnBlur, validationRules, values, setFieldError, clearFieldError]
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Reset touched state
|
|
317
|
+
*/
|
|
318
|
+
const resetTouched = useCallback(() => {
|
|
319
|
+
setTouchedState({});
|
|
320
|
+
}, []);
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Reset dirty state
|
|
324
|
+
*/
|
|
325
|
+
const resetDirty = useCallback(() => {
|
|
326
|
+
setDirtyState({});
|
|
327
|
+
}, []);
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get props to spread on input component
|
|
331
|
+
*/
|
|
332
|
+
const getInputProps = useCallback(
|
|
333
|
+
<K extends keyof Values>(
|
|
334
|
+
field: K,
|
|
335
|
+
options: {
|
|
336
|
+
type?: 'input' | 'checkbox' | 'radio' | 'select';
|
|
337
|
+
withError?: boolean;
|
|
338
|
+
withFocus?: boolean;
|
|
339
|
+
} = {}
|
|
340
|
+
): FormInputProps<Values[K]> => {
|
|
341
|
+
const { type = 'input', withError = true, withFocus = true } = options;
|
|
342
|
+
|
|
343
|
+
const baseProps: FormInputProps<Values[K]> = {
|
|
344
|
+
value: values[field],
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// Add error if needed
|
|
348
|
+
if (withError && errors[field]) {
|
|
349
|
+
baseProps.error = errors[field];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Add blur handler if focus tracking is enabled
|
|
353
|
+
if (withFocus) {
|
|
354
|
+
baseProps.onBlur = () => {
|
|
355
|
+
setFieldTouched(field, true);
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Handle different input types
|
|
360
|
+
if (type === 'checkbox') {
|
|
361
|
+
return {
|
|
362
|
+
...baseProps,
|
|
363
|
+
onChange: (value: Values[K]) => {
|
|
364
|
+
setFieldValue(field, value);
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (type === 'radio' || type === 'select') {
|
|
370
|
+
return {
|
|
371
|
+
...baseProps,
|
|
372
|
+
onChange: (value: Values[K]) => {
|
|
373
|
+
setFieldValue(field, value);
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Default text input
|
|
379
|
+
return {
|
|
380
|
+
...baseProps,
|
|
381
|
+
onChangeText: (text: string) => {
|
|
382
|
+
setFieldValue(field, text as any);
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
},
|
|
386
|
+
[values, errors, setFieldValue, setFieldTouched]
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Handle form submit
|
|
391
|
+
*/
|
|
392
|
+
const onSubmit = useCallback(
|
|
393
|
+
(handleSubmit: FormSubmitHandler<Values>) => {
|
|
394
|
+
return async (event?: any) => {
|
|
395
|
+
event?.preventDefault?.();
|
|
396
|
+
|
|
397
|
+
// Validate form
|
|
398
|
+
const isFormValid = validate();
|
|
399
|
+
if (!isFormValid) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Transform values if needed
|
|
404
|
+
const transformedValues = transformValues(values);
|
|
405
|
+
|
|
406
|
+
// Call submit handler
|
|
407
|
+
await handleSubmit(transformedValues, event);
|
|
408
|
+
};
|
|
409
|
+
},
|
|
410
|
+
[validate, values, transformValues]
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
values,
|
|
415
|
+
errors,
|
|
416
|
+
touched,
|
|
417
|
+
dirty,
|
|
418
|
+
setFieldValue,
|
|
419
|
+
setValues,
|
|
420
|
+
setFieldError,
|
|
421
|
+
setErrors,
|
|
422
|
+
clearFieldError,
|
|
423
|
+
clearErrors,
|
|
424
|
+
reset,
|
|
425
|
+
validateField,
|
|
426
|
+
validate,
|
|
427
|
+
isValid,
|
|
428
|
+
isDirty,
|
|
429
|
+
getFieldStatus,
|
|
430
|
+
getInputProps,
|
|
431
|
+
onSubmit,
|
|
432
|
+
resetTouched,
|
|
433
|
+
resetDirty,
|
|
434
|
+
setFieldTouched,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { FormValidator } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Built-in validators
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Validates that a value is not empty
|
|
9
|
+
*/
|
|
10
|
+
export const isNotEmpty = (message = 'This field is required'): FormValidator<any> => {
|
|
11
|
+
return (value: any) => {
|
|
12
|
+
if (value === null || value === undefined || value === '') {
|
|
13
|
+
return message;
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
16
|
+
return message;
|
|
17
|
+
}
|
|
18
|
+
if (typeof value === 'string' && value.trim().length === 0) {
|
|
19
|
+
return message;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validates minimum length
|
|
27
|
+
*/
|
|
28
|
+
export const minLength = (
|
|
29
|
+
min: number,
|
|
30
|
+
message?: string
|
|
31
|
+
): FormValidator<string> => {
|
|
32
|
+
return (value: string) => {
|
|
33
|
+
if (!value) return null;
|
|
34
|
+
if (value.length < min) {
|
|
35
|
+
return message || `Must be at least ${min} characters`;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validates maximum length
|
|
43
|
+
*/
|
|
44
|
+
export const maxLength = (
|
|
45
|
+
max: number,
|
|
46
|
+
message?: string
|
|
47
|
+
): FormValidator<string> => {
|
|
48
|
+
return (value: string) => {
|
|
49
|
+
if (!value) return null;
|
|
50
|
+
if (value.length > max) {
|
|
51
|
+
return message || `Must be at most ${max} characters`;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validates that value matches a pattern
|
|
59
|
+
*/
|
|
60
|
+
export const matches = (
|
|
61
|
+
pattern: RegExp,
|
|
62
|
+
message = 'Invalid format'
|
|
63
|
+
): FormValidator<string> => {
|
|
64
|
+
return (value: string) => {
|
|
65
|
+
if (!value) return null;
|
|
66
|
+
if (!pattern.test(value)) {
|
|
67
|
+
return message;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validates email format
|
|
75
|
+
*/
|
|
76
|
+
export const isEmail = (message = 'Invalid email'): FormValidator<string> => {
|
|
77
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
78
|
+
return matches(emailRegex, message);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Validates minimum value
|
|
83
|
+
*/
|
|
84
|
+
export const min = (
|
|
85
|
+
minValue: number,
|
|
86
|
+
message?: string
|
|
87
|
+
): FormValidator<number> => {
|
|
88
|
+
return (value: number) => {
|
|
89
|
+
if (value === null || value === undefined) return null;
|
|
90
|
+
if (value < minValue) {
|
|
91
|
+
return message || `Must be at least ${minValue}`;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validates maximum value
|
|
99
|
+
*/
|
|
100
|
+
export const max = (
|
|
101
|
+
maxValue: number,
|
|
102
|
+
message?: string
|
|
103
|
+
): FormValidator<number> => {
|
|
104
|
+
return (value: number) => {
|
|
105
|
+
if (value === null || value === undefined) return null;
|
|
106
|
+
if (value > maxValue) {
|
|
107
|
+
return message || `Must be at most ${maxValue}`;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Validates that value is in a range
|
|
115
|
+
*/
|
|
116
|
+
export const inRange = (
|
|
117
|
+
minValue: number,
|
|
118
|
+
maxValue: number,
|
|
119
|
+
message?: string
|
|
120
|
+
): FormValidator<number> => {
|
|
121
|
+
return (value: number) => {
|
|
122
|
+
if (value === null || value === undefined) return null;
|
|
123
|
+
if (value < minValue || value > maxValue) {
|
|
124
|
+
return message || `Must be between ${minValue} and ${maxValue}`;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Validates that value is in a list
|
|
132
|
+
*/
|
|
133
|
+
export const isInArray = <T>(
|
|
134
|
+
array: T[],
|
|
135
|
+
message = 'Invalid value'
|
|
136
|
+
): FormValidator<T> => {
|
|
137
|
+
return (value: T) => {
|
|
138
|
+
if (!array.includes(value)) {
|
|
139
|
+
return message;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
};
|
|
143
|
+
};
|