UNPKG

vee-validate

Version:
1,561 lines (1,541 loc) 118 kB
/** * vee-validate v4.8.6 * (c) 2023 Abdelrahman Awad * @license MIT */ import { getCurrentInstance, inject, warn as warn$1, computed, ref, watch, unref, isRef, reactive, onUnmounted, nextTick, onMounted, provide, onBeforeUnmount, defineComponent, toRef, resolveDynamicComponent, h, watchEffect, markRaw } from 'vue'; import { setupDevtoolsPlugin } from '@vue/devtools-api'; 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; } 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.`); } const FormContextKey = Symbol('vee-validate-form'); 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. * */ 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; // eslint-disable-next-line eqeqeq 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; // eslint-disable-next-line eqeqeq 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(); 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 // eslint-disable-next-line no-self-compare return a !== a && b !== b; } function isFile(a) { if (!isClient) { return false; } return a instanceof File; } 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; } 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 = getCurrentInstance(); return (vm === null || vm === void 0 ? void 0 : vm.provides[symbol]) || inject(symbol, def); } function warn(message) { warn$1(`[vee-validate]: ${message}`); } /** * Ensures we deal with a singular field value */ function normalizeField(field) { if (Array.isArray(field)) { return field[0]; } return field; } 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; } /** * Creates a throttled function that only invokes the provided function (`func`) at most once per within a given number of milliseconds * (`limit`) */ function throttle(func, limit) { let inThrottle; let lastResult; return function (...args) { // eslint-disable-next-line @typescript-eslint/no-this-alias const context = this; if (!inThrottle) { inThrottle = true; setTimeout(() => (inThrottle = false), limit); lastResult = func.apply(context, args); } return lastResult; }; } function debounceAsync(inner, ms = 0) { let timer = null; let resolves = []; return function (...args) { // Run the function after a certain amount of time if (timer) { window.clearTimeout(timer); } timer = window.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; onDone(result, args); return result; }; } function computedDeep({ get, set }) { const baseRef = ref(klona(get())); watch(get, newValue => { if (isEqual(newValue, baseRef.value)) { return; } baseRef.value = klona(newValue); }, { deep: true, }); watch(baseRef, newValue => { if (isEqual(newValue, get())) { return; } set(klona(newValue)); }, { deep: true, }); return baseRef; } function unravel(value) { if (isCallable(value)) { return value(); } return unref(value); } function lazyToRef(value) { return computed(() => unravel(value)); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const 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 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 input.value; } /** * 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) => { const val = getFromPath(crossTable, value) || 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); const errors = result.errors; return { errors, valid: !errors.length, }; } /** * Starts the validation process. */ async function _validate(field, value) { if (isTypedSchema(field.rules) || isYupValidator(field.rules)) { return validateFieldWithTypedSchema(value, field.rules); } // if a generic function or chain of generic functions if (isCallable(field.rules) || Array.isArray(field.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(field.rules) ? field.rules : [field.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' && result; if (isValid) { continue; } 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(field.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) { var _a; try { const output = await yupSchema.validate(values, { abortEarly: false }); 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, schema) { const typedSchema = isTypedSchema(schema) ? schema : yupToTypedSchema(schema); const result = await typedSchema.parse(value); const messages = []; for (const error of result.errors) { if (error.errors.length) { messages.push(...error.errors); } } return { 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(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, }; } 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, }; } let ID_COUNTER = 0; function useFieldState(path, init) { const { value, initialValue, setInitialValue } = _useFieldValue(path, init.modelValue, init.form); const { errorMessage, errors, setErrors } = _useFieldErrors(path, init.form); const meta = _useFieldMeta(value, initialValue, errors); const id = ID_COUNTER >= Number.MAX_SAFE_INTEGER ? 0 : ++ID_COUNTER; 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, errors, errorMessage, setState, }; } /** * Creates the field value and resolves the initial value */ function _useFieldValue(path, modelValue, form) { const modelRef = ref(unref(modelValue)); function resolveInitialValue() { if (!form) { return unref(modelRef); } return getFromPath(form.meta.value.initialValues, unref(path), unref(modelRef)); } function setInitialValue(value) { if (!form) { modelRef.value = value; return; } form.stageInitialValue(unref(path), value, true); } const initialValue = computed(resolveInitialValue); // if no form is associated, use a regular ref. if (!form) { const value = 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(unref(path), currentValue, true); // otherwise use a computed setter that triggers the `setFieldValue` const value = computed({ get() { return getFromPath(form.values, unref(path)); }, set(newVal) { form.setFieldValue(unref(path), newVal); }, }); 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 (isRef(modelValue)) { return unref(modelValue); } if (modelValue !== undefined) { return modelValue; } return getFromPath(form.values, unref(path), unref(initialValue)); } /** * Creates meta flags state and some associated effects with them */ function _useFieldMeta(currentValue, initialValue, errors) { const meta = reactive({ touched: false, pending: false, valid: true, validated: !!unref(errors).length, initialValue: computed(() => unref(initialValue)), dirty: computed(() => { return !isEqual(unref(currentValue), unref(initialValue)); }), }); watch(errors, value => { meta.valid = !value.length; }, { immediate: true, flush: 'sync', }); return meta; } /** * Creates the error message state for the field state */ function _useFieldErrors(path, form) { function normalizeErrors(messages) { if (!messages) { return []; } return Array.isArray(messages) ? messages : [messages]; } if (!form) { const errors = ref([]); return { errors, errorMessage: computed(() => errors.value[0]), setErrors: (messages) => { errors.value = normalizeErrors(messages); }, }; } const errors = computed(() => form.errorBag.value[unref(path)] || []); return { errors, errorMessage: computed(() => errors.value[0]), setErrors: (messages) => { form.setFieldErrorBag(unref(path), normalizeErrors(messages)); }, }; } function installDevtoolsPlugin(app) { if ((process.env.NODE_ENV !== 'production')) { setupDevtoolsPlugin({ id: 'vee-validate-devtools-plugin', label: 'VeeValidate Plugin', packageName: 'vee-validate', homepage: 'https://vee-validate.logaretm.com/v4', app, logo: 'https://vee-validate.logaretm.com/v4/logo.png', }, setupApiHooks); } } const DEVTOOLS_FORMS = {}; const DEVTOOLS_FIELDS = {}; let API; const refreshInspector = throttle(() => { setTimeout(async () => { await nextTick(); API === null || API === void 0 ? void 0 : API.sendInspectorState(INSPECTOR_ID); API === null || API === void 0 ? void 0 : API.sendInspectorTree(INSPECTOR_ID); }, 100); }, 100); function registerFormWithDevTools(form) { const vm = getCurrentInstance(); if (!API) { const app = vm === null || vm === void 0 ? void 0 : vm.appContext.app; if (!app) { return; } installDevtoolsPlugin(app); } DEVTOOLS_FORMS[form.formId] = Object.assign({}, form); DEVTOOLS_FORMS[form.formId]._vm = vm; onUnmounted(() => { delete DEVTOOLS_FORMS[form.formId]; refreshInspector(); }); refreshInspector(); } function registerSingleFieldWithDevtools(field) { const vm = getCurrentInstance(); if (!API) { const app = vm === null || vm === void 0 ? void 0 : vm.appContext.app; if (!app) { return; } installDevtoolsPlugin(app); } DEVTOOLS_FIELDS[field.id] = Object.assign({}, field); DEVTOOLS_FIELDS[field.id]._vm = vm; onUnmounted(() => { delete DEVTOOLS_FIELDS[field.id]; refreshInspector(); }); refreshInspector(); } const INSPECTOR_ID = 'vee-validate-inspector'; const COLORS = { error: 0xbd4b4b, success: 0x06d77b, unknown: 0x54436b, white: 0xffffff, black: 0x000000, blue: 0x035397, purple: 0xb980f0, orange: 0xf5a962, gray: 0xbbbfca, }; let SELECTED_NODE = null; function setupApiHooks(api) { API = api; api.addInspector({ id: INSPECTOR_ID, icon: 'rule', label: 'vee-validate', noSelectionText: 'Select a vee-validate node to inspect', actions: [ { icon: 'done_outline', tooltip: 'Validate selected item', action: async () => { if (!SELECTED_NODE) { console.error('There is not a valid selected vee-validate node or component'); return; } await SELECTED_NODE.validate(); }, }, { icon: 'delete_sweep', tooltip: 'Clear validation state of the selected item', action: () => { if (!SELECTED_NODE) { console.error('There is not a valid selected vee-validate node or component'); return; } if ('id' in SELECTED_NODE) { SELECTED_NODE.resetField(); return; } SELECTED_NODE.resetForm(); }, }, ], }); api.on.getInspectorTree(payload => { if (payload.inspectorId !== INSPECTOR_ID) { return; } const forms = Object.values(DEVTOOLS_FORMS); const fields = Object.values(DEVTOOLS_FIELDS); payload.rootNodes = [ ...forms.map(mapFormForDevtoolsInspector), ...fields.map(field => mapFieldForDevtoolsInspector(field)), ]; }); api.on.getInspectorState((payload, ctx) => { if (payload.inspectorId !== INSPECTOR_ID || ctx.currentTab !== `custom-inspector:${INSPECTOR_ID}`) { return; } const { form, field, type } = decodeNodeId(payload.nodeId); if (form && type === 'form') { payload.state = buildFormState(form); SELECTED_NODE = form; return; } if (field && type === 'field') { payload.state = buildFieldState(field); SELECTED_NODE = field; return; } SELECTED_NODE = null; }); } function mapFormForDevtoolsInspector(form) { const { textColor, bgColor } = getTagTheme(form); const formTreeNodes = {}; Object.values(form.fieldsByPath.value).forEach(field => { const fieldInstance = Array.isArray(field) ? field[0] : field; if (!fieldInstance) { return; } setInPath(formTreeNodes, unref(fieldInstance.name), mapFieldForDevtoolsInspector(fieldInstance, form)); }); function buildFormTree(tree, path = []) { const key = [...path].pop(); if ('id' in tree) { return Object.assign(Object.assign({}, tree), { label: key || tree.label }); } if (isObject(tree)) { return { id: `${path.join('.')}`, label: key || '', children: Object.keys(tree).map(key => buildFormTree(tree[key], [...path, key])), }; } if (Array.isArray(tree)) { return { id: `${path.join('.')}`, label: `${key}[]`, children: tree.map((c, idx) => buildFormTree(c, [...path, String(idx)])), }; } return { id: '', label: '', children: [] }; } const { children } = buildFormTree(formTreeNodes); return { id: encodeNodeId(form), label: 'Form', children, tags: [ { label: 'Form', textColor, backgroundColor: bgColor, }, { label: `${Object.keys(form.fieldsByPath.value).length} fields`, textColor: COLORS.white, backgroundColor: COLORS.unknown, }, ], }; } function mapFieldForDevtoolsInspector(field, form) { const fieldInstance = normalizeField(field); const { textColor, bgColor } = getTagTheme(fieldInstance); const isGroup = Array.isArray(field) && field.length > 1; return { id: encodeNodeId(form, fieldInstance, !isGroup), label: unref(fieldInstance.name), children: Array.isArray(field) ? field.map(fieldItem => mapFieldForDevtoolsInspector(fieldItem, form)) : undefined, tags: [ isGroup ? undefined : { label: 'Field', textColor, backgroundColor: bgColor, }, !form ? { label: 'Standalone', textColor: COLORS.black, backgroundColor: COLORS.gray, } : undefined, !isGroup && fieldInstance.type === 'checkbox' ? { label: 'Checkbox', textColor: COLORS.white, backgroundColor: COLORS.blue, } : undefined, !isGroup && fieldInstance.type === 'radio' ? { label: 'Radio', textColor: COLORS.white, backgroundColor: COLORS.purple, } : undefined, isGroup ? { label: 'Group', textColor: COLORS.black, backgroundColor: COLORS.orange, } : undefined, ].filter(Boolean), }; } function encodeNodeId(form, field, encodeIndex = true) { const fieldPath = form ? unref(field === null || field === void 0 ? void 0 : field.name) : field === null || field === void 0 ? void 0 : field.id; const fieldGroup = fieldPath ? form === null || form === void 0 ? void 0 : form.fieldsByPath.value[fieldPath] : undefined; let idx; if (encodeIndex && field && Array.isArray(fieldGroup)) { idx = fieldGroup.indexOf(field); } const idObject = { f: form === null || form === void 0 ? void 0 : form.formId, ff: fieldPath, idx, type: field ? 'field' : 'form' }; return btoa(JSON.stringify(idObject)); } function decodeNodeId(nodeId) { try { const idObject = JSON.parse(atob(nodeId)); const form = DEVTOOLS_FORMS[idObject.f]; if (!form && idObject.ff) { const field = DEVTOOLS_FIELDS[idObject.ff]; if (!field) { return {}; } return { type: idObject.type, field, }; } if (!form) { return {}; } const fieldGroup = form.fieldsByPath.value[idObject.ff]; return { type: idObject.type, form, field: Array.isArray(fieldGroup) ? fieldGroup[idObject.idx || 0] : fieldGroup, }; } catch (err) { // console.error(`Devtools: [vee-validate] Failed to parse node id ${nodeId}`); } return {}; } function buildFieldState(field) { const { errors, meta, value } = field; return { 'Field state': [ { key: 'errors', value: errors.value }, { key: 'initialValue', value: meta.initialValue, }, { key: 'currentValue', value: value.value, }, { key: 'touched', value: meta.touched, }, { key: 'dirty', value: meta.dirty, }, { key: 'valid', value: meta.valid, }, ], }; } function buildFormState(form) { const { errorBag, meta, values, isSubmitting, submitCount } = form; return { 'Form state': [ { key: 'submitCount', value: submitCount.value, }, { key: 'isSubmitting', value: isSubmitting.value, }, { key: 'touched', value: meta.value.touched, }, { key: 'dirty', value: meta.value.dirty, }, { key: 'valid', value: meta.value.valid, }, { key: 'initialValues', value: meta.value.initialValues, }, { key: 'currentValues', value: values, }, { key: 'errors', value: keysOf(errorBag.value).reduce((acc, key) => { var _a; const message = (_a = errorBag.value[key]) === null || _a === void 0 ? void 0 : _a[0]; if (message) { acc[key] = message; } return acc; }, {}), }, ], }; } /** * Resolves the tag color based on the form state */ function getTagTheme(fieldOrForm) { // const fallbackColors = { // bgColor: COLORS.unknown, // textColor: COLORS.white, // }; const isValid = 'id' in fieldOrForm ? fieldOrForm.meta.valid : fieldOrForm.meta.value.valid; return { bgColor: isValid ? COLORS.success : COLORS.error, textColor: isValid ? COLORS.black : COLORS.white, }; } /** * Creates a field composite. */ function useField(path, rules, opts) { if (hasCheckedAttr(opts === null || opts === void 0 ? void 0 : opts.type)) { return useCheckboxField(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, modelPropName, syncVModel, form: controlForm, } = normalizeOptions(opts); const injectedForm = controlled ? injectWithSelf(FormContextKey) : undefined; const form = controlForm || injectedForm; const name = lazyToRef(path); // a flag indicating if the field is about to be removed/unmounted. let markedForRemoval = false; const { id, value, initialValue, meta, setState, errors, errorMessage } = useFieldState(name, { modelValue, form, }); if (syncVModel) { useVModel({ value, prop: modelPropName, handleChange }); } /** * Handles common onBlur meta update */ const handleBlur = () => { meta.touched = true; }; const normalizedRules = computed(() => { let rulesValue = unref(rules); const schema = unref(form === null || form === void 0 ? void 0 : form.schema); if (schema && !isYupValidator(schema) && !isTypedSchema(schema)) { rulesValue = extractRuleFromSchema(schema, unref(name)) || rulesValue; } if (isYupValidator(rulesValue) || isTypedSchema(rulesValue) || isCallable(rulesValue) || Array.isArray(rulesValue)) { return rulesValue; } return normalizeRules(rulesValue); }); async function validateCurrentValue(mode) { var _a, _b; if (form === null || form === void 0 ? void 0 : form.validateSchema) { return (_a = (await form.validateSchema(mode)).results[unref(name)]) !== null && _a !== void 0 ? _a : { valid: true, errors: [] }; } return validate(value.value, normalizedRules.value, { name: unref(name), label: unref(label), values: (_b = form === null || form === void 0 ? void 0 : form.values) !== null && _b !== void 0 ? _b : {}, bails, }); } const validateWithStateMutation = withLatest(async () => { meta.pending = true; meta.validated = true; return validateCurrentValue('validated-only'); }, result => { if (markedForRemoval) { result.valid = true; result.errors = []; } setState({ errors: result.errors }); meta.pending = false; return result; }); const validateValidStateOnly = withLatest(async () => { return validateCurrentValue('silent'); }, result => { if (markedForRemoval) { result.valid = true; } 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); value.value = newValue; if (!validateOnValueUpdate && shouldValidate) { validateWithStateMutation(); } } // Runs the initial validation 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; } let unwatchValue; let lastWatchedValue = klona(value.value); function watchValue() { unwatchValue = watch(value, (val, oldVal) => { if (isEqual(val, oldVal) && isEqual(val, lastWatchedValue)) { return; } const validateFn = validateOnValueUpdate ? validateWithStateMutation : validateValidStateOnly; validateFn(); lastWatchedValue = klona(val); }, { deep: true, }); } watchValue(); function resetField(state) { var _a; unwatchValue === null || unwatchValue === void 0 ? void 0 : unwatchValue(); 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(); // need to watch at nex