UNPKG

react-hook-formify

Version:

A smart wrapper around react-hook-form + zustand

242 lines (232 loc) 9.99 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // index.ts var index_exports = {}; __export(index_exports, { Field: () => field_default, Form: () => form_default, useFormStore: () => useFormStore }); module.exports = __toCommonJS(index_exports); // src/components/form.tsx var import_react2 = require("react"); var import_react_hook_form = require("react-hook-form"); var import_react_i18next = require("react-i18next"); var import_yup = require("@hookform/resolvers/yup"); // ../../../node_modules/zustand/esm/vanilla.mjs var createStoreImpl = (createState) => { let state; const listeners = /* @__PURE__ */ new Set(); const setState = (partial, replace) => { const nextState = typeof partial === "function" ? partial(state) : partial; if (!Object.is(nextState, state)) { const previousState = state; state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState); listeners.forEach((listener) => listener(state, previousState)); } }; const getState = () => state; const getInitialState = () => initialState; const subscribe = (listener) => { listeners.add(listener); return () => listeners.delete(listener); }; const api = { setState, getState, getInitialState, subscribe }; const initialState = state = createState(setState, getState, api); return api; }; var createStore = ((createState) => createState ? createStoreImpl(createState) : createStoreImpl); // ../../../node_modules/zustand/esm/react.mjs var import_react = __toESM(require("react"), 1); var identity = (arg) => arg; function useStore(api, selector = identity) { const slice = import_react.default.useSyncExternalStore( api.subscribe, import_react.default.useCallback(() => selector(api.getState()), [api, selector]), import_react.default.useCallback(() => selector(api.getInitialState()), [api, selector]) ); import_react.default.useDebugValue(slice); return slice; } var createImpl = (createState) => { const api = createStore(createState); const useBoundStore = (selector) => useStore(api, selector); Object.assign(useBoundStore, api); return useBoundStore; }; var create = ((createState) => createState ? createImpl(createState) : createImpl); // src/store/form.store.ts var useFormStore = create((set) => ({ formData: {}, setFormData: (key, data) => set((state) => ({ formData: { ...state.formData, [key]: data } })), resetForm: () => set({ formData: {} }) })); // src/utils/debounce.ts function debounce(func, wait) { let timeout; const debounced = (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); }; debounced.cancel = () => clearTimeout(timeout); return debounced; } // src/utils/validation.ts var yup = __toESM(require("yup")); var generateSchema = (fields = [], t) => { const shape = {}; const getBaseValidator = (field) => { if (typeof field?.type === "function") return field?.type(yup); switch (field?.type) { case "email": return yup.string().email(t("validation.email")); case "number": return yup.number().transform((v, o) => o === "" ? null : v).nullable().typeError(t("validation.number")); case "object": return yup.object().nullable().transform((value, originalValue) => { return originalValue === "" ? null : value; }); case "array": return yup.array().nullable().transform((value, originalValue) => { return originalValue === "" ? null : value; }); case "file": return yup.mixed().nullable().transform((value, originalValue) => { return originalValue === "" ? null : value; }).test("fileType", t("validation.invalid"), (value) => { if (value == null) return true; if (value instanceof File) return true; if (Array.isArray(value) && value.length > 0) return true; return typeof value === "object" && Object.keys(value).length > 0; }); case "boolean": return yup.boolean().typeError(t("validation.boolean")); case "date": return yup.date().nullable().transform((v, o) => o === "" ? null : v).typeError(t("validation.date")); case "string": default: return yup.string(); } }; const applyRules = (schema, rules) => { if (rules.required) schema = schema.required(t("validation.required")); if (rules.min !== void 0) schema = schema.min?.(rules.min, t("validation.min", { min: rules.min })) ?? schema; if (rules.max !== void 0) schema = schema.max?.(rules.max, t("validation.max", { max: rules.max })) ?? schema; if (rules.matches) schema = schema.matches?.(rules.matches.regex, t(rules.matches.message || "validation.invalid")) ?? schema; return schema; }; fields.forEach((field) => { if (field?.when && (field.when.field || Array.isArray(field.when.fields))) { const whenFields = Array.isArray(field.when.fields) ? field.when.fields : [field.when.field]; const conditionFn = field.when.is; const thenRules = field.when.then || {}; const otherwiseRules = field.when.otherwise || {}; shape[field.name] = yup.mixed().when(whenFields, (...args) => { const values = args.slice(0, whenFields.length); const base = getBaseValidator(field); try { const condition = typeof conditionFn === "function" ? conditionFn(...values) : conditionFn === values[0]; return condition ? applyRules(base, thenRules) : applyRules(base, otherwiseRules); } catch (err) { console.warn(`generateSchema: conditionFn error in field "${field.name}"`, err); return base; } }); } else { shape[field.name] = applyRules(getBaseValidator(field), field); } }); return yup.object().shape(shape); }; // src/components/form.tsx var import_jsx_runtime = require("react/jsx-runtime"); var FormComponent = (0, import_react2.forwardRef)(({ name = "form", children, onSubmit, enableStore, isLoading = false, fields = [] }, ref) => { const { t } = (0, import_react_i18next.useTranslation)(); const initialValues = Object.fromEntries(fields.map((f) => [f.name, f.value ?? ""])); const methods = (0, import_react_hook_form.useForm)({ defaultValues: initialValues, values: initialValues, resolver: (0, import_yup.yupResolver)(generateSchema(fields, t)), mode: "all", shouldFocusError: false, reValidateMode: "onChange" }); (0, import_react2.useEffect)(() => { if (!enableStore) return; const debouncedUpdate = debounce((values) => { useFormStore.getState().setFormData(name, values); }, 300); const subscription = methods.watch((values) => { debouncedUpdate(values); }); return () => { subscription.unsubscribe(); debouncedUpdate.cancel(); }; }, [enableStore, methods, methods.watch, name]); const handleSubmit = async (values) => { const isValid = await methods.trigger(); if (isValid) { const renderedValues = Object.fromEntries( fields.map((field) => { const rawValue = values[field.name]; const transformed = typeof field.onSubmitValue === "function" ? field.onSubmitValue(rawValue) : rawValue; return [field.name, transformed]; }) ); onSubmit?.({ values: renderedValues, ...methods }); } else { console.log("Form error"); } }; return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_hook_form.FormProvider, { ...methods, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", { ref, onSubmit: methods.handleSubmit(handleSubmit), children: typeof children === "function" ? children({ ...methods, isLoading, values: methods.watch(), errors: methods.formState.errors }) : children }) }); }); var form_default = FormComponent; // src/components/field.tsx var import_react_hook_form2 = require("react-hook-form"); var import_jsx_runtime2 = require("react/jsx-runtime"); var FieldComponent = ({ name, component, ...rest }) => { const { control, getFieldState } = (0, import_react_hook_form2.useFormContext)(); const Component = component || null; return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_hook_form2.Controller, { name, control, render: ({ field }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Component, { ...getFieldState(name), ...rest, ...field }) }); }; var field_default = FieldComponent; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Field, Form, useFormStore });