el-form-react
Version:
React form components and hooks powered by Zod validation
412 lines (408 loc) • 12.7 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
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 __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/hooks.ts
var hooks_exports = {};
__export(hooks_exports, {
useForm: () => useForm
});
module.exports = __toCommonJS(hooks_exports);
__reExport(hooks_exports, require("el-form-core"), module.exports);
// src/useForm.ts
var import_react = require("react");
var import_zod = require("zod");
var import_el_form_core2 = require("el-form-core");
// src/utils/index.ts
var import_el_form_core = require("el-form-core");
function addArrayItemReact(obj, path, item) {
const result = { ...obj };
const normalizedPath = path.replace(/\[(\d+)\]/g, ".$1");
const keys = normalizedPath.split(".").filter((key) => key !== "");
let current = result;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!isNaN(Number(key))) {
if (Array.isArray(current)) {
current[Number(key)] = Array.isArray(current[Number(key)]) ? [...current[Number(key)]] : { ...current[Number(key)] };
current = current[Number(key)];
}
} else {
if (typeof current[key] !== "object" || current[key] === null) {
current[key] = {};
} else {
current[key] = Array.isArray(current[key]) ? [...current[key]] : { ...current[key] };
}
current = current[key];
}
}
const arrayKey = keys[keys.length - 1];
if (!isNaN(Number(arrayKey))) {
if (Array.isArray(current)) {
current = [...current];
current[Number(arrayKey)] = item;
}
} else {
if (!Array.isArray(current[arrayKey])) {
current[arrayKey] = [];
} else {
current[arrayKey] = [...current[arrayKey]];
}
current[arrayKey].push(item);
}
return result;
}
// src/useForm.ts
function useForm(options) {
const {
schema,
initialValues = {},
validateOnChange = false,
validateOnBlur = false
} = options;
const fieldRefs = (0, import_react.useRef)(/* @__PURE__ */ new Map());
const [formState, setFormState] = (0, import_react.useState)({
values: initialValues,
errors: {},
touched: {},
isSubmitting: false,
isValid: false,
isDirty: false
});
const validate = (0, import_react.useCallback)(
(values) => {
try {
schema.parse(values);
return { isValid: true, errors: {} };
} catch (error) {
if (error instanceof import_zod.z.ZodError) {
return {
isValid: false,
errors: (0, import_el_form_core2.parseZodErrors)(error)
};
}
return { isValid: false, errors: {} };
}
},
[schema]
);
const checkIsDirty = (0, import_react.useCallback)(
(currentValues) => {
return JSON.stringify(initialValues || {}) !== JSON.stringify(currentValues || {});
},
[initialValues]
);
const checkFieldIsDirty = (0, import_react.useCallback)(
(fieldName) => {
const initialValue = initialValues[fieldName];
const currentValue = formState.values[fieldName];
return JSON.stringify(initialValue) !== JSON.stringify(currentValue);
},
[initialValues, formState.values]
);
const register = (0, import_react.useCallback)(
(name) => {
const fieldValue = name.includes(".") ? (0, import_el_form_core2.getNestedValue)(formState.values, name) : formState.values[name];
const isCheckbox = typeof fieldValue === "boolean";
const baseProps = {
name,
onChange: (e) => {
const target = e.target;
const value = target.type === "checkbox" ? target.checked : target.type === "number" ? target.value ? Number(target.value) : void 0 : target.value;
setFormState((prev) => {
const newValues = name.includes(".") ? (0, import_el_form_core2.setNestedValue)(prev.values, name, value) : { ...prev.values, [name]: value };
let newErrors = { ...prev.errors };
if (name.includes(".")) {
const nestedError = (0, import_el_form_core2.getNestedValue)(newErrors, name);
if (nestedError) {
newErrors = (0, import_el_form_core2.setNestedValue)(newErrors, name, void 0);
}
} else {
delete newErrors[name];
}
if (validateOnChange) {
const { errors } = validate(newValues);
newErrors = errors;
}
return {
...prev,
values: newValues,
errors: newErrors,
isDirty: checkIsDirty(newValues)
};
});
},
onBlur: (_e) => {
setFormState((prev) => {
const newTouched = name.includes(".") ? (0, import_el_form_core2.setNestedValue)(prev.touched, name, true) : { ...prev.touched, [name]: true };
let newErrors = prev.errors;
if (validateOnBlur) {
const { errors } = validate(prev.values);
newErrors = errors;
}
return {
...prev,
touched: newTouched,
errors: newErrors,
isDirty: checkIsDirty(prev.values)
};
});
}
};
if (isCheckbox) {
return {
...baseProps,
checked: Boolean(fieldValue)
};
}
return {
...baseProps,
value: fieldValue || ""
};
},
[formState.values, validateOnChange, validateOnBlur, validate, checkIsDirty]
);
const handleSubmit = (0, import_react.useCallback)(
(onValid, onError) => {
return (e) => {
e.preventDefault();
setFormState((prev) => ({ ...prev, isSubmitting: true }));
const { isValid, errors } = validate(formState.values);
setFormState((prev) => ({
...prev,
errors,
isValid,
isSubmitting: false,
isDirty: checkIsDirty(formState.values)
}));
if (isValid) {
onValid(formState.values);
} else {
if (onError) {
onError(errors);
}
}
};
},
[formState.values, validate, checkIsDirty]
);
const reset = (0, import_react.useCallback)(
(options2) => {
const newValues = options2?.values ?? initialValues;
setFormState({
values: newValues,
errors: options2?.keepErrors ? formState.errors : {},
touched: options2?.keepTouched ? formState.touched : {},
isSubmitting: false,
isValid: false,
isDirty: options2?.keepDirty ? formState.isDirty : false
});
},
[initialValues, formState]
);
const setValue = (0, import_react.useCallback)(
(path, value) => {
setFormState((prev) => {
const newValues = (0, import_el_form_core2.setNestedValue)(prev.values, path, value);
const { errors } = validate(newValues);
return {
...prev,
values: newValues,
errors,
isDirty: checkIsDirty(newValues)
};
});
},
[validate, checkIsDirty]
);
const addArrayItemHandler = (0, import_react.useCallback)(
(path, item) => {
setFormState((prev) => {
const newValues = addArrayItemReact(prev.values, path, item);
const { errors } = validate(newValues);
return {
...prev,
values: newValues,
errors,
isDirty: checkIsDirty(newValues)
};
});
},
[validate, checkIsDirty]
);
const removeArrayItemHandler = (0, import_react.useCallback)(
(path, index) => {
setFormState((prev) => {
const newValues = (0, import_el_form_core2.removeArrayItem)(prev.values, path, index);
const { errors } = validate(newValues);
return {
...prev,
values: newValues,
errors,
isDirty: checkIsDirty(newValues)
};
});
},
[validate, checkIsDirty]
);
const watch = (0, import_react.useCallback)(
(nameOrNames) => {
if (!nameOrNames) {
return formState.values;
}
if (Array.isArray(nameOrNames)) {
const result = {};
nameOrNames.forEach((name) => {
result[name] = formState.values[name];
});
return result;
}
return formState.values[nameOrNames];
},
[formState.values]
);
const isDirty = (0, import_react.useCallback)(
(name) => {
if (name) {
return checkFieldIsDirty(name);
}
return formState.isDirty;
},
[formState.isDirty, checkFieldIsDirty]
);
const getFieldState = (0, import_react.useCallback)(
(name) => {
return {
isDirty: checkFieldIsDirty(name),
isTouched: !!formState.touched[name],
error: formState.errors[name]
};
},
[formState, checkFieldIsDirty]
);
const getDirtyFields = (0, import_react.useCallback)(() => {
const dirtyFields = {};
Object.keys(formState.values).forEach((key) => {
const fieldName = key;
if (checkFieldIsDirty(fieldName)) {
dirtyFields[fieldName] = true;
}
});
return dirtyFields;
}, [formState.values, checkFieldIsDirty]);
const getTouchedFields = (0, import_react.useCallback)(() => {
return { ...formState.touched };
}, [formState.touched]);
const trigger = (0, import_react.useCallback)(
async (nameOrNames) => {
if (!nameOrNames) {
const { isValid: isValid2 } = validate(formState.values);
return isValid2;
}
if (Array.isArray(nameOrNames)) {
const fieldsToValidate = {};
nameOrNames.forEach((name) => {
fieldsToValidate[name] = formState.values[name];
});
const { isValid: isValid2 } = validate(fieldsToValidate);
return isValid2;
}
const fieldToValidate = {};
fieldToValidate[nameOrNames] = formState.values[nameOrNames];
const { isValid } = validate(fieldToValidate);
return isValid;
},
[formState.values, validate]
);
const clearErrors = (0, import_react.useCallback)((name) => {
setFormState((prev) => {
if (name) {
const newErrors = { ...prev.errors };
delete newErrors[name];
return { ...prev, errors: newErrors };
}
return { ...prev, errors: {} };
});
}, []);
const setError = (0, import_react.useCallback)(
(name, error) => {
setFormState((prev) => ({
...prev,
errors: { ...prev.errors, [name]: error }
}));
},
[]
);
const setFocus = (0, import_react.useCallback)(
(name, options2) => {
const fieldRef = fieldRefs.current.get(name);
if (fieldRef) {
fieldRef.focus();
if (options2?.shouldSelect && "select" in fieldRef) {
fieldRef.select();
}
}
},
[]
);
const resetField = (0, import_react.useCallback)(
(name) => {
setFormState((prev) => {
const newValues = { ...prev.values };
newValues[name] = initialValues[name];
const newErrors = { ...prev.errors };
delete newErrors[name];
const newTouched = { ...prev.touched };
delete newTouched[name];
return {
...prev,
values: newValues,
errors: newErrors,
touched: newTouched,
isDirty: checkIsDirty(newValues)
};
});
},
[initialValues, checkIsDirty]
);
return {
register,
handleSubmit,
formState,
reset,
setValue,
watch,
getFieldState,
isDirty,
getDirtyFields,
getTouchedFields,
trigger,
clearErrors,
setError,
setFocus,
addArrayItem: addArrayItemHandler,
removeArrayItem: removeArrayItemHandler,
resetField
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
useForm,
...require("el-form-core")
});
//# sourceMappingURL=hooks.js.map