UNPKG

@regle/core

Version:

Headless form validation library for Vue 3

1,457 lines (1,431 loc) 84.3 kB
import { computed, effectScope, getCurrentInstance, getCurrentScope, isRef, markRaw, nextTick, onMounted, onScopeDispose, reactive, ref, shallowRef, toRef, toValue, triggerRef, unref, version, watch, watchEffect } from "vue"; //#region ../shared/utils/isFile.ts /** * Server side friendly way of checking for a File */ function isFile(value) { return value?.constructor.name == "File" || value?.constructor.name == "FileList"; } //#endregion //#region ../shared/utils/isEmpty.ts /** * This is the inverse of isFilled. It will check if the value is in any way empty (including arrays and objects) * * isEmpty also acts as a type guard. * * @param value - the target value * @param [considerEmptyArrayInvalid=true] - will return false if set to `false`. (default: `true`) */ function isEmpty(value, considerEmptyArrayInvalid = true) { if (value === void 0 || value === null) return true; if (value instanceof Date) return isNaN(value.getTime()); else if (isFile(value)) return value.size <= 0; else if (Array.isArray(value)) { if (considerEmptyArrayInvalid) return value.length === 0; return false; } else if (typeof value === "object" && value != null) return Object.keys(value).length === 0; return !String(value).length; } //#endregion //#region ../shared/utils/symbol.ts const RegleRuleSymbol = Symbol("regle-rule"); //#endregion //#region ../shared/utils/cloneDeep.ts function getRegExpFlags(regExp) { if (typeof regExp.source.flags == "string") return regExp.source.flags; else { let flags = []; regExp.global && flags.push("g"); regExp.ignoreCase && flags.push("i"); regExp.multiline && flags.push("m"); regExp.sticky && flags.push("y"); regExp.unicode && flags.push("u"); return flags.join(""); } } function cloneDeep(obj) { let result = obj; let type = {}.toString.call(obj).slice(8, -1); if (type == "Set") result = new Set([...obj].map((value) => cloneDeep(value))); if (type == "Map") result = new Map([...obj].map((kv) => [cloneDeep(kv[0]), cloneDeep(kv[1])])); if (type == "Date") result = new Date(obj.getTime()); if (type == "RegExp") result = RegExp(obj.source, getRegExpFlags(obj)); if (type == "Array" || type == "Object") { result = Array.isArray(obj) ? [] : {}; for (let key in obj) result[key] = cloneDeep(obj[key]); } return result; } //#endregion //#region ../shared/utils/object.utils.ts function isObject(obj) { if (obj && (obj instanceof Date || obj.constructor.name == "File" || obj.constructor.name == "FileList")) return false; return typeof obj === "object" && obj !== null && !Array.isArray(obj); } function merge(obj1, ...objs) { var args = [].slice.call(arguments); var arg; var i = args.length; while (arg = args[i - 1], i--) if (!arg || typeof arg != "object" && typeof arg != "function") throw new Error("expected object, got " + arg); var result = args[0]; var extenders = args.slice(1); var len = extenders.length; for (var i = 0; i < len; i++) { var extender = extenders[i]; for (var key in extender) result[key] = extender[key]; } return result; } //#endregion //#region ../shared/utils/toDate.ts /** * This utility will coerce any string, number or Date value into a Date using the Date constructor. */ function toDate(argument) { const argStr = Object.prototype.toString.call(argument); if (argument == null) return /* @__PURE__ */ new Date(NaN); else if (argument instanceof Date || typeof argument === "object" && argStr === "[object Date]") return new Date(argument.getTime()); else if (typeof argument === "number" || argStr === "[object Number]") return new Date(argument); else if (typeof argument === "string" || argStr === "[object String]") return new Date(argument); else return /* @__PURE__ */ new Date(NaN); } //#endregion //#region ../shared/utils/debounce.ts function debounce(func, wait, immediate) { let timeout; const debouncedFn = (...args) => new Promise((resolve) => { clearTimeout(timeout); timeout = setTimeout(() => { timeout = void 0; if (!immediate) Promise.resolve(func.apply(this, [...args])).then(resolve); }, wait); if (immediate && !timeout) Promise.resolve(func.apply(this, [...args])).then(resolve); }); debouncedFn.cancel = () => { clearTimeout(timeout); timeout = void 0; }; return debouncedFn; } //#endregion //#region ../shared/utils/isEqual.ts function isEqual(a, b, deep = false, firstDeep = true) { if (a === b) return true; if (a && b && typeof a == "object" && typeof b == "object") { if (a.constructor !== b.constructor) return false; var length, i, keys; if (Array.isArray(a) && Array.isArray(b)) { length = a.length; if (length != b.length) return false; if (firstDeep || !firstDeep && deep) { for (i = length; i-- !== 0;) if (!isEqual(a[i], b[i], deep, false)) return false; } return true; } if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); keys = Object.keys(a); length = keys.length; if (length !== Object.keys(b).length) return false; for (i = length; i-- !== 0;) if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; for (i = length; i-- !== 0;) { var key = keys[i]; if (isObject(a) && isObject(b)) { if (firstDeep || !firstDeep && deep) { if (!isEqual(a[key], b[key], deep, false)) return false; } } return true; } return true; } return a !== a && b !== b; } //#endregion //#region src/types/rules/rule.internal.types.ts const InternalRuleType = { Inline: "__inline", Async: "__async" }; //#endregion //#region src/types/utils/groups.ts function mergeBooleanGroupProperties(entries, property) { return entries.some((entry) => { return entry[property]; }); } function mergeArrayGroupProperties(entries, property) { return entries.reduce((all, entry) => { const fetchedProperty = entry[property] || []; return all.concat(fetchedProperty); }, []); } //#endregion //#region src/core/createRule/unwrapRuleParameters.ts /** * Returns a clean list of parameters * Removing Ref and executing function to return the unwrapped value */ function unwrapRuleParameters(params) { try { return params.map((param) => toValue(param)); } catch (e) { return []; } } /** * Returns a clean list of parameters * Removing Ref and executing function to return the unwrapped value */ function createReactiveParams(params) { return params.map((param) => { if (param instanceof Function) return computed(param); else if (isRef(param)) return param; return toRef(() => param); }); } /** * Due to `function.length` not returning default parameters, it needed to parse the func.toString() */ function getFunctionParametersLength(func) { const funcStr = func.toString(); const cleanStr = funcStr.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, ""); const paramsMatch = cleanStr.match(/^(?:async\s*)?(?:function\b.*?\(|\((.*?)\)|(\w+))\s*=>|\((.*?)\)\s*=>|function.*?\((.*?)\)|\((.*?)\)/); if (!paramsMatch) return 0; const paramsSection = paramsMatch[0] || paramsMatch[1] || paramsMatch[2] || paramsMatch[3] || paramsMatch[4] || ""; const paramList = paramsSection.split(",").map((p) => p.trim()).filter((p) => p.length > 0); return paramList.length; } //#endregion //#region src/core/createRule/defineRuleProcessors.ts function defineRuleProcessors(definition, ...params) { const { validator, type } = definition; const isAsync = type === InternalRuleType.Async || validator.constructor.name === "AsyncFunction"; const defaultProcessors = { validator(value, ...args) { return definition.validator(value, ...unwrapRuleParameters(args.length ? args : params)); }, message(metadata) { if (typeof definition.message === "function") return definition.message({ ...metadata, $params: unwrapRuleParameters(metadata?.$params?.length ? metadata.$params : params) }); else return definition.message; }, active(metadata) { if (typeof definition.active === "function") return definition.active({ ...metadata, $params: unwrapRuleParameters(metadata?.$params?.length ? metadata.$params : params) }); else return definition.active ?? true; }, tooltip(metadata) { if (typeof definition.tooltip === "function") return definition.tooltip({ ...metadata, $params: unwrapRuleParameters(metadata?.$params?.length ? metadata.$params : params) }); else return definition.tooltip ?? []; }, exec(value) { const validator$1 = definition.validator(value, ...unwrapRuleParameters(params)); let rawResult; if (validator$1 instanceof Promise) return validator$1.then((result) => { rawResult = result; if (typeof rawResult === "object" && "$valid" in rawResult) return rawResult.$valid; else if (typeof rawResult === "boolean") return rawResult; return false; }); else rawResult = validator$1; if (typeof rawResult === "object" && "$valid" in rawResult) return rawResult.$valid; else if (typeof rawResult === "boolean") return rawResult; return false; } }; const processors = markRaw({ ...defaultProcessors, _validator: definition.validator, _message: definition.message, _active: definition.active, _tooltip: definition.tooltip, _type: definition.type, _message_patched: false, _tooltip_patched: false, _async: isAsync, _params: createReactiveParams(params), _brand: RegleRuleSymbol }); return processors; } //#endregion //#region src/core/createRule/createRule.ts /** * Create a typed custom rule that can be used like default rules. * It can also be declared in the global options * * It will automatically detect if the rule is async * * * @param definition - The rule processors object * * @returns A rule definition that can be callable depending on params presence * * @exemple * * ```ts * // Create a simple rule with no params * import {isFilled} from '@regle/rules'; * * export const isFoo = createRule({ * validator(value: Maybe<string>) { * if (isFilled(value)) { * return value === 'foo'; * } * return true * }, * message: "The value should be 'foo'" * }) * * ``` * * Docs: {@link https://reglejs.dev/core-concepts/rules/reusable-rules} */ function createRule(definition) { if (typeof definition.validator === "function") { let fakeParams = []; const staticProcessors = defineRuleProcessors(definition, ...fakeParams); const isAsync = definition.async ?? definition.validator.constructor.name === "AsyncFunction"; if (getFunctionParametersLength(definition.validator) > 1) { const ruleFactory = function(...params) { return defineRuleProcessors(definition, ...params); }; ruleFactory.validator = staticProcessors.validator; ruleFactory.message = staticProcessors.message; ruleFactory.active = staticProcessors.active; ruleFactory.tooltip = staticProcessors.tooltip; ruleFactory.type = staticProcessors.type; ruleFactory.exec = staticProcessors.exec; ruleFactory._validator = staticProcessors.validator; ruleFactory._message = staticProcessors.message; ruleFactory._active = staticProcessors.active; ruleFactory._tooltip = staticProcessors.tooltip; ruleFactory._type = definition.type; ruleFactory._message_pacthed = false; ruleFactory._tooltip_pacthed = false; ruleFactory._async = isAsync; return ruleFactory; } else return staticProcessors; } throw new Error("[createRule] validator must be a function"); } //#endregion //#region src/core/useStorage/useStorage.ts /** * Inspired by Vuelidate storage */ function useStorage() { const ruleDeclStorage = shallowRef(/* @__PURE__ */ new Map()); const fieldsStorage = shallowRef(/* @__PURE__ */ new Map()); const collectionsStorage = shallowRef(/* @__PURE__ */ new Map()); const dirtyStorage = shallowRef(/* @__PURE__ */ new Map()); const ruleStatusStorage = shallowRef(/* @__PURE__ */ new Map()); const arrayStatusStorage = shallowRef(/* @__PURE__ */ new Map()); function getFieldsEntry($path) { const existingFields = fieldsStorage.value.get($path); if (existingFields) return existingFields; else { const $fields = ref({}); fieldsStorage.value.set($path, $fields); return $fields; } } function getCollectionsEntry($path) { const existingEach = collectionsStorage.value.get($path); if (existingEach) return existingEach; else { const $each = ref([]); collectionsStorage.value.set($path, $each); return $each; } } function addArrayStatus($arrayId, itemId, value) { arrayStatusStorage.value.set(`${$arrayId}-${itemId}`, value); } function getArrayStatus($arrayId, itemId) { return arrayStatusStorage.value.get(`${$arrayId}-${itemId}`); } function deleteArrayStatus($arrayId, itemId) { if ($arrayId && itemId != null) arrayStatusStorage.value.delete(`${$arrayId}-${itemId}`); } function setDirtyEntry($path, dirty) { dirtyStorage.value.set($path, dirty); } function getDirtyState(path) { return dirtyStorage.value.get(path) ?? false; } function addRuleDeclEntry($path, options) { ruleDeclStorage.value.set($path, options); } function checkRuleDeclEntry($path, newRules) { const storedRulesDefs = ruleDeclStorage.value.get($path); if (!storedRulesDefs) return void 0; const storedRules = storedRulesDefs; const isValidCache = areRulesChanged(newRules, storedRules); if (!isValidCache) return { valid: false }; return { valid: true }; } function areRulesChanged(newRules, storedRules) { const storedRulesKeys = Object.keys(storedRules); const newRulesKeys = Object.keys(newRules); if (newRulesKeys.length !== storedRulesKeys.length) return false; const hasAllValidators = newRulesKeys.every((ruleKey) => storedRulesKeys.includes(ruleKey)); if (!hasAllValidators) return false; return newRulesKeys.every((ruleKey) => { const newRuleElement = newRules[ruleKey]; const storedRuleElement = storedRules[ruleKey]; if (!storedRuleElement || !newRuleElement || typeof newRuleElement === "function" || typeof storedRuleElement === "function") return false; if (typeof newRuleElement === "number") return false; else if (typeof newRuleElement === "boolean") return false; else if (!newRuleElement._params) return true; else return newRuleElement._params?.every((paramKey, index) => { if (typeof storedRuleElement === "number" || typeof storedRuleElement === "boolean") return true; else { const storedParams = unwrapRuleParameters(storedRuleElement._params); const newParams = unwrapRuleParameters(newRuleElement._params); return storedParams?.[index] === newParams?.[index]; } }); }); } function trySetRuleStatusRef(path) { const ruleStatus = ruleStatusStorage.value.get(path); if (ruleStatus) return ruleStatus; else { const $pending = ref(false); const $valid = ref(true); const $metadata = ref({}); const $validating = ref(false); ruleStatusStorage.value.set(path, { $pending, $valid, $metadata, $validating }); return { $pending, $valid, $metadata, $validating }; } } if (getCurrentScope()) onScopeDispose(() => { ruleDeclStorage.value.clear(); fieldsStorage.value.clear(); collectionsStorage.value.clear(); dirtyStorage.value.clear(); ruleStatusStorage.value.clear(); arrayStatusStorage.value.clear(); }); return { addRuleDeclEntry, setDirtyEntry, checkRuleDeclEntry, getDirtyState, trySetRuleStatusRef, getFieldsEntry, getCollectionsEntry, getArrayStatus, addArrayStatus, deleteArrayStatus, arrayStatusStorage }; } //#endregion //#region src/utils/object.utils.ts function isRefObject(obj) { return isObject(obj.value); } function unwrapGetter(getter, value, index) { const scope = effectScope(); let unwrapped; if (getter instanceof Function) unwrapped = scope.run(() => getter(value, index ?? 0)); else unwrapped = getter; return { scope, unwrapped }; } //#endregion //#region src/utils/version-compare.ts const VersionIs = { LessThan: -1, EqualTo: 0, GreaterThan: 1 }; /** * Compare two versions quickly. * @param current Is this version greater, equal to, or less than the other? * @param other The version to compare against the current version * @return 1 if current is greater than other, 0 if they are equal or equivalent, and -1 if current is less than other */ function versionCompare(current, other) { const cp = String(current).split("."); const op = String(other).split("."); for (let depth = 0; depth < Math.min(cp.length, op.length); depth++) { const cn = Number(cp[depth]); const on = Number(op[depth]); if (cn > on) return VersionIs.GreaterThan; if (on > cn) return VersionIs.LessThan; if (!isNaN(cn) && isNaN(on)) return VersionIs.GreaterThan; if (isNaN(cn) && !isNaN(on)) return VersionIs.LessThan; } return VersionIs.EqualTo; } const isVueSuperiorOrEqualTo3dotFive = versionCompare(version, "3.5.0") === -1 ? false : true; //#endregion //#region src/utils/randomId.ts function uniqueIDNuxt() { return Math.floor(Math.random() * Date.now()).toString(); } function randomId() { if (typeof window === "undefined") return uniqueIDNuxt(); else { const uint32 = window.crypto.getRandomValues(new Uint32Array(1))[0]; return uint32.toString(10); } } //#endregion //#region src/utils/state.utils.ts function tryOnScopeDispose(fn) { if (getCurrentScope()) { onScopeDispose(fn); return true; } return false; } function createGlobalState(stateFactory) { let initialized = false; let state; const scope = effectScope(true); return (...args) => { if (!initialized) { state = scope.run(() => stateFactory(...args)); initialized = true; } return state; }; } //#endregion //#region src/core/useRegle/guards/ruleDef.guards.ts function isNestedRulesDef(state, rules) { return isRefObject(state) || isObject(rules.value) && !isEmpty(rules.value) && !Object.entries(rules.value).some(([key, rule]) => isRuleDef(rule) || typeof rule === "function"); } function isCollectionRulesDef(rules, state, schemaMode = false) { return !!rules.value && isObject(rules.value) && "$each" in rules.value || schemaMode && Array.isArray(state.value) && state.value.some(isObject) || Array.isArray(state.value) && state.value.some(isObject); } function isValidatorRulesDef(rules) { return !!rules.value && isObject(rules.value); } function isRuleDef(rule) { return isObject(rule) && "_validator" in rule; } function isFormRuleDefinition(rule) { if (typeof rule.value === "function") { if ("_validator" in rule.value) return true; return false; } return true; } //#endregion //#region src/core/useRegle/guards/rule.status.guards.ts function isNestedRulesStatus(rule) { return isObject(rule) && "$fields" in rule; } function isFieldStatus(rule) { return !!rule && "$rules" in rule; } //#endregion //#region src/core/useRegle/useErrors.ts function extractRulesErrors({ field, silent = false }) { return Object.entries(field.$rules ?? {}).map(([_, rule]) => { if (silent && !rule.$valid) return rule.$message; else if (!rule.$valid && field.$error && !rule.$validating) return rule.$message; return null; }).filter((msg) => !!msg).reduce((acc, value) => { if (typeof value === "string") return acc?.concat([value]); else return acc?.concat(value); }, []).concat(field.$error ? field.$externalErrors ?? [] : []).concat(field.$error ? field.$schemaErrors ?? [] : []); } function extractRulesTooltips({ field }) { return Object.entries(field.$rules ?? {}).map(([_, rule]) => rule.$tooltip).filter((tooltip) => !!tooltip).reduce((acc, value) => { if (typeof value === "string") return acc?.concat([value]); else return acc?.concat(value); }, []); } function isCollectionError(errors) { return isObject(errors) && "$each" in errors; } function flatErrors(errors, options) { const { includePath = false } = options ?? {}; if (Array.isArray(errors) && errors.every((err) => !isObject(err))) return errors; else if (isCollectionError(errors)) { const selfErrors = includePath ? errors.$self?.map((err) => ({ error: err, path: "" })) ?? [] : errors.$self ?? []; const eachErrors = errors.$each?.map((err) => iterateErrors(err, includePath)) ?? []; return selfErrors?.concat(eachErrors.flat()); } else return Object.entries(errors).map(([key, value]) => iterateErrors(value, includePath, [key])).flat(); } function iterateErrors(errors, includePath = false, _path) { const path = includePath && !_path ? [] : _path; if (Array.isArray(errors) && errors.every((err) => !isObject(err))) { if (includePath) return errors.map((err) => ({ error: err, path: path?.join(".") ?? "" })); return errors; } else if (isCollectionError(errors)) { const selfErrors = path?.length ? errors.$self?.map((err) => ({ error: err, path: path.join(".") })) ?? [] : errors.$self ?? []; const eachErrors = errors.$each?.map((err, index) => iterateErrors(err, includePath, path?.concat(index.toString()))) ?? []; return selfErrors?.concat(eachErrors.flat()); } else return Object.entries(errors).map(([key, value]) => iterateErrors(value, includePath, path?.concat(key))).flat(); } //#endregion //#region src/core/useRegle/root/createReactiveRuleStatus.ts function createReactiveRuleStatus({ customMessages, rule, ruleKey, state, path, storage, $debounce, modifiers }) { let scope = effectScope(); let scopeState = {}; let $unwatchState; const $haveAsync = ref(false); const $maybePending = ref(false); const { $pending, $valid, $metadata, $validating } = storage.trySetRuleStatusRef(`${path}.${ruleKey}`); function $watch() { scope = effectScope(); scopeState = scope.run(() => { const $fieldDirty = ref(false); const $fieldError = ref(false); const $fieldInvalid = ref(true); const $fieldPending = ref(false); const $fieldCorrect = ref(false); const $defaultMetadata = computed(() => ({ $value: state.value, $error: $fieldError.value, $dirty: $fieldDirty.value, $pending: $fieldPending.value, $correct: $fieldCorrect.value, $invalid: $fieldInvalid.value, $rule: { $valid: $valid.value, $invalid: !$valid.value, $pending: $pending.value }, $params: $params.value, ...$metadata.value })); const $active = computed(() => { if (isFormRuleDefinition(rule)) if (typeof rule.value.active === "function") return rule.value.active($defaultMetadata.value); else return !!rule.value.active; else return true; }); function computeRuleProcessor(key) { let result = ""; const customProcessor = customMessages ? customMessages[ruleKey]?.[key] : void 0; if (customProcessor) if (typeof customProcessor === "function") result = customProcessor($defaultMetadata.value); else result = customProcessor; if (isFormRuleDefinition(rule)) { const patchedKey = `_${key}_patched`; if (!(customProcessor && !rule.value[patchedKey])) if (typeof rule.value[key] === "function") result = rule.value[key]($defaultMetadata.value); else result = rule.value[key] ?? ""; } return result; } const $message = computed(() => { let message = computeRuleProcessor("message"); if (isEmpty(message)) message = "This field is not valid"; return message; }); const $tooltip = computed(() => { return computeRuleProcessor("tooltip"); }); const $type = computed(() => { if (isFormRuleDefinition(rule) && rule.value.type) return rule.value.type; else return ruleKey; }); const $validator = computed(() => { if (isFormRuleDefinition(rule)) return rule.value.validator; else return rule.value; }); const $params = computed(() => { if (typeof rule.value === "function") return []; return unwrapRuleParameters(rule.value._params ?? []); }); const $path = computed(() => `${path}.${$type.value}`); return { $active, $message, $type, $validator, $params, $path, $tooltip, $fieldCorrect, $fieldError, $fieldDirty, $fieldPending, $fieldInvalid }; }); $unwatchState = watch(scopeState?.$params, () => { if (!modifiers.$silent.value || modifiers.$rewardEarly.value && scopeState.$fieldError.value) $parse(); }); } $watch(); function updatePendingState() { $valid.value = true; if (scopeState.$fieldDirty.value) $pending.value = true; } async function computeAsyncResult() { let ruleResult = false; try { const validator = scopeState.$validator.value; if (typeof validator !== "function") { console.error(`${path}: Incorrect rule format, it needs to be either a function or created with "createRule".`); return false; } const resultOrPromise = validator(state.value, ...scopeState.$params.value); let cachedValue = state.value; updatePendingState(); let validatorResult; if (resultOrPromise instanceof Promise) validatorResult = await resultOrPromise; else validatorResult = resultOrPromise; if (state.value !== cachedValue) return true; if (typeof validatorResult === "boolean") ruleResult = validatorResult; else { const { $valid: $valid$1,...rest } = validatorResult; ruleResult = $valid$1; $metadata.value = rest; } } catch (e) { ruleResult = false; } finally { $pending.value = false; } return ruleResult; } const $computeAsyncDebounce = debounce(computeAsyncResult, $debounce ?? 200); async function $parse() { try { $validating.value = true; let ruleResult = false; $maybePending.value = true; if (isRuleDef(rule.value) && rule.value._async) ruleResult = await $computeAsyncDebounce(); else { const validator = scopeState.$validator.value; const resultOrPromise = validator(state.value, ...scopeState.$params.value); if (resultOrPromise instanceof Promise) console.warn("You used a async validator function on a non-async rule, please use \"async await\" or the \"withAsync\" helper"); else if (resultOrPromise != null) if (typeof resultOrPromise === "boolean") ruleResult = resultOrPromise; else { const { $valid: $valid$1,...rest } = resultOrPromise; ruleResult = $valid$1; $metadata.value = rest; } } $valid.value = ruleResult; return ruleResult; } catch (e) { return false; } finally { $validating.value = false; $maybePending.value = false; } } function $reset() { $valid.value = true; $metadata.value = {}; $pending.value = false; $validating.value = false; $watch(); } function $unwatch() { $unwatchState(); scope.stop(); scope = effectScope(); } return reactive({ ...scopeState, $pending, $valid, $metadata, $haveAsync, $maybePending, $validating, $parse, $unwatch, $watch, $reset }); } //#endregion //#region src/core/useRegle/root/createReactiveFieldStatus.ts function createReactiveFieldStatus({ state, rulesDef, customMessages, path, fieldName, storage, options, externalErrors, schemaErrors, schemaMode, onUnwatch, $isArray, initialState, shortcuts, onValidate }) { let scope = effectScope(); let scopeState; let fieldScopes = []; let $unwatchState; let $unwatchValid; let $unwatchDirty; let $unwatchAsync; let $unwatchRuleFieldValues; let $commit = () => {}; function createReactiveRulesResult() { const declaredRules = rulesDef.value; const storeResult = storage.checkRuleDeclEntry(path, declaredRules); $localOptions.value = Object.fromEntries(Object.entries(declaredRules).filter(([ruleKey]) => ruleKey.startsWith("$"))); $watch(); $rules.value = Object.fromEntries(Object.entries(rulesDef.value).filter(([ruleKey]) => !ruleKey.startsWith("$")).map(([ruleKey, rule]) => { if (rule) { const ruleRef = toRef(() => rule); return [ruleKey, createReactiveRuleStatus({ modifiers: { $silent: scopeState.$silent, $rewardEarly: scopeState.$rewardEarly }, customMessages, rule: ruleRef, ruleKey, state, path, storage, $debounce: $localOptions.value.$debounce })]; } return []; }).filter((ruleDef) => !!ruleDef.length)); scopeState.processShortcuts(); define$commit(); if (storeResult?.valid != null) { scopeState.$dirty.value = storage.getDirtyState(path); if (scopeState.$dirty.value && !scopeState.$silent.value || scopeState.$rewardEarly.value && scopeState.$error.value) $commit(); } storage.addRuleDeclEntry(path, declaredRules); } function define$commit() { $commit = scopeState.$debounce.value ? debounce($commitHandler, scopeState.$debounce.value ?? scopeState.$haveAnyAsyncRule ? 100 : 0) : $commitHandler; } function $unwatch() { if ($rules.value) Object.entries($rules.value).forEach(([_, rule]) => { rule.$unwatch(); }); $unwatchDirty(); $unwatchRuleFieldValues?.(); if (scopeState.$dirty.value) storage.setDirtyEntry(path, scopeState.$dirty.value); $unwatchState?.(); scope.stop(); scope = effectScope(); fieldScopes.forEach((s) => s.stop()); fieldScopes = []; onUnwatch?.(); $unwatchAsync?.(); } function $watch() { if ($rules.value) Object.entries($rules.value).forEach(([_, rule]) => { rule.$watch(); }); scopeState = scope.run(() => { const $dirty = ref(false); const triggerPunishment = ref(false); const $anyDirty = computed(() => $dirty.value); const $debounce$1 = computed(() => { return $localOptions.value.$debounce; }); const $deepCompare = computed(() => { if ($localOptions.value.$deepCompare != null) return $localOptions.value.$deepCompare; return false; }); const $lazy$1 = computed(() => { if ($localOptions.value.$lazy != null) return $localOptions.value.$lazy; else if (unref(options.lazy) != null) return unref(options.lazy); return false; }); const $rewardEarly$1 = computed(() => { if ($localOptions.value.$rewardEarly != null) return $localOptions.value.$rewardEarly; else if (unref(options.rewardEarly) != null) return unref(options.rewardEarly); return false; }); const $clearExternalErrorsOnChange$1 = computed(() => { if ($localOptions.value.$clearExternalErrorsOnChange != null) return $localOptions.value.$clearExternalErrorsOnChange; else if (unref(options.clearExternalErrorsOnChange) != null) return unref(options.clearExternalErrorsOnChange); else if ($silent.value) return false; return true; }); const $silent = computed(() => { if ($rewardEarly$1.value) return true; else if ($localOptions.value.$silent != null) return $localOptions.value.$silent; else if (unref(options.silent) != null) return unref(options.silent); else return false; }); const $autoDirty$1 = computed(() => { if ($localOptions.value.$autoDirty != null) return $localOptions.value.$autoDirty; else if (unref(options.autoDirty) != null) return unref(options.autoDirty); return true; }); const $validating$1 = computed(() => { return Object.entries($rules.value).some(([key, ruleResult]) => { return ruleResult.$validating; }); }); const $silentValue = computed({ get: () => state.value, set(value) { $unwatchState(); state.value = value; define$watchState(); } }); const $error = computed(() => { return $invalid.value && !$pending.value && $dirty.value; }); const $errors = computed(() => { return extractRulesErrors({ field: { $rules: $rules.value, $error: $error.value, $externalErrors: externalErrors?.value, $schemaErrors: schemaErrors?.value } }); }); const $silentErrors = computed(() => { return extractRulesErrors({ field: { $rules: $rules.value, $error: $error.value, $externalErrors: externalErrors?.value, $schemaErrors: schemaErrors?.value }, silent: true }); }); const $edited = computed(() => { if ($dirty.value) { if (initialState.value instanceof Date && state.value instanceof Date) return toDate(initialState.value).getDate() !== toDate(state.value).getDate(); else if (initialState.value == null) return !!state.value; else if (Array.isArray(state.value) && Array.isArray(initialState.value)) return !isEqual(state.value, initialState.value, $localOptions.value.$deepCompare); return initialState.value !== state.value; } return false; }); const $anyEdited = computed(() => $edited.value); const $tooltips = computed(() => { return extractRulesTooltips({ field: { $rules: $rules.value } }); }); const $ready = computed(() => { if ($silent.value) return !($invalid.value || $pending.value); return $anyDirty.value && !($invalid.value || $pending.value); }); const $pending = computed(() => { if (triggerPunishment.value || !$rewardEarly$1.value) return Object.entries($rules.value).some(([key, ruleResult]) => { return ruleResult.$pending; }); return false; }); const $invalid = computed(() => { if (externalErrors?.value?.length || schemaErrors?.value?.length) return true; else if ($inactive.value) return false; else if (!$rewardEarly$1.value || $rewardEarly$1.value) { const result = Object.entries($rules.value).some(([_, ruleResult]) => { return !(ruleResult.$valid && !ruleResult.$maybePending); }); return result; } return false; }); const $name = computed(() => fieldName); const $inactive = computed(() => { if (Object.keys(rulesDef.value).filter(([ruleKey]) => !ruleKey.startsWith("$")).length === 0 && !schemaMode) return true; return false; }); const $correct = computed(() => { if (externalErrors?.value?.length) return false; else if ($inactive.value) return false; else if ($dirty.value && !isEmpty(state.value) && !$validating$1.value && !$pending.value) if (schemaMode) return !schemaErrors?.value?.length; else { const atLeastOneActiveRule = Object.values($rules.value).some((ruleResult) => ruleResult.$active); if (atLeastOneActiveRule) return Object.values($rules.value).filter((ruleResult) => ruleResult.$active).every((ruleResult) => ruleResult.$valid); else return false; } return false; }); const $haveAnyAsyncRule$1 = computed(() => { return Object.entries($rules.value).some(([key, ruleResult]) => { return ruleResult.$haveAsync; }); }); function processShortcuts() { if (shortcuts?.fields) Object.entries(shortcuts.fields).forEach(([key, value]) => { const scope$1 = effectScope(); $shortcuts$1[key] = scope$1.run(() => { const result = ref(); watchEffect(() => { result.value = value(reactive({ $dirty, $externalErrors: externalErrors?.value ?? [], $value: state, $silentValue, $rules, $error, $pending, $invalid, $correct, $errors, $ready, $silentErrors, $anyDirty, $tooltips, $name, $inactive, $edited, $anyEdited })); }); return result; }); fieldScopes.push(scope$1); }); } const $shortcuts$1 = {}; return { $error, $pending, $invalid, $correct, $debounce: $debounce$1, $deepCompare, $lazy: $lazy$1, $errors, $ready, $silentErrors, $rewardEarly: $rewardEarly$1, $autoDirty: $autoDirty$1, $silent, $clearExternalErrorsOnChange: $clearExternalErrorsOnChange$1, $anyDirty, $edited, $anyEdited, $name, $haveAnyAsyncRule: $haveAnyAsyncRule$1, $shortcuts: $shortcuts$1, $validating: $validating$1, $tooltips, $dirty, processShortcuts, $silentValue, $inactive }; }); define$watchState(); $unwatchDirty = watch(scopeState.$dirty, (newDirty) => { storage.setDirtyEntry(path, newDirty); Object.values($rules.value).forEach((rule) => { rule.$fieldDirty = newDirty; }); }); $unwatchRuleFieldValues = watch([ scopeState.$error, scopeState.$correct, scopeState.$invalid, scopeState.$pending ], () => { Object.values($rules.value).forEach((rule) => { rule.$fieldError = scopeState.$error.value; rule.$fieldInvalid = scopeState.$invalid.value; rule.$fieldPending = scopeState.$pending.value; rule.$fieldCorrect = scopeState.$correct.value; }); }); $unwatchAsync = watch(scopeState.$haveAnyAsyncRule, define$commit); } function define$watchState() { $unwatchState = watch(state, () => { if (scopeState.$autoDirty.value && !scopeState.$silent.value) { if (!scopeState.$dirty.value) scopeState.$dirty.value = true; } if (rulesDef.value instanceof Function) createReactiveRulesResult(); if (!scopeState.$silent.value || scopeState.$rewardEarly.value && scopeState.$error.value) $commit(); if (scopeState.$clearExternalErrorsOnChange.value) $clearExternalErrors(); }, { deep: $isArray ? true : isVueSuperiorOrEqualTo3dotFive ? 1 : true }); } function $commitHandler() { Object.values($rules.value).forEach((rule) => { rule.$parse(); }); } const $rules = ref({}); const $localOptions = ref({}); createReactiveRulesResult(); function $reset(options$1, fromParent) { $clearExternalErrors(); scopeState.$dirty.value = false; storage.setDirtyEntry(path, false); if (!fromParent) if (options$1?.toInitialState) state.value = cloneDeep(initialState.value); else if (options$1?.toState) { let newInitialState; if (typeof options$1?.toState === "function") newInitialState = options$1?.toState(); else newInitialState = options$1?.toState; initialState.value = cloneDeep(newInitialState); state.value = cloneDeep(newInitialState); } else initialState.value = isObject(state.value) ? cloneDeep(state.value) : Array.isArray(state.value) ? [...state.value] : state.value; if (options$1?.clearExternalErrors) $clearExternalErrors(); if (!fromParent) Object.entries($rules.value).forEach(([_, rule]) => { rule.$reset(); }); if (!scopeState.$lazy.value && !scopeState.$silent.value && !fromParent) Object.values($rules.value).forEach((rule) => { return rule.$parse(); }); } function $touch(runCommit = true, withConditions = false) { if (!scopeState.$dirty.value) scopeState.$dirty.value = true; if (withConditions && runCommit) { if (!scopeState.$silent.value || scopeState.$rewardEarly.value && scopeState.$error.value) $commit(); } else if (runCommit) $commit(); } async function $validate() { try { if (schemaMode) if (onValidate) { $touch(false); return onValidate(); } else return { valid: false, data: state.value }; const data = state.value; if (!scopeState.$dirty.value) scopeState.$dirty.value = true; else if (!scopeState.$silent.value && scopeState.$dirty.value && !scopeState.$pending.value) return { valid: !scopeState.$error.value, data }; if (schemaMode) return { valid: !schemaErrors?.value?.length, data }; else if (isEmpty($rules.value)) return { valid: true, data }; const results = await Promise.allSettled(Object.entries($rules.value).map(([key, rule]) => { return rule.$parse(); })); const validationResults = results.every((value) => { if (value.status === "fulfilled") return value.value === true; else return false; }); return { valid: validationResults, data }; } catch (e) { return { valid: false, data: state.value }; } } function $extractDirtyFields(filterNullishValues = true) { if (scopeState.$dirty.value) return state.value; if (filterNullishValues) return { _null: true }; return null; } function $clearExternalErrors() { if (externalErrors?.value?.length) externalErrors.value = []; } if (!scopeState.$lazy.value && !scopeState.$dirty.value && !scopeState.$silent.value) $commit(); const { $shortcuts, $validating, $autoDirty, $rewardEarly, $clearExternalErrorsOnChange, $haveAnyAsyncRule, $debounce, $lazy,...restScope } = scopeState; return reactive({ ...restScope, $externalErrors: externalErrors, $value: state, $rules, ...$shortcuts, $reset, $touch, $validate, $unwatch, $watch, $extractDirtyFields, $clearExternalErrors }); } //#endregion //#region src/core/useRegle/root/collections/createReactiveCollectionElement.ts function createCollectionElement({ $id, path, index, options, storage, stateValue, customMessages, rules, externalErrors, schemaErrors, initialState, shortcuts, fieldName, schemaMode }) { const $fieldId = rules.$key ? rules.$key : randomId(); let $path = `${path}.${String($fieldId)}`; if (typeof stateValue.value === "object" && stateValue.value != null) if (!stateValue.value.$id) Object.defineProperties(stateValue.value, { $id: { value: $fieldId, enumerable: false, configurable: false, writable: false } }); else $path = `${path}.${stateValue.value.$id}`; const $externalErrors = toRef(externalErrors?.value ?? [], index); const $schemaErrors = computed(() => schemaErrors?.value?.[index]); const $status = createReactiveChildrenStatus({ state: stateValue, rulesDef: toRef(() => rules), customMessages, path: $path, storage, options, externalErrors: $externalErrors, schemaErrors: $schemaErrors, initialState, shortcuts, fieldName, schemaMode }); if ($status) { const valueId = stateValue.value?.$id; $status.$id = valueId ?? String($fieldId); storage.addArrayStatus($id, $status.$id, $status); } return $status; } //#endregion //#region src/core/useRegle/root/collections/createReactiveCollectionRoot.ts function createReactiveCollectionStatus({ state, rulesDef, customMessages, path, storage, options, externalErrors, schemaErrors, schemaMode, initialState, shortcuts, fieldName }) { let scope = effectScope(); let scopeState; let immediateScope = effectScope(); let immediateScopeState; let collectionScopes = []; if (!Array.isArray(state.value) && !rulesDef.value.$each) return void 0; const $id = ref(); const $value = ref(state.value); const $localOptions = ref({}); let $unwatchState; let $unwatchDirty; const $selfStatus = ref({}); const $eachStatus = storage.getCollectionsEntry(path); immediateScopeState = immediateScope.run(() => { const isPrimitiveArray = computed(() => { if (!state.value?.length) return false; if (Array.isArray(state.value) && state.value.length) return state.value.every((s) => typeof s !== "object"); else if (rulesDef.value.$each && !(rulesDef.value.$each instanceof Function)) return Object.values(rulesDef.value.$each).every((rule) => isRuleDef(rule) || typeof rule === "function"); return false; }); return { isPrimitiveArray }; }); createStatus(); $watch(); function createStatus() { $localOptions.value = Object.fromEntries(Object.entries(rulesDef.value).filter(([ruleKey]) => ruleKey.startsWith("$"))); if (typeof state.value === "object") { if (state.value != null && !state.value?.$id && state.value !== null) { $id.value = randomId(); Object.defineProperties(state.value, { $id: { value: $id.value, enumerable: false, configurable: false, writable: false } }); } else if (state.value?.$id) $id.value = state.value.$id; } $value.value = $selfStatus.value.$value; if (Array.isArray(state.value) && !immediateScopeState.isPrimitiveArray.value) $eachStatus.value = state.value.filter((value) => typeof value === "object").map((value, index) => { const { scope: scope$1, unwrapped } = unwrapGetter(rulesDef.value.$each, toRef(() => value), index); if (scope$1) collectionScopes.push(scope$1); const initialStateRef = toRef(initialState.value ?? [], index); const $externalErrors = toRef(externalErrors?.value ?? {}, `$each`); const $schemaErrors = computed(() => schemaErrors?.value?.$each); const element = createCollectionElement({ $id: $id.value, path, customMessages, rules: unwrapped ?? {}, stateValue: toRef(() => value), index, options, storage, externalErrors: $externalErrors, schemaErrors: $schemaErrors, initialState: initialStateRef, shortcuts, fieldName, schemaMode }); if (element) return element; return null; }).filter((each) => !!each); else $eachStatus.value = []; $selfStatus.value = createReactiveFieldStatus({ state, rulesDef, customMessages, path, storage, options, externalErrors: toRef(externalErrors?.value ?? {}, `$self`), schemaErrors: computed(() => schemaErrors?.value?.$self), $isArray: true, initialState, shortcuts, fieldName, schemaMode }); } function updateStatus() { if (Array.isArray(state.value) && !immediateScopeState.isPrimitiveArray.value) { const previousStatus = cloneDeep($eachStatus.value); $eachStatus.value = state.value.filter((value) => typeof value === "object").map((value, index) => { const currentValue = toRef(() => value); if (value.$id && $eachStatus.value.find((each) => each.$id === value.$id)) { const existingStatus = storage.getArrayStatus($id.value, value.$id); if (existingStatus) { existingStatus.$value = currentValue; return existingStatus; } return null; } else { const { scope: scope$1, unwrapped } = unwrapGetter(rulesDef.value.$each, currentValue, index); if (scope$1) collectionScopes.push(scope$1); const $externalErrors = toRef(externalErrors?.value ?? {}, `$each`); const $schemaErrors = computed(() => schemaErrors?.value?.$each ?? []); const element = createCollectionElement({ $id: $id.value, path, customMessages, rules: unwrapped ?? {}, stateValue: currentValue, index, options, storage, externalErrors: $externalErrors, schemaErrors: $schemaErrors, initialState: toRef(initialState.value ?? [], index), shortcuts, fieldName, schemaMode }); if (element) return element; return null; } }).filter((each) => !!each); previousStatus.filter(($each) => !state.value?.find((f) => $each.$id === f.$id)).forEach((_, index) => { storage.deleteArrayStatus($id.value, index.toString()); }); } else $eachStatus.value = []; } function define$watchState() { $unwatchState = watch(state, () => { if (state.value != null && !Object.hasOwn(state.value, "$id")) createStatus(); else updateStatus(); }, { deep: isVueSuperiorOrEqualTo3dotFive ? 1 : true, flush: "pre" }); $unwatchDirty = watch(state, () => { if (scopeState.$autoDirty.value && !scopeState.$silent.value) $touch(false, true); }, { flush: "pre" }); } function $watch() { define$watchState(); scope = effectScope(); scopeState = scope.run(() => { const $silentValue = computed({ get: () => state.value, set(value) { $unwatchState?.(); $unwatchDirty?.(); state.value = value; define$watchState(); } }); const $dirty = computed(() => { return $selfStatus.value.$dirty && (!$eachStatus.value.length || $eachStatus.value.every((statusOrField) => { return statusOrField.$dirty; })); }); const $anyDirty = computed(() => { return $selfStatus.value.$anyDirty || $eachStatus.value.some((statusOrField) => { return statusOrField.$anyDirty; }); }); const $invalid = computed(() => { return $selfStatus.value.$invalid || $eachStatus.value.some((statusOrField) => { return statusOrField.$invalid; }); }); const $correct = computed(() => { return (isEmpty($selfStatus.value.$rules) ? true : $selfStatus.value.$correct) && (!$eachStatus.value.length || $eachStatus.value.every((statusOrField) => { return statusOrField.$correct || statusOrField.$anyDirty && !statusOrField.$invalid; })); }); const $error = computed(() => { return $selfStatus.value.$error || $eachStatus.value.some((statusOrField) => { return statusOrField.$error; }); }); const $ready = computed(() => { return !($invalid.value || $pending.value); }); const $pending = computed(() => { return $selfStatus.value.$pending || $eachStatus.value.some((statusOrField) => { return statusOrField.$pending; }); }); const $edited = computed(() => { return !!$eachStatus.value.length && $eachStatus.value.every((statusOrField) => { return statusOrField.$edited; }); }); const $anyEdited = computed(() => { return $selfStatus.value.$anyEdited || $eachStatus.value.some((statusOrField) => { return statusOrField.$anyEdited; }); }); const $errors = computed(() => { return { $self: $selfStatus.value.$errors, $each: $eachStatus.value.map(($each) => $each.$errors) }; }); const $silentErrors = computed(() => { return { $self: $selfStatus.value.$silentErrors, $each: $eachStatus.value.map(($each) => $each.$silentErrors) }; }); const $rewardEarly = computed(() => { if ($localOptions.value.$rewardEarly != null) return $localOptions.value.$rewardEarly; else if (unref(options.rewardEarly) != null) return unref(options.rewardEarly); return false; }); const $silent = computed(() => { if ($rewardEarly.value) return true; else if ($localOptions.value.$silent != null) return $localOptions.value.$silent; else if (unref(options.silent) != null) return unref(options.silent); else return false; }); const $autoDirty = computed(() => { if ($localOptions.value.$autoDirty != null) return $localOptions.value.$autoDirty; else if (unref(options.autoDirty) != null) return unref(options.autoDirty); return true; }); const $name = computed(() => fieldName); function processShortcuts() { if (shortcuts?.collections) Object.entries(shortcuts?.collections).forEach(([key, value]) => { const scope$1 = effectScope(); $shortcuts$1[key] = scope$1.run(() => { const result = ref(); watchEffect(() => { result.value = value(reactive({ $dirty, $error, $silentValue, $pending, $invalid, $correct, $errors, $ready,