vee-validate
Version:
Painless forms for Vue.js
1,372 lines (1,352 loc) • 138 kB
JavaScript
/**
* vee-validate v4.15.1
* (c) 2025 Abdelrahman Awad
* @license MIT
*/
var VeeValidate = (function (exports, vue) {
'use strict';
function isCallable(fn) {
return typeof fn === 'function';
}
function isNullOrUndefined(value) {
return value === null || value === undefined;
}
const isObject = (obj) => obj !== null && !!obj && typeof obj === 'object' && !Array.isArray(obj);
function isIndex(value) {
return Number(value) >= 0;
}
function toNumber(value) {
const n = parseFloat(value);
return isNaN(n) ? value : n;
}
function isObjectLike(value) {
return typeof value === 'object' && value !== null;
}
function getTag(value) {
if (value == null) {
return value === undefined ? '[object Undefined]' : '[object Null]';
}
return Object.prototype.toString.call(value);
}
// Reference: https://github.com/lodash/lodash/blob/master/isPlainObject.js
function isPlainObject(value) {
if (!isObjectLike(value) || getTag(value) !== '[object Object]') {
return false;
}
if (Object.getPrototypeOf(value) === null) {
return true;
}
let proto = value;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(value) === proto;
}
function merge(target, source) {
Object.keys(source).forEach(key => {
if (isPlainObject(source[key]) && isPlainObject(target[key])) {
if (!target[key]) {
target[key] = {};
}
merge(target[key], source[key]);
return;
}
target[key] = source[key];
});
return target;
}
/**
* Constructs a path with dot paths for arrays to use brackets to be compatible with vee-validate path syntax
*/
function normalizeFormPath(path) {
const pathArr = path.split('.');
if (!pathArr.length) {
return '';
}
let fullPath = String(pathArr[0]);
for (let i = 1; i < pathArr.length; i++) {
if (isIndex(pathArr[i])) {
fullPath += `[${pathArr[i]}]`;
continue;
}
fullPath += `.${pathArr[i]}`;
}
return fullPath;
}
const RULES = {};
/**
* Adds a custom validator to the list of validation rules.
*/
function defineRule(id, validator) {
// makes sure new rules are properly formatted.
guardExtend(id, validator);
RULES[id] = validator;
}
/**
* Gets an already defined rule
*/
function resolveRule(id) {
return RULES[id];
}
/**
* Guards from extension violations.
*/
function guardExtend(id, validator) {
if (isCallable(validator)) {
return;
}
throw new Error(`Extension Error: The validator '${id}' must be a function.`);
}
function set(obj, key, val) {
if (typeof val.value === 'object') val.value = klona(val.value);
if (!val.enumerable || val.get || val.set || !val.configurable || !val.writable || key === '__proto__') {
Object.defineProperty(obj, key, val);
} else obj[key] = val.value;
}
function klona(x) {
if (typeof x !== 'object') return x;
var i=0, k, list, tmp, str=Object.prototype.toString.call(x);
if (str === '[object Object]') {
tmp = Object.create(x.__proto__ || null);
} else if (str === '[object Array]') {
tmp = Array(x.length);
} else if (str === '[object Set]') {
tmp = new Set;
x.forEach(function (val) {
tmp.add(klona(val));
});
} else if (str === '[object Map]') {
tmp = new Map;
x.forEach(function (val, key) {
tmp.set(klona(key), klona(val));
});
} else if (str === '[object Date]') {
tmp = new Date(+x);
} else if (str === '[object RegExp]') {
tmp = new RegExp(x.source, x.flags);
} else if (str === '[object DataView]') {
tmp = new x.constructor( klona(x.buffer) );
} else if (str === '[object ArrayBuffer]') {
tmp = x.slice(0);
} else if (str.slice(-6) === 'Array]') {
// ArrayBuffer.isView(x)
// ~> `new` bcuz `Buffer.slice` => ref
tmp = new x.constructor(x);
}
if (tmp) {
for (list=Object.getOwnPropertySymbols(x); i < list.length; i++) {
set(tmp, list[i], Object.getOwnPropertyDescriptor(x, list[i]));
}
for (i=0, list=Object.getOwnPropertyNames(x); i < list.length; i++) {
if (Object.hasOwnProperty.call(tmp, k=list[i]) && tmp[k] === x[k]) continue;
set(tmp, k, Object.getOwnPropertyDescriptor(x, k));
}
}
return tmp || x;
}
const FormContextKey = Symbol('vee-validate-form');
const PublicFormContextKey = Symbol('vee-validate-form-context');
const FieldContextKey = Symbol('vee-validate-field-instance');
const IS_ABSENT = Symbol('Default empty value');
const isClient = typeof window !== 'undefined';
function isLocator(value) {
return isCallable(value) && !!value.__locatorRef;
}
function isTypedSchema(value) {
return !!value && isCallable(value.parse) && value.__type === 'VVTypedSchema';
}
function isYupValidator(value) {
return !!value && isCallable(value.validate);
}
function hasCheckedAttr(type) {
return type === 'checkbox' || type === 'radio';
}
function isContainerValue(value) {
return isObject(value) || Array.isArray(value);
}
/**
* True if the value is an empty object or array
*/
function isEmptyContainer(value) {
if (Array.isArray(value)) {
return value.length === 0;
}
return isObject(value) && Object.keys(value).length === 0;
}
/**
* Checks if the path opted out of nested fields using `[fieldName]` syntax
*/
function isNotNestedPath(path) {
return /^\[.+\]$/i.test(path);
}
/**
* Checks if an element is a native HTML5 multi-select input element
*/
function isNativeMultiSelect(el) {
return isNativeSelect(el) && el.multiple;
}
/**
* Checks if an element is a native HTML5 select input element
*/
function isNativeSelect(el) {
return el.tagName === 'SELECT';
}
/**
* Checks if a tag name with attrs object will render a native multi-select element
*/
function isNativeMultiSelectNode(tag, attrs) {
// The falsy value array is the values that Vue won't add the `multiple` prop if it has one of these values
const hasTruthyBindingValue = ![false, null, undefined, 0].includes(attrs.multiple) && !Number.isNaN(attrs.multiple);
return tag === 'select' && 'multiple' in attrs && hasTruthyBindingValue;
}
/**
* Checks if a node should have a `:value` binding or not
*
* These nodes should not have a value binding
* For files, because they are not reactive
* For multi-selects because the value binding will reset the value
*/
function shouldHaveValueBinding(tag, attrs) {
return !isNativeMultiSelectNode(tag, attrs) && attrs.type !== 'file' && !hasCheckedAttr(attrs.type);
}
function isFormSubmitEvent(evt) {
return isEvent(evt) && evt.target && 'submit' in evt.target;
}
function isEvent(evt) {
if (!evt) {
return false;
}
if (typeof Event !== 'undefined' && isCallable(Event) && evt instanceof Event) {
return true;
}
// this is for IE and Cypress #3161
/* istanbul ignore next */
if (evt && evt.srcElement) {
return true;
}
return false;
}
function isPropPresent(obj, prop) {
return prop in obj && obj[prop] !== IS_ABSENT;
}
/**
* Compares if two values are the same borrowed from:
* https://github.com/epoberezkin/fast-deep-equal
* We added a case for file matching since `Object.keys` doesn't work with Files.
*
* NB: keys with the value undefined are ignored in the evaluation and considered equal to missing keys.
* */
function isEqual(a, b) {
if (a === b)
return true;
if (a && b && typeof a === 'object' && typeof b === 'object') {
if (a.constructor !== b.constructor)
return false;
// eslint-disable-next-line no-var
var length, i, keys;
if (Array.isArray(a)) {
length = a.length;
if (length != b.length)
return false;
for (i = length; i-- !== 0;)
if (!isEqual(a[i], b[i]))
return false;
return true;
}
if (a instanceof Map && b instanceof Map) {
if (a.size !== b.size)
return false;
for (i of a.entries())
if (!b.has(i[0]))
return false;
for (i of a.entries())
if (!isEqual(i[1], b.get(i[0])))
return false;
return true;
}
// We added this part for file comparison, arguably a little naive but should work for most cases.
// #3911
if (isFile(a) && isFile(b)) {
if (a.size !== b.size)
return false;
if (a.name !== b.name)
return false;
if (a.lastModified !== b.lastModified)
return false;
if (a.type !== b.type)
return false;
return true;
}
if (a instanceof Set && b instanceof Set) {
if (a.size !== b.size)
return false;
for (i of a.entries())
if (!b.has(i[0]))
return false;
return true;
}
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
length = a.length;
if (length != b.length)
return false;
for (i = length; i-- !== 0;)
if (a[i] !== b[i])
return false;
return true;
}
if (a.constructor === RegExp)
return a.source === b.source && a.flags === b.flags;
if (a.valueOf !== Object.prototype.valueOf)
return a.valueOf() === b.valueOf();
if (a.toString !== Object.prototype.toString)
return a.toString() === b.toString();
// Remove undefined values before object comparison
a = normalizeObject(a);
b = normalizeObject(b);
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;) {
// eslint-disable-next-line no-var
var key = keys[i];
if (!isEqual(a[key], b[key]))
return false;
}
return true;
}
// true if both NaN, false otherwise
return a !== a && b !== b;
}
/**
* Returns a new object where keys with an `undefined` value are removed.
*
* @param a object to normalize
*/
function normalizeObject(a) {
return Object.fromEntries(Object.entries(a).filter(([, value]) => value !== undefined));
}
function isFile(a) {
if (!isClient) {
return false;
}
return a instanceof File;
}
function cleanupNonNestedPath(path) {
if (isNotNestedPath(path)) {
return path.replace(/\[|\]/gi, '');
}
return path;
}
function getFromPath(object, path, fallback) {
if (!object) {
return fallback;
}
if (isNotNestedPath(path)) {
return object[cleanupNonNestedPath(path)];
}
const resolvedValue = (path || '')
.split(/\.|\[(\d+)\]/)
.filter(Boolean)
.reduce((acc, propKey) => {
if (isContainerValue(acc) && propKey in acc) {
return acc[propKey];
}
return fallback;
}, object);
return resolvedValue;
}
/**
* Sets a nested property value in a path, creates the path properties if it doesn't exist
*/
function setInPath(object, path, value) {
if (isNotNestedPath(path)) {
object[cleanupNonNestedPath(path)] = value;
return;
}
const keys = path.split(/\.|\[(\d+)\]/).filter(Boolean);
let acc = object;
for (let i = 0; i < keys.length; i++) {
// Last key, set it
if (i === keys.length - 1) {
acc[keys[i]] = value;
return;
}
// Key does not exist, create a container for it
if (!(keys[i] in acc) || isNullOrUndefined(acc[keys[i]])) {
// container can be either an object or an array depending on the next key if it exists
acc[keys[i]] = isIndex(keys[i + 1]) ? [] : {};
}
acc = acc[keys[i]];
}
}
function unset(object, key) {
if (Array.isArray(object) && isIndex(key)) {
object.splice(Number(key), 1);
return;
}
if (isObject(object)) {
delete object[key];
}
}
/**
* Removes a nested property from object
*/
function unsetPath(object, path) {
if (isNotNestedPath(path)) {
delete object[cleanupNonNestedPath(path)];
return;
}
const keys = path.split(/\.|\[(\d+)\]/).filter(Boolean);
let acc = object;
for (let i = 0; i < keys.length; i++) {
// Last key, unset it
if (i === keys.length - 1) {
unset(acc, keys[i]);
break;
}
// Key does not exist, exit
if (!(keys[i] in acc) || isNullOrUndefined(acc[keys[i]])) {
break;
}
acc = acc[keys[i]];
}
const pathValues = keys.map((_, idx) => {
return getFromPath(object, keys.slice(0, idx).join('.'));
});
for (let i = pathValues.length - 1; i >= 0; i--) {
if (!isEmptyContainer(pathValues[i])) {
continue;
}
if (i === 0) {
unset(object, keys[0]);
continue;
}
unset(pathValues[i - 1], keys[i - 1]);
}
}
/**
* A typed version of Object.keys
*/
function keysOf(record) {
return Object.keys(record);
}
// Uses same component provide as its own injections
// Due to changes in https://github.com/vuejs/vue-next/pull/2424
function injectWithSelf(symbol, def = undefined) {
const vm = vue.getCurrentInstance();
return (vm === null || vm === void 0 ? void 0 : vm.provides[symbol]) || vue.inject(symbol, def);
}
function resolveNextCheckboxValue(currentValue, checkedValue, uncheckedValue) {
if (Array.isArray(currentValue)) {
const newVal = [...currentValue];
// Use isEqual since checked object values can possibly fail the equality check #3883
const idx = newVal.findIndex(v => isEqual(v, checkedValue));
idx >= 0 ? newVal.splice(idx, 1) : newVal.push(checkedValue);
return newVal;
}
return isEqual(currentValue, checkedValue) ? uncheckedValue : checkedValue;
}
function debounceAsync(inner, ms = 0) {
let timer = null;
let resolves = [];
return function (...args) {
// Run the function after a certain amount of time
if (timer) {
clearTimeout(timer);
}
// @ts-expect-error timer is a number
timer = setTimeout(() => {
// Get the result of the inner function, then apply it to the resolve function of
// each promise that has been created since the last time the inner function was run
const result = inner(...args);
resolves.forEach(r => r(result));
resolves = [];
}, ms);
return new Promise(resolve => resolves.push(resolve));
};
}
function applyModelModifiers(value, modifiers) {
if (!isObject(modifiers)) {
return value;
}
if (modifiers.number) {
return toNumber(value);
}
return value;
}
function withLatest(fn, onDone) {
let latestRun;
return async function runLatest(...args) {
const pending = fn(...args);
latestRun = pending;
const result = await pending;
if (pending !== latestRun) {
return result;
}
latestRun = undefined;
return onDone(result, args);
};
}
function computedDeep({ get, set }) {
const baseRef = vue.ref(klona(get()));
vue.watch(get, newValue => {
if (isEqual(newValue, baseRef.value)) {
return;
}
baseRef.value = klona(newValue);
}, {
deep: true,
});
vue.watch(baseRef, newValue => {
if (isEqual(newValue, get())) {
return;
}
set(klona(newValue));
}, {
deep: true,
});
return baseRef;
}
function normalizeErrorItem(message) {
return Array.isArray(message) ? message : message ? [message] : [];
}
function resolveFieldOrPathState(path) {
const form = injectWithSelf(FormContextKey);
const state = path ? vue.computed(() => form === null || form === void 0 ? void 0 : form.getPathState(vue.toValue(path))) : undefined;
const field = path ? undefined : vue.inject(FieldContextKey);
if (!field && !(state === null || state === void 0 ? void 0 : state.value)) ;
return state || field;
}
function omit(obj, keys) {
const target = {};
for (const key in obj) {
if (!keys.includes(key)) {
target[key] = obj[key];
}
}
return target;
}
function debounceNextTick(inner) {
let lastTick = null;
let resolves = [];
return function (...args) {
// Run the function after a certain amount of time
const thisTick = vue.nextTick(() => {
if (lastTick !== thisTick) {
return;
}
// Get the result of the inner function, then apply it to the resolve function of
// each promise that has been created since the last time the inner function was run
const result = inner(...args);
resolves.forEach(r => r(result));
resolves = [];
lastTick = null;
});
lastTick = thisTick;
return new Promise(resolve => resolves.push(resolve));
};
}
function normalizeChildren(tag, context, slotProps) {
if (!context.slots.default) {
return context.slots.default;
}
if (typeof tag === 'string' || !tag) {
return context.slots.default(slotProps());
}
return {
default: () => { var _a, _b; return (_b = (_a = context.slots).default) === null || _b === void 0 ? void 0 : _b.call(_a, slotProps()); },
};
}
/**
* Vue adds a `_value` prop at the moment on the input elements to store the REAL value on them, real values are different than the `value` attribute
* as they do not get casted to strings unlike `el.value` which preserves user-code behavior
*/
function getBoundValue(el) {
if (hasValueBinding(el)) {
return el._value;
}
return undefined;
}
/**
* Vue adds a `_value` prop at the moment on the input elements to store the REAL value on them, real values are different than the `value` attribute
* as they do not get casted to strings unlike `el.value` which preserves user-code behavior
*/
function hasValueBinding(el) {
return '_value' in el;
}
function parseInputValue(el) {
if (el.type === 'number') {
return Number.isNaN(el.valueAsNumber) ? el.value : el.valueAsNumber;
}
if (el.type === 'range') {
return Number.isNaN(el.valueAsNumber) ? el.value : el.valueAsNumber;
}
return el.value;
}
function normalizeEventValue(value) {
if (!isEvent(value)) {
return value;
}
const input = value.target;
// Vue sets the current bound value on `_value` prop
// for checkboxes it it should fetch the value binding type as is (boolean instead of string)
if (hasCheckedAttr(input.type) && hasValueBinding(input)) {
return getBoundValue(input);
}
if (input.type === 'file' && input.files) {
const files = Array.from(input.files);
return input.multiple ? files : files[0];
}
if (isNativeMultiSelect(input)) {
return Array.from(input.options)
.filter(opt => opt.selected && !opt.disabled)
.map(getBoundValue);
}
// makes sure we get the actual `option` bound value
// #3440
if (isNativeSelect(input)) {
const selectedOption = Array.from(input.options).find(opt => opt.selected);
return selectedOption ? getBoundValue(selectedOption) : input.value;
}
return parseInputValue(input);
}
/**
* Normalizes the given rules expression.
*/
function normalizeRules(rules) {
const acc = {};
Object.defineProperty(acc, '_$$isNormalized', {
value: true,
writable: false,
enumerable: false,
configurable: false,
});
if (!rules) {
return acc;
}
// Object is already normalized, skip.
if (isObject(rules) && rules._$$isNormalized) {
return rules;
}
if (isObject(rules)) {
return Object.keys(rules).reduce((prev, curr) => {
const params = normalizeParams(rules[curr]);
if (rules[curr] !== false) {
prev[curr] = buildParams(params);
}
return prev;
}, acc);
}
/* istanbul ignore if */
if (typeof rules !== 'string') {
return acc;
}
return rules.split('|').reduce((prev, rule) => {
const parsedRule = parseRule(rule);
if (!parsedRule.name) {
return prev;
}
prev[parsedRule.name] = buildParams(parsedRule.params);
return prev;
}, acc);
}
/**
* Normalizes a rule param.
*/
function normalizeParams(params) {
if (params === true) {
return [];
}
if (Array.isArray(params)) {
return params;
}
if (isObject(params)) {
return params;
}
return [params];
}
function buildParams(provided) {
const mapValueToLocator = (value) => {
// A target param using interpolation
if (typeof value === 'string' && value[0] === '@') {
return createLocator(value.slice(1));
}
return value;
};
if (Array.isArray(provided)) {
return provided.map(mapValueToLocator);
}
// #3073
if (provided instanceof RegExp) {
return [provided];
}
return Object.keys(provided).reduce((prev, key) => {
prev[key] = mapValueToLocator(provided[key]);
return prev;
}, {});
}
/**
* Parses a rule string expression.
*/
const parseRule = (rule) => {
let params = [];
const name = rule.split(':')[0];
if (rule.includes(':')) {
params = rule.split(':').slice(1).join(':').split(',');
}
return { name, params };
};
function createLocator(value) {
const locator = (crossTable) => {
var _a;
const val = (_a = getFromPath(crossTable, value)) !== null && _a !== void 0 ? _a : crossTable[value];
return val;
};
locator.__locatorRef = value;
return locator;
}
function extractLocators(params) {
if (Array.isArray(params)) {
return params.filter(isLocator);
}
return keysOf(params)
.filter(key => isLocator(params[key]))
.map(key => params[key]);
}
const DEFAULT_CONFIG = {
generateMessage: ({ field }) => `${field} is not valid.`,
bails: true,
validateOnBlur: true,
validateOnChange: true,
validateOnInput: false,
validateOnModelUpdate: true,
};
let currentConfig = Object.assign({}, DEFAULT_CONFIG);
const getConfig = () => currentConfig;
const setConfig = (newConf) => {
currentConfig = Object.assign(Object.assign({}, currentConfig), newConf);
};
const configure = setConfig;
/**
* Validates a value against the rules.
*/
async function validate(value, rules, options = {}) {
const shouldBail = options === null || options === void 0 ? void 0 : options.bails;
const field = {
name: (options === null || options === void 0 ? void 0 : options.name) || '{field}',
rules,
label: options === null || options === void 0 ? void 0 : options.label,
bails: shouldBail !== null && shouldBail !== void 0 ? shouldBail : true,
formData: (options === null || options === void 0 ? void 0 : options.values) || {},
};
const result = await _validate(field, value);
return Object.assign(Object.assign({}, result), { valid: !result.errors.length });
}
/**
* Starts the validation process.
*/
async function _validate(field, value) {
const rules = field.rules;
if (isTypedSchema(rules) || isYupValidator(rules)) {
return validateFieldWithTypedSchema(value, Object.assign(Object.assign({}, field), { rules }));
}
// if a generic function or chain of generic functions
if (isCallable(rules) || Array.isArray(rules)) {
const ctx = {
field: field.label || field.name,
name: field.name,
label: field.label,
form: field.formData,
value,
};
// Normalize the pipeline
const pipeline = Array.isArray(rules) ? rules : [rules];
const length = pipeline.length;
const errors = [];
for (let i = 0; i < length; i++) {
const rule = pipeline[i];
const result = await rule(value, ctx);
const isValid = typeof result !== 'string' && !Array.isArray(result) && result;
if (isValid) {
continue;
}
if (Array.isArray(result)) {
errors.push(...result);
}
else {
const message = typeof result === 'string' ? result : _generateFieldError(ctx);
errors.push(message);
}
if (field.bails) {
return {
errors,
};
}
}
return {
errors,
};
}
const normalizedContext = Object.assign(Object.assign({}, field), { rules: normalizeRules(rules) });
const errors = [];
const rulesKeys = Object.keys(normalizedContext.rules);
const length = rulesKeys.length;
for (let i = 0; i < length; i++) {
const rule = rulesKeys[i];
const result = await _test(normalizedContext, value, {
name: rule,
params: normalizedContext.rules[rule],
});
if (result.error) {
errors.push(result.error);
if (field.bails) {
return {
errors,
};
}
}
}
return {
errors,
};
}
function isYupError(err) {
return !!err && err.name === 'ValidationError';
}
function yupToTypedSchema(yupSchema) {
const schema = {
__type: 'VVTypedSchema',
async parse(values, context) {
var _a;
try {
const output = await yupSchema.validate(values, { abortEarly: false, context: (context === null || context === void 0 ? void 0 : context.formData) || {} });
return {
output,
errors: [],
};
}
catch (err) {
// Yup errors have a name prop one them.
// https://github.com/jquense/yup#validationerrorerrors-string--arraystring-value-any-path-string
if (!isYupError(err)) {
throw err;
}
if (!((_a = err.inner) === null || _a === void 0 ? void 0 : _a.length) && err.errors.length) {
return { errors: [{ path: err.path, errors: err.errors }] };
}
const errors = err.inner.reduce((acc, curr) => {
const path = curr.path || '';
if (!acc[path]) {
acc[path] = { errors: [], path };
}
acc[path].errors.push(...curr.errors);
return acc;
}, {});
return { errors: Object.values(errors) };
}
},
};
return schema;
}
/**
* Handles yup validation
*/
async function validateFieldWithTypedSchema(value, context) {
const typedSchema = isTypedSchema(context.rules) ? context.rules : yupToTypedSchema(context.rules);
const result = await typedSchema.parse(value, { formData: context.formData });
const messages = [];
for (const error of result.errors) {
if (error.errors.length) {
messages.push(...error.errors);
}
}
return {
value: result.value,
errors: messages,
};
}
/**
* Tests a single input value against a rule.
*/
async function _test(field, value, rule) {
const validator = resolveRule(rule.name);
if (!validator) {
throw new Error(`No such validator '${rule.name}' exists.`);
}
const params = fillTargetValues(rule.params, field.formData);
const ctx = {
field: field.label || field.name,
name: field.name,
label: field.label,
value,
form: field.formData,
rule: Object.assign(Object.assign({}, rule), { params }),
};
const result = await validator(value, params, ctx);
if (typeof result === 'string') {
return {
error: result,
};
}
return {
error: result ? undefined : _generateFieldError(ctx),
};
}
/**
* Generates error messages.
*/
function _generateFieldError(fieldCtx) {
const message = getConfig().generateMessage;
if (!message) {
return 'Field is invalid';
}
return message(fieldCtx);
}
function fillTargetValues(params, crossTable) {
const normalize = (value) => {
if (isLocator(value)) {
return value(crossTable);
}
return value;
};
if (Array.isArray(params)) {
return params.map(normalize);
}
return Object.keys(params).reduce((acc, param) => {
acc[param] = normalize(params[param]);
return acc;
}, {});
}
async function validateTypedSchema(schema, values) {
const typedSchema = isTypedSchema(schema) ? schema : yupToTypedSchema(schema);
const validationResult = await typedSchema.parse(klona(values), { formData: klona(values) });
const results = {};
const errors = {};
for (const error of validationResult.errors) {
const messages = error.errors;
// Fixes issue with path mapping with Yup 1.0 including quotes around array indices
const path = (error.path || '').replace(/\["(\d+)"\]/g, (_, m) => {
return `[${m}]`;
});
results[path] = { valid: !messages.length, errors: messages };
if (messages.length) {
errors[path] = messages[0];
}
}
return {
valid: !validationResult.errors.length,
results,
errors,
values: validationResult.value,
source: 'schema',
};
}
async function validateObjectSchema(schema, values, opts) {
const paths = keysOf(schema);
const validations = paths.map(async (path) => {
var _a, _b, _c;
const strings = (_a = opts === null || opts === void 0 ? void 0 : opts.names) === null || _a === void 0 ? void 0 : _a[path];
const fieldResult = await validate(getFromPath(values, path), schema[path], {
name: (strings === null || strings === void 0 ? void 0 : strings.name) || path,
label: strings === null || strings === void 0 ? void 0 : strings.label,
values: values,
bails: (_c = (_b = opts === null || opts === void 0 ? void 0 : opts.bailsMap) === null || _b === void 0 ? void 0 : _b[path]) !== null && _c !== void 0 ? _c : true,
});
return Object.assign(Object.assign({}, fieldResult), { path });
});
let isAllValid = true;
const validationResults = await Promise.all(validations);
const results = {};
const errors = {};
for (const result of validationResults) {
results[result.path] = {
valid: result.valid,
errors: result.errors,
};
if (!result.valid) {
isAllValid = false;
errors[result.path] = result.errors[0];
}
}
return {
valid: isAllValid,
results,
errors,
source: 'schema',
};
}
let ID_COUNTER = 0;
function useFieldState(path, init) {
const { value, initialValue, setInitialValue } = _useFieldValue(path, init.modelValue, init.form);
if (!init.form) {
const { errors, setErrors } = createFieldErrors();
const id = ID_COUNTER >= Number.MAX_SAFE_INTEGER ? 0 : ++ID_COUNTER;
const meta = createFieldMeta(value, initialValue, errors, init.schema);
function setState(state) {
var _a;
if ('value' in state) {
value.value = state.value;
}
if ('errors' in state) {
setErrors(state.errors);
}
if ('touched' in state) {
meta.touched = (_a = state.touched) !== null && _a !== void 0 ? _a : meta.touched;
}
if ('initialValue' in state) {
setInitialValue(state.initialValue);
}
}
return {
id,
path,
value,
initialValue,
meta,
flags: { pendingUnmount: { [id]: false }, pendingReset: false },
errors,
setState,
};
}
const state = init.form.createPathState(path, {
bails: init.bails,
label: init.label,
type: init.type,
validate: init.validate,
schema: init.schema,
});
const errors = vue.computed(() => state.errors);
function setState(state) {
var _a, _b, _c;
if ('value' in state) {
value.value = state.value;
}
if ('errors' in state) {
(_a = init.form) === null || _a === void 0 ? void 0 : _a.setFieldError(vue.unref(path), state.errors);
}
if ('touched' in state) {
(_b = init.form) === null || _b === void 0 ? void 0 : _b.setFieldTouched(vue.unref(path), (_c = state.touched) !== null && _c !== void 0 ? _c : false);
}
if ('initialValue' in state) {
setInitialValue(state.initialValue);
}
}
return {
id: Array.isArray(state.id) ? state.id[state.id.length - 1] : state.id,
path,
value,
errors,
meta: state,
initialValue,
flags: state.__flags,
setState,
};
}
/**
* Creates the field value and resolves the initial value
*/
function _useFieldValue(path, modelValue, form) {
const modelRef = vue.ref(vue.unref(modelValue));
function resolveInitialValue() {
if (!form) {
return vue.unref(modelRef);
}
return getFromPath(form.initialValues.value, vue.unref(path), vue.unref(modelRef));
}
function setInitialValue(value) {
if (!form) {
modelRef.value = value;
return;
}
form.setFieldInitialValue(vue.unref(path), value, true);
}
const initialValue = vue.computed(resolveInitialValue);
// if no form is associated, use a regular ref.
if (!form) {
const value = vue.ref(resolveInitialValue());
return {
value,
initialValue,
setInitialValue,
};
}
// to set the initial value, first check if there is a current value, if there is then use it.
// otherwise use the configured initial value if it exists.
// prioritize model value over form values
// #3429
const currentValue = resolveModelValue(modelValue, form, initialValue, path);
form.stageInitialValue(vue.unref(path), currentValue, true);
// otherwise use a computed setter that triggers the `setFieldValue`
const value = vue.computed({
get() {
return getFromPath(form.values, vue.unref(path));
},
set(newVal) {
form.setFieldValue(vue.unref(path), newVal, false);
},
});
return {
value,
initialValue,
setInitialValue,
};
}
/*
to set the initial value, first check if there is a current value, if there is then use it.
otherwise use the configured initial value if it exists.
prioritize model value over form values
#3429
*/
function resolveModelValue(modelValue, form, initialValue, path) {
if (vue.isRef(modelValue)) {
return vue.unref(modelValue);
}
if (modelValue !== undefined) {
return modelValue;
}
return getFromPath(form.values, vue.unref(path), vue.unref(initialValue));
}
/**
* Creates meta flags state and some associated effects with them
*/
function createFieldMeta(currentValue, initialValue, errors, schema) {
const isRequired = vue.computed(() => { var _a, _b, _c; return (_c = (_b = (_a = vue.toValue(schema)) === null || _a === void 0 ? void 0 : _a.describe) === null || _b === void 0 ? void 0 : _b.call(_a).required) !== null && _c !== void 0 ? _c : false; });
const meta = vue.reactive({
touched: false,
pending: false,
valid: true,
required: isRequired,
validated: !!vue.unref(errors).length,
initialValue: vue.computed(() => vue.unref(initialValue)),
dirty: vue.computed(() => {
return !isEqual(vue.unref(currentValue), vue.unref(initialValue));
}),
});
vue.watch(errors, value => {
meta.valid = !value.length;
}, {
immediate: true,
flush: 'sync',
});
return meta;
}
/**
* Creates the error message state for the field state
*/
function createFieldErrors() {
const errors = vue.ref([]);
return {
errors,
setErrors: (messages) => {
errors.value = normalizeErrorItem(messages);
},
};
}
/**
* Creates a field composite.
*/
function useField(path, rules, opts) {
if (hasCheckedAttr(opts === null || opts === void 0 ? void 0 : opts.type)) {
return useFieldWithChecked(path, rules, opts);
}
return _useField(path, rules, opts);
}
function _useField(path, rules, opts) {
const { initialValue: modelValue, validateOnMount, bails, type, checkedValue, label, validateOnValueUpdate, uncheckedValue, controlled, keepValueOnUnmount, syncVModel, form: controlForm, } = normalizeOptions(opts);
const injectedForm = controlled ? injectWithSelf(FormContextKey) : undefined;
const form = controlForm || injectedForm;
const name = vue.computed(() => normalizeFormPath(vue.toValue(path)));
const validator = vue.computed(() => {
const schema = vue.toValue(form === null || form === void 0 ? void 0 : form.schema);
if (schema) {
return undefined;
}
const rulesValue = vue.unref(rules);
if (isYupValidator(rulesValue) ||
isTypedSchema(rulesValue) ||
isCallable(rulesValue) ||
Array.isArray(rulesValue)) {
return rulesValue;
}
return normalizeRules(rulesValue);
});
const isTyped = !isCallable(validator.value) && isTypedSchema(vue.toValue(rules));
const { id, value, initialValue, meta, setState, errors, flags } = useFieldState(name, {
modelValue,
form,
bails,
label,
type,
validate: validator.value ? validate$1 : undefined,
schema: isTyped ? rules : undefined,
});
const errorMessage = vue.computed(() => errors.value[0]);
if (syncVModel) {
useVModel({
value,
prop: syncVModel,
handleChange,
shouldValidate: () => validateOnValueUpdate && !flags.pendingReset,
});
}
/**
* Handles common onBlur meta update
*/
const handleBlur = (evt, shouldValidate = false) => {
meta.touched = true;
if (shouldValidate) {
validateWithStateMutation();
}
};
async function validateCurrentValue(mode) {
var _a, _b;
if (form === null || form === void 0 ? void 0 : form.validateSchema) {
const { results } = await form.validateSchema(mode);
return (_a = results[vue.toValue(name)]) !== null && _a !== void 0 ? _a : { valid: true, errors: [] };
}
if (validator.value) {
return validate(value.value, validator.value, {
name: vue.toValue(name),
label: vue.toValue(label),
values: (_b = form === null || form === void 0 ? void 0 : form.values) !== null && _b !== void 0 ? _b : {},
bails,
});
}
return { valid: true, errors: [] };
}
const validateWithStateMutation = withLatest(async () => {
meta.pending = true;
meta.validated = true;
return validateCurrentValue('validated-only');
}, result => {
if (flags.pendingUnmount[field.id]) {
return result;
}
setState({ errors: result.errors });
meta.pending = false;
meta.valid = result.valid;
return result;
});
const validateValidStateOnly = withLatest(async () => {
return validateCurrentValue('silent');
}, result => {
meta.valid = result.valid;
return result;
});
function validate$1(opts) {
if ((opts === null || opts === void 0 ? void 0 : opts.mode) === 'silent') {
return validateValidStateOnly();
}
return validateWithStateMutation();
}
// Common input/change event handler
function handleChange(e, shouldValidate = true) {
const newValue = normalizeEventValue(e);
setValue(newValue, shouldValidate);
}
// Runs the initial validation
vue.onMounted(() => {
if (validateOnMount) {
return validateWithStateMutation();
}
// validate self initially if no form was handling this
// forms should have their own initial silent validation run to make things more efficient
if (!form || !form.validateSchema) {
validateValidStateOnly();
}
});
function setTouched(isTouched) {
meta.touched = isTouched;
}
function resetField(state) {
var _a;
const newValue = state && 'value' in state ? state.value : initialValue.value;
setState({
value: klona(newValue),
initialValue: klona(newValue),
touched: (_a = state === null || state === void 0 ? void 0 : state.touched) !== null && _a !== void 0 ? _a : false,
errors: (state === null || state === void 0 ? void 0 : state.errors) || [],
});
meta.pending = false;
meta.validated = false;
validateValidStateOnly();
}
const vm = vue.getCurrentInstance();
function setValue(newValue, shouldValidate = true) {
value.value = vm && syncVModel ? applyModelModifiers(newValue, vm.props.modelModifiers) : newValue;
const validateFn = shouldValidate ? validateWithStateMutation : validateValidStateOnly;
validateFn();
}
function setErrors(errors) {
setState({ errors: Array.isArray(errors) ? errors : [errors] });
}
const valueProxy = vue.computed({
get() {
return value.value;
},
set(newValue) {
setValue(newValue, validateOnValueUpdate);
},
});
const field = {
id,
name,
label,
value: valueProxy,
meta,
errors,
errorMessage,
type,
checkedValue,
uncheckedValue,
bails,
keepValueOnUnmount,
resetField,
handleReset: () => resetField(),
validate: validate$1,
handleChange,
handleBlur,
setState,
setTouched,
setErrors,
setValue,
};
vue.provide(FieldContextKey, field);
if (vue.isRef(rules) && typeof vue.unref(rules) !== 'function') {
vue.watch(rules, (value, oldValue) => {
if (isEqual(value, oldValue)) {
return;
}
meta.validated ? validateWithStateMutation() : validateValidStateOnly();
}, {
deep: true,
});
}
// if no associated form return the field API immediately
if (!form) {
return field;
}
// associate the field with the given form
// extract cross-field dependencies in a computed prop
const dependencies = vue.computed(() => {
const rulesVal = validator.value;
// is falsy, a function schema or a yup schema
if (!rulesVal ||
isCallable(rulesVal) ||
isYupValidator(rulesVal) ||
isTypedSchema(rulesVal) ||
Array.isArray(rulesVal)) {
return {};
}
return Object.keys(rulesVal).reduce((acc, rule) => {
const deps = extractLocators(rulesVal[rule])
.map((dep) => dep.__locatorRef)
.reduce((depAcc, depName) => {
const depValue = getFromPath(form.values, depName) || form.values[depName];
if (depValue !== undefined) {
depAcc[depName] = depValue;
}