UNPKG

@formkit/validation

Version:

Validation plugin for FormKit

1 lines 31.3 kB
{"version":3,"sources":["../src/validation.ts"],"names":["validation","node","hints"],"mappings":"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","sourcesContent":["import { FormKitNode, FormKitMessage, createMessage } from '@formkit/core'\nimport {\n FormKitObservedNode,\n createObserver,\n applyListeners,\n diffDeps,\n removeListeners,\n FormKitDependencies,\n isKilled,\n} from '@formkit/observer'\nimport { has, empty, token, clone, cloneAny, eq } from '@formkit/utils'\n\n/**\n * Special validation properties that affect the way validations are applied.\n *\n * @public\n */\nexport interface FormKitValidationHints {\n /**\n * If this validation fails, should it block the form from being submitted or\n * considered \"valid\"? There are some cases where it is acceptable to allow\n * an incorrect value to still be allowed to submit.\n */\n blocking: boolean\n /**\n * Only run this rule after this many milliseconds of debounce. This is\n * particularity helpful for more \"expensive\" async validation rules like\n * checking if a username is taken from the backend.\n */\n debounce: number\n /**\n * Normally the first validation rule to fail blocks other rules from running\n * if this flag is flipped to true, this rule will be run every time even if\n * a previous rule in the validation stack failed.\n */\n force: boolean\n /**\n * Most validation rules are not run when the input is empty, but this flag\n * allows that behavior to be changed.\n */\n skipEmpty: boolean\n /**\n * The actual name of the validation rule.\n */\n name: string\n}\n\n/**\n * Defines what fully parsed validation rules look like.\n * @public\n */\nexport type FormKitValidation = {\n /**\n * The actual rule function that will be called\n */\n rule: FormKitValidationRule\n /**\n * Arguments to be passed to the validation rule\n */\n args: any[]\n /**\n * The debounce timer for this input.\n */\n timer: number\n /**\n * The state of a validation, can be true, false, or null which means unknown.\n */\n state: boolean | null\n /**\n * Determines if the rule should be considered for the next run cycle. This\n * does not mean the rule will be validated, it just means that it should be\n * considered.\n */\n queued: boolean\n /**\n * Dependencies this validation rule is observing.\n */\n deps: FormKitDependencies\n /**\n * The observed node that is being validated.\n */\n observer: FormKitObservedNode\n /**\n * An observer that updates validation messages when it’s dependencies change,\n * for example, the label of the input.\n */\n messageObserver?: FormKitObservedNode\n} & FormKitValidationHints\n\n/**\n * Defines what validation rules look like when they are parsed, but have not\n * necessarily had validation rules substituted in yet.\n * @public\n */\nexport type FormKitValidationIntent = [string | FormKitValidationRule, ...any[]]\n\n/**\n * Signature for a generic validation rule. It accepts an input — often a string\n * — but should be able to accept any input type, and returns a boolean\n * indicating whether or not it passed validation.\n * @public\n */\nexport type FormKitValidationRule = {\n (node: FormKitNode, ...args: any[]): boolean | Promise<boolean>\n ruleName?: string\n} & Partial<FormKitValidationHints>\n\n/**\n * A validation rule result.\n * @public\n */\nexport interface FormKitValidationRuleResult {\n result: boolean\n validation: FormKitValidation\n}\n\n/**\n * FormKit validation rules are structured as on object of key/function pairs\n * where the key of the object is the validation rule name.\n * @public\n */\nexport interface FormKitValidationRules {\n [index: string]: FormKitValidationRule\n}\n\n/**\n * The interface for the localized validation message function.\n * @public\n */\nexport interface FormKitValidationMessage {\n (...args: FormKitValidationI18NArgs): string\n}\n\n/**\n * The interface for the localized validation message registry.\n * @public\n */\nexport interface FormKitValidationMessages {\n [index: string]: string | FormKitValidationMessage\n}\n\n/**\n * Determines the validation nonce.\n * @public\n */\ninterface FormKitValidationState {\n input: string | null\n rerun: number | null\n isPassing: boolean\n}\n\n/**\n * The arguments that are passed to the validation messages in the i18n plugin.\n *\n * @public\n */\nexport type FormKitValidationI18NArgs = [\n {\n node: FormKitNode\n name: string\n args: any[]\n message?: string\n }\n]\n\n/**\n * Message that gets set when the node is awaiting validation.\n */\nconst validatingMessage = /* #__PURE__ */ createMessage({\n type: 'state',\n blocking: true,\n visible: false,\n value: true,\n key: 'validating',\n})\n\n/**\n * The actual validation plugin function. Everything must be bootstrapped here.\n *\n * @param baseRules - Base validation rules to include in the plugin. By default,\n * FormKit makes all rules in the \\@formkit/rules package available via the\n * defaultConfig.\n *\n * @public\n */\nexport function createValidationPlugin(baseRules: FormKitValidationRules = {}) {\n return function validationPlugin(node: FormKitNode): void {\n let propRules = cloneAny(node.props.validationRules || {})\n let availableRules = { ...baseRules, ...propRules }\n // create an observed node\n const state = { input: token(), rerun: null, isPassing: true }\n let validation = cloneAny(node.props.validation)\n // If the node's validation props change, reboot:\n node.on('prop:validation', ({ payload }) => reboot(payload, propRules))\n node.on('prop:validationRules', ({ payload }) =>\n reboot(validation, payload)\n )\n /**\n * Reboots the validation using new rules or declarations/intents.\n * @param newValidation - New validation declaration to use\n * @param newRules - New validation rules to use\n * @returns\n */\n function reboot(\n newValidation: undefined | string | FormKitValidationIntent[],\n newRules: FormKitValidationRules\n ) {\n if (\n eq(Object.keys(propRules || {}), Object.keys(newRules || {})) &&\n eq(validation, newValidation)\n )\n return\n propRules = cloneAny(newRules)\n validation = cloneAny(newValidation)\n availableRules = { ...baseRules, ...propRules }\n // Destroy all observers that may re-trigger validation on an old stack\n // Clear existing message observers\n node.props.parsedRules?.forEach((validation: FormKitValidation) => {\n removeMessage(validation)\n removeListeners(validation.observer.receipts)\n validation.observer.kill()\n })\n // Remove all existing messages before re-validating\n node.store.filter(() => false, 'validation')\n node.props.parsedRules = parseRules(newValidation, availableRules, node)\n state.isPassing = true\n validate(node, node.props.parsedRules, state)\n }\n\n // Validate the field when this plugin is initialized\n node.props.parsedRules = parseRules(validation, availableRules, node)\n validate(node, node.props.parsedRules, state)\n }\n}\n\n/**\n * Given parsed validations, a value and a node, run the validations and set\n * the appropriate store messages on the node.\n * @param value - The value being validated\n * @param node - The Node this value belongs to\n * @param rules - The rules\n */\nfunction validate(\n node: FormKitNode | FormKitObservedNode,\n validations: FormKitValidation[],\n state: FormKitValidationState\n) {\n if (isKilled(node)) return\n state.input = token()\n node.store.set(\n /* #__PURE__ */ createMessage({\n key: 'failing',\n value: !state.isPassing,\n visible: false,\n })\n )\n state.isPassing = true\n node.store.filter((message) => !message.meta.removeImmediately, 'validation')\n validations.forEach(\n (validation) => validation.debounce && clearTimeout(validation.timer)\n )\n if (validations.length) {\n node.store.set(validatingMessage)\n run(0, validations, state, false, () => {\n node.store.remove(validatingMessage.key)\n node.store.set(\n /* #__PURE__ */ createMessage({\n key: 'failing',\n value: !state.isPassing,\n visible: false,\n })\n )\n })\n }\n}\n\n/**\n * Runs validation rules recursively while collecting dependencies allowing for\n * cross-node validation rules that automatically re-trigger when a foreign\n * value is changed.\n * @param current - The index of the current validation rule\n * @param validations - The remaining validation rule stack to run\n * @param node - An observed node, the owner of this validation stack\n * @param state - An object of state information about this run\n * @param removeImmediately - Should messages created during this call be removed immediately when a new commit takes place?\n * @returns\n */\nfunction run(\n current: number,\n validations: FormKitValidation[],\n state: FormKitValidationState,\n removeImmediately: boolean,\n complete: () => void\n): void {\n const validation = validations[current]\n if (!validation) return complete()\n const node = validation.observer\n if (isKilled(node)) return\n const currentRun = state.input\n validation.state = null\n\n function next(async: boolean, result: boolean | null): void {\n if (state.input !== currentRun) return\n state.isPassing = state.isPassing && !!result\n validation.queued = false\n const newDeps = node.stopObserve()\n const diff = diffDeps(validation.deps, newDeps)\n applyListeners(\n node,\n diff,\n function revalidate() {\n // Event callback for when the deps change:\n try {\n node.store.set(validatingMessage)\n } catch (e) {}\n validation.queued = true\n if (state.rerun) clearTimeout(state.rerun)\n state.rerun = setTimeout(\n validate,\n 0,\n node,\n validations,\n state\n ) as unknown as number\n },\n 'unshift' // We want these listeners to run before other events are emitted so the 'state.validating' will be reliable.\n )\n validation.deps = newDeps\n\n validation.state = result\n if (result === false) {\n createFailedMessage(validation, removeImmediately || async)\n } else {\n removeMessage(validation)\n }\n if (validations.length > current + 1) {\n const nextValidation = validations[current + 1]\n if (\n (result || nextValidation.force || !nextValidation.skipEmpty) &&\n nextValidation.state === null\n ) {\n // If the next rule was never run then it has not been observed so it could never\n // run again on its own.\n nextValidation.queued = true\n }\n run(current + 1, validations, state, removeImmediately || async, complete)\n } else {\n // The validation has completed\n complete()\n }\n }\n if (\n (!empty(node.value) || !validation.skipEmpty) &&\n (state.isPassing || validation.force)\n ) {\n if (validation.queued) {\n runRule(validation, node, (result: boolean | Promise<boolean>) => {\n result instanceof Promise\n ? result.then((r) => next(true, r))\n : next(false, result)\n })\n } else {\n // In this case our rule is not queued, so literally nothing happened that\n // would affect it, we just need to move past this rule and make no\n // modifications to state\n run(current + 1, validations, state, removeImmediately, complete)\n }\n } else if (empty(node.value) && validation.skipEmpty && state.isPassing) {\n // This rule is not run because it is empty — the previous rule passed so normally we would run this rule\n // but in this case we cannot because it is empty. The node being empty is the only condition by which\n // this rule is not run, so the only dep at this point to the the value of the node.\n node.observe()\n node.value\n next(false, state.isPassing)\n } else {\n // This rule is not being run because a previous validation rule is failing and this one is not forced\n // In this case we should call next validation — a `null` result here explicitly means the rule was not run.\n next(false, null)\n }\n}\n\n/**\n * Run a validation rule debounced or not.\n * @param validation - A validation to debounce\n */\nfunction runRule(\n validation: FormKitValidation,\n node: FormKitObservedNode,\n after: (result: boolean | Promise<boolean>) => void\n) {\n if (validation.debounce) {\n validation.timer = setTimeout(() => {\n node.observe()\n after(validation.rule(node, ...validation.args))\n }, validation.debounce) as unknown as number\n } else {\n node.observe()\n after(validation.rule(node, ...validation.args))\n }\n}\n\n/**\n * The messages given to this function have already been set on the node, but\n * any other validation messages on the node that are not included in this\n * stack should be removed because they have been resolved.\n * @param node - The node to operate on.\n * @param messages - A new stack of messages\n */\nfunction removeMessage(validation: FormKitValidation) {\n const key = `rule_${validation.name}`\n if (validation.messageObserver) {\n validation.messageObserver = validation.messageObserver.kill()\n }\n if (has(validation.observer.store, key)) {\n validation.observer.store.remove(key)\n }\n}\n\n/**\n *\n * @param value - The value that is failing\n * @param validation - The validation object\n */\nfunction createFailedMessage(\n validation: FormKitValidation,\n removeImmediately: boolean\n): void {\n const node = validation.observer\n if (isKilled(node)) return\n\n if (!validation.messageObserver) {\n validation.messageObserver = createObserver(node._node)\n }\n validation.messageObserver.watch(\n (node) => {\n const i18nArgs: FormKitValidationI18NArgs = createI18nArgs(\n node,\n validation\n )\n return i18nArgs\n },\n (i18nArgs) => {\n const customMessage = createCustomMessage(node, validation, i18nArgs)\n // Here we short circuit the i18n system to force the output.\n const message = /* #__PURE__ */ createMessage({\n blocking: validation.blocking,\n key: `rule_${validation.name}`,\n meta: {\n /**\n * Use this key instead of the message root key to produce i18n validation\n * messages.\n */\n messageKey: validation.name,\n /**\n * For messages that were created *by or after* a debounced or async\n * validation rule — we make note of it so we can immediately remove them\n * as soon as the next commit happens.\n */\n removeImmediately,\n /**\n * Determines if this message should be passed to localization.\n */\n localize: !customMessage,\n /**\n * The arguments that will be passed to the validation rules\n */\n i18nArgs,\n },\n type: 'validation',\n value: customMessage || 'This field is not valid.',\n })\n node.store.set(message)\n }\n )\n}\n\n/**\n * Returns a custom validation message if applicable.\n * @param node - FormKit Node\n * @param validation - The validation rule being processed.\n */\nfunction createCustomMessage(\n node: FormKitNode,\n validation: FormKitValidation,\n i18nArgs: FormKitValidationI18NArgs\n): string | undefined {\n const customMessage =\n node.props.validationMessages &&\n has(node.props.validationMessages, validation.name)\n ? node.props.validationMessages[validation.name]\n : undefined\n if (typeof customMessage === 'function') {\n return customMessage(...i18nArgs)\n }\n return customMessage\n}\n\n/**\n * Creates the arguments passed to the i18n\n * @param node - The node that performed the validation\n * @param validation - The validation that failed\n */\nfunction createI18nArgs(\n node: FormKitNode,\n validation: FormKitValidation\n): FormKitValidationI18NArgs {\n // If a custom message has been found, short circuit the i18n system.\n return [\n {\n node,\n name: createMessageName(node),\n args: validation.args,\n },\n ]\n}\n\n/**\n * Given a node, this returns the name that should be used in validation\n * messages. This is either the `validationLabel` prop, the `label` prop, or\n * the name of the input (in that order).\n * @param node - The node to display\n * @returns\n * @public\n */\nexport function createMessageName(node: FormKitNode): string {\n if (typeof node.props.validationLabel === 'function') {\n return node.props.validationLabel(node)\n }\n return (\n node.props.validationLabel ||\n node.props.label ||\n node.props.name ||\n String(node.name)\n )\n}\n\n/**\n * Describes hints, must also be changed in the debounceExtractor.\n */\nconst hintPattern = '(?:[\\\\*+?()0-9]+)'\n\n/**\n * A pattern to describe rule names. Rules names can only contain letters,\n * numbers, and underscores and must start with a letter.\n */\nconst rulePattern = '[a-zA-Z][a-zA-Z0-9_]+'\n\n/**\n * Regular expression for extracting rule data.\n */\nconst ruleExtractor = new RegExp(\n `^(${hintPattern}?${rulePattern})(?:\\\\:(.*)+)?$`,\n 'i'\n)\n\n/**\n * Validation hints are special characters preceding a validation rule, like\n * !phone\n */\nconst hintExtractor = new RegExp(`^(${hintPattern})(${rulePattern})$`, 'i')\n\n/**\n * Given a hint string like ^(200)? or ^? or (200)?^ extract the hints to\n * matches.\n */\nconst debounceExtractor = /([\\*+?]+)?(\\(\\d+\\))([\\*+?]+)?/\n\n/**\n * Determines if a given string is in the proper debounce format.\n */\nconst hasDebounce = /\\(\\d+\\)/\n\n/**\n * The default values of the available validation hints.\n */\nexport const defaultHints: FormKitValidationHints = {\n blocking: true,\n debounce: 0,\n force: false,\n skipEmpty: true,\n name: '',\n}\n\n/**\n * Parse validation intents and strings into validation rule stacks.\n * @param validation - Either a string a validation rules, or proper array of structured rules.\n * @internal\n */\nexport function parseRules(\n validation: undefined | string | FormKitValidationIntent[],\n rules: FormKitValidationRules,\n node: FormKitNode\n): FormKitValidation[] {\n if (!validation) return []\n const intents =\n typeof validation === 'string'\n ? extractRules(validation)\n : clone(validation)\n return intents.reduce((validations, args) => {\n let rule = args.shift() as string | FormKitValidationRule\n const hints = {}\n if (typeof rule === 'string') {\n const [ruleName, parsedHints] = parseHints(rule)\n if (has(rules, ruleName)) {\n rule = rules[ruleName]\n Object.assign(hints, parsedHints)\n }\n }\n if (typeof rule === 'function') {\n validations.push({\n observer: createObserver(node),\n rule,\n args,\n timer: 0,\n state: null,\n queued: true,\n deps: new Map(),\n ...defaultHints,\n ...fnHints(hints, rule),\n })\n }\n return validations\n }, [] as FormKitValidation[])\n}\n\n/**\n * A string of validation rules written in FormKitRule notation.\n * @param validation - The string of rules\n * @internal\n */\nfunction extractRules(validation: string): FormKitValidationIntent[] {\n return validation.split('|').reduce((rules, rule) => {\n const parsedRule = parseRule(rule)\n if (parsedRule) {\n rules.push(parsedRule)\n }\n return rules\n }, [] as FormKitValidationIntent[])\n}\n\n/**\n * Given a rule like confirm:password_confirm produce a FormKitValidationIntent\n * @param rule - A string representing a validation rule.\n * @returns\n */\nfunction parseRule(rule: string): FormKitValidationIntent | false {\n const trimmed = rule.trim()\n if (trimmed) {\n const matches = trimmed.match(ruleExtractor)\n if (matches && typeof matches[1] === 'string') {\n const ruleName = matches[1].trim()\n const args =\n matches[2] && typeof matches[2] === 'string'\n ? matches[2].split(',').map((s) => s.trim())\n : []\n return [ruleName, ...args]\n }\n }\n return false\n}\n\n/**\n * Given a rule name, detect if there are any additional hints like !\n * @param ruleName - string representing a rule name\n * @returns\n */\nfunction parseHints(\n ruleName: string\n): [string, Partial<FormKitValidationHints>] {\n const matches = ruleName.match(hintExtractor)\n if (!matches) {\n return [ruleName, { name: ruleName }]\n }\n const map: { [index: string]: Partial<FormKitValidationHints> } = {\n '*': { force: true },\n '+': { skipEmpty: false },\n '?': { blocking: false },\n }\n const [, hints, rule] = matches\n const hintGroups = hasDebounce.test(hints)\n ? hints.match(debounceExtractor) || []\n : [, hints]\n return [\n rule,\n [hintGroups[1], hintGroups[2], hintGroups[3]].reduce(\n (hints: Partial<FormKitValidationHints>, group: string | undefined) => {\n if (!group) return hints\n if (hasDebounce.test(group)) {\n hints.debounce = parseInt(group.substr(1, group.length - 1))\n } else {\n group\n .split('')\n .forEach(\n (hint) => has(map, hint) && Object.assign(hints, map[hint])\n )\n }\n return hints\n },\n { name: rule } as Partial<FormKitValidationHints>\n ),\n ]\n}\n\n/**\n * Extracts hint properties from the validation rule function itself and applies\n * them if they are not already in the set of validation hints extracted from\n * strings.\n * @param existingHints - An existing set of hints already parsed\n * @param rule - The actual rule function, which can contain hint properties\n * @returns\n */\nfunction fnHints(\n existingHints: Partial<FormKitValidationHints>,\n rule: FormKitValidationRule\n) {\n if (!existingHints.name) {\n existingHints.name = rule.ruleName || rule.name\n }\n return ['skipEmpty', 'force', 'debounce', 'blocking'].reduce(\n (hints: Partial<FormKitValidationHints>, hint: string) => {\n if (has(rule, hint) && !has(hints, hint)) {\n Object.assign(hints, {\n [hint]: rule[hint as keyof FormKitValidationHints],\n })\n }\n return hints\n },\n existingHints\n )\n}\n\n/**\n * Extracts all validation messages from the given node and all its descendants.\n * This is not reactive and must be re-called each time the messages change.\n * @param node - The FormKit node to extract validation rules from — as well as its descendants.\n * @public\n */\nexport function getValidationMessages(\n node: FormKitNode\n): Map<FormKitNode, FormKitMessage[]> {\n const messages: Map<FormKitNode, FormKitMessage[]> = new Map()\n const extract = (n: FormKitNode) => {\n const nodeMessages = []\n for (const key in n.store) {\n const message = n.store[key]\n if (\n message.type === 'validation' &&\n message.visible &&\n typeof message.value === 'string'\n ) {\n nodeMessages.push(message)\n }\n }\n if (nodeMessages.length) {\n messages.set(n, nodeMessages)\n }\n return n\n }\n extract(node).walk(extract)\n return messages\n}\n"]}