@regle/core
Version:
Headless form validation library for Vue 3
1,457 lines (1,431 loc) • 84.3 kB
JavaScript
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,