UNPKG

vee-validate

Version:
1,478 lines (1,458 loc) 124 kB
/** * vee-validate v4.15.1 * (c) 2025 Abdelrahman Awad * @license MIT */ 'use strict'; var vue = require('vue'); 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; } return depAcc; }, {}); Object.assign(acc, deps); return acc; }, {}); }); // Adds a watcher that runs the validation whenever field dependencies change vue.watch(dependencies, (deps, oldDeps) => { // Skip if no dependencies or if the field wasn't manipulated if (!Object.keys(deps).length) { return; } const shouldValidate = !isEqual(deps, oldDeps); if (shouldValidate) { meta.validated ? validateWithStateMutation() : validateValidStateOnly(); } }); vue.onBeforeUnmount(() => { var _a; const shouldKeepValue = (_a = vue.toValue(field.keepValueOnUnmount)) !== null && _a !== void 0 ? _a : vue.toValue(form.keepValuesOnUnmount); const path = vue.toValue(name); if (shouldKeepValue || !form || flags.pendingUnmount[field.id]) { form === null || form === void 0 ? void 0 : form.removePathState(path, id); return; } flags.pendingUnmount[field.id] = true; const pathState = form.getPathState(path); const matchesId = Array.isArray(pathState === null || pathState === void 0 ? void 0 : pathState.id) && (pathState === null || pathState === void 0 ? void 0 : pathState.multiple) ? pathState === null || pathState === void 0 ? void 0 : pathState.id.includes(field.id) : (pathState === null || pathState === void 0 ? void 0 : pathState.id) === field.id; if (!matchesId) { return; } if ((pathState === null || pathState === void 0 ? void 0 : pathState.multiple) && Array.isArray(pathState.value)) { const valueIdx = pathState.value.findIndex(i => isEqual(i, vue.toValue(field.checkedValue))); if (valueIdx > -1) { const newVal = [...pathState.value]; newVal.splice(valueIdx, 1); form.setFieldValue(path, newVal); } if (Array.isArray(pathState.id)) { pathState.id.splice(pathState.id.indexOf(field.id), 1); } } else { form.unsetPathValue(vue.toValue(name)); } form.removePathState(path, id); }); return field; } /** * Normalizes partial field options to include the full options */ function normalizeOptions(opts) { const defaults = () => ({ initialValue: undefined, validateOnMount: false, bails: true, label: undefined, validateOnValueUpdate: true, keepValueOnUnmount: undefined, syncVModel: false, controlled: true, }); const isVModelSynced = !!(opts === null || opts === void 0 ? void 0 : opts.syncVModel); const modelPropName = typeof (opts === null || opts === void 0 ? void 0 : opts.syncVModel) === 'string' ? opts.syncVModel : (opts === null || opts === void 0 ? void 0 : opts.modelPropName) || 'modelValue'; const initialValue = isVModelSynced && !('initialValue' in (opts || {})) ? getCurrentModelValue(vue.getCurrentInstance(), modelPropName) : opts === null || opts === void 0 ? void 0 : opts.initialValue; if (!opts) { return Object.assign(Object.assign({}, defaults()), { initialValue }); } // TODO: Deprecate this in next major release const checkedValue = 'valueProp' in opts ? opts.valueProp : opts.checkedValue; const controlled = 'standalone' in opts ? !opts.standalone : opts.controlled; const syncVModel = (opts === null || opts === void 0 ? void 0 : opts.modelPropName) || (opts === null || opts === void 0 ? void 0 : opts.syncVModel) || false; return Object.assign(Object.assign(Object.assign({}, defaults()), (opts || {})), { initialValue, controlled: controlled !== null && controlled !== void 0 ? controlled : true, checkedValue, syncVModel }); } function useFieldWithChecked(name, rules, opts) { const form = !(opts === null || opts === void 0 ? void 0 : opts.standalone) ? injectWithSelf(FormContextKey) : undefined; const checkedValue = opts === null || opts === void 0 ? void 0 : opts.checkedValue; const uncheckedValue = opts === null || opts === void 0 ? void 0 : opts.uncheckedValue; function patchCheckedApi(field) { const handleChange = field.handleChange; const checked = vue.computed(() => { const currentValue = vue.toValue(field.value); const checkedVal = vue.toValue(checkedValue); return Array.isArray(currentValue) ? currentValue.findIndex(v => isEqual(v, checkedVal)) >= 0 : isEqual(checkedVal, currentValue); }); function handleCheckboxChange(e, shouldValidate = true) { var _a, _b; if (checked.value === ((_a = e === null || e === void 0 ? void 0 : e.target) === null || _a === void 0 ? void 0 : _a.checked)) { if (shouldValidate) { field.validate(); } return; } const path = vue.toValue(name); const pathState = form === null || form === void 0 ? void 0 : form.getPathState(path); const value = normalizeEventValue(e); let newValue = (_b = vue.toValue(checkedValue)) !== null && _b !== void 0 ? _b : value; if (form && (pathState === null || pathState === void 0 ? vo