UNPKG

slimeform

Version:

Form state management and validation for Vue3

340 lines (327 loc) 9.47 kB
import { __async, __spreadProps, __spreadValues } from "./chunk-GONKA3OC.js"; // src/index.ts import { reactive as reactive2, readonly as readonly2, ref as ref3 } from "vue"; // src/util/is.ts var isFunction = (value) => typeof value === "function"; function isHasOwn(object, key) { return Object.prototype.hasOwnProperty.call(object, key); } var isNullOrUndefined = (value) => value == null; var isDateObject = (value) => value instanceof Date; var isObjectType = (value) => typeof value === "object"; function isBaseType(value) { return isNullOrUndefined(value) || !isObjectType(value); } var isPromise = (obj) => Promise.resolve(obj) === obj; var isEmptyObject = (object) => Object.keys(object).length === 0; // src/defineStatus.ts import { computed, reactive, watchEffect } from "vue"; // src/util/deepEqual.ts function deepEqual(value1, value2) { return deepEqualRecursion(value1, value2); } function deepEqualRecursion(value1, value2, stack = /* @__PURE__ */ new WeakSet()) { if (isBaseType(value1) || isBaseType(value2)) { return { equal: value1 === value2, pointersEqual: false }; } let pointersEqual = value1 === value2; if (pointersEqual || stack.has(value1)) return { equal: pointersEqual, pointersEqual }; stack.add(value1); if (isDateObject(value1) && isDateObject(value2)) { return { equal: value1.getTime() === value2.getTime(), pointersEqual: false }; } const keys1 = Object.keys(value1); const keys2 = Object.keys(value2); if (keys1.length !== keys2.length) return { equal: false, pointersEqual: false }; for (const key of keys1) { const item1 = value1[key]; if (!keys2.includes(key)) return { equal: false, pointersEqual }; const item2 = value2[key]; const res = deepEqualRecursion(item1, item2, stack); if (!res.equal) return res; pointersEqual || (pointersEqual = res.pointersEqual); } stack.delete(value1); return { equal: true, pointersEqual }; } // src/util/watchIgnorable.ts import { ref, watch } from "vue"; function watchIgnorable(source, cb, options) { const ignoreCount = ref(0); const syncCount = ref(0); const syncStop = watch( source, () => { syncCount.value++; }, __spreadProps(__spreadValues({}, options), { flush: "sync" }) ); const stop = watch(source, (...args) => { const ignore = ignoreCount.value > 0 && ignoreCount.value === syncCount.value; ignoreCount.value = 0; syncCount.value = 0; if (!ignore) cb(...args); }, options); const ignoreUpdates = (updater) => { const prev = syncCount.value; updater(); const changes = syncCount.value - prev; ignoreCount.value += changes; }; const ignorePrevAsyncUpdates = () => { ignoreCount.value = syncCount.value; }; return { ignoreUpdates, ignorePrevAsyncUpdates, stop: () => { syncStop(); stop(); } }; } // src/defineStatus.ts function initStatus(status, formObj, initialForm, formDefaultMessage, formLazy, rule) { for (const key in formObj) { if (!isHasOwn(formObj, key)) continue; const fieldRule = isHasOwn(rule, key) ? rule[key] : void 0; status[key] = reactive(__spreadValues({ message: formDefaultMessage, isError: false, isDirty: computed(() => { const res = deepEqual(initialForm.value[key], formObj[key]); if (process.env.NODE_ENV !== "production") { if (res.pointersEqual) console.error(new Error('[SlimeForm]: in useForm(...): The "form" parameter is an invalid factory function because a duplicate reference is returned. Maybe you need a deep copy of your initial value in the form function.')); } return !res.equal; }) }, statusControl(key, status, formObj, formDefaultMessage, formLazy, fieldRule))); } } function statusControl(key, status, formObj, formDefaultMessage, formLazy, fieldRule) { function setError(message, isError = true) { status[key].message = message; status[key].isError = isError; } function ruleEffect() { if (!fieldRule) return; const { message, valid } = fieldRule.validate(formObj[key], { fullResult: true }); setError(message, !valid); } let stopEffect = null; const init = () => { if (!fieldRule || formLazy || stopEffect) return; stopEffect = watchEffect(ruleEffect); }; const { ignoreUpdates } = watchIgnorable( () => formObj[key], init, { deep: isObjectType(formObj[key]) } ); function clearError() { if (stopEffect) { stopEffect(); stopEffect = null; } setError(formDefaultMessage, false); } function verify() { ruleEffect(); return !status[key].isError; } return { verify, setError, clearError, init, _ignoreUpdate: ignoreUpdates }; } // src/getters.ts import { computed as computed2, readonly } from "vue"; function useDirtyFields(form, status) { return computed2(() => { const fields = {}; const keys = Object.keys(form); for (const key of keys) { if (status[key].isDirty && isHasOwn(form, key)) fields[key] = form[key]; } return readonly(fields); }); } function useIsError(status) { return computed2(() => { var _a; const keys = Object.keys(status); for (const key of keys) { if ((_a = status[key]) == null ? void 0 : _a.isError) return true; } return false; }); } function useIsFormDirty(dirtyFields) { return computed2(() => !isEmptyObject(dirtyFields.value)); } // src/submitter.ts import { ref as ref2 } from "vue"; // src/util/invoke.ts function invoke(func, ...args) { return func(...args); } // src/submitter.ts function createSubmit(formData, fn, options) { const { enableVerify = true } = options != null ? options : {}; const { verify } = formData; const submitting = ref2(false); const submitFn = () => { if (enableVerify && !verify()) return; const ret = fn(formData); if (isPromise(ret)) { return invoke(() => __async(this, null, function* () { submitting.value = true; try { return yield ret; } finally { submitting.value = false; } })); } else { return ret; } }; return { submitting, submit: submitFn }; } function createSubmitter(formData) { return (...args) => createSubmit(formData(), ...args); } // src/rule.ts function initRule(formRule, defaultMessage) { const rule = {}; for (const key in formRule) { let validate2 = function(v, options = {}) { const { fullResult } = options; for (const r of fieldRules || []) { const result = r(v); if (!result || typeof result === "string") return fullResult ? { valid: false, message: result || defaultMessage } : false; } return fullResult ? { valid: true, message: defaultMessage } : true; }; var validate = validate2; const fieldRules = invoke(() => { const formRuleItem = formRule == null ? void 0 : formRule[key]; return isFunction(formRuleItem) ? [formRuleItem] : formRuleItem; }); rule[key] = { validate: validate2 }; } return rule; } // src/index.ts var defaultParam = { defaultMessage: "", lazy: false, fullValidation: false }; function useForm(param) { const options = Object.assign({}, defaultParam, param); const { form: formBuilder, rule: formRule, defaultMessage: formDefaultMessage, lazy: formLazy, fullValidation } = options; const initialForm = ref3(formBuilder()); const form = reactive2(formBuilder()); const rule = initRule(formRule, formDefaultMessage); const status = reactive2({}); initStatus(status, form, initialForm, formDefaultMessage, formLazy, rule); const dirtyFields = useDirtyFields(form, status); const formData = __spreadValues({ form, status: readonly2(status), dirtyFields, isDirty: useIsFormDirty(dirtyFields), isError: useIsError(status) }, createControl(formBuilder, initialForm, form, status, fullValidation)); return __spreadProps(__spreadValues({}, formData), { rule, submitter: createSubmitter(() => formData) }); } function createControl(formBuilder, initialForm, form, status, fullValidation) { const verify = () => { let isPass = true; if (fullValidation) { Object.keys(status).forEach((key) => { const fieldResult = status[key].verify(); isPass = isPass && fieldResult; }); } else { Object.keys(status).forEach((key) => { isPass = isPass && status[key].verify(); }); } return isPass; }; const clearErrors = () => { Object.keys(status).forEach((key) => { status[key].clearError(); }); }; const reset = (...fields) => { initialForm.value = formBuilder(); const newForm = formBuilder(); const needReset = (field) => fields.length === 0 || fields.includes(field); for (const key in form) { if (!needReset(key)) continue; if (!isHasOwn(form, key)) continue; if (isHasOwn(newForm, key)) { status[key]._ignoreUpdate(() => { form[key] = newForm[key]; }); status[key].clearError(); } else { delete form[key]; } } }; const onSubmit = (callback) => verify() ? callback() : null; return { verify, clearErrors, reset, onSubmit }; } export { useForm };