UNPKG

slimeform

Version:

Form state management and validation for Vue3

340 lines (318 loc) 9.79 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); var _chunk6ZD43436cjs = require('./chunk-6ZD43436.cjs'); // src/index.ts var _vue = require('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 // 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 function watchIgnorable(source, cb, options) { const ignoreCount = _vue.ref.call(void 0, 0); const syncCount = _vue.ref.call(void 0, 0); const syncStop = _vue.watch.call(void 0, source, () => { syncCount.value++; }, _chunk6ZD43436cjs.__spreadProps.call(void 0, _chunk6ZD43436cjs.__spreadValues.call(void 0, {}, options), { flush: "sync" }) ); const stop = _vue.watch.call(void 0, 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] = _vue.reactive.call(void 0, _chunk6ZD43436cjs.__spreadValues.call(void 0, { message: formDefaultMessage, isError: false, isDirty: _vue.computed.call(void 0, () => { 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 = _vue.watchEffect.call(void 0, 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 function useDirtyFields(form, status) { return _vue.computed.call(void 0, () => { const fields = {}; const keys = Object.keys(form); for (const key of keys) { if (status[key].isDirty && isHasOwn(form, key)) fields[key] = form[key]; } return _vue.readonly.call(void 0, fields); }); } function useIsError(status) { return _vue.computed.call(void 0, () => { 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 _vue.computed.call(void 0, () => !isEmptyObject(dirtyFields.value)); } // src/submitter.ts // 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 = _vue.ref.call(void 0, false); const submitFn = () => { if (enableVerify && !verify()) return; const ret = fn(formData); if (isPromise(ret)) { return invoke(() => _chunk6ZD43436cjs.__async.call(void 0, 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 = _vue.ref.call(void 0, formBuilder()); const form = _vue.reactive.call(void 0, formBuilder()); const rule = initRule(formRule, formDefaultMessage); const status = _vue.reactive.call(void 0, {}); initStatus(status, form, initialForm, formDefaultMessage, formLazy, rule); const dirtyFields = useDirtyFields(form, status); const formData = _chunk6ZD43436cjs.__spreadValues.call(void 0, { form, status: _vue.readonly.call(void 0, status), dirtyFields, isDirty: useIsFormDirty(dirtyFields), isError: useIsError(status) }, createControl(formBuilder, initialForm, form, status, fullValidation)); return _chunk6ZD43436cjs.__spreadProps.call(void 0, _chunk6ZD43436cjs.__spreadValues.call(void 0, {}, 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 }; } exports.useForm = useForm;