UNPKG

auto-form-validator

Version:

A smart, zero-setup form validation library for React with automatic rule detection, i18n support, async validation, and UI integration helpers.

172 lines (166 loc) 6.14 kB
// src/hooks/useAutoValidator.ts import React, { useRef, useState, useEffect, useCallback } from "react"; // src/utils/validators.ts var validateValue = async (value, rules, messages) => { if (rules.required && !value.trim()) { return typeof rules.required === "object" && rules.required.message ? rules.required.message : messages.required; } if (rules.minLength && value.length < (typeof rules.minLength === "object" ? rules.minLength.value : rules.minLength)) { const min = typeof rules.minLength === "object" ? rules.minLength.value : rules.minLength; return typeof rules.minLength === "object" && rules.minLength.message ? rules.minLength.message : messages.minLength(min); } if (rules.maxLength && value.length > (typeof rules.maxLength === "object" ? rules.maxLength.value : rules.maxLength)) { const max = typeof rules.maxLength === "object" ? rules.maxLength.value : rules.maxLength; return typeof rules.maxLength === "object" && rules.maxLength.message ? rules.maxLength.message : messages.maxLength(max); } if (rules.pattern && !(typeof rules.pattern === "object" && "test" in rules.pattern ? rules.pattern.test(value) : rules.pattern.test(value))) { if (typeof rules.pattern === "object" && "message" in rules.pattern && rules.pattern.message) { return rules.pattern.message; } return messages.pattern; } if (rules.custom) { const result = rules.custom(value); if (result) return result; } if (rules.async) { const asyncError = await rules.async(value); if (asyncError) return asyncError || messages.async; } return null; }; // src/utils/messages.ts var messagesMap = { en: { required: "This field is required", minLength: (n) => `Minimum length is ${n}`, maxLength: (n) => `Maximum length is ${n}`, pattern: "Invalid format", async: "Validation failed" }, hi: { required: "\u092F\u0939 \u092B\u093C\u0940\u0932\u094D\u0921 \u0906\u0935\u0936\u094D\u092F\u0915 \u0939\u0948", minLength: (n) => `\u0928\u094D\u092F\u0942\u0928\u0924\u092E \u0932\u0902\u092C\u093E\u0908 ${n} \u0939\u094B\u0928\u0940 \u091A\u093E\u0939\u093F\u090F`, maxLength: (n) => `\u0905\u0927\u093F\u0915\u0924\u092E \u0932\u0902\u092C\u093E\u0908 ${n} \u0939\u094B\u0928\u0940 \u091A\u093E\u0939\u093F\u090F`, pattern: "\u0905\u092E\u093E\u0928\u094D\u092F \u092A\u094D\u0930\u093E\u0930\u0942\u092A", async: "\u092E\u093E\u0928\u094D\u092F\u0915\u0930\u0923 \u0935\u093F\u092B\u0932 \u0939\u0941\u0906" } }; // src/components/validationPatterns.ts var defaultValidationPatterns = { email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, number: /^-?\d+(\.\d+)?$/, tel: /^\+?[1-9]\d{1,14}$/, url: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/, date: /^\d{4}-\d{2}-\d{2}$/, time: /^([01]\d|2[0-3]):([0-5]\d)$/, color: /^#([0-9A-F]{3}){1,2}$/i }; // src/hooks/useAutoValidator.ts function debounce(func, wait = 300) { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; } var useAutoValidator = (manualSchema = {}, language = "en") => { const fields = useRef({}); const [errors, setErrors] = useState({}); const messages = messagesMap[language]; const extractRules = useCallback( (el) => { const rules = {}; if (el.required) rules.required = true; if ("minLength" in el && el.minLength > 0) rules.minLength = el.minLength; if ("maxLength" in el && el.maxLength > 0) rules.maxLength = el.maxLength; if ("pattern" in el && el.pattern) rules.pattern = new RegExp(el.pattern); const typePattern = defaultValidationPatterns[el.type]; if (typePattern) { rules.pattern = typePattern; } return rules; }, [] ); const getField = useCallback((name) => { if (!fields.current[name]) { const ref = React.createRef(); fields.current[name] = { ref, rules: {} }; } return fields.current[name]; }, []); useEffect(() => { Object.entries(fields.current).forEach(([name, { ref }]) => { const el = ref.current; if (el) { const extractedRules = extractRules(el); fields.current[name].rules = extractedRules; } }); }, [extractRules]); const validateAndSetError = async (name) => { const field = fields.current[name]; if (!field || !field.ref.current) return; const rules = manualSchema[name] || field.rules; const value = field.ref.current.value; const error = await validateValue(value, rules, messages); setErrors((prev) => ({ ...prev, [name]: error })); }; const debouncedValidateAndSetError = useCallback( debounce(validateAndSetError, 300), [manualSchema, messages] ); const register = (name) => { const field = getField(name); const handleBlur = async () => { await validateAndSetError(name); }; const handleChange = () => { debouncedValidateAndSetError(name); }; return { name, ref: (el) => { field.ref.current = el; }, onBlur: handleBlur, onChange: handleChange }; }; const validateForm = async (values) => { let valid = true; const newErrors = {}; for (const name in values) { const field = fields.current[name]; const rules = manualSchema[name] || (field == null ? void 0 : field.rules) || {}; const value = values[name]; const error = await validateValue(value, rules, messages); newErrors[name] = error; if (error) valid = false; } setErrors(newErrors); return valid; }; const resetErrors = () => setErrors({}); return { register, errors, validateForm, resetErrors }; }; // src/components/ErrorMessage.tsx import React2 from "react"; var ErrorMessage = ({ name, errors }) => { if (!errors[name]) return null; return /* @__PURE__ */ React2.createElement("p", { style: { color: "red", fontSize: "0.875rem", marginTop: "4px" } }, errors[name]); }; var ErrorMessage_default = ErrorMessage; export { ErrorMessage_default as ErrorMessage, useAutoValidator };