Repository: logaretm/vee-validate Branch: main Commit: 7d8cc524f920 Files: 420 Total size: 1.3 MB Directory structure: gitextract_83vgg6c_/ ├── .changeset/ │ ├── README.md │ ├── config.json │ ├── evil-loops-shine.md │ ├── gentle-geckos-share.md │ ├── happy-wasps-rush.md │ ├── pre.json │ ├── pretty-onions-add.md │ ├── revert-number-to-string.md │ ├── six-parrots-flash.md │ ├── smart-needles-own.md │ └── stupid-friends-relate.md ├── .circleci/ │ └── config.yml ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── Feature_request.md │ │ └── bug_report.yaml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .husky/ │ ├── .gitignore │ ├── commit-msg │ └── pre-commit ├── .prettierrc ├── CHANGELOG.md ├── CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── commitlint.config.mjs ├── docs/ │ ├── .gitignore │ ├── CHANGELOG.md │ ├── _redirects │ ├── astro.config.ts │ ├── baseLink.ts │ ├── highlight.ts │ ├── package.json │ ├── postcss.config.js │ ├── public/ │ │ ├── img/ │ │ │ ├── browserconfig.xml │ │ │ └── manifest.json │ │ └── loadTheme.js │ ├── scripts/ │ │ └── afterBuild.js │ ├── src/ │ │ ├── components/ │ │ │ ├── Ad.vue │ │ │ ├── CodeTitle.vue │ │ │ ├── ContentWrapper.vue │ │ │ ├── DocBadge.vue │ │ │ ├── DocFlavor.vue │ │ │ ├── DocMenu.vue │ │ │ ├── DocNextStep.vue │ │ │ ├── DocSearch.vue │ │ │ ├── DocTip.vue │ │ │ ├── DocToc.vue │ │ │ ├── EditPage.vue │ │ │ ├── ExpandTransition.vue │ │ │ ├── FeatureCard.vue │ │ │ ├── FloatingMenu.vue │ │ │ ├── Icon.vue │ │ │ ├── LiveExample.vue │ │ │ ├── LogQueryStr.vue │ │ │ ├── MainPageExample.vue │ │ │ ├── MdxRepl.vue │ │ │ ├── Repl.vue │ │ │ ├── SideMenu.vue │ │ │ ├── SideMenuButton.vue │ │ │ ├── SiteHead.astro │ │ │ ├── SponsorButton.vue │ │ │ ├── SpriteSheet.astro │ │ │ ├── StarCount.vue │ │ │ ├── TheHeader.vue │ │ │ ├── ThemeSwitcher.vue │ │ │ ├── UiLibraries.vue │ │ │ ├── VersionSwitcher.vue │ │ │ └── examples/ │ │ │ ├── ComponentsBasic.vue │ │ │ ├── CompositionBasic.vue │ │ │ ├── CompositionComponentBindsBasic01.vue │ │ │ ├── CompositionComponentBindsBasic02.vue │ │ │ ├── CompositionComponentBindsBasic03.vue │ │ │ ├── CompositionComponentBindsBasic04.vue │ │ │ ├── CompositionComponentBindsBasic05.vue │ │ │ ├── CompositionCustomField01.vue │ │ │ ├── CompositionCustomField02.vue │ │ │ ├── CompositionCustomField03.vue │ │ │ ├── CompositionCustomField04.vue │ │ │ ├── CompositionCustomField05.vue │ │ │ ├── CompositionCustomFieldCheckbox.vue │ │ │ ├── CompositionDynamicSchemaComputed.vue │ │ │ ├── CompositionDynamicSchemaYupLazy.vue │ │ │ ├── CompositionHandlingForms01.vue │ │ │ ├── CompositionHandlingForms02.vue │ │ │ ├── CompositionHandlingForms03.vue │ │ │ ├── CompositionHandlingForms04.vue │ │ │ ├── CompositionHandlingForms05.vue │ │ │ ├── CompositionHandlingForms06.vue │ │ │ ├── CompositionHandlingForms07.vue │ │ │ ├── CompositionHandlingForms08.vue │ │ │ ├── CompositionHandlingForms09.vue │ │ │ ├── CompositionHandlingForms10.vue │ │ │ ├── CompositionHandlingForms11.vue │ │ │ ├── CompositionHandlingForms12.vue │ │ │ ├── CompositionHandlingForms13.vue │ │ │ ├── CompositionHandlingForms14.vue │ │ │ ├── CompositionInputBindsBasic01.vue │ │ │ ├── CompositionInputBindsBasic02.vue │ │ │ ├── CompositionInputBindsBasic03.vue │ │ │ ├── CompositionInputFieldFn.vue │ │ │ ├── CompositionInputFieldValibot.vue │ │ │ ├── CompositionInputFieldYup.vue │ │ │ ├── CompositionInputFieldZod.vue │ │ │ ├── CompositionNested01.vue │ │ │ ├── CompositionNested02.vue │ │ │ ├── CompositionNested03.vue │ │ │ ├── CompositionNested04.vue │ │ │ ├── CompositionNested05.vue │ │ │ ├── CompositionValibotBasic.vue │ │ │ ├── CompositionValidateFnBasic.vue │ │ │ ├── CompositionYupBasic.vue │ │ │ ├── CompositionZodBasic.vue │ │ │ ├── CustomCheckboxInputBasic.vue │ │ │ ├── CustomInputBasic.vue │ │ │ ├── CustomInputBasicError.vue │ │ │ ├── CustomInputBasicValueEvent.vue │ │ │ ├── CustomInputFieldAggressive.vue │ │ │ ├── CustomInputFieldBasic.vue │ │ │ ├── CustomInputFieldEager.vue │ │ │ ├── CustomInputFieldMeta.vue │ │ │ ├── CustomInputFieldMultiErrors.vue │ │ │ └── CustomInputFieldVModel.vue │ │ ├── config.ts │ │ ├── constants.ts │ │ ├── env.d.ts │ │ ├── integrations/ │ │ │ └── svgSprite.ts │ │ ├── layouts/ │ │ │ ├── HomeLayout.astro │ │ │ └── PageLayout.astro │ │ ├── pages/ │ │ │ ├── api/ │ │ │ │ ├── composition-helpers.mdx │ │ │ │ ├── configuration.mdx │ │ │ │ ├── error-message.mdx │ │ │ │ ├── field-array.mdx │ │ │ │ ├── field.mdx │ │ │ │ ├── form.mdx │ │ │ │ ├── use-field-array.mdx │ │ │ │ ├── use-field.mdx │ │ │ │ └── use-form.mdx │ │ │ ├── examples/ │ │ │ │ ├── array-fields.mdx │ │ │ │ ├── async-validation.mdx │ │ │ │ ├── checkboxes-and-radio.mdx │ │ │ │ ├── cross-field-validation.mdx │ │ │ │ ├── custom-checkboxes.mdx │ │ │ │ ├── custom-inputs.mdx │ │ │ │ ├── dynamic-validation-triggers.mdx │ │ │ │ ├── multistep-form-wizard.mdx │ │ │ │ ├── ui-libraries.mdx │ │ │ │ ├── using-stores.mdx │ │ │ │ └── value-formatting.mdx │ │ │ ├── guide/ │ │ │ │ ├── components/ │ │ │ │ │ ├── handling-forms.mdx │ │ │ │ │ ├── nested-objects-and-arrays.mdx │ │ │ │ │ └── validation.mdx │ │ │ │ ├── composition-api/ │ │ │ │ │ ├── caveats.mdx │ │ │ │ │ ├── custom-inputs.mdx │ │ │ │ │ ├── getting-started.mdx │ │ │ │ │ ├── handling-forms.mdx │ │ │ │ │ ├── helpers.mdx │ │ │ │ │ └── nested-objects-and-arrays.mdx │ │ │ │ ├── devtools.mdx │ │ │ │ ├── global-validators.mdx │ │ │ │ ├── i18n.mdx │ │ │ │ ├── migration.mdx │ │ │ │ ├── overview.mdx │ │ │ │ └── testing.mdx │ │ │ ├── index.astro │ │ │ ├── integrations/ │ │ │ │ └── nuxt.mdx │ │ │ ├── resources.mdx │ │ │ ├── submit-target.astro │ │ │ └── tutorials/ │ │ │ ├── basics.mdx │ │ │ └── dynamic-form-generator.mdx │ │ ├── styles/ │ │ │ ├── home.css │ │ │ ├── page.css │ │ │ └── tailwind.css │ │ └── utils/ │ │ ├── examples.ts │ │ ├── github.ts │ │ └── seo.ts │ ├── tailwind.config.js │ ├── theme.json │ └── tsconfig.json ├── eslint.config.js ├── package.json ├── packages/ │ ├── i18n/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── locale/ │ │ │ │ ├── ar.json │ │ │ │ ├── az.json │ │ │ │ ├── bg.json │ │ │ │ ├── bn.json │ │ │ │ ├── ca.json │ │ │ │ ├── ckb.json │ │ │ │ ├── cs.json │ │ │ │ ├── cy.json │ │ │ │ ├── da.json │ │ │ │ ├── de.json │ │ │ │ ├── el.json │ │ │ │ ├── en.json │ │ │ │ ├── es.json │ │ │ │ ├── et.json │ │ │ │ ├── eu.json │ │ │ │ ├── fa.json │ │ │ │ ├── fi.json │ │ │ │ ├── fr.json │ │ │ │ ├── he.json │ │ │ │ ├── hr.json │ │ │ │ ├── hu.json │ │ │ │ ├── id.json │ │ │ │ ├── it.json │ │ │ │ ├── ja.json │ │ │ │ ├── ka.json │ │ │ │ ├── km.json │ │ │ │ ├── ko.json │ │ │ │ ├── kz.json │ │ │ │ ├── lt.json │ │ │ │ ├── lv.json │ │ │ │ ├── mn.json │ │ │ │ ├── ms_MY.json │ │ │ │ ├── nb_NO.json │ │ │ │ ├── ne.json │ │ │ │ ├── nl.json │ │ │ │ ├── nn_NO.json │ │ │ │ ├── pl.json │ │ │ │ ├── pt_BR.json │ │ │ │ ├── pt_PT.json │ │ │ │ ├── ro.json │ │ │ │ ├── ru.json │ │ │ │ ├── sk.json │ │ │ │ ├── sl.json │ │ │ │ ├── so.json │ │ │ │ ├── sq.json │ │ │ │ ├── sr.json │ │ │ │ ├── sr_Latin.json │ │ │ │ ├── sv.json │ │ │ │ ├── th.json │ │ │ │ ├── tr.json │ │ │ │ ├── ug.json │ │ │ │ ├── uk.json │ │ │ │ ├── uz.json │ │ │ │ ├── vi.json │ │ │ │ ├── zh_CN.json │ │ │ │ └── zh_TW.json │ │ │ └── utils.ts │ │ └── tests/ │ │ └── index.spec.ts │ ├── nuxt/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ └── src/ │ │ └── module.ts │ ├── rules/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── alpha.ts │ │ │ ├── alpha_dash.ts │ │ │ ├── alpha_helper.ts │ │ │ ├── alpha_num.ts │ │ │ ├── alpha_spaces.ts │ │ │ ├── between.ts │ │ │ ├── confirmed.ts │ │ │ ├── digits.ts │ │ │ ├── dimensions.ts │ │ │ ├── email.ts │ │ │ ├── ext.ts │ │ │ ├── image.ts │ │ │ ├── index.ts │ │ │ ├── integer.ts │ │ │ ├── is.ts │ │ │ ├── is_not.ts │ │ │ ├── length.ts │ │ │ ├── max.ts │ │ │ ├── max_value.ts │ │ │ ├── mimes.ts │ │ │ ├── min.ts │ │ │ ├── min_value.ts │ │ │ ├── not_one_of.ts │ │ │ ├── numeric.ts │ │ │ ├── one_of.ts │ │ │ ├── regex.ts │ │ │ ├── required.ts │ │ │ ├── size.ts │ │ │ ├── toTypedSchema.ts │ │ │ ├── url.ts │ │ │ └── utils.ts │ │ └── tests/ │ │ ├── .eslintrc.json │ │ ├── alpha.spec.ts │ │ ├── alpha_dash.spec.ts │ │ ├── alpha_num.spec.ts │ │ ├── alpha_spaces.spec.ts │ │ ├── between.spec.ts │ │ ├── confirmed.spec.ts │ │ ├── digits.spec.ts │ │ ├── dimensions.spec.ts │ │ ├── email.spec.ts │ │ ├── ext.spec.ts │ │ ├── helpers/ │ │ │ └── index.ts │ │ ├── image.spec.ts │ │ ├── integer.spec.ts │ │ ├── is.spec.ts │ │ ├── is_not.spec.ts │ │ ├── length.spec.ts │ │ ├── max.spec.ts │ │ ├── max_value.spec.ts │ │ ├── mimes.spec.ts │ │ ├── min.spec.ts │ │ ├── min_value.spec.ts │ │ ├── not_one_of.spec.ts │ │ ├── numeric.spec.ts │ │ ├── one_of.spec.ts │ │ ├── regex.spec.ts │ │ ├── required.spec.ts │ │ ├── size.spec.ts │ │ ├── toTypedSchema.spec.ts │ │ └── url.spec.ts │ ├── shared/ │ │ ├── README.md │ │ ├── index.ts │ │ ├── types.ts │ │ ├── utils.spec.ts │ │ └── utils.ts │ └── vee-validate/ │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── ErrorMessage.ts │ │ ├── Field.ts │ │ ├── FieldArray.ts │ │ ├── Form.ts │ │ ├── config.ts │ │ ├── defineRule.ts │ │ ├── devtools.ts │ │ ├── globals.d.ts │ │ ├── index.ts │ │ ├── symbols.ts │ │ ├── types/ │ │ │ ├── common.ts │ │ │ ├── devtools.ts │ │ │ ├── forms.ts │ │ │ ├── index.ts │ │ │ └── paths.ts │ │ ├── useField.ts │ │ ├── useFieldArray.ts │ │ ├── useFieldError.ts │ │ ├── useFieldState.ts │ │ ├── useFieldValue.ts │ │ ├── useForm.ts │ │ ├── useFormErrors.ts │ │ ├── useFormValues.ts │ │ ├── useIsFieldDirty.ts │ │ ├── useIsFieldTouched.ts │ │ ├── useIsFieldValid.ts │ │ ├── useIsFormDirty.ts │ │ ├── useIsFormTouched.ts │ │ ├── useIsFormValid.ts │ │ ├── useIsSubmitting.ts │ │ ├── useIsValidating.ts │ │ ├── useResetForm.ts │ │ ├── useSetFieldError.ts │ │ ├── useSetFieldTouched.ts │ │ ├── useSetFieldValue.ts │ │ ├── useSetFormErrors.ts │ │ ├── useSetFormTouched.ts │ │ ├── useSetFormValues.ts │ │ ├── useSubmitCount.ts │ │ ├── useSubmitForm.ts │ │ ├── useValidateField.ts │ │ ├── useValidateForm.ts │ │ ├── utils/ │ │ │ ├── assertions.ts │ │ │ ├── common.ts │ │ │ ├── events.ts │ │ │ ├── index.ts │ │ │ ├── rules.ts │ │ │ └── vnode.ts │ │ └── validate.ts │ └── tests/ │ ├── .eslintrc.json │ ├── ErrorMessage.spec.ts │ ├── Field.spec.ts │ ├── FieldArray.spec.ts │ ├── Form.spec.ts │ ├── define.spec.ts │ ├── helpers/ │ │ ├── ModelComp.ts │ │ └── index.ts │ ├── useField.spec.ts │ ├── useFieldArray.spec.ts │ ├── useFieldError.spec.ts │ ├── useFieldValue.spec.ts │ ├── useForm.spec.ts │ ├── useFormErrors.spec.ts │ ├── useFormValues.spec.ts │ ├── useIsFieldDirty.spec.ts │ ├── useIsFieldTouched.spec.ts │ ├── useIsFieldValid.spec.ts │ ├── useIsFormDirty.spec.ts │ ├── useIsFormTouched.spec.ts │ ├── useIsFormValid.spec.ts │ ├── useIsSubmitting.spec.ts │ ├── useIsValidating.spec.ts │ ├── useResetForm.spec.ts │ ├── useSetFieldError.spec.ts │ ├── useSetFieldTouched.spec.ts │ ├── useSetFieldValue.spec.ts │ ├── useSetFormErrors.spec.ts │ ├── useSetFormTouched.spec.ts │ ├── useSetFormValues.spec.ts │ ├── useSubmitCount.spec.ts │ ├── useSubmitForm.spec.ts │ ├── useValidateField.spec.ts │ ├── useValidateForm.spec.ts │ ├── utils/ │ │ └── assertions.spec.ts │ └── validate.spec.ts ├── pnpm-workspace.yaml ├── scripts/ │ ├── build.mjs │ ├── config.mjs │ ├── copy-mds.mjs │ ├── generate-dts.mjs │ ├── info.mjs │ ├── normalize-path.mjs │ ├── release.sh │ └── tag-release.mjs ├── tsconfig.json ├── vitest.config.ts └── vitest.setup.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .changeset/README.md ================================================ # Changesets Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) ================================================ FILE: .changeset/config.json ================================================ { "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [["vee-validate", "@vee-validate/i18n", "@vee-validate/rules", "@vee-validate/nuxt"]], "linked": [], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": [] } ================================================ FILE: .changeset/evil-loops-shine.md ================================================ --- 'vee-validate': minor --- feat: remove deprecated useField props ================================================ FILE: .changeset/gentle-geckos-share.md ================================================ --- 'vee-validate': patch --- fix(devtools): prevent SSR memory leak in DevTools integration ================================================ FILE: .changeset/happy-wasps-rush.md ================================================ --- 'vee-validate': patch --- feat: expose `getConfig` as a public API ================================================ FILE: .changeset/pre.json ================================================ { "mode": "pre", "tag": "beta", "initialVersions": { "vee-validate-docs": null, "@vee-validate/i18n": "4.15.1", "@vee-validate/nuxt": "4.15.1", "@vee-validate/rules": "4.15.1", "vee-validate": "4.15.1" }, "changesets": [ "evil-loops-shine", "gentle-geckos-share", "happy-wasps-rush", "pretty-onions-add", "revert-number-to-string", "six-parrots-flash", "smart-needles-own", "stupid-friends-relate" ] } ================================================ FILE: .changeset/pretty-onions-add.md ================================================ --- 'vee-validate': patch --- Fix dev tools not showing all field states ================================================ FILE: .changeset/revert-number-to-string.md ================================================ --- 'vee-validate': patch --- fix: revert number input type back to string from number, closes #4699 and #4482 ================================================ FILE: .changeset/six-parrots-flash.md ================================================ --- 'vee-validate': minor --- feat: remove deprecated useForm define methods ================================================ FILE: .changeset/smart-needles-own.md ================================================ --- 'vee-validate': major '@vee-validate/rules': major '@vee-validate/nuxt': major '@vee-validate/i18n': major --- feat: implement standard schema ================================================ FILE: .changeset/stupid-friends-relate.md ================================================ --- 'vee-validate': patch --- Fix dev tools do not display nested fields with name 'id' ================================================ FILE: .circleci/config.yml ================================================ # Use the latest 2.1 version of CircleCI pipeline process engine. # See: https://circleci.com/docs/2.0/configuration-reference version: 2.1 jobs: # Below is the definition of your job to build and test your app, you can rename and customize it as you want. test: docker: - image: cimg/node:22.11 steps: # Checkout the code as the first step. - checkout - restore_cache: name: Restore pnpm Package Cache keys: - pnpm-packages-{{ checksum "pnpm-lock.yaml" }} - run: name: Use latest Corepack command: | echo "Before: corepack version => $(corepack --version || echo 'not installed')" sudo npm install -g corepack@latest echo "After : corepack version => $(corepack --version)" - run: name: Install pnpm package manager command: | corepack enable --install-directory ~/bin corepack prepare pnpm@latest-9 --activate pnpm --version pnpm config set store-dir .pnpm-store - run: name: Install dependencies command: pnpm install - save_cache: name: Save pnpm Package Cache key: pnpm-packages-{{ checksum "pnpm-lock.yaml" }} paths: - .pnpm-store - run: name: Build command: pnpm build - run: name: Type check command: pnpm typecheck - run: name: Generate Coverage command: pnpm cover - run: name: Upload Coverage command: bash <(curl -s https://codecov.io/bash) workflows: ci: jobs: - test ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: logaretm # patreon: logaretm # open_collective: vee-validate ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # 'https://www.buymeacoffee.com/logaretm' ================================================ FILE: .github/ISSUE_TEMPLATE/Feature_request.md ================================================ --- name: 🚀 Feature request about: Suggest an idea for this project. --- **Is your feature request related to a problem? Please describe.** **Describe the solution you'd like** **Describe alternatives you've considered** ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yaml ================================================ name: Bug Report description: File a bug report title: '' body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! - type: textarea id: what-happened attributes: label: What happened? description: Describe the bug placeholder: Tell us what you see! validations: required: true - type: textarea id: repro attributes: label: Reproduction steps description: 'How do you trigger this bug? Please walk us through it step by step.' value: | 1. 2. 3. ... - type: dropdown id: version attributes: label: Version description: What version of Vue.js and vee-validate are you running? options: - Vue.js 3.x and vee-validate 5.x - Vue.js 3.x and vee-validate 4.x - Vue.js 2.x and vee-validate 3.x - Vue.js 2.x and vee-validate 2.x validations: required: true - type: checkboxes id: browsers attributes: label: What browsers are you seeing the problem on? options: - label: Firefox - label: Chrome - label: Safari - label: Microsoft Edge - type: textarea id: logs attributes: label: Relevant log output description: If you have an error log or stacktrace, copy it and paste it here render: shell - type: input id: demo attributes: label: Demo link description: If you can reproduce this issue on codesandbox/codepen, please paste the link here validations: required: true - type: checkboxes attributes: label: Code of Conduct description: The Code of Conduct helps create a friendly environment options: - label: I agree to follow this project's [Code of Conduct](CONDUCT.md) required: true ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ 🔎 __Overview__ <!-- Explain the why behind adding this PR, here is a couple of examples --> <!-- This PR {adds/fixes/improves} the {feature/bug/something}. This PR changes the {locale} messages style because {reason} --> 🤓 __Code snippets/examples (if applicable)__ ```js // some code ``` ✔ __Issues affected__ <!-- list of issues formatted like this closes #{issue id} --> ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: [push, workflow_call] jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - run: corepack enable - uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm' - name: Install dependencies run: pnpm install - name: Build run: pnpm build - name: Lint run: pnpm lint typecheck: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - run: corepack enable - uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm' - name: Install dependencies run: pnpm install - name: Build run: pnpm build - name: Type Check run: pnpm typecheck tests: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - run: corepack enable - uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm' - name: Install dependencies run: pnpm install - name: Build run: pnpm build - name: Test run: pnpm test ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: workflow_dispatch permissions: id-token: write contents: write jobs: checks: uses: ./.github/workflows/ci.yml release: needs: checks runs-on: ubuntu-latest permissions: id-token: write contents: write steps: - name: Checkout code uses: actions/checkout@v4 - run: corepack enable - uses: actions/setup-node@v4 with: node-version: 24 cache: 'pnpm' registry-url: 'https://registry.npmjs.org' - name: Install dependencies run: pnpm install - name: Tag and Release run: | git config --global user.email "github-action@users.noreply.github.com" git config --global user.name "GitHub Action" pnpm ci:version git add . git commit -m "chore(release): publish" pnpm ci:tag pnpm ci:publish git push && git push --tags env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} NPM_CONFIG_PROVENANCE: true ================================================ FILE: .gitignore ================================================ node_modules .nyc_output npm-debug.log coverage .idea .vscode .vs yarn-error.log dist .rpt2_cache ./types .DS_STORE lerna-debug.log packages/*/src/playground.ts ================================================ FILE: .husky/.gitignore ================================================ _ ================================================ FILE: .husky/commit-msg ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" pnpm commitlint --edit $1 ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" pnpm lint-staged ================================================ FILE: .prettierrc ================================================ { "printWidth": 120, "tabWidth": 2, "semi": true, "singleQuote": true, "bracketSpacing": true, "arrowParens": "avoid", "endOfLine": "lf", "plugins": ["prettier-plugin-astro"], "overrides": [ { "files": "*.astro", "options": { "parser": "astro" } } ] } ================================================ FILE: CHANGELOG.md ================================================ # Change Log ## 5.0.0-beta.1 ### Minor Changes - f629397: feat: remove deprecated useField props - fb5e04e: feat: remove deprecated useForm define methods ### Patch Changes - f2807b8: fix(devtools): prevent SSR memory leak in DevTools integration - e6db423: feat: expose `getConfig` as a public API - 49fcf4c: Fix dev tools not showing all field states - 1ce0731: fix: revert number input type back to string from number, closes #4699 and #4482 - 095df65: Fix dev tools do not display nested fields with name 'id' ## 5.0.0-beta.0 ### Major Changes - 04ff47c: feat: implement standard schema ## 4.15.1 ### Patch Changes - 721e980: Align FormErrors type with its actual structure at runtime. - 546d82e: fix: normalize objects before equality checks closes #5006 ## 4.15.0 ### Patch Changes - 30281f5: fix: lazy load the devtools dep to force it out of production bundle - ec121b1: fix: skip loading devtools if in SSR ## 4.14.7 ### Patch Changes - be994b4: fix: show uncontrolled field info in devtools closes #4914 ## 4.14.6 ## 4.14.5 ### Patch Changes - e9f8c88: fix: force loading the mjs module when using nuxt ## 4.14.4 ### Patch Changes - f33974c: fix(types): expose field and form slot prop types closes #4900 - 0991c01: fix: devtools crashing when a field name is defined as getter - ecb540a: fix: handle getter field names properly closes #4877 - 4f88d85: fix: specify module type on package.json ## 4.14.3 ### Patch Changes - 07c27d5: fix: remove rogue console.log ## 4.14.2 ### Patch Changes - f0d4e24: fix: upgrade vue devtools dependency version closes #4863 ## 4.14.1 ## 4.14.0 ### Minor Changes - 404cf57: chore: bump release ### Patch Changes - f7a4929: feat: expose useFormContext closes #4490 - 97cebd8: chore: add 'exports' field in package.json for all packages - 421ae69: "fix(types): export component internal types" ## 4.13.2 ### Patch Changes - afbd0e5: feat: support valibot 0.33.0 ## 4.13.1 ## 4.13.0 ### Minor Changes - 454bc45: fix: force resetForm should not merge values closes #4680 closes #4729 - 27fe5c8: feat: provide form values as context for yup closes #4753 ### Patch Changes - ae3772a: feat: expose setValue on Field instance and slot props closes #4755 - fd008c1: feat: added ResetFormOpts arg to useResetForm closes #4707 ## 4.12.8 ### Patch Changes - f8bab9c: "fix: field-level validation not working with typed scheams closes #4744" ## 4.12.7 ### Patch Changes - 1376794: fix: handle meta.required for single field schemas closes #4738 - 1376794: fix: add try-catch for schema description logic across all major schema providers - c4415f8: fix: ensure meta.required is reactive whenever the schema changes closes #4738 ## 4.12.6 ### Patch Changes - 07d01fd: fix: re-apply errors to avoid race conditions ## 4.12.5 ### Patch Changes - d779980: fix: make sure removePathState removes the correct path state - 9eda544: "fix: remove event arg from define field handlers for component compat closes #4637" ## 4.12.4 ### Patch Changes - 2a09a58: "fix: check if both source and target objects are POJOs" ## 4.12.3 ### Patch Changes - 72e4379: fix: remove deep data mutation warning closes #4597 - a18c19f: feat: allow path meta querying for nested fields closes #4575 - e2171f8: feat: expose some state on form instance ## 4.12.2 ### Patch Changes - b2203c8e: fix: apply schema casts when submitting closes #4565 - ec8a4d7e: fix: defineField should respect global validateOnModelUpdate closes #4567 ## 4.12.1 ### Patch Changes - 36f6b9e6: fix: reset form and field behaviors for unspecified values closes #4564 - c1c6f399: fix: unref initial values when initializing the form closes #4563 ## 4.12.0 ### Minor Changes - bbecc973: feat: deprecate reactive initial values closes #4402 ### Patch Changes - f9a95843: feat: add label support to defineField closes #4530 - f688896f: fix: avoid overriding paths and destroy path on remove closes #4476 closes #4557 - 2abb8966: fix: clone values before reset closes #4536 - e370413b: fix: handle hoisted paths overriding one another - 95b701f7: feat: allow getters for field arrays ## 4.11.8 ### Patch Changes - d1b5b855: fix: avoid triggering extra model value events closes #4461 - 78c4668e: feat: allow null as a valid Form prop type closes #4483 ## 4.11.7 ### Patch Changes - a1414f6a: fix: export ModelessBinds type closes #4478 ## 4.11.6 ### Patch Changes - f683e909: fix(types): infer the model value prop name correctly ## 4.11.5 ### Patch Changes - 27c9ef24: feat(types): stronger define component bind types closes #4421 - 804ec6fa: fix: use flags to avoid validating during reset #4404 #4467 ## 4.11.4 ### Patch Changes - 4d8ed7eb: feat: added reset opts to force values closes #4440 - b53400e2: fix: silent validation should not mark a field as validated - 8f680bf1: fix: clone the schema object before validating closes #4459 - 5231f439: fix: respect validate on model update configuration closes #4451, closes #4467 ## 4.11.3 ## 4.11.2 ### Patch Changes - 2ff045c1: fix: do not warn if a form or a field was resolved closes #4399 - 73219b40: feat: expose all internal types - 4947e88f: feat: expose BaseInputBinds and BaseComponentBinds interfaces #4409 - ecbb690d: feat: query fields meta state ## 4.11.1 ### Patch Changes - 5e23dcb9: fix: add support for parsing range inputs ## 4.11.0 ### Minor Changes - 2d8143f9: feat: added composition setter functions ## 4.10.9 ### Patch Changes - c02337f3: fix: correct the setErrors type to allow for string[] ## 4.10.8 ### Patch Changes - a9a473b4: feat(perf): improve performance setFieldError and setFieldValue closes #4382 ## 4.10.7 ### Patch Changes - 9290f5a9: fix: clone values inserted into field arrays closes #4372 - 93f8001a: fix: do not warn if the validation is for removed paths closes #4368 ## 4.10.6 ### Patch Changes - 40ce7a91: feat: expose normalizeRules closes #4348 - e9b215a7: fix: resetForm should cast typed schema values closes #4347 - 4e11ff95: fix: validate form values on setValues by default closes #4359 - e354a13a: fix: Normalize error paths to use brackets for indices closes #4211 - 68080d28: feat: use silent validation when field is initialized closes #4312 ## 4.10.5 ### Patch Changes - 6a1dc9bd: fix: component blur event and respect model update config closes #4346 ## 4.10.4 ### Patch Changes - 2f9ca91c: fix(types): remove deep readonly type for now ## 4.10.3 ### Patch Changes - 32537e14: fix: less strict object checks for undefined and missing keys closes #4341 - c3698f07: fix: respect model modifiers when emitting the value closes #4333 ## 4.10.2 ### Patch Changes - 1660048e: fix: define binds not respecting config events ## 4.10.1 ### Patch Changes - fc416918: fix: handle NaN when parsing number inputs closes #4328 - 435e7857: fix: reset present values after all path mutation - 273cca74: fix: reset field should not validate closes #4323 ## 4.10.0 ### Minor Changes - 7a548f42: chore: require vue 3.3 and refactor types - 7ce9d671: feat(breaking): disable v-model support by default closes #4283 - bfd6b00a: "feat: allow custom models for defineComponentBinds" - d4fafc95: "feat: allow handleBlur to run validations" - 05d957ec: feat: mark form values as readonly closes #4282 ### Patch Changes - 77345c42: fix: reset form should merge values closes #4320 - f1dc1359: fix: use event value if no checked value for checkbox/radio closes #4308 - 3e4a7c13: feat(dx): make `syncVModel` accept the model propName - 2cf0eec9: feat: allow multiple messages in a validator fn closes #4322 #4318 - ed208918: fix: trigger validation with setFieldValue by default closes #4314 - 6a3f9f15: fix: parse native number fields closes #4313 ## 4.9.6 ### Patch Changes - b138282a: fix(types): export SetFieldValueOptions interface closes #4290 - 6e074f77: fix: handleBlur should respect blur validate config closes #4285 ## 4.9.5 ### Patch Changes - 7356c102: fix: setFieldError should set meta.valid closes #4274 ## 4.9.4 ### Patch Changes - f4ea2c05: fix: exclude undefined and null from initial values closes #4139 ## 4.9.3 ### Patch Changes - 09d5596b: fix: run validation on value change closes #4251 - 9bfbfaaf: feat: added isValidating to useForm - 48b45d91: fix: hoist nested errors path to the deepest direct parent closes #4063 ## 4.9.2 ### Patch Changes - 31090e0d: avoid double unset path with field array remove - 9046308b: fixed validations running for unmounted fields - fe322a07: batch unsets and sort paths unset order for safer unsets closes #4115 ## 4.9.1 ### Patch Changes - 681bbab4: Added type-fest to core package dependencies ## 4.9.0 ### Minor Changes - 41b5d39b: Implemented path types into various form API functions - 95409080: Added component and input binds helpers ### Patch Changes - 7554d4a6: fix field array triggering validation when an item is removed - 298577b7: setValues does not delete unspecified fields values ## 4.8.6 ### Patch Changes - 6e0b0557: Introduced official nuxt module package ## 4.8.5 ### Patch Changes - 9048a238: fixed zod union issues not showing up as errors closes #4204 All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [4.8.4](https://github.com/logaretm/vee-validate/compare/v4.8.3...v4.8.4) (2023-03-24) ### Bug Fixes - make initial values partial closes [#4195](https://github.com/logaretm/vee-validate/issues/4195) ([eeccd0c](https://github.com/logaretm/vee-validate/commit/eeccd0c55814408670eced3717d0347590da3488)) - properly unref the schema before checking for default values closes [#4196](https://github.com/logaretm/vee-validate/issues/4196) ([8e3663d](https://github.com/logaretm/vee-validate/commit/8e3663d18357574ea4d394197f2c66889eeef6fa)) ### Features - allow name ref to be a lazy function ([8fb543a](https://github.com/logaretm/vee-validate/commit/8fb543a6e91c17d8541389e29c7014dc1f804c91)) ## [4.8.3](https://github.com/logaretm/vee-validate/compare/v4.8.2...v4.8.3) (2023-03-15) **Note:** Version bump only for package vee-validate ## [4.8.2](https://github.com/logaretm/vee-validate/compare/v4.8.1...v4.8.2) (2023-03-14) ### Bug Fixes - do not use name as a default label for useField closes [#4164](https://github.com/logaretm/vee-validate/issues/4164) ([d5acff7](https://github.com/logaretm/vee-validate/commit/d5acff719797c77ba4ff3be5f78c4a45374f9809)) ## [4.8.1](https://github.com/logaretm/vee-validate/compare/v4.8.0...v4.8.1) (2023-03-12) ### Bug Fixes - make sure to have a fallback for undefined casts closes [#4186](https://github.com/logaretm/vee-validate/issues/4186) ([9f1c63b](https://github.com/logaretm/vee-validate/commit/9f1c63b4dbc59f30c17bfe427020586db36cbdec)) ### Features - expose errorBag to <Form /> slot props ([371744e](https://github.com/logaretm/vee-validate/commit/371744eea3d3cb0a244dcd9788f4f3f2a7714132)) # [4.8.0](https://github.com/logaretm/vee-validate/compare/v4.7.4...v4.8.0) (2023-03-12) ### Bug Fixes - finally handicap yup schema resolution ([303b1fb](https://github.com/logaretm/vee-validate/commit/303b1fb771ee78816ef0916e4f0e26318ad641b0)) - initial sync with v-model if enabled closes [#4163](https://github.com/logaretm/vee-validate/issues/4163) ([1040643](https://github.com/logaretm/vee-validate/commit/1040643f40ba622010ab935095dffb8d926cd76d)) - properly aggregrate nested errors for yup ([7f90bbc](https://github.com/logaretm/vee-validate/commit/7f90bbceeaeb7806a9626adb72981933a69db96f)) - remove console.log from devtools integration ([3c2d51c](https://github.com/logaretm/vee-validate/commit/3c2d51c56f80918ef6644b034594df1a3e81eb03)) - remove yup schema type and rely on assertions ([5cbb913](https://github.com/logaretm/vee-validate/commit/5cbb913071e315264d62fda7d1219bdc28d3faf0)) - render zod multiple errors in nested objects closes [#4078](https://github.com/logaretm/vee-validate/issues/4078) ([f74fb69](https://github.com/logaretm/vee-validate/commit/f74fb69977d17ef8fab4c22734ffd76ca1c02a48)) - run silent validation after array mutations closes [#4096](https://github.com/logaretm/vee-validate/issues/4096) ([044b4b4](https://github.com/logaretm/vee-validate/commit/044b4b44601908330c65541ce2bee6a110b1604f)) - type inference fix ([ac0383f](https://github.com/logaretm/vee-validate/commit/ac0383f1fb335bf92c9249f65bf319ca182545b7)) - watch and re-init array fields if form data changed closes [#4153](https://github.com/logaretm/vee-validate/issues/4153) ([6e784cc](https://github.com/logaretm/vee-validate/commit/6e784ccacbe89b5cd9daa9e3827808f7056aac04)) ### Features - Better Yup and Zod typing with output types and input inference ([#4064](https://github.com/logaretm/vee-validate/issues/4064)) ([3820a5b](https://github.com/logaretm/vee-validate/commit/3820a5b8eb3f8c6cd9239057746ccfb4b2e57e76)) - export type `FieldState` ([#4159](https://github.com/logaretm/vee-validate/issues/4159)) ([69c0d12](https://github.com/logaretm/vee-validate/commit/69c0d12434d50b52f4691c2f95d739049a3d1fcb)) ## [4.7.4](https://github.com/logaretm/vee-validate/compare/v4.7.3...v4.7.4) (2023-02-07) ### Bug Fixes - pass the field label as a seperate value closes [#4097](https://github.com/logaretm/vee-validate/issues/4097) ([89f8689](https://github.com/logaretm/vee-validate/commit/89f8689b673be27f0fc221d6c096efa11dacd3e6)) ### Features - **#4117:** add resetField on Form/useForm ([#4120](https://github.com/logaretm/vee-validate/issues/4120)) ([87c4278](https://github.com/logaretm/vee-validate/commit/87c42787c0b4de5a09abe0d29deb92b28b59023e)), closes [#4117](https://github.com/logaretm/vee-validate/issues/4117) - expose state getters on the form instance via template refs ([#4121](https://github.com/logaretm/vee-validate/issues/4121)) ([7f1c39c](https://github.com/logaretm/vee-validate/commit/7f1c39c0d9a0d1f7b7768b68c6705b5bfda91599)) ## [4.7.3](https://github.com/logaretm/vee-validate/compare/v4.7.2...v4.7.3) (2022-11-13) ### Bug Fixes - use cloned value when setting field value closes [#3991](https://github.com/logaretm/vee-validate/issues/3991) ([90b61fc](https://github.com/logaretm/vee-validate/commit/90b61fc8810a1fdc677507251735b4210f175f4b)) ## [4.7.2](https://github.com/logaretm/vee-validate/compare/v4.7.1...v4.7.2) (2022-11-02) ### Bug Fixes - don't mutate validated meta when silent validation closes [#3981](https://github.com/logaretm/vee-validate/issues/3981) closes [#3982](https://github.com/logaretm/vee-validate/issues/3982) ([6652a22](https://github.com/logaretm/vee-validate/commit/6652a22f99cde5b018c633365025d74e15dde835)) ## [4.7.1](https://github.com/logaretm/vee-validate/compare/v4.7.0...v4.7.1) (2022-10-23) ### Bug Fixes - clean up single group value after unmount closes [#3963](https://github.com/logaretm/vee-validate/issues/3963) ([#3972](https://github.com/logaretm/vee-validate/issues/3972)) ([8ccfd2b](https://github.com/logaretm/vee-validate/commit/8ccfd2b2b542963d3d35cfe5f82490c94ec1635f)) - correctly mutate deep field array item and trigger validation ([#3974](https://github.com/logaretm/vee-validate/issues/3974)) ([267736f](https://github.com/logaretm/vee-validate/commit/267736f43ca207a8fe35af30020fc61fdc009265)) - mark slot prop field value as any closes [#3969](https://github.com/logaretm/vee-validate/issues/3969) ([#3973](https://github.com/logaretm/vee-validate/issues/3973)) ([70ddc5b](https://github.com/logaretm/vee-validate/commit/70ddc5b60232f0dc761b7803a3220010d2f8ba69)) # [4.7.0](https://github.com/logaretm/vee-validate/compare/v4.6.10...v4.7.0) (2022-10-09) ### Features - allow passing form control to useField closes [#3204](https://github.com/logaretm/vee-validate/issues/3204) ([#3923](https://github.com/logaretm/vee-validate/issues/3923)) ([4c59d63](https://github.com/logaretm/vee-validate/commit/4c59d634f25d7fff024b50f3ffd667f7fdf0076c)) - expose controlled values on useForm ([#3924](https://github.com/logaretm/vee-validate/issues/3924)) ([2517319](https://github.com/logaretm/vee-validate/commit/25173196f3b689d919015cf8e7df8254b9e3090e)) ## [4.6.10](https://github.com/logaretm/vee-validate/compare/v4.6.9...v4.6.10) (2022-09-30) ### Bug Fixes - use ssr safe file check ([56663aa](https://github.com/logaretm/vee-validate/commit/56663aa2e50d7aa285ca1cb22887c8e8b3f7fd3c)) ## [4.6.9](https://github.com/logaretm/vee-validate/compare/v4.6.8...v4.6.9) (2022-09-19) ### Bug Fixes - perform field reset before all values reset closes [#3934](https://github.com/logaretm/vee-validate/issues/3934) ([1c016d9](https://github.com/logaretm/vee-validate/commit/1c016d93b367229644dca643931ef63bc6e433dc)) ## [4.6.8](https://github.com/logaretm/vee-validate/compare/v4.6.7...v4.6.8) (2022-09-19) ### Bug Fixes - ensure validation if we skip checkbox value setting [#3927](https://github.com/logaretm/vee-validate/issues/3927) ([#3930](https://github.com/logaretm/vee-validate/issues/3930)) ([82d05db](https://github.com/logaretm/vee-validate/commit/82d05dbd2a5c7d5ea2fe7b73222dd339e92ee373)) - extend is equal with file comparison logic [#3911](https://github.com/logaretm/vee-validate/issues/3911) ([#3932](https://github.com/logaretm/vee-validate/issues/3932)) ([c7c806c](https://github.com/logaretm/vee-validate/commit/c7c806c0c5393f3188c16384f5fc1b46ebc78cbd)) - handle nested value change validation [#3926](https://github.com/logaretm/vee-validate/issues/3926) ([#3929](https://github.com/logaretm/vee-validate/issues/3929)) ([771e7f2](https://github.com/logaretm/vee-validate/commit/771e7f21cf332052b74c5506a8c2f38f666cae55)) ### Features - expose RuleExpression type closes [#3913](https://github.com/logaretm/vee-validate/issues/3913) ([cdaf22d](https://github.com/logaretm/vee-validate/commit/cdaf22df04b42a68f55133ad3854aae9a7ad6953)) ## [4.6.7](https://github.com/logaretm/vee-validate/compare/v4.6.6...v4.6.7) (2022-08-27) ### Bug Fixes - allow generics for generic function type ([91e97aa](https://github.com/logaretm/vee-validate/commit/91e97aa41bca278970780973fcbf90e17fb29920)) - handle validation races for async validations ([#3908](https://github.com/logaretm/vee-validate/issues/3908)) ([8c82079](https://github.com/logaretm/vee-validate/commit/8c82079dac8535678e45428ad8e5afe7dcd3da63)) ## [4.6.6](https://github.com/logaretm/vee-validate/compare/v4.6.5...v4.6.6) (2022-08-16) ### Bug Fixes - return value if no model modifiers are defined closes [#3895](https://github.com/logaretm/vee-validate/issues/3895) ([#3896](https://github.com/logaretm/vee-validate/issues/3896)) ([6ab40df](https://github.com/logaretm/vee-validate/commit/6ab40df4452c5bee8a487a37164e2273c2aaf0ba)) ## [4.6.5](https://github.com/logaretm/vee-validate/compare/v4.6.4...v4.6.5) (2022-08-11) ### Bug Fixes - reset the original value when resetField is called [#3891](https://github.com/logaretm/vee-validate/issues/3891) ([#3892](https://github.com/logaretm/vee-validate/issues/3892)) ([7113dcc](https://github.com/logaretm/vee-validate/commit/7113dccdeb962d8efa064ff0ebd171b2aa2f4c4d)) ## [4.6.4](https://github.com/logaretm/vee-validate/compare/v4.6.3...v4.6.4) (2022-08-07) ### Bug Fixes - make sure to deep watch created models by useFieldModel ([fbe273c](https://github.com/logaretm/vee-validate/commit/fbe273c6f2c5d30a1996777561eda2268d8a02e0)) ## [4.6.3](https://github.com/logaretm/vee-validate/compare/v4.6.2...v4.6.3) (2022-08-07) ### Features - Expose InvalidSubmissionHandler and GenericValidateFunction types ([#3853](https://github.com/logaretm/vee-validate/issues/3853)) ([3ccf27d](https://github.com/logaretm/vee-validate/commit/3ccf27d5b9c1fe9cf655b89533eb1802cb5717d4)) ## [4.6.2](https://github.com/logaretm/vee-validate/compare/v4.6.1...v4.6.2) (2022-07-17) ### Bug Fixes - avoid toggling field array checkboxes values closes [#3844](https://github.com/logaretm/vee-validate/issues/3844) ([fffad4b](https://github.com/logaretm/vee-validate/commit/fffad4bea68cc949d0bce440b5daf43901aaca7f)) ### Features - expose field and form options closes [#3843](https://github.com/logaretm/vee-validate/issues/3843) ([7437612](https://github.com/logaretm/vee-validate/commit/7437612ab554f8f65b445f7b065725b570a9a14a)) ## [4.6.1](https://github.com/logaretm/vee-validate/compare/v4.6.0...v4.6.1) (2022-07-12) ### Bug Fixes - pass onInvalidSubmit prop to submitForm closes [#3841](https://github.com/logaretm/vee-validate/issues/3841) ([b6cf543](https://github.com/logaretm/vee-validate/commit/b6cf543b600246942fc7f6802a0cc6ea1038603a)) # [4.6.0](https://github.com/logaretm/vee-validate/compare/v4.5.11...v4.6.0) (2022-07-11) ### Bug Fixes - added existing undefined path fallback closes [#3801](https://github.com/logaretm/vee-validate/issues/3801) ([fd0500c](https://github.com/logaretm/vee-validate/commit/fd0500c9cb4448b232eddb4cd5d8d081e5d48d08)) - avoid inserting value binding for file type inputs closes [#3760](https://github.com/logaretm/vee-validate/issues/3760) ([3c76bb2](https://github.com/logaretm/vee-validate/commit/3c76bb2ebcbafaf46047b8e41bcc053e41cf27bf)) - avoid validating when field instance exists ([3759df2](https://github.com/logaretm/vee-validate/commit/3759df20f5ba48a43d5dea4bb6d94e875f15c331)) - compare form meta.dirty based on original values than staged initials closes [#3782](https://github.com/logaretm/vee-validate/issues/3782) ([f3ffd3c](https://github.com/logaretm/vee-validate/commit/f3ffd3c00ac1f2b73b6a3039cb997d08cf8e452b)) - expose ValidationOptions type closes [#3825](https://github.com/logaretm/vee-validate/issues/3825) ([9854865](https://github.com/logaretm/vee-validate/commit/9854865ae60431256e6fb9c921d1eabc9093b5e4)) - exposed component APIs to their TS defs with refs closes [#3292](https://github.com/logaretm/vee-validate/issues/3292) ([ae59d0f](https://github.com/logaretm/vee-validate/commit/ae59d0f6f3728a2a95732517d11fdf970127fe9c)) - fast equal before deciding value was changed closes [#3808](https://github.com/logaretm/vee-validate/issues/3808) ([3d582ec](https://github.com/logaretm/vee-validate/commit/3d582ec6c884467199cc7fb86ffe0e571d85c4fb)) - use multiple batch queues for both validation modes closes [#3783](https://github.com/logaretm/vee-validate/issues/3783) ([6156603](https://github.com/logaretm/vee-validate/commit/6156603f537fb46030017fb3a4d003b6bec0d4e8)) ### Features - **4.6:** Allow mutating field array iterable's value property ([#3618](https://github.com/logaretm/vee-validate/issues/3618)) ([#3759](https://github.com/logaretm/vee-validate/issues/3759)) ([c3c40e5](https://github.com/logaretm/vee-validate/commit/c3c40e50b68cbf8aee3356416561fdf5d23ac6d2)) - add move to FieldArray ([a52f133](https://github.com/logaretm/vee-validate/commit/a52f13356c44616d699e02f9a243dd08c7bcc38e)) - added unsetValueOnUnmount config ([#3815](https://github.com/logaretm/vee-validate/issues/3815)) ([e6e1c1d](https://github.com/logaretm/vee-validate/commit/e6e1c1d66bfd4c453ac21c00b3faa2d6470040a8)) - added useFieldModel to useForm API ([26c828e](https://github.com/logaretm/vee-validate/commit/26c828e21495c485d489ea1319575d9b5c271801)) - allow keep values config to be reactive ([5009bd8](https://github.com/logaretm/vee-validate/commit/5009bd88c09f7a8c753fc52dd5bf8d4d5234567b)) - better normalization for native input file events ([2751552](https://github.com/logaretm/vee-validate/commit/2751552a42b4eaa57d22ea24c38cd31cfd5b9955)) - Remove yup type dependency ([#3704](https://github.com/logaretm/vee-validate/issues/3704)) ([e772f9a](https://github.com/logaretm/vee-validate/commit/e772f9a7b9f0e45680a65dfae249ee2092ca850e)) - Sync useField with component v-model ([#3806](https://github.com/logaretm/vee-validate/issues/3806)) ([0ef7582](https://github.com/logaretm/vee-validate/commit/0ef75823d1b90e1213f8a31014c2cf347d386ec1)) ## [4.5.11](https://github.com/logaretm/vee-validate/compare/v4.5.10...v4.5.11) (2022-04-10) ### Bug Fixes - ignore validation of removed array elements closes [#3748](https://github.com/logaretm/vee-validate/issues/3748) ([3d49faa](https://github.com/logaretm/vee-validate/commit/3d49faa4101902c2e77aee0a2d43cd29b69f7b4e)) ### Features - chain of GenericValidateFunction in useField ([#3725](https://github.com/logaretm/vee-validate/issues/3725)) ([#3726](https://github.com/logaretm/vee-validate/issues/3726)) ([8db4077](https://github.com/logaretm/vee-validate/commit/8db407785c5611c10c221eabd747c3f31770145b)) ## [4.5.10](https://github.com/logaretm/vee-validate/compare/v4.5.9...v4.5.10) (2022-03-08) **Note:** Version bump only for package vee-validate ## [4.5.9](https://github.com/logaretm/vee-validate/compare/v4.5.8...v4.5.9) (2022-02-22) ### Bug Fixes - mark fields validated via form validate as validated ([ad9fa9d](https://github.com/logaretm/vee-validate/commit/ad9fa9d853a8cabb26cdde04c20c07d4f2673aa4)) ## [4.5.8](https://github.com/logaretm/vee-validate/compare/v4.5.7...v4.5.8) (2022-01-23) ### Bug Fixes - clear old error path error when changing field name closes [#3664](https://github.com/logaretm/vee-validate/issues/3664) ([f736e62](https://github.com/logaretm/vee-validate/commit/f736e62b1bb82f940d14d74a6d505c913c1c3dde)) - field array swap not working when falsy values are present at paths ([40afbd9](https://github.com/logaretm/vee-validate/commit/40afbd9cc3fb3de71de3f6ebb0a1b2774d9018ff)) ## [4.5.7](https://github.com/logaretm/vee-validate/compare/v4.5.6...v4.5.7) (2021-12-07) ### Bug Fixes - always attach model update event closes [#3583](https://github.com/logaretm/vee-validate/issues/3583) ([6a53e80](https://github.com/logaretm/vee-validate/commit/6a53e80525a9c38ce8851407b832bc8409c3f334)) ## [4.5.6](https://github.com/logaretm/vee-validate/compare/v4.5.5...v4.5.6) (2021-11-17) ### Bug Fixes - corrected the typing for the resetField function closes [#3568](https://github.com/logaretm/vee-validate/issues/3568) ([4e9460e](https://github.com/logaretm/vee-validate/commit/4e9460e3a4f51f4a78ddcdf17f7c3073f899404f)) - new devtools typings ([f288ca5](https://github.com/logaretm/vee-validate/commit/f288ca5a59d36f23ba7f6bdd210493588f744940)) - use watchEffect to compute form meta closes [#3580](https://github.com/logaretm/vee-validate/issues/3580) ([e8729dc](https://github.com/logaretm/vee-validate/commit/e8729dc72d2a027a666515360c9537a62a8d46ad)) ## [4.5.5](https://github.com/logaretm/vee-validate/compare/v4.5.4...v4.5.5) (2021-11-01) ### Bug Fixes - prevent toggle checkboxes when form resets closes [#3551](https://github.com/logaretm/vee-validate/issues/3551) ([cad12ba](https://github.com/logaretm/vee-validate/commit/cad12ba7502af7268029930a9176d8e160efeef6)) ## [4.5.4](https://github.com/logaretm/vee-validate/compare/v4.5.3...v4.5.4) (2021-10-20) **Note:** Version bump only for package vee-validate ## [4.5.3](https://github.com/logaretm/vee-validate/compare/v4.5.2...v4.5.3) (2021-10-17) ### Features - added slot typings for components closes [#3534](https://github.com/logaretm/vee-validate/issues/3534) ([#3537](https://github.com/logaretm/vee-validate/issues/3537)) ([52a2a38](https://github.com/logaretm/vee-validate/commit/52a2a385ec6e65c7eaaed0a67615c45aba07de64)) ## [4.5.2](https://github.com/logaretm/vee-validate/compare/v4.5.1...v4.5.2) (2021-09-30) ### Bug Fixes - use klona/full mode to handle luxon values closes [#3508](https://github.com/logaretm/vee-validate/issues/3508) ([048c9c0](https://github.com/logaretm/vee-validate/commit/048c9c03d38ffd871ee4b3504daf1c83d42e9516)) ## [4.5.1](https://github.com/logaretm/vee-validate/compare/v4.5.0...v4.5.1) (2021-09-29) **Note:** Version bump only for package vee-validate # [4.5.0](https://github.com/logaretm/vee-validate/compare/v4.4.11...v4.5.0) (2021-09-26) **Note:** Version bump only for package vee-validate ## [4.4.11](https://github.com/logaretm/vee-validate/compare/v4.4.10...v4.4.11) (2021-09-11) ### Bug Fixes - dynamic rule forcing validation closes [#3485](https://github.com/logaretm/vee-validate/issues/3485) ([d3f0fc0](https://github.com/logaretm/vee-validate/commit/d3f0fc094c89375bd67bdd3f533e5ab545a83611)) ## [4.4.10](https://github.com/logaretm/vee-validate/compare/v4.4.9...v4.4.10) (2021-08-31) ### Bug Fixes - added silent validation run after reset closes [#3463](https://github.com/logaretm/vee-validate/issues/3463) ([a61f7ab](https://github.com/logaretm/vee-validate/commit/a61f7ab532d6d2fd9f237145f91bbcc9043431f6)) - handle absent model value closes [#3468](https://github.com/logaretm/vee-validate/issues/3468) ([2c4a7ff](https://github.com/logaretm/vee-validate/commit/2c4a7ffb84811ae86a1698e6e15f41dc32f8fb8d)) - **types:** remove arguments of PrivateFieldContext.handleReset ([2e45d1f](https://github.com/logaretm/vee-validate/commit/2e45d1f8a8444c0aabfd307364cadfab74802d02)) - ensure option bound value type is preserved closes [#3440](https://github.com/logaretm/vee-validate/issues/3440) ([b144615](https://github.com/logaretm/vee-validate/commit/b1446152d6f6cd4843ab206d667a7d744c2a14fc)) ## [4.4.9](https://github.com/logaretm/vee-validate/compare/v4.4.8...v4.4.9) (2021-08-05) ### Bug Fixes - ensure to clone user passed values in setters closes [#3428](https://github.com/logaretm/vee-validate/issues/3428) ([a720c24](https://github.com/logaretm/vee-validate/commit/a720c2444b64d28743ba0500aa970419029352cb)) - prioritize the current value if another field of same name is mounted closes [#3429](https://github.com/logaretm/vee-validate/issues/3429) ([cf036ec](https://github.com/logaretm/vee-validate/commit/cf036ecf9a5dad401c752c132ef5333d0f442441)) ## [4.4.8](https://github.com/logaretm/vee-validate/compare/v4.4.7...v4.4.8) (2021-07-31) **Note:** Version bump only for package vee-validate ## [4.4.7](https://github.com/logaretm/vee-validate/compare/v4.4.6...v4.4.7) (2021-07-20) ### Bug Fixes - avoid watching values at the end of reset calls closes [#3407](https://github.com/logaretm/vee-validate/issues/3407) ([86f594f](https://github.com/logaretm/vee-validate/commit/86f594f4a7cee5ed5f581419bdbd985fc53f8358)) ### Features - add standalone prop for fields ([#3379](https://github.com/logaretm/vee-validate/issues/3379)) ([3689437](https://github.com/logaretm/vee-validate/commit/36894378aa3636eeb4fb54aa747319e21c6eb5cd)) - expose FieldContext type closes [#3398](https://github.com/logaretm/vee-validate/issues/3398) ([a6e4c0a](https://github.com/logaretm/vee-validate/commit/a6e4c0ac580d4145c72118ac535bfa082c771068)) - expose form and field injection keys ([6034e66](https://github.com/logaretm/vee-validate/commit/6034e66836e0566e17f36744da19088aca33fbad)) ## [4.4.6](https://github.com/logaretm/vee-validate/compare/v4.4.5...v4.4.6) (2021-07-08) ### Bug Fixes - clean error message for singular fields after unmount ([#3385](https://github.com/logaretm/vee-validate/issues/3385)) ([4e81cce](https://github.com/logaretm/vee-validate/commit/4e81cce292380974728b952a2fa1724c1ea4f086)) - quit unsetting path if its already unset ([cfe45ba](https://github.com/logaretm/vee-validate/commit/cfe45ba38690ec27b5ee4e48a80336834a932a78)) - expose setters in composition API ([d79747d](https://github.com/logaretm/vee-validate/commit/d79747de4a25d1ced151d9bd5b767e815d7e32bf)) ## [4.4.5](https://github.com/logaretm/vee-validate/compare/v4.4.4...v4.4.5) (2021-06-13) ## [4.4.4](https://github.com/logaretm/vee-validate/compare/v4.4.3...v4.4.4) (2021-06-05) ### Bug Fixes - field with pre-register schema errors should be validated on register closes [#3342](https://github.com/logaretm/vee-validate/issues/3342) ([61c7359](https://github.com/logaretm/vee-validate/commit/61c73597b2e69c094e75c02476d825c5236710b5)) - make sure to create the container path if it exists while null or undefined ([79d3779](https://github.com/logaretm/vee-validate/commit/79d37798ccf2fef56714bdad4db553086df0ad48)) - make sure to create the container path if it exists while null or undefined ([79d3779](https://github.com/logaretm/vee-validate/commit/79d37798ccf2fef56714bdad4db553086df0ad48)) ### Features - expose setters in composition API ([61f942f](https://github.com/logaretm/vee-validate/commit/61f942f511e6fcceb10a74272ac845017ce88997)) ## [4.4.3](https://github.com/logaretm/vee-validate/compare/v4.4.2...v4.4.3) (2021-06-02) ### Bug Fixes - respect the Field bails option closes [#3332](https://github.com/logaretm/vee-validate/issues/3332) ([6679387](https://github.com/logaretm/vee-validate/commit/66793878e317f32f4759b3d01e27e3b9072eff67)) ## [4.4.2](https://github.com/logaretm/vee-validate/compare/v4.4.1...v4.4.2) (2021-05-28) ### Bug Fixes - clean up the old values path when fields exchange names fixes [#3325](https://github.com/logaretm/vee-validate/issues/3325) ([fe51c12](https://github.com/logaretm/vee-validate/commit/fe51c126ae6258ac0888ee47d9d01a27b889a5c1)) ## [4.4.1](https://github.com/logaretm/vee-validate/compare/v4.4.0...v4.4.1) (2021-05-24) ### Bug Fixes - forgot adding errors in useValidationForm ([d032d3b](https://github.com/logaretm/vee-validate/commit/d032d3b55438169fa87c18d89e073fffe3988d56)) - re-introduce the errors prop back on the form validation result closes [#3317](https://github.com/logaretm/vee-validate/issues/3317) ([b439a73](https://github.com/logaretm/vee-validate/commit/b439a73bf3c37298c251b74223984d54b8949a95)) # [4.4.0](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.2...v4.4.0) (2021-05-23) ### Bug Fixes - seperate model detection from event emitting closes [#3312](https://github.com/logaretm/vee-validate/issues/3312) ([5e72852](https://github.com/logaretm/vee-validate/commit/5e72852e80b971121d10422cf84085b07bb2d8fb)) # [4.4.0-alpha.2](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.1...v4.4.0-alpha.2) (2021-05-14) ### Bug Fixes - avoid clearing all errors before validating schema ([51c2e78](https://github.com/logaretm/vee-validate/commit/51c2e7890b87d971850dfc609c09d19b79a96fb6)) # [4.4.0-alpha.1](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.0...v4.4.0-alpha.1) (2021-05-14) ### Bug Fixes - minifier issue when handling await ([f206cac](https://github.com/logaretm/vee-validate/commit/f206cacd7e0d03a36fce5b236c23906997e0287b)) # [4.4.0-alpha.0](https://github.com/logaretm/vee-validate/compare/v4.3.6...v4.4.0-alpha.0) (2021-05-14) ### Bug Fixes - deprecate handleInput and use handleChange for both events ([#3303](https://github.com/logaretm/vee-validate/issues/3303)) ([4cb10de](https://github.com/logaretm/vee-validate/commit/4cb10de0a5f589f72c82cdd4a8859b7f044ae84c)) ### Features - custom values and errors ([#3305](https://github.com/logaretm/vee-validate/issues/3305)) ([427802b](https://github.com/logaretm/vee-validate/commit/427802b94ea309d12df26ba51ac1b3a24e4e8d46)) ## [4.3.6](https://github.com/logaretm/vee-validate/compare/v4.3.5...v4.3.6) (2021-05-08) ### Bug Fixes - added a symbol to detect non passed props with Vue 3.1.x ([#3295](https://github.com/logaretm/vee-validate/issues/3295)) ([0663539](https://github.com/logaretm/vee-validate/commit/06635397424526c3a3c4a53f63322bbfd55000ee)) ## [4.3.5](https://github.com/logaretm/vee-validate/compare/v4.3.4...v4.3.5) (2021-05-01) ### Bug Fixes - priotrize self injections over parent injections closes [#3270](https://github.com/logaretm/vee-validate/issues/3270) ([07c1234](https://github.com/logaretm/vee-validate/commit/07c12341d7f2e25e41a56ea0d5e38e9a374ae84b)) ## [4.3.4](https://github.com/logaretm/vee-validate/compare/v4.3.3...v4.3.4) (2021-04-27) ### Bug Fixes - update the valid flag regardless of mode closes [#3284](https://github.com/logaretm/vee-validate/issues/3284) ([6594ad1](https://github.com/logaretm/vee-validate/commit/6594ad15e4423c6a7861da188560b06f98365d9d)) ## [4.3.3](https://github.com/logaretm/vee-validate/compare/v4.3.2...v4.3.3) (2021-04-22) ### Features - touch all fields on submit ([#3278](https://github.com/logaretm/vee-validate/issues/3278)) ([fc4e400](https://github.com/logaretm/vee-validate/commit/fc4e400f7d9349c1e82bba8412d13e0cf69be0e1)) ## [4.3.2](https://github.com/logaretm/vee-validate/compare/v4.3.1...v4.3.2) (2021-04-21) ### Bug Fixes - unwrap initial value with useField.resetField fixes [#3272](https://github.com/logaretm/vee-validate/issues/3272) ([#3274](https://github.com/logaretm/vee-validate/issues/3274)) ([f6e9574](https://github.com/logaretm/vee-validate/commit/f6e95741f31fc085f718e07d3b1f1adfe0229df6)) ## [4.3.1](https://github.com/logaretm/vee-validate/compare/v4.3.0...v4.3.1) (2021-04-18) ### Bug Fixes - give error message component a name ([b7dcebf](https://github.com/logaretm/vee-validate/commit/b7dcebfcd202538cf082314817f97c3b8e07fefb)) - minor perf enhancement by lazy evaulation of slot props ([a306b1b](https://github.com/logaretm/vee-validate/commit/a306b1b0047ec82eaf727a6e380856de077c4fbe)) # [4.3.0](https://github.com/logaretm/vee-validate/compare/v4.2.4...v4.3.0) (2021-04-07) ### Features - added support for reactive schemas ([#3238](https://github.com/logaretm/vee-validate/issues/3238)) ([295d656](https://github.com/logaretm/vee-validate/commit/295d6567035bc3c452ad0f13fce13ff362b08005)) - added support for setting multiple field errors closes [#3117](https://github.com/logaretm/vee-validate/issues/3117) ([db0a6a0](https://github.com/logaretm/vee-validate/commit/db0a6a02cdc0fdab02a18e4756005c46dc06b1f8)) - support v-model.number ([#3252](https://github.com/logaretm/vee-validate/issues/3252)) ([8f491da](https://github.com/logaretm/vee-validate/commit/8f491da0b0998d0f7383a6a444d6aa498e3d96f4)) ## [4.2.4](https://github.com/logaretm/vee-validate/compare/v4.2.3...v4.2.4) (2021-03-26) ### Bug Fixes - validation triggered on value change ([10549b7](https://github.com/logaretm/vee-validate/commit/10549b77dc350cee4f198cb14e3fd12f61e12b80)) ## [4.2.3](https://github.com/logaretm/vee-validate/compare/v4.2.2...v4.2.3) (2021-03-22) ### Bug Fixes - prevent yup schema from setting non-interacted fields errors closes [#3228](https://github.com/logaretm/vee-validate/issues/3228) ([534f8b2](https://github.com/logaretm/vee-validate/commit/534f8b28850c9f28245a748f956d1358bb7cb2e1)) ## [4.2.2](https://github.com/logaretm/vee-validate/compare/v4.2.1...v4.2.2) (2021-03-03) ### Bug Fixes - ensure having a truthy fallback for fields missing in schema ([7cd6941](https://github.com/logaretm/vee-validate/commit/7cd694114403f7c252b6ba6b83c159110cdc58cf)) - handle pending validation runs during field unmounting ([ef5a7cc](https://github.com/logaretm/vee-validate/commit/ef5a7ccb269db8bbdee446e76dd60ebe8704b57e)) ## [4.2.1](https://github.com/logaretm/vee-validate/compare/v4.2.0...v4.2.1) (2021-02-26) ### Bug Fixes - added initial check against the field errors ([4288fb6](https://github.com/logaretm/vee-validate/commit/4288fb6291a3ed17d46569fd2b0baa690beb9cb1)) # [4.2.0](https://github.com/logaretm/vee-validate/compare/v4.1.20...v4.2.0) (2021-02-24) **Note:** Version bump only for package vee-validate ## [4.1.20](https://github.com/logaretm/vee-validate/compare/v4.1.19...v4.1.20) (2021-02-24) ### Bug Fixes - avoid setting checkbox values before registeration closes [#3183](https://github.com/logaretm/vee-validate/issues/3183) ([ab5f821](https://github.com/logaretm/vee-validate/commit/ab5f82103f8cfe5f5934a51057ce989ad30d0d44)) - change errors source to form closes [#3177](https://github.com/logaretm/vee-validate/issues/3177) ([7c13c92](https://github.com/logaretm/vee-validate/commit/7c13c92f477bc3d63067509fd9fec72964263f5d)) - use the issues array for zod error aggregation closes [#3184](https://github.com/logaretm/vee-validate/issues/3184) ([01b89e4](https://github.com/logaretm/vee-validate/commit/01b89e4940e997ef65dc950be3a13e0ffc18e881)) ## [4.1.19](https://github.com/logaretm/vee-validate/compare/v4.1.18...v4.1.19) (2021-02-16) ### Bug Fixes - use relative imports for shared type ([6790545](https://github.com/logaretm/vee-validate/commit/6790545dc9c35550d231fb14a310f3655dbc7256)) ### Features - improve typing for field yup schema ([c59f1f0](https://github.com/logaretm/vee-validate/commit/c59f1f01526b160a1081f276d732523ad9ab5ba2)) ## [4.1.18](https://github.com/logaretm/vee-validate/compare/v4.1.17...v4.1.18) (2021-02-10) ### Bug Fixes - avoid unsetting field value if switched with another closes [#3166](https://github.com/logaretm/vee-validate/issues/3166) ([f5a79fe](https://github.com/logaretm/vee-validate/commit/f5a79fe3b15f7437acf183c162e69178fd4fa7ec)) ## [4.1.17](https://github.com/logaretm/vee-validate/compare/v3.2.0...v4.1.17) (2021-02-08) ### Bug Fixes - add a handler for regex object params closes [#3073](https://github.com/logaretm/vee-validate/issues/3073) ([7a5e2eb](https://github.com/logaretm/vee-validate/commit/7a5e2ebf8303395372ae08ebcca55427a58faecb)) - added emits and onSubmit custom prop ([#3115](https://github.com/logaretm/vee-validate/issues/3115)) ([8f2c110](https://github.com/logaretm/vee-validate/commit/8f2c110f14add0fbd82a28a91601e89938144624)) - array radio fields not switching value correctly closes [#3141](https://github.com/logaretm/vee-validate/issues/3141) ([3d4efef](https://github.com/logaretm/vee-validate/commit/3d4efef68c63a3b57e2bf14fed913dbf841a7f5e)) - avoid returning undefined for form errors when form does not exist ([8cce17a](https://github.com/logaretm/vee-validate/commit/8cce17ae2846be912d51926c79e557ed8bb39582)) - avoid validating dependencies via watcheffect closes [#3156](https://github.com/logaretm/vee-validate/issues/3156) ([a7b91f6](https://github.com/logaretm/vee-validate/commit/a7b91f6e6c38f0b5262e2d4c1814154efa3b78c8)) - cast radio buttons value correctly closes [#3064](https://github.com/logaretm/vee-validate/issues/3064) ([3e0f9a4](https://github.com/logaretm/vee-validate/commit/3e0f9a47369edac32d0c8a068f8b61d8f761458f)) - clear out initial values for unregistered fields closes [#3060](https://github.com/logaretm/vee-validate/issues/3060) ([56206de](https://github.com/logaretm/vee-validate/commit/56206de995fe8f2eaca3e303ab6980784a3c95b1)) - correctly set the initial value from the v-model closes [#3107](https://github.com/logaretm/vee-validate/issues/3107) ([4bed9a8](https://github.com/logaretm/vee-validate/commit/4bed9a806323139d2f274e51b6bfe3de2190e54d)) - export submission types [#3112](https://github.com/logaretm/vee-validate/issues/3112) ([3f35167](https://github.com/logaretm/vee-validate/commit/3f351670da02364b0fb8e61198145dfa02dc59b9)) - fill the target rule params for message generators closes [#3077](https://github.com/logaretm/vee-validate/issues/3077) ([f5e1bd3](https://github.com/logaretm/vee-validate/commit/f5e1bd3cbc278a8588fa0c96af66823d82eefb8c)) - handle formless checkboxes value toggling closes [#3105](https://github.com/logaretm/vee-validate/issues/3105) ([504f30b](https://github.com/logaretm/vee-validate/commit/504f30bfcbcb1db710397ef05545b5008b0103fb)) - handle reactive field names and value swaps ([cf8051d](https://github.com/logaretm/vee-validate/commit/cf8051d3b92eb43103f4e7c682e615343239d717)) - missing export for useErrors helpers ([28537cc](https://github.com/logaretm/vee-validate/commit/28537cc547cf945b10adc485620ad226f71d60fc)) - pass down listeners to the input node closes [#3048](https://github.com/logaretm/vee-validate/issues/3048) ([2526a63](https://github.com/logaretm/vee-validate/commit/2526a63c2361e412b528cf370c03b39cb84b606d)) - prevent default reset behavior with handleReset ([a66df13](https://github.com/logaretm/vee-validate/commit/a66df13c3f39d84984581dc3c0ce368b052b6e8e)) - prevent resetForm from toggling checkbox value [#3084](https://github.com/logaretm/vee-validate/issues/3084) ([38778f9](https://github.com/logaretm/vee-validate/commit/38778f96471b6aa16fb020cfb1bde56b77a19cfb)) - react to validation events changes ([078e61b](https://github.com/logaretm/vee-validate/commit/078e61b17bd299a28752b733b494a0ddb368a812)) - reset meta correctly with resetField ([012658c](https://github.com/logaretm/vee-validate/commit/012658c082a00b1beeb53ce8cf3fcd91bc5b21ec)) - resolve component before rendering closes [#3014](https://github.com/logaretm/vee-validate/issues/3014) ([f8f481d](https://github.com/logaretm/vee-validate/commit/f8f481daad754a4b18a91e2b07b9549433d023f9)) - resolve path values with global rules closes [#3157](https://github.com/logaretm/vee-validate/issues/3157) ([beaf316](https://github.com/logaretm/vee-validate/commit/beaf3168490aee585542a19c9a910d9493e78208)) - set field initial value on the fid lookup closes [#3128](https://github.com/logaretm/vee-validate/issues/3128) ([650d5cf](https://github.com/logaretm/vee-validate/commit/650d5cf9f75f9b9247fc813acf2aff4089f05415)) - support dynamic labels closes [#3053](https://github.com/logaretm/vee-validate/issues/3053) ([31b2238](https://github.com/logaretm/vee-validate/commit/31b223878bda75c3150217ea80bb878d8dc1e320)) - typing issue from [#3134](https://github.com/logaretm/vee-validate/issues/3134) ([29e5cff](https://github.com/logaretm/vee-validate/commit/29e5cffc654a2502f29fe616eda088de958e02d3)) - use the custom injection fn for initial field values ([38cd32b](https://github.com/logaretm/vee-validate/commit/38cd32bd3ae9f263510d0ab4a1713c6a9a2011af)) ### Features - add submit count state ([#3070](https://github.com/logaretm/vee-validate/issues/3070)) ([a7fe71e](https://github.com/logaretm/vee-validate/commit/a7fe71e01072dacfeb7baa80eebf0b8d7d9d3ffd)) - added context awareness to composition helpers for fields ([b59fe88](https://github.com/logaretm/vee-validate/commit/b59fe88197ce3cd587edfc33666bcb676030fa61)) - added context information to validation functions ([7e6675d](https://github.com/logaretm/vee-validate/commit/7e6675db6a103eae33cbb6d959621b4549af66b2)) - added test cases and fallbacks for unresolved cases ([71bda03](https://github.com/logaretm/vee-validate/commit/71bda03a72a9e8f27bc0b7620ce78ba48a194309)) - added the useResetForm helper ([4c57715](https://github.com/logaretm/vee-validate/commit/4c57715ab621526a5c987cff9a53cb5b7af2155a)) - added unchecked-value prop to the field component ([af910c3](https://github.com/logaretm/vee-validate/commit/af910c3f3c6343538658ab90f356dd8957bb6a1a)) - added useErrors and useField error helpers ([4cda2fe](https://github.com/logaretm/vee-validate/commit/4cda2fea6428a7f10b53b633daa46252bf779289)) - added useIsDirty helpers ([6b7e4ab](https://github.com/logaretm/vee-validate/commit/6b7e4abfcdb2f0eebe0dd8c62785178fbee8d25f)) - added useIsSubmitting helper ([7a58fd8](https://github.com/logaretm/vee-validate/commit/7a58fd840425a5e09f625054389aebbb096c2e1a)) - added useIsTouched helpers ([fdb2d5a](https://github.com/logaretm/vee-validate/commit/fdb2d5a3c7c82d55aefef2deb95823e1ba6ba93d)) - added useIsValid helpers ([26fbb29](https://github.com/logaretm/vee-validate/commit/26fbb29467bab66c159e98793e4269768845b938)) - added useSubmitCount helper ([c4a6dea](https://github.com/logaretm/vee-validate/commit/c4a6deae68b588494ff0e2477d7ec2b9302c6f09)) - added useSubmitForm hook ([#3101](https://github.com/logaretm/vee-validate/issues/3101)) ([d042882](https://github.com/logaretm/vee-validate/commit/d04288295a090328f7022641799dbaee1c404b91)) - added useValidateField and useValidateForm helpers ([62355a8](https://github.com/logaretm/vee-validate/commit/62355a8db6477562f0689208669d0a1be63de03c)) - added validate field function to form and useForm ([#3133](https://github.com/logaretm/vee-validate/issues/3133)) ([926bed1](https://github.com/logaretm/vee-validate/commit/926bed1bded6990f17a51ca68e9aa47c339a80f2)) - added validate method on the form ref instance closes [#3030](https://github.com/logaretm/vee-validate/issues/3030) ([ed0faff](https://github.com/logaretm/vee-validate/commit/ed0faffd79615830a9f7c247abf1eae2254ee3f9)) - added validation trigger config per component closes [#3066](https://github.com/logaretm/vee-validate/issues/3066) ([f0e30a2](https://github.com/logaretm/vee-validate/commit/f0e30a2cc79843040028b7070bc88846f2447c85)) - added value change support for native multi select ([#3146](https://github.com/logaretm/vee-validate/issues/3146)) ([0601586](https://github.com/logaretm/vee-validate/commit/0601586eabbf76fac9d4fa79e6ae1d86fd3a0e37)) - added values helpers ([e0f16d6](https://github.com/logaretm/vee-validate/commit/e0f16d6f5c01c7b1e4e8832b3490b8cc7e7b8aa7)) - added warnings for non existent fields and allow reactive paths ([4182d2f](https://github.com/logaretm/vee-validate/commit/4182d2f1716d712962dff3b6be27916e311e5870)) - avoid watching rules when passed as functions ([539f753](https://github.com/logaretm/vee-validate/commit/539f7535bf935e62030b83f8c7b19e95256bcc52)) - dont render any tags when no message exists closes [#3118](https://github.com/logaretm/vee-validate/issues/3118) ([92eba41](https://github.com/logaretm/vee-validate/commit/92eba41a2cdef643bc2af4c2a0366382cdffc625)) - enhance ts typing for form functions ([8f7d8e8](https://github.com/logaretm/vee-validate/commit/8f7d8e89864b5df5255cbe5e88713022537ec236)) - enhance useField types ([dcb8049](https://github.com/logaretm/vee-validate/commit/dcb80495ffdefb2e789887e1d40b2c4a57ade257)) - enrich form validation results ([0c84c80](https://github.com/logaretm/vee-validate/commit/0c84c809fa729cd2b8620329305b4da0a45e9eaf)) - export some internal types closes [#3065](https://github.com/logaretm/vee-validate/issues/3065) ([b88dffd](https://github.com/logaretm/vee-validate/commit/b88dffdb4c638bd439d093f653bfa1915f4ad9be)) - field.reset() should reset the field to its initial value ([a11f1b7](https://github.com/logaretm/vee-validate/commit/a11f1b7dda3deafe683e13a00b28a7fab09b82cb)) - implement similar reset API for fields ([38c3923](https://github.com/logaretm/vee-validate/commit/38c392320b4154061ccc5d70dde11517357467e8)) - new reset API ([6983738](https://github.com/logaretm/vee-validate/commit/69837383e42636c24d6ee7d15cb5fe8e98f2ac55)) - rename reset methods to be more consistent ([3a0dc4d](https://github.com/logaretm/vee-validate/commit/3a0dc4db2f1a00a8a4f3940ddd452d9b1369cace)) - update docs ([0f5ac98](https://github.com/logaretm/vee-validate/commit/0f5ac98153f74bdbbd1d9f5090e4dc4b438c998f)) - use internal yup types ([#3123](https://github.com/logaretm/vee-validate/issues/3123)) ([7554bfc](https://github.com/logaretm/vee-validate/commit/7554bfc49b0103f218f901148bc06e6a455f09b0)) - use resolveDynamicComponent instead ([f1b5f89](https://github.com/logaretm/vee-validate/commit/f1b5f896840ed159df06cf59badd83282496b777)) ### Performance Improvements - cache field props in a computed property ([d266878](https://github.com/logaretm/vee-validate/commit/d2668787d0ffcab5ba2e8be048ee7334d2b0f9e7)) - cache form slot props in a computed property ([49fa2c1](https://github.com/logaretm/vee-validate/commit/49fa2c1b4a337149c533c13725d2e71bb2664706)) ## [4.1.16](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.15...vee-validate@4.1.16) (2021-02-07) **Note:** Version bump only for package vee-validate ## [4.1.15](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.14...vee-validate@4.1.15) (2021-02-07) ### Bug Fixes - resolve path values with global rules closes [#3157](https://github.com/logaretm/vee-validate/issues/3157) ([beaf316](https://github.com/logaretm/vee-validate/commit/beaf3168490aee585542a19c9a910d9493e78208)) ## [4.1.14](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.13...vee-validate@4.1.14) (2021-02-06) ### Bug Fixes - avoid validating dependencies via watcheffect closes [#3156](https://github.com/logaretm/vee-validate/issues/3156) ([a7b91f6](https://github.com/logaretm/vee-validate/commit/a7b91f6e6c38f0b5262e2d4c1814154efa3b78c8)) ## [4.1.13](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.12...vee-validate@4.1.13) (2021-02-01) ### Features - added value change support for native multi select ([#3146](https://github.com/logaretm/vee-validate/issues/3146)) ([0601586](https://github.com/logaretm/vee-validate/commit/0601586eabbf76fac9d4fa79e6ae1d86fd3a0e37)) ## [4.1.12](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.11...vee-validate@4.1.12) (2021-01-26) ### Bug Fixes - array radio fields not switching value correctly closes [#3141](https://github.com/logaretm/vee-validate/issues/3141) ([3d4efef](https://github.com/logaretm/vee-validate/commit/3d4efef68c63a3b57e2bf14fed913dbf841a7f5e)) - clear out initial values for unregistered fields closes [#3060](https://github.com/logaretm/vee-validate/issues/3060) ([56206de](https://github.com/logaretm/vee-validate/commit/56206de995fe8f2eaca3e303ab6980784a3c95b1)) - typing issue from [#3134](https://github.com/logaretm/vee-validate/issues/3134) ([29e5cff](https://github.com/logaretm/vee-validate/commit/29e5cffc654a2502f29fe616eda088de958e02d3)) ## [4.1.11](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.10...vee-validate@4.1.11) (2021-01-19) ### Features - added validate field function to form and useForm ([#3133](https://github.com/logaretm/vee-validate/issues/3133)) ([926bed1](https://github.com/logaretm/vee-validate/commit/926bed1bded6990f17a51ca68e9aa47c339a80f2)) ## [4.1.10](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.9...vee-validate@4.1.10) (2021-01-17) ### Bug Fixes - set field initial value on the fid lookup closes [#3128](https://github.com/logaretm/vee-validate/issues/3128) ([650d5cf](https://github.com/logaretm/vee-validate/commit/650d5cf9f75f9b9247fc813acf2aff4089f05415)) ## [4.1.9](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.8...vee-validate@4.1.9) (2021-01-13) ### Features - use internal yup types ([#3123](https://github.com/logaretm/vee-validate/issues/3123)) ([7554bfc](https://github.com/logaretm/vee-validate/commit/7554bfc49b0103f218f901148bc06e6a455f09b0)) ## [4.1.8](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.7...vee-validate@4.1.8) (2021-01-12) ### Features - dont render any tags when no message exists closes [#3118](https://github.com/logaretm/vee-validate/issues/3118) ([92eba41](https://github.com/logaretm/vee-validate/commit/92eba41a2cdef643bc2af4c2a0366382cdffc625)) ## [4.1.7](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.6...vee-validate@4.1.7) (2021-01-12) ### Bug Fixes - export submission types [#3112](https://github.com/logaretm/vee-validate/issues/3112) ([3f35167](https://github.com/logaretm/vee-validate/commit/3f351670da02364b0fb8e61198145dfa02dc59b9)) ## [4.1.6](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.5...vee-validate@4.1.6) (2021-01-11) ### Bug Fixes - added emits and onSubmit custom prop ([#3115](https://github.com/logaretm/vee-validate/issues/3115)) ([8f2c110](https://github.com/logaretm/vee-validate/commit/8f2c110f14add0fbd82a28a91601e89938144624)) ## [4.1.5](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.4...vee-validate@4.1.5) (2021-01-05) ### Bug Fixes - correctly set the initial value from the v-model closes [#3107](https://github.com/logaretm/vee-validate/issues/3107) ([4bed9a8](https://github.com/logaretm/vee-validate/commit/4bed9a806323139d2f274e51b6bfe3de2190e54d)) ## [4.1.4](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.3...vee-validate@4.1.4) (2021-01-04) ### Bug Fixes - handle formless checkboxes value toggling closes [#3105](https://github.com/logaretm/vee-validate/issues/3105) ([504f30b](https://github.com/logaretm/vee-validate/commit/504f30bfcbcb1db710397ef05545b5008b0103fb)) ## [4.1.3](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.2...vee-validate@4.1.3) (2021-01-02) ### Features - enhance useField types ([dcb8049](https://github.com/logaretm/vee-validate/commit/dcb80495ffdefb2e789887e1d40b2c4a57ade257)) ## [4.1.2](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.1...vee-validate@4.1.2) (2020-12-26) ### Features - added useSubmitForm hook ([#3101](https://github.com/logaretm/vee-validate/issues/3101)) ([d042882](https://github.com/logaretm/vee-validate/commit/d04288295a090328f7022641799dbaee1c404b91)) ## [4.1.1](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.0...vee-validate@4.1.1) (2020-12-18) ### Bug Fixes - missing export for useErrors helpers ([28537cc](https://github.com/logaretm/vee-validate/commit/28537cc547cf945b10adc485620ad226f71d60fc)) # [4.1.0](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.7...vee-validate@4.1.0) (2020-12-18) ### Bug Fixes - avoid returning undefined for form errors when form does not exist ([8cce17a](https://github.com/logaretm/vee-validate/commit/8cce17ae2846be912d51926c79e557ed8bb39582)) ### Features - added context awareness to composition helpers for fields ([b59fe88](https://github.com/logaretm/vee-validate/commit/b59fe88197ce3cd587edfc33666bcb676030fa61)) - added test cases and fallbacks for unresolved cases ([71bda03](https://github.com/logaretm/vee-validate/commit/71bda03a72a9e8f27bc0b7620ce78ba48a194309)) - added the useResetForm helper ([4c57715](https://github.com/logaretm/vee-validate/commit/4c57715ab621526a5c987cff9a53cb5b7af2155a)) - added useErrors and useField error helpers ([4cda2fe](https://github.com/logaretm/vee-validate/commit/4cda2fea6428a7f10b53b633daa46252bf779289)) - added useIsDirty helpers ([6b7e4ab](https://github.com/logaretm/vee-validate/commit/6b7e4abfcdb2f0eebe0dd8c62785178fbee8d25f)) - added useIsSubmitting helper ([7a58fd8](https://github.com/logaretm/vee-validate/commit/7a58fd840425a5e09f625054389aebbb096c2e1a)) - added useIsTouched helpers ([fdb2d5a](https://github.com/logaretm/vee-validate/commit/fdb2d5a3c7c82d55aefef2deb95823e1ba6ba93d)) - added useIsValid helpers ([26fbb29](https://github.com/logaretm/vee-validate/commit/26fbb29467bab66c159e98793e4269768845b938)) - added useSubmitCount helper ([c4a6dea](https://github.com/logaretm/vee-validate/commit/c4a6deae68b588494ff0e2477d7ec2b9302c6f09)) - added useValidateField and useValidateForm helpers ([62355a8](https://github.com/logaretm/vee-validate/commit/62355a8db6477562f0689208669d0a1be63de03c)) - added values helpers ([e0f16d6](https://github.com/logaretm/vee-validate/commit/e0f16d6f5c01c7b1e4e8832b3490b8cc7e7b8aa7)) - added warnings for non existent fields and allow reactive paths ([4182d2f](https://github.com/logaretm/vee-validate/commit/4182d2f1716d712962dff3b6be27916e311e5870)) - enhance ts typing for form functions ([8f7d8e8](https://github.com/logaretm/vee-validate/commit/8f7d8e89864b5df5255cbe5e88713022537ec236)) - enrich form validation results ([0c84c80](https://github.com/logaretm/vee-validate/commit/0c84c809fa729cd2b8620329305b4da0a45e9eaf)) - export some internal types closes [#3065](https://github.com/logaretm/vee-validate/issues/3065) ([b88dffd](https://github.com/logaretm/vee-validate/commit/b88dffdb4c638bd439d093f653bfa1915f4ad9be)) ## [4.0.7](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.6...vee-validate@4.0.7) (2020-12-18) ### Bug Fixes - react to validation events changes ([078e61b](https://github.com/logaretm/vee-validate/commit/078e61b17bd299a28752b733b494a0ddb368a812)) ## [4.0.6](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.5...vee-validate@4.0.6) (2020-12-15) ### Bug Fixes - prevent default reset behavior with handleReset ([a66df13](https://github.com/logaretm/vee-validate/commit/a66df13c3f39d84984581dc3c0ce368b052b6e8e)) - prevent resetForm from toggling checkbox value [#3084](https://github.com/logaretm/vee-validate/issues/3084) ([38778f9](https://github.com/logaretm/vee-validate/commit/38778f96471b6aa16fb020cfb1bde56b77a19cfb)) ### Features - added unchecked-value prop to the field component ([af910c3](https://github.com/logaretm/vee-validate/commit/af910c3f3c6343538658ab90f356dd8957bb6a1a)) ### Performance Improvements - cache field props in a computed property ([d266878](https://github.com/logaretm/vee-validate/commit/d2668787d0ffcab5ba2e8be048ee7334d2b0f9e7)) - cache form slot props in a computed property ([49fa2c1](https://github.com/logaretm/vee-validate/commit/49fa2c1b4a337149c533c13725d2e71bb2664706)) ## [4.0.5](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.4...vee-validate@4.0.5) (2020-12-12) ### Features - added validation trigger config per component closes [#3066](https://github.com/logaretm/vee-validate/issues/3066) ([f0e30a2](https://github.com/logaretm/vee-validate/commit/f0e30a2cc79843040028b7070bc88846f2447c85)) ## [4.0.4](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.3...vee-validate@4.0.4) (2020-12-09) ### Bug Fixes - add a handler for regex object params closes [#3073](https://github.com/logaretm/vee-validate/issues/3073) ([7a5e2eb](https://github.com/logaretm/vee-validate/commit/7a5e2ebf8303395372ae08ebcca55427a58faecb)) - fill the target rule params for message generators closes [#3077](https://github.com/logaretm/vee-validate/issues/3077) ([f5e1bd3](https://github.com/logaretm/vee-validate/commit/f5e1bd3cbc278a8588fa0c96af66823d82eefb8c)) ### Features - add submit count state ([#3070](https://github.com/logaretm/vee-validate/issues/3070)) ([a7fe71e](https://github.com/logaretm/vee-validate/commit/a7fe71e01072dacfeb7baa80eebf0b8d7d9d3ffd)) ## [4.0.3](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.2...vee-validate@4.0.3) (2020-12-05) ### Bug Fixes - cast radio buttons value correctly closes [#3064](https://github.com/logaretm/vee-validate/issues/3064) ([3e0f9a4](https://github.com/logaretm/vee-validate/commit/3e0f9a47369edac32d0c8a068f8b61d8f761458f)) - reset meta correctly with resetField ([012658c](https://github.com/logaretm/vee-validate/commit/012658c082a00b1beeb53ce8cf3fcd91bc5b21ec)) - use the custom injection fn for initial field values ([38cd32b](https://github.com/logaretm/vee-validate/commit/38cd32bd3ae9f263510d0ab4a1713c6a9a2011af)) ## [4.0.2](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.1...vee-validate@4.0.2) (2020-11-27) ### Bug Fixes - support dynamic labels closes [#3053](https://github.com/logaretm/vee-validate/issues/3053) ([31b2238](https://github.com/logaretm/vee-validate/commit/31b223878bda75c3150217ea80bb878d8dc1e320)) ## [4.0.1](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0...vee-validate@4.0.1) (2020-11-25) ### Bug Fixes - pass down listeners to the input node closes [#3048](https://github.com/logaretm/vee-validate/issues/3048) ([2526a63](https://github.com/logaretm/vee-validate/commit/2526a63c2361e412b528cf370c03b39cb84b606d)) # [4.0.0](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.19...vee-validate@4.0.0) (2020-11-16) ### Features - added validate method on the form ref instance closes [#3030](https://github.com/logaretm/vee-validate/issues/3030) ([ed0faff](https://github.com/logaretm/vee-validate/commit/ed0faffd79615830a9f7c247abf1eae2254ee3f9)) - update docs ([0f5ac98](https://github.com/logaretm/vee-validate/commit/0f5ac98153f74bdbbd1d9f5090e4dc4b438c998f)) # [4.0.0-beta.19](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.18...vee-validate@4.0.0-beta.19) (2020-11-07) ### Bug Fixes - resolve component before rendering closes [#3014](https://github.com/logaretm/vee-validate/issues/3014) ([f8f481d](https://github.com/logaretm/vee-validate/commit/f8f481daad754a4b18a91e2b07b9549433d023f9)) ### Features - field.reset() should reset the field to its initial value ([a11f1b7](https://github.com/logaretm/vee-validate/commit/a11f1b7dda3deafe683e13a00b28a7fab09b82cb)) - implement similar reset API for fields ([38c3923](https://github.com/logaretm/vee-validate/commit/38c392320b4154061ccc5d70dde11517357467e8)) - new reset API ([6983738](https://github.com/logaretm/vee-validate/commit/69837383e42636c24d6ee7d15cb5fe8e98f2ac55)) - rename reset methods to be more consistent ([3a0dc4d](https://github.com/logaretm/vee-validate/commit/3a0dc4db2f1a00a8a4f3940ddd452d9b1369cace)) - use resolveDynamicComponent instead ([f1b5f89](https://github.com/logaretm/vee-validate/commit/f1b5f896840ed159df06cf59badd83282496b777)) # [4.0.0-beta.18](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.17...vee-validate@4.0.0-beta.18) (2020-11-05) ### Bug Fixes - handle reactive field names and value swaps ([cf8051d](https://github.com/logaretm/vee-validate/commit/cf8051d3b92eb43103f4e7c682e615343239d717)) ### Features - avoid watching rules when passed as functions ([539f753](https://github.com/logaretm/vee-validate/commit/539f7535bf935e62030b83f8c7b19e95256bcc52)) # [4.0.0-beta.17](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.16...vee-validate@4.0.0-beta.17) (2020-11-04) ### Features - added context information to validation functions ([7e6675d](https://github.com/logaretm/vee-validate/commit/7e6675db6a103eae33cbb6d959621b4549af66b2)) # [4.0.0-beta.16](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.15...vee-validate@4.0.0-beta.16) (2020-10-29) ### Features - initial form meta ([#3003](https://github.com/logaretm/vee-validate/issues/3003)) ([f7fd407](https://github.com/logaretm/vee-validate/commit/f7fd407cf0e6dad9c92585a4a82594af962de8f4)) # [4.0.0-beta.15](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.14...vee-validate@4.0.0-beta.15) (2020-10-28) ### Features - add `initialErrors` prop ([#3002](https://github.com/logaretm/vee-validate/issues/3002)) ([9850b3f](https://github.com/logaretm/vee-validate/commit/9850b3f2f1c1739f31ff05f32890196097ef426e)) # [4.0.0-beta.14](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.13...vee-validate@4.0.0-beta.14) (2020-10-26) ### Features - deprecate the disabled prop ([29f4dca](https://github.com/logaretm/vee-validate/commit/29f4dca6bd4d02281bf71f8ed4c836f30e0e46d0)) - use injection keys to type inject API ([79207b2](https://github.com/logaretm/vee-validate/commit/79207b25a23782acc527394af23703b138c881db)) # [4.0.0-beta.13](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.12...vee-validate@4.0.0-beta.13) (2020-10-23) ### Features - `useForm` Field types ([#2996](https://github.com/logaretm/vee-validate/issues/2996)) ([727f229](https://github.com/logaretm/vee-validate/commit/727f2295d421ef92620995a356bcaee53770299b)) # [4.0.0-beta.12](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.11...vee-validate@4.0.0-beta.12) (2020-10-21) ### Bug Fixes - upgrade to Vue 3.0.2 and fix broken cases ([ede7214](https://github.com/logaretm/vee-validate/commit/ede72147bd998b888825457541ff964df5e7a2fd)) # [4.0.0-beta.11](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.10...vee-validate@4.0.0-beta.11) (2020-10-18) ### Bug Fixes - provide yup object schema type to the useForm closes [#2988](https://github.com/logaretm/vee-validate/issues/2988) ([29157f7](https://github.com/logaretm/vee-validate/commit/29157f7a36dd14dc9a6c411ffddbbeb9d3749f6e)) # [4.0.0-beta.10](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.9...vee-validate@4.0.0-beta.10) (2020-10-15) ### Bug Fixes - properly initialize initial values closes [#2978](https://github.com/logaretm/vee-validate/issues/2978) ([c0ba699](https://github.com/logaretm/vee-validate/commit/c0ba699757cbd2c3ab409d5ee8d2fa3a205907d8)) - typos in test descriptions ([#2970](https://github.com/logaretm/vee-validate/issues/2970)) ([a0132df](https://github.com/logaretm/vee-validate/commit/a0132dfcc2aab4ba48f175b846228544c80fe4a8)) # [4.0.0-beta.9](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.8...vee-validate@4.0.0-beta.9) (2020-10-14) ### Bug Fixes - improve useForm meta types ([#2963](https://github.com/logaretm/vee-validate/issues/2963)) ([6b46047](https://github.com/logaretm/vee-validate/commit/6b46047278633a095243fcce4ba94ddd94e08c11)) ### Features - meta setters ([#2967](https://github.com/logaretm/vee-validate/issues/2967)) ([5036e13](https://github.com/logaretm/vee-validate/commit/5036e13e0f5974589387746398446fa5f318dc0d)) # [4.0.0-beta.8](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.7...vee-validate@4.0.0-beta.8) (2020-10-12) ### Features - added handleInput and handleBlur to field scoped slot props ([69d5833](https://github.com/logaretm/vee-validate/commit/69d5833e85d1f455fa43de83251c634b8efa89fa)) - expose reset() on the form controller object ([3229ee7](https://github.com/logaretm/vee-validate/commit/3229ee722e8df5f2e79155e1a4e5ec4729dff726)) - new meta tags API ([#2958](https://github.com/logaretm/vee-validate/issues/2958)) ([7494bfc](https://github.com/logaretm/vee-validate/commit/7494bfc6533fa29bd0668294d694aca96721d52d)) - remove aria attributes and leave it to userland ([365d825](https://github.com/logaretm/vee-validate/commit/365d825b9bc3e2955b31b941f12d5856c9be8bfe)) - remove valid fields from errors mapping ([1eee524](https://github.com/logaretm/vee-validate/commit/1eee52407f4d7156a541811053b529f7540c931c)) # [4.0.0-beta.7](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.6...vee-validate@4.0.0-beta.7) (2020-10-10) ### Bug Fixes - avoid accessing properties in form directly to avoid warninings ([c5627af](https://github.com/logaretm/vee-validate/commit/c5627af64b252c8f7ec18e7f0a4296f315c7bf99)) - update the handleSubmit signature ([#2954](https://github.com/logaretm/vee-validate/issues/2954)) ([d17517d](https://github.com/logaretm/vee-validate/commit/d17517daf692c48ac4fa1cfce5ac0bb051e73d2e)) # [4.0.0-beta.6](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.5...vee-validate@4.0.0-beta.6) (2020-10-10) ### Features - form and fields values setters ([#2949](https://github.com/logaretm/vee-validate/issues/2949)) ([cc2cb41](https://github.com/logaretm/vee-validate/commit/cc2cb413dfa23aefeb8be6e4bf7fa17927e0e1ce)) - reactive initial form values ([#2946](https://github.com/logaretm/vee-validate/issues/2946)) ([ac2c68f](https://github.com/logaretm/vee-validate/commit/ac2c68fdbfb7062674f8294a1f0f6d33fc8792b3)) # [4.0.0-beta.5](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.4...vee-validate@4.0.0-beta.5) (2020-10-08) ### Bug Fixes - sync model value on input closes [#2944](https://github.com/logaretm/vee-validate/issues/2944) ([5f77fa9](https://github.com/logaretm/vee-validate/commit/5f77fa931bdb01cc6415c4edd1dcaa7eb7e1a0d2)) # [4.0.0-beta.4](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.3...vee-validate@4.0.0-beta.4) (2020-10-08) ### Bug Fixes - prevent recursive re-render model update ([#2943](https://github.com/logaretm/vee-validate/issues/2943)) ([9fa319f](https://github.com/logaretm/vee-validate/commit/9fa319f0e42f8225565e2f54d1bebd07898574a4)) - set falsy initial values ([4b29e72](https://github.com/logaretm/vee-validate/commit/4b29e721f06fe30a5f7207935ae3d6291ea464fe)) - use validateField instead of onChange handler for blur events ([636077a](https://github.com/logaretm/vee-validate/commit/636077a35183b33372825cd4075a143383ed0c68)) # [4.0.0-beta.3](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.2...vee-validate@4.0.0-beta.3) (2020-10-06) ### Bug Fixes - avoid toggling checkbox `checked` attr in `handleChange` ([#2937](https://github.com/logaretm/vee-validate/issues/2937)) ([b8dafbd](https://github.com/logaretm/vee-validate/commit/b8dafbdb75e305f00c6effc21391f364db9236d0)) ### Features - added `validateOnMount` prop to `Field` and `Form` components ([#2938](https://github.com/logaretm/vee-validate/issues/2938)) ([3a0d878](https://github.com/logaretm/vee-validate/commit/3a0d878e453163f305acc87c5d4c93812f77f340)) # [4.0.0-beta.2](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.1...vee-validate@4.0.0-beta.2) (2020-10-05) ### Features - field labels ([#2933](https://github.com/logaretm/vee-validate/issues/2933)) ([513137f](https://github.com/logaretm/vee-validate/commit/513137f28c6266d3e752448b00eb1c3d410ae474)) # [4.0.0-beta.1](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.0...vee-validate@4.0.0-beta.1) (2020-10-02) ### Bug Fixes - avoid binding the value to file inputs ([02a2745](https://github.com/logaretm/vee-validate/commit/02a27456ba961540a882ec4f94a24a271b0ea3a3)) # [4.0.0-beta.0](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.14...vee-validate@4.0.0-beta.0) (2020-10-01) ### Bug Fixes - make sure to unwrap initial value ([0298a92](https://github.com/logaretm/vee-validate/commit/0298a926de5536154a69088b55cb688133638a39)) ### Features - validation triggers ([#2927](https://github.com/logaretm/vee-validate/issues/2927)) ([e725f43](https://github.com/logaretm/vee-validate/commit/e725f43a47dd1993699c0450fd8777aa921c7a49)) # [4.0.0-alpha.14](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.13...vee-validate@4.0.0-alpha.14) (2020-09-20) ### Bug Fixes - **core:** in case of radio or checkbox explicitly set initialValue ([#2907](https://github.com/logaretm/vee-validate/issues/2907)) ([e45ec82](https://github.com/logaretm/vee-validate/commit/e45ec82ee8fa6fabd4d3012a03ba8f9b72854631)) ### Features - use symbols to avoid provide/inject conflicts ([cc80032](https://github.com/logaretm/vee-validate/commit/cc8003213c34a8a33d84802f2c93598e1ac3c6f0)) - workspaces ([#2904](https://github.com/logaretm/vee-validate/issues/2904)) ([0c05f94](https://github.com/logaretm/vee-validate/commit/0c05f9486a73744273de6816f00f689916aba91c)) # [4.0.0-alpha.13](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.12...vee-validate@4.0.0-alpha.13) (2020-09-16) ### Features - nested objects/arrays ([#2897](https://github.com/logaretm/vee-validate/issues/2897)) ([8d161a1](https://github.com/logaretm/vee-validate/commit/8d161a137a65c90ec8f7189743be24802231cf29)) # [4.0.0-alpha.12](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.11...vee-validate@4.0.0-alpha.12) (2020-09-15) ### Features - cast single checkboxes values to booleans closes [#2889](https://github.com/logaretm/vee-validate/issues/2889) ([7a08184](https://github.com/logaretm/vee-validate/commit/7a081845ac6a4bc09c51e52c5996b65814a48baf)) - invoke generateMessage handler for local functions closes [#2893](https://github.com/logaretm/vee-validate/issues/2893) ([e9fe773](https://github.com/logaretm/vee-validate/commit/e9fe77365877edda51548c9539ec085fff91586b)) # [4.0.0-alpha.11](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.10...vee-validate@4.0.0-alpha.11) (2020-09-02) **Note:** Version bump only for package vee-validate # [4.0.0-alpha.10](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.9...vee-validate@4.0.0-alpha.10) (2020-08-29) ### Bug Fixes - added temporary fix for [#2873](https://github.com/logaretm/vee-validate/issues/2873) with form meta ([6e1bf17](https://github.com/logaretm/vee-validate/commit/6e1bf176e7ba5c890afab6c11731dac54924d39b)) # [4.0.0-alpha.9](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.8...vee-validate@4.0.0-alpha.9) (2020-08-28) ### Bug Fixes - adapt to the breaking changes in #vue-1682 closes [#2873](https://github.com/logaretm/vee-validate/issues/2873) ([05f7df3](https://github.com/logaretm/vee-validate/commit/05f7df313f9f47ca79bdf99be35cb2ccfea0c346)) # [4.0.0-alpha.8](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.7...vee-validate@4.0.0-alpha.8) (2020-08-13) ### Bug Fixes - detect initial values from v-model ([e566302](https://github.com/logaretm/vee-validate/commit/e566302bb485353f03baccdf98f35a255605e15d)) - handle unmount issue when removed value is falsy for checkboxes ([b6393f4](https://github.com/logaretm/vee-validate/commit/b6393f4adce9346cadaf1f423dca29645bf3c2f1)) - initial array values for checkboxes not populated correctly in form ([fb99edc](https://github.com/logaretm/vee-validate/commit/fb99edc309c26f9be2baa71f90ec1ac59ddcdc9d)) - umounting group of checkbox issues ([8c77af5](https://github.com/logaretm/vee-validate/commit/8c77af52955b235a6bd2357a35036097e109e37f)) ### Features - added basic v-model support ([c93d125](https://github.com/logaretm/vee-validate/commit/c93d125b4d6c0af8365920ee577c883493e60648)) - merge ctx.attrs to any rendered root node ([5c9979c](https://github.com/logaretm/vee-validate/commit/5c9979ce45d4ab10cd019ad0c25159e013198301)) - sync the model value with inner value ([57d7923](https://github.com/logaretm/vee-validate/commit/57d79232f490be3525c2576ef83376a2f5643386)) # [4.0.0-alpha.7](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.6...vee-validate@4.0.0-alpha.7) (2020-08-04) ### Bug Fixes - avoid removing array value for a non-group field closes [#2847](https://github.com/logaretm/vee-validate/issues/2847) ([69f2092](https://github.com/logaretm/vee-validate/commit/69f2092db7d53665986dd384cae561d1b13bd8f5)) - bails affects yup non-object validators ([a50645b](https://github.com/logaretm/vee-validate/commit/a50645b1c0206d0e7d85ec6681ff6dc224536fa2)) - initial values on HTML inputs ([c4f4eb9](https://github.com/logaretm/vee-validate/commit/c4f4eb9fe97b13fedb93ac760614eb53c177ffb3)) ### Features - deprecate the skipOptional config ([e62f5ea](https://github.com/logaretm/vee-validate/commit/e62f5ea6d31e82ac9f257627e8544431b933c4f9)) # [4.0.0-alpha.6](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.5...vee-validate@4.0.0-alpha.6) (2020-07-27) ### Bug Fixes - render input tags by default for the field component ([858c47b](https://github.com/logaretm/vee-validate/commit/858c47b4a7fa740611abaf026e6e5db6cdb41050)) # [4.0.0-alpha.5](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.4...vee-validate@4.0.0-alpha.5) (2020-07-24) ### Bug Fixes - unregister fields once they are unmounted ([0d601cb](https://github.com/logaretm/vee-validate/commit/0d601cb60b3ba907e6c0d73dd129c0c7b086316e)) ### Features - **v4:** add checkbox and radio HTML input support ([#2835](https://github.com/logaretm/vee-validate/issues/2835)) ([ab3d499](https://github.com/logaretm/vee-validate/commit/ab3d4998caf5950656dc0476f13215d598b28832)) - render input by default for the field component ([81d055d](https://github.com/logaretm/vee-validate/commit/81d055d704deaa12b392fd9197218733b3a0bb8d)) # [4.0.0-alpha.4](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.3...vee-validate@4.0.0-alpha.4) (2020-07-23) **Note:** Version bump only for package vee-validate # [4.0.0-alpha.3](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.2...vee-validate@4.0.0-alpha.3) (2020-07-21) ### Features - automatic injection of the form controller ([c039831](https://github.com/logaretm/vee-validate/commit/c0398318ec70c925b6bcb2afa859ec89488e1f78)) - remove debounce feature and make it userland ([b7263ce](https://github.com/logaretm/vee-validate/commit/b7263ce0f887388709846975b59965e440636089)) # [4.0.0-alpha.2](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.1...vee-validate@4.0.0-alpha.2) (2020-07-19) ### Features - always render a from by default ([402603a](https://github.com/logaretm/vee-validate/commit/402603a8f755a377a056debf24815611a01c3037)) # 4.0.0-alpha.1 (2020-07-18) ### Bug Fixes - added check for cross-fields extraction on unsupported schema ([0ff1bad](https://github.com/logaretm/vee-validate/commit/0ff1bad84a90189f11897cada01fd091e5593bb7)) - added errorMessage prop to the field type ([f1553d0](https://github.com/logaretm/vee-validate/commit/f1553d01b94a74580700fd8712b67688f9c89c15)) - added single error message prop to the provider slot props ([bc97d0c](https://github.com/logaretm/vee-validate/commit/bc97d0c6463cd7e466bb7b3555671e7891d4c60b)) - added unwrap util function ([121bffc](https://github.com/logaretm/vee-validate/commit/121bffc05a9c6e2e204b843d5eb8d7678e5d0fff)) - adjust the useField options to be less strict ([7ea8263](https://github.com/logaretm/vee-validate/commit/7ea826373a78b4fa6343f1da9db0e43879fa0e4e)) - check if a form is present before accessing its schema ([3656181](https://github.com/logaretm/vee-validate/commit/3656181b17a6e44c8f470570ee5126cf2a83ae41)) - debouncing not working correctly and move it to hoc only ([86280a1](https://github.com/logaretm/vee-validate/commit/86280a15e9fb1f94ef8c042a9d04d437f38936b0)) - ensure we unwrap the field id if it was reactive ([7f91e93](https://github.com/logaretm/vee-validate/commit/7f91e930ec8cce4f2e17b49ee9d642d7e9343d6f)) - initial validation not respecting the config opts ([2443d44](https://github.com/logaretm/vee-validate/commit/2443d44b1b00eda39ff884f33f85414aa2b1d34e)) - localization default fallback not being interpolated correctly ([165e89c](https://github.com/logaretm/vee-validate/commit/165e89c6136126d6b946640623261f32b299a2a3)) - no clue why this isn't building ([0d3e7fd](https://github.com/logaretm/vee-validate/commit/0d3e7fdea6f28e29d25f488cae527e925608da7e)) - only add novalidate attr if the rendered element is form ([3638cea](https://github.com/logaretm/vee-validate/commit/3638cead19c9501783e23b43248ce49d7bdf51d7)) - param mapping causing target names to resolve incorrectly ([fb77dc6](https://github.com/logaretm/vee-validate/commit/fb77dc673cb1eff72a1508cff7b4aaed60d8450e)) - set pending back to false earlier in the cycle ([a4237a2](https://github.com/logaretm/vee-validate/commit/a4237a2f8dfde5efcc1d39b5a400e988b8740df9)) - temporary fix for the unamed import issue with vue-beta 4 ([62d27e9](https://github.com/logaretm/vee-validate/commit/62d27e9c9293026d26d62709c2e691d3eb15753e)) - unwrap flags before sending them to the observer slot ([19f7886](https://github.com/logaretm/vee-validate/commit/19f7886adae59b4442139f6e1a3f3905ab54f86a)) - use the proper model event name ([5704db8](https://github.com/logaretm/vee-validate/commit/5704db879019b89b001f496f5f113df24ad09bc6)) - watch target fields once they change ([a4184b0](https://github.com/logaretm/vee-validate/commit/a4184b0065c26df77b680cfbda7450a81b6764ef)) ### Features - adapt the changes from the v3 master branch ([2301c5a](https://github.com/logaretm/vee-validate/commit/2301c5ae75eb8590cb2cc919215ffe4ae934b885)) - add name resolution from v3 ([ba77fdd](https://github.com/logaretm/vee-validate/commit/ba77fdde4f7e5400c6755331af4705715ecc885b)) - add native submit alternative to handleSubmit ([bc00888](https://github.com/logaretm/vee-validate/commit/bc008880607f0393c4e6bd9eb2d44ebb40aa3604)) - added 'as' prop to the validation provider ([5c8ae9c](https://github.com/logaretm/vee-validate/commit/5c8ae9cac2dd418c5bf78b8a0c68e7d256dc96ce)) - added alert role to the error message ([714abfe](https://github.com/logaretm/vee-validate/commit/714abfede6cb2cd2ab1dd72319d27630af6fe9b6)) - added aria and a11y improvements ([ca74f16](https://github.com/logaretm/vee-validate/commit/ca74f165988be3c0c5a6f828508b6aed3fd6e3a0)) - added built-in support for yup validation schema ([e436b75](https://github.com/logaretm/vee-validate/commit/e436b75c4b8b7a085adf701d07b54b798da9a774)) - added ErrorMessage component ([9570412](https://github.com/logaretm/vee-validate/commit/957041270b947e1b70301c3935b6d1ac0bb05a5d)) - added support for custom components ([c661c7e](https://github.com/logaretm/vee-validate/commit/c661c7e1f352e2806c2e2da7bc2c860cfa62f3ff)) - added useField and useForm hooks ([c1e9007](https://github.com/logaretm/vee-validate/commit/c1e900736ed9585d8997d2080f001aad28060281)) - allow the as prop to be a component definition ([29790d4](https://github.com/logaretm/vee-validate/commit/29790d47f17fe49c897bf5b2fda0508f57990479)) - allow the observer to render forms and handle submit events ([9e0d59b](https://github.com/logaretm/vee-validate/commit/9e0d59b11d239c7f1e6d4bc287d9e49aa0376f0d)) - allow validation schema to accept other expressions ([ddeeaea](https://github.com/logaretm/vee-validate/commit/ddeeaea8041c3fad894aff0c827dd9f71b65224d)) - change default field value to undefiend ([00c8754](https://github.com/logaretm/vee-validate/commit/00c87549244447423e0833f8294c5c607bdcf105)) - deprecate names option on validate API ([fe90820](https://github.com/logaretm/vee-validate/commit/fe90820b4b0d4d10df81c2bbd019c3b63d371edf)) - deprecate the 'required' flag ([283caa0](https://github.com/logaretm/vee-validate/commit/283caa0fdd353d990680d42e64be8d8362b6aad5)) - enable interaction modes and localization APIs ([8486aaf](https://github.com/logaretm/vee-validate/commit/8486aaf0fadba03f38b5dd8a5ab857c10e7aa49c)) - expose errorMessage prop on useField and Provider ([04eecaa](https://github.com/logaretm/vee-validate/commit/04eecaa13cc8ab0cc18336021bb912f924e37968)) - expose the form values and pass them to the handleSubmit ([de51155](https://github.com/logaretm/vee-validate/commit/de511555c371bef73037d514e19d44eb4d292eae)) - hook up the provider with new observer implementation ([4d18a65](https://github.com/logaretm/vee-validate/commit/4d18a6572af6af4630bdc2508e027e67d3c0d579)) - implement bails for useField and ValidationProvider ([486babd](https://github.com/logaretm/vee-validate/commit/486babd031efd5a71a819ff535a0e0c661bc45fe)) - implement initial values ([8239130](https://github.com/logaretm/vee-validate/commit/82391301152751eb03097dad4521dc1c275c47e7)) - implement validation debounce ([e294409](https://github.com/logaretm/vee-validate/commit/e2944099ef2074d59f908f7949df3a1059ab3b4e)) - implemented disabled prop ([88bf28e](https://github.com/logaretm/vee-validate/commit/88bf28e89d9e635ebbc79e593a326d4dd2025cdb)) - make rules watchable ([90530cd](https://github.com/logaretm/vee-validate/commit/90530cdebede5bf33a62221371380ad8554326ba)) - make the as prop take priority to determine what to render ([d5a033f](https://github.com/logaretm/vee-validate/commit/d5a033fc57b7ddea8aff4a0f4fe802d7c2489a9c)) - new field binding object ([a58a84b](https://github.com/logaretm/vee-validate/commit/a58a84b009fef5dbfffa2a93a54643b3830cb4bc)) - new handleSubmit signature ([63cbeaf](https://github.com/logaretm/vee-validate/commit/63cbeafd1cfb5e1e14ec42e34c0691a26b258897)) - only export the provider for now ([0bf3efe](https://github.com/logaretm/vee-validate/commit/0bf3efe230be2d80b9e4693779e095c04997a52b)) - remove vid from fields ([1b9bded](https://github.com/logaretm/vee-validate/commit/1b9bdedeb68006535c7087aef267906e2f7bed1d)) - support immediate validation ([42cd6ed](https://github.com/logaretm/vee-validate/commit/42cd6edcfc0c11ea05106e66486ed4772c749548)) - support inline rules as functions ([3c74681](https://github.com/logaretm/vee-validate/commit/3c7468186ac5a6e7fa6bb44b30de4102ef5c31cd)) - support yup validation schemas on field-level ([0802512](https://github.com/logaretm/vee-validate/commit/0802512e181a8a33feaa227770f9e203fcf0cea5)) - updated vnode utils to handle Vue 3 VNode API ([29a4fe8](https://github.com/logaretm/vee-validate/commit/29a4fe859823d5a74814c2dabb3b664185e56366)) - use defineComponent to type Provider and Observer definitions ([80980cf](https://github.com/logaretm/vee-validate/commit/80980cfec81447638aa82b42c208f9ec6f9826f8)) - validate yup form schemas using object validation ([bf216dd](https://github.com/logaretm/vee-validate/commit/bf216dde30a6d90c976bac844129ccbd08a00392)) - validation schema support ([523824a](https://github.com/logaretm/vee-validate/commit/523824a0977d599f6ff2a271ee2edebd5aef36ef)) - working draft for the vprovider with composition api ([b830054](https://github.com/logaretm/vee-validate/commit/b8300547cbafa9904f2b769b8309925ad6da180f)) ================================================ FILE: CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at logaretm1@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Vee-Validate First of all, thanks for taking interest into contributing to this repository, below is what you need to know about the project. ## Getting Started Fork the repository, or clone it: ```sh git clone https://github.com/logaretm/vee-validate.git ``` Install dependencies using [pnpm](https://pnpm.io/) ```sh pnpm i ``` ## Issues When creating issues, please provide as much details as possible. A clear explanation on the issue and a reliable production example can help us greatly in improving this project. Your issue may get closed if it cannot be easily reproduced so please provide a working example using either [Codesandbox](https://codesandbox.io/) or [StackBlitz](https://stackblitz.com/). Your example should only focus on the issue, minimal and clearly produces the issue. You can fork this [StackBlitz template](https://stackblitz.com/edit/vee-validate-issue-template?file=src%App.vue) to get a starting working environment ready for your demo with all vee-validate packages pre-installed and using the latest release. If your issue gets closed for not providing enough info or not responding to the maintainers' comments, do not consider it a hostile action. There are probably other issues that the maintainers are working on and must give priority to issues that are well investigated, you can always revisit the issue and address the reasons that it was closed and we will be happy to re-open it and address it properly. Sometimes a commit will close your issue without a response from the maintainers so make sure you read the issue timeline to prevent any misunderstandings. ## Code Style The code style is enforced with `eslint` and `prettier` and is checked automatically whenever you commit. Any violation of the code style may prevent merging your contribution so make sure you follow it. And yes we love our semi-colons. ## Commit Style Commit messages are enforced with `commitlint` which is configured to help you write a suitable commit message, the checks are run automatically when you commit. ## Contributing To The Docs If you want to contribute to the docs you can find it in the `docs` folder. The docs are using [astro](https://astro.build/) and the [MDX plugin](https://docs.astro.build/en/guides/integrations-guide/mdx/) to write the doc pages. To run the documentation locally: ```sh pnpm docs:dev ``` ## Pull Requests **Before you open a PR, make sure to communicate via issues about your intent to avoid PRing something you think is an issue when it might be a design choice**. This is a checklist of the stuff you need to be aware of: - Make sure you fill the PR template provided - PRs should have titles that are clear as possible - Make sure that your PR is up to date with the branch you are targeting, use `git rebase` for this - Unfinished/In-Progress PRs should be marked as a `draft` - Make sure to mention which issues are being fixed by the PR so they can be closed properly - Make sure to preview all pending PRs to make sure your work won't conflict with other ongoing pull-request - Coordinate with ongoing conflicting PRs' authors to make it easier to merge both your PRs - Make sure to generate a changeset on the PR branch before merging it, this will help us generate a changelog for the next release ## Source Code Currently we are using TypeScript for the codebase, feel free to use any of it's features with a minor exception to: - `Enums` as we prefer to use string literals instead. - `namespace` not needed in our codebase ## Testing Each test file represents a unit test to the corresponding file in the src folder. You need to build the files before you run the tests: ```sh pnpm build ``` Then to run the tests: ```sh pnpm test ``` To check the tests coverage: ```sh pnpm cover ``` ## Mono repo this project uses mono-repo style using pnpm workspaces and [changesets](https://github.com/changesets/changesets). ## Building Use this command to build all project bundles ```sh pnpm build ``` If you are working on a specific package within the vee-validate mono repo and only want to build that, then use the following command to build specific packages, the package id is the folder name in the `packages` folder. ```sh pnpm build vee-validate pnpm build rules pnpm build zod # etc... ``` ## Tips for Testing your changes If you need to try out your changes, here is a few tips - Build vee-validate dist files once you are done with your changes - To use the built files you can either use `pnpm link {PKG_NAME}` and link the package dist files to your project, or use [yalc](https://github.com/whitecolor/yalc). ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) Abdelrahman Awad <logaretm1@gmail.com> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ <p align="center"> <a href="https://vee-validate.logaretm.com" target="_blank"> <img src="https://raw.githubusercontent.com/logaretm/vee-validate/main/logo.png" width="200" title="Go to website"> </a> </p> <p align="center"> Painless Vue forms </p> <p align="center"> <a target="_blank" href="https://www.npmjs.com/package/vee-validate"> <img src="https://img.shields.io/npm/v/vee-validate.svg?label=&color=05bda8"> </a> <a target="_blank" href="https://npm-stat.com/charts.html?package=vee-validate"> <img src="https://img.shields.io/npm/dm/vee-validate.svg?color=05bd6d&label="> </a> <a href="https://vee-validate.logaretm.com/v5/" target="_blank"> <img src="https://img.shields.io/badge/-docs%20and%20demos-009f53"> </a> <a href="https://github.com/sponsors/logaretm"> <img src="https://img.shields.io/badge/-%E2%99%A5%20Sponsors-ec5cc6"> </a> </p> <br> <p align="center"> <a href="https://github.com/sponsors/logaretm"> <img src='https://sponsors.logaretm.com/sponsors.svg'> </a> </p> <br> ## Features - **🍞 Easy:** Declarative validation that is familiar and easy to setup - **🧘‍♀️ Flexible:** Synchronous, Asynchronous, field-level or form-level validation - **⚡️ Fast:** Build faster forms faster with intuitive API and small footprint - **🏏 Minimal:** Only handles the complicated form concerns, gives you full control over everything else - **😎 UI Agnostic:** Works with native HTML elements or your favorite UI library components - **🦾 Progressive:** Works whether you use Vue.js as a progressive enhancement or in a complex setup - **✅ Built-in Rules:** Companion lib with 25+ Rules that covers most needs in most web applications - **🌐 i18n:** 45+ locales for built-in rules contributed by developers from all over the world ## Getting Started ### Installation ```sh # Install with yarn yarn add vee-validate # Install with npm npm install vee-validate --save ``` ### Vue version support The main v4 version supports Vue 3.x only, for previous versions of Vue, check the following the table | vue Version | vee-validate version | Documentation Link | | ----------- | -------------------- | ---------------------------------------------------------------------------------------- | | `2.x` | `2.x` or `3.x` | [v2](https://vee-validate.logaretm.com/v2) or [v3](https://vee-validate.logaretm.com/v3) | | `3.x` | `4.x` or `5.x` | [v4](https://vee-validate.logaretm.com/v4) or [v5](https://vee-validate.logaretm.com/v5) | ### Usage vee-validate offers two styles to integrate form validation into your Vue.js apps. #### Composition API The fastest way to create a form and manage its validation, behavior, and values is with the composition API. Create your form with `useForm` and then use `defineField` to create your field model and props/attributes and `handleSubmit` to use the values and send them to an API. ```vue <script setup> import { useForm } from 'vee-validate'; // Validation, or use `yup` or `zod` function required(value) { return value ? true : 'This field is required'; } // Create the form const { defineField, handleSubmit, errors } = useForm({ validationSchema: { field: required, }, }); // Define fields const [field, fieldProps] = defineField('field'); // Submit handler const onSubmit = handleSubmit(values => { // Submit to API console.log(values); }); </script> <template> <form @submit="onSubmit"> <input v-model="field" v-bind="fieldProps" /> <span>{{ errors.field }}</span> <button>Submit</button> </form> </template> ``` You can do so much more than this, for more info [check the composition API documentation](https://vee-validate.logaretm.com/v5/guide/composition-api/getting-started/). #### Declarative Components Higher-order components can also be used to build forms. Register the `Field` and `Form` components and create a simple `required` validator: ```vue <script setup> import { Field, Form } from 'vee-validate'; // Validation, or use `yup` or `zod` function required(value) { return value ? true : 'This field is required'; } // Submit handler function onSubmit(values) { // Submit to API console.log(values); } </script> <template> <Form v-slot="{ errors }" @submit="onSubmit"> <Field name="field" :rules="required" /> <span>{{ errors.field }}</span> <button>Submit</button> </Form> </template> ``` The `Field` component renders an `input` of type `text` by default but you can [control that](https://vee-validate.logaretm.com/v5/api/field#rendering-fields) ## 📚 Documentation Read the [documentation and demos](https://vee-validate.logaretm.com/v4). ## Contributing You are welcome to contribute to this project, but before you do, please make sure you read the [contribution guide](/CONTRIBUTING.md). ## Credits - Inspired by Laravel's [validation syntax](https://laravel.com/docs/5.4/validation) - v4 API Inspired by [Formik's](https://github.com/formium/formik) - Nested path types by [react-hook-form](https://github.com/react-hook-form/react-hook-form) - Logo by [Baianat](https://github.com/baianat) ## Emeriti Here we honor past contributors and sponsors who have been a major part on this project. - [Baianat](https://github.com/baianat). ## ⚖️ License Released under [MIT](/LICENSE) by [@logaretm](https://github.com/logaretm). ================================================ FILE: commitlint.config.mjs ================================================ export default { extends: ['@commitlint/config-conventional'], rules: { 'body-leading-blank': [1, 'always'], 'footer-leading-blank': [1, 'always'], 'header-max-length': [2, 'always', 72], 'scope-case': [2, 'always', 'lower-case'], 'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']], 'subject-empty': [2, 'never'], 'subject-full-stop': [2, 'never', '.'], 'type-case': [2, 'always', 'lower-case'], 'type-empty': [2, 'never'], 'type-enum': [ 2, 'always', ['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'], ], }, }; ================================================ FILE: docs/.gitignore ================================================ .nuxt dist node_modules sw.* ================================================ FILE: docs/CHANGELOG.md ================================================ # vee-validate-docs ## null ### Patch Changes - Updated dependencies [9867e59] - Updated dependencies [721e980] - Updated dependencies [9803aa2] - Updated dependencies [546d82e] - @vee-validate/valibot@4.15.1 - vee-validate@4.15.1 - @vee-validate/yup@4.15.1 - @vee-validate/zod@4.15.1 ## null ### Patch Changes - Updated dependencies [5cba0aa] - Updated dependencies [30281f5] - Updated dependencies [ec121b1] - Updated dependencies [db26a74] - Updated dependencies [f92455a] - @vee-validate/yup@4.15.0 - vee-validate@4.15.0 - @vee-validate/valibot@4.15.0 - @vee-validate/zod@4.15.0 ## null ### Patch Changes - Updated dependencies [be994b4] - vee-validate@4.14.7 - @vee-validate/valibot@4.14.7 - @vee-validate/yup@4.14.7 - @vee-validate/zod@4.14.7 ## null ### Patch Changes - vee-validate@4.14.6 - @vee-validate/zod@4.14.6 - @vee-validate/yup@4.14.6 - @vee-validate/valibot@4.14.6 ## null ### Patch Changes - Updated dependencies [e9f8c88] - vee-validate@4.14.5 - @vee-validate/valibot@4.14.5 - @vee-validate/yup@4.14.5 - @vee-validate/zod@4.14.5 ## null ### Patch Changes - Updated dependencies [f33974c] - Updated dependencies [0991c01] - Updated dependencies [ecb540a] - Updated dependencies [4f88d85] - vee-validate@4.14.4 - @vee-validate/valibot@4.14.4 - @vee-validate/yup@4.14.4 - @vee-validate/zod@4.14.4 ## null ### Patch Changes - Updated dependencies [07c27d5] - vee-validate@4.14.3 - @vee-validate/valibot@4.14.3 - @vee-validate/yup@4.14.3 - @vee-validate/zod@4.14.3 ## null ### Patch Changes - Updated dependencies [f0d4e24] - vee-validate@4.14.2 - @vee-validate/valibot@4.14.2 - @vee-validate/yup@4.14.2 - @vee-validate/zod@4.14.2 ## null ### Patch Changes - vee-validate@4.14.1 - @vee-validate/zod@4.14.1 - @vee-validate/yup@4.14.1 - @vee-validate/valibot@4.14.1 ## null ### Patch Changes - Updated dependencies [f7a4929] - Updated dependencies [97cebd8] - Updated dependencies [404cf57] - Updated dependencies [421ae69] - vee-validate@4.14.0 - @vee-validate/valibot@4.14.0 - @vee-validate/yup@4.14.0 - @vee-validate/zod@4.14.0 ## null ### Patch Changes - Updated dependencies [afbd0e5] - vee-validate@4.13.2 - @vee-validate/valibot@4.13.2 - @vee-validate/yup@4.13.2 - @vee-validate/zod@4.13.2 ================================================ FILE: docs/_redirects ================================================ # redirects old component api paths to new one /guide/validation /v5/guide/components/validation /guide/handling-forms /v5/guide/components/handling-forms /guide/nested-objects-and-arrays /v5/guide/components/nested-objects-and-arrays /v5/guide/composition/validation /v5/guide/composition/getting-started ================================================ FILE: docs/astro.config.ts ================================================ import { defineConfig } from 'astro/config'; import vue from '@astrojs/vue'; import remarkGfm from 'remark-gfm'; import sitemap from '@astrojs/sitemap'; import mdx from '@astrojs/mdx'; import highlight from './highlight'; import baseLink from './baseLink'; import { svgSprite } from './src/integrations/svgSprite'; import partytown from '@astrojs/partytown'; // https://astro.build/config export default defineConfig({ site: process.env.NODE_ENV === 'production' ? 'https://vee-validate.logaretm.com/' : 'http://localhost:4321/', trailingSlash: 'always', base: '/v5', vite: { ssr: { noExternal: ['@vue/repl'], }, }, integrations: [ vue(), sitemap(), mdx({ remarkPlugins: [baseLink('/v5'), highlight, remarkGfm], }), svgSprite, partytown({ config: { forward: ['dataLayer.push'], }, }), ], }); ================================================ FILE: docs/baseLink.ts ================================================ import { visit } from 'unist-util-visit'; export default function addBasePath(basePath: string) { return function () { return function (ast) { visit(ast, 'link', node => { if (node.url.startsWith('/') && !node.url.startsWith(`${basePath}/`)) { node.url = `${basePath}${node.url}`; } if (node.url.startsWith('http')) { addProperties(node, { rel: 'noopener', target: '_blank', }); } }); return ast; }; }; } function addProperties(node: any, props: Record<string, string>) { if (!node.data) { node.data = {}; } if (!node.data.hProperties) { node.data.hProperties = {}; } node.data.hProperties = { ...node.data.hProperties, ...props, }; } ================================================ FILE: docs/highlight.ts ================================================ import * as shiki from 'shiki'; import { JSDOM } from 'jsdom'; import { visit } from 'unist-util-visit'; import theme from './theme.json'; const dom = new JSDOM(); const document = dom.window.document; /** * Some langs are highlighted with different grammar rules but need to be displayed */ const LANG_REPLACEMENTS = { 'vue-html': 'vue', }; const highlighterPromise = shiki.getHighlighter({ // Complete themes: https://github.com/shikijs/shiki/tree/master/packages/themes theme: theme as any, langs: ['js', 'ts', 'vue', 'graphql', 'jsx', 'css', 'sh', 'yaml', 'json', 'vue-html', 'html'], }); export default function highlight() { return async function (tree) { const highlighter = await highlighterPromise; visit(tree, 'code', visitor); function visitor(node) { const { lang, lines, fileName } = parseParts(node.lang || 'sh'); try { const html = replaceColorsWithVariables(highlighter.codeToHtml(node.value, { lang: lang || 'sh' })); const fragment = document.createDocumentFragment(); const wrapper = document.createElement('div'); wrapper.classList.add('shiki-snippet'); wrapper.innerHTML = html; fragment.appendChild(wrapper); const langSpan = createSpan(LANG_REPLACEMENTS[lang] || lang, 'shiki-language', { 'data-language': lang, }); const shikiEl = fragment.querySelector('.shiki') as HTMLElement | null; shikiEl?.prepend(langSpan); if (lines.length) { lines.forEach(line => { const lineEl = fragment.querySelector(`.line:nth-child(${line})`) as HTMLElement | null; lineEl?.classList.add('is-highlighted'); }); shikiEl?.classList.add('with-line-highlights'); } fragment.querySelector('.line:last-child:empty')?.remove(); if ([...fragment.querySelectorAll('.line')].length === 1) { shikiEl?.classList.add('single-line'); } if (fileName) { wrapper.prepend(createSpan(fileName, 'filename')); shikiEl?.classList.add('with-filename'); } node.value = fragment.querySelector('.shiki-snippet')?.outerHTML; node.type = 'html'; } catch (err) { // eslint-disable-next-line no-console console.error(err); // eslint-disable-next-line no-console console.log(node.lang); } } }; } function createSpan(text: string, className: string, attrs?: Record<string, string>) { const document = dom.window.document; const span = document.createElement('span'); span.textContent = text; span.classList.add(className); if (attrs) { Object.keys(attrs).forEach(attr => { span.setAttribute(attr, attrs[attr]); }); } return span; } function replaceColorsWithVariables(html) { const colors = [ { variable: '--code-foreground', value: '#f8f8f2' }, { variable: '--code-background', value: '#22212c' }, { variable: '--code-token-constant', value: '#9580ff' }, { variable: '--code-token-operator', value: '#ff80bf' }, { variable: '--code-token-type', value: '#80ffea' }, { variable: '--code-token-parameter', value: '#ffca80' }, { variable: '--code-token-attribute', value: '#8aff80' }, { variable: '--code-token-regex', value: '#ff9580' }, { variable: '--code-token-string', value: '#ffff80' }, { variable: '--code-token-comment', value: '#7970a9' }, ]; let str = html; colors.forEach(color => { str = str.replace(new RegExp(`color:\\s*${color.value}`, 'ig'), `color: var(${color.variable})`); }); return str; } function parseLines(lines) { return lines.split(',').reduce((acc, line) => { if (/-/.test(line)) { const [start, end] = line.split('-').map(ln => Number(ln)); let current = start; while (current <= end) { acc.push(current); current++; } return acc; } acc.push(Number(line)); return acc; }, []); } const fileNameRE = /\[(.+)\]/; const linesRE = /\{(.+)\}/; function parseParts(lang) { lang = lang.trim(); const [, fileName] = lang.match(fileNameRE) || []; const [, lines] = lang.match(linesRE) || []; const rawLang = lang.replace(fileNameRE, '').replace(linesRE, ''); return { lang: rawLang, lines: lines ? parseLines(lines.replace()) : [], fileName, }; } ================================================ FILE: docs/package.json ================================================ { "private": true, "name": "vee-validate-docs", "scripts": { "dev": "astro dev", "start": "astro dev", "build": "astro build && node scripts/afterBuild.js" }, "dependencies": { "@astrojs/mdx": "^1.1.5", "@astrojs/partytown": "^2.0.2", "@astrojs/sitemap": "^3.0.3", "@astrojs/vue": "^3.0.4", "@docsearch/css": "^3.5.2", "@docsearch/js": "^3.5.2", "@floating-ui/dom": "^1.5.3", "@stackblitz/sdk": "^1.9.0", "@types/fs-extra": "^11.0.4", "@types/lodash-es": "^4.17.12", "@vue/repl": "^3.0.0", "@vueuse/core": "^10.7.0", "astro": "^3.6.4", "autoprefixer": "^10.4.16", "fs-extra": "^11.2.0", "jsdom": "^23.0.1", "lodash-es": "^4.17.21", "remark-gfm": "^3.0.1", "shiki": "^0.14.6", "tailwindcss": "^3.4.15", "unist-util-visit": "^5.0.0", "valibot": "^1.0.0-beta.7", "vee-validate": "workspace:*", "vue": "^3.4.26", "yup": "^1.3.2", "zod": "^3.22.4" }, "version": null } ================================================ FILE: docs/postcss.config.js ================================================ module.exports = { plugins: { 'tailwindcss/nesting': {}, tailwindcss: {}, autoprefixer: {}, }, }; ================================================ FILE: docs/public/img/browserconfig.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig> ================================================ FILE: docs/public/img/manifest.json ================================================ { "name": "App", "icons": [ { "src": "\/android-icon-36x36.png", "sizes": "36x36", "type": "image\/png", "density": "0.75" }, { "src": "\/android-icon-48x48.png", "sizes": "48x48", "type": "image\/png", "density": "1.0" }, { "src": "\/android-icon-72x72.png", "sizes": "72x72", "type": "image\/png", "density": "1.5" }, { "src": "\/android-icon-96x96.png", "sizes": "96x96", "type": "image\/png", "density": "2.0" }, { "src": "\/android-icon-144x144.png", "sizes": "144x144", "type": "image\/png", "density": "3.0" }, { "src": "\/android-icon-192x192.png", "sizes": "192x192", "type": "image\/png", "density": "4.0" } ] } ================================================ FILE: docs/public/loadTheme.js ================================================ (function setUserPreferredTheme() { const themeSetting = localStorage.getItem('theme'); // no dark setting, get it from browser const theme = themeSetting || 'dark'; document.documentElement.classList.toggle('dark', theme === 'dark'); localStorage.setItem('theme', theme); })(); ================================================ FILE: docs/scripts/afterBuild.js ================================================ /* eslint-disable @typescript-eslint/no-var-requires */ const path = require('path'); const fs = require('fs'); fs.copyFileSync(path.join(__dirname, '../_redirects'), path.join(__dirname, '../dist/_redirects')); ================================================ FILE: docs/src/components/Ad.vue ================================================ <template> <div v-if="!hasError" id="ad" :class="{ 'placement-home': placement === 'home-page' }"> <!-- Show an image ad --> <div data-ea-publisher="vee-validatelogaretmcom" data-ea-type="image"></div> </div> <div v-else id="ad" class="error"> <div class="p-4 flex flex-col"> <span class="font-bold font-display">🙏 Enable Ads</span> <span class="mt-2"> Please enable ads on your browser for this website. </span> <span class="mt-1"> Ads are a funding source to this project. </span> <div class="mt-2 or-fund-it"> <span class="mx-1 whitespace-nowrap">or consider</span> </div> <a target="_blank" rel="noopener" href="https://github.com/sponsors/logaretm" class="mt-2 bg-pink-600 py-1 px-2 text-white font-medium flex items-center justify-center font-display rounded group hover:bg-pink-700" > <svg class="stroke-current transform transition duration-200 group-hover:scale-110 w-5 h-5 mr-1" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" ></path> </svg> Sponsor </a> </div> </div> </template> <script setup lang="ts"> import { onMounted, ref } from 'vue'; defineProps<{ placement?: 'content' | 'home-page'; }>(); const hasError = ref(false); function loadScript() { const script = document.createElement('script'); script.src = 'https://media.ethicalads.io/media/client/ethicalads.min.js'; const el = document.querySelector('#ad'); el?.appendChild(script); script.onerror = error => { if (navigator.onLine) { hasError.value = true; } }; } onMounted(loadScript); </script> <style lang="postcss"> #ad { @apply static text-gray-400 mt-4 hidden md:block; z-index: 1; .ea-placement { @apply rounded-md overflow-hidden bg-zinc-200; .ea-content { @apply m-0 rounded-none; a { @apply text-zinc-500; } strong { @apply text-accent-900; } } .ea-callout { @apply m-0 py-2 px-4 text-center bg-zinc-300 rounded-none; } } &.error { width: 180px; } } .dark { #ad { .ea-placement { @apply bg-zinc-800; } .ea-callout { @apply bg-zinc-950; a { @apply text-zinc-600; } } &.error { @apply bg-zinc-800; } } } .or-fund-it { @apply text-zinc-500 flex items-center space-x-2; &::before, &::after { height: 1px; width: 100%; content: ''; @apply flex-shrink bg-zinc-500; } } </style> ================================================ FILE: docs/src/components/CodeTitle.vue ================================================ <template> <component :is="tag"> <slot /> </component> </template> <script> export default { props: { level: [Number, String], }, computed: { tag() { return `h${this.level}`; }, }, }; </script> <style lang="postcss" scoped> h4:deep(code:not([class])) { @apply text-lg; } h5:deep(code:not([class])) { @apply text-base; } </style> ================================================ FILE: docs/src/components/ContentWrapper.vue ================================================ <template> <div class="rendered-content"> <slot /> </div> </template> <style lang="postcss"> .rendered-content { hr { @apply mt-10 mb-8 border border-gray-300; } h1 { @apply text-5xl mb-8 font-bold; } h2 { @apply text-2xl lg:text-3xl; } h3 { @apply text-lg lg:text-xl; } h4, h5 { @apply text-lg; } h1, h2, h3 { @apply font-display; } h2, h3, h4, h5 { @apply font-semibold my-8 relative w-max max-w-full break-normal; transform: translateX(2ch); &::before { @apply absolute text-accent-800; margin-left: -2ch; content: '#'; } @screen lg { transform: none; } &::after { content: ''; display: block; } } p + p { @apply mt-4; } ul { @apply px-8 my-4 list-disc; li + li { @apply mt-2; } } ul { li { @apply relative antialiased list-none; &:before { @apply w-6 h-6 text-lg absolute rounded-full flex items-center justify-center flex-shrink-0; content: ''; background-image: url("data:image/svg+xml,%3Csvg fill='none' stroke='%2309a884' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'%3E%3C/path%3E%3C/svg%3E"); left: -2rem; } } } .shiki-snippet { @apply my-5 rounded-md text-sm shadow; .filename { @apply inline-block py-2 px-8 bottom-full rounded-t-md font-extrabold text-sm select-none tracking-wide border border-b-0 border-emerald-500 border-opacity-30 bg-gray-200; font-family: 'Courier New', monospace; } .shiki-language { @apply absolute right-2 top-2 text-xs; color: var(--code-lang-label); } } .shiki { @apply font-mono pb-4 pt-6 text-sm border border-emerald-500 border-opacity-30 relative rounded-md; counter-reset: step; counter-increment: step 0; line-height: 1.4; overflow: auto; overflow: overlay; &.with-filename { @apply rounded-tl-none rounded-tr-md; } code { @apply flex flex-col w-full; } &.with-line-highlights { .line { @apply transition-all duration-300; } .line:not(.is-highlighted) { @apply filter grayscale opacity-40 brightness-100; } &:hover { .line:not(.is-highlighted) { @apply filter-none opacity-100; } .line.is-highlighted { background-color: var(--code-line-highlight); } } } &:not(.single-line) { .line { @apply block pr-2; padding-left: 1.4rem; line-height: 1.6; &::before { @apply select-none; content: counter(step); counter-increment: step; width: 1rem; border-left: 0.2rem transparent solid; margin-right: 1.3rem; display: inline-block; text-align: right; color: var(--code-line-numbers); } &.is-highlighted { padding-left: 0; &::before { padding-left: 1.4rem; margin-right: 2rem; border-color: var(--code-line-highlight-border); } } } } &.single-line { @apply px-6; } } blockquote { @apply py-4 rounded-r-lg pl-4 bg-black border-l-4 border-accent-800 italic my-8 text-lg; } pre[class*='language-'] { @apply rounded-lg my-4 block border border-gray-100; } *:not(pre) > code:not([class]) { @apply text-sm px-1 rounded bg-gray-100 border border-gray-200 text-gray-900; } iframe { @apply my-8; } details { @apply my-10 px-3; summary { @apply outline-none mb-8; } } ol { @apply list-decimal px-8; } p, li, td { a[href] { @apply text-accent-800; &:hover { @apply underline; } } } table { @apply w-full my-4 overflow-hidden; } th, td { @apply border border-gray-100; } th { @apply text-sm font-medium; } th, td { @apply p-3; } } .dark { *:not(pre) > code:not([class]) { @apply px-1 bg-black border border-gray-500 text-gray-200; } pre code, pre[class*='language-'] code { color: #f8f8f2; } *[class*='language-']::before { color: #7970a9; } pre[class*='language-'] { @apply border-carbon; } th, td { @apply border-gray-500; } .shiki-snippet { .filename { @apply bg-gray-700; } } hr { @apply border border-carbon; } } </style> ================================================ FILE: docs/src/components/DocBadge.vue ================================================ <template> <span class="px-2 py-1 bg-accent-800 text-white text-sm rounded-lg">{{ title }}</span> </template> <script setup lang="ts"> defineProps<{ title: string; }>(); </script> ================================================ FILE: docs/src/components/DocFlavor.vue ================================================ <template> <div class="mt-8"> <h2 class="text-2xl font-display inline-block">Choose a flavor</h2> <p class="mt-4">Before you go on, you should choose which flavor of vee-validate you want to use.</p> <div class="mt-8 grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-8 text-white"> <a :href="`/v5/guide/components/${next}/`" class="bg-gray-600 hover:bg-accent-900 p-6 rounded-lg relative"> <p class="text-xl font-semibold font-display">Components</p> <p class="mt-8">Simple, high-level, dynamic template-based higher-order components.</p> <p class="mt-4">Great for simple UI components and native HTML elements with custom styling.</p> <svg class="w-20 h-20 absolute top-0 right-0 mt-4 mr-4 text-gray-900 opacity-10" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" ></path> </svg> </a> <a :href="`/v5/guide/composition-api/${next}/`" class="bg-gray-600 hover:bg-accent-900 p-6 rounded-lg relative"> <p class="text-xl font-semibold font-display">Composition API</p> <p class="mt-8">Low level, intuitive composition API functions.</p> <p class="mt-4">Great for building complex UI form components and general purpose data validation.</p> <svg class="w-20 h-20 absolute top-0 right-0 mt-4 mr-4 text-gray-900 opacity-10" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 14v6m-3-3h6M6 10h2a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2zm10 0h2a2 2 0 002-2V6a2 2 0 00-2-2h-2a2 2 0 00-2 2v2a2 2 0 002 2zM6 20h2a2 2 0 002-2v-2a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2z" ></path> </svg> </a> </div> </div> </template> <script setup lang="ts"> defineProps({ next: String, }); </script> <style lang="postcss" scoped> p { @apply text-lg lg:text-base; } h2 { @apply font-semibold relative text-3xl lg:text-xl; transform: translateX(2ch); &::before { @apply absolute text-accent-800; margin-left: -2ch; content: '#'; } @screen lg { transform: none; } } a { transition: background-color 0.4s ease-in-out; svg { transition: transform 0.5s ease-in-out; } &:hover { svg { transform: scale(1.1) translate3d(-2px, 2px, 0); } } } </style> ================================================ FILE: docs/src/components/DocMenu.vue ================================================ <template> <div class="relative mt-20 lg:mt-4 font-ui"> <div class="bg-gradient-to-b from-white dark:from-dark to-transparent h-4 absolute -top-px lg:top-0 inset-x-0" ></div> <nav class="space-y-8 md:text-sm overflow-y-auto overscroll-y-contain px-8 lg:px-2 py-4"> <div v-for="category in menu" :key="category.title"> <p v-if="category.pages.length > 1" class="md:text-xs font-bold text-gray-400 uppercase"> {{ category.title }} </p> <div class="mt-3 space-y-2 w-full"> <a v-if="category.pages.length === 1" :href="category.pages[0].path" :aria-current="currentUrl === category.pages[0].path ? 'page' : undefined" class="flex items-center" > <span v-if="category.pages[0].icon" class="mr-2 bg-gray-200 dark:bg-gray-500 rounded p-1"> <Icon :name="category.pages[0].icon" class="w-5 h-5 fill-current" /> </span> {{ category.pages[0].menuTitle || category.pages[0].title }} </a> <template v-else> <div v-for="page in category.pages" :key="page.title" class="group"> <a v-if="!page.children" :href="page.path" :aria-current="currentUrl === page.path ? 'page' : undefined" class="flex items-center ml-2" > {{ page.menuTitle || page.title }} <span v-if="page.new" class="ml-2 w-2 h-2 rounded-full flex-shrink-0 bg-blue-600"></span> </a> <div v-else class="flex flex-col bg-gray-200 dark:bg-gray-600 w-full rounded-lg py-3 px-2"> <button type="button" class="w-full flex items-center focus:outline-none transition-colors duration-300" @click="expanded[page.title] = !expanded[page.title]" > <Icon :name="page.icon" class="w-5 h-5 fill-current" /> <span class="ml-2 group-hover:text-accent-800">{{ page.menuTitle || page.title }}</span> <svg class="ml-auto w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path v-if="!expanded[page.title]" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" ></path> <path v-else stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"></path> </svg> </button> <ExpandTransition> <ul v-show="expanded[page.title]" class="mt-3 space-y-2"> <li v-for="childPage in page.children" :key="childPage.title" class="pl-7 flex items-center"> <a :href="childPage.path" :aria-current="currentUrl === childPage.path ? 'page' : undefined"> {{ childPage.menuTitle || childPage.title }} </a> <span v-if="childPage.new" class="ml-2 w-2 h-2 rounded-full flex-shrink-0 bg-blue-600"></span> </li> </ul> </ExpandTransition> </div> </div> </template> </div> </div> </nav> <div class="bg-gradient-to-b from-transparent to-white dark:to-dark h-4 absolute -bottom-px lg:bottom-0 inset-x-0" ></div> </div> </template> <script lang="ts" setup> import { reactive } from 'vue'; import ExpandTransition from '@/components/ExpandTransition.vue'; import Icon from '@/components/Icon.vue'; export interface CategoryMenuItem { title: string; pages: { title: string; menuTitle?: string; path: string; new?: boolean; icon?: string; children?: { title: string; new?: boolean; menuTitle?: string; path: string; }[]; }[]; } const props = defineProps<{ menu: CategoryMenuItem[]; currentUrl: string; }>(); const expanded = reactive<Record<string, boolean>>({}); if (props.currentUrl) { if (props.currentUrl.includes('/composition-api')) { expanded['Composition API'] = true; } if (props.currentUrl.includes('/components')) { expanded['Components'] = true; } } </script> <style lang="postcss" scoped> nav { max-height: calc(80vh - 96px); a { @screen motion { transition: color 0.2s ease-in-out; } &:hover { @apply text-accent-800; } &[aria-current='page'] { @apply text-accent-800; } } /* Global Scrollbar styling */ &::-webkit-scrollbar { width: 7px; cursor: pointer; /*background-color: rgba(229, 231, 235, var(--bg-opacity));*/ } &::-webkit-scrollbar-track { background-color: none; cursor: pointer; /*background: red;*/ } &::-webkit-scrollbar-thumb { cursor: pointer; background-color: #e8e8e8; /* #E7E5E4; */ border-radius: 50px; /*outline: 1px solid grey;*/ } } .dark { nav { /* Global Scrollbar styling */ &::-webkit-scrollbar { width: 7px; cursor: pointer; /*background-color: rgba(229, 231, 235, var(--bg-opacity));*/ } &::-webkit-scrollbar-track { background-color: none; cursor: pointer; /*background: red;*/ } &::-webkit-scrollbar-thumb { cursor: pointer; background-color: #333; /* #E7E5E4; */ border-radius: 50px; /*outline: 1px solid grey;*/ } } } </style> ================================================ FILE: docs/src/components/DocNextStep.vue ================================================ <template> <div class="pt-4"> <h2 class="text-xl">Next Step</h2> <p v-if="intro">{{ intro }}</p> <a :href="href" class="mt-8 bg-accent-800 w-full flex items-start p-4 rounded text-white"> <svg class="w-5 h-5 mr-2 mt-1 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" > <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path> </svg> <div class="NextTitle flex flex-col"> <span class="text-lg font-semibold flex">{{ title }}</span> <span class="mt-2 text-sm">{{ description }}</span> </div> </a> </div> </template> <script lang="ts" setup> import { computed } from 'vue'; import { trim, trimEnd } from 'lodash-es'; const props = defineProps<{ title: string; description: string; path: string; intro: string; }>(); const href = computed(() => { return `${trimEnd(import.meta.env.BASE_URL, '/')}/${trim(props.path, '/')}/`; }); </script> <style lang="postcss" scoped> h2 { @apply font-semibold mb-8 relative; transform: translateX(2ch); &::before { @apply absolute text-accent-800; margin-left: -2ch; content: '#'; } @screen lg { transform: none; } } a { .NextTitle { transition: transform 0.3s ease-in-out; } &:hover { .NextTitle { transform: translate3d(10px, 0, 0); } } } </style> ================================================ FILE: docs/src/components/DocSearch.vue ================================================ <template> <div> <button type="button" class="border border-gray-200 hover:border-gray-300 dark:border-carbon dark:bg-gray-600 dark:hover:border-accent-800 dark:hover:border-opacity-50 flex items-center px-4 py-2 rounded-md w-full" @click="onClick" > <svg width="24" height="24" fill="none" aria-hidden="true" class="mr-3 flex-none text-gray-300 dark:text-gray-400" > <path d="m19 19-3.5-3.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ></path> <circle cx="11" cy="11" r="6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ></circle> </svg> <span class="text-sm text-gray-400 dark:text-gray-300"> Search Docs...</span> <span class="ml-auto flex items-center text-xs text-gray-500 dark:text-gray-300 space-x-1"> <kbd class="bg-gray-200 dark:bg-gray-500 p-1 shadow flex-shrink-0 w-4 h-4 flex items-center justify-center" >⌘</kbd > <kbd class="bg-gray-200 dark:bg-gray-500 p-1 shadow flex-shrink-0 w-4 h-4 flex items-center justify-center" >K</kbd > </span> </button> <div id="docsearch" class="hidden"></div> </div> </template> <script lang="ts" setup> import { onMounted } from 'vue'; import config from '@/config'; import docsearch from '@docsearch/js'; function stripTrailingSlash(url: string) { return url.replace(/\/$|\/(?=\?)|\/(?=#)/g, ''); } function getRelativePath(absoluteUrl: string) { const { pathname, hash } = new URL(absoluteUrl); const url = pathname + hash; return stripTrailingSlash(url); } function initialize(userOptions) { docsearch({ ...userOptions, container: '#docsearch', debug: process.env.NODE_ENV !== 'production', transformItems: items => { return items.map(item => { return { ...item, url: getRelativePath(item.url), }; }); }, }); } onMounted(() => { initialize(config.algolia); }); function onClick() { (document.querySelector('#docsearch button') as HTMLElement)?.click(); } </script> <style lang="postcss"> @import '@docsearch/css'; .DocSearch { font-family: Arial, Helvetica, sans-serif; --docsearch-primary-color: var(--accent); --docsearch-highlight-color: var(--docsearch-primary-color); --docsearch-text-color: var(--color-gray-200); --docsearch-modal-background: #f6f6f6; --docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color); --docsearch-searchbox-background: #e8e8e8; --docsearch-searchbox-focus-background: #e8e8e8; --docsearch-hit-color: var(--color-gray-200); --docsearch-muted-color: var(--color-gray-500); --docsearch-logo-color: var(--accent); } .DocSearch-Button { @apply w-full ml-0 rounded-md px-3 !important; } .DocSearch-Button-Placeholder { @apply px-3 !important; } .DocSearch-Screen-Icon > svg { display: inline !important; } .dark { .DocSearch { --docsearch-text-color: #fff; --docsearch-container-background: rgba(9, 10, 17, 0.8); --docsearch-modal-background: hsl(240 6% 9%); --docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309; --docsearch-searchbox-background: hsl(0 0% 29%); --docsearch-searchbox-focus-background: hsl(0 0% 29%); --docsearch-hit-color: #fff; --docsearch-hit-shadow: none; --docsearch-hit-background: #333; --docsearch-key-gradient: linear-gradient(-26.5deg, #161618, #4a4a4a); --docsearch-key-shadow: inset 0 -2px 0 0 #5a6069, inset 0 0 1px 1px #959595, 0 2px 2px 0 rgba(3, 4, 9, 0.3); --docsearch-footer-background: hsl(240 6% 9%); --docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, 0.5), 0 -4px 8px 0 rgba(0, 0, 0, 0.2); --docsearch-logo-color: var(--accent); --docsearch-muted-color: var(--color-gray-500); } .DocSearch-NoResults, .DocSearch-Footer, .DocSearch-Help, .DocSearch-Reset { color: #e8e8e8; } } </style> ================================================ FILE: docs/src/components/DocTip.vue ================================================ <template> <div class="p-6 rounded my-8 border-l-4 bg-gray-100 dark:bg-zinc-800" :class="{ 'border-accent-800 text-accent-800': !type || type === 'tip', 'border-warning text-warning': type === 'warn', 'border-error text-error': type === 'danger', }" > <div class="flex flex-row items-center"> <svg v-if="['danger', 'warn'].includes(type)" fill="currentColor" class="h-6 w-6 mr-2" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" ></path> </svg> <svg v-if="type === 'tip' || !type" class="h-6 w-6 mr-2" fill="currentColor" viewBox="0 0 20 20"> <path d="M11 3a1 1 0 10-2 0v1a1 1 0 102 0V3zM15.657 5.757a1 1 0 00-1.414-1.414l-.707.707a1 1 0 001.414 1.414l.707-.707zM18 10a1 1 0 01-1 1h-1a1 1 0 110-2h1a1 1 0 011 1zM5.05 6.464A1 1 0 106.464 5.05l-.707-.707a1 1 0 00-1.414 1.414l.707.707zM5 10a1 1 0 01-1 1H3a1 1 0 110-2h1a1 1 0 011 1zM8 16v-1h4v1a2 2 0 11-4 0zM12 14c.015-.34.208-.646.477-.859a4 4 0 10-4.954 0c.27.213.462.519.476.859h4.002z" ></path> </svg> <p v-if="title" class="font-bold text-base">{{ title }}</p> <p v-else class="font-bold text-base uppercase">{{ type || 'tip' }}</p> </div> <div class="mt-4 text-black dark:text-white"> <slot /> </div> </div> </template> <script setup lang="ts"> defineProps<{ title?: string; type?: 'tip' | 'warn' | 'danger'; }>(); </script> ================================================ FILE: docs/src/components/DocToc.vue ================================================ <template> <nav class="py-4 overflow-y-auto overscroll-y-contain" v-show="headings.length"> <p class="font-bold text-xs uppercase text-gray-400">On this page</p> <ul class="mt-4 space-y-1 text-sm"> <li v-for="heading in headings" :key="heading.slug" :class="{ 'ml-4': heading.depth === 3 }"> <a class="inline-block py-1" :href="`#${heading.slug}`">{{ heading.text }}</a> </li> </ul> </nav> </template> <script setup lang="ts"> import { computed } from 'vue'; const props = defineProps<{ headings: { depth: number; slug: string; text: string }[]; }>(); const headings = computed(() => { return props.headings.filter(h => h.depth <= 3 && h.depth !== 1); }); </script> <style lang="postcss" scoped> nav { a { @screen motion { transition: transform 0.3s ease-in-out, color 0.2s ease-in-out; } &:hover { @apply text-accent-800; @screen motion { transform: translate3d(10px, 0, 0); } } } } nav { max-height: calc(80vh - 225px); a { @screen motion { transition: color 0.2s ease-in-out; } &:hover { @apply text-accent-800; } &[aria-current='page'] { @apply text-accent-800; } } /* Global Scrollbar styling */ &::-webkit-scrollbar { width: 7px; cursor: pointer; /*background-color: rgba(229, 231, 235, var(--bg-opacity));*/ } &::-webkit-scrollbar-track { background-color: none; cursor: pointer; /*background: red;*/ } &::-webkit-scrollbar-thumb { cursor: pointer; background-color: #e8e8e8; /* #E7E5E4; */ border-radius: 50px; /*outline: 1px solid grey;*/ } } .dark { nav { /* Global Scrollbar styling */ &::-webkit-scrollbar { width: 7px; cursor: pointer; /*background-color: rgba(229, 231, 235, var(--bg-opacity));*/ } &::-webkit-scrollbar-track { background-color: none; cursor: pointer; /*background: red;*/ } &::-webkit-scrollbar-thumb { cursor: pointer; background-color: #333; /* #E7E5E4; */ border-radius: 50px; /*outline: 1px solid grey;*/ } } } </style> ================================================ FILE: docs/src/components/EditPage.vue ================================================ <template> <div class="flex items-center"> <a :href="pageLink" target="_blank" rel="noopener" class="flex items-center text-sm font-bold text-carbon dark:text-gray-400" > <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" ></path> </svg> <span class="ml-2">Edit This Page on GitHub</span> </a> </div> </template> <script lang="ts" setup> import { computed } from 'vue'; const props = defineProps<{ path: string; }>(); const pageLink = computed(() => { const path = props.path.replace(/\/$/g, ''); return `https://github.com/logaretm/vee-validate/edit/main/${path}`; }); </script> ================================================ FILE: docs/src/components/ExpandTransition.vue ================================================ <template> <transition name="expand" :appear="appear" @enter="enter" @after-enter="afterEnter" @leave="leave"> <slot /> </transition> </template> <script> export default { name: 'TransitionExpand', props: { appear: { type: Boolean, default: false, }, }, methods: { enter(element) { const width = getComputedStyle(element).width; element.style.width = width; element.style.position = 'absolute'; element.style.visibility = 'hidden'; element.style.height = 'auto'; requestAnimationFrame(() => { const height = getComputedStyle(element).height; element.style.width = null; element.style.position = null; element.style.visibility = null; element.style.height = 0; requestAnimationFrame(() => { element.style.height = height; }); }); }, afterEnter(element) { element.style.height = 'auto'; }, leave(element) { const height = getComputedStyle(element).height; element.style.height = height; // Force repaint to make sure the // animation is triggered correctly. // eslint-disable-next-line no-unused-expressions getComputedStyle(element).height; setTimeout(() => { element.style.height = 0; }); }, }, }; </script> <style lang="postcss" scoped> .expand-enter-active, .expand-leave-active { transition-property: opacity, height; transition-duration: 0.3s; transition-timing-function: ease-in-out; overflow: hidden; } .expand-enter, .expand-leave-to { height: 0; } * { will-change: height; transform: translateZ(0); backface-visibility: hidden; perspective: 1000px; } </style> ================================================ FILE: docs/src/components/FeatureCard.vue ================================================ <template> <div ref="elementRef" class="FeatureCard bg-gradient-to-br from-gray-200 to-gray-100 dark:from-zinc-800 dark:to-zinc-900 p-6 rounded-3xl font-display shadow-sm" > <h3 class="text-xl font-bold z-10">{{ feature.title }}</h3> <p class="z-10 mt-4 font-display font-medium text-gray-600 dark:text-gray-300" v-html="feature.details" /> </div> </template> <script setup lang="ts"> import { shallowRef } from 'vue'; import { useEventListener } from '@vueuse/core'; const props = defineProps<{ feature: { title: string; details: string; }; }>(); const elementRef = shallowRef<HTMLElement>(); useEventListener( elementRef, 'mousemove', evt => { if (!elementRef.value) { return; } const { x, y } = elementRef.value.getBoundingClientRect(); elementRef.value.style.setProperty('--x', `${evt.clientX - x}px`); elementRef.value.style.setProperty('--y', `${evt.clientY - y}px`); }, { passive: true }, ); </script> <style lang="postcss" scoped> .FeatureCard { @apply relative; &:before { @apply pointer-events-none absolute select-none rounded-3xl opacity-0; z-index: -1; content: ''; inset: -1px; transition-property: opacity; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 0.3s; animation-duration: 0.3s; background: radial-gradient(200px circle at var(--x) var(--y), #009f53 0, #06d77b 0, #009f53 25%, transparent 80%); } &:hover:before { @apply opacity-100; } } </style> ================================================ FILE: docs/src/components/FloatingMenu.vue ================================================ <template> <slot name="trigger" :toggle="onMenuToggle" :isOpen="isOpen" /> <div ref="menuRef" class="fixed z-40 max-w-xs top-0 left-0"> <transition name="menu"> <div v-if="isOpen" class="bg-white shadow border border-gray-200 dark:bg-zinc-800 dark:border-zinc-700 p-3 rounded-md" > <slot name="menu" /> </div> </transition> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import { onClickOutside } from '@vueuse/core'; import { computePosition, Placement, offset } from '@floating-ui/dom'; const props = defineProps<{ placement?: Placement; }>(); const menuRef = ref<HTMLElement>(); const isOpen = ref(false); async function updateStyle(reference: HTMLElement) { if (!menuRef.value) { return; } const { x, y } = await computePosition(reference, menuRef.value, { strategy: 'fixed', placement: props.placement || 'bottom', middleware: [offset({ mainAxis: 10 })], }); menuRef.value.style.transform = `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`; } function onMenuToggle(e: Event) { isOpen.value = !isOpen.value; if (isOpen.value) { updateStyle(e.target as HTMLElement); } } onClickOutside(menuRef, () => { isOpen.value = false; }); </script> <style scoped lang="postcss"> .menu-enter-active, .menu-leave-active { transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out; } .menu-enter-from, .menu-leave-to { opacity: 0; transform: scale(0.8); } </style> ================================================ FILE: docs/src/components/Icon.vue ================================================ <template> <svg class="icon flex-shrink-0"> <use :xlink:href="`${basePath}#icon-${name}`"></use> </svg> </template> <script setup lang="ts"> const props = defineProps<{ name: string; }>(); const basePath = import.meta.env.DEV ? '' : '/v5/sprite.svg'; </script> ================================================ FILE: docs/src/components/LiveExample.vue ================================================ <template> <div ref="elRef"></div> </template> <script setup lang="ts"> import { onMounted, ref } from 'vue'; import sdk from '@stackblitz/sdk'; const props = defineProps<{ id: string; previewOnly?: boolean; clickToLoad?: boolean; }>(); const elRef = ref<HTMLElement>(); onMounted(() => { if (!elRef.value) { return; } sdk.embedProjectId(elRef.value, props.id, { forceEmbedLayout: true, openFile: 'src/App.vue', hideNavigation: true, height: 500, view: props.previewOnly ? 'preview' : undefined, clickToLoad: props.clickToLoad, }); }); </script> ================================================ FILE: docs/src/components/LogQueryStr.vue ================================================ <template> <div> Submitted data: <pre>{{ queryObj }}</pre> </div> </template> <script setup lang="ts"> import { onMounted, ref } from 'vue'; const queryObj = ref(); onMounted(() => { try { const query = new URLSearchParams(window.location.search); queryObj.value = Object.fromEntries(query.entries()); } catch { queryObj.value = { Error: 'Invalid query string in URL' }; } }); </script> <style scoped></style> ================================================ FILE: docs/src/components/MainPageExample.vue ================================================ <template> <div class="mt-14 flex flex-col items-center"> <div class="bg-gray-200 dark:bg-black max-w-xs p-1 rounded-md flex items-center gap-2 font-display font-semibold text-sm mb-8" > <button type="button" class="flavor-btn" :aria-pressed="currentFlavor === 'components'" @click="setFlavor('components')" > Components </button> <button type="button" class="flavor-btn" :aria-pressed="currentFlavor === 'composition'" @click="setFlavor('composition')" > Composition API </button> </div> <div v-if="currentFlavor === 'components'" class="w-full"> <MdxRepl :files="{ 'App.vue': 'ComponentsBasic' }" /> </div> <div v-else class="w-full"> <MdxRepl :files="{ 'App.vue': 'CompositionBasic' }" /> </div> </div> </template> <script setup lang="ts"> import { ref } from 'vue'; import MdxRepl from './MdxRepl.vue'; const currentFlavor = ref<'components' | 'composition'>('components'); function setFlavor(flavor: (typeof currentFlavor)['value']) { currentFlavor.value = flavor; } </script> <style lang="postcss" scoped> .flavor-btn { @apply px-3 py-1.5 rounded-md hover:text-emerald-500 select-none; &[aria-pressed='true'] { @apply bg-emerald-500 dark:bg-emerald-600 text-white hover:text-white; } } iframe { @apply w-full min-h-[800px] rounded-md border-4 border-emerald-500 border-opacity-60; } </style> ================================================ FILE: docs/src/components/MdxRepl.vue ================================================ <template> <div class="my-8 relative"> <Suspense v-if="editor"> <Repl :editor="editor" :files="files" /> <template #fallback> <div class="bg-gray-200 dark:bg-zinc-950 w-full h-full rounded-md absolute inset-0"></div> </template> </Suspense> <div v-else class="bg-gray-200 dark:bg-zinc-950 w-full h-full rounded-md absolute inset-0"></div> </div> </template> <script setup lang="ts"> import { defineAsyncComponent, onMounted, shallowRef } from 'vue'; const Repl = defineAsyncComponent(() => import('./Repl.vue')); const props = defineProps<{ files: Record<string, string>; }>(); let editor = shallowRef(); onMounted(async () => { editor.value = (await import('@vue/repl/codemirror-editor')).default; }); </script> ================================================ FILE: docs/src/components/Repl.vue ================================================ <script setup lang="ts" async> import { ref, version } from 'vue'; import { useFullscreen } from '@vueuse/core'; import { Repl, ReplStore } from '@vue/repl'; import { getViteProjectConfig } from '@/utils/examples'; import StackBlitzSdk from '@stackblitz/sdk'; const props = defineProps<{ files: Record<string, string>; editor: any; }>(); const containerRef = ref<HTMLElement>(); const { isFullscreen, toggle: toggleFullscreen } = useFullscreen(containerRef); const files = await Promise.all( Object.entries(props.files).map(async ([filename, componentName]) => { const code = await import(`./examples/${componentName}.vue?raw`); return [filename, code.default]; }), ); const store = new ReplStore({ serializedState: btoa(JSON.stringify(Object.fromEntries(files))), }); // pre-set import map store.setImportMap({ imports: { 'vee-validate': 'https://unpkg.com/vee-validate@latest/dist/vee-validate.mjs', '@vee-validate/i18n': 'https://unpkg.com/@vee-validate/i18n@latest/dist/vee-validate-i18n.esm.js', '@vee-validate/rules': 'https://unpkg.com/@vee-validate/rules@latest/dist/vee-validate-rules.esm.js', 'property-expr': 'https://esm-repo.netlify.app/property-expr.esm.js', 'tiny-case': 'https://esm-repo.netlify.app/tiny-case.esm.js', toposort: 'https://esm-repo.netlify.app/topsort.esm.js', yup: 'https://unpkg.com/yup@1.2.0/index.esm.js', zod: 'https://unpkg.com/zod@3.21.4/lib/index.mjs', valibot: 'https://unpkg.com/valibot@1.0.0-beta.7/dist/index.js', '@vue/devtools-api': 'https://unpkg.com/@vue/devtools-api@6.5.0/lib/esm/index.js', vue: `https://unpkg.com/vue@${version}/dist/vue.esm-browser.prod.js`, }, }); // use a specific version of Vue store.setVueVersion(version); function onForkClick() { const exampleFiles = files.reduce( (acc, file) => { acc[file[0]] = file[1]; return acc; }, {} as Record<string, string>, ); StackBlitzSdk.openProject(getViteProjectConfig(exampleFiles), { newWindow: true, startScript: 'dev', openFile: 'src/App.vue', }); } </script> <template> <div class="flex flex-col border border-gray-300 dark:border-emerald-500 border-opacity-80 rounded-md shadow-sm overflow-hidden relative" ref="containerRef" > <div class="flex items-center justify-end py-1.5 px-2 absolute z-10 right-0 h-[var(--header-height)]"> <button type="button" class="ml-1 p-1 hover:bg-white hover:bg-opacity-10 rounded" title="Go full screen" @click="toggleFullscreen" > <svg class="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <path v-if="!isFullscreen" d="M13.28 7.78l3.22-3.22v2.69a.75.75 0 001.5 0v-4.5a.75.75 0 00-.75-.75h-4.5a.75.75 0 000 1.5h2.69l-3.22 3.22a.75.75 0 001.06 1.06zM2 17.25v-4.5a.75.75 0 011.5 0v2.69l3.22-3.22a.75.75 0 011.06 1.06L4.56 16.5h2.69a.75.75 0 010 1.5h-4.5a.747.747 0 01-.75-.75zM12.22 13.28l3.22 3.22h-2.69a.75.75 0 000 1.5h4.5a.747.747 0 00.75-.75v-4.5a.75.75 0 00-1.5 0v2.69l-3.22-3.22a.75.75 0 10-1.06 1.06zM3.5 4.56l3.22 3.22a.75.75 0 001.06-1.06L4.56 3.5h2.69a.75.75 0 000-1.5h-4.5a.75.75 0 00-.75.75v4.5a.75.75 0 001.5 0V4.56z" ></path> <path v-else d="M3.28 2.22a.75.75 0 00-1.06 1.06L5.44 6.5H2.75a.75.75 0 000 1.5h4.5A.75.75 0 008 7.25v-4.5a.75.75 0 00-1.5 0v2.69L3.28 2.22zM13.5 2.75a.75.75 0 00-1.5 0v4.5c0 .414.336.75.75.75h4.5a.75.75 0 000-1.5h-2.69l3.22-3.22a.75.75 0 00-1.06-1.06L13.5 5.44V2.75zM3.28 17.78l3.22-3.22v2.69a.75.75 0 001.5 0v-4.5a.75.75 0 00-.75-.75h-4.5a.75.75 0 000 1.5h2.69l-3.22 3.22a.75.75 0 101.06 1.06zM13.5 14.56l3.22 3.22a.75.75 0 101.06-1.06l-3.22-3.22h2.69a.75.75 0 000-1.5h-4.5a.75.75 0 00-.75.75v4.5a.75.75 0 001.5 0v-2.69z" ></path> </svg> </button> <button type="button" class="ml-1 p-1 hover:bg-white hover:bg-opacity-10 rounded" title="Fork in StackBlitz" @click="onForkClick" > <svg class="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" > <path d="M11.983 1.907a.75.75 0 00-1.292-.657l-8.5 9.5A.75.75 0 002.75 12h6.572l-1.305 6.093a.75.75 0 001.292.657l8.5-9.5A.75.75 0 0017.25 8h-6.572l1.305-6.093z" ></path> </svg> </button> </div> <Repl :store="store" :editor="editor" :show-ts-config="false" :showCompileOutput="false" :ssr="false" :showImportMap="false" :class="{ 'no-files': files.length <= 1, 'is-fullscreen': isFullscreen }" /> </div> </template> <style lang="postcss" scoped> .vue-repl { @apply flex-grow; --bg: #e8e8e8 !important; --bg-soft: #e8e8e8 !important; --border: hsl(0 0% 74%) !important; --text-light: #000 !important; --color-branding: #009f53 !important; --color-branding-dark: #009f53 !important; &:deep(iframe) { margin: 0; } &:deep(.wrapper) { &:has(.toggle) { @apply hidden; } } &:deep(.stretch) { @apply items-stretch; } &:deep(.right) { height: unset; .active { @apply border-none; } } &:deep(.add) { @apply hidden; } &:deep(.right .tab-buttons) { @apply invisible; } &:deep(.output-container) { @apply h-full; } &:deep(.editor-container) { @apply h-full; } &.no-files { &:deep(.file-selector) { @apply hidden; } } &:deep(.file .remove) { @apply hidden; } &:deep(.CodeMirror) { background-color: #f6f6f6; color: var(--symbols); --symbols: #2c3e50; --base: #2c3e50; --comment: #848486; --keyword: #b2085f; --string: #0a7a34; --variable: #c25205; --number: #9580ff; --tags: #b2085f; --brackets: var(--symbols); --property: var(--symbols); --attribute: #c25205; --cursor: #fff; --selected-bg: #454158; --selected-bg-non-focus: rgba(255, 255, 255, 0.15); } &:deep(.CodeMirror-scroll) { max-height: 500px; } &.is-fullscreen { &:deep(.CodeMirror-scroll) { max-height: unset; } } } .dark { .vue-repl { --bg: #1c1c21 !important; --bg-soft: hsl(240 6% 9%) !important; --border: #333 !important; --text-light: #aaa !important; --color-branding: #06d77b !important; --color-branding-dark: #06d77b !important; &:deep(.CodeMirror) { color: var(--symbols); background-color: #1c1c21; --symbols: #f8f8f2; --base: #f8f8f2; --comment: #7970a9; --keyword: #ff80bf; --string: #ffff80; --variable: #8aff80; --number: #9580ff; --tags: #ff80bf; --brackets: var(--symbols); --property: var(--symbols); --attribute: #8aff80; --cursor: #fff; --selected-bg: #454158; --selected-bg-non-focus: rgba(255, 255, 255, 0.15); } } } </style> ================================================ FILE: docs/src/components/SideMenu.vue ================================================ <template> <transition name="slide"> <div v-if="modelValue" class="SideMenu lg:hidden"> <DocMenu :menu="menu" :current-url="currentUrl" /> </div> </transition> </template> <script setup lang="ts"> import { watch } from 'vue'; import DocMenu from '@/components/DocMenu.vue'; const props = defineProps<{ modelValue: boolean; menu: { title: string; pages: any[] }[]; currentUrl: string; }>(); watch( () => props.modelValue, val => { document.body.classList.toggle('overflow-hidden', val); } ); </script> <style lang="postcss" scoped> .SideMenu { @apply fixed h-screen w-screen inset-0 z-20 overflow-y-auto bg-white dark:bg-dark dark:text-white text-dark flex flex-col overflow-hidden; } @screen motion { .slide-enter-active, .slide-leave-active { transition: transform 0.25s ease-in-out, opacity 0.25s ease-in-out; } .slide-enter, .slide-leave-to { opacity: 0; transform: translate3d(100%, 0, 0); } } nav { a { transition: color 0.3s ease-in-out; &::-moz-focus-inner { border: 0; } &:hover { @apply text-accent-800; } &:focus { @apply text-accent-800; outline: 2px dotted currentColor; outline-offset: 2px; } &::after { @apply absolute inset-0 w-full h-full; border: 6px solid transparent; transition: border-color 0.3s ease-in-out; content: ''; } &.active { &::after { transition: border-color 0.3s ease-in-out; @apply border-accent-800; } } } } </style> ================================================ FILE: docs/src/components/SideMenuButton.vue ================================================ <template> <button @click="$emit('update:modelValue', !modelValue)" class="burger burger-squeeze" aria-label="Menu" :class="{ open: modelValue }" > <div class="burger-lines"></div> </button> </template> <script setup lang="ts"> defineProps({ modelValue: { type: Boolean, required: true, }, }); </script> <style lang="postcss" scoped> .burger { height: 3em; width: 3em; font-size: 12px; cursor: pointer; -webkit-transition: 0.2s all; -o-transition: 0.2s all; transition: 0.2s all; -webkit-tap-highlight-color: transparent; &:focus { outline: none; } } .burger .burger-lines:after { left: 0; top: -1em; } .burger .burger-lines:before { left: 1em; top: 1em; } .burger:after { content: ''; display: block; position: absolute; height: 150%; width: 150%; top: -25%; left: -25%; } .burger .burger-lines { top: 50%; margin-top: -0.125em; } .burger .burger-lines, .burger .burger-lines:after, .burger .burger-lines:before { pointer-events: none; display: block; content: ''; width: 100%; border-radius: 0.25em; background-color: #000; height: 0.25em; position: absolute; -webkit-transform: rotate(0); -ms-transform: rotate(0); transform: rotate(0); } .dark { .burger .burger-lines, .burger .burger-lines:after, .burger .burger-lines:before { background-color: #fff; } } .burger .burger-lines:after { left: 0; top: -1em; } .burger .burger-lines:before { left: 1em; top: 1em; } .burger.burger-squeeze .burger-lines:after, .burger.burger-squeeze .burger-lines:before { width: 2em; } @screen motion { .burger.burger-squeeze .burger-lines, .burger.burger-squeeze .burger-lines:after, .burger.burger-squeeze .burger-lines:before { -webkit-transition: 0.2s top 0.2s, 0.1s left, 0.2s transform, 0.4s background-color 0.2s; -o-transition: 0.2s top 0.2s, 0.1s left, 0.2s transform, 0.4s background-color 0.2s; transition: 0.2s top 0.2s, 0.1s left, 0.2s transform, 0.4s background-color 0.2s; } .burger.burger-squeeze.open .burger-lines, .burger.burger-squeeze.open .burger-lines:after, .burger.burger-squeeze.open .burger-lines:before { -webkit-transition: 0.2s background-color, 0.2s top, 0.2s left, 0.2s transform 0.15s; -o-transition: 0.2s background-color, 0.2s top, 0.2s left, 0.2s transform 0.15s; transition: 0.2s background-color, 0.2s top, 0.2s left, 0.2s transform 0.15s; } } .burger.burger-squeeze.open .burger-lines { background-color: transparent; } .burger.burger-squeeze.open .burger-lines:before, .burger.burger-squeeze.open .burger-lines:after { left: 0.5em; top: 0px; } .burger.burger-squeeze.open .burger-lines:before { -webkit-transform: rotate(-45deg); -ms-transform: rotate(-45deg); transform: rotate(-45deg); } .burger.burger-squeeze.open .burger-lines:after { -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); } </style> ================================================ FILE: docs/src/components/SiteHead.astro ================================================ --- import { generateLinks, generateMetaTags, generateSocialImage } from '@/utils/seo'; import config from '@/config'; const { frontmatter } = Astro.props; const title = frontmatter?.title || 'VeeValidate: Painless Vue.js forms'; const description = frontmatter?.description || 'Painless Vue.js forms'; const path = frontmatter?.url || '/v5/'; const image = generateSocialImage({ title: title, tagline: description, imagePublicID: 'open-source/vee-validate-share.png', }); const metaTags = generateMetaTags({ title, description, url: `${config.appURL}${path}`, image, }); const links = generateLinks({ url: `${config.appURL}${path}`, }); --- <head> <title>{title} {metaTags.map(t => )} {links.map(t => )} ================================================ FILE: docs/src/components/SponsorButton.vue ================================================ ================================================ FILE: docs/src/components/SpriteSheet.astro ================================================ --- const iconModules = import.meta.env.DEV ? await import.meta.glob('../icons/*.svg', { as: 'raw' }) : {}; const icons = import.meta.env.DEV ? await Promise.all( Object.keys(iconModules).map(k => { return iconModules[k]().then(content => { const id = k.split('/').pop().split('.').shift(); return content.replace('', ''); }); }) ) : []; --- { import.meta.env.DEV && ( ) } ================================================ FILE: docs/src/components/StarCount.vue ================================================ ================================================ FILE: docs/src/components/TheHeader.vue ================================================ ================================================ FILE: docs/src/components/ThemeSwitcher.vue ================================================ ================================================ FILE: docs/src/components/UiLibraries.vue ================================================ ================================================ FILE: docs/src/components/VersionSwitcher.vue ================================================ ================================================ FILE: docs/src/components/examples/ComponentsBasic.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionBasic.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionComponentBindsBasic01.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionComponentBindsBasic02.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionComponentBindsBasic03.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionComponentBindsBasic04.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionComponentBindsBasic05.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionCustomField01.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionCustomField02.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionCustomField03.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionCustomField04.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionCustomField05.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionCustomFieldCheckbox.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionDynamicSchemaComputed.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionDynamicSchemaYupLazy.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms01.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms02.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms03.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms04.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms05.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms06.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms07.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms08.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms09.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms10.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms11.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms12.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms13.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionHandlingForms14.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionInputBindsBasic01.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionInputBindsBasic02.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionInputBindsBasic03.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionInputFieldFn.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionInputFieldValibot.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionInputFieldYup.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionInputFieldZod.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionNested01.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionNested02.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionNested03.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionNested04.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionNested05.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionValibotBasic.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionValidateFnBasic.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionYupBasic.vue ================================================ ================================================ FILE: docs/src/components/examples/CompositionZodBasic.vue ================================================ ================================================ FILE: docs/src/components/examples/CustomCheckboxInputBasic.vue ================================================ ================================================ FILE: docs/src/components/examples/CustomInputBasic.vue ================================================ ================================================ FILE: docs/src/components/examples/CustomInputBasicError.vue ================================================ ================================================ FILE: docs/src/components/examples/CustomInputBasicValueEvent.vue ================================================ ================================================ FILE: docs/src/components/examples/CustomInputFieldAggressive.vue ================================================ ================================================ FILE: docs/src/components/examples/CustomInputFieldBasic.vue ================================================ ================================================ FILE: docs/src/components/examples/CustomInputFieldEager.vue ================================================ ================================================ FILE: docs/src/components/examples/CustomInputFieldMeta.vue ================================================ ================================================ FILE: docs/src/components/examples/CustomInputFieldMultiErrors.vue ================================================ ================================================ FILE: docs/src/components/examples/CustomInputFieldVModel.vue ================================================ ================================================ FILE: docs/src/config.ts ================================================ export default { algolia: { apiKey: '63f5a0934f840b36e5d33af009ddff15', appId: 'F6BYW6NAIH', indexName: 'vee-validate-v4', }, appURL: process.env.NODE_ENV === 'production' ? 'https://vee-validate.logaretm.com' : 'http://localhost:3000', gaId: 'UA-100131478-1', } as const; ================================================ FILE: docs/src/constants.ts ================================================ export const features = [ { title: 'Form-level validation', description: '' }, { title: 'Field-level validation', description: '' }, { title: 'Array fields', description: '' }, { title: 'UI Agnostic', description: 'Use any HTML markup, or use any 3rd party components, or bring or build your very own.', }, { title: 'Higher-order Components', description: '' }, { title: 'Composition API', description: '' }, { title: 'Async validators', description: 'Use sync or async validators or a mix of both.' }, { title: 'Backend API errors', description: 'The form actions API allow you to easily set form errors from your Backend API, or at any time.', }, { title: 'Form Submissions', description: 'Gives you access to both valid and invalid submissions so you can build amazing UX.', }, { title: 'Flexible validator API', description: 'Use plain JS functions, or Yup or Zod schemas, or use the included validators. The validator API is very flexible and you can integrate any 3rd party validator.', }, { title: 'State-store friendly', description: 'Straightforward API that you can use with Pinia or any composition-capable state store.', }, { title: 'Vue Devtools integration', description: 'A custom inspector is provided so you can easily debug your from state and submissions.', }, ]; ================================================ FILE: docs/src/env.d.ts ================================================ /// ================================================ FILE: docs/src/integrations/svgSprite.ts ================================================ import path from 'path'; import { AstroIntegration } from 'astro'; import { writeFile } from 'fs-extra'; const integration: AstroIntegration = { name: 'svgSprite', hooks: { async 'astro:build:done'() { const iconModules = await import.meta.glob('../icons/*.svg', { as: 'raw' }); const icons = await Promise.all( Object.keys(iconModules).map(k => { return iconModules[k]().then(content => { const id = k.split('/').pop().split('.').shift(); return content.replace('', ''); }); }), ); const sprite = ``; await writeFile(path.join(path.resolve(), '../docs/dist/sprite.svg'), sprite, { encoding: 'utf-8' }); }, }, }; export { integration as svgSprite }; ================================================ FILE: docs/src/layouts/HomeLayout.astro ================================================ --- import TheHeader from '@/components/TheHeader.vue'; import SiteHead from '@/components/SiteHead.astro'; import Ad from '@/components/Ad.vue'; import SpriteSheet from '@/components/SpriteSheet.astro'; import '@/styles/tailwind.css'; import '@/styles/home.css'; import { buildMenu, Frontmatter } from '@/utils/seo'; const menu = [ { title: 'tutorials', pages: buildMenu(await Astro.glob('../pages/tutorials/*.mdx')), }, { title: 'guide', pages: buildMenu(await Astro.glob('../pages/guide/*.mdx')), }, { title: 'examples', pages: buildMenu(await Astro.glob('../pages/examples/*.mdx')), }, { title: 'resources', pages: buildMenu(await Astro.glob('../pages/resources.mdx')), }, { title: 'integrations', pages: buildMenu(await Astro.glob('../pages/integrations/*.mdx')), }, { title: 'api reference', pages: buildMenu(await Astro.glob('../pages/api/*.mdx')), }, ]; ---
================================================ FILE: docs/src/layouts/PageLayout.astro ================================================ --- import path from 'path'; import SiteHead from '@/components/SiteHead.astro'; import Ad from '@/components/Ad.vue'; import SpriteSheet from '@/components/SpriteSheet.astro'; import TheHeader from '@/components/TheHeader.vue'; import EditPage from '@/components/EditPage.vue'; import SponsorButton from '@/components/SponsorButton.vue'; import DocMenu from '@/components/DocMenu.vue'; import DocToc from '@/components/DocToc.vue'; import DocNextStep from '@/components/DocNextStep.vue'; import DocSearch from '@/components/DocSearch.vue'; import ContentWrapper from '@/components/ContentWrapper.vue'; import { buildMenu, Frontmatter } from '@/utils/seo'; import '@/styles/tailwind.css'; import '@/styles/page.css'; const { headings, frontmatter } = Astro.props; const filePath = path.relative(path.resolve('../'), frontmatter.file); const page = frontmatter as Frontmatter; const menu = [ { title: 'resources', pages: buildMenu([ { ...(await Astro.glob('../pages/resources.mdx'))[0], icon: 'grid', }, ]), }, { title: 'tutorials', pages: buildMenu(await Astro.glob('../pages/tutorials/*.mdx')), }, { title: 'guide', pages: buildMenu([ ...(await Astro.glob('../pages/guide/*.mdx')), { frontmatter: { order: 2, title: 'Components', }, children: await Astro.glob('../pages/guide/components/*.mdx'), icon: 'code', }, { frontmatter: { order: 3, title: 'Composition API', }, children: await Astro.glob('../pages/guide/composition-api/*.mdx'), icon: 'grid-add', }, ]), }, { title: 'examples', pages: buildMenu(await Astro.glob('../pages/examples/*.mdx')), }, { title: 'integrations', pages: buildMenu(await Astro.glob('../pages/integrations/*.mdx')), }, { title: 'api reference', pages: buildMenu(await Astro.glob('../pages/api/*.mdx')), }, ]; ---
{page.next && }
================================================ FILE: docs/src/pages/api/composition-helpers.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Composition Helpers description: Various composable functions to compose validation logic into your components order: 9 --- import DocTip from '@/components/DocTip.vue'; import CodeTitle from '@/components/CodeTitle.vue'; # Composition Helpers The composition helpers are various functions that you can use to craft specialized form components, like a submission indicator component or a custom error messages component. These functions expose validation state to child components, most of these functions expose 2 variants of each state. On a form level and a field level. ## API Reference All of the following code snippets assume you are using them inside a `setup` function. `useFieldError(field?: string): ComputedRef` Returns a computed ref to a single field's error message, returns `undefined` if no errors were found for that field or if the field does not exist. ```js import { useFieldError } from 'vee-validate'; const message = useFieldError('fieldName'); message.value; // string or `undefined` ``` You can also use it in a child component that has a parent that used `useField`, The `useFieldError` will automatically pick up the field and produce its error messages. ```js import { useFieldError } from 'vee-validate'; // Will look for the first parent that used `useField` const message = useFieldError(); message.value; ``` `useFormErrors(): ComputedRef>` Returns a computed ref to the error bag of the entire form, fields with no errors will not be included in the error bag object. ```js import { useFormErrors } from 'vee-validate'; const errors = useFormErrors(); message.value; // {} ``` `useIsFieldDirty(field?: string): ComputedRef` Returns a computed ref to the specified field's `dirty` meta state. ```js import { useIsFieldDirty } from 'vee-validate'; const isDirty = useIsFieldDirty(); isDirty.value; // true or false ``` You can also use it in a child component that has a parent that used `useField`, The `useIsFieldDirty` will automatically pick up the field and produce its meta `dirty` value ```js import { useIsFieldDirty } from 'vee-validate'; // Will look for the first parent that used `useField` const isDirty = useIsFieldDirty(); ``` `useIsFormDirty(): ComputedRef` Returns a computed ref to the context form `dirty` meta state. ```js import { useIsFormDirty } from 'vee-validate'; const isDirty = useIsFormDirty(); isDirty.value; // true or false ``` `useIsFieldTouched(field?: string): ComputedRef` Returns a computed ref to the specified field's `touched` meta state. ```js import { useIsFieldTouched } from 'vee-validate'; const isTouched = useIsFieldTouched('fieldName'); isTouched.value; // true or false ``` You can also use it in a child component that has a parent that used `useField`, The `useIsFieldTouched` will automatically pick up the field and produce its meta `touched` value ```js import { useIsFieldTouched } from 'vee-validate'; // Will look for the first parent that used `useField` const isTouched = useIsFieldTouched(); ``` `useIsFormTouched(): ComputedRef` Returns a computed ref to the context form `touched` meta state. ```js import { useIsFormTouched } from 'vee-validate'; const isTouched = useIsFormTouched(); isTouched.value; // true or false ``` `useIsFieldValid(field?: string): ComputedRef` Returns a computed ref to the specified field's `valid` meta state, inner `value` will be `true` if the field has no errors, and `false` if it has any error message. ```js import { useIsFieldValid } from 'vee-validate'; const isValid = useIsFieldValid('fieldName'); isValid.value; // true or false ``` You can also use it in a child component that has a parent that used `useField`, The `useIsFieldValid` will automatically pick up the field and produce its meta `valid` value ```js import { useIsFieldValid } from 'vee-validate'; // Will look for the first parent that used `useField` const isValid = useIsFieldValid(); ``` Creating disabled buttons based on the `valid` attribute isn't accurate, because if the field hasn't been validated yet it, the `valid` property will be `true` which isn't accurate. You should combine `valid` checks with `dirty` state to get the most accuracy. `useIsFormValid(): ComputedRef` Returns a computed ref to the context form `valid` meta state. ```js import { useIsFormValid } from 'vee-validate'; const isValid = useIsFormValid(); isValid.value; // true or false ``` Creating disabled buttons based on the `valid` attribute isn't accurate, because if the form hasn't been validated yet it, the `valid` property will be `true` which isn't accurate. You should combine `valid` checks with `dirty` state to get the most accuracy. `useValidateField(field?: string): () => Promise` Returns a function that validates the field and returns a validation result object containing any errors, if the `errors` field is empty then it means the field is valid. If a field doesn't not exist it will return an empty `errors` field with a warning. ```js import { useValidateField } from 'vee-validate'; const validate = useValidateField('fieldName'); await validate(); ``` You can also use it in a child component that has a parent that used `useField`, The `useValidateField` will automatically pick up the field and will return the function that validates it. ```js import { useValidateField } from 'vee-validate'; // Will look for the first parent that used `useField` const validate = useValidateField(); ``` `useValidateForm(): () => Promise` Returns a function that validates the form and returns a `Form`. ```js import { useValidateForm } from 'vee-validate'; const validate = useValidateForm(); await validate(); ``` `useIsSubmitting(): ComputedRef` Returns a computed ref to the form's `isSubmitting` state. ```js import { useIsSubmitting } from 'vee-validate'; const isSubmitting = useIsSubmitting(); isSubmitting.value; // true or false ``` `useIsValidating(): ComputedRef` Returns a computed ref to the form's `isValidating` state. ```js import { useIsValidating } from 'vee-validate'; const isValidating = useIsValidating(); isValidating.value; // true or false ``` `useSubmitCount(): ComputedRef` Returns a computed ref to the form's `submitCount` state. ```js import { useSubmitCount } from 'vee-validate'; const count = useSubmitCount(); count.value; ``` `useResetForm(): () => void` Returns a function that you can use to reset the form ```js import { useResetForm } from 'vee-validate'; const resetForm = useResetForm(); resetForm(); // resets the form ``` `useSubmitForm(cb: SubmissionHandler): () => void` Returns a function that you can use to validate the form and submit if it turns out valid. It does this by accepting a function that should handle the submission logic like sending data to your API. That function will not run unless the form is valid and it receives all the fields current values packed in an object. ```js import { useSubmitForm } from 'vee-validate'; const submitForm = useSubmitForm((values, actions) => { // Send data to your api ... alert(JSON.stringify(values, null, 2)); // You can perform any of the form actions using the actions object // set a single field value actions.setFieldValue('field', 'hello'); // set multiple fields values actions.setValues({ email: 'value', password: 'hi' }); // set a single field error actions.setFieldError('field', 'this field is bad'); // set multiple fields errors actions.setErrors({ email: 'bad email', password: 'bad password' }); // reset the form actions.resetForm(); }); ``` While it is recommended to use actual `form` elements for accessibility, you could still use `useSubmitForm` to submit any group of data which may or may not be involved with a `form` element. `useFieldValue(field?: string): ComputedRef` Returns a computed ref to the specified field's current value. ```js import { useFieldValue } from 'vee-validate'; const currentValue = useFieldValue('fieldName'); currentValue.value; ``` You can also use it in a child component that has a parent that used `useField`, The `useFieldValue` will automatically pick up the field and produce its current value. ```js import { useFieldValue } from 'vee-validate'; // Will look for the first parent that used `useField` const currentValue = useFieldValue(); ``` `useFormValues(): ComputedRef>` Returns a computed ref to the context form current values. ```js import { useFormValues } from 'vee-validate'; const values = useFormValues(); values.value; ``` ================================================ FILE: docs/src/pages/api/configuration.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Configuration description: VeeValidate Global Config Reference order: 8 --- import DocTip from '@/components/DocTip.vue'; # Configuration vee-validate exposes global configs to help with a few repeated or certain behaviors that needs to be set app-wide. ## Config Options | Option | Type | Description | | --------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | bails | `boolean` | Whether to run validations to completion or quit on the first, default is `true`. Doesn't affect `yup` schemas | | generateMessage | `(ctx: FieldValidationMetaInfo) => string` | A message generator function for i18n libraries and a fallback for rules with no messages. For more information about the [`FieldValidationMetaInfo`](https://github.com/logaretm/vee-validate/blob/main/packages/shared/types.ts#L1-L9) type and the purpose of this, see the [Global Message Generator Guide](/guide/i18n#global-message-generator) | | validateOnBlur | `boolean` | If validation should be triggered on `blur` event, default is `true` | | validateOnChange | `boolean` | If validation should be triggered on `change` event, default is `true` | | validateOnInput | `boolean` | If validation should be triggered on `input` event, default is `false` | | validateOnModelUpdate | `boolean` | If validation should be triggered on `update:modelValue` (v-model) event, default is `true` | This is slightly verbose, but this gives you exact control on which events triggers validation. ## Updating The Config You can change the global config using the `configure` function exposed by vee-validate passing any options that you need to change. You can call that function at any time during runtime but the changes will take effect for new `Field` and `useField` afterwards. Here is an example: ```js import { configure } from 'vee-validate'; configure({ bails: false, }); ``` Note that the following config do not affect `useField`, they only apply to the `` component: - `validateOnBlur` - `validateOnChange` - `validateOnInput` - `validateOnModelUpdate` ================================================ FILE: docs/src/pages/api/error-message.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: ErrorMessage description: API reference for the ErrorMessage component menuTitle: '' order: 3 --- import DocTip from '@/components/DocTip.vue'; # ErrorMessage The `ErrorMessage` component is used to conveniently display error messages without resorting to scoped slots on either the `Form` and `Field` components. It also renders nothing if there are no messages for the associated field. The basic usage looks like this: ```vue ``` ## Rendering Messages The `ErrorMessage` component accepts an `as` prop that allows to control the root node to render for your error messages, by default it will render a `span` if none provided if there is an error message for the field. ```vue-html ``` For more flexible markup and the ability to render multiple root nodes, you can use the `ErrorMessage` component's default scoped slot ```vue-html

Error:

{{ message }}

``` If an `as` prop is not provided while having a slot, it will render the slot contents only. _Effectively becoming a renderless-component._ You can use a combination of both to render a root node with children: ```vue-html

Error:

{{ message }}

``` When there are not any error messages for the field the `` component will render nothing, even if you used the slot or `as` prop. ## API ### Props | Prop | Type | Required/Default | Description | | :--- | :------- | :--------------- | :--------------------------------------- | | as | `string` | `"span"` | The element to render as a root node | | name | `string` | Required | The field's name to display messages for | ### Slots #### default The default slot gives you access to the following props: | Scoped Prop | Type | Description | | :---------- | :------- | :-------------------------- | | message | `string` | The element's error message | Check the sample above for rendering with scoped slots ## Caveats - The ErrorMessage component must be used inside a `Form` component otherwise it won't be able to find the errors. - ErrorMessage component can only display errors for a field that exists within the same form, it cannot reference fields outside its own form. ================================================ FILE: docs/src/pages/api/field-array.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: FieldArray description: API reference for the Field Array component menuTitle: '' order: 4 --- import DocTip from '@/components/DocTip.vue'; import DocBadge from '@/components/DocBadge.vue'; import CodeTitle from '@/components/CodeTitle.vue'; # FieldArray The `` component requires being used inside a `Form` component or a `useForm` to be called at its parent tree. The `` component is used to manage repeatable array fields. It is a renderless component, meaning it doesn't render anything of its own. ```vue ``` ## API Reference ### Props | Prop | Type | Required/Default | Description | | :---------- | :------- | :--------------- | :------------------------------------- | | `arrayPath` | `string` | Yes | The form array path you wish to manage | ### Slots #### default The default slot gives you access to the following props: `fields: FieldArrayEntry` This is a **read-only** version of your array items, wrapped inside a `FieldArrayEntry` object which has the following interface: ```ts interface FieldEntry { // The actual value of the item, this is what exists in the form values value: TValue; // a value you can use as a key for iteration, automatically generated. key: string | number; // true if this is the first array item isFirst: boolean; // true if this is the last array item isLast: boolean; } ``` `push(item: any)` Adds an item to the end of the array. `prepend(item: any)` Adds an item to the start of the array. `remove(idx: number)` Removes the item at the specified index from the array if it exists. `swap(idxA: number, idxB: number)` Swaps the items at the given indexes with each other. Both indexes must exist in the array or it won't have an effect. `insert(idx: number, item: any)` Adds an item at the specified index. If the specified index will place the item out of bounds (i.e: larger than length) the operation will be ignored, you still can add items as the last item of the array. `update(idx: number, value: any)` Updates the value at the specified index, note that it doesn't merge the values if they are objects. If the specified index is outside the array boundary the operation will be ignored. `replace(items: any[])` Replaces the entire array of fields. `move(oldIdx: number, newIdx: number)` Moves an array item to a different position within the array. If one of the indices is outside of the array boundaries the operation will be ignored. ================================================ FILE: docs/src/pages/api/field.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Field description: API reference for the Field component menuTitle: '' order: 1 --- import DocTip from '@/components/DocTip.vue'; import CodeTitle from '@/components/CodeTitle.vue'; # Field The `` component is an extremely flexible component that makes rendering input fields easy and intuitive, By default it renders an HTML `input` tag. So a simple text input can be rendered like this: ```vue ``` ## Rendering Fields The `Field` component allows you to render practically anything and gives you complete flexibility and control over how your fields are rendered. The `Field` component renders an HTML `input` tag if not specified otherwise. Which can be done in two ways. ### Rendering simple fields with 'as' prop The `as` prop tells the Field component which tag to render in its place, you can pass any additional attributes like `type="text"` and it will be passed to the rendered `input` tag as well as any listeners and slots. For example you could render a `select` input like this: ```vue-html ``` The `Field` component has partial support for native `select[multiple]` element, while it picks up the multiple values correctly, it doesn't set the initial values UI state on the element itself. You may use `v-model` here or bind the `selected` attributes on the options which is straightforward with the `value` prop exposed on the slot props. ```vue-html ``` You can also render any globally defined components: ```vue-html ``` For validation to work, the rendered tag with `as` prop must conform to the events that the `Field` component listens for, you can view a list of these in the [Validation Behavior](/guide/components/validation#validation-behavior) ### Rendering Complex Fields with Scoped Slots The `as` prop is very easy to use but also limited as you cannot render a group of markup. Fortunately the `Field` component makes use of the scoped-slots (v-slot) feature to allow you to render complex markup: ```vue-html

Hint: Enter a secure password you can remember

``` The most crucial part of rendering fields with `v-slot` is that you **bind the `field` object to your input element/input**, the `field` object contains all the common attributes and listeners required for the field to be validated, this is done automatically if you are using the `as` prop. When rendering File inputs and custom components, binding the `field` object may not be a good idea in these cases as generally you need to pick the suitable attributes that you should bind. In the case of `input[type="file"]` you could do something like this: ```vue-html ``` This is because the file input doesn't work with two-way binding since you cannot really force pick a file from a user's device. Custom components may emit different events and may have additional requirements for two-way binding to work. You can check the [UI Libraries](/examples/ui-libraries) section for a few examples on how to do that. When using `v-slot` on the `Field` component you no longer have to provide an `as` prop and then it will become a renderless component. Whichever element/component you chose to render with `` you can pass also any props or attributes the element/component would normally receive for example if you render `input` elements you can pass any of it's attributes like `type` or `disabled` to just name a few. ### Using v-model The `` component uses a different way to apply input values to your rendered inputs using the `v-bind="field"` syntax which adds various listeners and attributes to your rendered inputs. Because of this, using `v-model` will conflict with `v-bind="field"` because both will attempt to update the input value. For simple inputs this is not an issue: ```vue-html ``` But for complex inputs rendered with scoped slots (v-slot), you need to place it on the `Field` component tag itself, not the rendered input. ```vue-html ``` Note that you no longer should use `v-model` on your input as `v-bind="field"` will take care of the rest. ## API Reference ### Props | Prop | Type | Required/Default | Description | | :-------------------- | :----------------------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | as | `string` | `"span"` | The element to render as a root node, defaults to `input` | | name | `string` | Required | The field's name, must be inside `
` | | rules | `object \| string \| Function` | `null` | The field's validation rules | | validateOnMount | `boolean` | `false` | If true, field will be validated when the component is mounted | | validateOnInput | `boolean` | `false` | If true, field will be validated when `input` event is dispatched/emitted | | validateOnChange | `boolean` | `true` | If true, field will be validated when `change` event is dispatched/emitted | | validateOnBlur | `boolean` | `true` | If true, field will be validated when `blur` event is dispatched/emitted | | validateOnModelUpdate | `boolean` | `true` | If true, field will be validated when `update:modelValue` event is emitted | | bails | `boolean` | `true` | Stops validating as soon as a rule fails the validation | | label | `string` | `undefined` | A different string to override the field `name` prop in error messages, useful for display better or formatted names. The generated message won't be updated if this prop changes, you will need to re-validate the input. | | value | `any` | `undefined` | The field's initial value, optional as long as the field type is not `checkbox` or `radio`. | | type | `string` | `undefined` | The field type, must be provided if you want your field to behave as a `checkbox` or a `radio` input. | | unchecked-value | `any` | `undefined` | Only useful when the `type="checkbox"` and the field is a single checkbox field (not bound to an array value). Controls the input's value when it's unchecked. | | standalone | `boolean` | `false` | Excludes the field from participating in any `Form` or `useForm` contexts, useful for creating inputs that do contribute to the `values` object. In other words, the form won't pick up or validate fields marked as standalone | | keepValue | `boolean` | `undefined` | If the field value should be kept in the form when it gets unmounted, default follows the form's configuration | ### Slots #### default The default slot gives you access to the following props: `meta: FieldMeta` Contains useful information/flags about the field status. ```ts interface FieldMeta { touched: boolean; // if the field has been blurred (via handleBlur) dirty: boolean; // if the field has been manipulated (via handleChange) valid: boolean; // if the field doesn't have any errors validated: boolean; // if the field has been validated pending: boolean; // if validation is in progress initialValue?: any; // the field's initial value } ``` The `valid` flag on the meta object can be tricky, because by default it stars off with `true` for a few moments, only then it is updated to its proper state. Combining your `valid` flag checks with `dirty` will yield the expected result based on user interaction. `errors: string[]` An array containing all error messages for the field. `value: unknown` The current value of the field, useful to compare and do conditional rendering based on the field value. **You should not use it as a target of `v-model` or `:value` binding**. Instead use the `field` prop. `errorMessage: ComputedRef` The first error in the `errors` array if available, a handy shortcut to display error messages `resetField: (state?: Partial) => void` Resets the field's validation state, reverts all `meta` object to their default values and clears out the error messages, it will also reset the field value to it's initial value. Note that no error messages will be generated if the initial value is invalid after reset, the `valid` flag will be then set to `true` in that case. Note that it is unsafe to use this function as an event handler directly, check the following snippet: ```vue-html ``` You can use `resetField` to update the fields' current value to something other than its initial value, ```vue-html ``` `handleReset: () => void` Similar to `resetField` but it doesn't accept any arguments and can be safely used as an event handler. The values won't be validated after reset. `validate: () => Promise<{ errors: string[] }>` Validates the field's current value and returns a promise that resolves with an object containing the error messages emitted by the various rule(s). ```js const { validate } = useField('field', value => !!value); // trigger validation await validate(); ``` `handleChange: (evt: Event | any, shouldValidate?: boolean) => void` Updates the field value, and validates the field by default. Can be used as an event handler to bind on the field. If the passed argument isn't an event object it will be used as the new value for that field. You can update the field value without validation by passing `false` as a second argument. `handleBlur: (evt: Event | any) => void` Validates the field by default unless explicitly [specified by validation triggers](/guide/components/validation#customizing-validation-triggers). Can be used as an event handler to bind on the field. If the passed argument isn't an event object it will be used as the new value for that field. It sets the `touched` meta flag to true Because this handler doesn't set the field value, it might not report validation correctly if other events are unspecified or disabled. `setTouched: (isTouched: boolean) => void` Sets the `touched` meta flag for this field, useful to create your own `blur` handlers #### `field` Contains a few properties that you can use `v-bind` with to get all vee-validate features on that input. The following is a description of the properties `field.value: any` The field's current value, you can bind it with `value` prop on your inputs to sync both values. Don't use it with `v-model` otherwise your input will freeze. `field.name: string` The field name. `field.onBlur: (e: Event | unknown) => void` An array containing a few listeners for the `blur` event, it involves updating some meta information and triggers validation by default. `field.onInput: (e: Event | unknown) => void` An array containing a few listeners for the `input` event, it involves updating the field value and some meta information. `field.onChange: (e: Event | unknown) => void` An array containing a few listeners for the `change` event, it involves updating the field value and triggering validation. #### `componentField` Same as `field`, but provides value as `modelValue` to use with components ================================================ FILE: docs/src/pages/api/form.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Form description: API reference for the Form component menuTitle: '' order: 2 --- import DocTip from '@/components/DocTip.vue'; import CodeTitle from '@/components/CodeTitle.vue'; # Form ## Form Component The `` component is like its name, a simple HTML form but with a few adjustments and DX improvements, By default it will render a native HTML `form` element. ```vue-html ``` ## Rendering Forms Just like the `Field` component you can pass whatever you want to render in its place, for example a custom `v-form` component that is registered globally: ```vue-html
``` By default a `submit` and `reset` listeners are added to the rendered tag specified by the `as` prop. For more complex form markup, you can render a `div` and inline your forms in the `Form` component's slots. ```vue-html

Sign up form

``` Notice the character-case difference between `Form` and `form`, the uppercased one is the component provided by vee-validate while the lowercased one is the native `form` element, you might run into issues when not using Vue compiler workflow like script tags. In these cases it is recommended to rename the `Form` component to something that will not conflict with native HTML. ```js // Using named imports import { Form as ValidationForm } from 'vee-validate'; const component = { components: { // If you have VeeValidate globally via a CDN script ValidationForm: VeeValidate.Form, }, }; ``` Lastly, you can use the `Form` component slot props to access various aspects of the form state: ```vue-html
    {{ values }}
  
``` ## Renderless Forms While not recommended, you can make the `Form` component a renderless component by passing an empty string to the as prop, this is useful if you already need to add a form inside the scoped slot: ```vue-html

Sign up form

      {{ values }}
    
``` ## API Reference ### Props | Prop | Type | Default | Description | | :--------------- | :----------------------------------- | :---------- | :----------------------------------------------------------------------------------------------------------- | | as | `string` | `"form"` | The element to render as a root node | | validationSchema | `Record` | `undefined` | An object describing a schema to validate fields with, can be a plain object or a `yup` object schema | | initialValues | `Record` | `undefined` | Initial values to fill the fields with, when provided the fields will be validated on mounted | | initialErrors | `Record` | `undefined` | Initial form errors to fill the fields with, the errors will be added when the form component is mounted | | initialTouched | `Record` | `undefined` | Initial touched fields, the status will be applied when the form component is mounted | | validateOnMount | `boolean` | `false` | If true, the fields currently present in the form will be validated when the `
` component is mounted | | keepValues | `boolean` | `false` | If the fields values should be deleted when they get unmounted, default is false | | name | `string` | `"Form"` | The name of the form that is displayed in the devtools. | ### Slots #### default The default slot gives you access to the following props: `errors: Record` An object that maps field names to their error messages, it only takes the first error message of each field if multiple exists. Here is an example of its shape: ```js { email: "this field must be a valid email", password: "too short" } ``` Any fields without error messages will not be included in the object. So you can safely iterate over it with `Object.keys()` knowing all the included fields are invalid. `errorBag: Record` An object that maps field names to all of their error messages. Here is an example of its shape: ```js { email: ["this field is required", "this field must be a valid email"], password: "too short" } ``` Any fields without error messages will not be included in the object. So be careful when accessing the errors array for each field `isSubmitting: boolean` Indicates if the submission handler is still running, once it resolves/rejects it will be automatically set to `false` again. `isValidating: boolean` Indicates if the validate function is still running, once validate function is done it will be automatically set to `false` again. `meta: FormMeta` Contains an aggregated meta information/flags reflecting the state of all the fields inside the form. ```ts interface FormMeta { touched: boolean; // if at least one field is touched (was blurred) dirty: boolean; // if at least one field is dirty (manipulated) valid: boolean; // if all fields are valid pending: boolean; // if at least one field is pending validation initialValues?: Record; // a map of the form's initial values } ``` `values: Record` Contains the current form values `setFieldError: (field: string, message: string) => void` Sets a field's error message, useful for setting messages form an API or that are not available as a validation rule. If you try to set an error for a field that doesn't exist, it will be added to the form's errors object and it will change the form's `valid` state `setErrors: (fields: Record) => void` Sets multiple fields error messages, uses `setFieldError` internally. `setFieldValue: (field: string, value: any) => void` Sets a field's value, if a field does not exist it will not be reflected in the `values` ref. This will trigger validation on the field whose value changed. If you try to set a value for a field that doesn't exist, it will be added to the form's values object and will stay there until the next `resetForm` is called. `setValues: (fields: Record) => void` Sets all fields values, will trigger validation for the changed fields. `setFieldTouched: (field: string, isTouched: boolean) => void` Sets a field's `touched` meta flag, if you set it for a non-existing field it will have no effect. `setTouched: (fields: Record) => void` Sets multiple fields `touched` meta flag, does not validate. `validate: () => Promise<{ valid: boolean; errors: Record}>` Validates all the fields and populates the `errors` object, returns a promise that resolves to an object containing aggregated validation result of all fields. `validateField: (field: string) => Promise<{ valid: boolean; errors: string[] }>` Validates a specific field inside the form, returns a promise that resolves to an object containing the validation result. `handleSubmit: (evt: Event, cb: (values: Record, ctx: SubmissionContext) => any) => Promise` This can be used as an event handler for `submit` events, it accepts the event object and a callback function that will run if the form is valid. If an event object is provided, `preventDefault` and `stopPropagation` will be automatically called on it. Note that this is only useful if you are not rendering a form tag on the `` component. By default the `Form` component uses this handler to handle any `submit` events. `submitForm: (evt: Event) => void` This function can also be used as an event handler for form `submit` event, the different that it will prevent the propagation and submission of the form as long as they are invalid. Once all the fields are valid it will submit the form with the native HTML behavior following the `form` element's `action` and `method` attributes. This is useful if you plan to handle form submissions using a backend API like Laravel. `submitCount: number` The number of submission attempts by the user, it increments whenever `submitForm` or `handleSubmit` callback are called. `handleReset: () => void` Clears error messages, resets the meta state for all fields and reverts their values to their initial state and resets the `submitCount` to `0`. You can use this function as handler for the `reset` events on native form elements. `resetForm: (state?: Partial) => void` Clears error messages, resets the meta state for all fields and reverts their values to their initial state and resets the `submitCount`. Accepts an optional object containing the new form state, useful if you need to reset the form values to different values other than their initial state. This is the shape of the `state` object: ```ts interface FormState { // any error messages errors: Record; // touched meta flags touched: Record; // Form Values values: Record; // The form submit attempts count submitCount: number; } ``` In the following example the form is resetting the `email` field value to another value, this will change the field current value as well as it's initial value. Meaning any future calls of `resetForm` without arguments or `handleReset` will use `example@example.com` as their value. This also applies if fields are reset individually using `resetField` on either `useField` return value or `` component's slot props. ```vue-html ... ``` ================================================ FILE: docs/src/pages/api/use-field-array.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: useFieldArray description: API reference for the useFieldArray composition API function menuTitle: useFieldArray() order: 7 --- import DocBadge from '@/components/DocBadge.vue'; import CodeTitle from '@/components/CodeTitle.vue'; # useFieldArray `useFieldArray` is a custom composition API function that allows you to manage repeatable fields and forms entries and provides common operation helpers. Basic usage: ```vue ``` ## API Reference The full signature of the `useFieldArray` function looks like this: ```ts interface FieldEntry { value: Ref; key: string | number; isFirst: boolean; isLast: boolean; } interface FieldArrayContext { fields: Ref[]>; remove(idx: number): void; replace(newArray: TValue[]): void; update(idx: number, value: TValue): void; push(value: TValue): void; swap(indexA: number, indexB: number): void; insert(idx: number, value: TValue): void; prepend(value: TValue): void; move(oldIdx: number, newIdx: number): void; } function useFieldArray: (arrayPath: string): FieldArrayContext; ``` ### Composable API The following sections documents each available property on the `useFieldArray` composable. `fields: Ref[]>` This is a **read-only** version of your array items, wrapped inside a `FieldArrayEntry` object which has the following interface: ```ts interface FieldEntry { // The actual value of the item as readonly, this is what exists in the form values value: TValue; // a value you can use as a key for iteration, automatically generated key: string | number; // true if this is the first array item isFirst: boolean; // true if this is the last array item isLast: boolean; } ``` ```js // call composable in component setup to get readable version of links array const { fields } = useFieldArray('links'); ``` `push(item: any)` Adds an item to the end of the array. ```js // get push function from composable in component setup const { push } = useFieldArray('links'); // call push function within custom function const myPushFunction = () => { // adds a new item to the array push({ url: '' }); }; ``` `prepend(item: any)` Adds an item to the start of the array. ```js // get prepend function from composable in component setup const { prepend } = useFieldArray('links'); // call prepend function within custom function const myPrependFunction = () => { // adds a new item to the array prepend({ url: '' }); }; ``` `remove(idx: number)` Removes the item at the specified index from the array if it exists. ```js // get remove function from composable in component setup const { remove } = useFieldArray('links'); // call remove function within custom function const myRemoveFunction = () => { // removes the second item from the array remove(1); }; ``` `swap(idxA: number, idxB: number)` Swaps the items at the given indexes with each other. Both indexes must exist in the array or it won't have an effect. ```js // get swap function from composable in component setup const { swap } = useFieldArray('links'); // call swap function within custom function const mySwapFunction = () => { // Swaps the 4th item with the 5th swap(3, 4); }; ``` `insert(idx: number, item: any)` Adds an item at the specified index. If the specified index will place the item out of bounds (i.e: larger than length) the operation will be ignored, you still can add items as the last item of the array. ```js // get insert function from composable in component setup const { insert } = useFieldArray('links'); // call insert function within custom function const myInsertFunction = () => { // inserts a new item to the array at the second index insert(1, { url: '' }); }; ``` `update(idx: number, value: any)` Updates the value at the specified index, note that it doesn't merge the values if they are objects. If the specified index is outside the array boundary the operation will be ignored. ```js // get update function from composable in component setup const { update } = useFieldArray('links'); // call update function within custom function const myUpdateFunction = () => { // updates the 2nd item value update(1, { url: '' }); }; ``` `replace(items: any[])` Replaces the entire array of fields. ```js // get replace function from composable in component setup const { replace } = useFieldArray('links'); // call replace function within custom function const myReplaceFunction = () => { // replace the entire array with these items replace([{ url: 'https://google.com' }, { url: 'https://vuejs.org' }]); }; ``` `move(oldIdx:number, newIdx: number)` Moves an array item to a different position within the array. ```js // get move function from composable in component setup const { move } = useFieldArray('links'); // call move function within custom function const myMoveFunction = () => { // move array item to a different position move(2, 1); }; ``` ================================================ FILE: docs/src/pages/api/use-field.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: useField description: API reference for the useField composition API function menuTitle: useField() order: 5 --- import DocTip from '@/components/DocTip.vue'; import CodeTitle from '@/components/CodeTitle.vue'; # useField `useField` is a custom composition API function that allows you to create data models that's automatically validated that you can then use to build your own custom input components with validation capabilities. It is very useful if you plan to build a UI component library that needs to have validation capabilities. In other words it acts as a primitive to allow you to compose validation logic into your components. The most basic usage looks like this: ```vue ``` Whenever the `value` ref is updated it will be validated and the `errorMessage` ref will be automatically filled with the first error message. Usually you would bind the `value` ref to your inputs using `v-model` or any other means and whenever you want to validate your field you update the `value` binding with the new value. Additionally you can use `yup` as a validator: ```vue ``` You are responsible for when the field validates, blurs or when its value changes. This gives you greater control over the `Field` component which may include or implement sensible defaults for most common use cases. ## Usage with TypeScript You can use `useField` with typescript and type the field's value type to ensure safety when manipulating it's value. The `useField` function is a generic function that receives the value type and applies it on the various interactions you have with its API. ```ts const { value, resetField } = useField('email', yup.string().email()); value.value = 1; // ⛔️ Error value.value = 'test@example.com'; // ✅ resetField({ value: 1, // ⛔️ Error }); resetField({ value: 'test@example.com', // ✅ }); ``` The validation rules can be either a string, object, function or a yup schema: ```js // Globally defined rules with `defineRule`, Laravel-like syntax useField('password', 'required|min:8'); // Globally defined rules object useField('password', { required: true, min: 8 }); // Simple validation function useField('password', value => { if (!value) { return 'password is required'; } if (value.length < 8) { return 'password must be at least 8 characters long'; } return true; }); // Yup validation useField('password', yup.string().required().min(8)); ``` ## API Reference ### Composable API The following sections documents each available property on the `useField` composable. `name: string | Ref | () => string` The field name, it can be a string or a reactive ref to a string or a getter than returns a string. It is used to identify the field and specify its value path. `value: Ref` A reactive reference to the field's current value, can be changed and will trigger validation by default unless disabled by the `validateOnValueUpdate` option. ```js const { value } = useField('field', value => !!value); value.value = 'hello world'; // sets the value and validates the field ``` You can also bind it with `v-model` to get two-way value binding with validation. `setValue: Ref` A method to update the fields value. ```js const { setValue } = useField('field', value => !!value); setValue('hello world') // sets the value and validates the field ``` `meta: FieldMeta` Contains useful information/flags about the field status, should be treated as **read only**. ```ts interface FieldMeta { touched: boolean; // if the field has been blurred (via handleBlur) dirty: boolean; // if the field has been manipulated (via handleChange) valid: boolean; // if the field doesn't have any errors pending: boolean; // if validation is in progress initialValue?: any; // the field's initial value } ``` The `valid` flag on the meta object can be tricky, because by default it stars off with `true` for a few moments, only then it is updated to its proper state. Combining your `valid` flag checks with `dirty` will yield the expected result based on user interaction. **usage** ```js const { meta } = useField('field', value => !!value); meta; // { valid: true, dirty: true, .... } ``` `errors: Ref` A reactive reference containing all error messages for the field, should be treated as **read only** ```js const { errors } = useField('field', value => !!value); errors.value; // ['field is not valid'] ``` `errorMessage: ComputedRef` A computed reference that returns the first error in the `errors` array, a handy shortcut to display error messages ```js const { errorMessage } = useField('field', value => !!value); errorMessage.value; // 'field is not valid' or undefined ``` `resetField: (state?: Partial) => void` Resets the field's validation state, by default it reverts all `meta` object to their default values and clears out the error messages. It also updates the field value to its initial value without validating them. ```js const { resetField } = useField('field', value => !!value); // reset the field meta and its initial value and clears errors resetField(); // reset the meta state, clears errors and updates the field value and its initial value resetField({ value: 'new value', }); // resets the meta state, resets the field to its initial value and sets the errors resetField({ errors: ['bad field'], }); // Marks the field as touched, while resetting its value and errors. resetField({ touched: true, }); // Changes the meta, initial and current values and sets the errors for the field resetField({ value: 'new value', touched: true, errors: ['bad field'], }); ``` Note that it is unsafe to use this function as an event handler directly, check the following snippet: ```vue-html ``` You can also use `handleReset` which is a safe alternative for `resetField`. `setErrors: (errors: string | string[]) => void` Sets then field errors, you can pass a single message string or an array of errors. ```js const { setErrors } = useField('field', value => !!value); // Sets the errors to a single error message setErrors('field is required'); // sets the errors to multiple error messages setErrors(['field is required', 'field must be valid']); // clears the errors setErrors([]); ``` `handleReset: () => void` Similar to `resetField` but it doesn't accept any arguments and can be safely used as an event handler. The values won't be validated after reset. ```js const { handleReset } = useField('field', value => !!value); // reset the field validation state and its initial value handleReset(); ``` `validate: () => Promise<{ errors: string[] }>` Validates the field's current value and returns a promise that resolves with an object containing the error messages emitted by the various rule(s). ```js const { validate } = useField('field', value => !!value); // trigger validation await validate(); ``` `handleChange: (evt: Event | any) => void` Updates the field value, and validates the field. Can be used as an event handler to bind on the field. If the passed argument isn't an event object it will be used as the new value for that field. It sets the `dirty` meta flag to `true` ```vue ``` `handleBlur: (evt: Event | any) => void` Sets the `touched` meta flag to `true` ```vue ``` `setTouched: (isTouched: boolean) => void` Sets the `touched` meta flag for this field, useful to create your own `blur` handlers ```js const { setTouched } = useField('field', value => !!value); // mark the field as touched setTouched(true); ``` `checked: ComputedRef | undefined` A computed property that indicates if the field should be checked or unchecked, only available if `type=checkbox` or `type=radio` in field options. Useful if you are creating [custom checkboxes](/examples/custom-checkboxes). ```js const { checked } = useField('field', ..., { type: 'checkbox', valueProp: 'Checkbox value' }); checked.value; // true or false ``` For more information on how you might use the `checked` property, check the [custom checkboxes example](/examples/custom-checkboxes). ## Additional options To pass additional configuration to your fields you can use more options. ```vue ``` | Field options | | -------------------------------------- | | `type` | | `label` | | `initialValue` | | `validateOnMount` | | `bails ` | | `standalone` | | `validateOnValueUpdate` | | `keepValueOnUnmount` | | `syncVModel` | | `checkedValue` (checkbox/radio only) | | `uncheckedValue` (checkbox/radio only) | ================================================ FILE: docs/src/pages/api/use-form.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: useForm description: API reference for the useForm composition API function menuTitle: useForm() order: 6 --- import DocTip from '@/components/DocTip.vue'; import CodeTitle from '@/components/CodeTitle.vue'; import DocBadge from '@/components/DocBadge.vue'; # useForm `useForm` is a custom composition API function that allows you to group fields created by `useField` and aggregates their state. It should be used to create logical forms or custom form components similar to the `
` component which is just a consumer of `useForm`. Note that you can only have one `useForm` per component. See this issue: https://github.com/logaretm/vee-validate/issues/4550 ## Field Types The `useForm` function has field typing capabilities if you need it, getting type information for your fields and their values can be very powerful when building complex forms. By unlocking the field type you automatically get more strict information for the various properties/methods exposed by `useForm` like `setErrors` and `setTouched`. There are two ways you can get the advanced typing information for your fields, the first is to provide a generic type to `useForm`. ```ts import { useForm } from 'vee-validate'; interface LoginForm { email: string; password: string; } // in your setup const { errors } = useForm(); ``` Or simply provide initial values to `useForm` and it will automatically pick up the type of `initialValues` and use it for the field types. ```ts import { useForm } from 'vee-validate'; const { errors, setErrors, setFieldValue } = useForm({ initialValues: { email: '', password: '', }, }); ``` Alternatively, you can use a [standard schema](https://standardschema.dev/) to infer the form types from the validation schema if you are using yup or zod. Whichever approach you prefer, you get full type information for your fields for all the functions exposed by `useForm`, here are a few examples. ```ts import { useForm } from 'vee-validate'; interface LoginForm { email: string; password: string; } // in your setup const { errors, setErrors, setFieldValue } = useForm(); errors.value; // typed as { email?: string; password?: string } setErrors({ email: 'This field is invalid', // auto-complete for `email` and `password` }); setFieldValue('email', 'example@gmail.com'); // auto-complete for the field name and its value type ``` For example if you were to do this in the previous example: ```ts setFieldValue('age', 5); // ⛔️ TypeScript error setFieldValue('email', 5); // ⛔️ TypeScript error ``` It will error out because `age` is not defined in the `LoginForm` type you defined. The second line errors out because the `email` field is typed as a `string`. ## API Reference ### Arguments `validationSchema?: any` Enables form-level validation, uses the specified schema to validate the fields. The schema can be either valid vee-validate global validators or functions or a yup object schema. `initialValues?: Record` The initial values for the form. ```js const { ... } = useForm({ initialValues: { email: 'example@gmail.com', password: 'p@$$w0rd', } }); ``` `initialErrors?: Record` The initial errors for the fields, useful for non hydrated SSR applications like Laravel, errors are applied on mounted. ```js const { ... } = useForm({ initialErrors: { email: 'This email is invalid', password: 'Password too short', } }); ``` `initialTouched?: Record` The initial touched status for the form fields, applied on mounted. ```js const { ... } = useForm({ initialTouched: { email: true, // touched password: false, // non-touched } }); ``` `validateOnMount?: boolean` If true, it will trigger validation for all fields once the form component is mounted. `name?: string` If set, it will be used as the form's name in the devtools. ```js const { ... } = useForm({ name: 'LoginForm', // Defaults to "Form" }); ``` ### Composable API The following sections documents each available property on the `useForm` composable. `errors: Ref>` An object that maps field names to their error messages, it only takes the first error message of each field if multiple exists. ```js const { errors } = useForm(); errors.value; // access the errors value ``` `errorBag: Ref>` An object that maps field names to all of their error messages. ```js const { errorBag } = useForm(); errorBag.value.email; // email field errors ``` Here is an example of its shape: ```js { email: ["this field is required", "this field must be a valid email"], password: "too short" } ``` Any fields without error messages will not be included in the object. So you can safely iterate over it with `Object.keys()` knowing all the included fields are invalid. `isSubmitting: Ref` Indicates if the submission handler is still running, once it resolves/rejects it will be automatically set to false again. ```js const { isSubmitting } = useForm(); isSubmitting.value; // true or false ``` `isValidating: Ref` Indicates if the validate function is still running, once validate function is done it will be automatically set to `false` again. ```js const { isValidating } = useForm(); isValidating.value; // true or false ``` `meta: ComputedRef` A computed property that contains an aggregated meta information/flags reflecting the state of all the fields inside the form. ```ts interface FormMeta { touched: boolean; // if at least one field is touched (was blurred) dirty: boolean; // if at least one field is dirty (manipulated) valid: boolean; // if the form doesn't have any error messages pending: boolean; // if at least one field is pending validation initialValues?: Record; // a map of the form's initial values } ``` **usage** ```js const { meta } = useForm(); meta.value; // { valid: false, invalid: true, dirty: true, .... } ``` `values: Record` A reactive property that contains the current form values, you should not try to mutate it directly. ```js const { values } = useForm(); values; // { email: 'something@gmail.com', .... } ``` `setFieldError: (field: string, message: string | undefined) => void` Sets a field's error message, useful for setting messages form an API or that are not available as a validation rule. Setting the message to `undefined` or an empty string clears the errors and marks the field as valid. ```js const { setFieldError } = useForm(); setFieldError('email', 'this email is already taken'); // Mark field as valid setFieldError('email', undefined); ``` If you try to set an error for a field that doesn't exist, it will be added to the form's errors object and it will change the form's `valid` state `setErrors: (fields: Record) => void` Sets multiple fields error messages, uses `setFieldError` internally. ```js const { setErrors } = useForm(); setErrors({ email: 'this email is already taken', password: 'someone already has this password 🤪', firstName: undefined, // clears errors and marks the field as valid }); ``` Any missing fields you didn't pass to `setErrors` will be unaffected and their state will not change `setFieldValue: (field: string, value: any) => void` Sets a field's value, if a field does not exist it will not be reflected in the `values` ref. This will trigger validation on the field whose value changed. ```js const { setFieldValue } = useForm(); setFieldValue('email', 'example@gmail.com'); ``` If you try to set a value for a field that doesn't exist, it will be added to the form's values object and will stay there until the next `resetForm` is called. `setValues: (fields: Record) => void` Sets all fields values, will trigger validation for the changed fields. ```js const { setValues } = useForm(); setValues({ email: 'example@gmail.com', password: 'p@a$$W0rD', }); ``` `setFieldTouched: (field: string, isTouched: boolean) => void` Sets a field's `touched` meta flag, if you set it for a non-existing field it will have no effect. ```js const { setFieldTouched } = useForm(); setFieldTouched('email', true); ``` `setTouched: (fields: Record) => void` Sets multiple fields `touched` meta flag, does not validate. ```js const { setTouched } = useForm(); setTouched({ email: true, password: false, }); ``` `validate: () => Promise<{ valid: boolean; errors: Record}>` Validates all the fields and populates the `errors` object, returns a promise that resolves to an object containing aggregated validation result of all fields. ```js const { validate } = useForm(); const { valid, errors } = await validate(); ``` `validateField: (field: string) => Promise<{ valid: boolean; errors: string[] }>` Validates a specific field inside the form, returns a promise that resolves to an object containing the validation result. ```js const { validateField } = useForm(); const { valid, errors } = await validateField('email'); ``` `handleSubmit: (cb: SubmissionHandler) => (evt?: Event) => Promise` This is a higher order function used to create `submit` event handlers, You shouldn't use it as a handler for the events directly but rather use it to create those handlers. The handlers created using this function will automatically prevent form submission and stop the propagation of the submit event. It accepts a function which runs after validating the form and if all fields are valid. The callback you pass will receive the form values as the first argument, which is an object containing the fields' values. ```vue ``` For advanced forms, you may need to trigger various actions on the form in the `submit` handler. Your callback receives a `FormActions` object as part of the second argument along with the event object that triggered the submission if available. ```js const { handleSubmit } = useForm(); const onSubmit = handleSubmit((values, actions) => { // Send data to API alert(JSON.stringify(values, null, 2)); // the form object contains useful methods // set a single field value actions.setFieldValue('field', 'hello'); // set multiple fields values actions.setValues({ email: 'value', password: 'hi' }); // set a single field error actions.setFieldError('field', 'this field is bad'); // set multiple fields errors actions.setErrors({ email: 'bad email', password: 'bad password' }); // reset the form actions.resetForm(); }); ``` `handleSubmit` contains a `withControlled` function that you can use to only submit fields controlled by `useField` or `useFieldModel`. Read the [guide](/guide/composition-api/handling-forms) for more information. ```vue ``` You can use `handleSubmit` to submit **virtual forms** that may use `form` elements or not. As you may have noticed the snippet above doesn't really care if you are using forms or not. `submitForm: (evt: Event) => void` Unlike `handleSubmit` this function can be used as an event handler for form `submit` event, it will prevent the propagation and submission of the forms using it as long as they are invalid. Once all the fields are valid it will submit the form with the native HTML behavior following the `form` element's `action` and `method` attributes. This is useful if you plan to handle form submissions using a backend API like Laravel or whatever. ```vue ``` `submitCount: number` The number of submission attempts by the user, it increments whenever `submitForm` or `handleSubmit` callback are called. `resetForm: (state?: Partial, opts?: ResetFormOpts) => void` Clears error messages, resets the meta state for all fields and reverts their values to their initial state as well as the `submitCount` state. Accepts an optional object containing the new form state, useful if you need to reset the form values to different values other than their initial state. This is the `FormState` interface: ```ts type TouchedFlags = { [k: string]: boolean }; interface FormState { // any error messages errors: Record; // touched meta flags touched: TouchedFlags; // Form Values values: Record; // The form submit attempts count submitCount: number; } ``` In this example, the `resetForm` function is updating the fields current values to the ones provided, these values will be used as the new initial values for future `resetForm` or `handleReset` calls. This also applies if the `Field` component or `useField` used their individual `resetField` function. ```js const { resetForm } = useForm(); // ... function onSubmit(values) { // send values to the API // ... // Reset the form values resetForm({ values: { firstName: '', lastName: '', email: '', password: '', }, }); } ``` By default `resetForm` merges the previous initial values with the new one provided, meaning only the provided ones will be overwritten. You can overwrite all the fields by passing `force: true` in the second argument. ```js const { values, resetForm } = useForm({ initialValues: { fname: '123', lname: '456' }, }); // values: { fname: 'test', lname: '456' } resetForm({ values: { fname: 'test' } }); // values: { fname: 'test' } resetForm({ values: { fname: 'test' } }, { force: true }); ``` `handleReset: () => void` Clears error messages, resets the meta state for all fields and reverts their values to their initial state as well as the `submitCount` state. you can use this function as handler for the `reset` events on native form elements. ```vue ``` `useFieldModel` This is deprecated, please use `defineField` instead. This creates a bindable two-way model value for the specified fields, there are a couple of signatures for `useFieldModel`. Must be called in the `setup` function. `useFieldModel` accepts either a single field path or multiple via an array. You can use either root field paths or nested paths like `some.user.path` with dot notation. ```vue ``` `defineInputBinds` This is deprecated, please use `defineField` instead. This creates a bindable object for the specified field. The bindable object only works with native HTML input elements, for components use `defineComponentBinds` instead. The `defineInputBinds` must be called in the `setup` function. `defineInputBinds` accepts a single field path, You can use either root field paths or nested paths like `some.user.path` with dot notation. ```vue ``` `defineComponentBinds` This is deprecated, please use `defineField` instead. This creates a bindable object for the specified field. The bindable object only works with components, for native HTML input elements use `defineInputBinds` instead. The `defineComponentBinds` must be called in the `setup` function. `defineComponentBinds` accepts a single field path, You can use either root field paths or nested paths like `some.user.path` with dot notation. ```vue ``` `defineField` This function returns a model and a props/attributes pair. The `defineField` must be called in the `setup` function. `defineField` accepts a single field path, You can use either root field paths or nested paths like `some.user.path` with dot notation. ```vue ``` This function also works with custom components. You can configure the props/attributes that are bound to the component by passing a second argument to `defineField`. You can use this configuration to adjust the `field` validation behavior and more with the individual elements you are using or the component library of your choice. Check the [UI library examples](/examples/ui-libraries) for live examples. ```ts const [field, props] = defineField('field', { // A getter that computes and adds any additional props to the component // It receives the current field state as an argument props(state) { // This is just an example, by default this is an empty object return { 'aria-invalid': state.errors.length > 0 ? 'true' : 'false', }; }, // A label for the field, only used with global validation rules label: 'a label', // Validates when `blur` event is emitted from the element/component validateOnBlur: true, // Validates when `change` event is emitted from the element/component validateOnChange: true, // Validates when `input` event is emitted from the element/component validateOnInput: false, // Validates when the returned model value changes validateOnModelUpdate: true, }); ``` You can also have a lazy configuration by passing a function that returns the configuration object. The only difference is `props` is now a plain object instead of a getter function. This means you can change any of the configuration based on the field state: ```ts const [field, props] = defineField('field', state => { return { // A getter that computes and adds any additional props to the component props: { // This is just an example, by default this is an empty object 'aria-invalid': state.errors.length > 0 ? 'true' : 'false', }, // Validates when `blur` event is emitted from the element/component validateOnBlur: true, // Validates when `change` event is emitted from the element/component validateOnChange: true, // Validates when `input` event is emitted from the element/component validateOnInput: false, // Validates when the returned model value changes validateOnModelUpdate: true, }; }); ``` ================================================ FILE: docs/src/pages/examples/array-fields.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Array Fields description: How to implement array fields in vee-validate order: 5 --- import LiveExample from '@/components/LiveExample.vue'; # Array Fields This example shows how to implement array fields in vee-validate ================================================ FILE: docs/src/pages/examples/async-validation.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Async Validation description: Using async validation with vee-validate order: 3 --- import LiveExample from '@/components/LiveExample.vue'; # Async Validation This is an example of field that uses an async rule to check if an email is available. ================================================ FILE: docs/src/pages/examples/checkboxes-and-radio.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Checkbox and Radio Inputs description: Validating checkboxes and Radio inputs order: 1 --- import DocTip from '@/components/DocTip.vue'; import LiveExample from '@/components/LiveExample.vue'; # Checkbox and Radio Inputs The documentation has so far avoided using `type="radio"` and `type="checkbox"` inputs because of their complex nature, however vee-validate supports both HTML checkboxes and radio inputs as well as your custom components that act as such (with caveats). The only requirements are that the fields:
- Must be inside a `Form` component or a [derivative (using useForm)](/api/use-form#creating-custom-form-components) - Must Have the same `name` prop - Should have a `type` attribute
When using `Field` slot props with checkbox/radio components, you still need to provide the `type` and `value` props to the `Field` node itself. ```vue-html ``` The same caveat applies if you are rendering another component with `as` prop: ```vue-html ``` ## Validating Radio Inputs vee-validate handles radio input groups as long as they have the `type="radio"` and the same `name` prop value. The selected value will be present in the `values` object. ## Validating Checkbox Inputs vee-validate handles checkboxes as long as they have the `type="checkbox"` prop and the same `name` prop value. The selected values will be collected in an array similar to what `v-model` does. If there is only one checkbox then its value will be directly assigned in the `values` object without binding it in an array. ================================================ FILE: docs/src/pages/examples/cross-field-validation.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Cross-Field Validation description: Validating input values depending on other inputs' values order: 4 --- import LiveExample from '@/components/LiveExample.vue'; # Cross-Field Validation This example shows how to create a password-confirmation-like rules using either `yup` or global validators. ================================================ FILE: docs/src/pages/examples/custom-checkboxes.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Custom Checkboxes description: Creating validatable checkbox inputs order: 6 --- import DocTip from '@/components/DocTip.vue'; import LiveExample from '@/components/LiveExample.vue'; # Custom Checkboxes Checkboxes are more complex than regular text inputs, because there is always two states to keep track of which can be confusing at first. Mainly you should keep track of: - Each individual input's value - Which inputs are current selected To make this easier, remember that native checkboxes behave as a group while each input maintains it's own value. Then a checkbox said to be checked when either: - It's value is the one currently selected - It's value is included in the ones selected This is because of the nature of checkboxes behaving as **"multi-value"** form inputs, so you need to keep track of each input's value and the currently selected one(s). With all of that in mind, vee-validate offers simple abstractions for checkboxes. You can build your own checkboxes with vee-validate's `useField` function which gives you the full capabilities of validation in a composable fashion. Because `useField` isn't aware of what kind of input will be composed with it, you will need to specify that the input is of type `checkbox` and pass a `checkedValue` as well which represents that single field's value. By doing so, you gain access to `checked` prop which tells you if the checkbox should be selected. ```js import { useField } from 'vee-validate'; export default { props: { // The group's value modelValue: { type: null, }, // Field's own value value: { type: null, }, name: { type: String, }, rules: { type: String, default: undefined, }, }, setup(props) { // We pass a function to make sure the name stays reactive const { checked, handleChange } = useField(() => props.name, props.rules, { // 👇 These are important type: 'checkbox', checkedValue: props.value, }); // select/unselect the input handleChange(props.value); return { checked, // readonly handleChange, }; }, }; ``` vee-validate handles some of the complexities of the checkbox inputs nature, by default if a checkbox field is registered it will be treated as a single input until another checkbox with the same name is registered. Then they will be treated as a group, and their values will affect the group value when they are selected or not. Here is a live example of a custom checkbox input: You can also specify a custom `uncheckedValue` which sets the input value to when it is unchecked. ```js const { checked, handleChange } = useField('toa', undefined, { // Will make the checkbox set its value to true/false if it was checked or not type: 'checkbox', checkedValue: true, uncheckedValue: false, }); ``` ================================================ FILE: docs/src/pages/examples/custom-inputs.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Custom Inputs description: Building custom inputs with useField() order: 2 --- import LiveExample from '@/components/LiveExample.vue'; # Custom Inputs More often than not, you will have complex requirements for your inputs. Like a custom design, multiple UI states, validation and more. You can easily build your custom components with `useField()` composable function. While it is possible to build your inputs with `` component it is not recommended as you won't have access to many of the underlying features to manage your UI state and you would rely on scoped slot props which is only available in the template. `useField` allows you to hook into the many underlying features of the `Field` component in a composable manner that you can easily integrate it in your UI, and on top of that your custom components will work seamlessly with `` component or `useForm` without any intervention on your part. In the following example we have a complex `TextInput` component that we use for many types of fields. ================================================ FILE: docs/src/pages/examples/dynamic-validation-triggers.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Dynamic validation triggers description: Dynamic interaction for your inputs order: 11 --- import LiveExample from '@/components/LiveExample.vue'; # Dynamic validation triggers In previous versions of vee-validate you could configure what events trigger the validation based on the field state. This feature was called "interaction modes". In vee-validate v4 this feature was removed because it is now possible to implement it with the composition API. This example should help you figure out how to migrate this feature from older vee-validate versions. ================================================ FILE: docs/src/pages/examples/multistep-form-wizard.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Multi-step Form Wizard description: a multi-step form wizard order: 7 --- import LiveExample from '@/components/LiveExample.vue'; # Multi-step Form Wizard These examples showcases a simple multi-step form (form wizard), with `next` and `previous` step navigation. ## Higher Order Components This example uses the higher-order components only. ## Composition API This example uses the composition API to construct a system of `` and `` components to make building form steps easier. ================================================ FILE: docs/src/pages/examples/ui-libraries.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Popular UI Libraries description: Validating popular Vue.js UI libraries with examples order: 10 --- import LiveExample from '@/components/LiveExample.vue'; # UI Libraries Examples Most popular Vue.js UI libraries come with their own built-in form logic and some may tackle a lot of what vee-validate tackles. If you find yourself not satisfied with said library's logic, you can add vee-validate power up these components. vee-validate is UI-agnostic, it doesn't offer any special treatment for the elements/components as long as they emit the right events. Integrating vee-validate can be different for each UI library, it mostly depends on the library's ability to outsource the form logic to 3rd party logic and how it tracks the form field values. In the next few examples you will find examples on how to do that in various ways with the most popular Vue.js libraries in no particular order. ## Shadcn Vue > Beautifully designed components that you can copy and paste into your apps. Accessible. Customizable. Open Source. ShadCN has an official guide on how to use vee-validate with their components, check it out [here](https://www.shadcn-vue.com/docs/components/form). ## Vuetify > [Vuetify](https://vuetifyjs.com/en/) is a no design skills required UI Library with beautifully handcrafted Vue Components. Check their [official example here](https://vuetifyjs.com/en/components/forms/#vee-validate). ## PrimeVue > [PrimeVue](https://primevue.org/) is a rich set of open source UI Components for Vue. Check their [official example here](https://primevue.org/inputtext/#veevalidate). ## Quasar [Quasar framework](https://quasar.dev/) aims to: > Effortlessly build high-performance & high-quality Vue 3 user interfaces in record time ## Element Plus > [Element Plus](https://element-plus.org/#/en-US), a Vue 3.0 based component library for developers, designers and product managers ## Headless UI > [Headless UI](https://headlessui.dev/), a completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS. ## Ionic Framework > [Ionic Framework](https://ionicframework.com/), An open source mobile toolkit for building high quality, cross-platform native and web app experiences. This example is originally forked from another example created by [Aaron Saunders](https://twitter.com/aaronksaunders). ## Ant Design > [Ant Design](https://next.antdv.com/) provides plenty of UI components to enrich your web applications, and we will improve components experience consistently. ## Nuxt UI > [Nuxt UI](https://ui.nuxtlabs.com/) is a Fully styled and customizable components for Nuxt.js ## Naive UI > [Naïve UI](https://www.naiveui.com/en-US/os-theme) Fairly Complete, Theme Customizable, Uses TypeScript, Fast, Kinda Interesting ## Bootstrap Vue > With [BootstrapVue](https://bootstrap-vue.org/) you can build responsive, mobile-first, and ARIA accessible projects on the web using Vue.js and the world's most popular front-end CSS library ## Request a UI library Can't find your favorite UI library here? You can request adding an example for it by creating an [issue here](https://github.com/logaretm/vee-validate/issues/new/choose) and we will consider adding it here. ================================================ FILE: docs/src/pages/examples/using-stores.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: State Stores description: using vee-validate with state stores order: 8 new: true --- import DocTip from '@/components/DocTip.vue'; import LiveExample from '@/components/LiveExample.vue'; # Stores If you want to integrate vee-validate with state management solutions you can do that with the composition API. ## Pinia [Pinia](https://pinia.esm.dev/) is a data store for Vue.js and it is the recommended solution to your Vue.js state management. The example integrates a form state into the store by utilizing a setup function when defining a store. This makes vee-validate act as a state provider for the form where the form values become your store state and submit function becomes your store action. Using `useForm` inside Pinia stores may cause unwanted behavior as lifecycle hooks only execute against the component that first initialized the store. ================================================ FILE: docs/src/pages/examples/value-formatting.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Value formatting and Masks description: using vee-validate with masked and formatted inputs order: 9 new: true --- import LiveExample from '@/components/LiveExample.vue'; # Value Formatting and Masks Some inputs may have a mask applied to them, that is their value is displayed in a different way than the actual value to make it more friendly towards the user. For example, a currency input could insert a thousand separator to make it easier for the user to enter and read large numbers. ```sh # hard to read 10000000 # Much easier 10,000,000 ``` It can be confusing as to how would you run your numeric validation rules on such inputs. The following examples show you how to handle such inputs by separating the "display value" from the actual value. ## Currency Format Example The following example integrates [`vue-currency-input`](https://github.com/dm4t2/vue-currency-input) using the composition API. While it is still possible to do it with the `` component, it is significantly easier to work with the composition API in this case. The main key to getting this right is to sync the value in multiple formats, the formatted one, and the non-formatted value. Ideally, vee-validate should be synced with the non-formatted one, you can do this by updating the value manually using `setValue` or `handleChange`. ================================================ FILE: docs/src/pages/guide/components/handling-forms.mdx ================================================ --- layout: ../../../layouts/PageLayout.astro title: Handling Forms description: Handling form state and submissions order: 3 next: path: guide/components/nested-objects-and-arrays title: Nested Objects and Arrays description: Structuring forms in nested paths in objects or arrays --- import DocTip from '@/components/DocTip.vue'; # Handling Forms vee-validate offers many helpers to handle form submissions, resets, and DX to make your forms much easier to reason about and less of a burden to maintain. The `Form` component handles the following form cases:
- Submitting forms with JavaScript listeners (AJAX) - Submitting forms using the classic/native approach (page reload) - Handling form resets
## Form Values So far you probably noticed we didn't use `v-model` once in the examples. This is because in most cases you don't need the model values until you submit them to your API or not at all if you are submitting an HTML form without JavaScript. Having to create models just to be able to reference them later is redundant and vee-validate goes around this by creating an internal model for the `` field component instances and tracks them and keeps them in sync with the input. You can still use `v-model` if you need it but vee-validate doesn't require it. You may access your form's values using the `values` scoped slot prop on the `Form` component: ```vue ``` You will rarely need to access the form values inside the template, but it is there if you ever need it. What's interesting is that vee-validate follows the assumption that most likely you will need the form values at the submission phase. So if you were to add a `submit` handler on the `` component, vee-validate will automatically pass the form values to your handler as the first argument. ```vue ``` ## Handling Submissions vee-validate exposes useful defaults to help you handle form submissions whether you submit them using JavaScript or native HTML submissions, in most cases, you would like to make sure all your fields are valid before you submit the form, this is done for you by default. Consider the last example, if you tried submitting the form, it won't be submitted unless all fields are valid. When rendering a form using the `as` prop, vee-validate will automatically listen for the `submit` event and prevent the execution of any `submit` listener you may have on the form. If you have a `submit` listener on the `Form` component, vee-validate assumes you will be handling submissions via JavaScript (AJAX) and automatically calls `event.preventDefault()` for you and once the form is finished validating and turned out to be valid it will pass all the values of the `` components. But in the case when you don't have a `submit` listener on your form, vee-validate assumes that the form will be submitted using the native HTML submission that causes the page to "reload". However vee-validate will make sure the form is not submitted unless all fields are valid, here is an example: ```vue-html ``` ## Manually Handling Submissions If you have complex markup requirements in your forms, you can use any of those `
` component slot props: - `handleSubmit`: automatically prevents native submission at all times, use for AJAX submissions - `submitForm`: prevents native submissions as long as the form is invalid, use for native submissions - `validate`: triggers validation on all fields belonging to the form ### Using handleSubmit The `handleSubmit` slot prop is probably the most common method you will use to handle form submissions manually, it accepts a callback that will be executed with the form values if the form is valid. ```vue ``` ### Using submitForm Alternatively if you plan to submit forms natively which will cause a page "reload" then use `submitForm` as an event handler: ```vue ``` This will prevent submitting the form until all fields are valid. ### Using validate() You can validate the form without submissions using the `validate()` slot prop function: ```vue-html ``` That form doesn't render a `form` tag, so vee-validate doesn't handle submissions for that group of fields. But you can still validate them using the `validate` function present on the `Form` component slot props. ## Submission Progress Quite often you need to show your users a submission indicator, or you might want to disable the submit button entirely until the submission attempt is done. The `Form` component offers an `isSubmitting` slot prop that you can use to show such UI indicators. The `isSubmitting` state will be set to `true` once the validation of the form starts (as a result of a submit event) and will keep track of the submission handler you passed to either `onSubmit` or until it calls `submitForm`. If the submission handler throws any errors or completes successfully it will be set to `false` afterward. Here is small example:

See the Pen vee-validate v4 isSubmitting by Abdelrahman Awad ( @logaretm) on CodePen.

Note that calling `validate` from the `Form` slot props will not cause the `isSubmitting` state to change, it will only change if either `submitForm` or `handleSubmit` are called or when a submit event is triggered. The `Form` component exposes a `submitCount` state that you can use to track the number of submission attempts done by the user. For more information check the [API Reference](/api/form). ## Handling Invalid Submissions In case you want to perform some logic after a form fails to submit due to validation errors (e.g: focusing the first invalid field), you can listen for the `onInvalidSubmit` event emitted by the `
` component. ```vue ``` Specifying a `onInvalidSubmit` prop or `@invalid-submit` will run your handler if you submit your form using either `handleSubmit` or the regular form submit event but not the `submitForm` function. ## Initial Values Since with vee-validate you don't have to use `v-model` to track your values, the `Form` component allows you to define the starting values for your fields, by default all fields start with `undefined` as a value. Using the `initialValues` prop you can send an object that contains the field names as keys and their values: ```vue ``` Doing so will trigger initial validation on the form and it will generate messages for fields that fail the initial validation. You can still use `v-model` on your fields to define model-based initial values. You can use `validateOnMount` prop present on the `` component to force an initial validation when the component is mounted. Note that **only the non-dirty fields will be updated**. In other words, **only the fields that were not manipulated by the user will be updated**. For information on how to set the values for all fields regardless of their dirty status check the following [Setting Form Values section](#setting-form-values) It's generally recommended that you provide the `initialValues`, this is because vee-validate cannot assume a reasonable initial value for your fields other than `undefined` which may cause unexpected behavior when using a 3rd-party validator that does not deal with `undefined`. ## Setting Form Values You can set any field's value using either `setFieldValue` or `setValues`, both methods are exposed on the `` component scoped slot props, and as component instance methods. You can call them with template `$refs` and for an added convenience you can call them in the submit handler callback. **Using scoped slot props** ```vue-html ``` **Using submit callback** ```vue ``` **Using template `$refs`** ```vue ``` Note that setting any field's value using this way will trigger validation ## Submission Behavior vee-validate does the following when you submit a form rendered by `
` or when calling either `handleSubmit` or `submitForm`: ### Before validation stage - Sets all fields `touched` meta to `true` - Sets `isSubmitting` form state to `true` - Increments the `submitCount` form state by `1` ### Validation stage - Sets form and individual fields meta `pending` to `true` to indicate validation is in progress - Runs the validation function/schema/rule against the current form values asynchronously - Checks for any errors in the validation result - If there are errors then it will skip the next stage and update the validation state (meta, errors) for the form and fields - If there aren't any errors then it will set the `pending` meta flag to `false` and proceed to the next stage ### After validation stage - Calls the `@submit` handler you specified, or calls the `handleSubmit` callback you provided. - After the callbacks in either method finish (it will wait if the result is asynchronous), then it will set `isSubmitting` to `false` Note that there isn't a need to have `isSubmitting` set back to false if you've used `submitForm`, as this submission method will perform a full-page refresh (native forms behavior). ## Handling Resets vee-validate also handles form resets in a similar way to submissions. When resetting the form, all fields' errors and meta flags will be reset to their original state, including the fields' values. Form reset is handled automatically if you are using the `as` prop to render a `form` element, like shown in this example: ```vue ``` Alternatively if you plan to use the scoped slot for complex markup, you can use the `handleReset` slot prop function to trigger the reset manually: ```vue-html ``` ### Resetting Forms After Submit Usually you will reset your forms after a successful submission, the `onSubmit` handler receives an additional `FormActions` object in the second argument that allows you do some actions on the form after submissions, this is the shape of the `FormActions` object: ```ts export interface FormActions { setFieldValue: (field: T, value: any) => void; setFieldError: (field: string, message: string | undefined) => void; setErrors: (fields: Partial>) => void; setValues: (fields: Partial>) => void; setFieldTouched: (field: string, isTouched: boolean) => void; setTouched: (fields: Partial>) => void; resetForm: (state?: Partial) => void; } ``` This is an example of using the form actions object to reset the form: ```vue ``` The `resetForm` accepts an optional `state` object that allows you to specify the new initial values for any of the fields state, this is the shape of the `FormState` object: ```ts interface FormState { // any error messages errors: Record; // dirty meta flags dirty: Record; // touched meta flags touched: Record; // Form Values values: Record; } ``` In the following snippet, `resetForm` is used to update the form values to specific ones other than their original values. This is useful if your receive your form state asynchronously ```js resetForm({ values: { email: 'example@example.com', password: '', }, }); ``` You can also use template `$refs` to reset the form whenever you need: ```vue ``` ## Initial Errors If you are building a non-SPA application it is very common to pre-fill form errors using server-side rendering, frameworks like Laravel make this very easy to do. vee-validate supports filling the errors initially before any validation is done using the `initialErrors` prop which is present on the `
` component scoped slot props. The `initialErrors` property accepts an object containing the field names as keys with their corresponding error message string. ```vue ``` `initialErrors` are applied once the `Form` component is mounted and is ignored after, so any changes to the `initialErrors` props won't affect the messages. See the next section for setting errors manually. ## Setting Errors Manually Quite often you will find yourself unable to replicate some validation rules on the client-side due to natural limitations. For example, `unique` email validation is complex to implement on the client-side, which is why the `` component allows you to set errors manually. You can set messages for fields by using either `setFieldError` which sets an error message for one field at a time, and the `setErrors` function which allows you to set error messages for multiple fields at once. Both functions are available on the `Form` component scoped slot props, and also on the `Form` component instance which enables you to use it with template `$refs`, and also for added convenience on the `submit` event handler since it would be the most common place for its usage. Here are a few snippets showcasing its usage in these various scenarios: **Using scoped slot props (recommended)** ```vue-html ``` **Using submit callback (recommended)** ```vue ``` **Using template `$refs`** ```vue ``` Always try to avoid using the template `$refs` to gain access to the `
` component methods, template `$refs` are designed to be an escape hatch of sorts when all else fails. So treat them as such and don't reach out for template `$refs` if you can help it. ================================================ FILE: docs/src/pages/guide/components/nested-objects-and-arrays.mdx ================================================ --- layout: ../../../layouts/PageLayout.astro title: Nested Objects and Arrays description: Structuring forms in nested paths in objects or arrays order: 4 next: path: guide/global-validators title: Global Validators description: Defining global rules to be used throughout your web app --- import DocTip from '@/components/DocTip.vue'; import DocBadge from '@/components/DocBadge.vue'; # Nested Objects and Arrays vee-validate supports nested objects and arrays, using field name syntax to indicate a field's path. This allows you to structure forms easily to make data mapping straightforward without having to deal with flat form values. ## Nested Objects You can specify a field to be nested in an object using dot paths, like what you would normally do in JavaScript to access a nested property. The field `name` prop acts as the path for that field: ```vue ``` Submitting the previous form would result in the following values being passed to your handler: ```js { "links": { "twitter": "https://twitter.com/logaretm", "github": "https://github.com/logaretm" } } ``` You are not limited to a specific depth, you can nest as much as you like. ## Nested Arrays Similar to objects, you can also nest your values in an array, using square brackets just like how you would do it in JavaScript. Here is the same example as above but in array format: ```vue ``` Submitting the previous form would result in the following values being passed to your handler: ```js { "links": [ "https://twitter.com/logaretm", "https://github.com/logaretm" ] } ``` vee-validate will only create nested arrays if the path expression is a complete number, for example, paths like `some.nested[0path]` will not create any arrays because the `0path` key is not a number. However `some.nested[0].path` will create the array with an object as the first item. ## Avoiding Nesting If your fields' names are using the dot notation and you want to avoid the nesting behavior which is enabled by default, all you need to do is wrap your field names in square brackets (`[]`) to disable nesting for those fields. ```vue ``` Submitting the previous form would result in the following values being passed to your handler: ```js { "links.twitter": "https://twitter.com/logaretm", "links.github": "https://github.com/logaretm" } ``` ## Field Arrays Field arrays are a special type of nested array fields, they are often used to collect repeatable pieces of data or repeatable forms. They are often called "repeatable fields". When dealing with those fields it is better to use `` component which gives you a few helpers you can use to manage the array fields. Here is a small example that shows how easy it is to create a repeatable `URL` field: ```vue ``` The `` component requires being used inside a `Form` component or a `useForm` to be called at its parent tree. ### Field Array Paths When planning to use `` you need to provide a `name` prop which is the path of the array starting from the root form value, you can use dot notation for object paths or indices for array paths. Here are a few examples: _*Iterate over the `users` array:*_ ```vue-html
``` _*Iterate over the `domains` inside `settings.dns` object:*_ ```vue-html
``` _*Iterate over both `users` and `links`:*_ ```vue-html
``` ### Iteration Keys The `FieldArrayEntry` item exposes a `key` property, this property is unique and is auto-generated for you so you can use it as an iteration key. ```vue-html
``` This auto-generated `key` property is very convenient as you no longer have to provide your own unique key for each item. The `key` property is not an index. It is a unique identifier for the array item that is independent of the array index, so you should not be using it to reference field names. ### Array Helpers The `` slot provides the following properties and functions: - `fields`: a **read-only** version of your array field items, it includes some useful properties like `key`, `isFirst` and `isLast`, the actual item value is inside `.value` property. You should use it to iterate with `v-for`. - `push(item: any)`: adds an item to the end of the array. - `insert(idx: number, item: any)`: Inserts an array item at the specified index. - `prepend(item: any)`: adds an item to the start of the array. - `remove(idx: number)`: removes the item with the given index from the array. - `swap(idxA: number, idxB: number)`: Swaps two array elements by their indexes. - `replace(items: any[])`: Replaces the entire array values with the given items. - `update(idx: number, value: any)`: Updates an array item value at the specified index. - `move(oldIdx: number, newIdx: number)`: Moves an array item to a different position within the array. [Read the API reference](/api/field-array) for more information. ## Caveats ### Paths creation and destruction vee-validate creates the paths inside the form data automatically but lazily, so initially, your form values won't contain the values of the fields unless you provide initial values for them. It might be worthwhile to provide initial data for your forms with nested paths. When fields get unmounted like in the case of conditional rendered fields with `v-if` or `v-for`, their path will be destroyed just as it was created if they are the last field in that path. So you need to be careful while accessing the nested field in `values` inside your submission handler or the `Form` component `values` slot prop. Path destruction can be annoying when dealing with multi-step forms or tabbed forms where you want all the values to be available even when the fields are unmounted. You can control this behavior by passing `keepValue` prop to the `` component or you can do it for all the fields by passing `keepValues` to the `
` component. Note that the priority of this configuration follows the field config first then it fallbacks to the form's config. ```vue ``` ### Referencing Errors When referencing errors using `errors` object on the `Form` slot props or the `ErrorMessage` component, make sure to reference the field name in the same way you set it on the `name` prop for that field. So even if you avoid nesting you should always include the square brackets. In other words `errors` do not get nested, they are always flat. ### Nested Fields With Validation Schema Since vee-validate supports [form-level validation](/guide/components/validation#form-level-validation), referencing the nested fields may vary depending on how you are specifying the schema. If you are using yup, you can utilize the nested `yup.object` or `yup.array` schemas to provide validation for your nested fields, here is a quick example: ```vue ``` You can [visit this link](/examples/array-fields) for a practical example using nested arrays. ================================================ FILE: docs/src/pages/guide/components/validation.mdx ================================================ --- layout: ../../../layouts/PageLayout.astro title: Validation description: Field-level and form-level validation and validation behavior and error messages order: 2 next: path: guide/components/handling-forms title: Handling Forms description: Handling form state and submissions --- import DocTip from '@/components/DocTip.vue'; import LiveExample from '@/components/LiveExample.vue'; # Validation vee-validate handles complex validations in a very easy way, it supports synchronous and asynchronous validation and allows defining rules on the field-level or the form level using validation schemas with built-in support for [yup](https://github.com/jquense/yup). You will be using the following components to validate your forms: - A `Field` component represents a single form input and can be used to render any kind of HTML elements or Vue components. - A `Form` component that renders a form. Do not confuse the `` tag with the HTML `` tag. - An `ErrorMessage` component that displays an error message for a field, you don't have to use it as there are many ways to render error messages. Here is the most simple example in action:

See the Pen Basic Example by Abdelrahman Awad ( @logaretm) on CodePen.

From this point forwards, the docs will assume basic knowledge of [Vue's SFC components](https://v3.vuejs.org/guide/single-file-component.html) and will demonstrate examples as such and will be using ES6+ code snippets. So be sure to brush up on these if you haven't already. ## Field-level Validation You can define validation rules for your fields using the `Field` component, your rules can be as simple as a function that accepts the current value and returns an error message. ```vue ``` ### Validating fields with yup [`yup`](https://github.com/jquense/yup) is a very popular, simple and powerful data validation library for JavaScript, you can use it in combination with vee-validate, You can use `yup` to define your validation rules for that field: ```vue{3,10,20} ``` For more information on the `Field` component, read [the API reference](/api/field). ## Form-level Validation vee-validate supports using a validation schema to define all your validations on your fields beforehand so you don't have to define them individually on your fields. Form-level validation is convenient if you are building large forms and want to keep your templates cleaner. A simple validation schema can be an object containing field names as keys and validation functions as the value for those keys: ```vue ``` ### Validation schemas with yup Fortunately there is already a very neat way to build validation schemas for your forms by using `yup`, it allows you create validation objects like this: ```js const schema = yup.object({ email: yup.string().required().email(), name: yup.string().required(), password: yup.string().required().min(8), }); ``` vee-validate has built-in support for yup schemas, You can pass your schemas to the `` component using the `validation-schema` prop: ```vue ``` For more information on the `Form` component, read [the API reference](/api/form). ### Reactive Form Schema You can have reactive form schemas using `computed` if you are looking to create dynamic schemas using either `yup` or a validation object. ```js import * as yup from 'yup'; export default { data: () => ({ min: 6, }), computed: { schema() { return yup.object({ password: yup.string().min(this.min), }); }, }, }; ``` ```vue ``` When the validation schema changes, only the fields that were validated at least once will be re-validated, the other fields won't be validated to avoid aggressive validation behavior. ## Validation Behavior By default vee-validate runs validation in these scenarios: **After field value change** - When a `change` event is dispatched/emitted - value changed externally (model update or others) Note that `input` event is not considered to be a trigger because it would make it too aggressive, you can configure the triggers in the next section to suit your needs. **After Rules change** - Only if the field was validated before via user interaction **After field is blurred** - Field has been blurred (`blur` event was emitted) **After form submissions** - When the form has been submitted with either `handleSubmit` or `submitForm` on the `` component slot props ### Customizing Validation Triggers By default vee-validate adds multiple event listeners to your fields: - **input:** Adds a `handleChange` handler that updates the field value, and it may validate if configured to do so (may update `meta.dirty` flag if the value changed). - **change:** Adds a `handleChange` handler that updates the field value and validates the field (may update `meta.dirty` flag if the value changed). - **blur:** Adds a `handleBlur` handler that updates the `meta.touched` flag. - **update:modelValue** Adds a `handleChange` handler to components emitting the `update:modelValue` event Notice that in all of these, the `handleChange` handler is the only one that triggers validation. You can configure if a handler should validate by using the `configure` helper: ```js import { configure } from 'vee-validate'; // Default values configure({ validateOnBlur: true, // controls if `blur` events should trigger validation with `handleChange` handler validateOnChange: true, // controls if `change` events should trigger validation with `handleChange` handler validateOnInput: false, // controls if `input` events should trigger validation with `handleChange` handler validateOnModelUpdate: true, // controls if `update:modelValue` events should trigger validation with `handleChange` handler }); ``` Note that configuring any of these options to `false` will not remove all the events, they only control if each event triggers a validation check or not. This might not be flexible enough for your needs, which is why you can define the same config per `Field` component instance: ```vue-html ``` Additionally if you need to use different events or have specific needs in mind, you can control which events to listen to by using the scoped-slot `handleChange` prop of the `` component and binding it to the desired event: ```vue-html ``` In addition to those events, you can also validate when the `` or `` components are mounted with `validateOnMount` prop present on both components: ```vue-html ``` You can also specify if a `handleChange` call should trigger validation or not by providing the second argument: ```vue-html ``` When applying `v-bind="field"` to a Vue component, be careful that the listeners will both be applied for Vue and native DOM events, meaning you might trigger validation unintentionally. An example of that could be `input[type="file"]` inputs, because you cannot bind the `value` attribute to a file instance which means two-way binding won't work there. In that case, only listing to handful of events makes more sense: ```vue-html ``` For custom components, it is recommended that you listen to the proper events when using `v-bind` with custom components, the following sample uses `modelValue` events. ```vue-html ``` For 3rd party components you may consult their documentation to figure which events to trigger validation for. Here are a few examples for the common UI frameworks [here](/examples/ui-libraries). ## Displaying Error Messages ### Using the Field slot-props If you intend to use the scoped slot on the `Field` component, you can access `errors` or `errorMessage` on the scoped slot props to render your messages: ```vue-html {{ errors[0] }} {{ errorMessage }} ``` This is convenient if you have a complex markup for your input and would like to keep everything contained within, it also allows you to create input components with built-in validation. ### Using the Form slot-props As you noticed the `
` component gives you access to the `errors` on its scoped-slot props which you can use to display any error messages for any `` within that form: ```vue-html {{ errors.field }} ``` and if you would like, you could display all error messages for your fields by iterating over the `errors` object: ```vue-html
``` ### Using ErrorMessage component You've seen how the `ErrorMessage` works in the previous examples, by default the `ErrorMessage` component renders a `span` but you can specify any kind of HTML element or global component to the `as` prop. ```vue-html
``` The `` component is very flexible and you can customize its render output with scoped slots to build complex messages markup, read the [ErrorMessage API reference](/api/error-message) for more information. ### Custom Field Labels More often than not, your fields will have names with underscores or shorthands which isn't very nice when showing in error messages, for example, you might have specific encoding to your field names because they might be generated by the backend. Ideally, you want to avoid having messages like: ```txt The down_p is required ``` And instead, show something more meaningful to the user ```txt The down payment is required ``` You can do this in two ways depending on which validators you are using (yup or [global validators](/guide/global-validators)). With yup it is very straightforward, you just need to call `label()` after defining your field's validations either in field level or form level: ```js const schema = Yup.object({ email_addr: Yup.string().email().required().label('Email Address'), acc_pazzword: Yup.string().min(5).required().label('Your Password'), }); ``` Here is a live example: If you are interested in how to do the same for global validators check the [i18n guide](/guide/i18n#custom-labels) ## Validation Metadata ### Field-level Meta Each field has metadata associated with it, the `meta` property available on the `` component contains additional information about the field: - `valid`: The current field validity, automatically updated for you. - `touched`: If the field was blurred (unfocused), can be updated with the `handleBlur` function or `setTouched` on the field's slot scope props. - `dirty`: If the field value was updated, you cannot change its value. - `pending`: If the field's validations are still running, useful for long-running async validation. - `initialValue`: The field's initial value, is `undefined` if you didn't specify any. ```vue-html
{{ meta }}
``` This is the typescript interface for a field's meta-object value ```ts interface FieldMeta { dirty: boolean; pending: boolean; touched: boolean; valid: boolean; initialValue: any; } ``` The default value is `undefined` unless specified which may cause unexpected `meta.dirty` results. To get accurate results for the `meta.dirty` flag, you must provide an initial value to your field even if the values are empty. ```vue-html
{{ meta }}
``` To reduce the verbosity of adding a `value` prop to each field, you could provide the `initial-values` prop to your `
` component instead. Since the `meta.valid` flag is initially `true` (because it just means there are no errors yet), it would cause problems if you have a "success" UI state an indicator. To avoid this case you could combine the `valid` flag with either `meta.dirty` or `meta.touched` to get accurate representation: ```vue-html ⛔️ {{ errorMessage }} ✅ Field is valid ``` ### Form-level Meta Forms also have their own `meta` value containing useful information about the form, it is an aggregation of the metadata for the fields inside that form. The form's metadata properties are: - `valid`: The form's validity status, will be `true` if the errors array is empty. After the form is mounted, vee-validate will update the flag to its accurate state - `touched`: If at least one field was blurred (unfocused) inside the form. - `dirty`: If at least one field's value was updated. - `pending`: If at least one field's validation is still pending. - `initialValues`: All fields' initial values, packed into an object where the keys are the field names. ```vue-html
{{ meta }}
``` Here is a similar example where we disable the form's submit button if no value was changed, we will check the `dirty` flag on the form's scoped slot props which should tell us if the form values have changed or not. ```vue ``` Notice that the `initial-values` in the previous example were provided, like mentioned for the `meta.dirty` accuracy for fields, to get accurate results for the `meta.dirty` flag, you must provide initial values to your forms even if the values are empty. Forms `meta.valid` flag is also initially `true` (because it just means there are no errors yet), it would cause problems if you have a "success" UI state or an indicator. To avoid this case you could combine the form's `valid` flag with either `meta.dirty` or `meta.touched` to get accurate representation: ```vue-html
⛔️ {{ errors.email }} ✅ Form is valid ```
================================================ FILE: docs/src/pages/guide/composition-api/caveats.mdx ================================================ --- layout: ../../../layouts/PageLayout.astro title: Caveats and best practices description: Things to watch out for when using the composition API order: 7 menuTitle: Best practices --- # Composition API Caveats When using the composition API, there are a few things that are not clear when first starting to use it. This page will contain these topics and help you understand how to workaround or address them. ## Function Field Names with `useField` You might've noticed that some examples in the docs pass the field name as a function to `useField`: ```js import { useField } from 'vee-validate'; export default { props: { name: String, }, setup(props) { const { value, errorMessage } = useField(() => props.name); }, }; ``` This is mainly because the `props` in Vue.js is a reactive object, meaning if you access or destruct any of its properties they will lose the reactivity aspect. Let's say you did the following: ```js import { useField } from 'vee-validate'; export default { props: { name: String, }, setup(props) { // ❌ Don't do this in custom input components const { value, errorMessage } = useField(props.name); }, }; ``` The implications are that vee-validate is no longer able to tell when the field name changes, which is crucial for syncing values when they do. A common example where field names change frequently is in a array field where `v-for` loops field names use the index or the iterated value to generate the field name ```vue{4} ``` To address this issue, you need to get a reactive reference to the `name` property. Vue offers a few ways to do this, so you can use any of the following methods: ```js import { toRef, toRefs, computed } from 'vue'; // ✅ using a function that returns the name const { value, errorMessage } = useField(() => props.name); // ✅ using `toRef` const { value, errorMessage } = useField(toRef(props, 'name')); // ✅ using `toRefs` const { name } = toRefs(props); const { value, errorMessage } = useField(name); // ✅ using `computed` const name = computed(() => props.name); const { value, errorMessage } = useField(name); ``` ## Destructing composable The composition API examples in the docs have always used the left-hand side destructing syntax when assigning component data with any of the composable functions vee-validate offers. To quickly refresh your memory: ```js const { value } = useField('field'); const { handleChange } = useForm(); const { fields } = useFieldArray('users'); ``` This is required by default, because each of these functions assumes you might never need the entire features each one provides and such each state/function is exposed independently as a `Ref` or a `Function`. This means if you try the following, it won't work as expected when used in your template: ```vue ``` This is because the `setup` function doesn't recursively expose the refs inside of these objects. If you prefer to use the composition API like shown above, then you can fix most of the issues by wrapping the function calls with `reactive()`. ```vue ``` You can read more on that behavior in [Vue.js docs](https://vuejs.org/guide/reusability/composables.html#conventions-and-best-practices). Note that once you wrap the composable calls with `reactive` you no longer can destruct them and preserve the reactivity. ================================================ FILE: docs/src/pages/guide/composition-api/custom-inputs.mdx ================================================ --- layout: ../../../layouts/PageLayout.astro title: Custom inputs description: Field-level and form-level validation and validation behavior and error messages with composition API order: 4 next: path: guide/composition-api/helpers title: Composable Helpers description: Access any field or form state within child components with these composable helpers. intro: | You've learned to build your form, your custom inputs. Now it is a quick one to figure out how to build special components like submission buttons, reset buttons, error displays and more with these composable helpers. --- # Building Custom inputs import DocTip from '@/components/DocTip.vue'; import Repl from '@/components/MdxRepl.vue'; ## Imperative vs Declarative So far we've been using `useForm` to create forms and use `defineField` to create field-binding objects to integrate them with our fields. However, that usually requires a lot of boilerplate code to create the binding object and the field component. For example, this is how a 5-field form looks like with `useForm` and `defineField`: ```js const { defineField } = useForm(); const email = defineField('email'); const firstName = defineField('firstName'); const lastName = defineField('lastName'); const password = defineField('password'); const passwordConfirm = defineField('passwordConfirm'); ``` This can get ugly very quickly especially if you have a lot of field arrays or nested fields involved. This is one of the downsides of using an imperative API, but with `useField` you can switch to declarative API and get rid of all that boilerplate code. `useField` is a composition function that is similar to `useForm`. it makes it easier to create and manage input components. You should use it when creating custom input components and that means you've made the choice that vee-validate will be an integral part of your input component system. ## Creating a custom input component Let's start with a simple example, we will create a `InputText` component that represents a text field. It can be as simple as this: ```vue ``` This works exactly the same way as with `defineField`, but now since you have a vee-validate field component, you can use it directly in any component with a form context and it will just work: Here is a live example: Notice how much of the burden of defining fields went away as soon as switched to the declarative approach. This is where `useField` really shines, but that's just us getting started. Follow along in this guide to make the most out of `useField`. ## Validation All previous examples have used the form's validation schema to validate the individual fields, however, you can also define a validation schema for each field individually. ### Validating with yup You can use Yup schemas to validate fields individually by passing the schema as the second argument to `useField`. ### Validating with Zod You can use [Zod](https://github.com/colinhacks/zod) directly with vee-validate. ### Validating with Valibot You can use [Valibot](https://valibot.dev/) to validate your fields. ### Other validation providers #### Standard Schema vee-validate supports [standard schema](https://standardschema.dev/) libraries, which includes all the previous validation providers and more. #### Validating with global validators Another option is using `@vee-validate/rules` which have been historically bundled with past versions of vee-validate. It includes rules that can be defined globally and then used anywhere using Laravel-like string expressions. You can refer to the full guide on global rules [here](/guide/global-validators). #### Validating with functions Another option is to just use any 3rd party validation tool you prefer. ### Triggers #### Default Behavior By default, vee-validate runs validation whenever the `value` ref changes whether it was bound by a `v-model` or changed in your code: ```js const { value } = useField('fieldName', yup.string().required()); // validation WILL be triggered value.value = 'something'; ``` You can disable that behavior by passing a `validateOnValueUpdate` option set to `false`: ```js const { value } = useField('fieldName', yup.string().required(), { validateOnValueUpdate: false, }); // validation WILL NOT trigger value.value = 'something'; ``` #### Handling Events `useField()` composition function is not concerned with any events, it only validates whenever the `value` ref changes. However, it gives you everything you need to set up your own validation experience. The `useField` function exposes some handler functions, each handling a specific aspect of the validation experience: - `handleChange`: Updates the field value, can be configured to trigger validation or silently update the value - `handleBlur`: Updates the `meta.touched` flag, doesn't trigger validation. ```js const { handleChange, handleBlur } = useField('someField'); ``` In this example, we are validating on the `input` event (when the user types), which would make the validation aggressive: With a slight adjustment we can make our validation lazy by changing the listener to `@change` (validates when the user leaves the control): ```vue-html
{{ errorMessage }}
``` Note that `handleChange` can be called anywhere, not just in the template, and not as just an event handler. You can use it to mutate the field value whenever you want, as an added bonus you can choose if `handleChange` should trigger a validation or not. ```js const { handleChange } = useField('someField'); // validates by default handleChange('new value'); // validates handleChange('new value', true); // Doesn't validate handleChange('new value', false); ``` Let's say you want to validate on `blur` instead. You can use the `handleBlur` in a similar way. The main differences are: - `handleBlur` doesn't mutate the `value` of the field. It only sets the `meta.touched` to `true`. - `handleBlur` does not validate the current value by default, you have to pass `true` as a second argument to trigger validation. With that info in mind, you can validate on `blur` like so: ```vue-html
{{ errorMessage }}
``` As you can see, the `useField` doesn't care which events you use `handleChange` for. This allows for greater flexibility that's not possible with the `` component, not as straightforward at least. Consider this validation experience: - Validate on Change/Blur initially (when the user leaves the control), let's call this lazy mode. - If the field is invalid, switch the validation to validate on input (when the user types), let's call this aggressive mode. - If the field is valid, go back to "lazy" mode, otherwise, be "aggressive". Implementing this requires some knowledge about how the `v-on` (we can bind objects on it) handler works. ```js const { errorMessage, value, handleChange } = useField(() => props.name, undefined, { validateOnValueUpdate: false, }); const validationListeners = { blur: evt => handleBlur(evt, true), change: handleChange, input: evt => handleChange(evt, !!errorMessage.value), }; ``` Then in your template, you can use `v-on` to add your listener object: ```vue-html ``` Here is a full example: ## v-model Support The `useField` function can automatically manage `v-model` integration for you. Usually, you will need to do this in every component you create: ```js const props = defineProps({ modelValue: String, }); const emit = defineEmits(['update:modelValue']); ``` Instead, you can let `useField` do that for you by telling it to enable `v-model` syncing: ```js const props = defineProps({ modelValue: String, }); const { value, errorMessage } = useField('fieldName', undefined, { syncVModel: true, }); ``` Now whenever `value` changes, you will emit an `update:modelValue` event with the new value. This is useful when you want to use `v-model` with your custom input component: You can also use different prop names for the `modelValue`, for example, `v-model:text` can be implemented by passing the model name directly to `syncVModel`. ```js const props = defineProps({ text: String, }); const { value, errorMessage } = useField('fieldName', undefined, { syncVModel: 'text', }); ``` This will emit `onUpdate:text` instead of `onUpdate:modelValue` whenever the `value` changes. ## Displaying Error Messages You've already seen how to display errors with `useForm`. With `useField` you can use `errorMessage` ref: ```js const { errorMessage, value } = useField('fieldName', yup.string().required()); // contains the error message if available errorMessage.value; ``` In addition to this, you can get all errors for the field using the `errors` ref which contains multiple error messages if applicable: ```js const { errors, value } = useField('fieldName', yup.string().required()); // contains an array of error messages, otherwise empty array errors.value; ``` Here is an example where each field displays its entire range of error messages: ### Custom Field Labels More often than not, your fields will have names with underscores or shorthands which isn't very nice when showing in error messages, for example, you might have specific encoding to your field names because they might be generated by the backend. Ideally, you want to avoid having messages like: ```txt The down_p is required ``` And instead show something more meaningful to the user ```txt The down payment is required ``` You can do this in two ways depending on which validators you are using (yup or [global validators](/guide/global-validators)). #### Custom Labels with Yup With yup it is very straightforward, you just need to call `label()` after defining your field's validations either at the field level or form level: ```js const schema = Yup.object({ email_addr: Yup.string().email().required().label('Email Address'), acc_password: Yup.string().min(5).required().label('Your Password'), }); ``` #### Custom Labels with Zod Zod does not have a built-in `label` method, but you can override the default error messages by passing a custom message to the chained validator. ```js const schema = z.object({ email_addr: z.string().email({ message: 'Email Address be a valid email address' }); acc_password: z.string().min(5, { message: 'Password be at least 5 characters long' }); }); ``` If you are interested in how to do the same for global validators check the [i18n guide](/guide/i18n#custom-labels) ## Field-level Meta Each field has metadata associated with it, the `meta` property returned from `useField` contains information about the field: - `valid`: The current field validity, is automatically updated for you. - `touched`: If the field was **touched**, can be updated with `setTouched` on `useField`'s return value. - `dirty`: If the field value was updated, you cannot change its value. - `pending`: If the field's validations are still running, useful for long-running async validation. - `initialValue`: The field's initial value, is `undefined` if you didn't specify any. ```js const { meta } = useField('fieldName'); meta.dirty; meta.pending; meta.touched; meta.valid; meta.initialValue; ``` This is the typescript interface for a field's meta value ```ts interface FieldMeta { dirty: boolean; pending: boolean; touched: boolean; valid: boolean; initialValue: any; } ``` Just like how the form's `meta` is read-only, this is also read-only and you cannot change it directly. Actually, only the `touched` meta value can be mutated using `handleBlur`, all other meta values are automatically updated for you as the field validates or when it changes its value. ```js const { meta, handleBlur } = useField('fieldName'); // updates meta.touched = true handleBlur(); ``` Since the `meta.valid` flag is initially `true` (because it just means there are no errors yet), it would cause problems if you have a "success" UI state as an indicator. To avoid this case you should combine the `valid` flag with either `meta.dirty` or `meta.touched` to get an accurate representation. You will see that in action in the next example. In the following example, we the various meta information flags to style the input with some styling. Notice in the previous example, we passed an `initialValue`, this is because the default field value is `undefined` which may cause unexpected `meta.dirty` results. To get accurate results for the `meta.dirty` flag, you must provide an initial value to your field even if the values are empty. To reduce the verbosity of adding an `initialValue` prop to each field, you could provide the `initialValues` prop to your `useForm` call instead. ## Building checkboxes Checkboxes are a hard type of input to implement, mainly because of the expectations about how it should mutate the form's value. For example, a checkbox can be used to toggle between `true` or `false` values which is common with single checkboxes. But it can also be used in a group to act as a multi-select between multiple options. In that case, it adds or removes its own "checked" value to the group value. This means checkboxes have three states to maintain: - It's own checked value. In HTML this is done with the `value` attribute for native `input[type="checkbox"]` elements. - The current form value and if it is a checkbox group or a single checkbox. This is usually the `modelValue` prop for components. - Whether it's checked or not, if the checked value equals or is in the form value then it is checked. This should be computed based on the previous fact. All of this can be hard to wrap your head around. However, `useField` makes this easy as it already handles the nuances of checkboxes. The `useField` function accepts a `type` option that you can use to tell vee-validate that the input type is a checkbox and also accepts a `checkedValue` option. ```ts const { handleChange, checked } = useField('myCheckbox', undefined, { type: 'checkbox', checkedValue: 'opt1', }); ``` A simple integration with an input element would look like this: Note that we are not using `v-model` with the `value` returned from `useField` here. This is because `handleChange` is better suited for checkboxes as it handles toggling the value on or off and is also aware of the form has other checkboxes so it also handles whether the value should be an array or a single value. You can find a more advanced example of checkboxes on [this page](/examples/custom-checkboxes/). ================================================ FILE: docs/src/pages/guide/composition-api/getting-started.mdx ================================================ --- layout: ../../../layouts/PageLayout.astro title: Getting started description: Field-level and form-level validation and validation behavior and error messages with composition API order: 1 next: path: guide/composition-api/handling-forms title: Handling forms description: Submissions, resets and form state intro: | Now that you've learned how to use forms to define fields, collect their values and validate them. Next, you will learn how to handle submissions and implement advanced patterns with forms. --- import DocTip from '@/components/DocTip.vue'; import DocBadge from '@/components/DocBadge.vue'; import LiveExample from '@/components/LiveExample.vue'; import Repl from '@/components/MdxRepl.vue'; # Getting started vee-validate is built from the ground up with the composition API through a collection of functions, mainly the `useField` and `useForm` functions. Internally the `
` and `` components actually use the composition functions under the hood. Meaning you can create your own custom input and form components and they will be treated the same as `` and `` components. You can mix them together and use a `Form` component with any other custom component that uses `useField` and vice versa. vee-validate supports synchronous and asynchronous validation and allows defining rules on the field level or the form level using validation schemas. vee-validate has first-class support for: - [Standard Schema](https://standardschema.dev/) libraries like zod, valibot, yup and more. - [Global validators](/guide/global-validators) (Laravel-like syntax) through `@vee-validate/rules`. vee-validate has historically been a declarative validation library, and while the composition API changes things a bit, it still follows the same mindset of declarative validation. vee-validate optimizes for building fields and forms, not values. ## When to use composition API While vee-validate offers both declarative components and composition functions to supercharge your forms, it is always up to you to decide which one to use. However, the composition API is easier to integrate and more flexible. You can build custom components with it or integrate it with any UI library. It is generally recommended to use the composition API. On this page, you will learn how to declare forms and how to hook your elements and components into vee-validate forms and achieve value tracking, validation, and more. ## Declaring Forms ### Form context You can declare forms with the `useForm` function exported from the `vee-validate` core package. This is a composition API function that marks the current component as a form. ```vue ``` Calling `useForm` creates a form context in the component and provides it for any child component that injects it. This means you should stick to calling `useForm` once in a component. Creating a form context does a few things: - Acts as a value collector for all the fields you will declare as child components. - Validates the fields and aggregates the errors. - Aggregates the validity, touched, and dirty states of all the fields. ### Field binds With `useForm` declared, you are now ready to integrate the form with your elements and components. vee-validate is agnostic to the UI you are using. You will learn how to associate your components and elements with the form and how to get value collection, validation, and error messages working. ### HTML Inputs `useForm` provides a function called `defineField`. This function accepts a field path and returns a value model and an object containing the bindings for the input element. The field path is a string that represents the path to the field in the form context. For example, if you have a field called `email` in the form context, the field path will be `email`. ```ts const { defineField } = useForm(); const [email, emailAttrs] = defineField('email'); ``` Note that `defineField` generates a pair of values, the first one is the value model and the second one is the attributes/props object. The props object contain attributes or event listeners that are useful to have on the input control or component which enables custom validation triggers and more. Here is a basic example of how to use `defineField` with a simple input element: Notice that as you type in the input, the `values` are automatically updated with the value changes. Let's quickly add a validation schema on the form to see some errors on the form. We will be using `yup` throughout the examples, but you can use `zod` or any other supported validation library you want. To add a `yup` schema or any kind of form schema, you pass it to the `validationSchema` option when calling `useForm`. Naturally, form schemas are almost always an `object` or a `shape` schema. ```ts{1,3-5,8} import * as yup from 'yup'; const schema = yup.object({ email: yup.string().required().email(), }); const { defineField } = useForm({ validationSchema: schema, }); const [email, emailAttrs] = defineField('email'); ``` Here is a full running example: Notice as you type into the input, the validation is then triggered and the errors are populated. By default `defineField` optimizes for aggressive validation, meaning the validation will be triggered whenever the model changes. You can change that behavior. For example, you can make it "lazy" by passing a configuration to `defineField` to disable validation on model updates with `validateOnModelUpdate` config. Now as you type in the field, the input is not immediately validated. You can do more with dynamic configurations. ### Components Similarly to HTML inputs, you can achieve the same results with the same `defineField` function. Following the previous examples we can achieve value tracking like this: As you type into the input, notice that the `values` are being updated. Let's add validation to the previous example: Notice that for components, validations are executed immediately. This is intended because component implementations are not standardized across the Vue ecosystem as there is no guarantee it will emit the same range of events as the native HTML elements. However, you can customize the validation trigger if you know the components you are using are emitting the right events to support the behavior. ### Mapping attributes and props But aside from the attributes and listeners that vee-validate adds to those binding objects, you can also map the attributes and props of the in. This is useful when you want to: - Map the attributes/props to a different name. - Pass new attributes/props to the component/element based on the field state. You can use `props` to include any additional props or attributes you need to add to the component/element. ```ts const { defineField } = useForm({ // ... }); const [email, emailProps] = defineField('email', { props: state => ({ error: state.errors[0] }) }); ``` In the following example, we have a component that accepts an `error` string prop and shows that message if it is not empty. This is common in many UI libraries as they try not to lock you into a specific validation library. The `state` object contains a lot of useful information about the field, it is fully typed so you can explore it with your IDE or visit the [source reference](https://github.com/logaretm/vee-validate/blob/main/packages/vee-validate/src/types/forms.ts#L255-L258) for more information. Notice that `form` also gives you access to `errors` so you can reference them anywhere in the component. ### Dynamic configuration Instead of passing a static configuration object `defineField`, you could pass a function that returns different configuration values. This is useful when you want the configuration to be dynamic based on the field state. Here is an example that shows how to make the validation behavior "eager". Meaning if the field does not have any errors then it will only validate on `change`. But once it is invalid, it validates on each input event, making it "eager" for success. ## Form Schema As you have seen in the previous examples, the `useForm` function accepts a `validationSchema` that is used to validate the form. We've been using yup to define the schema however you can use `zod` or any 3rd-party validators. ### Validating with Yup You can pass yup schemas directly as you've seen previously. ### Validating with Zod You can use [Zod](https://github.com/colinhacks/zod) in a very similar manner to how we've been using yup in the past examples. ```ts import { useForm } from 'vee-validate'; import { z } from 'zod'; const schema = z.object({ email: z.string().nonempty().email(), }); const { errors, values } = useForm({ validationSchema: schema, }); ``` Here is a full example using zod with `useForm`: There is a known issue with Zod's `refine` and `superRefine` not executing whenever some object keys are missing which is common with forms. This is not an issue with vee-validate as it is a design choice in Zod at the moment. Refer to [this issue](https://github.com/logaretm/vee-validate/issues/4338) for explanations and further reading. ### Validating with Valibot You can also use [Valibot](https://valibot.dev/) which is is a schema library with bundle size, type safety and developer experience in mind. It is a great alternative to Yup and Zod if bundle size is a concern. ```ts import { useForm } from 'vee-validate'; import * as v from 'valibot'; const schema = v.object({ email: v.pipe(v.string(), v.email('Invalid email'), v.nonEmpty('required')), }); const { errors, values } = useForm({ validationSchema: schema, }); ``` Here is a full example using valibot with `useForm`: ### Other validation providers and options #### Global Rules Another option is using `@vee-validate/rules` which have been historically bundled with past versions of vee-validate. It includes rules that can be defined globally and then used anywhere using Laravel-like string expressions. You can refer to the full guide on global rules [here](/guide/global-validators). #### Validating with functions Another option is to just use any 3rd party validation tool you prefer, something like [`validator.js`](https://github.com/validatorjs/validator.js/). Here is a quick example: Or you could use any custom function. Schema libraries like zod and yup are great at defining schemas especially with nested values so it is recommended that you use any [standard schema](https://standardschema.dev/) library to get full benefits. As an added bonus, you get full typescript support with any compatible library. ### Dynamic Form Schemas There are a few ways you can create dynamic schemas (reactive) where it changes the validation rules based on some state. The first way to do that is with `computed`. When the validation schema value changes, only the fields that were validated at least once will be re-validated, the other fields won't be validated to avoid aggressive validation behavior. There are other ways depending on which validation library you are using. For example, with `yup` you can achieve the same with `yup.lazy` or `zod.lazy`: ================================================ FILE: docs/src/pages/guide/composition-api/handling-forms.mdx ================================================ --- layout: ../../../layouts/PageLayout.astro title: Handling Forms description: Handling form state and submissions order: 2 next: path: guide/composition-api/nested-objects-and-arrays title: Nested Objects and Arrays description: Structuring form values in nested paths in objects or arrays intro: | So far we've only dealt with flat form values, what about nested objects and arrays? In the next guide you will learn how to use path names to structure your values and nest them declaratively. --- import DocTip from '@/components/DocTip.vue'; import Repl from '@/components/MdxRepl.vue'; # Handling Forms vee-validate makes it easy to handle form submissions, resets, and DX to make your forms much easier to reason about and less of a burden to maintain. The `useForm` function allows you to easily handle:
- Form state (valid/dirty/touched/pending). - Submitting forms and handling invalid submissions. - Handling form resets.
## Form Metadata Forms have a `meta` object value containing useful information about the form, it acts as an aggregation of the metadata for the fields inside that form. ```js const { meta } = useForm(); meta.value.dirty; meta.value.pending; meta.value.touched; meta.value.valid; meta.value.initialValues; ``` - `valid`: The form's validity status, will be `true` if the errors array is empty initially, but will be updated once the form is mounted. - `touched`: If at least one field was blurred (unfocused) inside the form. - `dirty`: If at least one field's value was updated. - `pending`: If at least one field's validation is still pending. - `initialValues`: All fields' initial values, this is an object where the keys are the field names. Here is a simple example where we disable the form's submit button unless a field was touched. ## Handling Submissions vee-validate exposes useful defaults to help you handle form submissions whether you submit them using JavaScript or native HTML submissions. ### JavaScript Submissions (AJAX) To handle submissions, you can use the `handleSubmit` function to create submission handlers for your forms, the `handleSubmit` function accepts a callback that receives the final form values. ```js const { handleSubmit } = useForm({ validationSchema: yup.object({ email: yup.string().email().required(), password: yup.string().min(6).required(), }), }); // Creates a submission handler // It validates all fields and doesn't call your function unless all fields are valid // You can bind `onSubmit` to a form element's submit event, or call it directly to submit the current data. const onSubmit = handleSubmit(values => { alert(JSON.stringify(values, null, 2)); }); ``` Here is an example that makes use of `handleSubmit` to validate before submitting the form. The `handleSubmit` function will only execute your callback once the returned function (`onSubmit` in the example) if all fields are valid, meaning you don't have to handle if the form is invalid in your logic. You can call the returned function either manually or via an event like `@submit` and it will validate all the fields and execute the callback if everything passes validation. As a bonus, when the returned function is used as an event handler (like in the previous example) it will automatically prevent the default submission of the form so you don't need to use the `prevent` modifier like you normally would. ### Full-Page Submissions (non-AJAX) For non-ajax submissions that trigger a full page reload, you can use the `submitForm` function instead of `handleSubmit`. You normally would use this if you are not building a single-page application. In the following example, we submit the form to another tab using the `get` form method. In that case **YOU MUST** use `submitForm` as an event handler for the `submit` event for a native `form` element, otherwise, it would have no effect. ### Handling Invalid Submissions Sometimes you want to perform some logic after a form fails to submit due to validation errors (e.g. focusing the first invalid field), you can pass a callback as the second argument to the `handleSubmit` function. ```js const { handleSubmit } = useForm(); function onSuccess(values) { alert(JSON.stringify(values, null, 2)); } function onInvalidSubmit({ values, errors, results }) { console.log(values); // current form values console.log(errors); // a map of field names and their first error message console.log(results); // a detailed map of field names and their validation results } // This handles both valid and invalid submissions const onSubmit = handleSubmit(onSuccess, onInvalidSubmit); ``` Here is a quick example of how to scroll to and focus the first invalid field after a failed submission attempt. ### Submission Progress Quite often you need to show your users a submission indicator, or you might want to disable the submit button entirely until the submission attempt is done. The `useForm` function exposes an `isSubmitting` ref that you can use. The `isSubmitting` state will be set to `true` once the validation of the form starts (as a result of a submit event) and will keep track of the submission handler you passed to either `onSubmit` or until it calls `submitForm`. If the submission handler throws any errors or completes successfully it will be set to `false` afterward. ```js const { isSubmitting } = useForm(); ``` ### Submit Count The `useForm` function also exposes a `submitCount` ref that you can use to track the number of submissions attempted by the user. The count is incremented regardless of the validation result. ```js const { submitCount } = useForm(); ``` Maybe you want to lock the form if too many attempts were made, or you want to show a message after the first submission attempt. ### Submission Behavior vee-validate does the following when calling submission handlers created by `handleSubmit` or when calling `submitForm` as a result of the user submitting the form. #### Before validation stage - Sets all fields `touched` meta to `true` - Sets `isSubmitting` form state to `true` - Increments the `submitCount` form state by `1` #### Validation stage - Sets form and individual fields meta `pending` to `true` to indicate validation is in progress - Runs the validation function/schema/rule against the current form values asynchronously - Checks for any errors in the validation result - If there are errors then it will skip the next stage and update the validation state (meta, errors) for the form and fields - If there aren't any errors then it will set the `pending` meta flag to `false` and proceed to the next stage #### After validation stage - Calls the `handleSubmit` handler you passed - After the callback finishes (it will wait if the result is asynchronous), then it will set `isSubmitting` to `false` Note that there isn't a need to have `isSubmitting` set back to false if you've used `submitForm`, as this submission method will perform a full-page refresh (native forms behavior). ## Form Values You may have noticed in the earlier examples that you can access all fields' values using the `values` reactive object returned by `useForm`. ```js const { values } = useForm(); ``` The `values` object is read-only and should not be mutated with a `v-model` or by assigning a value to it. This is because all mutations are done through the vee-validate API, this is because mutations to form state need to have a context. For example: ```js const { values } = useForm(); // ❌ Do not do that! values.email = ''; ``` In order for the form UX to be stable, we need to understand why the email value was set to `''`. Was it being reset? Should we run validation again? This is the type of small differences that are a result of vee-validate's design choice based on fields and forms, not values. ### Initial Values Since you don't have to use `v-model` to track your values, the `useForm` function allows you to define the starting values for your fields, by default all fields start with `undefined` as a value. Using the `initialValues` option you can send an object that contains the field names as keys and their values: ```js const { defineInputBinds } = useForm({ initialValues: { email: 'test@example.com', password: 'p@$$w0rd', }, }); ``` It's generally recommended that you provide the `initialValues`, this is because vee-validate cannot assume a reasonable initial value for your fields other than `undefined` which may cause unexpected behavior when using a 3rd-party validator that does not deal with `undefined`. You can reset initial values at [any time using the `resetForm`](#handling-resets) function returned by `useForm`. ### Manually Setting Form Values You can set any field's value using either `setFieldValue` or `setValues` returned by `useForm`. ```js const { setFieldValue, setValues } = useForm(); // Sets a value of a specific field in the form values setFieldValue('fieldName', 'value'); // Merges the given object with the current form values setValues({ fieldName: 'value', }); ``` ### Controlled Values The form values can be categorized into two categories: - Controlled values: values that have a form input controlling them via `defineInputBinds` or `defineComponentBinds` or `useField` or ``. - Uncontrolled values: values that are inserted dynamically with `setFieldValue` or inserted initially with initial values. Sometimes you maybe only interested in controlled values. For example, your initial data contains noisy extra properties from your API and you wish to ignore them when submitting them back to your API. When accessing `values` from `useForm` result or the submission handler you get all the values, both controlled and uncontrolled values. To get access to only the controlled values you can use `controlledValues` from the `useForm` result: ```js const { handleSubmit, controlledValues } = useForm(); const onSubmit = handleSubmit(async () => { // Send only controlled values to the API const response = await client.post('/users/', controlledValues.value); }); ``` Alternatively, for less verbosity, you can create submission handlers with only the controlled values with `handleSubmit.withControlled` which has the same API as `handleSubmit`: ```js const { handleSubmit } = useForm(); const onSubmit = handleSubmit.withControlled(async values => { // Send only controlled values to the API const response = await client.post('/users/', values); }); ``` Here is an example that filters out some noisy initial values when submitting the form using the `withControlled` modifier. ### Setting initial values asynchronously Sometimes your data is fetched asynchronously from an API and you want to set the initial values or the current values after the data is fetched. You can do that by using `resetForm` to set both current and initial data. You could alternatively use `setValues` but note that `setValues` can trigger validation and do not reset the meta state for the fields like `dirty` or `touched`. ## Handling Resets vee-validate also handles form resets in a similar way to submissions. When resetting the form, all fields' errors will be cleared, meta info will be reset to defaults and the values will be reset to their original or initial values. To reset forms you can use the `resetForm` function returned by `useForm`. You can also reset the form to a new state by passing a `FormState` object to the `resetForm` function. You can then set errors, `touched` meta, and the values. ```js const { resetForm } = useForm(); // Resets the form resetForm(); // resetForm({ touched: { email: false, }, errors: { email: 'custom error', }, values: { email: 'newvalue@email.com', }, }); ``` This is the shape of the `FormState` object: ```ts interface FormState { // any error messages errors: Record; // touched meta flags touched: Record; // Form Values values: Record; } ``` Here is an example where a full form is being reset: ### Resetting Forms After Submit Usually, you will reset your forms after successful submission. For convenience, the `onSubmit` handler receives an additional `FormActions` object in the second argument that allows you to do some actions on the form after submissions, this is the shape of the `FormActions` object: ```ts export interface FormActions { setFieldValue: (field: T, value: any) => void; setFieldError: (field: string, message: string | undefined) => void; setErrors: (fields: Partial>) => void; setValues: (fields: Partial>) => void; setFieldTouched: (field: string, isTouched: boolean) => void; setTouched: (fields: Partial>) => void; resetForm: (state?: Partial) => void; } ``` This is an example of using the form actions object to reset the form: ## Errors As you have previously seen in some examples, you have access to `errors` with `useForm` that contains a mapping of each field's path and its error message. ```js const { errors } = useForm(); ``` However, if you want to display multiple errors for your fields then you can use `errorBag` which is a mapping of each field's path and an array of error messages for that field. ```js const { errorBag } = useForm(); ``` Here is an example that displays multiple errors for a field: ### Initial Errors If you are building a non-SPA application it is very common to pre-fill form errors using server-side rendering, frameworks like Laravel and Rails make this very easy to do. vee-validate supports filling the errors initially before any validation is done using the `initialErrors` option. The `initialErrors` option accepts an object containing the field names as keys with their corresponding error message string. ```js useForm({ initialErrors: { email: 'This email is already taken', password: 'The password is too short', }, }); ``` `initialErrors` are applied once the component that called `useForm` is mounted and is ignored after, so any changes to the `initialErrors` props won't affect the messages. ### Setting Errors Manually Quite often you will find yourself unable to replicate some validation rules on the client-side due to natural limitations. For example, `unique` email validation is complex to implement on the client side. So the ability to set errors manually can be useful. You can set messages for fields by using either `setFieldError` which sets an error message for one field at a time, or by using the `setErrors` function which allows you to set error messages for multiple fields at once. Both functions are available as a return value from `useForm`. In the following example, we check if the server response contains any validation errors and we set them on the fields: ```js const { handleSubmit, setFieldError, setErrors } = useForm(); const onSubmit = handleSubmit(async values => { // Send data to the API const response = await client.post('/users/', values); // all good if (!response.errors) { return; } // set single field error if (response.errors.email) { setFieldError('email', response.errors.email); } // set multiple errors, assuming the keys are the names of the fields // and the key's value is the error message setErrors(response.errors); }); ``` Alternatively, you can use the `FormActions` passed as the second argument to the `handleSubmit` callback which contains both functions for convenience: ```js const onSubmit = handleSubmit(async (values, actions) => { // Send data to the API const response = await client.post('/users/', values); // ... // set single field error if (response.errors.email) { actions.setFieldError('email', response.errors.email); } // set multiple errors, assuming the keys are the names of the fields // and the values is the error message actions.setErrors(response.errors); }); ``` Here is an example that sets form errors after submission, usually you will have a backend API that returns the errors: ================================================ FILE: docs/src/pages/guide/composition-api/helpers.mdx ================================================ --- layout: ../../../layouts/PageLayout.astro title: Composition Helpers description: Composition API helpers menuTitle: Helpers order: 5 --- Aside from `useField` and `useForm` and `useFieldArray`, these are a collection of simple functions that you can use to opt-in specific parts of vee-validate features like form state and various actions you can perform on fields and forms. Here are a few examples of what you can build with these functions: - A custom submission progress component - A custom error message component. - A form validity indicators - reset buttons or submit buttons Here is a list of the functions available that you can use: - `useFieldError` Gives access to a single field's first error message - `useFormErrors` Gives access to the entire error bag of the form - `useIsFieldDirty` If a field is dirty - `useIsFormDirty` If the form is dirty (form contains at least one dirty field) - `useIsFieldTouched` If a field is touched - `useIsFormTouched` If the form is touched (form contains at least one touched field) - `useIsFieldValid` If a field is valid - `useIsFormValid` If all fields are **validated and valid** - `useValidateField` Returns a function that validates a specific field - `useValidateForm` Returns a function that validates the entire form - `useResetForm` Resets the form to its initial state - `useSubmitForm` Creates a submission function that validates and submits the form (even if no `form` element is involved) - `useIsSubmitting` If the form is currently submitting - `useIsValidating` If the form is currently validating by validate function - `useSubmitCount` The number of times the user attempted to submit the form - `useFieldValue` Returns a specific fields' current value - `useFormValues` Returns the current form field values For more information about the functions, you can head over to the [API reference and check them out](/api/composition-helpers). ================================================ FILE: docs/src/pages/guide/composition-api/nested-objects-and-arrays.mdx ================================================ --- layout: ../../../layouts/PageLayout.astro title: Nested Objects and Arrays description: Structuring form values in nested paths in objects or arrays order: 3 next: path: guide/composition-api/custom-inputs title: Custom Inputs description: Building input components with the composition API intro: | You've learned a lot about forms so far, but that's not all. Just like how vee-validate makes it easy to build forms, it also offers tools to help you build custom input components with the composition API. Enter `useField()`. --- import DocTip from '@/components/DocTip.vue'; import DocBadge from '@/components/DocBadge.vue'; import Repl from '@/components/MdxRepl.vue'; # Nested Objects and Arrays vee-validate supports nested objects and arrays by using field path syntax to indicate a field's path. This allows you to structure forms easily to make data mapping straightforward without having to deal with flat form values. ## Nested Objects You can specify a field to be nested in an object using dot paths, like what you would normally do in JavaScript to access a nested property. The field's `name` acts as the path for that field in the form values: ```js const { defineInputBinds } = useForm(); const twitterLink = defineInputBinds('links.twitter'); const githubLink = defineInputBinds('links.github'); ``` Submitting the previous form would result in the following values being passed to your handler: ```js { "links": { "twitter": "https://twitter.com/logaretm", "github": "https://github.com/logaretm" } } ``` Here is a live example so you can see it in action. You are not limited to a specific depth, you can nest as much as you like. ## Nested Arrays Similar to objects, you can also nest your values in an array, using square brackets just like how you would do it in JavaScript. ```js const { defineInputBinds } = useForm(); const twitterLink = defineInputBinds('links[0]'); const githubLink = defineInputBinds('links[1]'); ``` Here is the same example as above but in array format: Submitting the previous form would result in the following values being passed to your handler: ```js { "links": [ "https://twitter.com/logaretm", "https://github.com/logaretm" ] } ``` vee-validate will only create nested arrays if the path expression is a complete number, for example, paths like `some.nested[0path]` will not create any arrays because the `0path` key is not a number. However `some.nested[0].path` will create the array with an object as the first item. ## Avoiding Nesting If your fields' names are using the dot notation and you want to avoid the nesting behavior which is enabled by default, all you need to do is wrap your field names in square brackets (`[]`) to disable nesting for those fields. ```js const { defineInputBinds } = useForm(); const twitter = defineInputBinds('[links.twitter]'); const github = defineInputBinds('[links.github]'); ``` Submitting the previous form would result in the following values being passed to your handler: ```js { "links.twitter": "https://twitter.com/logaretm", "links.github": "https://github.com/logaretm" } ``` Here is a live example on that: ## Field Arrays Field arrays are a special type of nested array fields, they are often used to collect repeatable pieces of data or repeatable forms. They are often called "repeatable fields". To set up a repeatable block of the form, you can use `useFieldArray` to help you manage the array values and operations. It requires a form context to be present so `useForm` must be called before you use `useFieldArray` either in parent component or in the same component. ```js import { useForm, useFieldArray } from 'vee-validate'; useForm(); const { remove, push, fields } = useFieldArray('users'); ``` The `useFieldArray` exposes a few properties and functions to help you manage the array fields, the most special one is the `fields` array which contains the array items and represents their current value in the form. You will use `fields` as an iteration source to render the array items. Here is an example of a repeatable block of fields, we mutate the field value with `v-model` directly on the iteration item value. Notice that we used `field.key` as the key of the iteration, this is because vee-validate generates a unique key for each array item so you can rely it on without having to generate them yourself. The `key` property generated on array items is not an index. It is a unique identifier for the array item that is independent of the array index, so you should not be using it to reference field names. ### Field Array Paths When calling `useFieldArray` you need to provide a `name` prop which is the path of the array starting from the root form value, you can use dot notation for object paths or indices for array paths. Here are a few examples: _*Iterate over the `users` array:*_ ```js const { remove, push, fields } = useFieldArray('users'); ``` _*Iterate over the `domains` inside `settings.dns` object:*_ ```js const { remove, push, fields } = useFieldArray('settings.dns.domains'); ``` ### Array Helpers The `` function provides the following properties and functions: - `fields`: a **read-only** version of your array field items, it includes some useful properties like `key`, `isFirst` and `isLast`, the actual item value is inside `.value` property. You should use it to iterate with `v-for`. - `push(item: any)`: adds an item to the end of the array. - `prepend(item: any)`: adds an item to the start of the array. - `insert(idx: number, item: any)`: Inserts an array item at the specified index. - `remove(idx: number)`: removes the item with the given index from the array. - `swap(idxA: number, idxB: number)`: Swaps two array elements by their indexes. - `replace(items: any[])`: Replaces the entire array values with the given items. - `update(idx: number, value: any)`: Updates an array item value at the specified index. - `move(oldIdx: number, newIdx: number)`: Moves an array item to a different position within the array. [Read the API reference](/api/use-field-array) for more information. ## Caveats ### Paths creation and destruction vee-validate creates the paths inside the form data automatically but lazily, so initially, your form values won't contain the values of the fields unless you provide initial values for them. It might be worthwhile to provide initial data for your forms with nested paths. When fields get unmounted like in the case of conditional rendered fields with `v-if` or `v-for`, their path will be destroyed just as it was created if they are the last field in that path. So you need to be careful while accessing the nested field in `values` inside your submission handler. Path destruction can be annoying when dealing with multi-step forms or tabbed forms where you want all the values to be available even when the fields are unmounted. You can control this behavior by passing `keepValueOnUnmount` prop to the `useField` function or you can do it for all the fields by passing `keepValuesOnUnmount` to the `useForm` function. Note that the priority of this configuration follows the field config first then it fallbacks to the form's config. ```js import { useForm } from 'vee-validate'; // keep all values when their fields get unmounted const { values } = useForm({ keepValuesOnUnmount: true, }); ``` ```js import { useField } from 'vee-validate'; // this field value will be removed const field = useField('field', undefined, { keepValueOnUnmount: false, }); ``` ### Referencing Errors When referencing errors using `errors` object returned from the `useForm` function. Make sure to reference the field name in the same way you set it on the `name` argument for that field. So even if you avoid nesting you should always include the square brackets. In other words `errors` do not get nested, they are always flat. Since vee-validate supports `yup` and `zod` schemas, referencing the nested fields may vary depending on how you are specifying the schema. If you are using yup, you can utilize the nested `yup.object` or `yup.array` schemas to provide validation for your nested fields, here is an example: You can [visit this link](/examples/array-fields) for a practical example using nested arrays. ================================================ FILE: docs/src/pages/guide/devtools.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Devtools Plugin description: Using the vee-validate Vue.js devtools plugin order: 7 next: path: guide/testing title: Testing description: Testing form validation in your apps new: true --- import DocTip from '@/components/DocTip.vue'; import DocBadge from '@/components/DocBadge.vue'; # Devtools Plugin vee-validate ships with a Vue.js devtools plugin that allows you to inspect your forms. The terms "devtools plugin" or "plugin" in this page will refer to vee-validate's devtools plugin from now on. The devtools plugin is useful for debugging and inspecting your forms. A common situation is not having any clues on why a form isn't submitting, the devtools plugin exposes to you all of the validation state giving you insight for your forms behavior. Since vee-validate doesn't require any app configuration, the devtools plugin is auto installed when you use `useField` or `useForm` or their component counterpart `` and ``. Note that the plugin won't be installed in the following cases: - You are using the umd builds via CDN - Your `process.env.NODE_ENV` is set to `production` or `test` That means the plugin is only available to the workflows that employ either webpack or vite where `process.env.NODE_ENV` is available. ## The Inspector The devtools plugin adds a new "vee-validate" inspector that allows you to view your form state, at the moment all the properties are read only. To use the vee-validate inspector, switch from the `components` inspector to the `vee-validate` inspector: When working with multiple forms, you can distinguish between them by their name. The name of the form can be set using the `name` prop on the `` component or the `useForm` composable. ## Disabling the plugin If the plugin is causing you issues, you can disable it explicitly from the vue-devtools plugin page. Also please don't forget to report the issue [here](https://github.com/logaretm/vee-validate/issues/new?assignees=&labels=&template=bug_report.yaml). This short video shows how to disable the vee-validate devtools plugin: ================================================ FILE: docs/src/pages/guide/global-validators.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Global Validators description: Defining global rules to be used throughout your web app order: 5 next: path: guide/i18n title: Localization (i18n) description: Localization support with the first party module and other providers --- import LiveExample from '@/components/LiveExample.vue'; import DocTip from '@/components/DocTip.vue'; # Global Validators Sometimes you are building an application that is form-heavy and needs to validate forms frequently, like an admin dashboard or a form-based application. importing validation rules every time you need them can be quite tedious. VeeValidate allows you to define validation rules globally on the app-level which in turn can allow you to express your rules in a minimal syntax that is inspired by the [Laravel framework's validation syntax](https://laravel.com/docs/validation). ## Defining Global Validators You can define a global validator using the `defineRule` function exported by vee-validate: ```js import { defineRule } from 'vee-validate'; ``` The `defineRule` function accepts a rule name that acts as an identifier for that validation rule, the second argument is the validator function that will verify the field value. Here is an example of a simple `required` and an `email` rule: ```js import { defineRule } from 'vee-validate'; defineRule('required', value => { if (!value || !value.length) { return 'This field is required'; } return true; }); defineRule('email', value => { // Field is empty, should pass if (!value || !value.length) { return true; } // Check if email if (!/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}/.test(value)) { return 'This field must be a valid email'; } return true; }); ``` The validator function is a simple function that receives the current field value as the first argument, and it should return: - `true` if the validation passed - `string` if the validation failed and there is an error message to display - `false` if validation fails, in that case, vee-validate will try to generate an error message using `config.defaultMessage` - `Promise` if you have an asynchronous rule, the promise must either resolve to `string` or `true` You should make the `defineRule` calls in your application entry-point file to make sure your forms have access to them, otherwise, you may risk a form not being able to use some global rules because they weren't defined then. Here are some common entry points: - In vue-cli applications that would be the `src/main.js` file - In a Nuxt application, you need to create a `plugins/vee-validate.js` file and register it in `nuxt.config.js` ### Using Global Validators Now that you've defined your validators, for example, the `email` and `required` rules. You can pass them directly to `` component's `rules` prop like this: ```vue-html ``` Notice that to define multiple rules you have to place a `|` (pipe) character between your rules as a separator. ## Configuring Global Validators Sometimes your rules require more information to work properly, for example, if we want to define a `minLength` rule it won't be very useful to hard code the character limit, instead, it should receive it per field. VeeValidate passes rules arguments or (configuration) as the second argument to the validator function which is often an array containing the arguments in the same order they were passed in. Here is an example defining a `minLength` rule: ```js import { defineRule } from 'vee-validate'; defineRule('minLength', (value, [limit]) => { // The field is empty so it should pass if (!value || !value.length) { return true; } if (value.length < limit) { return `This field must be at least ${limit} characters`; } return true; }); ``` And then you can use it on the `Field` component like this: ```vue-html ``` Note that to pass arguments to rules, you need to place a colon `:` character to signify the beginning of rules. You can pass multiple arguments as a comma-separated list. Here is an example for a `minMax` rule: ```js import { defineRule } from 'vee-validate'; defineRule('minMax', (value, [min, max]) => { // The field is empty so it should pass if (!value || !value.length) { return true; } const numericValue = Number(value); if (numericValue < min) { return `This field must be greater than ${min}`; } if (numericValue > max) { return `This field must be less than ${max}`; } return true; }); ``` Then you can use it like this: ```vue-html ``` ## Schema Validation The [Form-level validation](/guide/components/validation#form-level-validation) feature isn't limited to `yup` validators, you can define your global validators and define them in the same way as the previous examples in the `Form` component `validation-schema` prop. Here is an example that uses all the rules we've defined prior in this page in a form validation schema: ```vue ``` This makes vee-validate extremely compatible with Laravel's Request validation and potentially opens the door to make your forms completely API-driven, eliminating the maintenance burden of syncing validation rules between frontend and backend. ## Cross-Field Validation Assuming you want to create a rule that verifies that a field matches another, like a password confirmation scenario. Because globally defined rules do not have scope access to other values you may have in the component, vee-validate sends a third argument to your global validators which is a `ValidationContext` that carries useful information about the form and field being validated. So assuming you want to build a `confirmed` rule, you would make it configurable by accepting the `target` field name and you can use the `ValidationContext.form` object to access its value: ```js import { defineRule } from 'vee-validate'; defineRule('confirmed', (value, [target], ctx) => { if (value === ctx.form[target]) { return true; } return 'Passwords must match'; }); ``` Here is an example of the two fields using that rule: ```vue-html ``` There is a shorthand for this using `@` prefix before specifying arguments, this tells vee-validate to automatically swap the specified field value in the arguments array: ```js import { defineRule } from 'vee-validate'; defineRule('confirmed', (value, [target]) => { if (value === target) { return true; } return 'Passwords must match'; }); ``` ```vue-html ``` This allows you to create more concise rules, you can reference any number of fields using this way. ## Object Expressions There is another way to use global validators which is more expressive by using JavaScript objects to describe the validation for your fields. For example this: ```js 'required|between:0,10'; ``` Could be rewritten as an object like this ```js { required: true, between: [0, 10] } ``` This makes defining rules more expressive and less limited by the string format. For dynamic expressions, you can use computed properties in the same way to define dynamic validation rules. ## @vee-validate/rules VeeValidate offers a wide range of common validators that you can use as global validators, they are packed in a separate package under the `@vee-validate/rules` namespace ```sh yarn add @vee-validate/rules # or with npm npm install @vee-validate/rules ``` You can then start importing and defining rules globally: ```js import { defineRule } from 'vee-validate'; import { required, email, min } from '@vee-validate/rules'; defineRule('required', required); defineRule('email', email); defineRule('min', min); ``` Or you can globally define all the available rules in the `@vee-validate/rules` package: ```js import { defineRule } from 'vee-validate'; import { all } from '@vee-validate/rules'; Object.entries(all).forEach(([name, rule]) => { defineRule(name, rule); }); ``` ## Caveats - Be careful of having too many global rules as this can slow down your initial website load time due to the large initial bundle size - It is recommended to treat your validation rules as pure functions, meaning they only operate with the information given to them - Having small, pure global validations is preferable to allow reusing them across the app - You could possibly trigger an infinite render-loop when using the [object expressions](/guide/global-validators#object-expressions) to define your validations for a field, read the [linked section](/guide/global-validators#object-expressions) for a workaround ## Available Rules ### Playground Here is a live demo where you can view and test all the global validators live #### alpha The field under validation may only contain alphabetic characters. ```vue-html ``` #### alpha_dash The field under validation may contain alphabetic characters, numbers, dashes, or underscores. ```vue-html ``` #### alpha_num The field under validation may contain alphabetic characters or numbers. ```vue-html ``` #### alpha_spaces The field under validation may contain alphabetic characters or spaces. ```vue-html ``` #### between The field under validation must have a numeric value bounded by a minimum value and a maximum value. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | ------------------ | | `min` | **yes** | The minimum value. | | `max` | **yes** | The maximum value. | #### confirmed The field under validation must have the same value as the confirmation field. ```vue-html
``` | Param Name | Required? | Description | | ---------- | --------- | ---------------------------------------------------------------------------- | | `target` | **yes** | The other field's `name` value, must use `@` to prefix the target field name | Any fields defined to have this rule must have a `
` parent in their hierarchy or `useForm()` called on their parent hierarchy. #### digits The field under validation must be numeric and have the specified number of digits. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | ----------------------------- | | `length` | **yes** | The number of digits allowed. | #### dimensions The file added to the field under validation must be an image (jpg,svg,jpeg,png,bmp,gif) having the exact specified dimension. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | --------------------- | | `width` | **yes** | The width in pixels. | | `height` | **yes** | The height in pixels. | #### email The field under validation must be a valid email. ```vue-html ``` #### not_one_of The field under validation must have a value that is not in the specified list. **Uses double equals** for checks. ```vue-html ``` `not_one_of` accepts an infinite number of params, each is a value that is disallowed. #### ext The file added to the field under validation must have one of the extensions specified. ```vue-html ``` `ext` accepts an infinite number of arguments representing extensions. ex: `ext:jpg,png,bmp,svg`. #### image The file added to the field under validation must have an image mime type (image/\*). ```vue-html ``` #### integer The field under validation must be a valid integer value. Doesn't accept exponential notation. ```vue-html ``` #### is The field under validation must match the given value, uses strict equality. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | ----------------------------------------------------------- | | `value` | **yes** | A value of any type to be compared against the field value. | #### is_not The field under validation must not match the given value, uses strict equality. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | ----------------------------------------------------------- | | `value` | **yes** | A value of any type to be compared against the field value. | #### length The field under validation must exactly have the specified number of items, only works for iterables. Allowed Iterable values are `string`, `array`, and any object that can be used with `Array.from`. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | ----------------------------------------------------------------------------- | | `length` | **yes** | A numeric value representing the exact number of items the value can contain. | #### max The field under validation length may not exceed the specified length. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | -------------------------------------------------------------- | | `length` | **yes** | A numeric value representing the maximum number of characters. | #### max_value The field under validation must be a numeric value and must not be greater than the specified value. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | -------------------------------------------------------- | | `max` | **yes** | A numeric value representing the greatest value allowed. | #### mimes The file type added to the field under validation should have one of the specified mime types. ```vue-html ``` `mimes` accepts an infinite number of arguments that are formatted as file types. EG: `mimes:image/jpeg,image/png`. You can use '\*' to specify a wild card, something like `mimes:image/*` will accept all image types. #### min The field under validation length should not be less than the specified length. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | -------------------------------------------------------------- | | `length` | **yes** | A numeric value representing the minimum number of characters. | #### min_value ```vue-html ``` The field under validation must be a numeric value and must not be less than the specified value. | Param Name | Required? | Description | | ---------- | --------- | -------------------------------------------------------- | | `min` | **yes** | A numeric value representing the smallest value allowed. | #### numeric The field under validation must only consist of numbers. ```vue-html ``` #### one_of The field under validation must have a value that is in the specified list. **Uses double equals** for checks. ```vue-html ``` `one_of` takes an infinite number of params, each is a value that is allowed. #### regex The field under validation must match the specified regular expression. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | --------------------------------------------------------- | | `pattern` | **yes** | A regular expression instance or string representing one. | You should not use the pipe '|' or commas ',' within your regular expression when using the string rules format as it will cause a conflict with how validators parsing works. You should use the object format of the rules instead as shown in the last snippet When using the `regex` rule, using the `g` flag may result in unexpected falsy validations. This is because vee-validate uses the same instance across validation attempts. #### required The field under validation must have a non-empty value. By default, all validators pass the validation if they have "empty values" unless they are required. Those empty values are empty strings, `undefined`, `null`, `false`, and empty arrays. ```vue-html ``` #### size The file size added to the field under validation must not exceed the specified size in kilobytes. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | ----------------------------------- | | `size` | **yes** | The maximum file size in kilobytes. | #### url The field under validation must be a valid url. You can pass a `pattern` if you need the url to be more restricted. ```vue-html ``` | Param Name | Required? | Description | | ---------- | --------- | --------------------------------------------------------- | | `pattern` | **no** | A regular expression instance or string representing one. | ================================================ FILE: docs/src/pages/guide/i18n.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Localization (i18n) description: Localization support with the first party module and other providers order: 6 next: path: guide/devtools title: Devtools Plugin description: Using the vee-validate Vue.js devtools plugin --- import LiveExample from '@/components/LiveExample.vue'; import DocTip from '@/components/DocTip.vue'; # Localization (i18n) This guide only addresses generating error messages for [globally defined validators](/guide/global-validators) using vee-validate's own `@vee-validate/i18n` plugin. If you are using vee-validate with yup, then you should check out [yup's localization guide](https://github.com/jquense/yup#localization-and-i18n). If you are using another library for data validation, check their i18n capabilities. ## Global Message Generator vee-validate exposes a global config called `generateMessage` which is a function that should return a string. Whenever any globally defined rule returns a falsy value the `generateMessage` will be called and its result will be used as an error message for that rule. The message generator function has the following type: ```ts interface FieldContext { field: string; // The field's name or label (see next section) value: any; // the field's current value form: Record; // other values in the form rule: { name: string; //the rule name params?: any[]; // any params sent to it }; } type ValidationgenerateMessage = (ctx: FieldContext) => string; ``` With this information, you could create message handlers with any kind of 3rd party libraries. To register a message generator use the `configure` function exposed by vee-validate: ```js import { configure } from 'vee-validate'; configure({ generateMessage: context => { return `The field ${context.field} is invalid`; }, }); ``` ### Custom Labels If you want to display different field names in your error messages, the `` component accepts a `label` prop which allows you to display better names for your fields in their generated messages. Here is an example: ```vue-html ``` The generated message will use `nice name` instead of the badly formatted one. ## Using @vee-validate/i18n ### Overview The `@vee-validate/i18n` contains a simple message generator function that you can use to generate localized messages from JSON objects: First, you need to install the `@vee-validate/i18n` package: ```sh yarn add @vee-validate/i18n # or with npm npm install @vee-validate/i18n ``` Import the `localize()` function from `@vee-validate/i18n` which returns a message generator function: ```js import { defineRule, configure } from 'vee-validate'; import { required } from '@vee-validate/rules'; import { localize } from '@vee-validate/i18n'; // Define the rule globally defineRule('required', required); configure({ // Generates an English message locale generator generateMessage: localize('en', { messages: { required: 'This field is required', }, }), }); ``` If you have multiple locales in your application, you can add them like this: ```js import { defineRule, configure } from 'vee-validate'; import { required } from '@vee-validate/rules'; import { localize } from '@vee-validate/i18n'; // Define the rule globally defineRule('required', required); configure({ generateMessage: localize({ en: { messages: { required: 'This field is required', }, }, ar: { messages: { required: 'هذا الحقل مطلوب', }, }, }), }); ``` You can change the locale using `setLocale` function exported by the `@vee-validate/i18n` anywhere in your application: ```js import { setLocale } from '@vee-validate/i18n'; setLocale('ar'); ``` To save you a lot of time translating `@vee-validate/rules` messages to your language, the awesome community around vee-validate already contributed over 40+ languages that you can use directly in your application and get started quickly. The localized files include localized messages for all the [global rules provided by @vee-validate/rules package](/guide/global-validators#available-rules). You can import the needed locale from the `@vee-validate/i18n/dist/locale/{localeCode}.json` and they can be passed directly to the localize function. This snippet adds the English and Arabic localized messages for all the rules of `@vee-validate/rules` package into the message generator function: ```js import { configure } from 'vee-validate'; import { localize } from '@vee-validate/i18n'; import en from '@vee-validate/i18n/dist/locale/en.json'; import ar from '@vee-validate/i18n/dist/locale/ar.json'; configure({ generateMessage: localize({ en, ar, }), }); ``` ### Localizing Field Names To localize the field name with the `@vee-validate/i18n` package, you can add a `names` property to your dictionaries containing the translated names of the fields. ```js import { configure } from 'vee-validate'; import { localize } from '@vee-validate/i18n'; configure({ generateMessage: localize({ en: { names: { age: 'Age', }, }, ar: { age: 'السن', }, }), }); ``` Then whenever a message is generated for a global rule, vee-validate will try to match the field name with the `names` property found in the active locale. This is such an example: ## Custom messages for specific fields You may want to override specific rule messages for specific fields, you can do this by calling `localize` with the specific messages object: ```js import { localize } from '@vee-validate/i18n'; localize('en', { fields: { password: { required: 'Hey! Password cannot be empty', }, }, }); // Or update multiple languages localize({ en: { fields: { password: { required: 'Hey! Password cannot be empty', }, }, }, }); ``` You can call `localize` anywhere in your component initialization, ideally before any error is generated so either `mounted` or `setup` should be safe to call `localize`. ### Placeholder Interpolation Your messages can be more complex than `required`, for example a `between` rule would need access to the params values to be able to produce a meaningful error message, the `@vee-validate/i18n` plugin comes with interpolation support for various placeholders, consider this example: ```js import { defineRule, configure } from 'vee-validate'; import { between } from '@vee-validate/rules'; import { localize } from '@vee-validate/i18n'; // Define the rule globally defineRule('between', between); configure({ // Generates an English message locale generator generateMessage: localize('en', { messages: { between: 'The {field} value must be between 0:{min} and 1:{max}', }, }), }); ``` The `{field}` placeholder will be replaced with the field name, and `0:{min}` and `1:{max}` will be replaced with the rule arguments. Note that the indices that prefix each param name are recommended because the interpolation will need to know the order of the arguments because you may define your global rules in one of `object` or `array` or `string` formats, and thus the interpolation process needs to know both argument names and their order if array params or strings are used. You can also interpolate the other fields in the form by using their names as placeholders. Here is an example that show cases interpolation for different cases: #### Custom Interpolation Prefix and Suffix You also have the ability to customize the `prefix` and `suffix` of the interpolated values, by default they are `{` and `}` respectively. Configure them by passing a third parameter of `InterpolateOptions` to the `localize` function: ```js import { defineRule, configure } from 'vee-validate'; import { between } from '@vee-validate/rules'; import { localize } from '@vee-validate/i18n'; // Define the rule globally defineRule('between', between); configure({ // Generates an English message locale generator generateMessage: localize( 'en', { messages: { // use double `{{` and `}}` i18next-like curly braces for the interpolated values, instead of the default `{` and `}` between: `The {{field}} field must be between 0:{{min}} and 1:{{max}}`, }, }, { prefix: '{{', suffix: '}}', }, ), }); ``` ### Loading Locale From CDN If you are using Vue.js over CDN, you can still load the locale files without having to copy them to your code, the `@vee-validate/i18n` library exposes a `loadLocaleFromURL` function that accepts a URL to a locale file. ```html ``` Here is an example of the previous snippet: ================================================ FILE: docs/src/pages/guide/migration.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Migration Guide description: How to migrate from vee-validate v4 to v5 order: 10 --- # Migrating from v4 to v5 vee-validate v5 is a major release that brings support to [standard schema](https://standardschema.dev/) libraries like zod, valibot, yup, and more. This means most of the companion packages like `@vee-validate/zod`, `@vee-validate/yup`, `@vee-validate/valibot` are no longer needed and are now deprecated. ## Changes to Nuxt Module The nuxt module won't suggest installing any packages anymore since most providers that were supported in v4 are now supported in v5 via standard schema API. ## Changes to schema behavior Since vee-validate v5 is considered schema agnostic, you lose some features that were previously possible with each dedicated validation provider. - Resolving `required` meta flag is no longer supported from schemas. - Using defaults to initialize the form values is no longer supported from schemas. While these features are critical to many apps, the standard schema spec does not provide a way to do that at the moment. Alternatives are being considered via external packages at the moment. ## Replace Typed Schema calls Typed schemas were a way for vee-validate v4 to support a common interface for multiple validation providers. However, there is a community driven package called [standard schema](https://standardschema.dev/) that provides a common interface for multiple validation providers that is becoming very popular and is already supported by the most popular validation providers. So in v5 we now introduced support for standard schema libraries, which means you can use any of the most popular validation providers with vee-validate v5 without installing any resolvers or adapter packages. This includes almost all the previous providers like zod, yup, and valibot. The only needed step is to remove all calls to `toTypedSchema` and replace them with the standard schema library of your choice. ### Zod vee-validate is compatible with zod 3.24.0 and above. ```ts import { z } from 'zod'; // v4 const schema = toTypedSchema(z.object({ name: z.string().min(1) })); // v5 - remove the call to `toTypedSchema` const schema = z.object({ name: z.string().min(1) }); const { value, errorMessage } = useForm({ validationSchema: schema }); ``` ### Yup You will need to upgrade `yup` to 1.7.0 or above since the standard schema support was added in that version. #### If you are using `@vee-validate/yup` ```ts import * as yup from 'yup'; // v4 const schema = toTypedSchema(yup.object({ name: yup.string().min(1) })); // v5 - remove the call to `toTypedSchema` const schema = yup.object({ name: yup.string().min(1) }); const { value, errorMessage } = useForm({ validationSchema: schema }); ``` #### If you are using yup directly Continue using your yup schema as you've been doing in the past. You will start getting type safety features out of the box, just make sure you are using yup 1.7.0 or above. ### Valibot vee-validate is compatible with valibot 1.0 and above. ```ts import * as v from 'valibot'; // v4 const schema = toTypedSchema(v.object({ email: v.pipe(v.string(), v.email()) })); // v5 - remove the call to `toTypedSchema` const schema = v.object({ email: v.pipe(v.string(), v.email()) }); const { value, errorMessage } = useForm({ validationSchema: schema }); ``` ### Joi vee-validate is compatible with Joi v18.0.0 and above. ```ts import * as Joi from 'joi'; // v4 const schema = toTypedSchema(Joi.object({ email: Joi.string().email() })); // v5 - remove the call to `toTypedSchema` const schema = Joi.object({ email: Joi.string().email() }); const { value, errorMessage } = useForm({ validationSchema: schema }); ``` ================================================ FILE: docs/src/pages/guide/overview.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Overview description: Getting started with VeeValidate order: 1 --- import DocTip from '@/components/DocTip.vue'; # Why vee-validate "Forms" is a difficult subject in frontend development. Not only do you have to deal with ensuring that correct values are submitted, but you should also provide a pleasant UX for your users. Building forms from scratch is a lot of work and you probably won't cover all your future needs as your requirements change over time, and as you add more features. **The time you spend working on a custom form solution is better spent building your application logic**. Most form libraries will save you a lot of time, but `vee-validate` tackles the major pain points of forms and then gets out of your way, some of those are: - Form state and value tracking - UX - Synchronous and Asynchronous Validation - Handling submissions vee-validate tries to handle all of the above and more by providing abstractions to these problems without any UI. This could be viewed as a double-edged sword, however, overriding UI and styles was the downfall of many component libraries and design languages. Because of that, vee-validate abstracts away the hard parts into pure logic compositions that you can add to your existing UI and component. There is nothing to override, there is no hidden cost. You can also use vee-validate to power your components internally, and as a result you can build up your form library without having to think about the hard parts. A great showcase of this approach is how vee-validate seamlessly [integrate with almost every component UI library](/examples/ui-libraries/) for Vue.js out there without any special treatment or hacks. ## Getting Started vee-validate makes use of two flavors to add validation to your forms. - **Composition API**: This is the best way to use vee-validate as it allows seamless integrations with your existing UI, or any 3rd party component library. - **Higher-order components (HOC)**: This approach is easy to use and is strictly used within the template, you can use it if you have simple forms and don't want to write a lot of JavaScript. Whichever approach you prefer to use, both flavors can be used interchangeably. So you can mix and match between the two approaches as needed. Most examples in the docs use the new [script setup](https://vuejs.org/api/sfc-script-setup.html#script-setup) SFC syntax for brevity. In case you're having difficulty following along, take some time to learn about it. ### Using a package manager For a more modern workflow with a bundler, you can install vee-validate using a package manager like `yarn`, `npm` or `pnpm`: ```sh yarn add vee-validate # or npm i vee-validate --save # or pnpm add vee-validate ``` ### Using a script tag You can use vee-validate with a script tag and a CDN, import the library like this: ```html ``` This will inject a `VeeValidate` global object, which you will use to access the various components and functions exposed by vee-validate. ================================================ FILE: docs/src/pages/guide/testing.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Testing description: Testing form validation in your apps order: 8 --- # Testing vee-validate has many tests of its own that cover almost every functionality, but if you find yourself needing to test vee-validate integration with your forms and fields, this guide should offer some guidance into the best practices and the caveats of doing that. ## Waiting for async validation vee-validate does all of the work asynchronously. This means whenever you trigger a validation using explicit `validate()` calls or form submissions or `change` events, you still need to "flush" the promises to assert the state you expect. For example, the following test will fail ```js import { useField } from 'useField'; const SomeComponent = { template: ` {{ errorMessage }} `, setup() { const { value, errorMessage } = useField('name', value => { return value ? true : 'field is required'; }); return { value, errorMessage, }; }, }; test('it validates', async () => { // assuming you have a mounting helper mount(SomeComponent); const input = document.querySelector('input'); input.value = ''; input.dispatchEvent(new Event('change')); // ❌ Fails expect(document.querySelector('span').textContent).toBe('Field is required'); }); ``` To wait for the validation to execute and render the error message, you can use the `flush-promises` npm package to wait for all promises to fulfill and also coupled with `wait-for-expect`. ```js import flushPromises from 'flush-promises'; import waitForExpect from 'wait-for-expect'; test('it validates', async () => { // assuming you have a mounting helper mount(SomeComponent); const input = document.querySelector('input'); input.value = ''; input.dispatchEvent(new Event('change')); // wait for the promises to fulfill await flushPromises(); await waitForExpect(() => { // ✅ Now passes expect(document.querySelector('span').textContent).toBe('Field is required'); }); }); ``` vee-validate may run validation in batches to improve performance which isn't always detected by `flush-promises`, so using both is recommended here. ## Testing error messages Messages can change for the simplest of reasons, for example, grammar or punctuation changes could break your tests if you test against the literal contents of the message. Ideally, you shouldn't rely on validation messages staying the same for your tests to pass, in any of the iterations when you work on your product you don't want your tests to fail just because of that. So instead of testing the literal messages, you want to test if they get any kind of validation message regardless of their content. There are a few suggestions to remedy that: ### Testing message existence You can test if the error field contains any message at all, this is useful if you rely on error messages from 3rd party, like the `@vee-validate/i18n` package which changes its messages frequently due to constant updates and fixes by the awesome contributors from all over the world. ```js // ❌ Breaks easily expect(errorElement.textContent).toBe('Field is required'); // ✅ expect(errorElement.textContent).toBeTruthy(); ``` ### Testing partial contents Another approach is to test that the messages contain the critical information needed for the user to understand how to fix their input. This assumes you have some idea of the contents of the message: ```js // ❌ Breaks easily expect(errorElement.textContent).toBe('Field is required'); // ✅ use `toContain` or similar matching assertions expect(errorElement.textContent).toContain('required'); ``` Of course, this can break easily but still is more flexible than the full literal check. ### Testing actual contents This approach relies on the fact that you organize your app user-facing strings in dictionary files, meaning messages are deterministic and not sprinkled across your component's code. ```js import messages from 'strings/validation.js'; // ✅ expect(errorElement.textContent).toBe(messages.required); ``` ## Mocking Form and Field Contexts While this is extremely rare you may need to mock the `FormContext` object or the `FieldContext` object, one of the reasons is to unit test some component that relies on those objects being provided with the `provide/inject` API. You can import the injection keys for those objects from vee-validate and manually inject your fields/forms. ```js import { provide } from 'vue'; import { FormContextKey, FieldContextKey } from 'vee-validate'; provide(FormContextKey, MockedForm); provide(FieldContextKey, MockedField); ``` To learn more about the mock details you should check the source code and see the typescript interfaces for [`FormContext`](https://github.com/logaretm/vee-validate/blob/main/packages/vee-validate/src/types.ts#L145) and [`FieldContext`](https://github.com/logaretm/vee-validate/blob/main/packages/vee-validate/src/types.ts#L66) objects and implement them as mocks. ================================================ FILE: docs/src/pages/index.astro ================================================ --- import HomeLayout from '@/layouts/HomeLayout.astro'; import SponsorButton from '@/components/SponsorButton.vue'; import Icon from '@/components/Icon.vue'; import MainPageExample from '@/components/MainPageExample.vue'; import StarCount from '@/components/StarCount.vue'; import FeatureCard from '@/components/FeatureCard.vue'; import { features } from '@/constants'; import UiLibraries from '@/components/UiLibraries.vue'; const highlights = [ { title: 'Flexible', details: 'Offers both declarative components or composable functions API.

vee-validate sets up the foundation for you to form in whatever style you prefer.', }, { title: 'Incremental', details: 'vee-validate can do a lot if you let it. Like tracking values, validation, handling submissions and more.

You may opt-in or out to all of these aspects. You are in control of how much form code you write.', }, { title: 'Great DX', details: 'Vue.js devtools support.

vee-validate makes debugging forms much easier and less of a wild goose chase every time that form does not submit.', }, ]; ---

VeeValidate

Painless Vue forms

VeeValidate is the most popular Vue.js form library. It takes care of value tracking, validation, errors, submissions and more.

{highlights.map(feature => )}

Works with your favorite UI framework

Listed libraries are ones with official examples, find more UI frameworks and libraries here.

Sponsors

You can also help this this project and my other projects by donating one time or by sponsoring via the following link


Features

    {features.map(feature =>
  • {feature.title}
  • )}

Do more with less

In this example, value tracking, validation and submissions are handled in just a few lines of code

================================================ FILE: docs/src/pages/integrations/nuxt.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Nuxt integration description: VeeValidate Nuxt module order: 3 --- import DocTip from '@/components/DocTip.vue'; # @vee-validate/nuxt

Official vee-validate's Nuxt module ## Features - Auto import of vee-validate components - Auto import of vee-validate composables No types are exposed by default to avoid having conflicts with other libraries, aside from vee-validate's main API components/composables. You can still import them via `vee-validate`. ## Getting Started In your nuxt project install the vee-validate nuxt module: ```sh # npm npm i @vee-validate/nuxt # pnpm pnpm add @vee-validate/nuxt # yarn yarn add @vee-validate/nuxt ``` Then add the module to your `modules` config in `nuxt.config.ts`: ```ts export default defineNuxtConfig({ // ... modules: [ //... '@vee-validate/nuxt', ], }); ``` ## Configuration You can configure a few aspects of the `@vee-validate/nuxt` module. Here is the config interface: ```ts{3-19} export default defineNuxtConfig({ // ... modules: [ //... [ '@vee-validate/nuxt', { // disable or enable auto imports autoImports: true, // Use different names for components componentNames: { Form: 'VeeForm', Field: 'VeeField', FieldArray: 'VeeFieldArray', ErrorMessage: 'VeeErrorMessage', }, }, ], ], }); ``` You can also use the `veeValidate` config key instead of the array syntax: ```ts{7-16} export default defineNuxtConfig({ // ... modules: [ //... '@vee-validate/nuxt', ], veeValidate: { // disable or enable auto imports autoImports: true, // Use different names for components componentNames: { Form: 'VeeForm', Field: 'VeeField', FieldArray: 'VeeFieldArray', ErrorMessage: 'VeeErrorMessage', }, }, }); ``` ================================================ FILE: docs/src/pages/resources.mdx ================================================ --- layout: ../layouts/PageLayout.astro title: Resources description: Articles, Tutorials, and Products on VeeValidate order: 1 --- # Resources ## Talks - [What's new in vee-validate v4](https://youtu.be/_r6PjzFLbqw) by [Abdelrahman Awad](https://twitter.com/logaretm) - Jul 18, 2020 - [Vue Form Validation w/ VeeValidate](https://www.youtube.com/watch?v=KN1ij6hWVDs) by [Aaron Saunders](https://twitter.com/aaronksaunders) - Apr 30, 2021 ## Community Videos - [Managing Form Validation with Custom Form Components And Vee-Validate Composition API](https://www.youtube.com/watch?v=2ET7IH57XwQ) by [Aaron Saunders](https://twitter.com/aaronksaunders) ## Products Creative-Tim are using vee-validate in their Vue dashboards, head over to [creative-tim](https://www.creative-tim.com/templates/vuejs?ref=vee-validate) and check their offerings. ================================================ FILE: docs/src/pages/submit-target.astro ================================================ --- import LogQueryStr from '@/components/LogQueryStr.vue'; --- Submit target ================================================ FILE: docs/src/pages/tutorials/basics.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Validation Basics description: Build a newsletter form with vee-validate order: 1 --- import LiveExample from '@/components/LiveExample.vue'; # Validation Basics VeeValidate offers many ways to do form validation, this tutorial will teach you how to do basic form validation using the simplest approach. ## What are we building In this tutorial, we will be building a "sign up newsletter" form where the user would have to fill a field to complete their sign-up. ## Prerequisites This tutorial assumes you know: - Modern JavaScript features like arrow functions and ES modules. - Vue's [SFC file syntax](https://v3.vuejs.org/guide/single-file-component.html#introduction). ## Setup It is preferable to use a local development environment to follow along, make sure to have the following: 1. Prepare a Vue 3.x project using the [`vue-cli`](https://cli.vuejs.org/)
Detailed Steps If not already prepared, install the New Vue CLI ```sh npm install -g @vue/cli@next ``` Using the `vue-cli`, create a new project and choose Vue 3 template: ```sh vue create vee-validate-tutorial ```
2. Add `vee-validate` to your project ```sh yarn add vee-validate # or npm install vee-validate --save ``` 3. Cleanup the contents of `App.vue`, it should look like the following: ```vue ``` And that's it, now you have an empty Vue project and vee-validate installed. ## Building the Form
First, start by adding some markup, you can start by having a `form` wrapping a few `input` elements.
```vue ``` So far so good, try filling the `email` field with a dummy value like `hello`. Then click the submit button once and see what happens. You will notice that the form submits and you should see `?email=` added in your URL in the address bar, it should have the same value that you entered in the `email` field. This is the native HTML form submission behavior. Usually, in modern applications, you don't want that and you prefer to handle submission with JavaScript. The `novalidate` attribute on the `
` element is meant to disable the native HTML form validation, we will get to validating the form by the end of this tutorial.
Add a `submit` event handler that prevents the native form submission, we will use `onSubmit` function to handle our form submission.
```vue{3,13-17} ``` Now type anything in the `email` field and click submit. You will notice a couple of things: 1. The word "Submitted" being logged to the console. 2. The value you entered wasn't added to the address bar, this means you've prevented the default submission behavior. So far so good, but the form isn't that useful unless it takes the correct data from the user. So let's add validation to the form. ## Adding Validation VeeValidate exposes 2 components that you will be using regularly, the `` and `` are components that will help you validate your forms and inputs.
Import them and register them on the Vue component, then replace the following elements with the vee-validate component: - Replace `` with `` while keeping the same attributes. - Replace `` with `` but remove both the `.prevent` modifier and the `novalidate` attribute.
```vue{3,4,7,12,15-18} ```
Change the `onSubmit` method so it receives an argument called `values` and logs it
```js{7-9} export default { components: { Form, Field, }, methods: { onSubmit(values) { console.log(values); }, }, }; ``` Try typing anything into the `email` field and click submit. You will see form values being logged into the console with the value you entered, this means vee-validate extracted the form values for you and passed it to your `onSubmit` handler. Now all that remains is to add the validation rules. There are multiple ways to define rules with VeeValidate, the most straightforward way is to use regular Vue methods.
Create a function called `validateEmail` that receives 1 argument called `value`.
It should look like this: ```vue{23-37} ``` The `validateEmail` function makes sure the `email` field is both required and is a valid email. Now you need to tell the ` You can do that by passing the `validateEmail` function to the `rules` prop on the `Field` component: ```vue{4} ``` Try testing these scenarios: 1. Type a random non-email value like `example` into the `email` field and try clicking submit. 2. Type a valid email like `hello@example.com` into the `email` field and try clicking submit. In the first case, you will notice that nothing was logged to the console, while in the second case you will see your form values being logged into the console the same as before. This means validation is working and vee-validate is not executing your `onSubmit` handler until the `email` field validation passes. The last step is to show error messages that you already return in the `validateEmail` so that your users have a better understanding of what is going on and why the form isn't submitting. ## Displaying Error Messages To display the error message, you will use the `ErrorMessage` component.
Import the `ErrorMessage` component from `vee-validate` and register it in your component:
```js{1,7} import { Field, Form, ErrorMessage } from 'vee-validate'; export default { components: { Field, Form, ErrorMessage, }, }; ```
Add the `` component to your template, passing a `name` prop that matches the `` name prop which is `"email"`.
```vue{5} ``` If you try the form now without entering anything you will see the required error message appear. Try filling anything that's not an email and notice the invalid email message appearing instead. Now you have successfully created a simple form and implemented validation and submission. You can check out the finished code in action: There is a lot more you can do with vee-validate, there are other ways and features you can use to clean up your form validation logic. Here are a few things that you can do with vee-validate: - Declare rules globally and use them in a Laravel-like syntax - Using 3rd-party libraries like `yup` to validate - Doing form-level validation using a validation schema - Advanced rendering of your inputs and forms using scoped-slots - Component-less validation with the composition API - Generating localized messages You can visit the [guide section](/guide/overview) to begin learning more about vee-validate. ================================================ FILE: docs/src/pages/tutorials/dynamic-form-generator.mdx ================================================ --- layout: ../../layouts/PageLayout.astro title: Build a Form Generator description: Learn how to build a schema-generated form order: 2 --- import DocTip from '@/components/DocTip.vue'; import LiveExample from '@/components/LiveExample.vue'; # Build a Form Generator Building forms is often a repetitive task and requires a lot of back and forth to maintain. Maybe your client asked to add a field, maybe they asked to remove a field. For most cases using static markup is good enough for your form needs but in some cases, it would be great if you had a dynamic form generator that would quickly render your fields based on some javascript object schema. In this tutorial, you will learn how to use vee-validate to build a form-generator without external libraries. Let's quickly recap what you will be building, the component we will be building should:
- Accept a form schema specifying the fields - Render the given schema - Use [yup](https://github.com/jquense/yup) to validate our form - Show error messages
This guide will cover how to build a basic form generator. If you are looking for a more robust solution for form generation, take a look at [Formvuelate](https://formvuelate.js.org/), and it has first-party [vee-validate support](https://formvuelate.js.org/guide/veevalidate.html). ## Prerequisites This tutorial assumes you know: - Modern JavaScript like arrow functions and ES modules. - Vue's [SFC file syntax](https://v3.vuejs.org/guide/single-file-component.html#introduction). - Vue's [list rendering with `v-for`](https://v3.vuejs.org/guide/list.html#mapping-an-array-to-elements-with-v-for). - Vue's [dynamic components](https://v3.vuejs.org/guide/component-basics.html#dynamic-components). This tutorial also assumes you already have an empty Vue-cli project that you will edit as you follow along and that you have installed `vee-validate` already. ## Laying The Foundation Before getting to the implementation details to implement a dynamic form generator, you need to have an overview of how it would work. So let's assume we have already implemented a component called `DynamicForm` that accepts a `schema` prop that has all the information needed to render the form. We have a few requirements to fulfill: - Should be able to provide the fields. - Should be able to specify types (elements) for those fields - Should be able to specify validation either on field-level or form-level Assuming we have such a component, we can imagine using it to be like this: ```vue[App.vue] ``` The form schema will contain `fields` property which is an array of the fields we want to render, each field entry will have these properties: - `label`: a friendly label to display with the input. - `name`: a unique name for the field to identify it. - `as`: the name of the input element to render, it can be any native HTML element. ## Rendering Fields
The initial implementation will follow these generic steps: - Use `Form` component from vee-validate to render the form - Iterate over each field in `schema.fields` - Render each field as a `Field` component passing all props to it Let's put that into some code.
```vue[components/DynamicForm.vue] ``` Notice that when you run the example, the `password` field is being rendered as a text field which isn't ideal. We would like to be able to pass the `type` property to the input element as well.
In the `App.vue` component, add highlighted line:
```vue{29}[App.vue] ```
In the `DynamicForm.vue` component, update the iteration with `v-for` portion to extract the known keys that we expect and collecting the rest in another object using ES6 object rest operator.
```vue{3-9}[components/DynamicForm.vue] ``` The `v-bind` there allows us to bind everything in the `attrs` object which is all the other props we didn't extract explicitly and bind them to the `Field` component. The `Field` component will pass down any props that it doesn't accept to whatever gets rendered in its place, effectively passing down other attributes to our `input` tags.
Bonus: Adding support for slotted inputs The `select` input introduces an edge case where your field would need to have child elements (i.e: `
## Handling Validation We would like each field to have its own validation rules defined on the schema. We will use [`yup`](https://github.com/jquense/yup) for those validation rules.
In `App.vue`, update the form's schema so that each field has a new `rules` property with sensible validation rules
```vue{7,20,26,33}[App.vue] ``` Now that each field has its own validation rules, we will need to display the error messages.
Import and register the `ErrorMessage` component inside the `DynamicForm.vue` component, and add it to the template after the ``.
```vue{27,34,19}[components/DynamicForm.vue] ``` And that's it, you should have validation working now 🎉 ## Demo You can check a live sample of what we did here. ## Conclusion In this guide, you learned how to use the dynamic rendering capabilities of Vue.js combined with the flexible nature of the vee-validate components. You created a form that renders fields and validates them based on javascript object schema. While the finished product is far from being complete, you can add features as needed to your form generator. If you are looking for a more robust solution for form generation, take a look at [Formvuelate](https://formvuelate.js.org/), and it has first-party [vee-validate support](https://formvuelate.js.org/#vee-validate-plugin). ================================================ FILE: docs/src/styles/home.css ================================================ .App { @apply h-full mx-auto; max-width: 1500px; grid-template-areas: 'content' 'footer'; display: grid; grid-gap: 20px; } .main { @apply py-12 px-6 min-w-0; grid-area: content; @screen lg { @apply px-0; } } #ad { @apply hidden; @screen md { @apply block !fixed bottom-4 right-4; } } ================================================ FILE: docs/src/styles/page.css ================================================ .PageApp { @apply h-full mx-auto; max-width: 1600px; grid-template-areas: 'content' 'footer'; display: grid; grid-gap: 50px; @screen lg { grid-template-areas: 'lside content rside' 'footer footer footer'; grid-template-columns: 300px 40rem 1fr; column-gap: 24px; } @screen xl { grid-template-areas: 'lside content rside' 'footer footer footer'; grid-template-columns: 300px 45rem 1fr; column-gap: 40px; } @screen 2xl { grid-template-areas: 'lside content rside' 'footer footer footer'; grid-template-columns: 300px 50rem 1fr; column-gap: 100px; } .main { @apply py-12 px-6 min-w-0 lg:px-0; } } ================================================ FILE: docs/src/styles/tailwind.css ================================================ @import url('https://fonts.googleapis.com/css?family=Montserrat:700,400,600,500|Noto+Sans:400,500,700&display=swap'); @import 'tailwindcss/base'; @import 'tailwindcss/components'; @import 'tailwindcss/utilities'; html, body { width: 100%; min-height: 100vh; @apply font-body; scroll-padding-top: 130px; } body { @apply bg-white text-gray-700 w-full; } .dark { body { @apply bg-dark text-white; } } .lside { grid-area: lside; } .rside { grid-area: rside; } .main { grid-area: content; } .fade-enter-active, .fade-leave-active { transition: opacity 0.8s ease-in-out, transform 0.2s ease-in-out; } .fade-enter, .fade-leave-to { opacity: 0; transform: translate3d(40%, 0, 0); } :root { --accent: #09a884; --code-foreground: #2c3e50; --code-background: #f6f6f6; --code-token-constant: #c25205; --code-token-operator: #b2085f; --code-token-type: #b92dbc; --code-token-parameter: #2c3e50; --code-token-attribute: #c25205; --code-token-regex: #ff9580; --code-token-string: #0a7a34; --code-token-comment: #848486; --code-line-numbers: #bbbbbb; --code-line-highlight: #f8f4e4; --code-line-highlight-border: #b92dbc; --code-lang-label: #bbbbbb; } .dark { --accent: #09a884; --code-foreground: #f8f8f2; --code-background: #22212c; --code-token-constant: #9580ff; --code-token-operator: #ff80bf; --code-token-type: #80ffea; --code-token-parameter: #ffca80; --code-token-attribute: #8aff80; --code-token-regex: #ff9580; --code-token-string: #ffff80; --code-token-comment: #7970a9; --code-line-numbers: #7970a9; --code-line-highlight: #16171d; --code-line-highlight-border: #7970a9; --code-lang-label: #7970a9; } ul.features { li { @apply relative antialiased list-none; &:before { @apply w-6 h-6 text-lg absolute rounded-full flex items-center justify-center flex-shrink-0; content: ''; background-image: url("data:image/svg+xml,%3Csvg fill='none' stroke='%2309a884' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'%3E%3C/path%3E%3C/svg%3E"); left: -2rem; } } } ================================================ FILE: docs/src/utils/examples.ts ================================================ import { Project } from '@stackblitz/sdk'; import { version } from '../../../packages/vee-validate/package.json'; export function getViteProjectConfig(files: Record): Project { return { template: 'node', title: `vee-validate example`, files: { 'package.json': `{ "name": "vite-vue-starter", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": { "vee-validate": "^${version}", "vue": "^3.3.11", "yup": "latest", "zod": "latest", "valibot": "latest" }, "devDependencies": { "@vitejs/plugin-vue": "^4.2.3", "vite": "^4.3.9" } }`, 'index.html': ` Vite + Vue
`, 'src/main.js': `import { createApp } from 'vue' import App from './App.vue' createApp(App).mount('#app') `, 'vite.config.js': `import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ plugins: [vue()], }) `, ...Object.fromEntries(Object.entries(files).map(([file, content]) => [`src/${file}`, content])), }, }; } ================================================ FILE: docs/src/utils/github.ts ================================================ export function fetchStarCount() { return fetch('https://api.github.com/repos/logaretm/vee-validate') .then(res => res.json()) .then(json => { return json.stargazers_count as number; }) .catch(err => { // eslint-disable-next-line no-console console.error(err); return 0; }); } ================================================ FILE: docs/src/utils/seo.ts ================================================ export function generateSocialImage({ title, tagline, cloudName = 'logaretm', imagePublicID, cloudinaryUrlBase = 'https://res.cloudinary.com', version = null, titleFont = 'montserrat', titleExtraConfig = 'bold', taglineExtraConfig = '', taglineFont = 'montserrat', imageWidth = 1280, imageHeight = 669, textAreaWidth = 760, textLeftOffset = 70, titleBottomOffset = 350, taglineTopOffset = 360, textColor = '09a884', taglineColor = 'ffffff', titleFontSize = 64, taglineFontSize = 48, }) { // configure social media image dimensions, quality, and format const imageConfig = [`w_${imageWidth}`, `h_${imageHeight}`, 'c_fill', 'q_auto', 'f_auto'].join(','); // configure the title text const titleConfig = [ `w_${textAreaWidth}`, 'c_fit', `co_rgb:${textColor}`, 'g_south_west', `x_${textLeftOffset}`, `y_${titleBottomOffset}`, `l_text:${titleFont}_${titleFontSize}${titleExtraConfig}:${encodeURIComponent(title)}`, ].join(','); // configure the tagline text const taglineConfig = [ `w_${textAreaWidth}`, 'c_fit', `co_rgb:${taglineColor}`, 'g_north_west', `x_${textLeftOffset}`, `y_${taglineTopOffset}`, `l_text:${taglineFont}_${taglineFontSize}${taglineExtraConfig}:${encodeURIComponent(tagline)}`, ].join(','); // combine all the pieces required to generate a Cloudinary URL const urlParts = [ cloudinaryUrlBase, cloudName, 'image', 'upload', imageConfig, titleConfig, taglineConfig, version, imagePublicID, ]; // remove any falsy sections of the URL (e.g. an undefined version) const validParts = urlParts.filter(Boolean); // join all the parts into a valid URL to the generated image return validParts.join('/'); } export function generateMetaTags({ title, description, image, url, keywords }: any) { return [ { name: 'description', hid: 'description', content: description, }, { name: 'og:description', property: 'og:description', hid: 'og:description', content: description, }, { name: 'og:title', hid: 'og:title', property: 'og:title', content: title, }, url ? { hid: 'og:url', name: 'og:url', property: 'og:url', content: url, } : undefined, image ? { name: 'og:image', hid: 'og:image', property: 'og:image', content: image, } : undefined, image ? { name: 'image', hid: 'image', property: 'image', content: image, } : undefined, { name: 'twitter:card', hid: 'twitter:card', content: 'summary_large_image', }, { hid: 'twitter:title', name: 'twitter:title', property: 'twitter:title', content: title, }, { hid: 'twitter:description', name: 'twitter:description', property: 'twitter:description', content: description, }, image ? { hid: 'twitter:image', name: 'twitter:image', property: 'twitter:image', content: image, } : undefined, keywords ? { hid: 'keywords', name: 'keywords', property: 'keywords', content: Array.isArray(keywords) ? keywords.join(', ') : keywords, } : undefined, ].filter(Boolean); } export function generateLinks({ url }) { return [ { hid: 'canonical', rel: 'canonical', href: url, }, ]; } export interface Frontmatter { title: string; menuTitle?: string; order: number; next?: { title: string; description: string; path: string; intro: string; }; description?: string; path?: string; } export function buildMenu(pages: { url?: string; frontmatter: Frontmatter; children?: any[]; icon?: string }[]) { return [ ...pages.sort((a, b) => { return a.frontmatter.order - b.frontmatter.order; }), ].map(p => { return { title: p.frontmatter.title, menuTitle: p.frontmatter.menuTitle, order: p.frontmatter.order, description: p.frontmatter.description, next: p.frontmatter.next, path: p.url || '', children: p.children ? buildMenu(p.children) : undefined, icon: p.icon || undefined, }; }); } ================================================ FILE: docs/tailwind.config.js ================================================ /* ** TailwindCSS Configuration File ** ** Docs: https://tailwindcss.com/docs/configuration ** Default: https://github.com/tailwindcss/tailwindcss/blob/master/stubs/defaultConfig.stub.js */ module.exports = { darkMode: 'class', content: [ 'src/components/**/*.vue', 'src/components/**/*.astro', 'src/layouts/**/*.vue', 'src/layouts/**/*.astro', 'src/pages/**/*.mdx', 'src/pages/**/*.astro', ], theme: { fontFamily: { display: ['Montserrat', 'sans-serif'], body: ['Atkinson-Hyperlegible', 'Segoe UI', 'Tahoma', 'Geneva', 'Verdana', 'sans-serif'], ui: ['Noto Sans', 'Segoe UI', 'Tahoma', 'Geneva', 'Verdana', 'sans-serif'], mono: [ 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', 'monospace', ], }, extend: { colors: { 'accent-900': '#009f53', 'accent-800': 'var(--accent, #06d77b)', 'accent-100': '#7bffc5', warning: '#ffa629', error: '#cf6679', black: '#000', white: '#fff', 'gray-800': '#151518', 'gray-700': 'hsl(240 6% 9%)', 'gray-600': '#333', 'gray-500': 'hsl(0 0% 29%)', 'gray-400': '#a2a2a2', 'gray-300': 'hsl(0 0% 74%)', 'gray-200': '#e8e8e8', 'gray-100': '#f6f6f6', 'dracula-selection': '#454158', background: '#22212C', dark: 'hsl(240 6% 9%)', carbon: '#333', }, screens: { motion: { raw: '(prefers-reduced-motion: no-preference)' }, }, }, }, }; ================================================ FILE: docs/theme.json ================================================ { "$schema": "vscode://schemas/color-theme", "name": "custom", "author": "Abdelrahman Awad", "maintainers": ["Abdelrahman Awad "], "colors": { "editor.foreground": "#F8F8F2", "editor.background": "#22212C" }, "tokenColors": [ { "scope": ["emphasis"], "settings": { "fontStyle": "italic" } }, { "scope": ["strong"], "settings": { "fontStyle": "bold" } }, { "scope": ["header"], "settings": { "foreground": "#9580ff" } }, { "scope": ["meta.diff", "meta.diff.header"], "settings": { "foreground": "#7970a9" } }, { "scope": ["markup.inserted"], "settings": { "foreground": "#8aff80" } }, { "scope": ["markup.deleted"], "settings": { "foreground": "#ff9580" } }, { "scope": ["markup.changed"], "settings": { "foreground": "#ffca80" } }, { "scope": ["invalid"], "settings": { "foreground": "#ff9580", "fontStyle": "underline italic" } }, { "scope": ["invalid.deprecated"], "settings": { "foreground": "#f8f8f2", "fontStyle": "underline italic" } }, { "scope": ["entity.name.filename"], "settings": { "foreground": "#ffff80" } }, { "scope": ["markup.error"], "settings": { "foreground": "#ff9580" } }, { "name": "Underlined markup", "scope": ["markup.underline"], "settings": { "fontStyle": "underline" } }, { "name": "Bold markup", "scope": ["markup.bold"], "settings": { "fontStyle": "bold", "foreground": "#ffca80" } }, { "name": "Markup headings", "scope": ["markup.heading"], "settings": { "fontStyle": "bold", "foreground": "#9580ff" } }, { "name": "Markup italic", "scope": ["markup.italic"], "settings": { "foreground": "#ffff80", "fontStyle": "italic" } }, { "name": "Bullets, lists (prose)", "scope": [ "beginning.punctuation.definition.list.markdown", "beginning.punctuation.definition.quote.markdown", "punctuation.definition.link.restructuredtext" ], "settings": { "foreground": "#80ffea" } }, { "name": "Inline code (prose)", "scope": ["markup.inline.raw", "markup.raw.restructuredtext"], "settings": { "foreground": "#8aff80" } }, { "name": "Links (prose)", "scope": ["markup.underline.link", "markup.underline.link.image"], "settings": { "foreground": "#80ffea" } }, { "name": "Link text, image alt text (prose)", "scope": [ "meta.link.reference.def.restructuredtext", "punctuation.definition.directive.restructuredtext", "string.other.link.description", "string.other.link.title" ], "settings": { "foreground": "#ff80bf" } }, { "name": "Blockquotes (prose)", "scope": ["entity.name.directive.restructuredtext", "markup.quote"], "settings": { "foreground": "#ffff80", "fontStyle": "italic" } }, { "name": "Horizontal rule (prose)", "scope": ["meta.separator.markdown"], "settings": { "foreground": "#7970a9" } }, { "name": "Code blocks", "scope": [ "fenced_code.block.language", "markup.raw.inner.restructuredtext", "markup.fenced_code.block.markdown punctuation.definition.markdown" ], "settings": { "foreground": "#8aff80" } }, { "name": "Prose constants", "scope": ["punctuation.definition.constant.restructuredtext"], "settings": { "foreground": "#9580ff" } }, { "name": "Braces in markdown headings", "scope": [ "markup.heading.markdown punctuation.definition.string.begin", "markup.heading.markdown punctuation.definition.string.end" ], "settings": { "foreground": "#9580ff" } }, { "name": "Braces in markdown paragraphs", "scope": [ "meta.paragraph.markdown punctuation.definition.string.begin", "meta.paragraph.markdown punctuation.definition.string.end" ], "settings": { "foreground": "#f8f8f2" } }, { "name": "Braces in markdown blockquotes", "scope": [ "markup.quote.markdown meta.paragraph.markdown punctuation.definition.string.begin", "markup.quote.markdown meta.paragraph.markdown punctuation.definition.string.end" ], "settings": { "foreground": "#ffff80" } }, { "name": "User-defined class names", "scope": ["entity.name.type.class", "entity.name.class"], "settings": { "foreground": "#80ffea", "fontStyle": "normal" } }, { "name": "this, super, self, etc.", "scope": [ "keyword.expressions-and-types.swift", "keyword.other.this", "variable.language", "variable.language punctuation.definition.variable.php", "variable.other.readwrite.instance.ruby", "variable.parameter.function.language.special" ], "settings": { "foreground": "#9580ff", "fontStyle": "italic" } }, { "name": "Inherited classes", "scope": ["entity.other.inherited-class"], "settings": { "fontStyle": "italic", "foreground": "#80ffea" } }, { "name": "Comments", "scope": ["comment", "punctuation.definition.comment", "unused.comment", "wildcard.comment"], "settings": { "foreground": "#7970a9" } }, { "name": "JSDoc-style keywords", "scope": [ "comment keyword.codetag.notation", "comment.block.documentation keyword", "comment.block.documentation storage.type.class" ], "settings": { "foreground": "#ff80bf" } }, { "name": "JSDoc-style types", "scope": ["comment.block.documentation entity.name.type"], "settings": { "foreground": "#80ffea", "fontStyle": "italic" } }, { "name": "JSDoc-style type brackets", "scope": ["comment.block.documentation entity.name.type punctuation.definition.bracket"], "settings": { "foreground": "#80ffea" } }, { "name": "JSDoc-style comment parameters", "scope": ["comment.block.documentation variable"], "settings": { "foreground": "#ffca80", "fontStyle": "italic" } }, { "name": "Constants", "scope": ["constant", "variable.other.constant"], "settings": { "foreground": "#9580ff" } }, { "name": "Constant escape sequences", "scope": ["constant.character.escape", "constant.character.string.escape", "constant.regexp"], "settings": { "foreground": "#ff80bf" } }, { "name": "HTML tags", "scope": ["entity.name.tag"], "settings": { "foreground": "#ff80bf" } }, { "name": "CSS attribute parent selectors ('&')", "scope": ["entity.other.attribute-name.parent-selector"], "settings": { "foreground": "#ff80bf" } }, { "name": "HTML/CSS attribute names", "scope": ["entity.other.attribute-name"], "settings": { "foreground": "#8aff80", "fontStyle": "italic" } }, { "name": "Function names", "scope": [ "entity.name.function", "meta.function-call.generic", "meta.function-call.object", "meta.function-call.php", "meta.function-call.static", "meta.method-call.java meta.method", "meta.method.groovy", "support.function.any-method.lua", "keyword.operator.function.infix" ], "settings": { "foreground": "#8aff80" } }, { "name": "Function parameters", "scope": [ "entity.name.variable.parameter", "meta.at-rule.function variable", "meta.at-rule.mixin variable", "meta.function.arguments variable.other.php", "meta.selectionset.graphql meta.arguments.graphql variable.arguments.graphql", "variable.parameter" ], "settings": { "fontStyle": "italic", "foreground": "#ffca80" } }, { "name": "Decorators", "scope": ["meta.decorator variable.other.readwrite", "meta.decorator variable.other.property"], "settings": { "foreground": "#8aff80", "fontStyle": "italic" } }, { "name": "Decorator Objects", "scope": ["meta.decorator variable.other.object"], "settings": { "foreground": "#8aff80" } }, { "name": "Keywords", "scope": ["keyword", "punctuation.definition.keyword"], "settings": { "foreground": "#ff80bf" } }, { "name": "Keyword \"new\"", "scope": ["keyword.control.new", "keyword.operator.new"], "settings": { "fontStyle": "bold" } }, { "name": "Generic selectors (CSS/SCSS/Less/Stylus)", "scope": ["meta.selector"], "settings": { "foreground": "#ff80bf" } }, { "name": "Language Built-ins", "scope": ["support"], "settings": { "fontStyle": "italic", "foreground": "#80ffea" } }, { "name": "Built-in magic functions and constants", "scope": ["support.function.magic", "support.variable", "variable.other.predefined"], "settings": { "fontStyle": "regular", "foreground": "#9580ff" } }, { "name": "Built-in functions / properties", "scope": ["support.function", "support.type.property-name"], "settings": { "fontStyle": "regular" } }, { "name": "Separators (key/value, namespace, inheritance, pointer, hash, slice, etc)", "scope": [ "constant.other.symbol.hashkey punctuation.definition.constant.ruby", "entity.other.attribute-name.placeholder punctuation", "entity.other.attribute-name.pseudo-class punctuation", "entity.other.attribute-name.pseudo-element punctuation", "meta.group.double.toml", "meta.group.toml", "meta.object-binding-pattern-variable punctuation.destructuring", "punctuation.colon.graphql", "punctuation.definition.block.scalar.folded.yaml", "punctuation.definition.block.scalar.literal.yaml", "punctuation.definition.block.sequence.item.yaml", "punctuation.definition.entity.other.inherited-class", "punctuation.function.swift", "punctuation.separator.dictionary.key-value", "punctuation.separator.hash", "punctuation.separator.inheritance", "punctuation.separator.key-value", "punctuation.separator.key-value.mapping.yaml", "punctuation.separator.namespace", "punctuation.separator.pointer-access", "punctuation.separator.slice", "string.unquoted.heredoc punctuation.definition.string", "support.other.chomping-indicator.yaml", "punctuation.separator.annotation" ], "settings": { "foreground": "#ff80bf" } }, { "name": "Brackets, braces, parens, etc.", "scope": [ "keyword.operator.other.powershell", "keyword.other.statement-separator.powershell", "meta.brace.round", "meta.function-call punctuation", "punctuation.definition.arguments.begin", "punctuation.definition.arguments.end", "punctuation.definition.entity.begin", "punctuation.definition.entity.end", "punctuation.definition.tag.cs", "punctuation.definition.type.begin", "punctuation.definition.type.end", "punctuation.section.scope.begin", "punctuation.section.scope.end", "storage.type.generic.java", "string.template meta.brace", "string.template punctuation.accessor" ], "settings": { "foreground": "#f8f8f2" } }, { "name": "Variable interpolation operators", "scope": [ "meta.string-contents.quoted.double punctuation.definition.variable", "punctuation.definition.interpolation.begin", "punctuation.definition.interpolation.end", "punctuation.definition.template-expression.begin", "punctuation.definition.template-expression.end", "punctuation.section.embedded.begin", "punctuation.section.embedded.coffee", "punctuation.section.embedded.end", "punctuation.section.embedded.end source.php", "punctuation.section.embedded.end source.ruby", "punctuation.definition.variable.makefile" ], "settings": { "foreground": "#ff80bf" } }, { "name": "Keys (serializable languages)", "scope": [ "entity.name.function.target.makefile", "entity.name.section.toml", "entity.name.tag.yaml", "variable.other.key.toml" ], "settings": { "foreground": "#80ffea" } }, { "name": "Dates / timestamps (serializable languages)", "scope": ["constant.other.date", "constant.other.timestamp"], "settings": { "foreground": "#ffca80" } }, { "name": "YAML aliases", "scope": ["variable.other.alias.yaml"], "settings": { "fontStyle": "italic underline", "foreground": "#8aff80" } }, { "name": "Storage", "scope": [ "storage", "meta.implementation storage.type.objc", "meta.interface-or-protocol storage.type.objc", "source.groovy storage.type.def" ], "settings": { "fontStyle": "regular", "foreground": "#ff80bf" } }, { "name": "Types", "scope": [ "entity.name.type", "keyword.primitive-datatypes.swift", "keyword.type.cs", "meta.protocol-list.objc", "meta.return-type.objc", "source.go storage.type", "source.groovy storage.type", "source.java storage.type", "source.powershell entity.other.attribute-name", "storage.class.std.rust", "storage.type.attribute.swift", "storage.type.c", "storage.type.core.rust", "storage.type.cs", "storage.type.groovy", "storage.type.objc", "storage.type.php", "storage.type.haskell", "storage.type.ocaml" ], "settings": { "fontStyle": "italic", "foreground": "#80ffea" } }, { "name": "Generics, templates, and mapped type declarations", "scope": [ "entity.name.type.type-parameter", "meta.indexer.mappedtype.declaration entity.name.type", "meta.type.parameters entity.name.type" ], "settings": { "foreground": "#ffca80" } }, { "name": "Modifiers", "scope": ["storage.modifier"], "settings": { "foreground": "#ff80bf" } }, { "name": "RegExp string", "scope": [ "string.regexp", "constant.other.character-class.set.regexp", "constant.character.escape.backslash.regexp" ], "settings": { "foreground": "#ffff80" } }, { "name": "Non-capture operators", "scope": ["punctuation.definition.group.capture.regexp"], "settings": { "foreground": "#ff80bf" } }, { "name": "RegExp start and end characters", "scope": ["string.regexp punctuation.definition.string.begin", "string.regexp punctuation.definition.string.end"], "settings": { "foreground": "#ff9580" } }, { "name": "Character group", "scope": ["punctuation.definition.character-class.regexp"], "settings": { "foreground": "#80ffea" } }, { "name": "Capture groups", "scope": ["punctuation.definition.group.regexp"], "settings": { "foreground": "#ffca80" } }, { "name": "Assertion operators", "scope": ["punctuation.definition.group.assertion.regexp", "keyword.operator.negation.regexp"], "settings": { "foreground": "#ff9580" } }, { "name": "Positive lookaheads", "scope": ["meta.assertion.look-ahead.regexp"], "settings": { "foreground": "#8aff80" } }, { "name": "Strings", "scope": ["string"], "settings": { "foreground": "#ffff80" } }, { "name": "String quotes (temporary vscode fix)", "scope": ["punctuation.definition.string.begin", "punctuation.definition.string.end"], "settings": { "foreground": "#ffff80" } }, { "name": "Property quotes (temporary vscode fix)", "scope": ["punctuation.support.type.property-name.begin", "punctuation.support.type.property-name.end"], "settings": { "foreground": "#80ffea" } }, { "name": "Docstrings", "scope": [ "string.quoted.docstring.multi", "string.quoted.docstring.multi.python punctuation.definition.string.begin", "string.quoted.docstring.multi.python punctuation.definition.string.end", "string.quoted.docstring.multi.python constant.character.escape" ], "settings": { "foreground": "#7970a9" } }, { "name": "Variables and object properties", "scope": [ "variable", "constant.other.key.perl", "support.variable.property", "variable.other.constant.js", "variable.other.constant.ts", "variable.other.constant.tsx" ], "settings": { "foreground": "#f8f8f2" } }, { "name": "Destructuring / aliasing reference name (LHS)", "scope": [ "meta.import variable.other.readwrite", "meta.object-binding-pattern-variable variable.object.property", "meta.variable.assignment.destructured.object.coffee variable" ], "settings": { "fontStyle": "italic", "foreground": "#ffca80" } }, { "name": "Destructuring / aliasing variable name (RHS)", "scope": [ "meta.import variable.other.readwrite.alias", "meta.export variable.other.readwrite.alias", "meta.variable.assignment.destructured.object.coffee variable variable" ], "settings": { "fontStyle": "normal", "foreground": "#f8f8f2" } }, { "name": "GraphQL keys", "scope": ["meta.selectionset.graphql variable"], "settings": { "foreground": "#ffff80" } }, { "name": "GraphQL function arguments", "scope": ["meta.selectionset.graphql meta.arguments variable"], "settings": { "foreground": "#f8f8f2" } }, { "name": "GraphQL fragment name (definition)", "scope": ["entity.name.fragment.graphql", "variable.fragment.graphql"], "settings": { "foreground": "#80ffea" } }, { "name": "Edge cases (foreground color resets)", "scope": [ "constant.other.symbol.hashkey.ruby", "keyword.operator.dereference.java", "keyword.operator.navigation.groovy", "meta.scope.for-loop.shell punctuation.definition.string.begin", "meta.scope.for-loop.shell punctuation.definition.string.end", "meta.scope.for-loop.shell string", "storage.modifier.import", "punctuation.section.embedded.begin.tsx", "punctuation.section.embedded.end.tsx", "punctuation.section.embedded.begin.jsx", "punctuation.section.embedded.end.jsx", "punctuation.separator.list.comma.css", "constant.language.empty-list.haskell" ], "settings": { "foreground": "#f8f8f2" } }, { "name": "Shell variables prefixed with \"$\" (edge case)", "scope": ["source.shell variable.other"], "settings": { "foreground": "#9580ff" } }, { "name": "Powershell constants mistakenly scoped to `support`, rather than `constant` (edge)", "scope": ["support.constant"], "settings": { "fontStyle": "normal", "foreground": "#9580ff" } }, { "name": "Makefile prerequisite names", "scope": ["meta.scope.prerequisites.makefile"], "settings": { "foreground": "#ffff80" } }, { "name": "SCSS attribute selector strings", "scope": ["meta.attribute-selector.scss"], "settings": { "foreground": "#ffff80" } }, { "name": "SCSS attribute selector brackets", "scope": [ "punctuation.definition.attribute-selector.end.bracket.square.scss", "punctuation.definition.attribute-selector.begin.bracket.square.scss" ], "settings": { "foreground": "#f8f8f2" } }, { "name": "Haskell Pragmas", "scope": ["meta.preprocessor.haskell"], "settings": { "foreground": "#7970a9" } } ] } ================================================ FILE: docs/tsconfig.json ================================================ // Example: starter tsconfig.json for Astro projects { "compilerOptions": { "baseUrl": ".", "allowSyntheticDefaultImports": true, // Enable top-level await and other modern ESM features. "target": "ESNext", "module": "ESNext", // Enable node-style module resolution, for things like npm package imports. "moduleResolution": "node", // Enable JSON imports. "resolveJsonModule": true, // Enable stricter transpilation for better output. "isolatedModules": true, // Astro will directly run your TypeScript code, no transpilation needed. "noEmit": true, "jsx": "preserve", "paths": { "@/*": ["src/*"] } }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] } ================================================ FILE: eslint.config.js ================================================ import eslint from '@eslint/js'; import globals from 'globals'; import tseslint from 'typescript-eslint'; export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, { languageOptions: { sourceType: 'module', globals: { ...globals.browser, }, }, rules: { '@typescript-eslint/camelcase': 'off', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'warn', 'no-console': 'error', }, }, { files: ['scripts/**/*'], languageOptions: { sourceType: 'commonjs', globals: { ...globals.nodeBuiltin, }, }, }, { files: ['docs/scripts/**/*', 'docs/*.config.js'], languageOptions: { sourceType: 'commonjs', globals: { ...globals.node, }, }, }, { files: ['scripts/**/*', 'packages/**/*.spec.ts'], languageOptions: { sourceType: 'commonjs', globals: { ...globals.nodeBuiltin, }, }, rules: { 'no-console': 'off', }, }, { ignores: [ 'packages/vee-validate/dist/*', 'packages/yup/dist/*', 'packages/zod/dist/*', 'packages/valibot/dist/*', 'packages/joi/dist/*', 'packages/rules/dist/*', 'packages/i18n/dist/*', 'packages/nuxt/dist/*', 'docs/dist/*', ], }, ); ================================================ FILE: package.json ================================================ { "name": "vee-validate-monorepo", "private": true, "description": "Painless forms for Vue.js", "author": "Abdelrahman Awad ", "license": "MIT", "type": "module", "packageManager": "pnpm@9.1.0", "homepage": "https://vee-validate.logaretm.com", "repository": "https://github.com/logaretm/vee-validate", "scripts": { "test": "vitest", "lint": "eslint . '**/*.{js,jsx,ts,tsx}' --fix", "format": "prettier \"./**/*.ts\" --write", "build": "node scripts/build.mjs", "cover": "vitest run --coverage", "postversion": "pnpm build", "typecheck": "pnpm tsc --noEmit --project ./tsconfig.json --skipLibCheck", "docs:dev": "cd ./docs && pnpm dev && cd -", "postinstall": "husky install", "release": "./scripts/release.sh", "ci:copy-mds": "node scripts/copy-mds.mjs", "ci:publish": "pnpm build && pnpm publish --provenance --access public -r", "ci:version": "pnpm changeset version && pnpm ci:copy-mds", "ci:tag": "node scripts/tag-release.mjs" }, "devDependencies": { "@changesets/cli": "^2.29.5", "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@eslint/js": "^9.2.0", "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-typescript": "^11.1.6", "@types/node": "^20.12.8", "@vitest/coverage-v8": "^1.6.0", "@vue/devtools-api": "^7.5.2", "@vue/devtools-kit": "^7.5.2", "chalk": "^5.3.0", "eslint": "^9.2.0", "eslint-config-prettier": "^9.1.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-n": "^17.4.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "^6.1.1", "filesize": "^10.1.1", "flush-promises": "^1.0.2", "fs-extra": "^11.2.0", "globals": "^15.1.0", "gzip-size": "^7.0.0", "husky": "^9.0.11", "jsdom": "^24.0.0", "klona": "^2.0.6", "lint-staged": "^15.2.2", "prettier": "^3.2.5", "prettier-plugin-astro": "^0.13.0", "raf-stub": "^3.0.0", "rollup": "^4.17.2", "rollup-plugin-dts": "^6.1.0", "terser": "^5.31.0", "tslint-config-prettier": "^1.18.0", "type-fest": "^4.8.3", "typescript": "5.3.3", "typescript-eslint": "^7.8.0", "vite-tsconfig-paths": "^4.3.2", "vitest": "^1.6.0", "vue": "^3.4.26", "yup": "^1.7.0", "zod": "^4.0.14" }, "peerDependencies": { "vue": "^3.4.26" }, "lint-staged": { "*.ts": [ "eslint --fix", "prettier --write", "vitest run related --passWithNoTests" ], "*.js": [ "eslint --fix", "vitest run related --passWithNoTests" ] } } ================================================ FILE: packages/i18n/CHANGELOG.md ================================================ # Change Log ## 5.0.0-beta.1 ## 5.0.0-beta.0 ### Major Changes - 04ff47c: feat: implement standard schema ## 4.15.1 ### Patch Changes - 0c8a539: fix(i18n): add missing messages for Slovenian locale ## 4.15.0 ## 4.14.7 ## 4.14.6 ## 4.14.5 ## 4.14.4 ### Patch Changes - 193a96f: feat: add setFallbackLocale for i18n closes #4872 - 4f88d85: fix: specify module type on package.json ## 4.14.3 ## 4.14.2 ## 4.14.1 ### Patch Changes - c118e86: fix: nested exports for the i18n package closes #4899 ## 4.14.0 ### Minor Changes - 404cf57: chore: bump release ### Patch Changes - 97cebd8: chore: add 'exports' field in package.json for all packages ## 4.13.2 ## 4.13.1 ## 4.13.0 ## 4.12.8 ## 4.12.7 ## 4.12.6 ## 4.12.5 ## 4.12.4 ## 4.12.3 ## 4.12.2 ## 4.12.1 ## 4.12.0 ## 4.11.8 ## 4.11.7 ## 4.11.6 ## 4.11.5 ## 4.11.4 ## 4.11.3 ## 4.11.2 ## 4.11.1 ## 4.11.0 ## 4.10.9 ## 4.10.8 ## 4.10.7 ## 4.10.6 ## 4.10.5 ## 4.10.4 ## 4.10.3 ## 4.10.2 ## 4.10.1 ## 4.10.0 ### Minor Changes - 7a548f42: chore: require vue 3.3 and refactor types ## 4.9.6 ## 4.9.5 ### Patch Changes - 16580b47: fix: allow labels to be localized with names closes #4268 ## 4.9.4 ## 4.9.3 ## 4.9.2 ## 4.9.1 ## 4.9.0 ## 4.8.6 ### Patch Changes - 6e0b0557: Introduced official nuxt module package ## 4.8.5 ### Patch Changes - 9048a238: fixed zod union issues not showing up as errors closes #4204 All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [4.8.3](https://github.com/logaretm/vee-validate/compare/v4.8.2...v4.8.3) (2023-03-15) **Note:** Version bump only for package @vee-validate/i18n ## [4.8.2](https://github.com/logaretm/vee-validate/compare/v4.8.1...v4.8.2) (2023-03-14) ### Bug Fixes - do not use name as a default label for useField closes [#4164](https://github.com/logaretm/vee-validate/issues/4164) ([d5acff7](https://github.com/logaretm/vee-validate/commit/d5acff719797c77ba4ff3be5f78c4a45374f9809)) ## [4.8.1](https://github.com/logaretm/vee-validate/compare/v4.8.0...v4.8.1) (2023-03-12) **Note:** Version bump only for package @vee-validate/i18n # [4.8.0](https://github.com/logaretm/vee-validate/compare/v4.7.4...v4.8.0) (2023-03-12) ### Features - Better Yup and Zod typing with output types and input inference ([#4064](https://github.com/logaretm/vee-validate/issues/4064)) ([3820a5b](https://github.com/logaretm/vee-validate/commit/3820a5b8eb3f8c6cd9239057746ccfb4b2e57e76)) ## [4.7.4](https://github.com/logaretm/vee-validate/compare/v4.7.3...v4.7.4) (2023-02-07) ### Bug Fixes - pass the field label as a seperate value closes [#4097](https://github.com/logaretm/vee-validate/issues/4097) ([89f8689](https://github.com/logaretm/vee-validate/commit/89f8689b673be27f0fc221d6c096efa11dacd3e6)) - wrong and missing RU translations, incorrect order ([#3987](https://github.com/logaretm/vee-validate/issues/3987)) ([1be36ab](https://github.com/logaretm/vee-validate/commit/1be36aba9add96c199f93f8e74f7c422e7e9ae1f)) ### Features - export i18n types closes [#4106](https://github.com/logaretm/vee-validate/issues/4106) ([c65ead8](https://github.com/logaretm/vee-validate/commit/c65ead874323a0bd58f96461f1037cb150cbdc7d)) ## [4.7.3](https://github.com/logaretm/vee-validate/compare/v4.7.2...v4.7.3) (2022-11-13) ### Bug Fixes - rename old excluded with not_one_of closes [#3993](https://github.com/logaretm/vee-validate/issues/3993) ([7fc5077](https://github.com/logaretm/vee-validate/commit/7fc50773275c9c65cdbb0735d0b14dfe7ffca227)) ## [4.7.2](https://github.com/logaretm/vee-validate/compare/v4.7.1...v4.7.2) (2022-11-02) **Note:** Version bump only for package @vee-validate/i18n ## [4.7.1](https://github.com/logaretm/vee-validate/compare/v4.7.0...v4.7.1) (2022-10-23) ### Bug Fixes - wrong placeholder in CS localization ([#3959](https://github.com/logaretm/vee-validate/issues/3959)) ([a4603fa](https://github.com/logaretm/vee-validate/commit/a4603fab9647918b6776072b3450aa6e0378660a)) # [4.7.0](https://github.com/logaretm/vee-validate/compare/v4.6.10...v4.7.0) (2022-10-09) **Note:** Version bump only for package @vee-validate/i18n ## [4.6.10](https://github.com/logaretm/vee-validate/compare/v4.6.9...v4.6.10) (2022-09-30) **Note:** Version bump only for package @vee-validate/i18n ## [4.6.9](https://github.com/logaretm/vee-validate/compare/v4.6.8...v4.6.9) (2022-09-19) **Note:** Version bump only for package @vee-validate/i18n ## [4.6.8](https://github.com/logaretm/vee-validate/compare/v4.6.7...v4.6.8) (2022-09-19) **Note:** Version bump only for package @vee-validate/i18n ## [4.6.7](https://github.com/logaretm/vee-validate/compare/v4.6.6...v4.6.7) (2022-08-27) **Note:** Version bump only for package @vee-validate/i18n ## [4.6.6](https://github.com/logaretm/vee-validate/compare/v4.6.5...v4.6.6) (2022-08-16) **Note:** Version bump only for package @vee-validate/i18n ## [4.6.5](https://github.com/logaretm/vee-validate/compare/v4.6.4...v4.6.5) (2022-08-11) **Note:** Version bump only for package @vee-validate/i18n ## [4.6.4](https://github.com/logaretm/vee-validate/compare/v4.6.3...v4.6.4) (2022-08-07) **Note:** Version bump only for package @vee-validate/i18n ## [4.6.3](https://github.com/logaretm/vee-validate/compare/v4.6.2...v4.6.3) (2022-08-07) **Note:** Version bump only for package @vee-validate/i18n ## [4.6.2](https://github.com/logaretm/vee-validate/compare/v4.6.1...v4.6.2) (2022-07-17) **Note:** Version bump only for package @vee-validate/i18n ## [4.6.1](https://github.com/logaretm/vee-validate/compare/v4.6.0...v4.6.1) (2022-07-12) **Note:** Version bump only for package @vee-validate/i18n # [4.6.0](https://github.com/logaretm/vee-validate/compare/v4.5.11...v4.6.0) (2022-07-11) ### Bug Fixes - added argument order for digits rule in ja.json closes [#3780](https://github.com/logaretm/vee-validate/issues/3780) ([9385457](https://github.com/logaretm/vee-validate/commit/938545765c825eead8182202faebfafdebd400c8)) - sk interpolation for min and digits rules closes [#3788](https://github.com/logaretm/vee-validate/issues/3788) ([778c52e](https://github.com/logaretm/vee-validate/commit/778c52e90afa7b89c6ede07214264b92dec0112a)) ### Features - **locale:** add km (khmer) locale ([#3812](https://github.com/logaretm/vee-validate/issues/3812)) ([b1ee664](https://github.com/logaretm/vee-validate/commit/b1ee664a21db211089a96f76966a6db21c9c7920)) ## [4.5.11](https://github.com/logaretm/vee-validate/compare/v4.5.10...v4.5.11) (2022-04-10) **Note:** Version bump only for package @vee-validate/i18n ## [4.5.10](https://github.com/logaretm/vee-validate/compare/v4.5.9...v4.5.10) (2022-03-08) **Note:** Version bump only for package @vee-validate/i18n ## [4.5.9](https://github.com/logaretm/vee-validate/compare/v4.5.8...v4.5.9) (2022-02-22) **Note:** Version bump only for package @vee-validate/i18n ## [4.5.8](https://github.com/logaretm/vee-validate/compare/v4.5.7...v4.5.8) (2022-01-23) **Note:** Version bump only for package @vee-validate/i18n ## [4.5.7](https://github.com/logaretm/vee-validate/compare/v4.5.6...v4.5.7) (2021-12-07) **Note:** Version bump only for package @vee-validate/i18n ## [4.5.6](https://github.com/logaretm/vee-validate/compare/v4.5.5...v4.5.6) (2021-11-17) ### Bug Fixes - improved et locale ([#3584](https://github.com/logaretm/vee-validate/issues/3584)) ([60e6f30](https://github.com/logaretm/vee-validate/commit/60e6f307a4039e84721741e31f0148e5b0628696)) ## [4.5.5](https://github.com/logaretm/vee-validate/compare/v4.5.4...v4.5.5) (2021-11-01) **Note:** Version bump only for package @vee-validate/i18n ## [4.5.4](https://github.com/logaretm/vee-validate/compare/v4.5.3...v4.5.4) (2021-10-20) **Note:** Version bump only for package @vee-validate/i18n ## [4.5.3](https://github.com/logaretm/vee-validate/compare/v4.5.2...v4.5.3) (2021-10-17) **Note:** Version bump only for package @vee-validate/i18n ## [4.5.2](https://github.com/logaretm/vee-validate/compare/v4.5.1...v4.5.2) (2021-09-30) **Note:** Version bump only for package @vee-validate/i18n ## [4.5.1](https://github.com/logaretm/vee-validate/compare/v4.5.0...v4.5.1) (2021-09-29) ### Bug Fixes - **i18n:** field name in default error message ([#3506](https://github.com/logaretm/vee-validate/issues/3506)) ([f1f5127](https://github.com/logaretm/vee-validate/commit/f1f51279d7cc343e7d3226fda025259e59945cfa)) # [4.5.0](https://github.com/logaretm/vee-validate/compare/v4.4.11...v4.5.0) (2021-09-26) ### Features - added language specific default ([#3501](https://github.com/logaretm/vee-validate/issues/3501)) ([debdee0](https://github.com/logaretm/vee-validate/commit/debdee032030a3209af8df34f710361150c924c2)) ## [4.4.11](https://github.com/logaretm/vee-validate/compare/v4.4.10...v4.4.11) (2021-09-11) **Note:** Version bump only for package @vee-validate/i18n ## [4.4.10](https://github.com/logaretm/vee-validate/compare/v4.4.9...v4.4.10) (2021-08-31) **Note:** Version bump only for package @vee-validate/i18n ## [4.4.9](https://github.com/logaretm/vee-validate/compare/v4.4.8...v4.4.9) (2021-08-05) **Note:** Version bump only for package @vee-validate/i18n ## [4.4.8](https://github.com/logaretm/vee-validate/compare/v4.4.7...v4.4.8) (2021-07-31) **Note:** Version bump only for package @vee-validate/i18n ## [4.4.7](https://github.com/logaretm/vee-validate/compare/v4.4.6...v4.4.7) (2021-07-20) ### Features - expose FieldContext type closes [#3398](https://github.com/logaretm/vee-validate/issues/3398) ([a6e4c0a](https://github.com/logaretm/vee-validate/commit/a6e4c0ac580d4145c72118ac535bfa082c771068)) ## [4.4.6](https://github.com/logaretm/vee-validate/compare/v4.4.5...v4.4.6) (2021-07-08) ## [4.4.5](https://github.com/logaretm/vee-validate/compare/v4.4.4...v4.4.5) (2021-06-13) **Note:** Version bump only for package @vee-validate/i18n ## [4.4.4](https://github.com/logaretm/vee-validate/compare/v4.4.3...v4.4.4) (2021-06-05) ## [4.4.3](https://github.com/logaretm/vee-validate/compare/v4.4.2...v4.4.3) (2021-06-02) **Note:** Version bump only for package @vee-validate/i18n ## [4.4.2](https://github.com/logaretm/vee-validate/compare/v4.4.1...v4.4.2) (2021-05-28) **Note:** Version bump only for package @vee-validate/i18n ## [4.4.1](https://github.com/logaretm/vee-validate/compare/v4.4.0...v4.4.1) (2021-05-24) **Note:** Version bump only for package @vee-validate/i18n # [4.4.0](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.2...v4.4.0) (2021-05-23) ### Bug Fixes - export the URL rule closes [#3310](https://github.com/logaretm/vee-validate/issues/3310) ([50b6b64](https://github.com/logaretm/vee-validate/commit/50b6b64bf0b2b9a905946ed0ef7b8252501b0ccb)) # [4.4.0-alpha.2](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.1...v4.4.0-alpha.2) (2021-05-14) **Note:** Version bump only for package @vee-validate/i18n # [4.4.0-alpha.1](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.0...v4.4.0-alpha.1) (2021-05-14) **Note:** Version bump only for package @vee-validate/i18n # [4.4.0-alpha.0](https://github.com/logaretm/vee-validate/compare/v4.3.6...v4.4.0-alpha.0) (2021-05-14) **Note:** Version bump only for package @vee-validate/i18n ## [4.3.6](https://github.com/logaretm/vee-validate/compare/v4.3.5...v4.3.6) (2021-05-08) **Note:** Version bump only for package @vee-validate/i18n ## [4.3.5](https://github.com/logaretm/vee-validate/compare/v4.3.4...v4.3.5) (2021-05-01) **Note:** Version bump only for package @vee-validate/i18n ## [4.3.4](https://github.com/logaretm/vee-validate/compare/v4.3.3...v4.3.4) (2021-04-27) **Note:** Version bump only for package @vee-validate/i18n ## [4.3.3](https://github.com/logaretm/vee-validate/compare/v4.3.2...v4.3.3) (2021-04-22) **Note:** Version bump only for package @vee-validate/i18n ## [4.3.2](https://github.com/logaretm/vee-validate/compare/v4.3.1...v4.3.2) (2021-04-21) **Note:** Version bump only for package @vee-validate/i18n ## [4.3.1](https://github.com/logaretm/vee-validate/compare/v4.3.0...v4.3.1) (2021-04-18) **Note:** Version bump only for package @vee-validate/i18n # [4.3.0](https://github.com/logaretm/vee-validate/compare/v4.2.4...v4.3.0) (2021-04-07) ### Bug Fixes - wrong string format in Japanese translation file ([#3257](https://github.com/logaretm/vee-validate/issues/3257)) ([99dbdb2](https://github.com/logaretm/vee-validate/commit/99dbdb288accfe0257d7c1073d4aeaa291eb1d6b)) ### Features - **rules:** add url validator ([#3253](https://github.com/logaretm/vee-validate/issues/3253)) ([1fad5bb](https://github.com/logaretm/vee-validate/commit/1fad5bb5e0f3264386bc8e40beeb4cdae2a832cb)) ## [4.2.4](https://github.com/logaretm/vee-validate/compare/v4.2.3...v4.2.4) (2021-03-26) **Note:** Version bump only for package @vee-validate/i18n ## [4.2.3](https://github.com/logaretm/vee-validate/compare/v4.2.2...v4.2.3) (2021-03-22) **Note:** Version bump only for package @vee-validate/i18n ## [4.2.2](https://github.com/logaretm/vee-validate/compare/v4.2.1...v4.2.2) (2021-03-03) **Note:** Version bump only for package @vee-validate/i18n ## [4.2.1](https://github.com/logaretm/vee-validate/compare/v4.2.0...v4.2.1) (2021-02-26) **Note:** Version bump only for package @vee-validate/i18n # [4.2.0](https://github.com/logaretm/vee-validate/compare/v4.1.20...v4.2.0) (2021-02-24) **Note:** Version bump only for package @vee-validate/i18n ## [4.1.20](https://github.com/logaretm/vee-validate/compare/v4.1.19...v4.1.20) (2021-02-24) **Note:** Version bump only for package @vee-validate/i18n ## [4.1.19](https://github.com/logaretm/vee-validate/compare/v4.1.18...v4.1.19) (2021-02-16) **Note:** Version bump only for package @vee-validate/i18n ## [4.1.17](https://github.com/logaretm/vee-validate/compare/v3.2.0...v4.1.17) (2021-02-08) ### Bug Fixes - add positional information to the zh_CN locale closes [#2898](https://github.com/logaretm/vee-validate/issues/2898) ([f5f44eb](https://github.com/logaretm/vee-validate/commit/f5f44ebb523db0f4d726cb8a07881d7c981c785c)) - publish the correct i18n typing and json files ([c77b1fc](https://github.com/logaretm/vee-validate/commit/c77b1fcd671fda4d5e975b77bf97a0ee22209c4f)) - update placeholder format for locales closes [#2871](https://github.com/logaretm/vee-validate/issues/2871) closes [#2875](https://github.com/logaretm/vee-validate/issues/2875) ([1cf8404](https://github.com/logaretm/vee-validate/commit/1cf840465e1fc09b09d8830673176530585de355)) ### Features - added `validateOnMount` prop to `Field` and `Form` components ([#2938](https://github.com/logaretm/vee-validate/issues/2938)) ([3a0d878](https://github.com/logaretm/vee-validate/commit/3a0d878e453163f305acc87c5d4c93812f77f340)) - added new loadLocaleFromURL function ([e7ba3db](https://github.com/logaretm/vee-validate/commit/e7ba3dbc82bed48d425f10771f4f5d11e767b042)) - full param interpolation ([#2880](https://github.com/logaretm/vee-validate/issues/2880)) ([0576504](https://github.com/logaretm/vee-validate/commit/0576504bb36d3d2583ecd8c6855a1ab39a4d0c34)) - invoke generateMessage handler for local functions closes [#2893](https://github.com/logaretm/vee-validate/issues/2893) ([e9fe773](https://github.com/logaretm/vee-validate/commit/e9fe77365877edda51548c9539ec085fff91586b)) - **i18n:** added setLocale function ([b5a1849](https://github.com/logaretm/vee-validate/commit/b5a184983958e0a537be89b370c9c15ff50b25f6)) # [4.1.0](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.3...@vee-validate/i18n@4.1.0) (2021-02-07) ### Features - added new loadLocaleFromURL function ([e7ba3db](https://github.com/logaretm/vee-validate/commit/e7ba3dbc82bed48d425f10771f4f5d11e767b042)) ## [4.0.3](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.2...@vee-validate/i18n@4.0.3) (2021-02-06) **Note:** Version bump only for package @vee-validate/i18n ## [4.0.2](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.1...@vee-validate/i18n@4.0.2) (2020-12-20) **Note:** Version bump only for package @vee-validate/i18n ## [4.0.1](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0...@vee-validate/i18n@4.0.1) (2020-11-25) **Note:** Version bump only for package @vee-validate/i18n # [4.0.0](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0-beta.2...@vee-validate/i18n@4.0.0) (2020-11-16) **Note:** Version bump only for package @vee-validate/i18n # [4.0.0-beta.2](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0-beta.1...@vee-validate/i18n@4.0.0-beta.2) (2020-11-04) **Note:** Version bump only for package @vee-validate/i18n # [4.0.0-beta.1](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0-beta.0...@vee-validate/i18n@4.0.0-beta.1) (2020-10-06) ### Features - added `validateOnMount` prop to `Field` and `Form` components ([#2938](https://github.com/logaretm/vee-validate/issues/2938)) ([3a0d878](https://github.com/logaretm/vee-validate/commit/3a0d878e453163f305acc87c5d4c93812f77f340)) # [4.0.0-beta.0](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0-alpha.7...@vee-validate/i18n@4.0.0-beta.0) (2020-10-01) **Note:** Version bump only for package @vee-validate/i18n # [4.0.0-alpha.7](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0-alpha.6...@vee-validate/i18n@4.0.0-alpha.7) (2020-09-15) ### Bug Fixes - add positional information to the zh_CN locale closes [#2898](https://github.com/logaretm/vee-validate/issues/2898) ([f5f44eb](https://github.com/logaretm/vee-validate/commit/f5f44ebb523db0f4d726cb8a07881d7c981c785c)) ### Features - invoke generateMessage handler for local functions closes [#2893](https://github.com/logaretm/vee-validate/issues/2893) ([e9fe773](https://github.com/logaretm/vee-validate/commit/e9fe77365877edda51548c9539ec085fff91586b)) # [4.0.0-alpha.6](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0-alpha.5...@vee-validate/i18n@4.0.0-alpha.6) (2020-08-29) ### Features - full param interpolation ([#2880](https://github.com/logaretm/vee-validate/issues/2880)) ([0576504](https://github.com/logaretm/vee-validate/commit/0576504bb36d3d2583ecd8c6855a1ab39a4d0c34)) # [4.0.0-alpha.5](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0-alpha.4...@vee-validate/i18n@4.0.0-alpha.5) (2020-08-28) ### Bug Fixes - update placeholder format for locales closes [#2871](https://github.com/logaretm/vee-validate/issues/2871) closes [#2875](https://github.com/logaretm/vee-validate/issues/2875) ([1cf8404](https://github.com/logaretm/vee-validate/commit/1cf840465e1fc09b09d8830673176530585de355)) # [4.0.0-alpha.4](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0-alpha.3...@vee-validate/i18n@4.0.0-alpha.4) (2020-07-27) **Note:** Version bump only for package @vee-validate/i18n # [4.0.0-alpha.3](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0-alpha.2...@vee-validate/i18n@4.0.0-alpha.3) (2020-07-23) ### Bug Fixes - publish the correct i18n typing and json files ([c77b1fc](https://github.com/logaretm/vee-validate/commit/c77b1fcd671fda4d5e975b77bf97a0ee22209c4f)) # [4.0.0-alpha.2](https://github.com/logaretm/vee-validate/compare/@vee-validate/i18n@4.0.0-alpha.1...@vee-validate/i18n@4.0.0-alpha.2) (2020-07-23) ### Features - **i18n:** added setLocale function ([b5a1849](https://github.com/logaretm/vee-validate/commit/b5a184983958e0a537be89b370c9c15ff50b25f6)) # 4.0.0-alpha.1 (2020-07-18) **Note:** Version bump only for package @vee-validate/i18n ================================================ FILE: packages/i18n/README.md ================================================ # @vee-validate/i18n

> Localization module for vee-validate ## What's this VeeValidate v4 breaks up the parts that made it a popular solution for form validation into it isolated parts. The core `vee-validate` package no longer includes logic for localization. This is where this package comes in. ## Installation ```sh yarn add @vee-validate/i18n # or with npm npm install @vee-validate/i18n ``` ## Usage import the `localize()` function from `@vee-validate/i18n` which returns a message generator function: ```js import { defineRule, configure } from 'vee-validate'; import { required } from '@vee-validate/rules'; import { localize } from '@vee-validate/i18n'; // Define the rule globally defineRule('required', required); configure({ // Generates an English message locale generator generateMessage: localize('en', { messages: { required: 'This field is required', }, }), }); ``` If you have multiple locales in your application, you can add them like this: ```js import { defineRule, configure } from 'vee-validate'; import { required } from '@vee-validate/rules'; import { localize } from '@vee-validate/i18n'; // Define the rule globally defineRule('required', required); configure({ generateMessage: localize({ en: { messages: { required: 'This field is required', }, }, ar: { messages: { required: 'هذا الحقل مطلوب', }, }, }), }); ``` You can change the locale using `setLocale` function exported by the `@vee-validate/i18n` anywhere in your application: ```js import { setLocale } from '@vee-validate/i18n'; setLocale('ar'); ``` ## Available Languages To save you a lot of time translating `@vee-validate/rules` messages to your language, the awesome community around vee-validate already contributed over **40+ languages** that you can use directly in your application and get started quickly. The localized files include localized messages for all the global rules provided by `@vee-validate/rules` package. You can import the locales from their JSON directory like this: ```js import { configure } from 'vee-validate'; import { localize } from '@vee-validate/i18n'; import en from '@vee-validate/i18n/dist/locale/en.json'; import ar from '@vee-validate/i18n/dist/locale/ar.json'; configure({ generateMessage: localize({ en, ar, }), }); ``` You can view a list of the available languages by [checking the locale folder](https://github.com/logaretm/vee-validate/tree/main/packages/i18n/src/locale) ================================================ FILE: packages/i18n/package.json ================================================ { "name": "@vee-validate/i18n", "version": "5.0.0-beta.1", "description": "Localization module for VeeValidate", "author": "Abdelrahman Awad ", "homepage": "https://vee-validate.logaretm.com/v5/guide/i18n", "license": "MIT", "module": "dist/vee-validate-i18n.mjs", "unpkg": "dist/vee-validate-i18n.iife.js", "main": "dist/vee-validate-i18n.mjs", "types": "dist/vee-validate-i18n.d.ts", "type": "module", "exports": { ".": { "types": "./dist/vee-validate-i18n.d.ts", "import": "./dist/vee-validate-i18n.mjs", "require": "./dist/vee-validate-i18n.cjs" }, "./dist/locale/*.json": "./dist/locale/*.json" }, "sideEffects": false, "files": [ "dist/*.js", "dist/*.d.ts", "dist/*.cjs", "dist/*.mjs", "dist/locale/*.json" ], "repository": { "type": "git", "url": "https://github.com/logaretm/vee-validate.git", "directory": "packages/i18n" }, "scripts": { "test": "echo \"Error: run tests from root\" && exit 1" }, "bugs": { "url": "https://github.com/logaretm/vee-validate/issues" } } ================================================ FILE: packages/i18n/src/index.ts ================================================ import { isCallable, FieldValidationMetaInfo, ValidationMessageGenerator, merge } from '../../shared'; import { InterpolateOptions } from '../../shared/types'; import { interpolate } from './utils'; export { FieldValidationMetaInfo }; export type ValidationMessageTemplate = ValidationMessageGenerator | string; export interface PartialI18nDictionary { name?: string; messages?: Record; names?: Record; fields?: Record>; } export type RootI18nDictionary = Record; class Dictionary { public locale: string; public fallbackLocale?: string; private container: RootI18nDictionary; private interpolateOptions: InterpolateOptions; public constructor( locale: string, dictionary: RootI18nDictionary, interpolateOptions: InterpolateOptions = { prefix: '{', suffix: '}' }, ) { this.container = {}; this.locale = locale; this.interpolateOptions = interpolateOptions; this.merge(dictionary); } public resolve(ctx: FieldValidationMetaInfo, interpolateOptions?: InterpolateOptions) { let result = this.format(this.locale, ctx, interpolateOptions); if (!result && this.fallbackLocale && this.fallbackLocale !== this.locale) { result = this.format(this.fallbackLocale, ctx, interpolateOptions); } return result || this.getDefaultMessage(this.locale, ctx); } public getDefaultMessage(locale: string, ctx: FieldValidationMetaInfo) { const { label, name } = ctx; const fieldName = this.resolveLabel(locale, name, label); return `${fieldName} is not valid`; } public getLocaleDefault(locale: string, field: string): string | ValidationMessageGenerator | undefined { return this.container[locale]?.fields?.[field]?._default || this.container[locale]?.messages?._default; } public resolveLabel(locale: string, name: string, label?: string): string { if (label) { return this.container[locale]?.names?.[label] || label; } return this.container[locale]?.names?.[name] || name; } public format(locale: string, ctx: FieldValidationMetaInfo, interpolateOptions?: InterpolateOptions) { let message!: ValidationMessageTemplate | undefined; const { rule, form, label, name } = ctx; const fieldName = this.resolveLabel(locale, name, label); if (!rule) { message = this.getLocaleDefault(locale, name) || ''; return isCallable(message) ? message(ctx) : interpolate(message, { ...form, field: fieldName }, interpolateOptions ?? this.interpolateOptions); } // find if specific message for that field was specified. message = this.container[locale]?.fields?.[name]?.[rule.name] || this.container[locale]?.messages?.[rule.name]; if (!message) { message = this.getLocaleDefault(locale, name) || ''; } return isCallable(message) ? message(ctx) : interpolate( message, { ...form, field: fieldName, params: rule.params }, interpolateOptions ?? this.interpolateOptions, ); } public merge(dictionary: RootI18nDictionary) { merge(this.container, dictionary); } } const DICTIONARY: Dictionary = new Dictionary('en', {}); function localize(dictionary: RootI18nDictionary): ValidationMessageGenerator; function localize(locale: string, dictionary?: PartialI18nDictionary): ValidationMessageGenerator; function localize( locale: string, dictionary?: PartialI18nDictionary, interpolateOptions?: InterpolateOptions, ): ValidationMessageGenerator; function localize( locale: string | RootI18nDictionary, dictionary?: PartialI18nDictionary, interpolateOptions?: InterpolateOptions, ) { const generateMessage: ValidationMessageGenerator = ctx => { return DICTIONARY.resolve(ctx, interpolateOptions); }; if (typeof locale === 'string') { DICTIONARY.locale = locale; if (dictionary) { DICTIONARY.merge({ [locale]: dictionary }); } return generateMessage; } DICTIONARY.merge(locale); return generateMessage; } /** * Sets the locale */ function setLocale(locale: string) { DICTIONARY.locale = locale; } /** * Sets the fallback locale. */ function setFallbackLocale(locale: string) { DICTIONARY.fallbackLocale = locale; } /** * Loads a locale file from URL and merges it with the current dictionary */ async function loadLocaleFromURL(url: string) { try { const locale: { code: string; messages: Record } = await fetch(url, { headers: { 'content-type': 'application/json', }, }).then(res => res.json()); if (!locale.code) { // eslint-disable-next-line no-console console.error('Could not identify locale, ensure the locale file contains `code` field'); return; } localize({ [locale.code]: locale }); } catch (err) { // eslint-disable-next-line no-console console.error(`Failed to load locale `); } } export { localize, setLocale, loadLocaleFromURL, setFallbackLocale }; ================================================ FILE: packages/i18n/src/locale/ar.json ================================================ { "code": "ar", "messages": { "alpha": "{field} يجب ان يحتوي على حروف فقط", "alpha_num": "{field} قد يحتوي فقط على حروف وارقام", "alpha_dash": "{field} قد يحتوي على حروف او الرموز - و _", "alpha_spaces": "{field} قد يحتوي فقط على حروف ومسافات", "between": "قيمة {field} يجب ان تكون ما بين 0:{min} و 1:{max}", "confirmed": "{field} لا يماثل التأكيد", "digits": "{field} يجب ان تحتوي فقط على ارقام والا يزيد عددها عن 0:{length} رقم", "dimensions": "{field} يجب ان تكون بمقاس 0:{width} بكسل في 1:{height} بكسل", "email": "{field} يجب ان يكون بريدا اليكتروني صحيح", "not_one_of": "الحقل {field} غير صحيح", "ext": "نوع ملف {field} غير صحيح", "image": "{field} يجب ان تكون صورة", "integer": "الحقل {field} يجب ان يكون عدداً صحيحاً", "length": "حقل {field} يجب الا يزيد عن 0:{length}", "max_value": "قيمة الحقل {field} يجب ان تكون اصغر من 0:{min} او تساويها", "max": "الحقل {field} يجب ان يحتوي على 0:{length} حروف على الأكثر", "mimes": "نوع ملف {field} غير صحيح", "min_value": "قيمة الحقل {field} يجب ان تكون اكبر من 0:{min} او تساويها", "min": "الحقل {field} يجب ان يحتوي على 0:{length} حروف على الأقل", "numeric": "{field} يمكن ان يحتوي فقط على ارقام", "one_of": "الحقل {field} يجب ان يكون قيمة صحيحة", "regex": "الحقل {field} غير صحيح", "required": "{field} مطلوب", "required_if": "حقل {field} مطلوب", "size": "{field} يجب ان يكون اقل من 0:{size} كيلوبايت", "url": "حقل {field} ليس رابطاً صحيحاً" } } ================================================ FILE: packages/i18n/src/locale/az.json ================================================ { "code": "az", "messages": { "alpha": "{field} yalnız hərflərdən ibarət olmalıdır", "alpha_num": "{field} yalnız rəqəmlərdən ibarət ola bilər", "alpha_dash": "{field} yalnız hərflər, rəqəmlər və defisdən ibarət ola bilər", "alpha_spaces": "{field} yalnız rəqəmlərdən və ara simvolundan (space) ibarət ola bilər", "between": "{field} yalnız 0:{min} və 1:{max} arası ola bilər", "confirmed": "{field} təsdiqləməsi yalnışdır", "digits": "{field} 0:{length} rəqəmdən ibarət olmalıdır", "dimensions": "{field} ölçüsü 0:{width}x1:{height} piksel olmalıdır", "email": "{field} düzgün formatda daxil olunmalıdır", "not_one_of": "{field} göstəricisi yalnışdır", "ext": "{field} düzgün fayl formatında olmalıdır", "image": "{field} şəkil olmalıdır", "integer": "{field} göstəricisi tam ədəd olmalıdır", "max_value": "{field} göstəricisi ən çox 0:{max} və ya daha az olmalıdır", "max": "{field} uzunluğu ən çox 0:{length} simvoldan ibarət ola bilər", "mimes": "{field} formatı yalnışdır", "min_value": "{field} göstəricisi minimum 0:{min} və ya daha çox olmalıdır", "min": "{field} uzunluğu ən az 0:{length} simvoldan ibarət olmalıdır", "numeric": "{field} yalnız rəqəmlərdən ibarət olmalıdır", "one_of": "{field} göstəricisi yalnışdır", "regex": "{field} formatı yalnışdır", "required": "{field} əlavə etmək zəruridir", "required_if": "{field} əlavə etmək zəruridir", "size": "{field} həcmi 0:{size}KB və daha az olmalıdır" } } ================================================ FILE: packages/i18n/src/locale/bg.json ================================================ { "code": "bg", "messages": { "alpha": "Полето {field} може да съдържа само азбучни знаци", "alpha_num": "Полето {field} може да съдържа само буквено-цифрови символи", "alpha_dash": "Полето {field} може да съдържа буквено-цифрови знаци, както и тирета и долни черти", "alpha_spaces": "Полето {field} може да съдържа само азбучни знаци, както и интервали", "between": "Полето {field} може да е между 0:{min} и 1:{max}", "confirmed": "Потвърждението не съвпада за полето {field}", "digits": "Полето {field} трябва да е цифрово и да съдържа точно 0:{length} цифри", "dimensions": "Полето {field} трябва да е 0:{width} пиксела по 1:{height} пиксела", "email": "Полето {field} трябва да е коректен Email адрес", "not_one_of": "Полето {field} трябва да е с валидна стойност", "ext": "Полето {field} трябва да е валиден файл", "image": "Полето {field} трябва да е снимка", "integer": "Полето {field} трябва да е цяло число", "length": "Полето {field} трябва да е с дължилна 0:{length} знака", "max_value": "Полето {field} трябва да бъде 0:{max} или по-малко", "max": "Полето {field} не може да бъде по-голямо от 0:{length} знака", "mimes": "Полето {field} трябва да е валиден тип файл", "min_value": "Полето {field} трябва да бъде минимум 0:{min} или повече", "min": "Полето {field} трябва да съдържа минимум 0:{length} символа", "numeric": "Полето {field} може да съдържа само цифри", "one_of": "Полето {field} трябва да е валидна стойност", "regex": "Полето {field} съдържа невалиден формат", "required": "Полето {field} е задължително", "required_if": "Полето {field} е задължително", "size": "Размерът на файла за полето {field} трябва да е под 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/bn.json ================================================ { "code": "bn", "messages": { "alpha": "এই {field} ক্ষেত্রে কেবলমাত্র অক্ষর থাকতে পারে", "alpha_num": "এই {field} ক্ষেত্রে কেবলমাত্র অক্ষর, সংখ্যা থাকতে পারে", "alpha_dash": "এই {field} ক্ষেত্রে কেবলমাত্র অক্ষর, সংখ্যা, ড্যাশ এবং আন্ডারস্কোর থাকতে পারে", "alpha_spaces": "এই {field} ক্ষেত্রে কেবলমাত্র অক্ষর, সংখ্যা, ড্যাশ এবং আন্ডারস্কোর থাকতে পারে", "between": "এই {field} ক্ষেত্রটি 0:{min} এবং 1:{max} এর মধ্যে হতে হবে", "confirmed": "এই {field} ক্ষেত্রটি মিলছে না", "digits": "এই {field} ক্ষেত্রটি সংখ্যা হতে হবে এবং ঠিক 0:{length} অঙ্ক থাকতে হবে", "dimensions": "এই {field} ক্ষেত্রটি 0:{width} পিক্সেল 1:{height} পিক্সেল হতে হবে", "email": "এই {field} ক্ষেত্রটি একটি বৈধ ইমেল হতে হবে", "not_one_of": "এই {field} ক্ষেত্রটির মান বৈধ না", "ext": "এই {field} ক্ষেত্রটির ফাইল বৈধ না", "image": "এই {field} ক্ষেত্রটি একটি চিত্র হতে হবে", "integer": "এই {field} ক্ষেত্রটি পূর্ণসংখ্যা হতে হবে", "length": "এই {field} ক্ষেত্রটি 0:{length} দীর্ঘ হতে হবে", "max_value": "এই {field} ক্ষেত্রটি 0:{max} বা তার চেয়ে কম হতে হবে", "max": "এই {field} ক্ষেত্রটি 0:{length} অক্ষরের চেয়ে বেশি হওয়া উচিত নয়", "mimes": "এই {field} ক্ষেত্রের একটি বৈধ ফাইল প্রকার থাকতে হবে", "min_value": "এই {field} ক্ষেত্রটি 0:{min} বা তার বেশি হতে হবে", "min": "এই {field} ক্ষেত্রটি কমপক্ষে 0:{length} অক্ষর হতে হবে", "numeric": "এই {field} ক্ষেত্রে কেবলমাত্র সংখ্যা থাকতে পারে", "one_of": "এই {field} ক্ষেত্রটির মান বৈধ না", "regex": "এই {field} ক্ষেত্রটির বিন্যাস বৈধ না", "required_if": "এই {field} ক্ষেত্রটি অবশ্যক", "required": "এই {field} ক্ষেত্রটি অবশ্যক", "size": "এই {field} ক্ষেত্রের আকার 0:{size}KB এর চেয়ে কম হওয়া উচিত" } } ================================================ FILE: packages/i18n/src/locale/ca.json ================================================ { "code": "ca", "messages": { "alpha": "El camp {field} només ha de contenir lletres", "alpha_num": "El camp {field} només ha de contenir lletres i números", "alpha_dash": "El camp {field} només ha de contenir lletres, números i guions", "alpha_spaces": "El camp {field} només ha de contenir lletres i espais", "between": "El camp {field} ha d'estar entre 0:{min} i 1:{max}", "confirmed": "El camp {field} no coincideix amb el camp {target}", "digits": "El camp {field} ha de ser numèric i contenir exactament 0:{length} dígits", "dimensions": "El camp {field} ha de ser de 0:{width} píxels per 1:{height} píxels", "email": "El camp {field} ha de ser un correu electrònic vàlid", "not_one_of": "El camp {field} ha de ser un valor vàlid", "ext": "El camp {field} ha de ser un fitxer vàlid", "image": "El camp {field} ha de ser una imatge", "max_value": "El camp {field} ha de ser de 0:{max} o menys", "max": "El camp {field} no ha de ser major a 0:{length} caràcters", "mimes": "El camp {field} ha de ser un tipus de fitxer vàlid", "min_value": "El camp {field} ha de ser de 0:{min} o superior", "min": "El camp {field} ha de tenir almenys 0:{length} caràcters", "numeric": "El camp {field} ha de contenir només caràcters numèrics", "one_of": "El camp {field} ha de ser un valor vàlid", "regex": "El format del camp {field} no és vàlid", "required": "El camp {field} és obligatori", "required_if": "El camp {field} és obligatori", "size": "El camp {field} ha de ser menor a 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/ckb.json ================================================ { "code": "ckb", "messages": { "_default": "{field}ەکە دروست نییە", "alpha": " {field} دەکرێت تەنها کاراکتەرە ئەلفابێتیکەکانی تێدا بێت", "alpha_num": "{field} پێویستە تەنها کاراکتەری ئەلفابێتیکی ژمارەیی بێت", "alpha_dash": "{field} پێویستە تەنها ژمارەی یاخود داش و ئەندەرسکۆر بێت", "alpha_spaces": "{field} پێویستە تەنها کاراکتەری ئەلفابێتیک و بۆشایی لەخۆبگرێت", "between": "بەهای {field} پێویستە لە نێوان 0:{min} و 1:{max} بێت", "confirmed": "دووپاتکردنەوەی {field} پەسەند نەکراوە", "digits": "بەهای {field} پێویستە ژمارە بێت و 0:{length} دیجیت بێت", "dimensions": "{field} پێویستە 0:{width} پیکسڵ بە 1:{height} پیکسڵ بێت", "email": "{field} پێویستە لە جۆری ئیمەیڵی دروست بێت", "not_one_of": "الحقل {field} غير صحيح", "ext": "جۆری {field} لە فایلێکی دروست نییە", "image": "{field} پێویستە لە جۆری وێنە بێت", "integer": "بەهای {field} پێویستە ژمارە بێت", "length": "درێژی {field} پێویستە 0:{length} بێت", "max_value": "بەهای {field} پێویستە 0:{length} یان کەمتر بێت", "max": "بەهای {field} نابێت زیاتر بێت لە 0:{length} کاراکتەر", "mimes": "{field} پێویستە جۆرێک لە فایلە دروستەکان بێت", "min_value": "بەهای {field} پێویستە 0:{min} یان زیاتر بێت", "min": "درێژی {field} پێویستە کەمتر نەبێت لە 0:{length} کاراکتەر", "numeric": "{field} پێویستە تەنها ژمارە لەخۆبگرێت", "oneOf": "{field} یەکێک نییە لە بەها دروستەکان", "regex": "جۆری {field} دروست نییە", "required": "{field} داواکراوە", "required_if": "{field} داواکراوە", "size": "{field} پێویستە لە 0:{size}KB کەمتربێت", "url": "{field} URL ێکی دروست نییە" } } ================================================ FILE: packages/i18n/src/locale/cs.json ================================================ { "code": "cs", "messages": { "_default": "Pole {field} není platné", "alpha": "Pole {field} může obsahovat pouze písmena", "alpha_num": "Pole {field} může obsahovat pouze alfanumerické znaky", "alpha_dash": "Pole {field} může obsahovat pouze alfanumerické znaky, pomlčky nebo podtržítka", "alpha_spaces": "Pole {field} může obsahovat pouze alfanumerické znaky a mezery", "between": "Pole {field} musí být mezi 0:{min} a 1:{max}", "confirmed": "Kontrola pole {field} se neshoduje", "digits": "Pole {field} musí být číslo a musí obshovat přesně 0:{length} číslic", "dimensions": "{field} musí mít 0:{width} pixelů na 1:{height} pixelů", "email": "Pole {field} musí být validní email", "not_one_of": "{field} musí být správná hodnota", "ext": "{field} musí být validní soubor", "image": "{field} musí být obrázek", "integer": "Pole {field} musí být celé číslo", "length": "Pole {field} musí mít délku 0:{length}", "max_value": "Pole {field} musí být 0:{max}, nebo mensí", "max": "{field} nesmí být delší než 0:{length} znaků", "mimes": "Pole {field} musí být správný typ souboru", "min_value": "Pole {field} musí být 0:{min}, nebo více", "min": "Pole {field} musí obsahovat alespoň 0:{length} znaků", "numeric": "Pole {field} může obsahovat pouze číslice", "one_of": "{field} musí být správná hodnota", "regex": "Pole {field} není vyplněno správně", "required_if": "Pole {field} je povinné", "required": "Pole {field} je povinné", "size": "Soubor {field} musí být menší než 0:{size}KB", "url": "Pole {field} není platná adresa URL" } } ================================================ FILE: packages/i18n/src/locale/cy.json ================================================ { "code": "cy", "messages": { "alpha": "Gall {field} ond gynnwys llythrennau'r wyddor", "alpha_num": "Gall {field} ond gynnwys llythrennau'r wyddor a rhifau", "alpha_dash": "Gall {field} ond gynnwys llythrennau'r wyddor a chysylltnod", "alpha_spaces": "Gall {field} ond gynnwys llythrennau'r wyddor a gofod", "between": "Mae'n rhaid i {field} fod rhwng 0:{min} a 1:{max}", "confirmed": "Dyw {field} ddim yn gyfatebol", "digits": "Mae'n rhaid i {field} fod yn ddigidau 0:{length} o hyd", "dimensions": "Mae'n rhaid i {field} fod yn 0:{width} pixel llydan, a 1:{height} pixel o uchder", "email": "Mae'n rhaid i {field} fod yn ebost ddilys", "not_one_of": "Dyw {field} ddim yn fewnbwn ddilys", "ext": "Dyw {field} ddim yn ffeil dilys", "image": "Mae'n rhaid i {field} fod yn lun", "integer": "Mae'n rhaid i {field} fod yn gyfanrif", "length": "Mae'n rhaid i {field}", "max_value": "Mae'n rhaid i {field} fod yn 0:{max} o hyd neu'n llai", "max": "Mae'n rhaid i {field} fod yn 0:{length} o hyd neu'n fwy", "mimes": "Mae'n rhaid i {field} fod yn ffeil ddilys", "min_value": "Mae'n rhaid i {field} fod yn 0:{min} o hyd neu'n fwy", "min": "Mae'n rhaid i {field} fod yn 0:{length} o hyd neu'n fwy", "numeric": "Mae'n rhaid i {field} fod yn rhif", "one_of": "Dyw {field} ddim yn fewnbwn dilys", "regex": "Dyw {field} ddim yn fformat dilys", "required_if": "Mae {field} yn angenrheidiol", "required": "Mae {field} yn angenrheidiol", "size": "Mae'n rhaid i faint {field} for yn llai na 0:{size}KB", "url": "Dyw {field} ddim yn URL dilys" } } ================================================ FILE: packages/i18n/src/locale/da.json ================================================ { "code": "da", "messages": { "_default": "{field} er ikke gyldigt", "alpha": "{field} må kun indeholde bogstaver", "alpha_num": "{field} må kun indeholde tal og bogstaver", "alpha_dash": "{field} må kun indeholde tal, bogstaver, bindestreger og underscores", "alpha_spaces": "{field} må kun indeholde bogstaver og mellemrum", "between": "{field} skal være mellem 0:{min} og 1:{max}", "confirmed": "{field} skal matche {target}", "digits": "{field} skal være et tal på 0:{length} cifre", "dimensions": "{field} skal være 0:{width} pixels gange 1:{height} pixels", "email": "{field} skal være en gyldig email", "not_one_of": "{field} skal være en gyldig værdi", "ext": "{field} skal være en gyldig filtype", "image": "{field} skal være et billede", "integer": "{field} skal være et heltal", "length": "{field} skal være 0:{length} karakterer langt", "max_value": "{field} må maksimalt være 0:{max} karakterer eller mindre", "max": "{field} må maksimalt være 0:{length} karakterer", "mimes": "{field} skal være en gyldig filtype", "min_value": "{field} skal minimum være 0:{min} karakterer eller mere", "min": "{field} skal minimum være 0:{length} karakterer", "numeric": "{field} skal være numerisk", "one_of": "{field} skal være en gyldig værdi", "regex": "{field} skal have et gyldigt format", "required_if": "{field} skal udfyldes", "required": "{field} skal udfyldes", "size": "{field} må maksimalt have en størrelse på 0:{size}KB", "url": "{field} er ikke en gyldig URL" } } ================================================ FILE: packages/i18n/src/locale/de.json ================================================ { "code": "de", "messages": { "_default": "{field} ist ungültig", "alpha": "{field} darf nur alphabetische Zeichen enthalten", "alpha_dash": "{field} darf alphanumerische Zeichen sowie Striche und Unterstriche enthalten", "alpha_num": "{field} darf nur alphanumerische Zeichen enthalten", "alpha_spaces": "{field} darf nur alphanumerische Zeichen und Leerzeichen enthalten", "between": "{field} muss zwischen 0:{min} und 1:{max} liegen", "confirmed": "Die Bestätigung von {field} stimmt nicht überein", "digits": "{field} muss numerisch sein und exakt 0:{length} Ziffern enthalten", "dimensions": "{field} muss 0:{width} x 1:{height} Bildpunkte groß sein", "email": "{field} muss eine gültige E-Mail-Adresse sein", "not_one_of": "{field} muss ein gültiger Wert sein", "ext": "{field} muss eine gültige Datei sein", "image": "{field} muss eine Grafik sein", "one_of": "{field} muss ein gültiger Wert sein", "integer": "{field} muss eine ganze Zahl sein", "length": "Die Länge von {field} muss 0:{length} sein", "max": "{field} darf nicht länger als 0:{length} Zeichen sein", "max_value": "{field} darf maximal 0:{max} sein", "mimes": "{field} muss einen gültigen Dateityp haben", "min": "{field} muss mindestens 0:{length} Zeichen lang sein", "min_value": "{field} muss mindestens 0:{min} sein", "numeric": "{field} darf nur numerische Zeichen enthalten", "regex": "Das Format von {field} ist ungültig", "required": "{field} ist ein Pflichtfeld", "required_if": "{field} ist ein Pflichtfeld", "size": "{field} muss kleiner als 0:{size}KB sein", "url": "{field} ist keine gültige URL" } } ================================================ FILE: packages/i18n/src/locale/el.json ================================================ { "code": "el", "messages": { "alpha": "{field} πρέπει να περιέχει μόνο αλφαβητικούς χαρακτήρες", "alpha_num": "{field} πρέπει να περιέχει μόνο αλφαριθμητικούς χαρακτήρες", "alpha_dash": "{field} μπορεί να περιέχει αλφαριθμητικούς χαρακτήρες, παύλες και κάτω παύλες", "alpha_spaces": "{field} μπορεί να περιέχει μόνο αλφαβητικούς χαρακτήρες και κενά", "between": "{field} πρέπει να είναι μεταξύ 0:{min} καί 1:{max}", "confirmed": "{field} δεν ταιριάζει με {target}", "digits": "{field} πρέπει να είναι αριθμός και να περιέχει 0:{length} ψηφία", "dimensions": "{field} πρέπει να είναι 0:{width} pixels επί 1:{height} pixels", "email": "{field} πρέπει να είναι έγκυρο email", "not_one_of": "{field} πρέπει να είναι έγκυρη τιμή", "ext": "{field} πρέπει να είναι έγκυρο αρχείο", "image": "{field} πρέπει να είναι εικόνα", "integer": "{field} πρέπει να είναι ακέραιος αριθμός", "length": "{field} πρέπει να είναι 0:{length} χαρακτήρες", "max_value": "{field} πρέπει να είναι 0:{max} ή λιγότερο", "max": "{field} δεν πρέπει να υπερβαίνει τους 0:{length} χαρακτήρες", "mimes": "{field} πρέπει να είναι έγκυρο αρχείο ΜΙΜΕ", "min_value": "{field} πρέπει να είναι 0:{min} ή περισσότερο", "min": "{field} πρέπει να είναι τουλάχιστον 0:{length} χαρακτήρες", "numeric": "{field} πρέπει να περιέχει μόνο αριθμούς", "one_of": "{field} πρέπει να είναι έγκυρη τιμή", "regex": "{field} δεν είναι έγκυρο", "required": "{field} δεν έχει συμπληρωθεί", "required_if": "{field} δεν έχει συμπληρωθεί", "size": "{field} πρέπει να μην υπερβαίνει τα 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/en.json ================================================ { "code": "en", "messages": { "_default": "The {field} is not valid", "alpha": "The {field} field may only contain alphabetic characters", "alpha_num": "The {field} field may only contain alpha-numeric characters", "alpha_dash": "The {field} field may contain alpha-numeric characters as well as dashes and underscores", "alpha_spaces": "The {field} field may only contain alphabetic characters as well as spaces", "between": "The {field} field must be between 0:{min} and 1:{max}", "confirmed": "The {field} field confirmation does not match", "digits": "The {field} field must be numeric and exactly contain 0:{length} digits", "dimensions": "The {field} field must be 0:{width} pixels by 1:{height} pixels", "email": "The {field} field must be a valid email", "not_one_of": "The {field} field is not a valid value", "ext": "The {field} field is not a valid file", "image": "The {field} field must be an image", "integer": "The {field} field must be an integer", "length": "The {field} field must be 0:{length} long", "max_value": "The {field} field must be 0:{max} or less", "max": "The {field} field may not be greater than 0:{length} characters", "mimes": "The {field} field must have a valid file type", "min_value": "The {field} field must be 0:{min} or more", "min": "The {field} field must be at least 0:{length} characters", "numeric": "The {field} field may only contain numeric characters", "one_of": "The {field} field is not a valid value", "regex": "The {field} field format is invalid", "required_if": "The {field} field is required", "required": "The {field} field is required", "size": "The {field} field size must be less than 0:{size}KB", "url": "The {field} field is not a valid URL" } } ================================================ FILE: packages/i18n/src/locale/es.json ================================================ { "code": "es", "messages": { "alpha": "El campo {field} solo debe contener letras", "alpha_dash": "El campo {field} solo debe contener letras, números y guiones", "alpha_num": "El campo {field} solo debe contener letras y números", "alpha_spaces": "El campo {field} solo debe contener letras y espacios", "between": "El campo {field} debe estar entre 0:{min} y 1:{max}", "confirmed": "El campo {field} no coincide", "digits": "El campo {field} debe ser numérico y contener exactamente 0:{length} dígitos", "dimensions": "El campo {field} debe ser de 0:{width} píxeles por 1:{height} píxeles", "email": "El campo {field} debe ser un correo electrónico válido", "not_one_of": "El campo {field} debe ser un valor válido", "ext": "El campo {field} debe ser un archivo válido", "image": "El campo {field} debe ser una imagen", "one_of": "El campo {field} debe ser un valor válido", "integer": "El campo {field} debe ser un entero", "length": "El largo del campo {field} debe ser 0:{length}", "max": "El campo {field} no debe ser mayor a 0:{length} caracteres", "max_value": "El campo {field} debe de ser 0:{max} o menor", "mimes": "El campo {field} debe ser un tipo de archivo válido", "min": "El campo {field} debe tener al menos 0:{length} caracteres", "min_value": "El campo {field} debe ser 0:{min} o superior", "numeric": "El campo {field} debe contener solo caracteres numéricos", "regex": "El formato del campo {field} no es válido", "required": "El campo {field} es obligatorio", "required_if": "El campo {field} es obligatorio", "size": "El campo {field} debe ser menor a 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/et.json ================================================ { "code": "et", "messages": { "_default": "{field} on sobimatu", "alpha": "{field} võib sisaldada ainult tähti", "alpha_num": "{field} võib sisaldada ainult tähti ja numbreid", "alpha_dash": "{field} võib sisaldada ainult tähti, numbreid, kriipse ja alakriipse", "alpha_spaces": "{field} võib sisaldada ainult tähti ja tühikuid", "between": "{field} peab jääma vahemikku 0:{min} kuni 1:{max}", "confirmed": "{field} on kontrollist erinev", "digits": "{field} peab koosnema täpselt 0:{length}-st numbrist", "dimensions": "{field} peab olema 0:{width} korda 1:{height} pikslit suur", "email": "{field} peab olema e-maili aadress", "not_one_of": "{field} ei oma sobivat väärtust", "ext": "{field} peab olema sobiv fail", "image": "{field} peab olema pilt", "integer": "{field} peab olema täisarv", "is": "{field} peab olema 0:{other}", "is_not": "{field} ei tohi olla 0:{other}", "length": "{field} peab olema 0:{length} ühikut pikk", "max_value": "{field} peab olema 0:{max} või väiksem", "max": "{field} ei tohi olla pikem kui 0:{length} tähemärki", "mimes": "{field} peab olema sobivat tüüpi fail", "min_value": "{field} peab olema 0:{min} või suurem", "min": "{field} peab olema vähemalt 0:{length} tähemärki pikk", "numeric": "{field} võib sisaldada ainult numbreid", "one_of": "{field} ei oma sobivat väärtust", "regex": "{field} pole sobival kujul", "required": "{field} on nõutud väli", "required_if": "{field} on nõutud väli", "size": "{field} peab olema väiksem kui 0:{size}KB", "url": "{field} pole sobiv URL" } } ================================================ FILE: packages/i18n/src/locale/eu.json ================================================ { "code": "eu", "messages": { "alpha": "{field} eremuak soilik karaktere alfabetikoak eduki ditzake", "alpha_dash": "{field} eremuak karaktere alfanumerikoak, marrak eta azpimarrak eduki ditzake", "alpha_num": "{field} eremuak soilik karaktere alfanumerikoak eduki ditzake", "alpha_spaces": "{field} eremuak soilik karaktere alfanumerikoak eta zuriuneak eduki ditzake", "between": "{field} eremuak 0:{min} eta 1:{max} artean egon behar du", "confirmed": "{field} berrespenak ez datoz bat", "digits": "{field} eremuak zenbakizkoa izan behar du eta zehazki 0:{length} digitu izan behar ditu", "dimensions": "{field} eremuak 0:{width} pixel bider 1:{height} pixel izan behar du", "email": "{field} eremuak baliozko helbide elektroniko bat izan behar du", "not_one_of": "{field} eremuak baliozko balio bat izan behar du", "ext": "{field} eremuak baliozko fitxategi bat izan behar du", "image": "{field} eremuak irudi bat izan behar du", "integer": "{field} eremuak zenbaki oso bat izan behar du", "length": "{field}(r)en luzerak 0:{length} izan behar du", "max_value": "{field} eremuak 0:{max} edo gutxiago izan behar du", "max": "{field} eremuak ezin ditu 0:{length} karaktere baino gehiago izan", "mimes": "{field} eremuak baliozko fitxategi-mota bat izan behar du", "min_value": "{field} eremuak 0:{min} edo gehiago izan behar du", "min": "{field} eremuak gutxienez 0:{length} karaktere izan behar ditu", "numeric": "{field} eremuak zenbakizko karaktereak soilik izan ditzake", "one_of": "{field} eremuak baliozko balio bat izan behar du", "regex": "{field} eremuaren formatua baliogabea da", "required": "{field} eremua derrigorrezkoa da", "required_if": "{field} eremua derrigorrezkoa da", "size": "{field}(e)n tamainak 0:{size}KB baino txikiagoa izan behar du" } } ================================================ FILE: packages/i18n/src/locale/fa.json ================================================ { "code": "fa", "messages": { "alpha": "{field} فقط می تواند از حروف تشکیل شود", "alpha_num": "{field} فقط میتواند از حروف و اعداد تشکیل شود", "alpha_dash": "{field} فقط می تواند از حروف، اعداد، خط فاصله و زیرخط تشکیل شود", "alpha_spaces": "{field} فقط می تواند از حروف و فاصله تشکیل شود", "between": "{field} باید بین 0:{min} و 1:{max} کارکتر باشد", "confirmed": "{field} با تاییدیه اش مطابقت ندارد", "digits": "{field} باید یک مقدار عددی و دقیقاً 0:{length} رقم باشد", "dimensions": "{field} باید در اندازه 0:{width} پیکسل عرض و 1:{height} پیکسل ارتفاع باشد", "email": "{field} باید یک پست الکترونیک معتبر باشد", "not_one_of": "{field}باید یک مقدار معتبر باشد", "ext": "{field} باید یک فایل معتبر باشد", "image": "{field} باید یک تصویر باشد", "integer": "{field} باید یک عدد صحیح باشد", "length": "{field} باید دقیقا 0:{length} کاراکتر باشد", "max_value": "مقدار {field} باید 0:{max} یا کمتر باشد", "max": "{field} نباید بیشتر از 0:{length} کارکتر باشد", "mimes": "{field} باید از نوع معتبر باشد", "min_value": "مقدار {field} باید 0:{min} یا بیشتر باشد", "min": "{field} باید حداقل 0:{length} کارکتر باشد", "numeric": "{field} فقط می تواند عددی باشد", "one_of": "{field} باید یک مقدار معتبر باشد", "regex": "قالب {field} قابل قبول نیست", "required_if": "{field} الزامی است", "required": "{field} الزامی است", "size": "حجم {field} کمتر از 0:{size}KB باشد" } } ================================================ FILE: packages/i18n/src/locale/fi.json ================================================ { "code": "fi", "messages": { "alpha": "{field} voi sisältää vain kirjaimia", "alpha_dash": "{field} voi sisältää vain kirajimia, numeroita, ja tavu-, tai alaviivoja", "alpha_num": "{field} voi sisältää vain kirjaimia ja numeroita", "between": "Kentän {field} tulee olla 0:{min} ja 1:{max} väliltä", "confirmed": "{field} ei vastannut {target}", "digits": "{field} tulee olla numeerinen ja tarkalleen 0:{length} merkkiä", "dimensions": "{field} tulee olla 0:{width} pikseliä kertaa 1:{height} pikseliä", "email": "{field} tulee olla kelvollinen sähköpostiosoite", "not_one_of": "{field} tulee olla kelvollinen arvo", "ext": "{field} tulee olla kelvollinen tiedosto", "image": "{field} tulee olla kelvollinen kuva", "max": "{field} ei saa olla pidempi kuin 0:{length} merkkiä", "mimes": "{field} tulee olla kelvollinen tiedostotyyppi", "min": "{field} tulee olla vähintään 0:{length} merkkiä", "numeric": "{field} voi sisältää vain numeroita", "one_of": "{field} tulee olla kelvollinen arvo", "regex": "{field} tulee olla kelvollinen säännöllinen lauseke", "required": "{field} on pakollinen kenttä", "required_if": "{field} on pakollinen kenttä", "size": "{field} tulee olla vähemmän kuin 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/fr.json ================================================ { "code": "fr", "messages": { "_default": "Le champ {field} est invalide", "alpha": "Le champ {field} ne peut contenir que des lettres", "alpha_num": "Le champ {field} ne peut contenir que des caractères alpha-numériques", "alpha_dash": "Le champ {field} ne peut contenir que des caractères alpha-numériques, tirets ou soulignés", "alpha_spaces": "Le champ {field} ne peut contenir que des lettres ou des espaces", "between": "Le champ {field} doit être compris entre 0:{min} et 1:{max}", "confirmed": "Le champ {field} ne correspond pas", "digits": "Le champ {field} doit être un nombre entier de 0:{length} chiffres", "dimensions": "Le champ {field} doit avoir une taille de 0:{width} pixels par 1:{height} pixels", "email": "Le champ {field} doit être une adresse e-mail valide", "not_one_of": "Le champ {field} doit être une valeur valide", "ext": "Le champ {field} doit être un fichier valide", "image": "Le champ {field} doit être une image", "integer": "Le champ {field} doit être un entier", "length": "Le champ {field} doit contenir 0:{length} caractères", "max_value": "Le champ {field} doit avoir une valeur de 0:{max} ou moins", "max": "Le champ {field} ne peut pas contenir plus de 0:{length} caractères", "mimes": "Le champ {field} doit avoir un type MIME valide", "min_value": "Le champ {field} doit avoir une valeur de 0:{min} ou plus", "min": "Le champ {field} doit contenir au minimum 0:{length} caractères", "numeric": "Le champ {field} ne peut contenir que des chiffres", "one_of": "Le champ {field} doit être une valeur valide", "regex": "Le champ {field} est invalide", "required": "Le champ {field} est obligatoire", "required_if": "Le champ {field} est obligatoire lorsque {target} possède cette valeur", "size": "Le champ {field} doit avoir un poids inférieur à 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/he.json ================================================ { "code": "he", "messages": { "alpha": "השדה {field} יכול להכיל רק אותיות", "alpha_num": "השדה {field} יכול להכיל רק אותיות ומספרים.", "alpha_dash": "השדה {field} יכול להכיל רק אותיות, מספרים ומקפים", "alpha_spaces": "השדה {field} יכול להכיל רק אותיות ורווחים", "between": "הערך {field} חייב להיות בין 0:{min} ל- 1:{max}", "confirmed": "הערכים של {field} חייבים להיות זהים", "digits": "השדה {field} חייב להיות מספר ולהכיל 0:{length} ספרות בדיוק", "dimensions": "השדה {field} חייב להיות 0:{width} פיקסלים על 1:{height} פיקסלים", "email": "השדה {field} חייב להכיל כתובת אימייל תקינה", "not_one_of": "השדה {field} חייב להכיל ערך תקין", "ext": "השדה {field} חייב להכיל קובץ תקין", "image": "השדה {field} חייב להכיל תמונה", "max_value": "השדה {field} יכול להיות 0:{max} לכל היותר", "max": "השדה {field} לא יכול להכיל יותר מ- 0:{length} תווים", "mimes": "הקובץ חייב להיות מסוג תקין", "min_value": "הערך של {field} חייב להיות לפחות 0:{min}", "min": "השדה {field} חייב להכיל 0:{length} תווים לפחות", "numeric": "השדה {field} יכול להכיל ספרות בלבד", "one_of": "השדה {field} חייב להיות בעל ערך תקין", "regex": "הפורמט של {field} אינו תקין", "required": "חובה למלא את השדה {field}", "required_if": "חובה למלא את השדה {field}", "size": "השדה {field} חייב לשקול פחות מ 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/hr.json ================================================ { "code": "hr", "messages": { "alpha": "{field} može sadržavati samo abecedne znakove", "alpha_num": "{field} može sadržavati samo alfanumeričke znakove", "alpha_dash": "{field} može sadržavati alfanumeričke znakove kao i crtice i podvlake", "alpha_spaces": "{field} može sadržavati samo abecedne znakove kao i razmake", "between": "{field} mora biti između 0:{min} i 1:{max}", "confirmed": "Potvrda {field} ne odgovara", "digits": "{field} mora biti numerički i točno sadrživati 0:{length} znamenke", "dimensions": "{field} mora biti 0:{width} piksela za 1:{height} piksela", "email": "{field} mora biti važeća e-pošta", "not_one_of": "Vrijednost {field} mora biti važeća vrijednost", "ext": "{field} mora biti važeća datoteka", "image": "{field} mora biti slika", "max_value": "Vrijednost {field} mora biti 0:{max} ili manje", "max": "{field} ne smije biti veći od 0:{length} znakova", "mimes": "{field} mora imati valjanu vrstu datoteke", "min_value": "Vrijednost {field} mora biti 0:{min} ili više", "min": "{field} mora biti barem 0:{length} znakova", "numeric": "{field} može sadrživati samo numeričke znakove", "one_of": "Vrijednost {field} mora biti važeća vrijednost", "regex": "Oblik {field} nije važeći", "required": "Polje {field} je obavezno", "required_if": "Polje {field} je obavezno", "size": "{field} mora biti manje od 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/hu.json ================================================ { "code": "hu", "messages": { "alpha": "A(z) {field} kizárólag betűket tartalmazhat", "alpha_dash": "A(z) {field} kizárólag betűket, számokat, kötőjeleket és alulvonásokat tartalmazhat", "alpha_num": "A(z) {field} kizárólag betűket és számokat tartalmazhat", "alpha_spaces": "A(z) {field} kizárólag betűket és szóközöket tartalmazhat", "between": "A(z) {field} 0:{min} és 1:{max} között kell, hogy legyen", "confirmed": "A(z) {field} nem egyezik a megerősítéssel", "digits": "A(z) {field} 0:{length} számjegyű kell, hogy legyen", "dimensions": "A(z) {field} felbontása 0:{width} és 1:{height} pixel között kell, hogy legyen", "email": "A(z) {field} nem érvényes email formátum", "not_one_of": "A(z) {field} értéke érvénytelen", "ext": "A(z) {field} nem érvényes fájl", "image": "A(z) {field} képfálj kell, hogy legyen", "one_of": "A kiválaszott {field} érvénytelen", "max": "A(z) {field} értéke nem lehet hosszabb mint 0:{length}", "max_value": "A(z) {field} értéke 0:{max} vagy kevesebb lehet", "mimes": "A(z) {field} kizárólag érvényes fájlformátumok egyike lehet", "min": "A(z) {field} értéke nem lehet rövidebb mint 0:{length}", "min_value": "A(z) {field} értéke 0:{min} vagy több lehet", "numeric": "A(z) {field} értéke szám kell, hogy legyen", "regex": "A(z) {field} formátuma érvénytelen", "required": "A(z) {field} megadása kötelező", "required_if": "A(z) {field} megadása kötelező", "size": "A(z) {field} méretének 0:{size} kilobájtnál kisebbnek kell lennie" } } ================================================ FILE: packages/i18n/src/locale/id.json ================================================ { "code": "id", "messages": { "alpha": "{field} hanya boleh berisi huruf", "alpha_num": "{field} hanya boleh berisi huruf dan angka", "alpha_dash": "{field} hanya boleh berisi huruf, angka, tanda hubung, dan garis bawah", "alpha_spaces": "{field} hanya boleh berisi huruf dan spasi", "between": "{field} harus bernilai antara 0:{min} dan 1:{max}", "confirmed": "{field} tidak sesuai dengan {target}", "digits": "{field} harus terdiri dari 0:{length} digit angka", "dimensions": "{field} harus berukuran lebar 0:{width}px dan tinggi 1:{height}px", "email": "{field} harus berupa alamat email yang valid", "not_one_of": "{field} mengandung nilai yang tidak diperbolehkan", "ext": "{field} harus berupa file dengan ekstensi yang valid", "image": "{field} harus berupa gambar", "integer": "{field} harus berupa bilangan bulat", "length": "{field} harus terdiri dari tepat 0:{length} karakter", "max_value": "Nilai {field} tidak boleh lebih dari 0:{max}", "max": "{field} tidak boleh lebih dari 0:{length} karakter", "mimes": "{field} harus memiliki tipe file yang valid", "min_value": "Nilai {field} tidak boleh kurang dari 0:{min}", "min": "{field} harus memiliki minimal 0:{length} karakter", "numeric": "{field} harus berupa angka", "one_of": "{field} harus berisi salah satu nilai yang diizinkan", "regex": "Format {field} tidak valid", "required": "{field} wajib diisi", "required_if": "{field} wajib diisi", "size": "Ukuran {field} tidak boleh melebihi 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/it.json ================================================ { "code": "it", "messages": { "alpha": "Il campo {field} può contenere solo caratteri alfabetici", "alpha_num": "Il campo {field} può contenere solo caratteri alfanumerici", "alpha_dash": "Il campo {field} può contenere caratteri alfa-numerici così come lineette e trattini di sottolineatura", "alpha_spaces": "Il campo {field} può contenere solo caratteri alfanumerici così come spazi", "between": "Il campo {field} deve essere compreso tra 0:{min} e 1:{max}", "confirmed": "Il campo {field} non corrisponde", "digits": "Il campo {field} deve essere numerico e contenere esattamente 0:{length} cifre", "dimensions": "Il campo {field} deve essere 0:{width} x 1:{height}", "email": "Il campo {field} deve essere un indirizzo email valido", "not_one_of": "Il campo {field} deve avere un valore valido", "ext": "Il campo {field} deve essere un file valido", "image": "Il campo {field} deve essere un'immagine", "integer": "Il campo {field} deve essere un numero", "is_not": "Il campo {field} non è valido", "length": "La lunghezza del campo {field} deve essere 0:{length}", "max_value": "Il campo {field} deve essere minore o uguale a 0:{max}", "max": "Il campo {field} non può essere più lungo di 0:{length} caratteri", "mimes": "Il campo {field} deve avere un tipo di file valido", "min_value": "Il campo {field} deve essere maggiore o uguale a 0:{min}", "min": "Il campo {field} deve avere almeno 0:{length} caratteri", "numeric": "Il campo {field} può contenere solo caratteri numerici", "one_of": "Il campo {field} deve avere un valore valido", "regex": "Il campo {field} non ha un formato valido", "required": "Il campo {field} è richiesto", "required_if": "Il campo {field} è richiesto", "size": "Il campo {field} deve essere inferiore a 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/ja.json ================================================ { "code": "ja", "messages": { "_default": "{field}は有効な値ではありません", "alpha": "{field}はアルファベットのみ使用できます", "alpha_num": "{field}は英数字のみ使用できます", "alpha_dash": "{field}は英数字とハイフン、アンダースコアのみ使用できます", "alpha_spaces": "{field}はアルファベットと空白のみ使用できます", "between": "{field}は 0:{min} から 1:{max} の間でなければなりません", "confirmed": "{field}が一致しません", "digits": "{field}は 0:{length}桁の数字でなければなりません", "dimensions": "{field}は幅 0:{width}px、高さ 1:{height}px 以内でなければなりません", "email": "{field}は有効なメールアドレスではありません", "not_one_of": "{field}は不正な値です", "ext": "{field}は有効なファイル形式ではありません", "image": "{field}は有効な画像形式ではありません", "integer": "{field}は整数のみ使用できます", "is": "{field}が一致しません", "length": "{field}は 0:{length} 文字でなければなりません", "max_value": "{field}は 0:{max} 以下でなければなりません", "max": "{field}は 0:{length} 文字以内にしてください", "mimes": "{field}は有効なファイル形式ではありません", "min_value": "{field}は 0:{min} 以上でなければなりません", "min": "{field}は 0:{length} 文字以上でなければなりません", "numeric": "{field}は数字のみ使用できます", "one_of": "{field}は有効な値ではありません", "regex": "{field}のフォーマットが正しくありません", "required": "{field}は必須項目です", "required_if": "{field}は必須項目です", "size": "{field}は 0:{size}KB 以内でなければなりません", "url": "{field}は有効なURLではありません" } } ================================================ FILE: packages/i18n/src/locale/ka.json ================================================ { "code": "ka", "messages": { "alpha": "{field} უნდა შეიცავდეს მხოლოდ ასოებს", "alpha_num": "{field} უნდა შეიცავდეს მხოლოდ ციფრებს", "alpha_dash": "{field} უნდა შესაძლებელია შეიცავდეს ციფრებს, ასოებს და პუნქტუაციის ნიშნებს", "alpha_spaces": "{field} უნდა შეიცავდეს მხოლოდ ასოებსა და ცარიელ სივრცეებს", "between": "{field} უნდა იყოს 0:{min} და 1:{max}ს შორის", "confirmed": "{field} არ ემთხვევა {target}(ი)ს", "digits": "{field} უნდა შეიცავდეს ციფრებს და უნდა იყოს ზუსტად 0:{length}-ნიშნა", "dimensions": "{field} უნდა იყოს 0:{width}x{height} ზომის (pixel)", "email": "{field}-ს უნდა ჰქონდეს ელ-ფოსტის სწორი ფორმატი", "not_one_of": "{field} უნდა იყოს სწორი მნიშვნელობა", "ext": "{field} უნდა იყოს ფაილი", "image": "{field} უნდა იყოს სურათი", "max_value": "{field} უნდა შეიცავდეს 0:{max} სიმბოლოს ან ნაკლებს", "max": "{field} არ უნდა იყოს 0:{length} სიმბოლოზე მეტი", "mimes": "{field}ს უნდა ჰქონდეს სწორი ფაილის ფორმატი", "min_value": "{field} უნდა შეიცავდეს 0:{min} ან მეტ სიმბოლოს", "min": "{field} უნდა შეიცავდეს მინიმუმ 0:{length} სიმბოლოს", "numeric": "{field} უნდა შეიცავდეს ციფრებს", "one_of": "{field} უნდა იყოს სწორი მნიშვნელობა", "regex": "{field}-(ი)ს ფორმატი არასწორია", "required": "{field} აუცილებელია", "required_if": "{field} აუცილებელია", "size": "{field} უნდა იყოს 0:{size}KB-ზე ნაკლები" } } ================================================ FILE: packages/i18n/src/locale/km.json ================================================ { "code": "km", "messages": { "_default": "{field} មិនមែនជាតម្លៃដែលត្រឹមត្រូវទេ", "alpha": "{field} តម្រូវអោយមានតែអក្សរតែប៉ុណ្ណោះ", "alpha_num": "{field} តម្រូវអោយមានតែអក្សរ និងលេខប៉ុណ្ណោះ", "alpha_dash": "{field} តម្រូវអោយមានតែអក្សរ លេខ សហសញ្ញា(-) និងសហសញ្ញា(_)", "alpha_spaces": "{field} តម្រូវអោយមានតែអក្សរ និងអាចដកឃ្លាបានប៉ុណ្ណោះ", "between": "{field} ត្រូវស្ថិតក្នុងចន្លោះពី 0:{min} ដល់ 1:{max}", "confirmed": "{field} មិនត្រូវនឹង {target} ទេ", "digits": "{field} ត្រូវតែជាលេខ និងមាន 0:{length} ខ្ទង់", "dimensions": "{field} ត្រូវមានទទឹង 0:{width}px និងបណ្តោល 1:{height}px", "email": "{field} មិនមែនជាអាសយដ្ឋានអ៊ីមែលដែលត្រឹមត្រូវទេ", "not_one_of": "{field} មិនមែនជាតម្លៃដែលត្រឹមត្រូវទេ", "ext": "{field} មិនមែនជាប្រភេទឯកសារដែលត្រឹមត្រូវទេ", "image": "{field} ត្រូវតែជារូបភាព", "integer": "{field} ត្រូវតែជាចំនួនគត់", "length": "{field} ត្រូវតែមានប្រវែងយ៉ាងហោចណាស់ 0:{length}", "max_value": "{field} ត្រូវតែតិចជាងឬស្មើ 0:{max}", "max": "{field} មិនអាចលើសពី 0:{length} តួអក្សរទេ", "mimes": "{field} មិនមែនជាប្រភេទឯកសារដែលត្រូវបានអនុញ្ញាតទេ", "min_value": "{field} ត្រូវតែធំជាងឬស្មើ 0:{min}", "min": "{field} ត្រូវតែមានយ៉ាងហោចណាស់ 0:{length} តួអក្សរ", "numeric": "{field} ត្រូវតែជាលេខប៉ុណ្ណោះ", "one_of": "{field} មិនមែនជាតម្លៃដែលត្រឹមត្រូវទេ", "regex": "{field} មិនត្រឹមត្រូវតាមទម្រង់ដែលបានកំណត់ទេ", "required_if": "{field} ត្រូវតែមាន", "required": "{field} ត្រូវតែមាន", "size": "{field} ត្រូវតែមានទំហំតូចជាង 0:{size}KB", "url": "{field} មិនមែនជាអាសយដ្ឋាន URL ដែលត្រឹមត្រូវទេ" } } ================================================ FILE: packages/i18n/src/locale/ko.json ================================================ { "code": "ko", "messages": { "_default": "{field} 항목의 값이 유효하지 않습니다", "alpha": "{field} 항목에는 영문자만 사용할 수 있습니다", "alpha_dash": "{field} 항목에는 영문자, 숫자, 대시(-)와 언더스코어(_)만 사용할 수 있습니다", "alpha_num": "{field} 항목에는 영문자와 숫자만 사용할 수 있습니다", "alpha_spaces": "{field} 항목에는 영문자와 공백만 사용할 수 있습니다", "between": "{field} 항목의 값은 0:{min}에서 1:{max} 사이여야 합니다", "confirmed": "{field} 항목의 값이 일치하지 않습니다", "digits": "{field} 항목의 값은 0:{length}자리의 숫자여야 합니다", "dimensions": "{field} 항목의 크기는 가로 0:{width}픽셀, 세로 1:{height}픽셀이어야 합니다", "email": "{field} 항목의 값은 유효한 이메일 형식이어야 합니다", "not_one_of": "{field} 항목의 값이 유효하지 않습니다", "ext": "{field} 항목은 유효한 파일이 아닙니다", "image": "{field} 항목은 이미지 파일이어야 합니다", "integer": "{field} 항목의 값은 정수여야 합니다", "length": "{field} 항목의 길이는 0:{length}글자여야 합니다", "max_value": "{field} 항목의 값은 0:{max} 이하여야 합니다", "max": "{field} 항목은 최대 0:{length}글자 이하여야 합니다", "mimes": "{field} 항목은 유효한 파일 형식이 아닙니다", "min_value": "{field} 항목의 값은 0:{min} 이상이어야 합니다", "min": "{field} 항목은 최소 0:{length}글자 이상이어야 합니다", "numeric": "{field} 항목에는 숫자만 사용할 수 있습니다", "one_of": "{field} 항목의 값이 유효하지 않습니다", "regex": "{field} 항목은 형식에 맞지 않습니다", "required": "{field} 항목은 필수 입력 항목입니다", "required_if": "{field} 항목은 필수 입력 항목입니다", "size": "{field} 항목의 크기는 0:{size}KB보다 작아야 합니다", "url": "{field} 항목은 유효한 URL이 아닙니다" } } ================================================ FILE: packages/i18n/src/locale/kz.json ================================================ { "code": "kz", "messages": { "_default": "{field} жолағы дұрыс емес", "alpha": "{field} жолағына тек әріптер жазуға болады", "alpha_num": "{field} жолағына тек әріптер және сандар жазуға болады", "alpha_dash": "{field} жолағына тек әріптер, сандар және дефис жазуға болады", "alpha_spaces": "{field} жолағына тек әріптер және бос орын жазуға болады", "between": "{field} жолағы 0:{min} және 1:{max} сандарының арасындағы сан болуға тиіс", "confirmed": "{field} жолағы басқа жолақпен бірдей емес", "digits": "{field} жолағы тура 0:{length} цифрдан тұратын сан болуға тиіс", "dimensions": "{field} жолағы 1:{height} пиксельге 0:{width} пиксель болатын сурет болуға тиіс", "email": "{field} жолағы қолданыстағы электронды мекенжай болуға тиіс", "not_one_of": "{field} жолағы рұқсат етілген мән болуға тиіс", "ext": "{field} жолағы қолданыстағы файл болуға тиіс", "image": "{field} жолағы сурет болуға тиіс", "integer": "{field} жолағы бүтін сан болуға тиіс", "length": "{field} жолағының ұзындығы мына мәннен ұзын болуға тиіс: 0:{length}", "max_value": "{field} жолағы 0:{max} не одан кіші сан болуға тиіс", "max": "{field} жолағы 0:{length} символдан ұзын бола алмайды", "mimes": "{field} жолағында рұқсат етілген файл типі болуға тиіс", "min_value": "{field} жолағы 0:{min} не одан үлкен сан болуға тиіс", "min": "{field} жолағы 0:{length} символдан қысқа болмауға тиіс", "numeric": "{field} жолағы сан болуға тиіс", "one_of": "{field} жолағы рұқсат етілген мән болуға тиіс", "regex": "{field} жолағының форматы дұрыс емес", "required_if": "{field} жолағын толтыру міндетті", "required": "{field} жолағын толтыру міндетті", "size": "{field} жолағы 0:{size}KB-ден кіші болуға тиіс", "url": "{field} жолағында форматы дұрыс емес сілтеме бар" } } ================================================ FILE: packages/i18n/src/locale/lt.json ================================================ { "code": "lt", "messages": { "alpha": "Laukelyje {field} leidžiamos tik raidės", "alpha_dash": "Laukelyje {field} leidžiamos tik raidės, skaičiai bei brūkšneliai", "alpha_num": "Laukelyje {field} leidžiamos tik raidės ir skaičiai", "alpha_spaces": "Laukelyje {field} leidžiamos tik raidės ir tarpai", "between": "Laukelio {field} reikšmė turi būti tarp 0:{min} ir 1:{max}", "confirmed": "Laukelio {field} patvirtinimas nesutampa", "digits": "Laukelio {field} reikšmė turi buti 0:{length} ženklų(-o) skaitmuo", "dimensions": "{field} turi būti 0:{width} px x 1:{height} px", "email": "Laukelis {field} turi būti teisinga el. pašto adresas", "not_one_of": "{field} reikšmė nėra leidžiama", "ext": "{field} turi būti tinkamas failas", "image": "{field} turi būti paveikslėlis", "one_of": "{field} reikšmė nėra leidžiama", "max": "{field} negali būti ilgesnis nei 0:{length}", "max_value": "{field} turi būti 0:{max} arba mažiau", "mimes": "{field} privalo turėti tinkmą failo tipą", "min": "{field} ilgis privalo būti bent 0:{length}", "min_value": "{field} turi būti 0:{min} arba daugiau", "numeric": "{field} turi būti tik skaitmenys", "regex": "Laukelio {field} formatas netinkamas", "required": "Laukelis {field} privalomas", "required_if": "Laukelis {field} privalomas", "size": "{field} turi būti mažesnis nei 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/lv.json ================================================ { "code": "lv", "messages": { "alpha": " Laukā {field} var ievadīt tikai burtus", "alpha_num": "Laukā {field} var ievadīt tikai burtus un ciparus", "alpha_dash": "Laukā {field} var ievadīt tikai burtus, ciparus, domuzīmes un pasvītras", "alpha_spaces": "Laukā {field} var ievadīt tikai burtus un atstarpes", "between": "Lauka {field} vērtībai jābūt intervālā starp 0:{min} un 1:{max}", "confirmed": "Apstiprinājuma lauka {field} vērtība nav korekta", "digits": "Lauka {field} vērtību veido precīzs 0:{length} ciparu skaits", "dimensions": "Lauka {field} izmērs ir 0:{width} pikseļu platumā un 1:{height} pikseļu augstumā", "email": "Lauka {field} vērtībai jābūt derīgai e-pasta adresei", "not_one_of": "Laukā {field} jāievada derīga vērtība", "ext": "Lauka {field} vērtībai jābūt failam", "image": "Lauka {field} vērtībai jābūt attēlam", "integer": "Lauka {field} vērtībai jābūt veselam skaitlim", "length": "Lauka {field} garumam jābūt 0:{length}", "max_value": "Lauka {field} vērtībai jābūt 0:{max} vai mazākai", "max": "Laukā {field} nevar ievadīt vairāk nekā 0:{length} rakstzīmes", "mimes": "Lauka {field} vērtībai jābūt failam", "min_value": "Laukā {field} izmanto minimālo vērtību 0:{min} vai lielāku", "min": "Laukā {field} jāievada vismaz 0:{length} rakstzīmes", "numeric": "Laukā {field} var ievadīt tikai ciparus", "one_of": "Lauka {field} vērtībai jābūt derīgai", "regex": "Lauka {field} formāts nav korekts", "required": "Lauks {field} ir obligāti aizpildāms", "required_if": "Lauks {field} ir obligāti aizpildāms", "size": "Lauka {field} lielumam jābut mazākam nekā 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/mn.json ================================================ { "code": "mn", "messages": { "alpha": "{field}-н утга зөвхөн үсэг агуулах боломжтой", "alpha_num": "{field}-н утга зөвхөн тоо болон үсэг агуулах боломжтой", "alpha_dash": "{field}-н утга зөвхөн үсэг, дундуур зураас, доогуур зураас агуулах боломжтой", "alpha_spaces": "{field}-н утга зөвхөн үсэг болон зай агуулах боломжтой", "between": "{field}-н утга зөвхөн 0:{min}-с 1:{max} -ны хооронд байх ёстой", "confirmed": "{field}-н утга буруу байна", "digits": "{field}-н утга зөвхөн тоо байх ба яг 0:{length} оронтой байна", "dimensions": "{field}-н хэмжээ 0:{width}x{height} пикселээс хэтрэх байх шаардлагатай", "email": "{field}-н утга бодит майл байх ёстой", "not_one_of": "{field}-н утга {not_one_of} байж болохгүй", "ext": "{field} заавал файл заавал {ext} форматтай байх ёстой", "image": "{field} заавал зураг байх ёстой", "integer": "{field}-н утга тоо байх ёстой", "length": "{field}-н урт нь 0:{length} байна", "max_value": "{field}-н утга 0:{max}-с хэтрэхгүй байна", "max": "{field}-н урт нь хамгийн ихдээ 0:{length} байна", "mimes": "{field} зөвшөөрөгдөөгүй файл форматтай байна", "min_value": "{field}-н утга 0:{min}-с багагүй байна", "min": "{field}-н урт нь хамгийн багадаа 0:{length} байна", "numeric": "{field}-н утга зөвхөн тоо байна", "one_of": "{field}-н утга заавал дараахын нэг нь байна. ({one_of})", "regex": "{field}-н утга буруу форматтай байна", "required": "{field}-н утга заавал байх ёстой", "required_if": "{field}-н утга заавал байх ёстой", "size": "{field} хэмжээ 0:{size}KB-с хэтрэхгүй байна" } } ================================================ FILE: packages/i18n/src/locale/ms_MY.json ================================================ { "code": "ms_MY", "messages": { "alpha": "{field} hanya boleh mempunyai karakter abjad sahaja", "alpha_dash": "{field} boleh mempunyai karakter angka-abjad, sengkang dan garis bawah", "alpha_num": "{field} hanya boleh mempunyai karakter angka-abjad", "alpha_spaces": "{field} hanya boleh mempunyai karakter abjad termasuklah aksara ruang", "between": "{field} perlulah di antara 0:{min} dan 1:{max}", "confirmed": "{field} pengesahan tidak sepadan", "digits": "{field} perlulah dalam bentuk angka dan mempunyai 0:{length} digit", "dimensions": "{field} perlulah berdimensi 0:{width} pixel darab 1:{height} pixels", "email": "{field} perlulah dalam format emel yang sah", "not_one_of": "{field} perlulah sah", "ext": "{field} perlulah dalam format fail yang sah", "image": "{field} perlulah dalam bentuk imej", "one_of": "{field} perlulah dalam nilai yang sah", "integer": "{field} perlulah dalam bentuk integer", "length": "Panjang {field} perlulah bernilai 0:{length}", "max": "{field} perlulah tidak melebihi 0:{length} karakter", "max_value": "{field} perlulah bernilai 0:{max} atau kurang", "mimes": "{field} perlulah mempunyai jenis fail yang sah", "min": "{field} perlulah sekurang-kurangnya mempunyai 0:{length} karakter", "min_value": "{field} perlulah bernilai 0:{min} atau lebih", "numeric": "{field} perlulah mempunyai hanya karakter angka sahaja", "regex": "Format {field} tidak sah", "required": "{field} adalah wajib", "required_if": "{field} adalah wajib", "size": "Saiz {field} perlulah kurang daripada 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/nb_NO.json ================================================ { "code": "nb_NO", "messages": { "alpha": "{field}-feltet kan bare inneholde bokstaver", "alpha_dash": "{field}-feltet kan bare inneholde alfanumeriske tegn, samt bindestrek og understrek", "alpha_num": "{field} kan bare inneholde alfanumeriske tegn", "alpha_spaces": "{field}-feltet kan bare inneholde alfanumeriske tegn og mellomrom", "between": "{field}-feltet må være imellom 0:{min} og 1:{max}", "confirmed": "{field}-feltet kan ikke bekreftes", "digits": "{field}-feltet må være numerisk og inneholde nøyaktig 0:{length} siffer", "dimensions": "{field}-feltet må være 0:{width} ganger 1:{height} piksler", "email": "{field}-feltet må være en gyldig e-postadresse", "not_one_of": "{field}-feltet må være en gyldig verdi", "ext": "{field}-feltet må være en gyldig fil", "image": "{field}-feltet må være et bilde", "one_of": "{field}-feltet må være en gyldig verdi", "max": "{field}-feltet kan ikke være lengre enn 0:{length} tegn", "max_value": "{field}-feltet må være 0:{max} eller mindre", "mimes": "{field}-feltet må ha en gyldig filtype", "min": "{field}-feltet må være minst 0:{length} tegn", "min_value": "{field}-feltet må være 0:{min} eller mer", "numeric": "{field}-feltet kan bare inneholde nummer", "regex": "{field}-feltet sin formatering er ugyldig", "required": "{field}-feltet er obligatorisk", "required_if": "{field}-feltet er obligatorisk", "size": "{field}-feltet må være mindre enn 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/ne.json ================================================ { "code": "ne", "messages": { "alpha": "{field} फिल्डमा वर्णमाला अक्षरहरू मात्र समावेश गर्न सकिन्छ।", "alpha_dash": "{field} फिल्डमा वर्णमाला अक्षरहरू साथै रेफ तथा अंडरस्कोर समावेश गर्न सकिन्छ।", "alpha_num": "{field} फिल्डमा संख्या वा अक्षरहरू मात्र समावेश गर्न सकिन्छ।", "alpha_spaces": "{field} फिल्डमा वर्णमाला अक्षरहरू र यिन बीच खाली ठाउँ मात्र समावेश गर्न सकिन्छ।", "between": "{field}को डाटा 0:{min} र 1:{max} को बीच हुनु पर्दछ।", "confirmed": "{field}को मान पुष्टिकरण फिल्डसंग मेल खाँदैन।", "digits": "{field} फिल्डमा अंकहरू समावेश हुनुका साथै ठ्याकै 0:{length}ओटा अंक समावेश हुनु पर्दछ।", "dimensions": "{field} फिल्डमा 0:{width} पिक्सेल चौड़ा र 1:{height} पिक्सेल अग्लो हुनु पर्दछ।", "email": "{field} फिल्डमा मान्य ईमेल हुनु पर्दछ।", "not_one_of": "{field} फिल्डमा मान्य मान हुनु पर्दछ।", "ext": "{field} फिल्डमा मान्य फाइल हुनु पर्दछ।", "image": "{field} फिल्डमा मान्य फोटो हुनु पर्दछ।", "one_of": "{field} फिल्डमा मान्य परिमाण हुनु पर्दछ।", "integer": "{field} फिल्डको मान पूर्णांक संख्या हुनु पर्दछ।", "length": "{field} फिल्डमा लम्बाई 0:{length} हुनु पर्दछ।", "max": "{field} फिल्डमा अक्षरहरू 0:{length}सम्म लामो हुँदा मान्य हुनेछ।", "max_value": "{field} फिल्डमा अंकहरु 0:{max} वा सोभन्दा कम हुनु पर्दछ।", "mimes": "{field} फिल्ड मान्य फाइलको प्रकार हुनु पर्दछ।", "min": "{field} फिल्ड कम्तिमा 0:{length} अक्षर वा अंकहरू हुनु पर्दछ।", "min_value": "{field} फिल्डमा अंकहरु 0:{min} वा बढी हुनु पर्दछ।", "numeric": "{field} फिल्डमा संख्यात्मक अंकहरू मात्र समावेश गर्दा मान्य हुनेछ", "regex": "{field} फिल्डमा मानको ढाँचा स्वीकार-योग्य छैन।", "required": "{field} फिल्ड आवश्यक छ।", "required_if": "{field} फिल्ड आवश्यक छ।", "size": "{field} परिणाम 0:{size}KB भन्दा कम हुनु पर्दछ।" } } ================================================ FILE: packages/i18n/src/locale/nl.json ================================================ { "code": "nl", "messages": { "_default": "{field} is ongeldig", "alpha": "{field} mag alleen letters bevatten", "alpha_dash": "{field} mag alleen letters, nummers, en streepjes bevatten", "alpha_num": "{field} mag alleen letters en nummers bevatten", "alpha_spaces": "{field} mag alleen letters en spaties bevatten", "between": "{field} moet tussen 0:{min} en 1:{max} liggen", "confirmed": "{field} bevestiging komt niet overeen", "digits": "{field} moet een nummer zijn en exact 0:{length} tekens bevatten", "dimensions": "{field} moet 0:{width} pixels breed zijn en 1:{height} pixels hoog", "email": "{field} moet een geldig e-mailadres zijn", "not_one_of": "{field} is ongeldig", "ext": "{field} moet een geldig bestand zijn", "image": "{field} moet een afbeelding zijn", "one_of": "{field} moet een geldige waarde zijn", "max": "{field} mag niet groter zijn dan 0:{length} karakters", "max_value": "{field} mag maximaal 0:{max} zijn", "mimes": "{field} moet een geldig bestandstype hebben", "min": "{field} moet minimaal 0:{length} karakters zijn", "min_value": "{field} moet minimaal 0:{min} zijn", "numeric": "{field} mag alleen nummers bevatten", "regex": "{field} formaat is ongeldig", "required": "{field} is verplicht", "required_if": "{field} is verplicht", "size": "{field} mag niet groter zijn dan 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/nn_NO.json ================================================ { "code": "nn_NO", "messages": { "alpha": "{field}-feltet kan berre innehalde bokstaver", "alpha_dash": "{field}-feltet kan berre innehalde alfa-numeriske tegn, samt bindestrek og understrek", "alpha_num": "{field} kan berre innehalde alfanumeriske tegn", "alpha_spaces": "{field}-feltet kan berre innehalde alfanumeriske tegn og mellomrom", "between": "{field}-feltet må vere mellom verdiane 0:{min} og 1:{max}", "confirmed": "{field}-feltet samsvarer ikkje", "digits": "{field}-feltet må vere numerisk og innehalde nøyaktig 0:{length} siffer", "dimensions": "{field}-feltet må vere 0:{width} gonger 1:{height} piksler", "email": "{field}-feltet må innehalde ein gyldig E-post adresse", "not_one_of": "{field}-feltet må ha ein gyldig verdi", "ext": "{field}-feltet må innehalde ei gyldig fil", "image": "{field}-feltet må vere eit bilete", "one_of": "{field}-feltet må vere ein gyldig verdi", "max": "{field}-feltet kan ikkje vere lengre enn 0:{length} tegn", "max_value": "{field} kan ikkje vere lengre enn 0:{max} tegn", "mimes": "{field}-feltet må ha ein gyldig filtype", "min": "{field}-feltet må innehalde minst 0:{length} tegn", "min_value": "{field}-feltet må vere 0:{min} eller mer", "numeric": "{field}-feltet kan berre innehalde nummer", "regex": "{field} har ugyldig formatering", "required": "{field} er eit obligatorisk felt", "required_if": "{field} er eit obligatorisk felt", "size": "{field}-feltet må vere mindre enn 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/pl.json ================================================ { "code": "pl", "messages": { "alpha": "Pole {field} może zawierać tylko litery", "alpha_dash": "Pole {field} może zawierać litery, cyfry oraz myślnik lub podkreślnik", "alpha_num": "Pole {field} może zawierać tylko litery i cyfry", "alpha_spaces": "Pole {field} może zawierać tylko litery oraz spacje", "between": "Pole {field} musi być pomiędzy 0:{min} oraz 1:{max}", "confirmed": "Pole {field} nie zgadza się z polem potwierdzającym {target}", "digits": "Pole {field} musi być liczbą i dokładnie 0:{length} cyfr", "dimensions": "Obraz {field} musi być szeroki na 0:{width} pikseli i wysoki na 1:{height} pikseli", "email": "Pole {field} musi być poprawnym adresem email", "not_one_of": "Pole {field} musi być poprawną wartością", "ext": "Plik {field} musi być poprawnym plikiem", "image": "Pole {field} musi być obrazem", "one_of": "Pole {field} musi być poprawną wartością", "integer": "Pole {field} musi być liczbą całkowitą", "length": "Pole {field} musi mieć długość 0:{length}", "max": "Pole {field} nie może być dłuższe niż 0:{length}", "max_value": "Pole {field} musi mieć maksymalną wartość 0:{max}", "mimes": "Plik {field} musi posiadać poprawne rozszerzenie", "min": "Pole {field} musi być długie na co najmniej 0:{length}", "min_value": "Pole {field} musi mieć minimalną wartość 0:{min}", "numeric": "Pole {field} może zawierać tylko cyfry", "regex": "Format pola {field} jest nieodpowiedni", "required": "Pole {field} jest wymagane", "required_if": "Pole {field} jest wymagane", "size": "Plik {field} musi być mniejszy niż 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/pt_BR.json ================================================ { "code": "pt_BR", "messages": { "alpha": "O campo {field} deve conter somente letras", "alpha_dash": "O campo {field} deve conter letras, números e traços", "alpha_num": "O campo {field} deve conter somente letras e números", "alpha_spaces": "O campo {field} só pode conter caracteres alfabéticos e espaços", "between": "O campo {field} deve estar entre 0:{min} e 1:{max}", "confirmed": "A confirmação do campo {field} deve ser igual", "digits": "O campo {field} deve ser numérico e ter exatamente 0:{length} dígitos", "dimensions": "O campo {field} deve ter 0:{width} pixels de largura por 1:{height} pixels de altura", "email": "O campo {field} deve ser um email válido", "not_one_of": "O campo {field} deve ser um valor válido", "ext": "O campo {field} deve ser um arquivo válido", "image": "O campo {field} deve ser uma imagem", "integer": "O campo {field} deve ser um número inteiro", "is": "O valor inserido no campo {field} não é válido", "one_of": "O campo {field} deve ter um valor válido", "length": "O tamanho do campo {field} deve ser 0:{length}", "max": "O campo {field} não deve ter mais que 0:{length} caracteres", "max_value": "O campo {field} precisa ser 0:{max} ou menor", "mimes": "O campo {field} deve ser um tipo de arquivo válido", "min": "O campo {field} deve conter pelo menos 0:{length} caracteres", "min_value": "O campo {field} precisa ser 0:{min} ou maior", "numeric": "O campo {field} deve conter apenas números", "regex": "O campo {field} possui um formato inválido", "required": "O campo {field} é obrigatório", "required_if": "O campo {field} é obrigatório", "size": "O campo {field} deve ser menor que 0:{size}KB", "url": "O campo {field} deve ser uma URL válida" } } ================================================ FILE: packages/i18n/src/locale/pt_PT.json ================================================ { "code": "pt_PT", "messages": { "alpha": "O campo {field} deve conter somente letras", "alpha_dash": "O campo {field} deve conter letras, números e traços", "alpha_num": "O campo {field} deve conter somente letras e números", "alpha_spaces": "O {field} só pode conter caracteres alfabéticos e espaços", "between": "O campo {field} deve estar entre 0:{min} e 1:{max}", "confirmed": "A confirmação do campo {field} deve ser igual", "digits": "O campo {field} deve ser numérico e ter 0:{length} dígitos", "dimensions": "O campo {field} deve ter 0:{width} pixels de largura por 1:{height} pixels de altura", "email": "O campo {field} deve ser um email válido", "integer": "O campo {field} deve ser um número inteiro", "not_one_of": "O campo {field} deve ser um valor válido", "ext": "O campo {field} deve ser um ficheiro válido", "image": "O campo {field} deve ser uma imagem", "is": "O valor inserido no campo {field} não é válido", "one_of": "O campo {field} deve ter um valor válido", "max": "O campo {field} não deve ter mais que 0:{length} caracteres", "max_value": "O campo {field} precisa ser 0:{max} ou menor", "mimes": "O campo {field} deve ser um tipo de ficheiro válido", "min": "O campo {field} deve conter pelo menos 0:{length} caracteres", "min_value": "O campo {field} precisa ser 0:{min} ou maior", "numeric": "O campo {field} deve conter apenas números", "regex": "O campo {field} possui um formato inválido", "required": "O campo {field} é obrigatório", "required_if": "O campo {field} é obrigatório", "size": "O campo {field} deve ser menor que 0:{size}KB", "url": "O campo {field} deve ser uma URL válida" } } ================================================ FILE: packages/i18n/src/locale/ro.json ================================================ { "code": "ro", "messages": { "alpha": "Câmpul {field} poate conține doar literele alfabetului", "alpha_dash": "Câmpul {field} poate conține litere și caracterele '-' sau '_'", "alpha_num": "Câmpul {field} poate conține doar caractere alfanumerice", "alpha_spaces": "Câmpul {field} poate conține literele și spații", "between": "Valoare câmpului {field} trebuie să fie între 0:{min} și 1:{max}", "confirmed": "Câmpul {field} nu coincide", "digits": "Câmpul {field} trebuie să fie numeric și să conțină exact 0:{length} caractere", "dimensions": "Câmpul {field} trebuie să fie 0:{width} pixeli lungime și 1:{height} pixeli înălțime", "email": "Câmpul {field} trebuie să conțină un email valid", "not_one_of": "Câmpul {field} trebuie să conțină o valoare validă", "ext": "Câmpul {field} trebuie să fie un nume de fișier valid", "image": "Câmpul {field} trebuie să fie o imagine", "one_of": "Câmpul {field} trebuie să conțină o valoare validă", "max": "Câmpul {field} nu poate conține mai mult de 0:{length} caractere", "max_value": "Valoarea câmpului {field} trebuie să fie maxim 0:{max}", "mimes": "Câmpul {field} trebuie să conțină un fișier cu extensie validă", "min": "Câmpul {field} trebuie să conțină cel puțin 0:{length} caractere", "min_value": "Valoarea câmpului {field} trebuie să fie mai mare de 0:{min}", "numeric": "Câmpul {field} poate conține doar valori numerice", "regex": "Formatul câmpului {field} este invalid", "required": "Câmpul {field} este obligatoriu", "required_if": "Câmpul {field} este obligatoriu", "size": "Câmpul {field} nu trebuie să depășească 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/ru.json ================================================ { "code": "ru", "messages": { "_default": "Поле {field} некорректно", "alpha": "Поле {field} может содержать только буквы", "alpha_num": "Поле {field} может содержать только буквы и цифры", "alpha_dash": "Поле {field} может содержать только буквы, цифры и дефис", "alpha_spaces": "Поле {field} может содержать только буквы и пробелы", "between": "Поле {field} должно быть числом между 0:{min} и 1:{max}", "confirmed": "Поле {field} не совпадает с другим полем", "digits": "Поле {field} должно быть числом ровно из 0:{length} цифр", "dimensions": "Поле {field} должно быть изображением 0:{width} пикселей на 1:{height} пикселей", "email": "Поле {field} должно быть действительным электронным адресом", "not_one_of": "Поле {field} должно быть допустимым значением", "ext": "Поле {field} должно быть действительным файлом", "image": "Поле {field} должно быть изображением", "integer": "Поле {field} должно быть целым числом", "length": "Длина поля {field} должна быть 0:{length}", "max_value": "Поле {field} должно быть числом 0:{max} или меньше", "max": "Поле {field} не может быть длиннее 0:{length} символов", "mimes": "Поле {field} должно иметь допустимый тип файла", "min_value": "Поле {field} должно быть числом 0:{min} или больше", "min": "Поле {field} должно быть не короче 0:{length} символов", "numeric": "Поле {field} должно быть числом", "one_of": "Поле {field} должно быть допустимым значением", "regex": "Поле {field} имеет некорректный формат", "required_if": "Поле {field} обязательно для заполнения", "required": "Поле {field} обязательно для заполнения", "size": "Поле {field} должно быть меньше, чем 0:{size}KB", "url": "Поле {field} содержит ссылку в некорректном формате" } } ================================================ FILE: packages/i18n/src/locale/sk.json ================================================ { "code": "sk", "messages": { "_default": "Pole {field} nie je platné", "alpha": "{field} môže obsahovať len písmená", "alpha_num": "{field} môže obsahovať len písmená a číslice", "alpha_dash": "{field} môže obsahovať len písmená, číslice, bodky a podčiarknutie", "alpha_spaces": "{field} môže obsahovať len písmená, číslice a medzery", "between": "Položka {field} musí byť medzi 0:{min} a 1:{max}", "confirmed": "Hodnota položky {field} nie je rovnaká", "digits": "Položka {field} musí obsahovať 0:{length} čísiel", "dimensions": "Položka {field} musí mať 0:{width} x 1:{height} pixlov", "email": "Položka {field} musí obsahovať správnu emailovú adresu", "not_one_of": "Položka {field} má nesprávnu hodnotu", "ext": "{field} nie je platný súbor", "image": "{field} nie je obrázok", "integer": "Pole {field} musí byť celé číslo", "length": "Pole {field} musí mať dĺžku 0:{length}", "max_value": "Položka {field} musí byť maximálne 0:{max}", "max": "Položka {field} môže obsahovať najviac 0:{length} znakov", "mimes": "Položka {field} obsahuje nesprávny typ súboru", "min_value": "Položka {field} musí byť minimálne 0:{min}", "min": "Položka {field} musí obsahovať minimálne 0:{length} znakov", "numeric": "Položka {field} môže obsahovať len číslice", "one_of": "Položka {field} má nesprávnu hodnotu", "regex": "Formát položky {field} je nesprávny", "required_if": "Položka {field} je povinná", "required": "Položka {field} je povinná", "size": "Súbor {field} musí byť menší ako 0:{size}KB", "url": "Pole {field} nie je platná adresa URL" } } ================================================ FILE: packages/i18n/src/locale/sl.json ================================================ { "code": "sl", "messages": { "_default": "Polje {field} ni veljavno", "alpha": "Polje {field} lahko vsebuje le črkovne znake", "alpha_dash": "Polje {field} lahko vsebuje le alfanumerične znake kot tudi vezaje in podčrtaje", "alpha_num": "Polje {field} lahko vsebuje le alfanumerične znake", "alpha_spaces": "Polje {field} lahko vsebuje le črkovne znake in presledke", "between": "Polje {field} mora biti med 0:{min} in 1:{max}", "confirmed": "Polje {field} se ne ujema", "digits": "Vrednost polja {field} mora biti numerična in vsebovati natančno 0:{length} številk", "dimensions": "Slika {field} mora biti široka 0:{width} slikovnih točk in visoka 1:{height} slikovnih točk", "email": "Vrednost polja {field} mora biti ustrezen e-naslov", "not_one_of": "Polje {field} mora biti ustrezne vrednosti", "ext": "Datoteka polja {field} mora biti ustrezna", "image": "Datoteka polja {field} mora biti slika", "one_of": "Polje {field} mora biti ustrezne vrednosti", "integer": "Polje {field} mora biti celo število", "is": "Vrednost polja {field} se ne ujema", "is_not": "Polje {field} vsebuje neveljavno vrednost", "length": "Polje {field} mora biti dolgo 0:{length}", "max": "Dolžina polja {field} ne sme biti večja od 0:{length} znakov", "max_value": "Vrednost polja {field} mora biti 0:{max} ali manj", "mimes": "Datoteka polja {field} mora biti ustreznega tipa", "min": "Dolžina polja {field} mora biti vsaj 0:{length} znakov", "min_value": "Vrednost polja {field} mora biti 0:{min} ali več", "numeric": "Polje {field} lahko vsebuje le numerične znake", "regex": "Vrednost polja {field} ni v ustreznem formatu", "required": "Polje {field} je obvezno", "required_if": "Polje {field} je obvezno", "size": "Velikost datoteke {field} mora biti manjša kot 0:{size}KB", "url": "Polje {field} ni veljaven URL" } } ================================================ FILE: packages/i18n/src/locale/so.json ================================================ { "code": "so", "messages": { "alpha": "Dhulka {field} dhulku wuxuu lahaan karaa oo keliya xarfaha alifbeetada", "alpha_num": "Dhulka {field} dhulku wuxuu lahaan karaa oo keliya xarfaha alfa-tirada ah", "alpha_dash": "Dhismaha {field} waxaa ku jiri kara jilayaasha alfa tiro ahaan ah iyo sidoo kale burburin iyo hoosta", "alpha_spaces": "The {field} field may only contain alphabetic characters as well as spaces", "between": "Meesha {field} waa inay u dhexeysa {min} iyo {max}", "confirmed": "Xaqiijinta garoonka {field} ma lahan", "digits": "Booska {field} waa inuu ahaadaa lambar", "dimensions": "Booska {field} waa inuu ahaadaa {width} pixels illaa {height} pixels", "email": "Booska {field} waa inuu ahaadaa emayl ansax ah", "not_one_of": "Booska {field} ma ahan qiime sax ah", "ext": "Booska {field} ma ahan fayl sax ah", "image": "Meesha {field} waa inay ahaato muuqaal", "integer": "Booska {field} waa inuu ahaadaa mid isku dhafan", "length": "Booska {field} waa inuu ahaadaa {length} dherer", "max_value": "Booska {field} waa inuu ahaadaa {max} ama kayar", "max": "Booska {field} dhulku kama badnaan karo xarfaha {length}", "mimes": "Meesha {field} waa inay lahaato nooc fayl oo sax ah", "min_value": "Booska {field} waa inuu ahaadaa {min} ama in kabadan", "min": "Booska {field} waa inuu ahaadaa ugu yaraan {max} xarfaha", "numeric": "Booska {field} waxaa kujiri kara oo keliya xarfaha tirooyinka ah", "oneOf": "Booska {field} ma ahan qiime sax ah", "regex": "Qaabka garoonka {field} waa ansax", "required_if": "Booska {field} waa loo baahan yahay", "required": "Booska {field} waa loo baahan yahay", "size": "Baaxadda garoonka {field} waa inay ka yaraato {size}KB" } } ================================================ FILE: packages/i18n/src/locale/sq.json ================================================ { "code": "sq", "messages": { "alpha": "{field} mund të përmbaj vetëm shkronja", "alpha_dash": "{field} mund të përmbaj karaktere alfanumerike, shenja si viza dhe shenja të pikësimit", "alpha_num": "{field} mund të përmbaj vetëm shenja alfanumerike", "alpha_spaces": "{field} mund të përmbaj vetëm shkronja dhe hapësira", "between": "{field} duhet të jetë në mes 0:{min} dhe 1:{max}", "confirmed": "{field} e konfirmimit nuk përputhet", "digits": "{field} duhet të jetë numerike dhe të përmbaj saktësisht 0:{length} shifra", "dimensions": "{field} duhet të jetë 0:{width} piksela me 1:{height} piksela", "email": "{field} duhet të jetë e-mail valid", "not_one_of": "Vlera {field} duhet të jetë vlerë valide", "ext": "{field} duhet të jetë fajll valid", "image": "{field} duhet të jetë fotografi", "one_of": "Vlera {field} duhet të jetë vlerë valide", "max": "{field} nuk duhet të jetë më i gjatë se 0:{length} karaktere", "max_value": "Vlera {field} duhet të jetë 0:{max} ose më e vogël", "mimes": "{field} duhet të përmbaj llojin e fajllit valid", "min": "{field} duhet të jetë së paku 0:{length} karakter", "min_value": "Vlera {field} duhet të jetë së paku 0:{min} ose më shume", "numeric": "{field} mund të përmbaj vetëm numra", "regex": "Formati {field} nuk është valid", "required": "Fusha {field} nuk duhet të jetë e zbrazët", "required_if": "Fusha {field} nuk duhet të jetë e zbrazët", "size": "{field} duhet të jetë më e vogël se 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/sr.json ================================================ { "code": "sr", "messages": { "alpha": "Поље {field} може садржати само слова", "alpha_dash": "Поље {field} може садржати алфанумеричке карактере и повлаке", "alpha_num": "Поље {field} може садржати само алфанумеричке карактере", "alpha_spaces": "Поље {field} може садржати само алфанумеричке карактере и размаке", "between": "Поље {field} мора бити између 0:{min} и 1:{max}", "confirmed": "Потврда поља {field} се не поклапа", "digits": "Поље {field} мора бити број и садржати тачно 0:{length} цифара", "dimensions": "Поље {field} мора бити 0:{width} x 1:{height} пиксела", "email": "Поље {field} мора бити валидан имејл", "not_one_of": "Поље {field} мора имати валидну вредност", "ext": "Поље {field} мора бити валидан фајл", "image": "Поље {field} мора бити слика", "one_of": "Поље {field} мора бити валидна вредност", "max": "Поље {field} не сме бити дуже од 0:{length} карактера", "max_value": "Поље {field} не сме бити веће од 0:{max}", "mimes": "Поље {field} мора бити валидан тип фајла", "min": "Поље {field} мора садржати најмање 0:{length} карактера", "min_value": "Поље {field} не сме бити мање од 0:{min}", "numeric": "Поље {field} мора бити број", "regex": "Формат поља {field} није валидан", "required": "Поље {field} је обавезно", "size": "Поље {field} мора бити мање од 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/sr_Latin.json ================================================ { "code": "sr_Latin", "messages": { "alpha": "Polje {field} može sadržati samo slova", "alpha_dash": "Polje {field} može sadržati alfanumeričke karaktere i povlake", "alpha_num": "Polje {field} može sadržati samo alfanumeričke karaktere", "alpha_spaces": "Polje {field} može sadržati samo alfanumeričke karaktere i razmake", "between": "Polje {field} mora biti između 0:{min} i 1:{max}", "confirmed": "Potvrda polja {field} se ne poklapa", "digits": "Polje {field} mora biti broj i sadržati tačno 0:{length} cifara", "dimensions": "Polje {field} mora biti 0:{width} x 1:{height} piksela", "email": "Polje {field} mora biti validan imejl", "not_one_of": "Polje {field} mora imati validnu vrednost", "ext": "Polje {field} mora biti validan fajl", "image": "Polje {field} mora biti slika", "one_of": "Polje {field} mora biti validna vrednost", "max": "Polje {field} ne sme biti duže od 0:{length} karaktera", "max_value": "Polje {field} ne sme biti veće od 0:{max}", "mimes": "Polje {field} mora biti validan tip fajla", "min": "Polje {field} mora sadržati najmanje 0:{length} karaktera", "min_value": "Polje {field} ne sme biti manje od 0:{min}", "numeric": "Polje {field} mora biti broj", "regex": "Format polja {field} nije validan", "required": "Polje {field} je obavezno", "required_if": "Polje {field} je obavezno", "size": "Polje {field} mora biti manje od 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/sv.json ================================================ { "code": "sv", "messages": { "_default": "Fältet {field} är inte giltigt", "alpha": "Fältet {field} får bara innehålla alfabetiska tecken", "alpha_dash": "Fältet {field} får bara innehålla alfanumeriska tecken såväl som snedstreck och understreck", "alpha_num": "Fältet {field} får bara innehålla alfanumeriska tecken", "alpha_spaces": "Fältet {field} får bara innehålla alfabetiska tecken och mellanslag", "between": "Fältet {field} måste vara mellan 0:{min} och 1:{max}", "confirmed": "Fältet {field} matchar inte {target}", "digits": "Fältet {field} måste vara numeriskt och innehålla exakt 0:{length} siffor", "dimensions": "Fältet {field} måste vara 0:{width} pixlar bred och 1:{height} pixlar hög", "email": "Fältet {field} måste vara en giltig e-postadress", "not_one_of": "Fältet {field} måste vara ett godkänt alternativ", "ext": "Fältet {field} måste vara en godkänd fil", "image": "Fältet {field} måste vara en bildfil", "integer": "Fältet {field} måste vara ett heltal", "length": "Fältet {field} måste vara 0:{length} långt", "one_of": "Fältet {field} måste vara ett godkänt alternativ", "max_value": "Fältet {field} måste vara 0:{max} eller mindre", "max": "Fältet {field} får inte vara längre än 0:{length} tecken", "mimes": "Fältet {field} måste ha en filändelse", "min_value": "Fältet {field} måste vara 0:{min} eller mer", "min": "Fältet {field} måste minst vara 0:{length} tecken", "numeric": "Fältet {field} får bara innehålla numeriska tecken", "regex": "Fältet {field} har en felaktig formatering", "required": "Fältet {field} är obligatoriskt", "required_if": "Fältet {field} är obligatoriskt", "size": "Fältet {field} måste vara mindre än 0:{size}KB", "url": "Fältet {field} är inte en giltig webbadress" } } ================================================ FILE: packages/i18n/src/locale/th.json ================================================ { "code": "th", "messages": { "alpha": "{field} ต้องเป็นตัวอักษรเท่านั้น", "alpha_dash": "{field} สามารถมีตัวอักษร ตัวเลข เครื่องหมายขีดกลาง (-) และเครื่องหมายขีดล่าง (_)", "alpha_num": "{field} ต้องเป็นตัวอักษร และตัวเลขเท่านั้น", "alpha_spaces": "{field} ต้องเป็นตัวอักษร และช่องว่างเท่านั้น", "between": "{field} ต้องเป็นค่าระหว่าง 0:{min} และ 1:{max}", "confirmed": "การยืนยันข้อมูลของ {field} ไม่ตรงกัน", "digits": "{field} ต้องเป็นตัวเลขจำนวน 0:{length} หลักเท่านั้น", "dimensions": "{field} ต้องมีขนาด 0:{width}x{height} px", "email": "{field} ต้องเป็นรูปแบบอีเมล", "not_one_of": "{field} ต้องเป็นค่าที่กำหนดเท่านั้น", "ext": "{field} สกุลไฟล์ไม่ถูกต้อง", "image": "{field} ต้องเป็นรูปภาพเท่านั้น", "one_of": "{field} ต้องเป็นค่าที่กำหนดเท่านั้น", "integer": "{field} ต้องเป็นเลขจำนวนเต็ม", "length": "{field} ต้องมีความยาว 0:{length}", "max": "{field} ต้องมีความยาวไม่เกิน 0:{length} ตัวอักษร", "max_value": "{field} ต้องมีค่าไม่เกิน 0:{max}", "mimes": "{field} ประเภทไฟล์ไม่ถูกต้อง", "min": "{field} ต้องมีความยาวอย่างน้อย 0:{length} ตัวอักษร", "min_value": "{field} ต้องมีค่าตั้งแต่ 0:{min} ขึ้นไป", "numeric": "{field} ต้องเป็นตัวเลขเท่านั้น", "regex": "รูปแบบ {field} ไม่ถูกต้อง", "required": "กรุณากรอก {field}", "required_if": "กรุณากรอก {field}", "size": "{field} ต้องมีขนาดไฟล์ไม่เกิน 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/tr.json ================================================ { "code": "tr", "messages": { "alpha": "{field} yalnızca harf içerebilir", "alpha_dash": "{field} alanı harf ve tire (-) ya da alttan tire (_) içerebilir", "alpha_num": "{field} yalnızca harf ve rakam içerebilir", "alpha_spaces": "{field} yalnızca harf boşluk (space) içerebilir", "between": "{field} 0:{min} ile 1:{max} aralığında olmalıdır", "confirmed": "{field} doğrulaması hatalı", "digits": "{field} sayısal ve 0:{length} basamaklı olmalıdır", "dimensions": "{field} alanı 0:{width} piksel ile 1:{height} piksel arasında olmalıdır", "email": "{field} alanının geçerli bir e-posta olması gerekir", "not_one_of": "{field} alanına geçerli bir değer giriniz", "ext": "{field} alanı geçerli bir dosya olmalıdır", "image": "{field} alanı resim dosyası olmalıdır", "integer": "{field} alanı bir tamsayı olmalıdır", "length": "{field} alanı 0:{length} uzunluğunda olmalıdır", "one_of": "{field} alanına geçerli bir değer giriniz", "max": "{field} alanı 0:{length} karakterden fazla olmamalıdır", "max_value": "{field} alanı 0:{max} ya da daha az bir değer olmalıdır", "mimes": "{field} geçerli bir dosya olmalıdır", "min": "{field} alanına en az 0:{length} karakter girilmelidir", "min_value": "{field} alanı 0:{min} ya da daha fazla bir değer olmalıdır", "numeric": "{field} alanına sayısal bir değer giriniz", "regex": "{field} formatı geçersiz", "required": "{field} alanı gereklidir", "required_if": "{field} alanı gereklidir", "size": "{field} alanı 0:{size}KB'dan daha az olmalıdır", "url": "{field} geçerli bir URL değil" } } ================================================ FILE: packages/i18n/src/locale/ug.json ================================================ { "code": "ug", "messages": { "alpha": "{field} پەقەت ھەرپ بولۇشى كېرەك", "alpha_num": "{field} پەقەت ھەرپ ۋە سان بولۇشى كېرەك", "alpha_dash": "{field} پەقەت ھەرپ، سان، سىزىقچە ۋە ئاستى سىزىق بولۇشى كېرەك", "alpha_spaces": "{field} پەقەت ھەرپ ۋە بوشلۇق بولۇشى كېرەك", "between": "{field} چوقۇم 0:{min} بىلەن 1:{max} ئارىسىدا بولۇشى كېرەك", "confirmed": "{field} نى دەلىللەش ئىناۋەتسىز", "digits": "{field} چوقۇم 0:{length} خانىلىق سان بولۇشى كېرەك", "dimensions": "{field} چوقۇم 0:{width} پىكسېلدە 1:{height} پىكسېل بولۇشى كېرەك", "email": "{field} چوقۇم ئىناۋەتلىك تورخەت بولۇشى كېرەك", "not_one_of": "{field} ئىناۋەتسىز", "ext": "{field} ئىناۋەتسىز ھۆججەت", "image": "{field} چوقۇم رەسىم بولۇشى كېرەك", "integer": "{field} چوقۇم پۈتۈن سان بولۇشى كېرەك", "length": "{field} نىڭ ئۇزۇنلۇقى چوقۇم 0:{length} بولۇشى كېرەك", "max_value": "{field} چوقۇم 0:{max} كە تەڭ ياكى كىچىك بولۇشى كېرەك", "max": "{field} نىڭ ئۇزۇنلۇقى 0:{length} دىن چوڭ بولماسلىقى كېرەك", "mimes": "{field} چوقۇم ئىناۋەتلىك ھۆججەت تىپى بولۇشى كېرەك", "min_value": "{field} چوقۇم 0:{min} كە تەڭ ياكى چوڭ بولۇشى كېرەك", "min": "{field} نىڭ ئۇزۇنلۇقى 0:{length} دىن كىچىك بولماسلىقى كېرەك", "numeric": "{field} پەقەت سان بولۇشى كېرەك", "oneOf": "{field} ئىناۋەتسىز", "regex": "{field} فورماتى ئىناۋەتسىز", "required_if": "{field} تەلەپ قىلىنىدۇ", "required": "{field} تەلەپ قىلىنىدۇ", "size": "{field} چوڭلۇقى چوقۇم 0:{size}KB دىن كىچىك بولۇشى كېرەك" } } ================================================ FILE: packages/i18n/src/locale/uk.json ================================================ { "code": "uk", "messages": { "alpha": "Поле {field} може містити тільки літери", "alpha_dash": "Поле {field} може містити буквено-цифрові символи, а також тире та підкреслення", "alpha_num": "Поле {field} може містити тільки літери та цифри", "alpha_spaces": "Поле {field} може містити тільки літери та пробіли", "between": "Поле {field} повинно бути між 0:{min} та 1:{max}", "confirmed": "Поле {field} не співпадає з підтвердженням", "digits": "Поле {field} повинно бути числовим та точно містити 0:{length} цифри", "dimensions": "Поле {field} повинно бути 0:{width} пікселів на 1:{height} пікселів", "email": "В полі {field} повинна бути адреса електронної пошти", "not_one_of": "Поле {field} повинно мати допустиме значення", "ext": "Поле {field} повинно бути дійсним файлом", "image": "В полі {field} має бути зображення", "one_of": "Поле {field} повинно бути допустимим значенням", "max": "Поле {field} не може бути більше, ніж 0:{length} символів", "max_value": "Поле {field} повинно бути 0:{max} або менше", "mimes": "Поле {field} повиннно мати дійсний тип файлу", "min": "Поле {field} має бути принаймні 0:{length} символів", "min_value": "Поле {field} повинно бути 0:{min} або більше", "numeric": "Поле {field} може містить лише цифри", "regex": "Поле {field} має невірний формат", "required": "Поле {field} повинно мати значення", "required_if": "Поле {field} повинно мати значення", "size": "Поле {field} повинно бути менше 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/uz.json ================================================ { "code": "uz", "messages": { "alpha": "{_field_} maydonida faqat harflar bo'lishi lozim", "alpha_dash": "{_field_} maydonida faqat harflar, raqamlar va tire (-) bo'lishi lozim", "alpha_num": "{_field_} maydonida faqat harflar va raqamlar bo'lishi lozim", "alpha_spaces": "{_field_} maydonida faqat harflar va bo'shliq (space) bo'lishi lozim", "between": "{_field_} maydoni uzunligi {min} va {max} oralig'ida bo'lishi lozim", "confirmed": "{_field_} maydoni {target} bilan farq qilayapti", "digits": "{_field_} maydoni raqam bo'lishi va uning uzunligi {length} ta bo'lishi lozim", "dimensions": "{_field_} maydoni {width} pikselga {height} piksel bo'lishi lozim", "email": "{_field_} maydoni haqiqiy elektron pochta bo'lishi lozim", "not_one_of": "{_field_} maydonida ruhsat etilgan belgi bo'lishi lozim", "ext": "{_field_} maydonida haqiqiy fayl bo'lishi lozim. ({args})", "image": "{_field_} maydonida rasm bo'lishi lozim", "oneOf": "{_field_} maydonida ruhsat etilgan belgi bo'lishi lozim", "integer": "{_field_} maydonida butun son bo'lishi lozim'", "length": "{_field_} maydonining uzunligi {length} ta bo'lishi lozim", "max": "{_field_} maydoni {length} ta belgidan ko'p bo'lishi mumkin emas", "max_value": "{_field_} maydonining uzunligi {max} ta yoki undan oz bo'lishi lozim", "mimes": "{_field_} maydonida ruxsat etilgan fayl turi bo'lishi lozim. ({args})", "min": "{_field_} maydoni uzunligi {length} ta belgidan kam bo'lmasligi lozim", "min_value": "{_field_} maydonining uzunligi {min} ta yoki undan ko'p bo'lishi lozim", "numeric": "{_field_} maydonida faqat raqam bo'lishi lozim'", "regex": "{_field_} maydonida xotolik bor", "required": "{_field_} maydoni majburiy, to'ldirishingiz lozim", "required_if": "{_field_} maydoni majburiy to'ldirilishi lozim", "size": "{_field_} maydoni {size}KB dan kam bo'lishi lozim", "double": "{_field_} maydoni ruhsat etilgan o'nlik son bo'lishi lozim" } } ================================================ FILE: packages/i18n/src/locale/vi.json ================================================ { "code": "vi", "messages": { "alpha": "{field} chỉ có thể chứa các kí tự chữ", "alpha_dash": "{field} có thể chứa các kí tự chữ (A-Z a-z), số (0-9), gạch ngang (-) và gạch dưới (_)", "alpha_num": "{field} chỉ có thể chứa các kí tự chữ và số", "alpha_spaces": "{field} chỉ có thế chứa các kí tự và khoảng trắng", "between": "{field} phải có giá trị nằm trong khoảng giữa 0:{min} và 1:{max}", "confirmed": "{field} khác với {target}", "digits": "Trường {field} chỉ có thể chứa các kí tự số và bắt buộc phải có độ dài là 0:{length}", "dimensions": "{field} phải có chiều rộng 0:{width} pixels và chiều cao 1:{height} pixels", "email": "{field} phải là một địa chỉ email hợp lệ", "not_one_of": "{field} phải chứa một giá trị hợp lệ", "ext": "{field} phải là một tệp", "image": "Trường {field} phải là một ảnh", "one_of": "{field} phải là một giá trị", "max": "{field} không thể có nhiều hơn 0:{length} kí tự", "max_value": "{field} phải nhỏ hơn hoặc bằng 0:{max}", "mimes": "{field} phải chứa kiểu tệp phù hợp", "min": "{field} phải chứa ít nhất 0:{length} kí tự", "min_value": "{field} phải lớn hơn hoặc bằng 0:{min}", "numeric": "{field} chỉ có thể có các kí tự số", "regex": "{field} có định dạng không đúng", "required": "{field} là bắt buộc", "required_if": "{field} là bắt buộc", "size": "{field} chỉ có thể chứa tệp nhỏ hơn 0:{size}KB" } } ================================================ FILE: packages/i18n/src/locale/zh_CN.json ================================================ { "code": "zh_CN", "messages": { "_default": "{field}不是一个有效值", "alpha": "{field}只能包含字母字符", "alpha_dash": "{field}能够包含字母数字字符、破折号和下划线", "alpha_num": "{field}只能包含字母数字字符", "alpha_spaces": "{field}只能包含字母字符和空格", "between": "{field}必须在0:{min}与1:{max}之间", "confirmed": "{field}不能和0:{target}匹配", "digits": "{field}必须是数字,且精确到0:{length}位数", "dimensions": "{field}必须在0:{width}像素与1:{height}像素之间", "email": "{field}不是一个有效的邮箱", "not_one_of": "{field}不是一个有效值", "ext": "{field}不是一个有效的文件", "image": "{field}不是一张有效的图片", "one_of": "{field}不是一个有效值", "integer": "{field}必须是整数", "length": "{field}长度必须为0:{length}", "max": "{field}不能超过0:{length}个字符", "max_value": "{field}必须小于或等于0:{max}", "mimes": "{field}不是一个有效的文件类型", "min": "{field}必须至少有0:{length}个字符", "min_value": "{field}必须大于或等于0:{min}", "numeric": "{field}只能包含数字字符", "regex": "{field}格式无效", "required": "{field}是必须的", "required_if": "{field}是必须的", "size": "{field}必须小于0:{size}KB", "url": "{field}不是一个有效的URL" } } ================================================ FILE: packages/i18n/src/locale/zh_TW.json ================================================ { "code": "zh_TW", "messages": { "_default": "{field} 的值無效", "alpha": "{field} 須以英文組成", "alpha_dash": "{field} 須以英數、破折號及底線組成", "alpha_num": "{field} 須以英數組成", "alpha_spaces": "{field} 須以英文及空格組成", "between": "{field} 須介於 0:{min} 至 1:{max}之間", "confirmed": " {field} 不一致", "digits": "{field} 須為 0:{length} 位數字", "dimensions": "{field} 圖片尺寸不正確。須為 0:{width} x 1:{height} 像素", "email": "{field} 須為有效的電子信箱", "not_one_of": "{field} 的選項無效", "ext": "{field} 須為有效的檔案", "image": "{field} 須為圖片", "one_of": "{field} 的選項無效", "integer": "{field} 須為整數", "length": "{field} 的長度須為 0:{length}", "max": "{field} 不能大於 0:{length} 個字元", "max_value": "{field} 不得大於 0:{max}", "mimes": "{field} 須為有效的檔案類型", "min": "{field} 不能小於 0:{length} 個字元", "min_value": "{field} 不得小於 0:{min}", "numeric": "{field} 須為數字", "regex": "{field} 的格式錯誤", "required": "{field} 為必填", "required_if": "{field} 為必填", "size": "{field} 的檔案須小於 0:{size}KB", "url": "{field} 須為有效的URL" } } ================================================ FILE: packages/i18n/src/utils.ts ================================================ import { InterpolateOptions } from '../../shared/types'; /** * Replaces placeholder values in a string with their actual values */ export function interpolate(template: string, values: Record, options: InterpolateOptions): string { const { prefix, suffix } = options; const regExp = buildRegex(prefix, suffix); return template.replace(regExp, function (_, param, placeholder): string { if (!param || !values.params) { return placeholder in values ? values[placeholder] : values.params && placeholder in values.params ? values.params[placeholder] : `${prefix}${placeholder}${suffix}`; } // Handles extended object params format if (!Array.isArray(values.params)) { return placeholder in values.params ? values.params[placeholder] : `${prefix}${placeholder}${suffix}`; } // Extended Params exit in the format of `paramIndex:{paramName}` where the index is optional const paramIndex = Number(param.replace(':', '')); return paramIndex in values.params ? values.params[paramIndex] : `${param}${prefix}${placeholder}${suffix}`; }); } function escapeRegex(string: string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } export function buildRegex(prefix: string, suffix: string): RegExp { const safePrefix = escapeRegex(prefix); const safeSuffix = escapeRegex(suffix); return new RegExp(`([0-9]:)?${safePrefix}((?:(?!${safeSuffix}).)+)${safeSuffix}`, 'g'); } ================================================ FILE: packages/i18n/tests/index.spec.ts ================================================ import { Ref } from 'vue'; import { defineRule, configure, useField } from '@/vee-validate'; import { required, between } from '@/rules'; import { localize, setFallbackLocale, setLocale } from '@/i18n'; import { mountWithHoc, setValue, flushPromises } from '../../vee-validate/tests/helpers'; defineRule('required', required); defineRule('between', between); beforeEach(() => { localize('en', { messages: { required: 'The {field} is required', }, }); }); test('can define new locales', async () => { configure({ generateMessage: localize('ar', { messages: { required: 'هذا الحقل مطلوب', }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); expect(error.textContent).toContain('هذا الحقل مطلوب'); }); test('can define specific messages for specific fields', async () => { configure({ generateMessage: localize('en', { fields: { test: { required: 'WRONG!', }, }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }} {{ errors[0] }}
`, }); await flushPromises(); const errors = wrapper.$el.querySelectorAll('.error'); expect(errors).toHaveLength(2); expect(errors[0].textContent).toContain('WRONG!'); expect(errors[1].textContent).toContain('The name is required'); }); // #4097 test('can define specific messages for specific fields with labels', async () => { configure({ generateMessage: localize('en', { fields: { test: { required: '{field} WRONG!', }, }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }} {{ errors[0] }}
`, }); await flushPromises(); const errors = wrapper.$el.querySelectorAll('.error'); expect(errors).toHaveLength(2); expect(errors[0].textContent).toContain('field WRONG!'); expect(errors[1].textContent).toContain('The name is required'); }); // #4097 test('can define specific messages for specific fields with labels and form schema', async () => { configure({ generateMessage: localize('en', { fields: { test: { required: '{field} WRONG!', }, }, }), }); const wrapper = mountWithHoc({ template: ` {{ errors[0] }} {{ errors[0] }} `, setup() { return { schema: { test: 'required', name: 'required', }, }; }, }); await flushPromises(); const errors = wrapper.$el.querySelectorAll('.error'); expect(errors).toHaveLength(2); expect(errors[0].textContent).toContain('field WRONG!'); expect(errors[1].textContent).toContain('The name is required'); }); test('can define labels or names for fields', async () => { configure({ generateMessage: localize('en', { messages: { required: '{field} is required' }, names: { first: 'First test', second: 'Second test', }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }} {{ errors[0] }}
`, }); await flushPromises(); const errors = wrapper.$el.querySelectorAll('.error'); expect(errors).toHaveLength(2); expect(errors[0].textContent).toContain('First test is required'); expect(errors[1].textContent).toContain('Second test is required'); }); test('can define localized labels for fields', async () => { configure({ generateMessage: localize('en', { messages: { required: '{field} is required' }, names: { first: 'First test', second: 'Second test', }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }} {{ errors[0] }}
`, }); await flushPromises(); const errors = wrapper.$el.querySelectorAll('.error'); expect(errors).toHaveLength(2); expect(errors[0].textContent).toContain('First test is required'); expect(errors[1].textContent).toContain('Second test is required'); }); // #4164 test('can define labels or names for fields with useField', async () => { let errorMessage!: Ref; configure({ generateMessage: localize('en', { messages: { required: '{field} is required' }, names: { first: 'First test', second: 'Second test', }, }), }); mountWithHoc({ setup() { const field = useField('first', 'required', { validateOnMount: true }); errorMessage = field.errorMessage; }, template: `
`, }); await flushPromises(); expect(errorMessage.value).toBe('First test is required'); }); test('can merge locales without setting the current one', async () => { configure({ generateMessage: localize({ ar: { messages: { required: 'هذا الحقل مطلوب', }, }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The field is required'); }); test('falls back to the default message if rule without message exists', async () => { defineRule('i18n', () => false); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); setValue(input, '12'); await flushPromises(); expect(error.textContent).toContain('name is not valid'); }); test('falls back to a language specific default message if rule without message exists', async () => { defineRule('i18n', () => false); configure({ generateMessage: localize('nl', { messages: { _default: '{field} is ongeldig', }, }), }); setLocale('nl'); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); setValue(input, '12'); await flushPromises(); expect(error.textContent).toContain('field is ongeldig'); }); test('can switch between locales with setLocale', async () => { configure({ generateMessage: localize({ en: { messages: { required: 'This field is required', }, }, ar: { messages: { required: 'هذا الحقل مطلوب', }, }, }), }); setLocale('en'); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); expect(error.textContent).toContain('This field is required'); setLocale('ar'); setValue(wrapper.$el.querySelector('input'), ''); await flushPromises(); expect(error.textContent).toContain('هذا الحقل مطلوب'); }); test('interpolates object params with short format', async () => { configure({ generateMessage: localize('en', { messages: { between: `The {field} field must be between {min} and {max}`, }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 10'); }); test('interpolates object params with extended format', async () => { configure({ generateMessage: localize('en', { messages: { between: `The {field} field must be between 0:{min} and 1:{max}`, }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 10'); }); test('interpolates array params', async () => { configure({ generateMessage: localize('en', { messages: { between: 'The {field} field must be between 0:{min} and 1:{max}', }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 10'); }); test('interpolates string params', async () => { configure({ generateMessage: localize('en', { messages: { between: 'The {field} field must be between 0:{min} and 1:{max}', }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 10'); }); describe('interpolation preserves placeholders if not found', () => { test('array format', async () => { configure({ generateMessage: localize('en', { messages: { between: 'The {field} field must be between 0:{min} and 1:{max}', }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 1:{max}'); }); test('object format', async () => { configure({ generateMessage: localize('en', { messages: { between: 'The {field} field must be between 0:{min} and 1:{max}', }, }), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 0 and {max}'); }); }); // #4726 - custom interpolation options test('custom interpolation options - interpolates object params with short format', async () => { configure({ generateMessage: localize( 'en', { messages: { between: `The {{field}} field must be between {{min}} and {{max}}`, }, }, { prefix: '{{', suffix: '}}', }, ), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 10'); }); // #4809 test('custom interpolation options - interpolates with long suffix/prefix', async () => { configure({ generateMessage: localize( 'en', { messages: { between: `The field field must be between min and max`, }, }, { prefix: '', suffix: '', }, ), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 10'); }); test('custom interpolation options - interpolates object params with short format and handlebars style interpolation', async () => { configure({ generateMessage: localize( 'en', { messages: { between: `The <%field%> field must be between <%min%> and <%max%>`, }, }, { prefix: '<%', suffix: '%>', }, ), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 10'); }); test('custom interpolation options - interpolates object params with extended format', async () => { configure({ generateMessage: localize( 'en', { messages: { between: `The {{field}} field must be between 0:{{min}} and 1:{{max}}`, }, }, { prefix: '{{', suffix: '}}', }, ), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 10'); }); test('custom interpolation options - interpolates array params', async () => { configure({ generateMessage: localize( 'en', { messages: { between: 'The {{field}} field must be between 0:{{min}} and 1:{{max}}', }, }, { prefix: '{{', suffix: '}}', }, ), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 10'); }); test('custom interpolation options - interpolates string params', async () => { configure({ generateMessage: localize( 'en', { messages: { between: 'The {{field}} field must be between 0:{{min}} and 1:{{max}}', }, }, { prefix: '{{', suffix: '}}', }, ), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 10'); }); describe('interpolation preserves placeholders if not found', () => { test('custom interpolation options - array format', async () => { configure({ generateMessage: localize( 'en', { messages: { between: 'The {{field}} field must be between 0:{{min}} and 1:{{max}}', }, }, { prefix: '{{', suffix: '}}', }, ), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 1 and 1:{{max}}'); }); test('custom interpolation options - object format', async () => { configure({ generateMessage: localize( 'en', { messages: { between: 'The {{field}} field must be between 0:{{min}} and 1:{{max}}', }, }, { prefix: '{{', suffix: '}}', }, ), }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); // locale wasn't set. expect(error.textContent).toContain('The name field must be between 0 and {{max}}'); }); }); test('can define fallback locale', async () => { configure({ generateMessage: localize({ en: { messages: { test: `Field is required`, }, }, ar: { messages: {}, }, }), }); setLocale('ar'); setFallbackLocale('en'); defineRule('test', () => false); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); setValue(input, '12'); await flushPromises(); expect(error.textContent).toContain('Field is required'); }); ================================================ FILE: packages/nuxt/CHANGELOG.md ================================================ # @vee-validate/nuxt ## 5.0.0-beta.1 ### Patch Changes - Updated dependencies [f629397] - Updated dependencies [f2807b8] - Updated dependencies [e6db423] - Updated dependencies [49fcf4c] - Updated dependencies [1ce0731] - Updated dependencies [fb5e04e] - Updated dependencies [095df65] - vee-validate@5.0.0-beta.1 ## 5.0.0-beta.0 ### Major Changes - 04ff47c: feat: implement standard schema ### Patch Changes - Updated dependencies [04ff47c] - vee-validate@5.0.0-beta.0 ## 4.15.1 ### Patch Changes - Updated dependencies [721e980] - Updated dependencies [546d82e] - vee-validate@4.15.1 ## 4.15.0 ### Patch Changes - Updated dependencies [30281f5] - Updated dependencies [ec121b1] - vee-validate@4.15.0 ## 4.14.7 ### Patch Changes - Updated dependencies [be994b4] - vee-validate@4.14.7 ## 4.14.6 ### Patch Changes - 41d82fd: fix: force vee-validate out of optimized deps - vee-validate@4.14.6 ## 4.14.5 ### Patch Changes - 141ad3e: chore: upgrade nuxt module dependencies and switch default to module type - e9f8c88: fix: force loading the mjs module when using nuxt - Updated dependencies [e9f8c88] - vee-validate@4.14.5 ## 4.14.4 ### Patch Changes - Updated dependencies [f33974c] - Updated dependencies [0991c01] - Updated dependencies [ecb540a] - Updated dependencies [4f88d85] - vee-validate@4.14.4 ## 4.14.3 ### Patch Changes - Updated dependencies [07c27d5] - vee-validate@4.14.3 ## 4.14.2 ### Patch Changes - Updated dependencies [f0d4e24] - vee-validate@4.14.2 ## 4.14.1 ### Patch Changes - vee-validate@4.14.1 ## 4.14.0 ### Minor Changes - 404cf57: chore: bump release ### Patch Changes - Updated dependencies [f7a4929] - Updated dependencies [97cebd8] - Updated dependencies [404cf57] - Updated dependencies [421ae69] - vee-validate@4.14.0 ## 4.13.2 ### Patch Changes - Updated dependencies [afbd0e5] - vee-validate@4.13.2 ## 4.13.1 ### Patch Changes - vee-validate@4.13.1 ## 4.13.0 ### Patch Changes - Updated dependencies [454bc45] - Updated dependencies [ae3772a] - Updated dependencies [27fe5c8] - Updated dependencies [fd008c1] - vee-validate@4.13.0 ## 4.12.8 ### Patch Changes - Updated dependencies [f8bab9c] - vee-validate@4.12.8 ## 4.12.7 ### Patch Changes - Updated dependencies [1376794] - Updated dependencies [c4415f8] - vee-validate@4.12.7 ## 4.12.6 ### Patch Changes - Updated dependencies [07d01fd] - vee-validate@4.12.6 ## 4.12.5 ### Patch Changes - Updated dependencies [d779980] - Updated dependencies [9eda544] - vee-validate@4.12.5 ## 4.12.4 ### Patch Changes - Updated dependencies [2a09a58] - vee-validate@4.12.4 ## 4.12.3 ### Patch Changes - Updated dependencies [72e4379] - Updated dependencies [a18c19f] - Updated dependencies [e2171f8] - vee-validate@4.12.3 ## 4.12.2 ### Patch Changes - Updated dependencies [b2203c8e] - Updated dependencies [ec8a4d7e] - vee-validate@4.12.2 ## 4.12.1 ### Patch Changes - Updated dependencies [36f6b9e6] - Updated dependencies [c1c6f399] - vee-validate@4.12.1 ## 4.12.0 ### Patch Changes - Updated dependencies [f9a95843] - Updated dependencies [bbecc973] - Updated dependencies [f688896f] - Updated dependencies [2abb8966] - Updated dependencies [e370413b] - Updated dependencies [95b701f7] - vee-validate@4.12.0 ## 4.11.8 ### Patch Changes - Updated dependencies [d1b5b855] - Updated dependencies [78c4668e] - vee-validate@4.11.8 ## 4.11.7 ### Patch Changes - Updated dependencies [a1414f6a] - vee-validate@4.11.7 ## 4.11.6 ### Patch Changes - Updated dependencies [f683e909] - vee-validate@4.11.6 ## 4.11.5 ### Patch Changes - Updated dependencies [27c9ef24] - Updated dependencies [804ec6fa] - vee-validate@4.11.5 ## 4.11.4 ### Patch Changes - Updated dependencies [4d8ed7eb] - Updated dependencies [b53400e2] - Updated dependencies [8f680bf1] - Updated dependencies [5231f439] - vee-validate@4.11.4 ## 4.11.3 ### Patch Changes - vee-validate@4.11.3 ## 4.11.2 ### Patch Changes - Updated dependencies [2ff045c1] - Updated dependencies [73219b40] - Updated dependencies [4947e88f] - Updated dependencies [ecbb690d] - vee-validate@4.11.2 ## 4.11.1 ### Patch Changes - Updated dependencies [5e23dcb9] - vee-validate@4.11.1 ## 4.11.0 ### Patch Changes - Updated dependencies [2d8143f9] - vee-validate@4.11.0 ## 4.10.9 ### Patch Changes - Updated dependencies [c02337f3] - vee-validate@4.10.9 ## 4.10.8 ### Patch Changes - Updated dependencies [a9a473b4] - vee-validate@4.10.8 ## 4.10.7 ### Patch Changes - Updated dependencies [9290f5a9] - Updated dependencies [93f8001a] - vee-validate@4.10.7 ## 4.10.6 ### Patch Changes - Updated dependencies [40ce7a91] - Updated dependencies [e9b215a7] - Updated dependencies [4e11ff95] - Updated dependencies [e354a13a] - Updated dependencies [68080d28] - vee-validate@4.10.6 ## 4.10.5 ### Patch Changes - Updated dependencies [6a1dc9bd] - vee-validate@4.10.5 ## 4.10.4 ### Patch Changes - Updated dependencies [2f9ca91c] - vee-validate@4.10.4 ## 4.10.3 ### Patch Changes - Updated dependencies [32537e14] - Updated dependencies [c3698f07] - vee-validate@4.10.3 ## 4.10.2 ### Patch Changes - Updated dependencies [1660048e] - vee-validate@4.10.2 ## 4.10.1 ### Patch Changes - Updated dependencies [fc416918] - Updated dependencies [435e7857] - Updated dependencies [273cca74] - vee-validate@4.10.1 ## 4.10.0 ### Patch Changes - Updated dependencies [77345c42] - Updated dependencies [7a548f42] - Updated dependencies [7ce9d671] - Updated dependencies [bfd6b00a] - Updated dependencies [f1dc1359] - Updated dependencies [d4fafc95] - Updated dependencies [3e4a7c13] - Updated dependencies [2cf0eec9] - Updated dependencies [05d957ec] - Updated dependencies [ed208918] - Updated dependencies [6a3f9f15] - vee-validate@4.10.0 ## 4.9.6 ### Patch Changes - Updated dependencies [b138282a] - Updated dependencies [6e074f77] - vee-validate@4.9.6 ## 4.9.5 ### Patch Changes - Updated dependencies [7356c102] - vee-validate@4.9.5 ## 4.9.4 ### Patch Changes - Updated dependencies [f4ea2c05] - vee-validate@4.9.4 ## 4.9.3 ### Patch Changes - Updated dependencies [09d5596b] - Updated dependencies [9bfbfaaf] - Updated dependencies [48b45d91] - vee-validate@4.9.3 ## 4.9.2 ### Patch Changes - Updated dependencies [31090e0d] - Updated dependencies [9046308b] - Updated dependencies [fe322a07] - vee-validate@4.9.2 ## 4.9.1 ### Patch Changes - Updated dependencies [681bbab4] - vee-validate@4.9.1 ## 4.9.0 ### Patch Changes - Updated dependencies [7554d4a6] - Updated dependencies [41b5d39b] - Updated dependencies [95409080] - Updated dependencies [298577b7] - vee-validate@4.9.0 ## 4.8.6 ### Patch Changes - 6e0b0557: Introduced official nuxt module package - Updated dependencies [6e0b0557] - vee-validate@4.8.6 ================================================ FILE: packages/nuxt/README.md ================================================ # @vee-validate/nuxt

> Official vee-validate Nuxt module

Official vee-validate's Nuxt module ## Features - Auto import of vee-validate components - Auto import of vee-validate composables - Detecting if you are using `zod` or `yup` and exposing the `toTypedSchema` suitable for either. ## Getting Started In your nuxt project install the vee-validate nuxt module: ```sh # npm npm i @vee-validate/nuxt # pnpm pnpm add @vee-validate/nuxt # yarn yarn add @vee-validate/nuxt ``` Then add the module to your `modules` config in `nuxt.config.ts`: ```ts export default defineNuxtConfig({ // ... modules: [ //... '@vee-validate/nuxt', ], }); ``` ## Types No types are exposed by default to avoid having conflicts with other libraries, aside from vee-validate's main API components/composables. You can still import them via `vee-validate`. ## Configuration You can configure a few aspects of the `@vee-validate/nuxt` module. Here is the config interface: ```ts export default defineNuxtConfig({ // ... modules: [ //... [ '@vee-validate/nuxt', { // disable or enable auto imports autoImports: true, // Use different names for components componentNames: { Form: 'VeeForm', Field: 'VeeField', FieldArray: 'VeeFieldArray', ErrorMessage: 'VeeErrorMessage', }, }, ], ], }); ``` You can also use the `veeValidate` config key instead of the array syntax: ```ts export default defineNuxtConfig({ // ... modules: [ //... '@vee-validate/nuxt', ], veeValidate: { // disable or enable auto imports autoImports: true, // Use different names for components componentNames: { Form: 'VeeForm', Field: 'VeeField', FieldArray: 'VeeFieldArray', ErrorMessage: 'VeeErrorMessage', }, }, }); ``` ================================================ FILE: packages/nuxt/package.json ================================================ { "name": "@vee-validate/nuxt", "version": "5.0.0-beta.1", "description": "Official vee-validate nuxt module", "author": "Abdelrahman Awad ", "license": "MIT", "type": "module", "main": "./dist/module.mjs", "types": "./dist/module.d.ts", "homepage": "https://vee-validate.logaretm.com/v5/integrations/nuxt", "repository": { "url": "https://github.com/logaretm/vee-validate.git", "type": "git", "directory": "packages/nuxt" }, "scripts": { "build": "nuxt-module-build build" }, "exports": { ".": { "types": "./dist/module.d.ts", "import": "./dist/module.mjs", "require": "./dist/module.cjs" } }, "sideEffects": false, "keywords": [ "VueJS", "Vue", "nuxtjs", "nuxt", "validation", "validator", "inputs", "form" ], "files": [ "dist/*" ], "dependencies": { "@nuxt/kit": "^3.13.2", "local-pkg": "^0.5.0", "vee-validate": "workspace:*" }, "devDependencies": { "@nuxt/eslint-config": "^0.6.0", "@nuxt/module-builder": "^0.8.4", "@nuxt/schema": "^3.13.2", "@nuxt/test-utils": "^3.14.4", "nuxt": "^3.13.2" } } ================================================ FILE: packages/nuxt/src/module.ts ================================================ import { defineNuxtModule, addComponent, addImports, resolveModule } from '@nuxt/kit'; import type { Nuxt, NuxtModule } from '@nuxt/schema'; type ComponentName = 'Field' | 'Form' | 'ErrorMessage' | 'FieldArray'; export interface VeeValidateNuxtOptions { autoImports?: boolean; componentNames?: Partial>; } const components: ComponentName[] = ['ErrorMessage', 'Field', 'FieldArray', 'Form']; const composables = [ 'useField', 'useFieldArray', 'useFieldError', 'useFieldValue', 'useForm', 'useFormContext', 'useFormErrors', 'useFormValues', 'useIsFieldDirty', 'useIsFieldTouched', 'useIsFieldValid', 'useIsFormDirty', 'useIsFormTouched', 'useIsFormValid', 'useIsSubmitting', 'useIsValidating', 'useResetForm', 'useSubmitCount', 'useSubmitForm', 'useValidateField', 'useValidateForm', ]; export default defineNuxtModule({ meta: { name: 'vee-validate', configKey: 'veeValidate', }, defaults: { autoImports: true, componentNames: {}, }, setup(options, nuxt) { addMjsAlias('vee-validate', 'vee-validate', nuxt); prepareVeeValidate(nuxt); if (options.autoImports) { composables.forEach(composable => { addImports({ name: composable, as: composable, from: 'vee-validate', }); }); components.forEach(component => { addComponent({ name: options.componentNames?.[component] ?? component, export: component, filePath: 'vee-validate', }); }); } }, }) as NuxtModule; function addMjsAlias(pkgName: string, fileName: string, nuxt: Nuxt) { // FIXME: Deprecated, idk why since it duplicate imports nuxt.options.alias[pkgName] = nuxt.options.alias[pkgName] || resolveModule(`${pkgName}/dist/${fileName}.mjs`, { paths: [nuxt.options.rootDir, import.meta.url], }); } declare module '@nuxt/schema' { interface NuxtConfig { 'vee-validate'?: VeeValidateNuxtOptions; } interface NuxtOptions { 'vee-validate'?: VeeValidateNuxtOptions; } } /** * Excludes vee-validate and vee-validate/rules from the optimization process. * The optimization process causes issues with the symbols export not matching between the module components and the main vee-validate package. * Maybe it is because vite chunks them in different files/sources. * Only happens with SSR tho, SPA works. */ function prepareVeeValidate(nuxt: Nuxt) { nuxt.options.vite.optimizeDeps = nuxt.options.vite.optimizeDeps || {}; nuxt.options.vite.optimizeDeps.exclude = nuxt.options.vite.optimizeDeps.exclude || []; nuxt.options.vite.optimizeDeps.exclude.push('vee-validate', '@vee-validate/rules'); } ================================================ FILE: packages/rules/CHANGELOG.md ================================================ # Change Log ## 5.0.0-beta.1 ### Patch Changes - Updated dependencies [f629397] - Updated dependencies [f2807b8] - Updated dependencies [e6db423] - Updated dependencies [49fcf4c] - Updated dependencies [1ce0731] - Updated dependencies [fb5e04e] - Updated dependencies [095df65] - vee-validate@5.0.0-beta.1 ## 5.0.0-beta.0 ### Major Changes - 04ff47c: feat: implement standard schema ### Patch Changes - Updated dependencies [04ff47c] - vee-validate@5.0.0-beta.0 ## 4.15.1 ### Patch Changes - Updated dependencies [721e980] - Updated dependencies [546d82e] - vee-validate@4.15.1 ## 4.15.0 ### Patch Changes - Updated dependencies [30281f5] - Updated dependencies [ec121b1] - vee-validate@4.15.0 ## 4.14.7 ### Patch Changes - Updated dependencies [be994b4] - vee-validate@4.14.7 ## 4.14.6 ### Patch Changes - vee-validate@4.14.6 ## 4.14.5 ### Patch Changes - Updated dependencies [e9f8c88] - vee-validate@4.14.5 ## 4.14.4 ### Patch Changes - 4f88d85: fix: specify module type on package.json - Updated dependencies [f33974c] - Updated dependencies [0991c01] - Updated dependencies [ecb540a] - Updated dependencies [4f88d85] - vee-validate@4.14.4 ## 4.14.3 ### Patch Changes - Updated dependencies [07c27d5] - vee-validate@4.14.3 ## 4.14.2 ### Patch Changes - Updated dependencies [f0d4e24] - vee-validate@4.14.2 ## 4.14.1 ### Patch Changes - vee-validate@4.14.1 ## 4.14.0 ### Minor Changes - 404cf57: chore: bump release ### Patch Changes - a8524a1: fix: incorrect shared type import in rules - 97cebd8: chore: add 'exports' field in package.json for all packages - e69e0df: fix: updated email regex closes #4801 - Updated dependencies [f7a4929] - Updated dependencies [97cebd8] - Updated dependencies [404cf57] - Updated dependencies [421ae69] - vee-validate@4.14.0 ## 4.13.2 ### Patch Changes - Updated dependencies [afbd0e5] - vee-validate@4.13.2 ## 4.13.1 ### Patch Changes - vee-validate@4.13.1 ## 4.13.0 ### Patch Changes - fdb2d47: fix: offer an all export with simplified typings closes #4766 - Updated dependencies [454bc45] - Updated dependencies [ae3772a] - Updated dependencies [27fe5c8] - Updated dependencies [fd008c1] - vee-validate@4.13.0 ## 4.12.8 ### Patch Changes - Updated dependencies [f8bab9c] - vee-validate@4.12.8 ## 4.12.7 ### Patch Changes - Updated dependencies [1376794] - Updated dependencies [c4415f8] - vee-validate@4.12.7 ## 4.12.6 ### Patch Changes - Updated dependencies [07d01fd] - vee-validate@4.12.6 ## 4.12.5 ### Patch Changes - Updated dependencies [d779980] - Updated dependencies [9eda544] - vee-validate@4.12.5 ## 4.12.4 ### Patch Changes - Updated dependencies [2a09a58] - vee-validate@4.12.4 ## 4.12.3 ### Patch Changes - 797f663: fix: use a better email re pulled from zod closes #4585 - Updated dependencies [72e4379] - Updated dependencies [a18c19f] - Updated dependencies [e2171f8] - vee-validate@4.12.3 ## 4.12.2 ### Patch Changes - Updated dependencies [b2203c8e] - Updated dependencies [ec8a4d7e] - vee-validate@4.12.2 ## 4.12.1 ### Patch Changes - Updated dependencies [36f6b9e6] - Updated dependencies [c1c6f399] - vee-validate@4.12.1 ## 4.12.0 ### Patch Changes - e35c361c: fix: make length rule consistent with other rules closes #4522 - Updated dependencies [f9a95843] - Updated dependencies [bbecc973] - Updated dependencies [f688896f] - Updated dependencies [2abb8966] - Updated dependencies [e370413b] - Updated dependencies [95b701f7] - vee-validate@4.12.0 ## 4.11.8 ### Patch Changes - Updated dependencies [d1b5b855] - Updated dependencies [78c4668e] - vee-validate@4.11.8 ## 4.11.7 ### Patch Changes - c6a1edc4: "fix: removed default export in rules pkg closes #4470" - Updated dependencies [a1414f6a] - vee-validate@4.11.7 ## 4.11.6 ### Patch Changes - Updated dependencies [f683e909] - vee-validate@4.11.6 ## 4.11.5 ### Patch Changes - Updated dependencies [27c9ef24] - Updated dependencies [804ec6fa] - vee-validate@4.11.5 ## 4.11.4 ### Patch Changes - Updated dependencies [4d8ed7eb] - Updated dependencies [b53400e2] - Updated dependencies [8f680bf1] - Updated dependencies [5231f439] - vee-validate@4.11.4 ## 4.11.3 ### Patch Changes - vee-validate@4.11.3 ## 4.11.2 ### Patch Changes - Updated dependencies [2ff045c1] - Updated dependencies [73219b40] - Updated dependencies [4947e88f] - Updated dependencies [ecbb690d] - vee-validate@4.11.2 ## 4.11.1 ### Patch Changes - Updated dependencies [5e23dcb9] - vee-validate@4.11.1 ## 4.11.0 ### Patch Changes - Updated dependencies [2d8143f9] - vee-validate@4.11.0 ## 4.10.9 ### Patch Changes - Updated dependencies [c02337f3] - vee-validate@4.10.9 ## 4.10.8 ### Patch Changes - Updated dependencies [a9a473b4] - vee-validate@4.10.8 ## 4.10.7 ### Patch Changes - Updated dependencies [9290f5a9] - Updated dependencies [93f8001a] - vee-validate@4.10.7 ## 4.10.6 ### Patch Changes - Updated dependencies [40ce7a91] - Updated dependencies [e9b215a7] - Updated dependencies [4e11ff95] - Updated dependencies [e354a13a] - Updated dependencies [68080d28] - vee-validate@4.10.6 ## 4.10.5 ### Patch Changes - Updated dependencies [6a1dc9bd] - vee-validate@4.10.5 ## 4.10.4 ### Patch Changes - Updated dependencies [2f9ca91c] - vee-validate@4.10.4 ## 4.10.3 ### Patch Changes - Updated dependencies [32537e14] - Updated dependencies [c3698f07] - vee-validate@4.10.3 ## 4.10.2 ### Patch Changes - Updated dependencies [1660048e] - vee-validate@4.10.2 ## 4.10.1 ### Patch Changes - Updated dependencies [fc416918] - Updated dependencies [435e7857] - Updated dependencies [273cca74] - vee-validate@4.10.1 ## 4.10.0 ### Patch Changes - Updated dependencies [77345c42] - Updated dependencies [7a548f42] - Updated dependencies [7ce9d671] - Updated dependencies [bfd6b00a] - Updated dependencies [f1dc1359] - Updated dependencies [d4fafc95] - Updated dependencies [3e4a7c13] - Updated dependencies [2cf0eec9] - Updated dependencies [05d957ec] - Updated dependencies [ed208918] - Updated dependencies [6a3f9f15] - vee-validate@4.10.0 ## 4.9.6 ### Patch Changes - Updated dependencies [b138282a] - Updated dependencies [6e074f77] - vee-validate@4.9.6 ## 4.9.5 ### Patch Changes - Updated dependencies [7356c102] - vee-validate@4.9.5 ## 4.9.4 ### Patch Changes - Updated dependencies [f4ea2c05] - vee-validate@4.9.4 ## 4.9.3 ### Patch Changes - Updated dependencies [09d5596b] - Updated dependencies [9bfbfaaf] - Updated dependencies [48b45d91] - vee-validate@4.9.3 ## 4.9.2 ### Patch Changes - Updated dependencies [31090e0d] - Updated dependencies [9046308b] - Updated dependencies [fe322a07] - vee-validate@4.9.2 ## 4.9.1 ### Patch Changes - Updated dependencies [681bbab4] - vee-validate@4.9.1 ## 4.9.0 ### Patch Changes - f5b34823: handle mimes with plus signs - Updated dependencies [7554d4a6] - Updated dependencies [41b5d39b] - Updated dependencies [95409080] - Updated dependencies [298577b7] - vee-validate@4.9.0 ## 4.8.6 ### Patch Changes - 6e0b0557: Introduced official nuxt module package - Updated dependencies [6e0b0557] - vee-validate@4.8.6 ## 4.8.5 ### Patch Changes - 9048a238: fixed zod union issues not showing up as errors closes #4204 - Updated dependencies [9048a238] - vee-validate@4.8.5 All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [4.8.4](https://github.com/logaretm/vee-validate/compare/v4.8.3...v4.8.4) (2023-03-24) **Note:** Version bump only for package @vee-validate/rules ## [4.8.3](https://github.com/logaretm/vee-validate/compare/v4.8.2...v4.8.3) (2023-03-15) **Note:** Version bump only for package @vee-validate/rules ## [4.8.2](https://github.com/logaretm/vee-validate/compare/v4.8.1...v4.8.2) (2023-03-14) **Note:** Version bump only for package @vee-validate/rules ## [4.8.1](https://github.com/logaretm/vee-validate/compare/v4.8.0...v4.8.1) (2023-03-12) **Note:** Version bump only for package @vee-validate/rules # [4.8.0](https://github.com/logaretm/vee-validate/compare/v4.7.4...v4.8.0) (2023-03-12) ### Bug Fixes - remove dead code closes [#4145](https://github.com/logaretm/vee-validate/issues/4145) closes [#4143](https://github.com/logaretm/vee-validate/issues/4143) ([04338f5](https://github.com/logaretm/vee-validate/commit/04338f53336ddc053c88b00c1ec6b1fe6935374d)) - remove extra double negation from between rule closes [#4144](https://github.com/logaretm/vee-validate/issues/4144) ([192d23f](https://github.com/logaretm/vee-validate/commit/192d23fb07de2c2d9872961e6f242e105a6bb8ea)) ### Features - Better Yup and Zod typing with output types and input inference ([#4064](https://github.com/logaretm/vee-validate/issues/4064)) ([3820a5b](https://github.com/logaretm/vee-validate/commit/3820a5b8eb3f8c6cd9239057746ccfb4b2e57e76)) ## [4.7.4](https://github.com/logaretm/vee-validate/compare/v4.7.3...v4.7.4) (2023-02-07) ### Bug Fixes - ext rule regex has wildcard when it should be literal period ([#4045](https://github.com/logaretm/vee-validate/issues/4045)) ([5265af5](https://github.com/logaretm/vee-validate/commit/5265af5c75b7809ebd73b22d4c8319a8a146da5a)) ## [4.7.3](https://github.com/logaretm/vee-validate/compare/v4.7.2...v4.7.3) (2022-11-13) ### Bug Fixes - rename old excluded with not_one_of closes [#3993](https://github.com/logaretm/vee-validate/issues/3993) ([7fc5077](https://github.com/logaretm/vee-validate/commit/7fc50773275c9c65cdbb0735d0b14dfe7ffca227)) ## [4.7.2](https://github.com/logaretm/vee-validate/compare/v4.7.1...v4.7.2) (2022-11-02) **Note:** Version bump only for package @vee-validate/rules ## [4.7.1](https://github.com/logaretm/vee-validate/compare/v4.7.0...v4.7.1) (2022-10-23) **Note:** Version bump only for package @vee-validate/rules # [4.7.0](https://github.com/logaretm/vee-validate/compare/v4.6.10...v4.7.0) (2022-10-09) **Note:** Version bump only for package @vee-validate/rules ## [4.6.10](https://github.com/logaretm/vee-validate/compare/v4.6.9...v4.6.10) (2022-09-30) **Note:** Version bump only for package @vee-validate/rules ## [4.6.9](https://github.com/logaretm/vee-validate/compare/v4.6.8...v4.6.9) (2022-09-19) **Note:** Version bump only for package @vee-validate/rules ## [4.6.8](https://github.com/logaretm/vee-validate/compare/v4.6.7...v4.6.8) (2022-09-19) **Note:** Version bump only for package @vee-validate/rules ## [4.6.7](https://github.com/logaretm/vee-validate/compare/v4.6.6...v4.6.7) (2022-08-27) **Note:** Version bump only for package @vee-validate/rules ## [4.6.6](https://github.com/logaretm/vee-validate/compare/v4.6.5...v4.6.6) (2022-08-16) **Note:** Version bump only for package @vee-validate/rules ## [4.6.5](https://github.com/logaretm/vee-validate/compare/v4.6.4...v4.6.5) (2022-08-11) **Note:** Version bump only for package @vee-validate/rules ## [4.6.4](https://github.com/logaretm/vee-validate/compare/v4.6.3...v4.6.4) (2022-08-07) **Note:** Version bump only for package @vee-validate/rules ## [4.6.3](https://github.com/logaretm/vee-validate/compare/v4.6.2...v4.6.3) (2022-08-07) **Note:** Version bump only for package @vee-validate/rules ## [4.6.2](https://github.com/logaretm/vee-validate/compare/v4.6.1...v4.6.2) (2022-07-17) **Note:** Version bump only for package @vee-validate/rules ## [4.6.1](https://github.com/logaretm/vee-validate/compare/v4.6.0...v4.6.1) (2022-07-12) **Note:** Version bump only for package @vee-validate/rules # [4.6.0](https://github.com/logaretm/vee-validate/compare/v4.5.11...v4.6.0) (2022-07-11) **Note:** Version bump only for package @vee-validate/rules ## [4.5.11](https://github.com/logaretm/vee-validate/compare/v4.5.10...v4.5.11) (2022-04-10) **Note:** Version bump only for package @vee-validate/rules ## [4.5.10](https://github.com/logaretm/vee-validate/compare/v4.5.9...v4.5.10) (2022-03-08) **Note:** Version bump only for package @vee-validate/rules ## [4.5.9](https://github.com/logaretm/vee-validate/compare/v4.5.8...v4.5.9) (2022-02-22) **Note:** Version bump only for package @vee-validate/rules ## [4.5.8](https://github.com/logaretm/vee-validate/compare/v4.5.7...v4.5.8) (2022-01-23) **Note:** Version bump only for package @vee-validate/rules ## [4.5.7](https://github.com/logaretm/vee-validate/compare/v4.5.6...v4.5.7) (2021-12-07) **Note:** Version bump only for package @vee-validate/rules ## [4.5.6](https://github.com/logaretm/vee-validate/compare/v4.5.5...v4.5.6) (2021-11-17) **Note:** Version bump only for package @vee-validate/rules ## [4.5.5](https://github.com/logaretm/vee-validate/compare/v4.5.4...v4.5.5) (2021-11-01) **Note:** Version bump only for package @vee-validate/rules ## [4.5.4](https://github.com/logaretm/vee-validate/compare/v4.5.3...v4.5.4) (2021-10-20) **Note:** Version bump only for package @vee-validate/rules ## [4.5.3](https://github.com/logaretm/vee-validate/compare/v4.5.2...v4.5.3) (2021-10-17) **Note:** Version bump only for package @vee-validate/rules ## [4.5.2](https://github.com/logaretm/vee-validate/compare/v4.5.1...v4.5.2) (2021-09-30) **Note:** Version bump only for package @vee-validate/rules ## [4.5.1](https://github.com/logaretm/vee-validate/compare/v4.5.0...v4.5.1) (2021-09-29) **Note:** Version bump only for package @vee-validate/rules # [4.5.0](https://github.com/logaretm/vee-validate/compare/v4.4.11...v4.5.0) (2021-09-26) **Note:** Version bump only for package @vee-validate/rules ## [4.4.11](https://github.com/logaretm/vee-validate/compare/v4.4.10...v4.4.11) (2021-09-11) **Note:** Version bump only for package @vee-validate/rules ## [4.4.10](https://github.com/logaretm/vee-validate/compare/v4.4.9...v4.4.10) (2021-08-31) **Note:** Version bump only for package @vee-validate/rules ## [4.4.9](https://github.com/logaretm/vee-validate/compare/v4.4.8...v4.4.9) (2021-08-05) **Note:** Version bump only for package @vee-validate/rules ## [4.4.8](https://github.com/logaretm/vee-validate/compare/v4.4.7...v4.4.8) (2021-07-31) **Note:** Version bump only for package @vee-validate/rules ## [4.4.7](https://github.com/logaretm/vee-validate/compare/v4.4.6...v4.4.7) (2021-07-20) **Note:** Version bump only for package @vee-validate/rules ## [4.4.6](https://github.com/logaretm/vee-validate/compare/v4.4.5...v4.4.6) (2021-07-08) ## [4.4.5](https://github.com/logaretm/vee-validate/compare/v4.4.4...v4.4.5) (2021-06-13) **Note:** Version bump only for package @vee-validate/rules ## [4.4.4](https://github.com/logaretm/vee-validate/compare/v4.4.3...v4.4.4) (2021-06-05) ## [4.4.3](https://github.com/logaretm/vee-validate/compare/v4.4.2...v4.4.3) (2021-06-02) **Note:** Version bump only for package @vee-validate/rules ## [4.4.2](https://github.com/logaretm/vee-validate/compare/v4.4.1...v4.4.2) (2021-05-28) **Note:** Version bump only for package @vee-validate/rules ## [4.4.1](https://github.com/logaretm/vee-validate/compare/v4.4.0...v4.4.1) (2021-05-24) **Note:** Version bump only for package @vee-validate/rules # [4.4.0](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.2...v4.4.0) (2021-05-23) ### Bug Fixes - export the URL rule closes [#3310](https://github.com/logaretm/vee-validate/issues/3310) ([50b6b64](https://github.com/logaretm/vee-validate/commit/50b6b64bf0b2b9a905946ed0ef7b8252501b0ccb)) # [4.4.0-alpha.2](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.1...v4.4.0-alpha.2) (2021-05-14) **Note:** Version bump only for package @vee-validate/rules # [4.4.0-alpha.1](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.0...v4.4.0-alpha.1) (2021-05-14) **Note:** Version bump only for package @vee-validate/rules # [4.4.0-alpha.0](https://github.com/logaretm/vee-validate/compare/v4.3.6...v4.4.0-alpha.0) (2021-05-14) **Note:** Version bump only for package @vee-validate/rules ## [4.3.6](https://github.com/logaretm/vee-validate/compare/v4.3.5...v4.3.6) (2021-05-08) **Note:** Version bump only for package @vee-validate/rules ## [4.3.5](https://github.com/logaretm/vee-validate/compare/v4.3.4...v4.3.5) (2021-05-01) **Note:** Version bump only for package @vee-validate/rules ## [4.3.4](https://github.com/logaretm/vee-validate/compare/v4.3.3...v4.3.4) (2021-04-27) **Note:** Version bump only for package @vee-validate/rules ## [4.3.3](https://github.com/logaretm/vee-validate/compare/v4.3.2...v4.3.3) (2021-04-22) **Note:** Version bump only for package @vee-validate/rules ## [4.3.2](https://github.com/logaretm/vee-validate/compare/v4.3.1...v4.3.2) (2021-04-21) **Note:** Version bump only for package @vee-validate/rules ## [4.3.1](https://github.com/logaretm/vee-validate/compare/v4.3.0...v4.3.1) (2021-04-18) **Note:** Version bump only for package @vee-validate/rules # [4.3.0](https://github.com/logaretm/vee-validate/compare/v4.2.4...v4.3.0) (2021-04-07) ### Features - **rules:** add url validator ([#3253](https://github.com/logaretm/vee-validate/issues/3253)) ([1fad5bb](https://github.com/logaretm/vee-validate/commit/1fad5bb5e0f3264386bc8e40beeb4cdae2a832cb)) ## [4.2.4](https://github.com/logaretm/vee-validate/compare/v4.2.3...v4.2.4) (2021-03-26) **Note:** Version bump only for package @vee-validate/rules ## [4.2.3](https://github.com/logaretm/vee-validate/compare/v4.2.2...v4.2.3) (2021-03-22) **Note:** Version bump only for package @vee-validate/rules ## [4.2.2](https://github.com/logaretm/vee-validate/compare/v4.2.1...v4.2.2) (2021-03-03) **Note:** Version bump only for package @vee-validate/rules ## [4.2.1](https://github.com/logaretm/vee-validate/compare/v4.2.0...v4.2.1) (2021-02-26) **Note:** Version bump only for package @vee-validate/rules # [4.2.0](https://github.com/logaretm/vee-validate/compare/v4.1.20...v4.2.0) (2021-02-24) **Note:** Version bump only for package @vee-validate/rules ## [4.1.20](https://github.com/logaretm/vee-validate/compare/v4.1.19...v4.1.20) (2021-02-24) **Note:** Version bump only for package @vee-validate/rules ## [4.1.19](https://github.com/logaretm/vee-validate/compare/v4.1.18...v4.1.19) (2021-02-16) **Note:** Version bump only for package @vee-validate/rules ## [4.1.17](https://github.com/logaretm/vee-validate/compare/v3.2.0...v4.1.17) (2021-02-08) ### Bug Fixes - cast digits param to number when in string format closes [#3067](https://github.com/logaretm/vee-validate/issues/3067) ([e7eb242](https://github.com/logaretm/vee-validate/commit/e7eb242a0ee9effa217133a3321e877b11cec652)) - digits rule bug ([8662307](https://github.com/logaretm/vee-validate/commit/8662307a5d86af475e304b745cc87ee16cd27d2f)) - handle empty files in files rules ([8e2f3d4](https://github.com/logaretm/vee-validate/commit/8e2f3d41fb6ff7cf6e9db593c8a5168fe53afada)) ### Features - added yup adapter pkg ([1847605](https://github.com/logaretm/vee-validate/commit/18476056fdeec7425d850313ee38b12de5ffd6c7)) - remove deprecated isTarget option ([0a7dd9c](https://github.com/logaretm/vee-validate/commit/0a7dd9cdf2cf197d84ce429c470e7b3e6e3468bd)) ## [4.0.6](https://github.com/logaretm/vee-validate/compare/@vee-validate/rules@4.0.5...@vee-validate/rules@4.0.6) (2021-02-07) **Note:** Version bump only for package @vee-validate/rules ## [4.0.5](https://github.com/logaretm/vee-validate/compare/@vee-validate/rules@4.0.4...@vee-validate/rules@4.0.5) (2021-01-11) **Note:** Version bump only for package @vee-validate/rules ## [4.0.4](https://github.com/logaretm/vee-validate/compare/@vee-validate/rules@4.0.3...@vee-validate/rules@4.0.4) (2020-12-20) **Note:** Version bump only for package @vee-validate/rules ## [4.0.3](https://github.com/logaretm/vee-validate/compare/@vee-validate/rules@4.0.2...@vee-validate/rules@4.0.3) (2020-12-07) ### Bug Fixes - cast digits param to number when in string format closes [#3067](https://github.com/logaretm/vee-validate/issues/3067) ([e7eb242](https://github.com/logaretm/vee-validate/commit/e7eb242a0ee9effa217133a3321e877b11cec652)) ## [4.0.2](https://github.com/logaretm/vee-validate/compare/@vee-validate/rules@4.0.1...@vee-validate/rules@4.0.2) (2020-12-05) **Note:** Version bump only for package @vee-validate/rules ## [4.0.1](https://github.com/logaretm/vee-validate/compare/@vee-validate/rules@4.0.0...@vee-validate/rules@4.0.1) (2020-11-25) **Note:** Version bump only for package @vee-validate/rules # [4.0.0](https://github.com/logaretm/vee-validate/compare/@vee-validate/rules@4.0.0-beta.0...@vee-validate/rules@4.0.0) (2020-11-16) **Note:** Version bump only for package @vee-validate/rules # [4.0.0-beta.0](https://github.com/logaretm/vee-validate/compare/@vee-validate/rules@4.0.0-alpha.2...@vee-validate/rules@4.0.0-beta.0) (2020-10-01) ### Bug Fixes - handle empty files in files rules ([8e2f3d4](https://github.com/logaretm/vee-validate/commit/8e2f3d41fb6ff7cf6e9db593c8a5168fe53afada)) # [4.0.0-alpha.2](https://github.com/logaretm/vee-validate/compare/@vee-validate/rules@4.0.0-alpha.1...@vee-validate/rules@4.0.0-alpha.2) (2020-07-27) **Note:** Version bump only for package @vee-validate/rules # 4.0.0-alpha.1 (2020-07-18) ### Bug Fixes - digits rule bug ([8662307](https://github.com/logaretm/vee-validate/commit/8662307a5d86af475e304b745cc87ee16cd27d2f)) ### Features - added yup adapter pkg ([1847605](https://github.com/logaretm/vee-validate/commit/18476056fdeec7425d850313ee38b12de5ffd6c7)) - remove deprecated isTarget option ([0a7dd9c](https://github.com/logaretm/vee-validate/commit/0a7dd9cdf2cf197d84ce429c470e7b3e6e3468bd)) ================================================ FILE: packages/rules/README.md ================================================ # @vee-validate/rules

> Common Laravel-Like rules module for vee-validate

## What's this VeeValidate v4 breaks up the parts that made it a popular solution for form validation into it isolated parts. The core `vee-validate` package no longer includes the rules that came by default in previous releases, the built-in rules were rebranded as **global validators**, This is where this package comes in, It includes the most common validators you will use in most of your applications, vee-validate allows you to express global rules just like Laravel's validation syntax. ## Installation ```sh yarn add @vee-validate/rules # or with npm npm install @vee-validate/rules ``` ## Usage Use the `defineRule` function from `vee-validate` core library to add rules exported by this library: ```js import { defineRule } from 'vee-validate'; import { required, email, min } from '@vee-validate/rules'; defineRule('required', required); defineRule('email', email); defineRule('min', min); ``` Or you can globally define all the available rules in the `@vee-validate/rules` package: ```js import { defineRule } from 'vee-validate'; import * as rules from '@vee-validate/rules'; Object.keys(rules).forEach(rule => { defineRule(rule, rules[rule]); }); ``` ### Available Rules - alpha - alpha_dash - alpha_num - alpha_spaces - between - confirmed - digits - dimensions - email - excluded - ext - image - one_of - integer - is - is_not - length - max - max_value - mimes - min - min_value - numeric - regex - required - required_if - size For more information about each rules, check the [documentation for global validators](https://vee-validate.logaretm.com/v5/guide/global-validators) ## Credits - Inspired by Laravel's [validation syntax](https://laravel.com/docs/5.4/validation) ================================================ FILE: packages/rules/package.json ================================================ { "name": "@vee-validate/rules", "version": "5.0.0-beta.1", "description": "Form Validation for Vue.js", "author": "Abdelrahman Awad ", "license": "MIT", "module": "dist/vee-validate-rules.mjs", "unpkg": "dist/vee-validate-rules.iife.js", "main": "dist/vee-validate-rules.mjs", "types": "dist/vee-validate-rules.d.ts", "type": "module", "exports": { ".": { "types": "./dist/vee-validate-rules.d.ts", "import": "./dist/vee-validate-rules.mjs", "require": "./dist/vee-validate-rules.cjs" } }, "homepage": "https://vee-validate.logaretm.com/v5/guide/global-validators", "repository": { "url": "https://github.com/logaretm/vee-validate.git", "type": "git", "directory": "packages/rules" }, "sideEffects": false, "keywords": [ "VueJS", "Vue", "validation", "validator", "inputs", "form" ], "files": [ "dist/*.js", "dist/*.d.ts", "dist/*.cjs", "dist/*.mjs" ], "dependencies": { "@standard-schema/spec": "^1.0.0", "vee-validate": "workspace:*" } } ================================================ FILE: packages/rules/src/alpha.ts ================================================ import { alpha, getLocale } from './alpha_helper'; import { isEmpty } from './utils'; const alphaValidator = (value: unknown, params: [string | undefined] | { locale?: string }): boolean => { if (isEmpty(value)) { return true; } const locale = getLocale(params); if (Array.isArray(value)) { return value.every(val => alphaValidator(val, { locale })); } const valueAsString = String(value); // Match at least one locale. if (!locale) { return Object.keys(alpha).some(loc => alpha[loc].test(valueAsString)); } return (alpha[locale] || alpha.en).test(valueAsString); }; export default alphaValidator; ================================================ FILE: packages/rules/src/alpha_dash.ts ================================================ import { alphaDash, getLocale } from './alpha_helper'; import { isEmpty } from './utils'; const alphaDashValidator = (value: unknown, params: [string | undefined] | { locale?: string }): boolean => { if (isEmpty(value)) { return true; } const locale = getLocale(params); if (Array.isArray(value)) { return value.every(val => alphaDashValidator(val, { locale })); } const valueAsString = String(value); // Match at least one locale. if (!locale) { return Object.keys(alphaDash).some(loc => alphaDash[loc].test(valueAsString)); } return (alphaDash[locale] || alphaDash.en).test(valueAsString); }; export default alphaDashValidator; ================================================ FILE: packages/rules/src/alpha_helper.ts ================================================ /* eslint-disable no-misleading-character-class */ /** * Some Alpha Regex helpers. * https://github.com/chriso/validator.js/blob/master/src/lib/alpha.js */ export const alpha: { [k: string]: RegExp } = { en: /^[A-Z]*$/i, cs: /^[A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]*$/i, da: /^[A-ZÆØÅ]*$/i, de: /^[A-ZÄÖÜß]*$/i, es: /^[A-ZÁÉÍÑÓÚÜ]*$/i, fr: /^[A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]*$/i, it: /^[A-Z\xC0-\xFF]*$/i, lt: /^[A-ZĄČĘĖĮŠŲŪŽ]*$/i, nl: /^[A-ZÉËÏÓÖÜ]*$/i, hu: /^[A-ZÁÉÍÓÖŐÚÜŰ]*$/i, pl: /^[A-ZĄĆĘŚŁŃÓŻŹ]*$/i, pt: /^[A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]*$/i, ru: /^[А-ЯЁ]*$/i, kz: /^[А-ЯЁ\u04D8\u04B0\u0406\u04A2\u0492\u04AE\u049A\u04E8\u04BA]*$/i, sk: /^[A-ZÁÄČĎÉÍĹĽŇÓŔŠŤÚÝŽ]*$/i, sr: /^[A-ZČĆŽŠĐ]*$/i, sv: /^[A-ZÅÄÖ]*$/i, tr: /^[A-ZÇĞİıÖŞÜ]*$/i, uk: /^[А-ЩЬЮЯЄІЇҐ]*$/i, ar: /^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]*$/, az: /^[A-ZÇƏĞİıÖŞÜ]*$/i, ug: /^[A-Zچۋېرتيۇڭوپھسداەىقكلزشغۈبنمژفگخجۆئ]*$/i, }; export const alphaSpaces: { [k: string]: RegExp } = { en: /^[A-Z\s]*$/i, cs: /^[A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ\s]*$/i, da: /^[A-ZÆØÅ\s]*$/i, de: /^[A-ZÄÖÜß\s]*$/i, es: /^[A-ZÁÉÍÑÓÚÜ\s]*$/i, fr: /^[A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ\s]*$/i, it: /^[A-Z\xC0-\xFF\s]*$/i, lt: /^[A-ZĄČĘĖĮŠŲŪŽ\s]*$/i, nl: /^[A-ZÉËÏÓÖÜ\s]*$/i, hu: /^[A-ZÁÉÍÓÖŐÚÜŰ\s]*$/i, pl: /^[A-ZĄĆĘŚŁŃÓŻŹ\s]*$/i, pt: /^[A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ\s]*$/i, ru: /^[А-ЯЁ\s]*$/i, kz: /^[А-ЯЁ\u04D8\u04B0\u0406\u04A2\u0492\u04AE\u049A\u04E8\u04BA\s]*$/i, sk: /^[A-ZÁÄČĎÉÍĹĽŇÓŔŠŤÚÝŽ\s]*$/i, sr: /^[A-ZČĆŽŠĐ\s]*$/i, sv: /^[A-ZÅÄÖ\s]*$/i, tr: /^[A-ZÇĞİıÖŞÜ\s]*$/i, uk: /^[А-ЩЬЮЯЄІЇҐ\s]*$/i, ar: /^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ\s]*$/, az: /^[A-ZÇƏĞİıÖŞÜ\s]*$/i, ug: /^[A-Zچۋېرتيۇڭوپھسداەىقكلزشغۈبنمژفگخجۆئ\s]*$/i, }; export const alphanumeric: { [k: string]: RegExp } = { en: /^[0-9A-Z]*$/i, cs: /^[0-9A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]*$/i, da: /^[0-9A-ZÆØÅ]$/i, de: /^[0-9A-ZÄÖÜß]*$/i, es: /^[0-9A-ZÁÉÍÑÓÚÜ]*$/i, fr: /^[0-9A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]*$/i, it: /^[0-9A-Z\xC0-\xFF]*$/i, lt: /^[0-9A-ZĄČĘĖĮŠŲŪŽ]*$/i, hu: /^[0-9A-ZÁÉÍÓÖŐÚÜŰ]*$/i, nl: /^[0-9A-ZÉËÏÓÖÜ]*$/i, pl: /^[0-9A-ZĄĆĘŚŁŃÓŻŹ]*$/i, pt: /^[0-9A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]*$/i, ru: /^[0-9А-ЯЁ]*$/i, kz: /^[0-9А-ЯЁ\u04D8\u04B0\u0406\u04A2\u0492\u04AE\u049A\u04E8\u04BA]*$/i, sk: /^[0-9A-ZÁÄČĎÉÍĹĽŇÓŔŠŤÚÝŽ]*$/i, sr: /^[0-9A-ZČĆŽŠĐ]*$/i, sv: /^[0-9A-ZÅÄÖ]*$/i, tr: /^[0-9A-ZÇĞİıÖŞÜ]*$/i, uk: /^[0-9А-ЩЬЮЯЄІЇҐ]*$/i, ar: /^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]*$/, az: /^[0-9A-ZÇƏĞİıÖŞÜ]*$/i, ug: /^[0-9A-Zچۋېرتيۇڭوپھسداەىقكلزشغۈبنمژفگخجۆئ]*$/i, }; export const alphaDash: { [k: string]: RegExp } = { en: /^[0-9A-Z_-]*$/i, cs: /^[0-9A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ_-]*$/i, da: /^[0-9A-ZÆØÅ_-]*$/i, de: /^[0-9A-ZÄÖÜß_-]*$/i, es: /^[0-9A-ZÁÉÍÑÓÚÜ_-]*$/i, fr: /^[0-9A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ_-]*$/i, it: /^[0-9A-Z\xC0-\xFF_-]*$/i, lt: /^[0-9A-ZĄČĘĖĮŠŲŪŽ_-]*$/i, nl: /^[0-9A-ZÉËÏÓÖÜ_-]*$/i, hu: /^[0-9A-ZÁÉÍÓÖŐÚÜŰ_-]*$/i, pl: /^[0-9A-ZĄĆĘŚŁŃÓŻŹ_-]*$/i, pt: /^[0-9A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ_-]*$/i, ru: /^[0-9А-ЯЁ_-]*$/i, kz: /^[0-9А-ЯЁ\u04D8\u04B0\u0406\u04A2\u0492\u04AE\u049A\u04E8\u04BA_-]*$/i, sk: /^[0-9A-ZÁÄČĎÉÍĹĽŇÓŔŠŤÚÝŽ_-]*$/i, sr: /^[0-9A-ZČĆŽŠĐ_-]*$/i, sv: /^[0-9A-ZÅÄÖ_-]*$/i, tr: /^[0-9A-ZÇĞİıÖŞÜ_-]*$/i, uk: /^[0-9А-ЩЬЮЯЄІЇҐ_-]*$/i, ar: /^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ_-]*$/, az: /^[0-9A-ZÇƏĞİıÖŞÜ_-]*$/i, ug: /^[0-9A-Zچۋېرتيۇڭوپھسداەىقكلزشغۈبنمژفگخجۆئ_-]*$/i, }; export const getLocale = (params?: { locale?: string } | [string | undefined]) => { if (!params) { return undefined; } return Array.isArray(params) ? params[0] : params.locale; }; ================================================ FILE: packages/rules/src/alpha_num.ts ================================================ import { alphanumeric, getLocale } from './alpha_helper'; import { isEmpty } from './utils'; const alphaNumValidator = (value: unknown, params: [string | undefined] | { locale?: string }): boolean => { if (isEmpty(value)) { return true; } const locale = getLocale(params); if (Array.isArray(value)) { return value.every(val => alphaNumValidator(val, { locale })); } const valueAsString = String(value); // Match at least one locale. if (!locale) { return Object.keys(alphanumeric).some(loc => alphanumeric[loc].test(valueAsString)); } return (alphanumeric[locale] || alphanumeric.en).test(valueAsString); }; export default alphaNumValidator; ================================================ FILE: packages/rules/src/alpha_spaces.ts ================================================ import { alphaSpaces, getLocale } from './alpha_helper'; import { isEmpty } from './utils'; const alphaSpacesValidator = (value: unknown, params: [string | undefined] | { locale?: string }): boolean => { if (isEmpty(value)) { return true; } const locale = getLocale(params); if (Array.isArray(value)) { return value.every(val => alphaSpacesValidator(val, { locale })); } const valueAsString = String(value); // Match at least one locale. if (!locale) { return Object.keys(alphaSpaces).some(loc => alphaSpaces[loc].test(valueAsString)); } return (alphaSpaces[locale] || alphaSpaces.en).test(valueAsString); }; export default alphaSpacesValidator; ================================================ FILE: packages/rules/src/between.ts ================================================ import { SimpleValidationRuleFunction } from '../../shared'; import { isEmpty } from './utils'; type BetweenParams = [string | number, string | number] | { min: number | string; max: number | string }; function getParams(params: BetweenParams) { if (Array.isArray(params)) { return { min: params[0], max: params[1] }; } return params; } const betweenValidator: SimpleValidationRuleFunction = (value, params): boolean => { if (isEmpty(value)) { return true; } const { min, max } = getParams(params); if (Array.isArray(value)) { return value.every(val => betweenValidator(val, { min, max })); } const valueAsNumber = Number(value); return Number(min) <= valueAsNumber && Number(max) >= valueAsNumber; }; export default betweenValidator; ================================================ FILE: packages/rules/src/confirmed.ts ================================================ import { getSingleParam } from './utils'; const confirmedValidator = (value: unknown, params: [string] | { target: string }) => { const target = getSingleParam(params, 'target'); return String(value) === String(target); }; export default confirmedValidator; ================================================ FILE: packages/rules/src/digits.ts ================================================ import { getSingleParam, isEmpty } from './utils'; const digitsValidator = (value: unknown, params: [string | number] | { length: string | number }): boolean => { if (isEmpty(value)) { return true; } const length = getSingleParam(params, 'length'); if (Array.isArray(value)) { return value.every(val => digitsValidator(val, { length })); } const strVal = String(value); return /^[0-9]*$/.test(strVal) && strVal.length === Number(length); }; export default digitsValidator; ================================================ FILE: packages/rules/src/dimensions.ts ================================================ import { isEmpty } from './utils'; const validateImage = (file: File, width: number, height: number): Promise => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const URL = window.URL || (window as any).webkitURL; return new Promise(resolve => { const image = new Image(); image.onerror = () => resolve(false); image.onload = () => resolve(image.width === width && image.height === height); image.src = URL.createObjectURL(file); }); }; type Params = [number | string, number | string] | { width: string | number; height: string | number }; function getParams(params: Params) { if (!params) { return { width: 0, height: 0 }; } if (Array.isArray(params)) { return { width: Number(params[0]), height: Number(params[1]) }; } return { width: Number(params.width), height: Number(params.height), }; } const dimensionsValidator = (files: unknown, params: Params) => { if (isEmpty(files)) { return true; } const { width, height } = getParams(params); const list = []; const fileList = Array.isArray(files) ? files : [files]; for (let i = 0; i < fileList.length; i++) { // if file is not an image, reject. if (!/\.(jpg|svg|jpeg|png|bmp|gif)$/i.test(fileList[i].name)) { return Promise.resolve(false); } list.push(fileList[i]); } return Promise.all(list.map(file => validateImage(file, width, height))).then(values => { return values.every(v => v); }); }; export default dimensionsValidator; ================================================ FILE: packages/rules/src/email.ts ================================================ /* eslint-disable no-useless-escape */ import { isEmpty } from './utils'; // https://github.com/colinhacks/zod/blob/40e72f9eaf576985f876d1afc2dbc22f73abc1ba/src/types.ts#L595 const emailRE = /^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; const emailValidator = (value: unknown) => { if (isEmpty(value)) { return true; } if (Array.isArray(value)) { return value.every(val => emailRE.test(String(val))); } return emailRE.test(String(value)); }; export default emailValidator; ================================================ FILE: packages/rules/src/ext.ts ================================================ import { isEmpty } from './utils'; const extValidator = (files: unknown, extensions: string[]) => { if (isEmpty(files)) { return true; } const regex = new RegExp(`\\.(${extensions.join('|')})$`, 'i'); if (Array.isArray(files)) { return files.every(file => regex.test((file as File).name)); } return regex.test((files as File).name); }; export default extValidator; ================================================ FILE: packages/rules/src/image.ts ================================================ import { isEmpty } from './utils'; const imageValidator = (files: unknown) => { if (isEmpty(files)) { return true; } const regex = /\.(jpg|svg|jpeg|png|bmp|gif|webp)$/i; if (Array.isArray(files)) { return files.every(file => regex.test((file as File).name)); } return regex.test((files as File).name); }; export default imageValidator; ================================================ FILE: packages/rules/src/index.ts ================================================ import alpha from './alpha'; import alpha_dash from './alpha_dash'; import alpha_num from './alpha_num'; import alpha_spaces from './alpha_spaces'; import between from './between'; import confirmed from './confirmed'; import digits from './digits'; import dimensions from './dimensions'; import email from './email'; import ext from './ext'; import image from './image'; import integer from './integer'; import is from './is'; import is_not from './is_not'; import length from './length'; import max from './max'; import max_value from './max_value'; import mimes from './mimes'; import min from './min'; import min_value from './min_value'; import not_one_of from './not_one_of'; import numeric from './numeric'; import one_of from './one_of'; import regex from './regex'; import required from './required'; import size from './size'; import url from './url'; import { toTypedSchema } from './toTypedSchema'; import { SimpleValidationRuleFunction } from '../../shared/types'; export const all: Record> = { alpha_dash, alpha_num, alpha_spaces, alpha, between, confirmed, digits, dimensions, email, ext, image, integer, is_not, is, length, max_value, max, mimes, min_value, min, not_one_of, numeric, one_of, regex, required, size, url, }; export { alpha_dash, alpha_num, alpha_spaces, alpha, between, confirmed, digits, dimensions, email, ext, image, integer, is_not, is, length, max_value, max, mimes, min_value, min, not_one_of, numeric, one_of, regex, required, size, url, toTypedSchema, }; ================================================ FILE: packages/rules/src/integer.ts ================================================ import { isEmpty } from './utils'; const integerValidator = (value: unknown) => { if (isEmpty(value)) { return true; } if (Array.isArray(value)) { return value.every(val => /^-?[0-9]+$/.test(String(val))); } return /^-?[0-9]+$/.test(String(value)); }; export default integerValidator; ================================================ FILE: packages/rules/src/is.ts ================================================ import { getSingleParam } from './utils'; const isValidator = (value: unknown, params: [unknown] | { other: unknown }) => { const other = getSingleParam(params, 'other'); return value === other; }; export default isValidator; ================================================ FILE: packages/rules/src/is_not.ts ================================================ import { getSingleParam } from './utils'; const isNotValidator = (value: unknown, params: [unknown] | { other: unknown }) => { const other = getSingleParam(params, 'other'); return value !== other; }; export default isNotValidator; ================================================ FILE: packages/rules/src/length.ts ================================================ import { getSingleParam, isEmpty } from './utils'; const lengthValidator = (value: unknown, params: [number | string] | { length: string | number }) => { if (isEmpty(value)) { return true; } // Normalize the length value const length = getSingleParam(params, 'length'); if (typeof value === 'number') { value = String(value); } if (!(value as ArrayLike).length) { value = Array.from(value as ArrayLike); } return (value as ArrayLike).length === Number(length); }; export default lengthValidator; ================================================ FILE: packages/rules/src/max.ts ================================================ import { getSingleParam, isEmpty } from './utils'; const maxLengthValidator = (value: unknown, params: [string | number] | { length: string | number }): boolean => { if (isEmpty(value)) { return true; } const length = getSingleParam(params, 'length'); if (Array.isArray(value)) { return value.every(val => maxLengthValidator(val, { length })); } return [...String(value)].length <= Number(length); }; export default maxLengthValidator; ================================================ FILE: packages/rules/src/max_value.ts ================================================ import { getSingleParam, isEmpty } from './utils'; const maxValueValidator = (value: unknown, params: [string | number] | { max: string | number }): boolean => { if (isEmpty(value)) { return true; } const max = getSingleParam(params, 'max'); if (Array.isArray(value)) { return value.length > 0 && value.every(val => maxValueValidator(val, { max })); } return Number(value) <= Number(max); }; export default maxValueValidator; ================================================ FILE: packages/rules/src/mimes.ts ================================================ import { isEmpty } from './utils'; const ADDED_MIME_RE = /\+(.+)?/; function buildRegExp(mime: string) { let strPattern = mime; if (ADDED_MIME_RE.test(mime)) { strPattern = mime.replace(ADDED_MIME_RE, '(\\+$1)?'); } return new RegExp(strPattern.replace('*', '.+'), 'i'); } const mimesValidator = (files: unknown, mimes: string[]) => { if (isEmpty(files)) { return true; } if (!mimes) { mimes = []; } const patterns = mimes.map(buildRegExp); if (Array.isArray(files)) { return files.every(file => patterns.some(p => p.test((file as File).type))); } return patterns.some(p => p.test((files as File).type)); }; export default mimesValidator; ================================================ FILE: packages/rules/src/min.ts ================================================ import { getSingleParam, isEmpty } from './utils'; const minValidator = (value: unknown, params: [string | number] | { length: string | number }): boolean => { if (isEmpty(value)) { return true; } const length = getSingleParam(params, 'length'); if (Array.isArray(value)) { return value.every(val => minValidator(val, { length })); } return [...String(value)].length >= Number(length); }; export default minValidator; ================================================ FILE: packages/rules/src/min_value.ts ================================================ import { getSingleParam, isEmpty } from './utils'; const minValueValidator = (value: unknown, params: [string | number] | { min: string | number }): boolean => { if (isEmpty(value)) { return true; } const min = getSingleParam(params, 'min'); if (Array.isArray(value)) { return value.length > 0 && value.every(val => minValueValidator(val, { min })); } return Number(value) >= Number(min); }; export default minValueValidator; ================================================ FILE: packages/rules/src/not_one_of.ts ================================================ import oneOf from './one_of'; import { isEmpty } from './utils'; const notOneOfValidator = (value: unknown, list: unknown[]) => { if (isEmpty(value)) { return true; } return !oneOf(value, list); }; export default notOneOfValidator; ================================================ FILE: packages/rules/src/numeric.ts ================================================ import { isEmpty } from './utils'; const ar = /^[٠١٢٣٤٥٦٧٨٩]+$/; const en = /^[0-9]+$/; const numericValidator = (value: unknown) => { if (isEmpty(value)) { return true; } const testValue = (val: unknown) => { const strValue = String(val); return en.test(strValue) || ar.test(strValue); }; if (Array.isArray(value)) { return value.every(testValue); } return testValue(value); }; export default numericValidator; ================================================ FILE: packages/rules/src/one_of.ts ================================================ import { isEmpty } from './utils'; const oneOfValidator = (value: unknown, list: unknown[]): boolean => { if (isEmpty(value)) { return true; } if (Array.isArray(value)) { return value.every(val => oneOfValidator(val, list)); } return Array.from(list).some(item => { return item == value; }); }; export default oneOfValidator; ================================================ FILE: packages/rules/src/regex.ts ================================================ import { getSingleParam, isEmpty } from './utils'; const regexValidator = (value: unknown, params: [string | RegExp] | { regex: RegExp | string }): boolean => { if (isEmpty(value)) { return true; } let regex = getSingleParam(params, 'regex'); if (typeof regex === 'string') { regex = new RegExp(regex); } if (Array.isArray(value)) { return value.every(val => regexValidator(val, { regex })); } return regex.test(String(value)); }; export default regexValidator; ================================================ FILE: packages/rules/src/required.ts ================================================ import { isEmptyArray, isNullOrUndefined } from '../../shared'; const requiredValidator = (value: unknown) => { if (isNullOrUndefined(value) || isEmptyArray(value) || value === false) { return false; } return !!String(value).trim().length; }; export default requiredValidator; ================================================ FILE: packages/rules/src/size.ts ================================================ import { getSingleParam, isEmpty } from './utils'; const sizeValidator = (files: unknown, params: [number | string] | { size: string | number }) => { if (isEmpty(files)) { return true; } let size = getSingleParam(params, 'size'); size = Number(size); if (isNaN(size)) { return false; } const nSize = size * 1024; if (!Array.isArray(files)) { return (files as File).size <= nSize; } for (let i = 0; i < files.length; i++) { if ((files[i] as File).size > nSize) { return false; } } return true; }; export default sizeValidator; ================================================ FILE: packages/rules/src/toTypedSchema.ts ================================================ import { RawFormSchema, validateObject, validate, GenericObject } from 'vee-validate'; import { StandardSchemaV1 } from '@standard-schema/spec'; export function toTypedSchema( rawSchema: RawFormSchema | string, ): StandardSchemaV1 { const schema: StandardSchemaV1 = { '~standard': { vendor: 'vee-validate', version: 1, async validate(values: unknown) { // single field if (typeof rawSchema === 'string') { const result = await validate(values, rawSchema); return { issues: result.errors.map(error => ({ path: [], message: error, })), }; } const result = await validateObject(rawSchema, values as GenericObject | undefined); const issues: StandardSchemaV1.Issue[] = []; if (result.valid) { return { value: result.values as TOutput, }; } for (const [path, error] of Object.entries(result.errors)) { if (error) { issues.push({ path: [path], message: error, }); } } return { issues, }; }, }, }; return schema; } ================================================ FILE: packages/rules/src/url.ts ================================================ import { getSingleParam, isEmpty } from './utils'; const urlValidator = (value: unknown, params: [string | RegExp | undefined] | { pattern?: string | RegExp }) => { if (isEmpty(value)) { return true; } let pattern = getSingleParam(params, 'pattern'); if (typeof pattern === 'string') { pattern = new RegExp(pattern); } try { new URL(value as string); } catch { return false; } return pattern?.test(value as string) ?? true; }; export default urlValidator; ================================================ FILE: packages/rules/src/utils.ts ================================================ export function getSingleParam(params: [TParam] | Record, paramName: string) { return Array.isArray(params) ? params[0] : params[paramName]; } export function isEmpty(value: unknown): boolean { if (value === null || value === undefined || value === '') { return true; } if (Array.isArray(value) && value.length === 0) { return true; } return false; } ================================================ FILE: packages/rules/tests/.eslintrc.json ================================================ { "rules": { "@typescript-eslint/no-explicit-any": "off" } } ================================================ FILE: packages/rules/tests/alpha.spec.ts ================================================ import validate from '../src/alpha'; test('validates that the string may only contains alphabetic characters', () => { expect(validate('abcdefgHijklMnOpqRsTUVwxYZ', [undefined])).toBe(true); expect(validate('', [undefined])).toBe(true); expect(validate(null, [undefined])).toBe(true); expect(validate(undefined, [undefined])).toBe(true); expect(validate('null', [undefined])).toBe(true); expect(validate('undefined', [undefined])).toBe(true); expect(validate(true, [undefined])).toBe(true); expect(validate(false, [undefined])).toBe(true); expect(validate(['abcdefg', 'hijk', 'lmnopq'], [undefined])).toBe(true); // invalid expect(validate('this is sparta', [undefined])).toBe(false); expect(validate('1234567a89', [undefined])).toBe(false); expect(validate({}, [undefined])).toBe(false); expect(validate(' ', [undefined])).toBe(false); expect(validate(['abcdefg', 'hijk', 'lmnopq123'], [undefined])).toBe(false); }); test('validates the string contains alphabetic chars from other locales', () => { // any locale. expect(validate('سلام', [undefined])).toBe(true); expect(validate('Привет', [undefined])).toBe(true); // specific locale expect(validate('peace', { locale: 'ar' })).toBe(false); expect(validate('peace', { locale: 'ru' })).toBe(false); // non-existent locale defaults to english validation. expect(validate('peace', { locale: 'blah' })).toBe(true); expect(validate('اين اشيائي', { locale: 'blah' })).toBe(false); // non english characters. }); ================================================ FILE: packages/rules/tests/alpha_dash.spec.ts ================================================ import validate from '../src/alpha_dash'; test('validates that the string may only contain alpha-numeric characters as well as dashes and spaces', () => { expect(validate('a', [undefined])).toBe(true); expect(validate('abcdefgHijklMnOpqRsTUVwxYZ', [undefined])).toBe(true); expect(validate('1234567890', [undefined])).toBe(true); expect(validate('abc123', [undefined])).toBe(true); expect(validate(123, [undefined])).toBe(true); expect(validate('', [undefined])).toBe(true); expect(validate(null, [undefined])).toBe(true); expect(validate(undefined, [undefined])).toBe(true); expect(validate('null', [undefined])).toBe(true); expect(validate('undefined', [undefined])).toBe(true); expect(validate('123-abc', [undefined])).toBe(true); expect(validate('123_abc', [undefined])).toBe(true); expect(validate(true, [undefined])).toBe(true); expect(validate(false, [undefined])).toBe(true); expect(validate(['a', 'b', 'cdef-_'], [undefined])).toBe(true); expect(validate('this is sparta', [undefined])).toBe(false); expect(validate({}, [undefined])).toBe(false); expect(validate(' ', [undefined])).toBe(false); expect(validate([' ', 'ada as'], [undefined])).toBe(false); }); test('validates the string contains alphabetic chars from other locales', () => { // any locale. expect(validate('سلا-م_', [undefined])).toBe(true); expect(validate('Привет_-', [undefined])).toBe(true); // specific locale expect(validate('peace', { locale: 'ar' })).toBe(false); expect(validate('peace', { locale: 'ru' })).toBe(false); // non-existent locale defaults to english validation. expect(validate('peace', { locale: 'blah' })).toBe(true); expect(validate('اين اشيائي', { locale: 'blah' })).toBe(false); // non english characters. }); ================================================ FILE: packages/rules/tests/alpha_num.spec.ts ================================================ import validate from '../src/alpha_num'; test('validates that the string may only contain alphabetic and numeric characters', () => { expect(validate('a', [undefined])).toBe(true); expect(validate('abcdefgHijklMnOpqRsTUVwxYZ', [undefined])).toBe(true); expect(validate('1234567890', [undefined])).toBe(true); expect(validate('abc123', [undefined])).toBe(true); expect(validate(123, [undefined])).toBe(true); expect(validate('', [undefined])).toBe(true); expect(validate(null, [undefined])).toBe(true); expect(validate(undefined, [undefined])).toBe(true); expect(validate('null', [undefined])).toBe(true); expect(validate('undefined', [undefined])).toBe(true); expect(validate(true, [undefined])).toBe(true); expect(validate(false, [undefined])).toBe(true); expect(validate(['asdad', 123, 'asd2123'], [undefined])).toBe(true); expect(validate('this is sparta', [undefined])).toBe(false); expect(validate('123-abc', [undefined])).toBe(false); expect(validate({}, [undefined])).toBe(false); expect(validate(' ', [undefined])).toBe(false); expect(validate(['asdasda ', '123 ad'], [undefined])).toBe(false); }); test('validates the string contains alphabetic chars from other locales', () => { // any locale. expect(validate('سلام12', [undefined])).toBe(true); expect(validate('Привет12', [undefined])).toBe(true); // specific locale expect(validate('peace', { locale: 'ar' })).toBe(false); expect(validate('peace', { locale: 'ru' })).toBe(false); // non-existent locale defaults to english validation. expect(validate('peace', { locale: 'blah' })).toBe(true); expect(validate('اين اشيائي', { locale: 'blah' })).toBe(false); // non english characters. }); ================================================ FILE: packages/rules/tests/alpha_spaces.spec.ts ================================================ import validate from '../src/alpha_spaces'; test('validates that the string may only contain alphabetic characters and spaces', () => { expect(validate('a', [undefined])).toBe(true); expect(validate('abcdefgHijklMnOpqRsTUVwxYZ', [undefined])).toBe(true); expect(validate('', [undefined])).toBe(true); expect(validate(null, [undefined])).toBe(true); expect(validate(undefined, [undefined])).toBe(true); expect(validate('null', [undefined])).toBe(true); expect(validate('undefined', [undefined])).toBe(true); expect(validate(true, [undefined])).toBe(true); expect(validate(false, [undefined])).toBe(true); expect(validate('this is sparta', [undefined])).toBe(true); expect(validate(' ', [undefined])).toBe(true); expect(validate(['adasd dasdasda', 'yy'], [undefined])).toBe(true); // invalid expect(validate('123-abc', [undefined])).toBe(false); expect(validate({}, [undefined])).toBe(false); expect(validate('1234567890', [undefined])).toBe(false); expect(validate('abc123', [undefined])).toBe(false); expect(validate(123, [undefined])).toBe(false); expect(validate(['adasd dasdasda', '123'], [undefined])).toBe(false); }); test('validates the string contains alphabetic chars from other locales', () => { // any locale. expect(validate('سلام عليكم', [undefined])).toBe(true); expect(validate('Привет т', [undefined])).toBe(true); // specific locale expect(validate('peace', { locale: 'ar' })).toBe(false); expect(validate('peace', { locale: 'ru' })).toBe(false); // non-existent locale defaults to english validation. expect(validate('peace', { locale: 'blah' })).toBe(true); expect(validate('اين اشيائي', { locale: 'blah' })).toBe(false); // non english characters. }); ================================================ FILE: packages/rules/tests/between.spec.ts ================================================ import validate from '../src/between'; test('validates numbers range', () => { const params = { min: 1, max: 3 }; expect(validate('1', params)).toBe(true); expect(validate(2, params)).toBe(true); expect(validate(3, params)).toBe(true); expect(validate([1, 2, 3], params)).toBe(true); expect(validate(undefined, params)).toBe(true); expect(validate(null, params)).toBe(true); expect(validate('', params)).toBe(true); expect(validate([], params)).toBe(true); // invalid expect(validate({}, params)).toBe(false); expect(validate('1234', params)).toBe(false); expect(validate('12', params)).toBe(false); expect(validate('abc', params)).toBe(false); expect(validate('12a', params)).toBe(false); expect(validate(0, params)).toBe(false); expect(validate(4, params)).toBe(false); expect(validate(-1, params)).toBe(false); expect(validate([4, 5, 6], params)).toBe(false); }); test('validates numbers range including negative numbers', () => { const range = { min: -10, max: 1 }; expect(validate(0, range)).toBe(true); expect(validate('-9', range)).toBe(true); }); ================================================ FILE: packages/rules/tests/confirmed.spec.ts ================================================ import validate from '../src/confirmed'; test('validates a field confirmation', () => { expect(validate('p@$$word', { target: 'p@$$word' })).toBe(true); // fields do not match. expect(validate('password', { target: 'p@$$word' })).toBe(false); }); ================================================ FILE: packages/rules/tests/digits.spec.ts ================================================ import validate from '../src/digits'; test('validates digits', () => { const params = { length: 3 }; // 3 digits only. expect(validate('123', params)).toBe(true); expect(validate('456', params)).toBe(true); expect(validate('789', params)).toBe(true); expect(validate('012', params)).toBe(true); expect(validate('000', params)).toBe(true); expect(validate(['012', '789'], params)).toBe(true); expect(validate(undefined, params)).toBe(true); expect(validate(null, params)).toBe(true); expect(validate('', params)).toBe(true); expect(validate([], params)).toBe(true); // invalid expect(validate(0, params)).toBe(false); expect(validate({}, params)).toBe(false); expect(validate('1234', params)).toBe(false); expect(validate('12', params)).toBe(false); expect(validate('abc', params)).toBe(false); expect(validate('12a', params)).toBe(false); expect(validate(['123', '12a'], params)).toBe(false); }); ================================================ FILE: packages/rules/tests/dimensions.spec.ts ================================================ import validate from '../src/dimensions'; import helpers from './helpers'; let fails = false; beforeEach(() => { window.URL.createObjectURL = () => { return 'data:image/png;base64,AAAAAAA'; }; (window as any).webkitURL = { createObjectURL() { return 'data:image/png;base64,AAAAAAA'; }, }; (window as any).Image = class Image { get src() { return 'test'; } set src(_: any) { (this as any).width = 150; (this as any).height = 100; (this as any)[fails ? 'onerror' : 'onload'](); } }; }); test('validates image dimensions', async () => { let result = await validate(helpers.file('file.jpg', 'image/jpeg', 10), { width: 150, height: 100 }); expect(result).toBe(true); // mock a failing Image, even with the right dimensions. fails = true; result = await validate([helpers.file('file.jpg', 'image/jpeg', 10)], { width: 150, height: 100 }); expect(result).toBe(false); fails = false; // not an image. result = await validate([helpers.file('file.pdf', 'application/pdf', 10)], { width: 150, height: 100 }); expect(result).toBe(false); // wrong dimensions. result = await validate([helpers.file('file.jpg', 'image/jpeg', 10)], { width: 15, height: 10 }); expect(result).toBe(false); (window as any).URL = undefined; // test webkit fallback. result = await validate([helpers.file('file.jpg', 'image/jpeg', 10)], { width: 150, height: 100 }); expect(result).toBe(true); }); ================================================ FILE: packages/rules/tests/email.spec.ts ================================================ import validate from '../src/email'; test('validates that the string is a valid email address', () => { expect(validate('someone@example.com')).toBe(true); expect(validate('someone@example.co')).toBe(true); expect(validate('someone123@example.co.uk')).toBe(true); expect(validate('very.common@example.com')).toBe(true); expect(validate('other.email-with-dash@example.com')).toBe(true); expect(validate('disposable.style.email.with+symbol@example.com')).toBe(true); expect(validate(['someone@example.com', 'someone12@example.com'])).toBe(true); expect(validate(undefined)).toBe(true); expect(validate(null)).toBe(true); expect(validate('')).toBe(true); expect(validate([])).toBe(true); // invalid expect(validate('Pelé@example.com')).toBe(false); expect(validate('@example.com')).toBe(false); expect(validate('@example')).toBe(false); expect(validate('undefined')).toBe(false); expect(validate('null')).toBe(false); expect(validate('someone@example.c')).toBe(false); expect(validate(['someone@example.com', 'someone@example.c'])).toBe(false); }); ================================================ FILE: packages/rules/tests/ext.spec.ts ================================================ import validate from '../src/ext'; import helpers from './helpers'; test('validates files extensions', () => { const params = ['txt', 'jpg', 'svg']; const validFiles = [ helpers.file('file.txt', 'text/plain'), helpers.file('file.jpg', 'image/jpeg'), helpers.file('file.svg', 'image/svg'), ]; expect(validate(validFiles, params)).toBe(true); expect(validate(helpers.file('file.pdf', 'application/pdf'), params)).toBe(false); expect(validate(helpers.file('filetxt', 'text/plain'), params)).toBe(false); expect(validate(helpers.file('file.jpgg', 'image/jpeg'), params)).toBe(false); }); ================================================ FILE: packages/rules/tests/helpers/index.ts ================================================ export default { file: (name: string, type: string, size = 1) => ({ name, type, size: size * 1024, }) as File, }; ================================================ FILE: packages/rules/tests/image.spec.ts ================================================ import validate from '../src/image'; import helpers from './helpers'; test('validates image files', () => { const validFiles = [ helpers.file('file.gif', 'image/gif'), helpers.file('file.jpg', 'image/jpeg'), helpers.file('file.jpeg', 'image/jpeg'), helpers.file('file.svg', 'image/svg'), helpers.file('file.bmp', 'image/bmp'), helpers.file('file.png', 'image/png'), helpers.file('file.png', 'image/webp'), ]; expect(validate(validFiles)).toBe(true); expect(validate(helpers.file('file.pdf', 'application/pdf'))).toBe(false); }); ================================================ FILE: packages/rules/tests/integer.spec.ts ================================================ import validate from '../src/integer'; test('validates integer numbers', () => { expect(validate('1234567890')).toBe(true); expect(validate(123)).toBe(true); expect(validate(-123)).toBe(true); expect(validate('-1234')).toBe(true); expect(validate(undefined)).toBe(true); expect(validate(null)).toBe(true); expect(validate('')).toBe(true); expect(validate([])).toBe(true); expect(validate(0)).toBe(true); // invalid expect(validate('a')).toBe(false); expect(validate('1234567a89')).toBe(false); expect(validate(true)).toBe(false); expect(validate(false)).toBe(false); expect(validate({})).toBe(false); expect(validate('+123')).toBe(false); expect(validate(12.2)).toBe(false); expect(validate('13.3')).toBe(false); }); ================================================ FILE: packages/rules/tests/is.spec.ts ================================================ import validate from '../src/is'; test('checks if the value matches another', () => { expect(validate(1, { other: '1' })).toBe(false); expect(validate(1, { other: 1 })).toBe(true); expect(validate(1, { other: 2 })).toBe(false); expect(validate({}, { other: {} })).toBe(false); const obj = {}; expect(validate(obj, { other: obj })).toBe(true); }); ================================================ FILE: packages/rules/tests/is_not.spec.ts ================================================ import validate from '../src/is_not'; test('checks if the value does not match another', () => { expect(validate(1, { other: '1' })).toBe(true); expect(validate(1, { other: 1 })).toBe(false); expect(validate(1, { other: 2 })).toBe(true); expect(validate({}, { other: {} })).toBe(true); const obj = {}; expect(validate(obj, { other: obj })).toBe(false); }); ================================================ FILE: packages/rules/tests/length.spec.ts ================================================ import validate from '../src/length'; test('validates number of characters in a string', () => { // exact length expect(validate('hey', { length: 3 })).toBe(true); expect(validate('hello', { length: 3 })).toBe(false); }); test('validates number of elements in an enumerable', () => { const firstSet = new Set(['h', 'e', 'y']); const secondSet = new Set(['h', 'e', 'l', 'l']); expect(validate(firstSet as any, { length: 3 })).toBe(true); expect(validate(secondSet as any, { length: 4 })).toBe(false); }); test('validates number of elements in an array', () => { // exact length expect(validate(['h', 'e', 'y'], { length: 3 })).toBe(true); expect(validate(['h', 'e', 'l', 'l', 'o'], { length: 3 })).toBe(false); }); test('validates strings consisting of numbers', () => { expect(validate(123 as any, { length: 3 })).toBe(true); }); test('skips empty values', () => { expect(validate('', { length: 3 })).toBe(true); expect(validate(null as any, { length: 3 })).toBe(true); expect(validate(undefined as any, { length: 3 })).toBe(true); }); ================================================ FILE: packages/rules/tests/max.spec.ts ================================================ import validate from '../src/max'; test('validates maximum number of characters in a string', () => { const params = { length: 3 }; // valid expect(validate(123, params)).toBe(true); expect(validate('abc', params)).toBe(true); expect(validate(1, params)).toBe(true); expect(validate(12, params)).toBe(true); expect(validate(undefined, params)).toBe(true); expect(validate(null, params)).toBe(true); expect(validate('', params)).toBe(true); expect(validate([1, 2], params)).toBe(true); expect(validate('𩸽寿司', params)).toBe(true); // invalid expect(validate('abcde', params)).toBe(false); expect(validate('null', params)).toBe(false); expect(validate('undefined', params)).toBe(false); expect(validate(['1234'], params)).toBe(false); expect(validate('𩸽寿司の', params)).toBe(false); }); ================================================ FILE: packages/rules/tests/max_value.spec.ts ================================================ import validate from '../src/max_value'; test('validates number maximum value', () => { const params = { max: 10 }; // valid. expect(validate(0, params)).toBe(true); expect(validate('1', params)).toBe(true); expect(validate(10, params)).toBe(true); expect(validate([10], params)).toBe(true); expect(validate(undefined, params)).toBe(true); expect(validate(null, params)).toBe(true); expect(validate('', params)).toBe(true); expect(validate([], params)).toBe(true); // invalid expect(validate(10.01, params)).toBe(false); expect(validate(11, params)).toBe(false); expect(validate({}, params)).toBe(false); expect(validate('abc', params)).toBe(false); expect(validate([10.01], params)).toBe(false); }); ================================================ FILE: packages/rules/tests/mimes.spec.ts ================================================ import validate from '../src/mimes'; import helpers from './helpers'; test('validates mime types', () => { const params = ['image/*', 'text/plain']; expect( validate( [ helpers.file('file.txt', 'text/plain'), helpers.file('file.jpg', 'image/jpeg'), helpers.file('file.svg', 'image/svg'), ], params, ), ).toBe(true); expect(validate(helpers.file('file.pdf', 'application/pdf'), params)).toBe(false); }); test('mimes with regex characters', () => { expect(validate(helpers.file('file.svg', 'image/svg'), ['image/svg+xml'])).toBe(true); expect(validate(helpers.file('file.svg', 'image/xml'), ['image/svg+xml'])).toBe(false); }); ================================================ FILE: packages/rules/tests/min.spec.ts ================================================ import validate from '../src/min'; test('validates minimum number of characters in a string', () => { const params = { length: 3 }; // valid. expect(validate('asjdj', params)).toBe(true); expect(validate('null', params)).toBe(true); expect(validate('undefined', params)).toBe(true); expect(validate(123, params)).toBe(true); expect(validate('abc', params)).toBe(true); expect(validate([123, '123', 'abc'], params)).toBe(true); expect(validate(undefined, params)).toBe(true); expect(validate(null, params)).toBe(true); expect(validate('', params)).toBe(true); expect(validate([], params)).toBe(true); expect(validate('𩸽寿司', params)).toBe(true); // invalid expect(validate(1, params)).toBe(false); expect(validate(12, params)).toBe(false); expect(validate([1], params)).toBe(false); expect(validate('𩸽', params)).toBe(false); }); ================================================ FILE: packages/rules/tests/min_value.spec.ts ================================================ import validate from '../src/min_value'; test('validates number minimum value', () => { const params = { min: -1 }; expect(validate(-1, params)).toBe(true); expect(validate(0, params)).toBe(true); expect(validate('5', params)).toBe(true); expect(validate([-1, 5], params)).toBe(true); expect(validate(undefined, params)).toBe(true); expect(validate(null, params)).toBe(true); expect(validate('', params)).toBe(true); expect(validate([], params)).toBe(true); // invalid expect(validate({}, params)).toBe(false); expect(validate('abc', params)).toBe(false); expect(validate(-2, params)).toBe(false); expect(validate('-3', params)).toBe(false); expect(validate(['-3'], params)).toBe(false); }); ================================================ FILE: packages/rules/tests/not_one_of.spec.ts ================================================ import validate from '../src/not_one_of'; test('validates that the value does not exist within a list', () => { const list = [1, 2, 3, 4, 5]; // valid. expect(validate(0, list)).toBe(true); expect(validate(6, list)).toBe(true); expect(validate([6], list)).toBe(true); // invalid expect(validate(1, list)).toBe(false); expect(validate(2, list)).toBe(false); expect(validate(3, list)).toBe(false); expect(validate(4, list)).toBe(false); expect(validate(5, list)).toBe(false); expect(validate([1, 2], list)).toBe(false); }); ================================================ FILE: packages/rules/tests/numeric.spec.ts ================================================ import validate from '../src/numeric'; test('validates that the string only contains numeric characters', () => { // valid. expect(validate('1234567890')).toBe(true); expect(validate(123)).toBe(true); expect(validate('٠١٢٣٤')).toBe(true); expect(validate('٠١٢٣٤٥٦٧٨٩')).toBe(true); expect(validate(undefined)).toBe(true); expect(validate(null)).toBe(true); expect(validate('')).toBe(true); expect(validate([])).toBe(true); expect(validate(0)).toBe(true); // invalid expect(validate('a')).toBe(false); expect(validate('1234567a89')).toBe(false); expect(validate(true)).toBe(false); expect(validate(false)).toBe(false); expect(validate({})).toBe(false); expect(validate('+123')).toBe(false); expect(validate('-123')).toBe(false); }); ================================================ FILE: packages/rules/tests/one_of.spec.ts ================================================ import validate from '../src/one_of'; test('validates that the value exists within a list', () => { const list = [1, 2, 3, 4, 5]; // valid. expect(validate(1, list)).toBe(true); expect(validate(2, list)).toBe(true); expect(validate(3, list)).toBe(true); expect(validate(4, list)).toBe(true); expect(validate(5, list)).toBe(true); // invalid expect(validate(0, list)).toBe(false); expect(validate(6, list)).toBe(false); expect(validate([1, 6], list)).toBe(false); }); ================================================ FILE: packages/rules/tests/regex.spec.ts ================================================ import validate from '../src/regex'; test('validates regular expressions', () => { const params = { regex: /^[0-9]+$/ }; expect(validate('1234567890', params)).toBe(true); expect(validate('abc', params)).toBe(false); expect(validate('abc-123', params)).toBe(false); expect(validate('1234abc5', params)).toBe(false); expect(validate(['1234567890', '321'], params)).toBe(true); expect(validate(['1234567890', 'abc'], params)).toBe(false); // empty values should pass expect(validate('', params)).toBe(true); // empty values pass expect(validate(undefined, params)).toBe(true); // empty values pass expect(validate(null, params)).toBe(true); // empty values pass expect(validate([], params)).toBe(true); // empty values pass }); ================================================ FILE: packages/rules/tests/required.spec.ts ================================================ import validate from '../src/required'; test('validates required', () => { // valid expect(validate('asjdj')).toBe(true); expect(validate(0)).toBe(true); expect(validate('undefined')).toBe(true); expect(validate('null')).toBe(true); expect(validate('s ')).toBe(true); expect(validate(true)).toBe(true); // invalid expect(validate('')).toBe(false); expect(validate(' ')).toBe(false); expect(validate([])).toBe(false); expect(validate(undefined)).toBe(false); expect(validate(null)).toBe(false); expect(validate(false)).toBe(false); }); ================================================ FILE: packages/rules/tests/size.spec.ts ================================================ import validate from '../src/size'; import helpers from './helpers'; test('validates file size', () => { const params = { size: 15 }; expect(validate([helpers.file('file.txt', 'text/plain', 10)], params)).toBe(true); expect(validate(helpers.file('file.txt', 'text/plain', 15), params)).toBe(true); expect(validate([helpers.file('file.txt', 'text/plain', 16)], params)).toBe(false); expect(validate([helpers.file('file.txt', 'text/plain', 16)], { size: 'not a number' })).toBe(false); }); ================================================ FILE: packages/rules/tests/toTypedSchema.spec.ts ================================================ import { defineRule, useField, useForm } from 'vee-validate'; import { toTypedSchema } from '../src/toTypedSchema'; import { mountWithHoc, flushPromises, setValue } from '../../vee-validate/tests/helpers'; import required from '../src/required'; import email from '../src/email'; import min from '../src/min'; const REQUIRED_MSG = 'field is required'; const MIN_MSG = 'field is too short'; const EMAIL_MSG = 'field must be a valid email'; defineRule('required', (val: any) => (required(val) ? true : REQUIRED_MSG)); defineRule('email', (val: any) => (email(val) ? true : EMAIL_MSG)); defineRule('min', (val: any, params: any) => (min(val, params) ? true : MIN_MSG)); test('validates typed schema form with global rules', async () => { const wrapper = mountWithHoc({ setup() { const schema = toTypedSchema<{ email: string; password: string }>({ email: 'required|email', password: 'required|min:8', }); const { defineField, errors } = useForm({ validationSchema: schema, validateOnMount: true, }); const [email] = defineField('email', { validateOnModelUpdate: true }); const [password] = defineField('password', { validateOnModelUpdate: true }); return { schema, email, password, errors, }; }, template: `
{{ errors.email }} {{ errors.password }}
`, }); const email = wrapper.$el.querySelector('#email'); const password = wrapper.$el.querySelector('#password'); const emailError = wrapper.$el.querySelector('#emailErr'); const passwordError = wrapper.$el.querySelector('#passwordErr'); await flushPromises(); setValue(email, 'hello@'); setValue(password, '1234'); await flushPromises(); expect(emailError.textContent).toBe(EMAIL_MSG); expect(passwordError.textContent).toBe(MIN_MSG); setValue(email, 'hello@email.com'); setValue(password, '12346789'); await flushPromises(); expect(emailError.textContent).toBe(''); expect(passwordError.textContent).toBe(''); }); test('validates typed field with global rules', async () => { const wrapper = mountWithHoc({ setup() { const rules = toTypedSchema('required|min:8'); const { value, errorMessage } = useField('test', rules); return { value, errorMessage, }; }, template: `

{{ errorMessage }}

`, }); const input = wrapper.$el.querySelector('input'); const error = wrapper.$el.querySelector('p'); setValue(input, ''); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MSG); setValue(input, '12'); await flushPromises(); expect(error.textContent).toBe(MIN_MSG); setValue(input, '12345678'); await flushPromises(); expect(error.textContent).toBe(''); }); ================================================ FILE: packages/rules/tests/url.spec.ts ================================================ import validate from '../src/url'; test('validates url', () => { const validUrl = 'https://test.com:8080/en/whatever/?q=test#wow'; // no pattern expect(validate(validUrl, {})).toBe(true); expect(validate('/only/path', {})).toBe(false); expect(validate('invalid', {})).toBe(false); // with pattern expect(validate(validUrl, { pattern: 'https://.*' })).toBe(true); expect(validate(validUrl, { pattern: /http:\/\/.*/ })).toBe(false); expect(validate(validUrl, ['/en/whatever/'])).toBe(true); expect(validate(validUrl, ['/fr/whatever/'])).toBe(false); }); ================================================ FILE: packages/shared/README.md ================================================ # @vee-validate/shared Common functions and utils used in other packages, this is never published and should be inlined with other packages output. ================================================ FILE: packages/shared/index.ts ================================================ export * from './utils'; export * from './types'; ================================================ FILE: packages/shared/types.ts ================================================ export interface FieldValidationMetaInfo { field: string; name: string; label?: string; value: unknown; form: Record; rule?: { name: string; params?: Record | unknown[]; }; } export type ValidationRuleFunction> = ( value: TValue, params: TParams, ctx: FieldValidationMetaInfo, ) => boolean | string | Promise; export type SimpleValidationRuleFunction> = ( value: TValue, params: TParams, ) => boolean | string | Promise; export type ValidationMessageGenerator = (ctx: FieldValidationMetaInfo) => string; export type Optional = T extends Record ? Partial : T | undefined; export type InterpolateOptions = { prefix: string; suffix: string; }; ================================================ FILE: packages/shared/utils.spec.ts ================================================ import { getTag, isPlainObject } from './utils'; describe('getTag()', () => { test('should return [object Undefined] for undefined type', () => { const tag = getTag(undefined); expect(tag).toBe('[object Undefined]'); }); test('should return [object Null] for null type', () => { const tag = getTag(null); expect(tag).toBe('[object Null]'); }); }); describe('isPlainObject()', () => { test('should return true if object has no prototype', () => { const result = isPlainObject(Object.create(null)); expect(result).toBe(true); }); }); ================================================ FILE: packages/shared/utils.ts ================================================ export function isCallable(fn: unknown): fn is (...args: any[]) => any { return typeof fn === 'function'; } export function isNullOrUndefined(value: unknown): value is undefined | null { return value === null || value === undefined; } export function isEmptyArray(arr: unknown): boolean { return Array.isArray(arr) && arr.length === 0; } export const isObject = (obj: unknown): obj is Record => obj !== null && !!obj && typeof obj === 'object' && !Array.isArray(obj); export function isIndex(value: unknown): value is number { return Number(value) >= 0; } export function toNumber(value: string): number | string { const n = parseFloat(value); return isNaN(n) ? value : n; } export function isObjectLike(value: any) { return typeof value === 'object' && value !== null; } export function getTag(value: any) { 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 export function isPlainObject(value: any) { 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; } export function merge(target: any, source: any) { 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 */ export function normalizeFormPath(path: string): string { 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; } ================================================ FILE: packages/vee-validate/CHANGELOG.md ================================================ # Change Log ## 5.0.0-beta.1 ### Minor Changes - f629397: feat: remove deprecated useField props - fb5e04e: feat: remove deprecated useForm define methods ### Patch Changes - f2807b8: fix(devtools): prevent SSR memory leak in DevTools integration - e6db423: feat: expose `getConfig` as a public API - 49fcf4c: Fix dev tools not showing all field states - 1ce0731: fix: revert number input type back to string from number, closes #4699 and #4482 - 095df65: Fix dev tools do not display nested fields with name 'id' ## 5.0.0-beta.0 ### Major Changes - 04ff47c: feat: implement standard schema ## 4.15.1 ### Patch Changes - 721e980: Align FormErrors type with its actual structure at runtime. - 546d82e: fix: normalize objects before equality checks closes #5006 ## 4.15.0 ### Patch Changes - 30281f5: fix: lazy load the devtools dep to force it out of production bundle - ec121b1: fix: skip loading devtools if in SSR ## 4.14.7 ### Patch Changes - be994b4: fix: show uncontrolled field info in devtools closes #4914 ## 4.14.6 ## 4.14.5 ### Patch Changes - e9f8c88: fix: force loading the mjs module when using nuxt ## 4.14.4 ### Patch Changes - f33974c: fix(types): expose field and form slot prop types closes #4900 - 0991c01: fix: devtools crashing when a field name is defined as getter - ecb540a: fix: handle getter field names properly closes #4877 - 4f88d85: fix: specify module type on package.json ## 4.14.3 ### Patch Changes - 07c27d5: fix: remove rogue console.log ## 4.14.2 ### Patch Changes - f0d4e24: fix: upgrade vue devtools dependency version closes #4863 ## 4.14.1 ## 4.14.0 ### Minor Changes - 404cf57: chore: bump release ### Patch Changes - f7a4929: feat: expose useFormContext closes #4490 - 97cebd8: chore: add 'exports' field in package.json for all packages - 421ae69: "fix(types): export component internal types" ## 4.13.2 ### Patch Changes - afbd0e5: feat: support valibot 0.33.0 ## 4.13.1 ## 4.13.0 ### Minor Changes - 454bc45: fix: force resetForm should not merge values closes #4680 closes #4729 - 27fe5c8: feat: provide form values as context for yup closes #4753 ### Patch Changes - ae3772a: feat: expose setValue on Field instance and slot props closes #4755 - fd008c1: feat: added ResetFormOpts arg to useResetForm closes #4707 ## 4.12.8 ### Patch Changes - f8bab9c: "fix: field-level validation not working with typed scheams closes #4744" ## 4.12.7 ### Patch Changes - 1376794: fix: handle meta.required for single field schemas closes #4738 - 1376794: fix: add try-catch for schema description logic across all major schema providers - c4415f8: fix: ensure meta.required is reactive whenever the schema changes closes #4738 ## 4.12.6 ### Patch Changes - 07d01fd: fix: re-apply errors to avoid race conditions ## 4.12.5 ### Patch Changes - d779980: fix: make sure removePathState removes the correct path state - 9eda544: "fix: remove event arg from define field handlers for component compat closes #4637" ## 4.12.4 ### Patch Changes - 2a09a58: "fix: check if both source and target objects are POJOs" ## 4.12.3 ### Patch Changes - 72e4379: fix: remove deep data mutation warning closes #4597 - a18c19f: feat: allow path meta querying for nested fields closes #4575 - e2171f8: feat: expose some state on form instance ## 4.12.2 ### Patch Changes - b2203c8e: fix: apply schema casts when submitting closes #4565 - ec8a4d7e: fix: defineField should respect global validateOnModelUpdate closes #4567 ## 4.12.1 ### Patch Changes - 36f6b9e6: fix: reset form and field behaviors for unspecified values closes #4564 - c1c6f399: fix: unref initial values when initializing the form closes #4563 ## 4.12.0 ### Minor Changes - bbecc973: feat: deprecate reactive initial values closes #4402 ### Patch Changes - f9a95843: feat: add label support to defineField closes #4530 - f688896f: fix: avoid overriding paths and destroy path on remove closes #4476 closes #4557 - 2abb8966: fix: clone values before reset closes #4536 - e370413b: fix: handle hoisted paths overriding one another - 95b701f7: feat: allow getters for field arrays ## 4.11.8 ### Patch Changes - d1b5b855: fix: avoid triggering extra model value events closes #4461 - 78c4668e: feat: allow null as a valid Form prop type closes #4483 ## 4.11.7 ### Patch Changes - a1414f6a: fix: export ModelessBinds type closes #4478 ## 4.11.6 ### Patch Changes - f683e909: fix(types): infer the model value prop name correctly ## 4.11.5 ### Patch Changes - 27c9ef24: feat(types): stronger define component bind types closes #4421 - 804ec6fa: fix: use flags to avoid validating during reset #4404 #4467 ## 4.11.4 ### Patch Changes - 4d8ed7eb: feat: added reset opts to force values closes #4440 - b53400e2: fix: silent validation should not mark a field as validated - 8f680bf1: fix: clone the schema object before validating closes #4459 - 5231f439: fix: respect validate on model update configuration closes #4451, closes #4467 ## 4.11.3 ## 4.11.2 ### Patch Changes - 2ff045c1: fix: do not warn if a form or a field was resolved closes #4399 - 73219b40: feat: expose all internal types - 4947e88f: feat: expose BaseInputBinds and BaseComponentBinds interfaces #4409 - ecbb690d: feat: query fields meta state ## 4.11.1 ### Patch Changes - 5e23dcb9: fix: add support for parsing range inputs ## 4.11.0 ### Minor Changes - 2d8143f9: feat: added composition setter functions ## 4.10.9 ### Patch Changes - c02337f3: fix: correct the setErrors type to allow for string[] ## 4.10.8 ### Patch Changes - a9a473b4: feat(perf): improve performance setFieldError and setFieldValue closes #4382 ## 4.10.7 ### Patch Changes - 9290f5a9: fix: clone values inserted into field arrays closes #4372 - 93f8001a: fix: do not warn if the validation is for removed paths closes #4368 ## 4.10.6 ### Patch Changes - 40ce7a91: feat: expose normalizeRules closes #4348 - e9b215a7: fix: resetForm should cast typed schema values closes #4347 - 4e11ff95: fix: validate form values on setValues by default closes #4359 - e354a13a: fix: Normalize error paths to use brackets for indices closes #4211 - 68080d28: feat: use silent validation when field is initialized closes #4312 ## 4.10.5 ### Patch Changes - 6a1dc9bd: fix: component blur event and respect model update config closes #4346 ## 4.10.4 ### Patch Changes - 2f9ca91c: fix(types): remove deep readonly type for now ## 4.10.3 ### Patch Changes - 32537e14: fix: less strict object checks for undefined and missing keys closes #4341 - c3698f07: fix: respect model modifiers when emitting the value closes #4333 ## 4.10.2 ### Patch Changes - 1660048e: fix: define binds not respecting config events ## 4.10.1 ### Patch Changes - fc416918: fix: handle NaN when parsing number inputs closes #4328 - 435e7857: fix: reset present values after all path mutation - 273cca74: fix: reset field should not validate closes #4323 ## 4.10.0 ### Minor Changes - 7a548f42: chore: require vue 3.3 and refactor types - 7ce9d671: feat(breaking): disable v-model support by default closes #4283 - bfd6b00a: "feat: allow custom models for defineComponentBinds" - d4fafc95: "feat: allow handleBlur to run validations" - 05d957ec: feat: mark form values as readonly closes #4282 ### Patch Changes - 77345c42: fix: reset form should merge values closes #4320 - f1dc1359: fix: use event value if no checked value for checkbox/radio closes #4308 - 3e4a7c13: feat(dx): make `syncVModel` accept the model propName - 2cf0eec9: feat: allow multiple messages in a validator fn closes #4322 #4318 - ed208918: fix: trigger validation with setFieldValue by default closes #4314 - 6a3f9f15: fix: parse native number fields closes #4313 ## 4.9.6 ### Patch Changes - b138282a: fix(types): export SetFieldValueOptions interface closes #4290 - 6e074f77: fix: handleBlur should respect blur validate config closes #4285 ## 4.9.5 ### Patch Changes - 7356c102: fix: setFieldError should set meta.valid closes #4274 ## 4.9.4 ### Patch Changes - f4ea2c05: fix: exclude undefined and null from initial values closes #4139 ## 4.9.3 ### Patch Changes - 09d5596b: fix: run validation on value change closes #4251 - 9bfbfaaf: feat: added isValidating to useForm - 48b45d91: fix: hoist nested errors path to the deepest direct parent closes #4063 ## 4.9.2 ### Patch Changes - 31090e0d: avoid double unset path with field array remove - 9046308b: fixed validations running for unmounted fields - fe322a07: batch unsets and sort paths unset order for safer unsets closes #4115 ## 4.9.1 ### Patch Changes - 681bbab4: Added type-fest to core package dependencies ## 4.9.0 ### Minor Changes - 41b5d39b: Implemented path types into various form API functions - 95409080: Added component and input binds helpers ### Patch Changes - 7554d4a6: fix field array triggering validation when an item is removed - 298577b7: setValues does not delete unspecified fields values ## 4.8.6 ### Patch Changes - 6e0b0557: Introduced official nuxt module package ## 4.8.5 ### Patch Changes - 9048a238: fixed zod union issues not showing up as errors closes #4204 All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [4.8.4](https://github.com/logaretm/vee-validate/compare/v4.8.3...v4.8.4) (2023-03-24) ### Bug Fixes - make initial values partial closes [#4195](https://github.com/logaretm/vee-validate/issues/4195) ([eeccd0c](https://github.com/logaretm/vee-validate/commit/eeccd0c55814408670eced3717d0347590da3488)) - properly unref the schema before checking for default values closes [#4196](https://github.com/logaretm/vee-validate/issues/4196) ([8e3663d](https://github.com/logaretm/vee-validate/commit/8e3663d18357574ea4d394197f2c66889eeef6fa)) ### Features - allow name ref to be a lazy function ([8fb543a](https://github.com/logaretm/vee-validate/commit/8fb543a6e91c17d8541389e29c7014dc1f804c91)) ## [4.8.3](https://github.com/logaretm/vee-validate/compare/v4.8.2...v4.8.3) (2023-03-15) **Note:** Version bump only for package vee-validate ## [4.8.2](https://github.com/logaretm/vee-validate/compare/v4.8.1...v4.8.2) (2023-03-14) ### Bug Fixes - do not use name as a default label for useField closes [#4164](https://github.com/logaretm/vee-validate/issues/4164) ([d5acff7](https://github.com/logaretm/vee-validate/commit/d5acff719797c77ba4ff3be5f78c4a45374f9809)) ## [4.8.1](https://github.com/logaretm/vee-validate/compare/v4.8.0...v4.8.1) (2023-03-12) ### Bug Fixes - make sure to have a fallback for undefined casts closes [#4186](https://github.com/logaretm/vee-validate/issues/4186) ([9f1c63b](https://github.com/logaretm/vee-validate/commit/9f1c63b4dbc59f30c17bfe427020586db36cbdec)) ### Features - expose errorBag to slot props ([371744e](https://github.com/logaretm/vee-validate/commit/371744eea3d3cb0a244dcd9788f4f3f2a7714132)) # [4.8.0](https://github.com/logaretm/vee-validate/compare/v4.7.4...v4.8.0) (2023-03-12) ### Bug Fixes - finally handicap yup schema resolution ([303b1fb](https://github.com/logaretm/vee-validate/commit/303b1fb771ee78816ef0916e4f0e26318ad641b0)) - initial sync with v-model if enabled closes [#4163](https://github.com/logaretm/vee-validate/issues/4163) ([1040643](https://github.com/logaretm/vee-validate/commit/1040643f40ba622010ab935095dffb8d926cd76d)) - properly aggregrate nested errors for yup ([7f90bbc](https://github.com/logaretm/vee-validate/commit/7f90bbceeaeb7806a9626adb72981933a69db96f)) - remove console.log from devtools integration ([3c2d51c](https://github.com/logaretm/vee-validate/commit/3c2d51c56f80918ef6644b034594df1a3e81eb03)) - remove yup schema type and rely on assertions ([5cbb913](https://github.com/logaretm/vee-validate/commit/5cbb913071e315264d62fda7d1219bdc28d3faf0)) - render zod multiple errors in nested objects closes [#4078](https://github.com/logaretm/vee-validate/issues/4078) ([f74fb69](https://github.com/logaretm/vee-validate/commit/f74fb69977d17ef8fab4c22734ffd76ca1c02a48)) - run silent validation after array mutations closes [#4096](https://github.com/logaretm/vee-validate/issues/4096) ([044b4b4](https://github.com/logaretm/vee-validate/commit/044b4b44601908330c65541ce2bee6a110b1604f)) - type inference fix ([ac0383f](https://github.com/logaretm/vee-validate/commit/ac0383f1fb335bf92c9249f65bf319ca182545b7)) - watch and re-init array fields if form data changed closes [#4153](https://github.com/logaretm/vee-validate/issues/4153) ([6e784cc](https://github.com/logaretm/vee-validate/commit/6e784ccacbe89b5cd9daa9e3827808f7056aac04)) ### Features - Better Yup and Zod typing with output types and input inference ([#4064](https://github.com/logaretm/vee-validate/issues/4064)) ([3820a5b](https://github.com/logaretm/vee-validate/commit/3820a5b8eb3f8c6cd9239057746ccfb4b2e57e76)) - export type `FieldState` ([#4159](https://github.com/logaretm/vee-validate/issues/4159)) ([69c0d12](https://github.com/logaretm/vee-validate/commit/69c0d12434d50b52f4691c2f95d739049a3d1fcb)) ## [4.7.4](https://github.com/logaretm/vee-validate/compare/v4.7.3...v4.7.4) (2023-02-07) ### Bug Fixes - pass the field label as a seperate value closes [#4097](https://github.com/logaretm/vee-validate/issues/4097) ([89f8689](https://github.com/logaretm/vee-validate/commit/89f8689b673be27f0fc221d6c096efa11dacd3e6)) ### Features - **#4117:** add resetField on Form/useForm ([#4120](https://github.com/logaretm/vee-validate/issues/4120)) ([87c4278](https://github.com/logaretm/vee-validate/commit/87c42787c0b4de5a09abe0d29deb92b28b59023e)), closes [#4117](https://github.com/logaretm/vee-validate/issues/4117) - expose state getters on the form instance via template refs ([#4121](https://github.com/logaretm/vee-validate/issues/4121)) ([7f1c39c](https://github.com/logaretm/vee-validate/commit/7f1c39c0d9a0d1f7b7768b68c6705b5bfda91599)) ## [4.7.3](https://github.com/logaretm/vee-validate/compare/v4.7.2...v4.7.3) (2022-11-13) ### Bug Fixes - use cloned value when setting field value closes [#3991](https://github.com/logaretm/vee-validate/issues/3991) ([90b61fc](https://github.com/logaretm/vee-validate/commit/90b61fc8810a1fdc677507251735b4210f175f4b)) ## [4.7.2](https://github.com/logaretm/vee-validate/compare/v4.7.1...v4.7.2) (2022-11-02) ### Bug Fixes - don't mutate validated meta when silent validation closes [#3981](https://github.com/logaretm/vee-validate/issues/3981) closes [#3982](https://github.com/logaretm/vee-validate/issues/3982) ([6652a22](https://github.com/logaretm/vee-validate/commit/6652a22f99cde5b018c633365025d74e15dde835)) ## [4.7.1](https://github.com/logaretm/vee-validate/compare/v4.7.0...v4.7.1) (2022-10-23) ### Bug Fixes - clean up single group value after unmount closes [#3963](https://github.com/logaretm/vee-validate/issues/3963) ([#3972](https://github.com/logaretm/vee-validate/issues/3972)) ([8ccfd2b](https://github.com/logaretm/vee-validate/commit/8ccfd2b2b542963d3d35cfe5f82490c94ec1635f)) - correctly mutate deep field array item and trigger validation ([#3974](https://github.com/logaretm/vee-validate/issues/3974)) ([267736f](https://github.com/logaretm/vee-validate/commit/267736f43ca207a8fe35af30020fc61fdc009265)) - mark slot prop field value as any closes [#3969](https://github.com/logaretm/vee-validate/issues/3969) ([#3973](https://github.com/logaretm/vee-validate/issues/3973)) ([70ddc5b](https://github.com/logaretm/vee-validate/commit/70ddc5b60232f0dc761b7803a3220010d2f8ba69)) # [4.7.0](https://github.com/logaretm/vee-validate/compare/v4.6.10...v4.7.0) (2022-10-09) ### Features - allow passing form control to useField closes [#3204](https://github.com/logaretm/vee-validate/issues/3204) ([#3923](https://github.com/logaretm/vee-validate/issues/3923)) ([4c59d63](https://github.com/logaretm/vee-validate/commit/4c59d634f25d7fff024b50f3ffd667f7fdf0076c)) - expose controlled values on useForm ([#3924](https://github.com/logaretm/vee-validate/issues/3924)) ([2517319](https://github.com/logaretm/vee-validate/commit/25173196f3b689d919015cf8e7df8254b9e3090e)) ## [4.6.10](https://github.com/logaretm/vee-validate/compare/v4.6.9...v4.6.10) (2022-09-30) ### Bug Fixes - use ssr safe file check ([56663aa](https://github.com/logaretm/vee-validate/commit/56663aa2e50d7aa285ca1cb22887c8e8b3f7fd3c)) ## [4.6.9](https://github.com/logaretm/vee-validate/compare/v4.6.8...v4.6.9) (2022-09-19) ### Bug Fixes - perform field reset before all values reset closes [#3934](https://github.com/logaretm/vee-validate/issues/3934) ([1c016d9](https://github.com/logaretm/vee-validate/commit/1c016d93b367229644dca643931ef63bc6e433dc)) ## [4.6.8](https://github.com/logaretm/vee-validate/compare/v4.6.7...v4.6.8) (2022-09-19) ### Bug Fixes - ensure validation if we skip checkbox value setting [#3927](https://github.com/logaretm/vee-validate/issues/3927) ([#3930](https://github.com/logaretm/vee-validate/issues/3930)) ([82d05db](https://github.com/logaretm/vee-validate/commit/82d05dbd2a5c7d5ea2fe7b73222dd339e92ee373)) - extend is equal with file comparison logic [#3911](https://github.com/logaretm/vee-validate/issues/3911) ([#3932](https://github.com/logaretm/vee-validate/issues/3932)) ([c7c806c](https://github.com/logaretm/vee-validate/commit/c7c806c0c5393f3188c16384f5fc1b46ebc78cbd)) - handle nested value change validation [#3926](https://github.com/logaretm/vee-validate/issues/3926) ([#3929](https://github.com/logaretm/vee-validate/issues/3929)) ([771e7f2](https://github.com/logaretm/vee-validate/commit/771e7f21cf332052b74c5506a8c2f38f666cae55)) ### Features - expose RuleExpression type closes [#3913](https://github.com/logaretm/vee-validate/issues/3913) ([cdaf22d](https://github.com/logaretm/vee-validate/commit/cdaf22df04b42a68f55133ad3854aae9a7ad6953)) ## [4.6.7](https://github.com/logaretm/vee-validate/compare/v4.6.6...v4.6.7) (2022-08-27) ### Bug Fixes - allow generics for generic function type ([91e97aa](https://github.com/logaretm/vee-validate/commit/91e97aa41bca278970780973fcbf90e17fb29920)) - handle validation races for async validations ([#3908](https://github.com/logaretm/vee-validate/issues/3908)) ([8c82079](https://github.com/logaretm/vee-validate/commit/8c82079dac8535678e45428ad8e5afe7dcd3da63)) ## [4.6.6](https://github.com/logaretm/vee-validate/compare/v4.6.5...v4.6.6) (2022-08-16) ### Bug Fixes - return value if no model modifiers are defined closes [#3895](https://github.com/logaretm/vee-validate/issues/3895) ([#3896](https://github.com/logaretm/vee-validate/issues/3896)) ([6ab40df](https://github.com/logaretm/vee-validate/commit/6ab40df4452c5bee8a487a37164e2273c2aaf0ba)) ## [4.6.5](https://github.com/logaretm/vee-validate/compare/v4.6.4...v4.6.5) (2022-08-11) ### Bug Fixes - reset the original value when resetField is called [#3891](https://github.com/logaretm/vee-validate/issues/3891) ([#3892](https://github.com/logaretm/vee-validate/issues/3892)) ([7113dcc](https://github.com/logaretm/vee-validate/commit/7113dccdeb962d8efa064ff0ebd171b2aa2f4c4d)) ## [4.6.4](https://github.com/logaretm/vee-validate/compare/v4.6.3...v4.6.4) (2022-08-07) ### Bug Fixes - make sure to deep watch created models by useFieldModel ([fbe273c](https://github.com/logaretm/vee-validate/commit/fbe273c6f2c5d30a1996777561eda2268d8a02e0)) ## [4.6.3](https://github.com/logaretm/vee-validate/compare/v4.6.2...v4.6.3) (2022-08-07) ### Features - Expose InvalidSubmissionHandler and GenericValidateFunction types ([#3853](https://github.com/logaretm/vee-validate/issues/3853)) ([3ccf27d](https://github.com/logaretm/vee-validate/commit/3ccf27d5b9c1fe9cf655b89533eb1802cb5717d4)) ## [4.6.2](https://github.com/logaretm/vee-validate/compare/v4.6.1...v4.6.2) (2022-07-17) ### Bug Fixes - avoid toggling field array checkboxes values closes [#3844](https://github.com/logaretm/vee-validate/issues/3844) ([fffad4b](https://github.com/logaretm/vee-validate/commit/fffad4bea68cc949d0bce440b5daf43901aaca7f)) ### Features - expose field and form options closes [#3843](https://github.com/logaretm/vee-validate/issues/3843) ([7437612](https://github.com/logaretm/vee-validate/commit/7437612ab554f8f65b445f7b065725b570a9a14a)) ## [4.6.1](https://github.com/logaretm/vee-validate/compare/v4.6.0...v4.6.1) (2022-07-12) ### Bug Fixes - pass onInvalidSubmit prop to submitForm closes [#3841](https://github.com/logaretm/vee-validate/issues/3841) ([b6cf543](https://github.com/logaretm/vee-validate/commit/b6cf543b600246942fc7f6802a0cc6ea1038603a)) # [4.6.0](https://github.com/logaretm/vee-validate/compare/v4.5.11...v4.6.0) (2022-07-11) ### Bug Fixes - added existing undefined path fallback closes [#3801](https://github.com/logaretm/vee-validate/issues/3801) ([fd0500c](https://github.com/logaretm/vee-validate/commit/fd0500c9cb4448b232eddb4cd5d8d081e5d48d08)) - avoid inserting value binding for file type inputs closes [#3760](https://github.com/logaretm/vee-validate/issues/3760) ([3c76bb2](https://github.com/logaretm/vee-validate/commit/3c76bb2ebcbafaf46047b8e41bcc053e41cf27bf)) - avoid validating when field instance exists ([3759df2](https://github.com/logaretm/vee-validate/commit/3759df20f5ba48a43d5dea4bb6d94e875f15c331)) - compare form meta.dirty based on original values than staged initials closes [#3782](https://github.com/logaretm/vee-validate/issues/3782) ([f3ffd3c](https://github.com/logaretm/vee-validate/commit/f3ffd3c00ac1f2b73b6a3039cb997d08cf8e452b)) - expose ValidationOptions type closes [#3825](https://github.com/logaretm/vee-validate/issues/3825) ([9854865](https://github.com/logaretm/vee-validate/commit/9854865ae60431256e6fb9c921d1eabc9093b5e4)) - exposed component APIs to their TS defs with refs closes [#3292](https://github.com/logaretm/vee-validate/issues/3292) ([ae59d0f](https://github.com/logaretm/vee-validate/commit/ae59d0f6f3728a2a95732517d11fdf970127fe9c)) - fast equal before deciding value was changed closes [#3808](https://github.com/logaretm/vee-validate/issues/3808) ([3d582ec](https://github.com/logaretm/vee-validate/commit/3d582ec6c884467199cc7fb86ffe0e571d85c4fb)) - use multiple batch queues for both validation modes closes [#3783](https://github.com/logaretm/vee-validate/issues/3783) ([6156603](https://github.com/logaretm/vee-validate/commit/6156603f537fb46030017fb3a4d003b6bec0d4e8)) ### Features - **4.6:** Allow mutating field array iterable's value property ([#3618](https://github.com/logaretm/vee-validate/issues/3618)) ([#3759](https://github.com/logaretm/vee-validate/issues/3759)) ([c3c40e5](https://github.com/logaretm/vee-validate/commit/c3c40e50b68cbf8aee3356416561fdf5d23ac6d2)) - add move to FieldArray ([a52f133](https://github.com/logaretm/vee-validate/commit/a52f13356c44616d699e02f9a243dd08c7bcc38e)) - added unsetValueOnUnmount config ([#3815](https://github.com/logaretm/vee-validate/issues/3815)) ([e6e1c1d](https://github.com/logaretm/vee-validate/commit/e6e1c1d66bfd4c453ac21c00b3faa2d6470040a8)) - added useFieldModel to useForm API ([26c828e](https://github.com/logaretm/vee-validate/commit/26c828e21495c485d489ea1319575d9b5c271801)) - allow keep values config to be reactive ([5009bd8](https://github.com/logaretm/vee-validate/commit/5009bd88c09f7a8c753fc52dd5bf8d4d5234567b)) - better normalization for native input file events ([2751552](https://github.com/logaretm/vee-validate/commit/2751552a42b4eaa57d22ea24c38cd31cfd5b9955)) - Remove yup type dependency ([#3704](https://github.com/logaretm/vee-validate/issues/3704)) ([e772f9a](https://github.com/logaretm/vee-validate/commit/e772f9a7b9f0e45680a65dfae249ee2092ca850e)) - Sync useField with component v-model ([#3806](https://github.com/logaretm/vee-validate/issues/3806)) ([0ef7582](https://github.com/logaretm/vee-validate/commit/0ef75823d1b90e1213f8a31014c2cf347d386ec1)) ## [4.5.11](https://github.com/logaretm/vee-validate/compare/v4.5.10...v4.5.11) (2022-04-10) ### Bug Fixes - ignore validation of removed array elements closes [#3748](https://github.com/logaretm/vee-validate/issues/3748) ([3d49faa](https://github.com/logaretm/vee-validate/commit/3d49faa4101902c2e77aee0a2d43cd29b69f7b4e)) ### Features - chain of GenericValidateFunction in useField ([#3725](https://github.com/logaretm/vee-validate/issues/3725)) ([#3726](https://github.com/logaretm/vee-validate/issues/3726)) ([8db4077](https://github.com/logaretm/vee-validate/commit/8db407785c5611c10c221eabd747c3f31770145b)) ## [4.5.10](https://github.com/logaretm/vee-validate/compare/v4.5.9...v4.5.10) (2022-03-08) **Note:** Version bump only for package vee-validate ## [4.5.9](https://github.com/logaretm/vee-validate/compare/v4.5.8...v4.5.9) (2022-02-22) ### Bug Fixes - mark fields validated via form validate as validated ([ad9fa9d](https://github.com/logaretm/vee-validate/commit/ad9fa9d853a8cabb26cdde04c20c07d4f2673aa4)) ## [4.5.8](https://github.com/logaretm/vee-validate/compare/v4.5.7...v4.5.8) (2022-01-23) ### Bug Fixes - clear old error path error when changing field name closes [#3664](https://github.com/logaretm/vee-validate/issues/3664) ([f736e62](https://github.com/logaretm/vee-validate/commit/f736e62b1bb82f940d14d74a6d505c913c1c3dde)) - field array swap not working when falsy values are present at paths ([40afbd9](https://github.com/logaretm/vee-validate/commit/40afbd9cc3fb3de71de3f6ebb0a1b2774d9018ff)) ## [4.5.7](https://github.com/logaretm/vee-validate/compare/v4.5.6...v4.5.7) (2021-12-07) ### Bug Fixes - always attach model update event closes [#3583](https://github.com/logaretm/vee-validate/issues/3583) ([6a53e80](https://github.com/logaretm/vee-validate/commit/6a53e80525a9c38ce8851407b832bc8409c3f334)) ## [4.5.6](https://github.com/logaretm/vee-validate/compare/v4.5.5...v4.5.6) (2021-11-17) ### Bug Fixes - corrected the typing for the resetField function closes [#3568](https://github.com/logaretm/vee-validate/issues/3568) ([4e9460e](https://github.com/logaretm/vee-validate/commit/4e9460e3a4f51f4a78ddcdf17f7c3073f899404f)) - new devtools typings ([f288ca5](https://github.com/logaretm/vee-validate/commit/f288ca5a59d36f23ba7f6bdd210493588f744940)) - use watchEffect to compute form meta closes [#3580](https://github.com/logaretm/vee-validate/issues/3580) ([e8729dc](https://github.com/logaretm/vee-validate/commit/e8729dc72d2a027a666515360c9537a62a8d46ad)) ## [4.5.5](https://github.com/logaretm/vee-validate/compare/v4.5.4...v4.5.5) (2021-11-01) ### Bug Fixes - prevent toggle checkboxes when form resets closes [#3551](https://github.com/logaretm/vee-validate/issues/3551) ([cad12ba](https://github.com/logaretm/vee-validate/commit/cad12ba7502af7268029930a9176d8e160efeef6)) ## [4.5.4](https://github.com/logaretm/vee-validate/compare/v4.5.3...v4.5.4) (2021-10-20) **Note:** Version bump only for package vee-validate ## [4.5.3](https://github.com/logaretm/vee-validate/compare/v4.5.2...v4.5.3) (2021-10-17) ### Features - added slot typings for components closes [#3534](https://github.com/logaretm/vee-validate/issues/3534) ([#3537](https://github.com/logaretm/vee-validate/issues/3537)) ([52a2a38](https://github.com/logaretm/vee-validate/commit/52a2a385ec6e65c7eaaed0a67615c45aba07de64)) ## [4.5.2](https://github.com/logaretm/vee-validate/compare/v4.5.1...v4.5.2) (2021-09-30) ### Bug Fixes - use klona/full mode to handle luxon values closes [#3508](https://github.com/logaretm/vee-validate/issues/3508) ([048c9c0](https://github.com/logaretm/vee-validate/commit/048c9c03d38ffd871ee4b3504daf1c83d42e9516)) ## [4.5.1](https://github.com/logaretm/vee-validate/compare/v4.5.0...v4.5.1) (2021-09-29) **Note:** Version bump only for package vee-validate # [4.5.0](https://github.com/logaretm/vee-validate/compare/v4.4.11...v4.5.0) (2021-09-26) **Note:** Version bump only for package vee-validate ## [4.4.11](https://github.com/logaretm/vee-validate/compare/v4.4.10...v4.4.11) (2021-09-11) ### Bug Fixes - dynamic rule forcing validation closes [#3485](https://github.com/logaretm/vee-validate/issues/3485) ([d3f0fc0](https://github.com/logaretm/vee-validate/commit/d3f0fc094c89375bd67bdd3f533e5ab545a83611)) ## [4.4.10](https://github.com/logaretm/vee-validate/compare/v4.4.9...v4.4.10) (2021-08-31) ### Bug Fixes - added silent validation run after reset closes [#3463](https://github.com/logaretm/vee-validate/issues/3463) ([a61f7ab](https://github.com/logaretm/vee-validate/commit/a61f7ab532d6d2fd9f237145f91bbcc9043431f6)) - handle absent model value closes [#3468](https://github.com/logaretm/vee-validate/issues/3468) ([2c4a7ff](https://github.com/logaretm/vee-validate/commit/2c4a7ffb84811ae86a1698e6e15f41dc32f8fb8d)) - **types:** remove arguments of PrivateFieldContext.handleReset ([2e45d1f](https://github.com/logaretm/vee-validate/commit/2e45d1f8a8444c0aabfd307364cadfab74802d02)) - ensure option bound value type is preserved closes [#3440](https://github.com/logaretm/vee-validate/issues/3440) ([b144615](https://github.com/logaretm/vee-validate/commit/b1446152d6f6cd4843ab206d667a7d744c2a14fc)) ## [4.4.9](https://github.com/logaretm/vee-validate/compare/v4.4.8...v4.4.9) (2021-08-05) ### Bug Fixes - ensure to clone user passed values in setters closes [#3428](https://github.com/logaretm/vee-validate/issues/3428) ([a720c24](https://github.com/logaretm/vee-validate/commit/a720c2444b64d28743ba0500aa970419029352cb)) - prioritize the current value if another field of same name is mounted closes [#3429](https://github.com/logaretm/vee-validate/issues/3429) ([cf036ec](https://github.com/logaretm/vee-validate/commit/cf036ecf9a5dad401c752c132ef5333d0f442441)) ## [4.4.8](https://github.com/logaretm/vee-validate/compare/v4.4.7...v4.4.8) (2021-07-31) **Note:** Version bump only for package vee-validate ## [4.4.7](https://github.com/logaretm/vee-validate/compare/v4.4.6...v4.4.7) (2021-07-20) ### Bug Fixes - avoid watching values at the end of reset calls closes [#3407](https://github.com/logaretm/vee-validate/issues/3407) ([86f594f](https://github.com/logaretm/vee-validate/commit/86f594f4a7cee5ed5f581419bdbd985fc53f8358)) ### Features - add standalone prop for fields ([#3379](https://github.com/logaretm/vee-validate/issues/3379)) ([3689437](https://github.com/logaretm/vee-validate/commit/36894378aa3636eeb4fb54aa747319e21c6eb5cd)) - expose FieldContext type closes [#3398](https://github.com/logaretm/vee-validate/issues/3398) ([a6e4c0a](https://github.com/logaretm/vee-validate/commit/a6e4c0ac580d4145c72118ac535bfa082c771068)) - expose form and field injection keys ([6034e66](https://github.com/logaretm/vee-validate/commit/6034e66836e0566e17f36744da19088aca33fbad)) ## [4.4.6](https://github.com/logaretm/vee-validate/compare/v4.4.5...v4.4.6) (2021-07-08) ### Bug Fixes - clean error message for singular fields after unmount ([#3385](https://github.com/logaretm/vee-validate/issues/3385)) ([4e81cce](https://github.com/logaretm/vee-validate/commit/4e81cce292380974728b952a2fa1724c1ea4f086)) - quit unsetting path if its already unset ([cfe45ba](https://github.com/logaretm/vee-validate/commit/cfe45ba38690ec27b5ee4e48a80336834a932a78)) - expose setters in composition API ([d79747d](https://github.com/logaretm/vee-validate/commit/d79747de4a25d1ced151d9bd5b767e815d7e32bf)) ## [4.4.5](https://github.com/logaretm/vee-validate/compare/v4.4.4...v4.4.5) (2021-06-13) ## [4.4.4](https://github.com/logaretm/vee-validate/compare/v4.4.3...v4.4.4) (2021-06-05) ### Bug Fixes - field with pre-register schema errors should be validated on register closes [#3342](https://github.com/logaretm/vee-validate/issues/3342) ([61c7359](https://github.com/logaretm/vee-validate/commit/61c73597b2e69c094e75c02476d825c5236710b5)) - make sure to create the container path if it exists while null or undefined ([79d3779](https://github.com/logaretm/vee-validate/commit/79d37798ccf2fef56714bdad4db553086df0ad48)) - make sure to create the container path if it exists while null or undefined ([79d3779](https://github.com/logaretm/vee-validate/commit/79d37798ccf2fef56714bdad4db553086df0ad48)) ### Features - expose setters in composition API ([61f942f](https://github.com/logaretm/vee-validate/commit/61f942f511e6fcceb10a74272ac845017ce88997)) ## [4.4.3](https://github.com/logaretm/vee-validate/compare/v4.4.2...v4.4.3) (2021-06-02) ### Bug Fixes - respect the Field bails option closes [#3332](https://github.com/logaretm/vee-validate/issues/3332) ([6679387](https://github.com/logaretm/vee-validate/commit/66793878e317f32f4759b3d01e27e3b9072eff67)) ## [4.4.2](https://github.com/logaretm/vee-validate/compare/v4.4.1...v4.4.2) (2021-05-28) ### Bug Fixes - clean up the old values path when fields exchange names fixes [#3325](https://github.com/logaretm/vee-validate/issues/3325) ([fe51c12](https://github.com/logaretm/vee-validate/commit/fe51c126ae6258ac0888ee47d9d01a27b889a5c1)) ## [4.4.1](https://github.com/logaretm/vee-validate/compare/v4.4.0...v4.4.1) (2021-05-24) ### Bug Fixes - forgot adding errors in useValidationForm ([d032d3b](https://github.com/logaretm/vee-validate/commit/d032d3b55438169fa87c18d89e073fffe3988d56)) - re-introduce the errors prop back on the form validation result closes [#3317](https://github.com/logaretm/vee-validate/issues/3317) ([b439a73](https://github.com/logaretm/vee-validate/commit/b439a73bf3c37298c251b74223984d54b8949a95)) # [4.4.0](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.2...v4.4.0) (2021-05-23) ### Bug Fixes - seperate model detection from event emitting closes [#3312](https://github.com/logaretm/vee-validate/issues/3312) ([5e72852](https://github.com/logaretm/vee-validate/commit/5e72852e80b971121d10422cf84085b07bb2d8fb)) # [4.4.0-alpha.2](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.1...v4.4.0-alpha.2) (2021-05-14) ### Bug Fixes - avoid clearing all errors before validating schema ([51c2e78](https://github.com/logaretm/vee-validate/commit/51c2e7890b87d971850dfc609c09d19b79a96fb6)) # [4.4.0-alpha.1](https://github.com/logaretm/vee-validate/compare/v4.4.0-alpha.0...v4.4.0-alpha.1) (2021-05-14) ### Bug Fixes - minifier issue when handling await ([f206cac](https://github.com/logaretm/vee-validate/commit/f206cacd7e0d03a36fce5b236c23906997e0287b)) # [4.4.0-alpha.0](https://github.com/logaretm/vee-validate/compare/v4.3.6...v4.4.0-alpha.0) (2021-05-14) ### Bug Fixes - deprecate handleInput and use handleChange for both events ([#3303](https://github.com/logaretm/vee-validate/issues/3303)) ([4cb10de](https://github.com/logaretm/vee-validate/commit/4cb10de0a5f589f72c82cdd4a8859b7f044ae84c)) ### Features - custom values and errors ([#3305](https://github.com/logaretm/vee-validate/issues/3305)) ([427802b](https://github.com/logaretm/vee-validate/commit/427802b94ea309d12df26ba51ac1b3a24e4e8d46)) ## [4.3.6](https://github.com/logaretm/vee-validate/compare/v4.3.5...v4.3.6) (2021-05-08) ### Bug Fixes - added a symbol to detect non passed props with Vue 3.1.x ([#3295](https://github.com/logaretm/vee-validate/issues/3295)) ([0663539](https://github.com/logaretm/vee-validate/commit/06635397424526c3a3c4a53f63322bbfd55000ee)) ## [4.3.5](https://github.com/logaretm/vee-validate/compare/v4.3.4...v4.3.5) (2021-05-01) ### Bug Fixes - priotrize self injections over parent injections closes [#3270](https://github.com/logaretm/vee-validate/issues/3270) ([07c1234](https://github.com/logaretm/vee-validate/commit/07c12341d7f2e25e41a56ea0d5e38e9a374ae84b)) ## [4.3.4](https://github.com/logaretm/vee-validate/compare/v4.3.3...v4.3.4) (2021-04-27) ### Bug Fixes - update the valid flag regardless of mode closes [#3284](https://github.com/logaretm/vee-validate/issues/3284) ([6594ad1](https://github.com/logaretm/vee-validate/commit/6594ad15e4423c6a7861da188560b06f98365d9d)) ## [4.3.3](https://github.com/logaretm/vee-validate/compare/v4.3.2...v4.3.3) (2021-04-22) ### Features - touch all fields on submit ([#3278](https://github.com/logaretm/vee-validate/issues/3278)) ([fc4e400](https://github.com/logaretm/vee-validate/commit/fc4e400f7d9349c1e82bba8412d13e0cf69be0e1)) ## [4.3.2](https://github.com/logaretm/vee-validate/compare/v4.3.1...v4.3.2) (2021-04-21) ### Bug Fixes - unwrap initial value with useField.resetField fixes [#3272](https://github.com/logaretm/vee-validate/issues/3272) ([#3274](https://github.com/logaretm/vee-validate/issues/3274)) ([f6e9574](https://github.com/logaretm/vee-validate/commit/f6e95741f31fc085f718e07d3b1f1adfe0229df6)) ## [4.3.1](https://github.com/logaretm/vee-validate/compare/v4.3.0...v4.3.1) (2021-04-18) ### Bug Fixes - give error message component a name ([b7dcebf](https://github.com/logaretm/vee-validate/commit/b7dcebfcd202538cf082314817f97c3b8e07fefb)) - minor perf enhancement by lazy evaulation of slot props ([a306b1b](https://github.com/logaretm/vee-validate/commit/a306b1b0047ec82eaf727a6e380856de077c4fbe)) # [4.3.0](https://github.com/logaretm/vee-validate/compare/v4.2.4...v4.3.0) (2021-04-07) ### Features - added support for reactive schemas ([#3238](https://github.com/logaretm/vee-validate/issues/3238)) ([295d656](https://github.com/logaretm/vee-validate/commit/295d6567035bc3c452ad0f13fce13ff362b08005)) - added support for setting multiple field errors closes [#3117](https://github.com/logaretm/vee-validate/issues/3117) ([db0a6a0](https://github.com/logaretm/vee-validate/commit/db0a6a02cdc0fdab02a18e4756005c46dc06b1f8)) - support v-model.number ([#3252](https://github.com/logaretm/vee-validate/issues/3252)) ([8f491da](https://github.com/logaretm/vee-validate/commit/8f491da0b0998d0f7383a6a444d6aa498e3d96f4)) ## [4.2.4](https://github.com/logaretm/vee-validate/compare/v4.2.3...v4.2.4) (2021-03-26) ### Bug Fixes - validation triggered on value change ([10549b7](https://github.com/logaretm/vee-validate/commit/10549b77dc350cee4f198cb14e3fd12f61e12b80)) ## [4.2.3](https://github.com/logaretm/vee-validate/compare/v4.2.2...v4.2.3) (2021-03-22) ### Bug Fixes - prevent yup schema from setting non-interacted fields errors closes [#3228](https://github.com/logaretm/vee-validate/issues/3228) ([534f8b2](https://github.com/logaretm/vee-validate/commit/534f8b28850c9f28245a748f956d1358bb7cb2e1)) ## [4.2.2](https://github.com/logaretm/vee-validate/compare/v4.2.1...v4.2.2) (2021-03-03) ### Bug Fixes - ensure having a truthy fallback for fields missing in schema ([7cd6941](https://github.com/logaretm/vee-validate/commit/7cd694114403f7c252b6ba6b83c159110cdc58cf)) - handle pending validation runs during field unmounting ([ef5a7cc](https://github.com/logaretm/vee-validate/commit/ef5a7ccb269db8bbdee446e76dd60ebe8704b57e)) ## [4.2.1](https://github.com/logaretm/vee-validate/compare/v4.2.0...v4.2.1) (2021-02-26) ### Bug Fixes - added initial check against the field errors ([4288fb6](https://github.com/logaretm/vee-validate/commit/4288fb6291a3ed17d46569fd2b0baa690beb9cb1)) # [4.2.0](https://github.com/logaretm/vee-validate/compare/v4.1.20...v4.2.0) (2021-02-24) **Note:** Version bump only for package vee-validate ## [4.1.20](https://github.com/logaretm/vee-validate/compare/v4.1.19...v4.1.20) (2021-02-24) ### Bug Fixes - avoid setting checkbox values before registeration closes [#3183](https://github.com/logaretm/vee-validate/issues/3183) ([ab5f821](https://github.com/logaretm/vee-validate/commit/ab5f82103f8cfe5f5934a51057ce989ad30d0d44)) - change errors source to form closes [#3177](https://github.com/logaretm/vee-validate/issues/3177) ([7c13c92](https://github.com/logaretm/vee-validate/commit/7c13c92f477bc3d63067509fd9fec72964263f5d)) - use the issues array for zod error aggregation closes [#3184](https://github.com/logaretm/vee-validate/issues/3184) ([01b89e4](https://github.com/logaretm/vee-validate/commit/01b89e4940e997ef65dc950be3a13e0ffc18e881)) ## [4.1.19](https://github.com/logaretm/vee-validate/compare/v4.1.18...v4.1.19) (2021-02-16) ### Bug Fixes - use relative imports for shared type ([6790545](https://github.com/logaretm/vee-validate/commit/6790545dc9c35550d231fb14a310f3655dbc7256)) ### Features - improve typing for field yup schema ([c59f1f0](https://github.com/logaretm/vee-validate/commit/c59f1f01526b160a1081f276d732523ad9ab5ba2)) ## [4.1.18](https://github.com/logaretm/vee-validate/compare/v4.1.17...v4.1.18) (2021-02-10) ### Bug Fixes - avoid unsetting field value if switched with another closes [#3166](https://github.com/logaretm/vee-validate/issues/3166) ([f5a79fe](https://github.com/logaretm/vee-validate/commit/f5a79fe3b15f7437acf183c162e69178fd4fa7ec)) ## [4.1.17](https://github.com/logaretm/vee-validate/compare/v3.2.0...v4.1.17) (2021-02-08) ### Bug Fixes - add a handler for regex object params closes [#3073](https://github.com/logaretm/vee-validate/issues/3073) ([7a5e2eb](https://github.com/logaretm/vee-validate/commit/7a5e2ebf8303395372ae08ebcca55427a58faecb)) - added emits and onSubmit custom prop ([#3115](https://github.com/logaretm/vee-validate/issues/3115)) ([8f2c110](https://github.com/logaretm/vee-validate/commit/8f2c110f14add0fbd82a28a91601e89938144624)) - array radio fields not switching value correctly closes [#3141](https://github.com/logaretm/vee-validate/issues/3141) ([3d4efef](https://github.com/logaretm/vee-validate/commit/3d4efef68c63a3b57e2bf14fed913dbf841a7f5e)) - avoid returning undefined for form errors when form does not exist ([8cce17a](https://github.com/logaretm/vee-validate/commit/8cce17ae2846be912d51926c79e557ed8bb39582)) - avoid validating dependencies via watcheffect closes [#3156](https://github.com/logaretm/vee-validate/issues/3156) ([a7b91f6](https://github.com/logaretm/vee-validate/commit/a7b91f6e6c38f0b5262e2d4c1814154efa3b78c8)) - cast radio buttons value correctly closes [#3064](https://github.com/logaretm/vee-validate/issues/3064) ([3e0f9a4](https://github.com/logaretm/vee-validate/commit/3e0f9a47369edac32d0c8a068f8b61d8f761458f)) - clear out initial values for unregistered fields closes [#3060](https://github.com/logaretm/vee-validate/issues/3060) ([56206de](https://github.com/logaretm/vee-validate/commit/56206de995fe8f2eaca3e303ab6980784a3c95b1)) - correctly set the initial value from the v-model closes [#3107](https://github.com/logaretm/vee-validate/issues/3107) ([4bed9a8](https://github.com/logaretm/vee-validate/commit/4bed9a806323139d2f274e51b6bfe3de2190e54d)) - export submission types [#3112](https://github.com/logaretm/vee-validate/issues/3112) ([3f35167](https://github.com/logaretm/vee-validate/commit/3f351670da02364b0fb8e61198145dfa02dc59b9)) - fill the target rule params for message generators closes [#3077](https://github.com/logaretm/vee-validate/issues/3077) ([f5e1bd3](https://github.com/logaretm/vee-validate/commit/f5e1bd3cbc278a8588fa0c96af66823d82eefb8c)) - handle formless checkboxes value toggling closes [#3105](https://github.com/logaretm/vee-validate/issues/3105) ([504f30b](https://github.com/logaretm/vee-validate/commit/504f30bfcbcb1db710397ef05545b5008b0103fb)) - handle reactive field names and value swaps ([cf8051d](https://github.com/logaretm/vee-validate/commit/cf8051d3b92eb43103f4e7c682e615343239d717)) - missing export for useErrors helpers ([28537cc](https://github.com/logaretm/vee-validate/commit/28537cc547cf945b10adc485620ad226f71d60fc)) - pass down listeners to the input node closes [#3048](https://github.com/logaretm/vee-validate/issues/3048) ([2526a63](https://github.com/logaretm/vee-validate/commit/2526a63c2361e412b528cf370c03b39cb84b606d)) - prevent default reset behavior with handleReset ([a66df13](https://github.com/logaretm/vee-validate/commit/a66df13c3f39d84984581dc3c0ce368b052b6e8e)) - prevent resetForm from toggling checkbox value [#3084](https://github.com/logaretm/vee-validate/issues/3084) ([38778f9](https://github.com/logaretm/vee-validate/commit/38778f96471b6aa16fb020cfb1bde56b77a19cfb)) - react to validation events changes ([078e61b](https://github.com/logaretm/vee-validate/commit/078e61b17bd299a28752b733b494a0ddb368a812)) - reset meta correctly with resetField ([012658c](https://github.com/logaretm/vee-validate/commit/012658c082a00b1beeb53ce8cf3fcd91bc5b21ec)) - resolve component before rendering closes [#3014](https://github.com/logaretm/vee-validate/issues/3014) ([f8f481d](https://github.com/logaretm/vee-validate/commit/f8f481daad754a4b18a91e2b07b9549433d023f9)) - resolve path values with global rules closes [#3157](https://github.com/logaretm/vee-validate/issues/3157) ([beaf316](https://github.com/logaretm/vee-validate/commit/beaf3168490aee585542a19c9a910d9493e78208)) - set field initial value on the fid lookup closes [#3128](https://github.com/logaretm/vee-validate/issues/3128) ([650d5cf](https://github.com/logaretm/vee-validate/commit/650d5cf9f75f9b9247fc813acf2aff4089f05415)) - support dynamic labels closes [#3053](https://github.com/logaretm/vee-validate/issues/3053) ([31b2238](https://github.com/logaretm/vee-validate/commit/31b223878bda75c3150217ea80bb878d8dc1e320)) - typing issue from [#3134](https://github.com/logaretm/vee-validate/issues/3134) ([29e5cff](https://github.com/logaretm/vee-validate/commit/29e5cffc654a2502f29fe616eda088de958e02d3)) - use the custom injection fn for initial field values ([38cd32b](https://github.com/logaretm/vee-validate/commit/38cd32bd3ae9f263510d0ab4a1713c6a9a2011af)) ### Features - add submit count state ([#3070](https://github.com/logaretm/vee-validate/issues/3070)) ([a7fe71e](https://github.com/logaretm/vee-validate/commit/a7fe71e01072dacfeb7baa80eebf0b8d7d9d3ffd)) - added context awareness to composition helpers for fields ([b59fe88](https://github.com/logaretm/vee-validate/commit/b59fe88197ce3cd587edfc33666bcb676030fa61)) - added context information to validation functions ([7e6675d](https://github.com/logaretm/vee-validate/commit/7e6675db6a103eae33cbb6d959621b4549af66b2)) - added test cases and fallbacks for unresolved cases ([71bda03](https://github.com/logaretm/vee-validate/commit/71bda03a72a9e8f27bc0b7620ce78ba48a194309)) - added the useResetForm helper ([4c57715](https://github.com/logaretm/vee-validate/commit/4c57715ab621526a5c987cff9a53cb5b7af2155a)) - added unchecked-value prop to the field component ([af910c3](https://github.com/logaretm/vee-validate/commit/af910c3f3c6343538658ab90f356dd8957bb6a1a)) - added useErrors and useField error helpers ([4cda2fe](https://github.com/logaretm/vee-validate/commit/4cda2fea6428a7f10b53b633daa46252bf779289)) - added useIsDirty helpers ([6b7e4ab](https://github.com/logaretm/vee-validate/commit/6b7e4abfcdb2f0eebe0dd8c62785178fbee8d25f)) - added useIsSubmitting helper ([7a58fd8](https://github.com/logaretm/vee-validate/commit/7a58fd840425a5e09f625054389aebbb096c2e1a)) - added useIsTouched helpers ([fdb2d5a](https://github.com/logaretm/vee-validate/commit/fdb2d5a3c7c82d55aefef2deb95823e1ba6ba93d)) - added useIsValid helpers ([26fbb29](https://github.com/logaretm/vee-validate/commit/26fbb29467bab66c159e98793e4269768845b938)) - added useSubmitCount helper ([c4a6dea](https://github.com/logaretm/vee-validate/commit/c4a6deae68b588494ff0e2477d7ec2b9302c6f09)) - added useSubmitForm hook ([#3101](https://github.com/logaretm/vee-validate/issues/3101)) ([d042882](https://github.com/logaretm/vee-validate/commit/d04288295a090328f7022641799dbaee1c404b91)) - added useValidateField and useValidateForm helpers ([62355a8](https://github.com/logaretm/vee-validate/commit/62355a8db6477562f0689208669d0a1be63de03c)) - added validate field function to form and useForm ([#3133](https://github.com/logaretm/vee-validate/issues/3133)) ([926bed1](https://github.com/logaretm/vee-validate/commit/926bed1bded6990f17a51ca68e9aa47c339a80f2)) - added validate method on the form ref instance closes [#3030](https://github.com/logaretm/vee-validate/issues/3030) ([ed0faff](https://github.com/logaretm/vee-validate/commit/ed0faffd79615830a9f7c247abf1eae2254ee3f9)) - added validation trigger config per component closes [#3066](https://github.com/logaretm/vee-validate/issues/3066) ([f0e30a2](https://github.com/logaretm/vee-validate/commit/f0e30a2cc79843040028b7070bc88846f2447c85)) - added value change support for native multi select ([#3146](https://github.com/logaretm/vee-validate/issues/3146)) ([0601586](https://github.com/logaretm/vee-validate/commit/0601586eabbf76fac9d4fa79e6ae1d86fd3a0e37)) - added values helpers ([e0f16d6](https://github.com/logaretm/vee-validate/commit/e0f16d6f5c01c7b1e4e8832b3490b8cc7e7b8aa7)) - added warnings for non existent fields and allow reactive paths ([4182d2f](https://github.com/logaretm/vee-validate/commit/4182d2f1716d712962dff3b6be27916e311e5870)) - avoid watching rules when passed as functions ([539f753](https://github.com/logaretm/vee-validate/commit/539f7535bf935e62030b83f8c7b19e95256bcc52)) - dont render any tags when no message exists closes [#3118](https://github.com/logaretm/vee-validate/issues/3118) ([92eba41](https://github.com/logaretm/vee-validate/commit/92eba41a2cdef643bc2af4c2a0366382cdffc625)) - enhance ts typing for form functions ([8f7d8e8](https://github.com/logaretm/vee-validate/commit/8f7d8e89864b5df5255cbe5e88713022537ec236)) - enhance useField types ([dcb8049](https://github.com/logaretm/vee-validate/commit/dcb80495ffdefb2e789887e1d40b2c4a57ade257)) - enrich form validation results ([0c84c80](https://github.com/logaretm/vee-validate/commit/0c84c809fa729cd2b8620329305b4da0a45e9eaf)) - export some internal types closes [#3065](https://github.com/logaretm/vee-validate/issues/3065) ([b88dffd](https://github.com/logaretm/vee-validate/commit/b88dffdb4c638bd439d093f653bfa1915f4ad9be)) - field.reset() should reset the field to its initial value ([a11f1b7](https://github.com/logaretm/vee-validate/commit/a11f1b7dda3deafe683e13a00b28a7fab09b82cb)) - implement similar reset API for fields ([38c3923](https://github.com/logaretm/vee-validate/commit/38c392320b4154061ccc5d70dde11517357467e8)) - new reset API ([6983738](https://github.com/logaretm/vee-validate/commit/69837383e42636c24d6ee7d15cb5fe8e98f2ac55)) - rename reset methods to be more consistent ([3a0dc4d](https://github.com/logaretm/vee-validate/commit/3a0dc4db2f1a00a8a4f3940ddd452d9b1369cace)) - update docs ([0f5ac98](https://github.com/logaretm/vee-validate/commit/0f5ac98153f74bdbbd1d9f5090e4dc4b438c998f)) - use internal yup types ([#3123](https://github.com/logaretm/vee-validate/issues/3123)) ([7554bfc](https://github.com/logaretm/vee-validate/commit/7554bfc49b0103f218f901148bc06e6a455f09b0)) - use resolveDynamicComponent instead ([f1b5f89](https://github.com/logaretm/vee-validate/commit/f1b5f896840ed159df06cf59badd83282496b777)) ### Performance Improvements - cache field props in a computed property ([d266878](https://github.com/logaretm/vee-validate/commit/d2668787d0ffcab5ba2e8be048ee7334d2b0f9e7)) - cache form slot props in a computed property ([49fa2c1](https://github.com/logaretm/vee-validate/commit/49fa2c1b4a337149c533c13725d2e71bb2664706)) ## [4.1.16](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.15...vee-validate@4.1.16) (2021-02-07) **Note:** Version bump only for package vee-validate ## [4.1.15](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.14...vee-validate@4.1.15) (2021-02-07) ### Bug Fixes - resolve path values with global rules closes [#3157](https://github.com/logaretm/vee-validate/issues/3157) ([beaf316](https://github.com/logaretm/vee-validate/commit/beaf3168490aee585542a19c9a910d9493e78208)) ## [4.1.14](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.13...vee-validate@4.1.14) (2021-02-06) ### Bug Fixes - avoid validating dependencies via watcheffect closes [#3156](https://github.com/logaretm/vee-validate/issues/3156) ([a7b91f6](https://github.com/logaretm/vee-validate/commit/a7b91f6e6c38f0b5262e2d4c1814154efa3b78c8)) ## [4.1.13](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.12...vee-validate@4.1.13) (2021-02-01) ### Features - added value change support for native multi select ([#3146](https://github.com/logaretm/vee-validate/issues/3146)) ([0601586](https://github.com/logaretm/vee-validate/commit/0601586eabbf76fac9d4fa79e6ae1d86fd3a0e37)) ## [4.1.12](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.11...vee-validate@4.1.12) (2021-01-26) ### Bug Fixes - array radio fields not switching value correctly closes [#3141](https://github.com/logaretm/vee-validate/issues/3141) ([3d4efef](https://github.com/logaretm/vee-validate/commit/3d4efef68c63a3b57e2bf14fed913dbf841a7f5e)) - clear out initial values for unregistered fields closes [#3060](https://github.com/logaretm/vee-validate/issues/3060) ([56206de](https://github.com/logaretm/vee-validate/commit/56206de995fe8f2eaca3e303ab6980784a3c95b1)) - typing issue from [#3134](https://github.com/logaretm/vee-validate/issues/3134) ([29e5cff](https://github.com/logaretm/vee-validate/commit/29e5cffc654a2502f29fe616eda088de958e02d3)) ## [4.1.11](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.10...vee-validate@4.1.11) (2021-01-19) ### Features - added validate field function to form and useForm ([#3133](https://github.com/logaretm/vee-validate/issues/3133)) ([926bed1](https://github.com/logaretm/vee-validate/commit/926bed1bded6990f17a51ca68e9aa47c339a80f2)) ## [4.1.10](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.9...vee-validate@4.1.10) (2021-01-17) ### Bug Fixes - set field initial value on the fid lookup closes [#3128](https://github.com/logaretm/vee-validate/issues/3128) ([650d5cf](https://github.com/logaretm/vee-validate/commit/650d5cf9f75f9b9247fc813acf2aff4089f05415)) ## [4.1.9](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.8...vee-validate@4.1.9) (2021-01-13) ### Features - use internal yup types ([#3123](https://github.com/logaretm/vee-validate/issues/3123)) ([7554bfc](https://github.com/logaretm/vee-validate/commit/7554bfc49b0103f218f901148bc06e6a455f09b0)) ## [4.1.8](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.7...vee-validate@4.1.8) (2021-01-12) ### Features - dont render any tags when no message exists closes [#3118](https://github.com/logaretm/vee-validate/issues/3118) ([92eba41](https://github.com/logaretm/vee-validate/commit/92eba41a2cdef643bc2af4c2a0366382cdffc625)) ## [4.1.7](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.6...vee-validate@4.1.7) (2021-01-12) ### Bug Fixes - export submission types [#3112](https://github.com/logaretm/vee-validate/issues/3112) ([3f35167](https://github.com/logaretm/vee-validate/commit/3f351670da02364b0fb8e61198145dfa02dc59b9)) ## [4.1.6](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.5...vee-validate@4.1.6) (2021-01-11) ### Bug Fixes - added emits and onSubmit custom prop ([#3115](https://github.com/logaretm/vee-validate/issues/3115)) ([8f2c110](https://github.com/logaretm/vee-validate/commit/8f2c110f14add0fbd82a28a91601e89938144624)) ## [4.1.5](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.4...vee-validate@4.1.5) (2021-01-05) ### Bug Fixes - correctly set the initial value from the v-model closes [#3107](https://github.com/logaretm/vee-validate/issues/3107) ([4bed9a8](https://github.com/logaretm/vee-validate/commit/4bed9a806323139d2f274e51b6bfe3de2190e54d)) ## [4.1.4](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.3...vee-validate@4.1.4) (2021-01-04) ### Bug Fixes - handle formless checkboxes value toggling closes [#3105](https://github.com/logaretm/vee-validate/issues/3105) ([504f30b](https://github.com/logaretm/vee-validate/commit/504f30bfcbcb1db710397ef05545b5008b0103fb)) ## [4.1.3](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.2...vee-validate@4.1.3) (2021-01-02) ### Features - enhance useField types ([dcb8049](https://github.com/logaretm/vee-validate/commit/dcb80495ffdefb2e789887e1d40b2c4a57ade257)) ## [4.1.2](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.1...vee-validate@4.1.2) (2020-12-26) ### Features - added useSubmitForm hook ([#3101](https://github.com/logaretm/vee-validate/issues/3101)) ([d042882](https://github.com/logaretm/vee-validate/commit/d04288295a090328f7022641799dbaee1c404b91)) ## [4.1.1](https://github.com/logaretm/vee-validate/compare/vee-validate@4.1.0...vee-validate@4.1.1) (2020-12-18) ### Bug Fixes - missing export for useErrors helpers ([28537cc](https://github.com/logaretm/vee-validate/commit/28537cc547cf945b10adc485620ad226f71d60fc)) # [4.1.0](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.7...vee-validate@4.1.0) (2020-12-18) ### Bug Fixes - avoid returning undefined for form errors when form does not exist ([8cce17a](https://github.com/logaretm/vee-validate/commit/8cce17ae2846be912d51926c79e557ed8bb39582)) ### Features - added context awareness to composition helpers for fields ([b59fe88](https://github.com/logaretm/vee-validate/commit/b59fe88197ce3cd587edfc33666bcb676030fa61)) - added test cases and fallbacks for unresolved cases ([71bda03](https://github.com/logaretm/vee-validate/commit/71bda03a72a9e8f27bc0b7620ce78ba48a194309)) - added the useResetForm helper ([4c57715](https://github.com/logaretm/vee-validate/commit/4c57715ab621526a5c987cff9a53cb5b7af2155a)) - added useErrors and useField error helpers ([4cda2fe](https://github.com/logaretm/vee-validate/commit/4cda2fea6428a7f10b53b633daa46252bf779289)) - added useIsDirty helpers ([6b7e4ab](https://github.com/logaretm/vee-validate/commit/6b7e4abfcdb2f0eebe0dd8c62785178fbee8d25f)) - added useIsSubmitting helper ([7a58fd8](https://github.com/logaretm/vee-validate/commit/7a58fd840425a5e09f625054389aebbb096c2e1a)) - added useIsTouched helpers ([fdb2d5a](https://github.com/logaretm/vee-validate/commit/fdb2d5a3c7c82d55aefef2deb95823e1ba6ba93d)) - added useIsValid helpers ([26fbb29](https://github.com/logaretm/vee-validate/commit/26fbb29467bab66c159e98793e4269768845b938)) - added useSubmitCount helper ([c4a6dea](https://github.com/logaretm/vee-validate/commit/c4a6deae68b588494ff0e2477d7ec2b9302c6f09)) - added useValidateField and useValidateForm helpers ([62355a8](https://github.com/logaretm/vee-validate/commit/62355a8db6477562f0689208669d0a1be63de03c)) - added values helpers ([e0f16d6](https://github.com/logaretm/vee-validate/commit/e0f16d6f5c01c7b1e4e8832b3490b8cc7e7b8aa7)) - added warnings for non existent fields and allow reactive paths ([4182d2f](https://github.com/logaretm/vee-validate/commit/4182d2f1716d712962dff3b6be27916e311e5870)) - enhance ts typing for form functions ([8f7d8e8](https://github.com/logaretm/vee-validate/commit/8f7d8e89864b5df5255cbe5e88713022537ec236)) - enrich form validation results ([0c84c80](https://github.com/logaretm/vee-validate/commit/0c84c809fa729cd2b8620329305b4da0a45e9eaf)) - export some internal types closes [#3065](https://github.com/logaretm/vee-validate/issues/3065) ([b88dffd](https://github.com/logaretm/vee-validate/commit/b88dffdb4c638bd439d093f653bfa1915f4ad9be)) ## [4.0.7](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.6...vee-validate@4.0.7) (2020-12-18) ### Bug Fixes - react to validation events changes ([078e61b](https://github.com/logaretm/vee-validate/commit/078e61b17bd299a28752b733b494a0ddb368a812)) ## [4.0.6](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.5...vee-validate@4.0.6) (2020-12-15) ### Bug Fixes - prevent default reset behavior with handleReset ([a66df13](https://github.com/logaretm/vee-validate/commit/a66df13c3f39d84984581dc3c0ce368b052b6e8e)) - prevent resetForm from toggling checkbox value [#3084](https://github.com/logaretm/vee-validate/issues/3084) ([38778f9](https://github.com/logaretm/vee-validate/commit/38778f96471b6aa16fb020cfb1bde56b77a19cfb)) ### Features - added unchecked-value prop to the field component ([af910c3](https://github.com/logaretm/vee-validate/commit/af910c3f3c6343538658ab90f356dd8957bb6a1a)) ### Performance Improvements - cache field props in a computed property ([d266878](https://github.com/logaretm/vee-validate/commit/d2668787d0ffcab5ba2e8be048ee7334d2b0f9e7)) - cache form slot props in a computed property ([49fa2c1](https://github.com/logaretm/vee-validate/commit/49fa2c1b4a337149c533c13725d2e71bb2664706)) ## [4.0.5](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.4...vee-validate@4.0.5) (2020-12-12) ### Features - added validation trigger config per component closes [#3066](https://github.com/logaretm/vee-validate/issues/3066) ([f0e30a2](https://github.com/logaretm/vee-validate/commit/f0e30a2cc79843040028b7070bc88846f2447c85)) ## [4.0.4](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.3...vee-validate@4.0.4) (2020-12-09) ### Bug Fixes - add a handler for regex object params closes [#3073](https://github.com/logaretm/vee-validate/issues/3073) ([7a5e2eb](https://github.com/logaretm/vee-validate/commit/7a5e2ebf8303395372ae08ebcca55427a58faecb)) - fill the target rule params for message generators closes [#3077](https://github.com/logaretm/vee-validate/issues/3077) ([f5e1bd3](https://github.com/logaretm/vee-validate/commit/f5e1bd3cbc278a8588fa0c96af66823d82eefb8c)) ### Features - add submit count state ([#3070](https://github.com/logaretm/vee-validate/issues/3070)) ([a7fe71e](https://github.com/logaretm/vee-validate/commit/a7fe71e01072dacfeb7baa80eebf0b8d7d9d3ffd)) ## [4.0.3](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.2...vee-validate@4.0.3) (2020-12-05) ### Bug Fixes - cast radio buttons value correctly closes [#3064](https://github.com/logaretm/vee-validate/issues/3064) ([3e0f9a4](https://github.com/logaretm/vee-validate/commit/3e0f9a47369edac32d0c8a068f8b61d8f761458f)) - reset meta correctly with resetField ([012658c](https://github.com/logaretm/vee-validate/commit/012658c082a00b1beeb53ce8cf3fcd91bc5b21ec)) - use the custom injection fn for initial field values ([38cd32b](https://github.com/logaretm/vee-validate/commit/38cd32bd3ae9f263510d0ab4a1713c6a9a2011af)) ## [4.0.2](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.1...vee-validate@4.0.2) (2020-11-27) ### Bug Fixes - support dynamic labels closes [#3053](https://github.com/logaretm/vee-validate/issues/3053) ([31b2238](https://github.com/logaretm/vee-validate/commit/31b223878bda75c3150217ea80bb878d8dc1e320)) ## [4.0.1](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0...vee-validate@4.0.1) (2020-11-25) ### Bug Fixes - pass down listeners to the input node closes [#3048](https://github.com/logaretm/vee-validate/issues/3048) ([2526a63](https://github.com/logaretm/vee-validate/commit/2526a63c2361e412b528cf370c03b39cb84b606d)) # [4.0.0](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.19...vee-validate@4.0.0) (2020-11-16) ### Features - added validate method on the form ref instance closes [#3030](https://github.com/logaretm/vee-validate/issues/3030) ([ed0faff](https://github.com/logaretm/vee-validate/commit/ed0faffd79615830a9f7c247abf1eae2254ee3f9)) - update docs ([0f5ac98](https://github.com/logaretm/vee-validate/commit/0f5ac98153f74bdbbd1d9f5090e4dc4b438c998f)) # [4.0.0-beta.19](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.18...vee-validate@4.0.0-beta.19) (2020-11-07) ### Bug Fixes - resolve component before rendering closes [#3014](https://github.com/logaretm/vee-validate/issues/3014) ([f8f481d](https://github.com/logaretm/vee-validate/commit/f8f481daad754a4b18a91e2b07b9549433d023f9)) ### Features - field.reset() should reset the field to its initial value ([a11f1b7](https://github.com/logaretm/vee-validate/commit/a11f1b7dda3deafe683e13a00b28a7fab09b82cb)) - implement similar reset API for fields ([38c3923](https://github.com/logaretm/vee-validate/commit/38c392320b4154061ccc5d70dde11517357467e8)) - new reset API ([6983738](https://github.com/logaretm/vee-validate/commit/69837383e42636c24d6ee7d15cb5fe8e98f2ac55)) - rename reset methods to be more consistent ([3a0dc4d](https://github.com/logaretm/vee-validate/commit/3a0dc4db2f1a00a8a4f3940ddd452d9b1369cace)) - use resolveDynamicComponent instead ([f1b5f89](https://github.com/logaretm/vee-validate/commit/f1b5f896840ed159df06cf59badd83282496b777)) # [4.0.0-beta.18](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.17...vee-validate@4.0.0-beta.18) (2020-11-05) ### Bug Fixes - handle reactive field names and value swaps ([cf8051d](https://github.com/logaretm/vee-validate/commit/cf8051d3b92eb43103f4e7c682e615343239d717)) ### Features - avoid watching rules when passed as functions ([539f753](https://github.com/logaretm/vee-validate/commit/539f7535bf935e62030b83f8c7b19e95256bcc52)) # [4.0.0-beta.17](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.16...vee-validate@4.0.0-beta.17) (2020-11-04) ### Features - added context information to validation functions ([7e6675d](https://github.com/logaretm/vee-validate/commit/7e6675db6a103eae33cbb6d959621b4549af66b2)) # [4.0.0-beta.16](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.15...vee-validate@4.0.0-beta.16) (2020-10-29) ### Features - initial form meta ([#3003](https://github.com/logaretm/vee-validate/issues/3003)) ([f7fd407](https://github.com/logaretm/vee-validate/commit/f7fd407cf0e6dad9c92585a4a82594af962de8f4)) # [4.0.0-beta.15](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.14...vee-validate@4.0.0-beta.15) (2020-10-28) ### Features - add `initialErrors` prop ([#3002](https://github.com/logaretm/vee-validate/issues/3002)) ([9850b3f](https://github.com/logaretm/vee-validate/commit/9850b3f2f1c1739f31ff05f32890196097ef426e)) # [4.0.0-beta.14](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.13...vee-validate@4.0.0-beta.14) (2020-10-26) ### Features - deprecate the disabled prop ([29f4dca](https://github.com/logaretm/vee-validate/commit/29f4dca6bd4d02281bf71f8ed4c836f30e0e46d0)) - use injection keys to type inject API ([79207b2](https://github.com/logaretm/vee-validate/commit/79207b25a23782acc527394af23703b138c881db)) # [4.0.0-beta.13](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.12...vee-validate@4.0.0-beta.13) (2020-10-23) ### Features - `useForm` Field types ([#2996](https://github.com/logaretm/vee-validate/issues/2996)) ([727f229](https://github.com/logaretm/vee-validate/commit/727f2295d421ef92620995a356bcaee53770299b)) # [4.0.0-beta.12](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.11...vee-validate@4.0.0-beta.12) (2020-10-21) ### Bug Fixes - upgrade to Vue 3.0.2 and fix broken cases ([ede7214](https://github.com/logaretm/vee-validate/commit/ede72147bd998b888825457541ff964df5e7a2fd)) # [4.0.0-beta.11](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.10...vee-validate@4.0.0-beta.11) (2020-10-18) ### Bug Fixes - provide yup object schema type to the useForm closes [#2988](https://github.com/logaretm/vee-validate/issues/2988) ([29157f7](https://github.com/logaretm/vee-validate/commit/29157f7a36dd14dc9a6c411ffddbbeb9d3749f6e)) # [4.0.0-beta.10](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.9...vee-validate@4.0.0-beta.10) (2020-10-15) ### Bug Fixes - properly initialize initial values closes [#2978](https://github.com/logaretm/vee-validate/issues/2978) ([c0ba699](https://github.com/logaretm/vee-validate/commit/c0ba699757cbd2c3ab409d5ee8d2fa3a205907d8)) - typos in test descriptions ([#2970](https://github.com/logaretm/vee-validate/issues/2970)) ([a0132df](https://github.com/logaretm/vee-validate/commit/a0132dfcc2aab4ba48f175b846228544c80fe4a8)) # [4.0.0-beta.9](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.8...vee-validate@4.0.0-beta.9) (2020-10-14) ### Bug Fixes - improve useForm meta types ([#2963](https://github.com/logaretm/vee-validate/issues/2963)) ([6b46047](https://github.com/logaretm/vee-validate/commit/6b46047278633a095243fcce4ba94ddd94e08c11)) ### Features - meta setters ([#2967](https://github.com/logaretm/vee-validate/issues/2967)) ([5036e13](https://github.com/logaretm/vee-validate/commit/5036e13e0f5974589387746398446fa5f318dc0d)) # [4.0.0-beta.8](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.7...vee-validate@4.0.0-beta.8) (2020-10-12) ### Features - added handleInput and handleBlur to field scoped slot props ([69d5833](https://github.com/logaretm/vee-validate/commit/69d5833e85d1f455fa43de83251c634b8efa89fa)) - expose reset() on the form controller object ([3229ee7](https://github.com/logaretm/vee-validate/commit/3229ee722e8df5f2e79155e1a4e5ec4729dff726)) - new meta tags API ([#2958](https://github.com/logaretm/vee-validate/issues/2958)) ([7494bfc](https://github.com/logaretm/vee-validate/commit/7494bfc6533fa29bd0668294d694aca96721d52d)) - remove aria attributes and leave it to userland ([365d825](https://github.com/logaretm/vee-validate/commit/365d825b9bc3e2955b31b941f12d5856c9be8bfe)) - remove valid fields from errors mapping ([1eee524](https://github.com/logaretm/vee-validate/commit/1eee52407f4d7156a541811053b529f7540c931c)) # [4.0.0-beta.7](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.6...vee-validate@4.0.0-beta.7) (2020-10-10) ### Bug Fixes - avoid accessing properties in form directly to avoid warninings ([c5627af](https://github.com/logaretm/vee-validate/commit/c5627af64b252c8f7ec18e7f0a4296f315c7bf99)) - update the handleSubmit signature ([#2954](https://github.com/logaretm/vee-validate/issues/2954)) ([d17517d](https://github.com/logaretm/vee-validate/commit/d17517daf692c48ac4fa1cfce5ac0bb051e73d2e)) # [4.0.0-beta.6](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.5...vee-validate@4.0.0-beta.6) (2020-10-10) ### Features - form and fields values setters ([#2949](https://github.com/logaretm/vee-validate/issues/2949)) ([cc2cb41](https://github.com/logaretm/vee-validate/commit/cc2cb413dfa23aefeb8be6e4bf7fa17927e0e1ce)) - reactive initial form values ([#2946](https://github.com/logaretm/vee-validate/issues/2946)) ([ac2c68f](https://github.com/logaretm/vee-validate/commit/ac2c68fdbfb7062674f8294a1f0f6d33fc8792b3)) # [4.0.0-beta.5](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.4...vee-validate@4.0.0-beta.5) (2020-10-08) ### Bug Fixes - sync model value on input closes [#2944](https://github.com/logaretm/vee-validate/issues/2944) ([5f77fa9](https://github.com/logaretm/vee-validate/commit/5f77fa931bdb01cc6415c4edd1dcaa7eb7e1a0d2)) # [4.0.0-beta.4](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.3...vee-validate@4.0.0-beta.4) (2020-10-08) ### Bug Fixes - prevent recursive re-render model update ([#2943](https://github.com/logaretm/vee-validate/issues/2943)) ([9fa319f](https://github.com/logaretm/vee-validate/commit/9fa319f0e42f8225565e2f54d1bebd07898574a4)) - set falsy initial values ([4b29e72](https://github.com/logaretm/vee-validate/commit/4b29e721f06fe30a5f7207935ae3d6291ea464fe)) - use validateField instead of onChange handler for blur events ([636077a](https://github.com/logaretm/vee-validate/commit/636077a35183b33372825cd4075a143383ed0c68)) # [4.0.0-beta.3](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.2...vee-validate@4.0.0-beta.3) (2020-10-06) ### Bug Fixes - avoid toggling checkbox `checked` attr in `handleChange` ([#2937](https://github.com/logaretm/vee-validate/issues/2937)) ([b8dafbd](https://github.com/logaretm/vee-validate/commit/b8dafbdb75e305f00c6effc21391f364db9236d0)) ### Features - added `validateOnMount` prop to `Field` and `Form` components ([#2938](https://github.com/logaretm/vee-validate/issues/2938)) ([3a0d878](https://github.com/logaretm/vee-validate/commit/3a0d878e453163f305acc87c5d4c93812f77f340)) # [4.0.0-beta.2](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.1...vee-validate@4.0.0-beta.2) (2020-10-05) ### Features - field labels ([#2933](https://github.com/logaretm/vee-validate/issues/2933)) ([513137f](https://github.com/logaretm/vee-validate/commit/513137f28c6266d3e752448b00eb1c3d410ae474)) # [4.0.0-beta.1](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-beta.0...vee-validate@4.0.0-beta.1) (2020-10-02) ### Bug Fixes - avoid binding the value to file inputs ([02a2745](https://github.com/logaretm/vee-validate/commit/02a27456ba961540a882ec4f94a24a271b0ea3a3)) # [4.0.0-beta.0](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.14...vee-validate@4.0.0-beta.0) (2020-10-01) ### Bug Fixes - make sure to unwrap initial value ([0298a92](https://github.com/logaretm/vee-validate/commit/0298a926de5536154a69088b55cb688133638a39)) ### Features - validation triggers ([#2927](https://github.com/logaretm/vee-validate/issues/2927)) ([e725f43](https://github.com/logaretm/vee-validate/commit/e725f43a47dd1993699c0450fd8777aa921c7a49)) # [4.0.0-alpha.14](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.13...vee-validate@4.0.0-alpha.14) (2020-09-20) ### Bug Fixes - **core:** in case of radio or checkbox explicitly set initialValue ([#2907](https://github.com/logaretm/vee-validate/issues/2907)) ([e45ec82](https://github.com/logaretm/vee-validate/commit/e45ec82ee8fa6fabd4d3012a03ba8f9b72854631)) ### Features - use symbols to avoid provide/inject conflicts ([cc80032](https://github.com/logaretm/vee-validate/commit/cc8003213c34a8a33d84802f2c93598e1ac3c6f0)) - workspaces ([#2904](https://github.com/logaretm/vee-validate/issues/2904)) ([0c05f94](https://github.com/logaretm/vee-validate/commit/0c05f9486a73744273de6816f00f689916aba91c)) # [4.0.0-alpha.13](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.12...vee-validate@4.0.0-alpha.13) (2020-09-16) ### Features - nested objects/arrays ([#2897](https://github.com/logaretm/vee-validate/issues/2897)) ([8d161a1](https://github.com/logaretm/vee-validate/commit/8d161a137a65c90ec8f7189743be24802231cf29)) # [4.0.0-alpha.12](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.11...vee-validate@4.0.0-alpha.12) (2020-09-15) ### Features - cast single checkboxes values to booleans closes [#2889](https://github.com/logaretm/vee-validate/issues/2889) ([7a08184](https://github.com/logaretm/vee-validate/commit/7a081845ac6a4bc09c51e52c5996b65814a48baf)) - invoke generateMessage handler for local functions closes [#2893](https://github.com/logaretm/vee-validate/issues/2893) ([e9fe773](https://github.com/logaretm/vee-validate/commit/e9fe77365877edda51548c9539ec085fff91586b)) # [4.0.0-alpha.11](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.10...vee-validate@4.0.0-alpha.11) (2020-09-02) **Note:** Version bump only for package vee-validate # [4.0.0-alpha.10](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.9...vee-validate@4.0.0-alpha.10) (2020-08-29) ### Bug Fixes - added temporary fix for [#2873](https://github.com/logaretm/vee-validate/issues/2873) with form meta ([6e1bf17](https://github.com/logaretm/vee-validate/commit/6e1bf176e7ba5c890afab6c11731dac54924d39b)) # [4.0.0-alpha.9](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.8...vee-validate@4.0.0-alpha.9) (2020-08-28) ### Bug Fixes - adapt to the breaking changes in #vue-1682 closes [#2873](https://github.com/logaretm/vee-validate/issues/2873) ([05f7df3](https://github.com/logaretm/vee-validate/commit/05f7df313f9f47ca79bdf99be35cb2ccfea0c346)) # [4.0.0-alpha.8](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.7...vee-validate@4.0.0-alpha.8) (2020-08-13) ### Bug Fixes - detect initial values from v-model ([e566302](https://github.com/logaretm/vee-validate/commit/e566302bb485353f03baccdf98f35a255605e15d)) - handle unmount issue when removed value is falsy for checkboxes ([b6393f4](https://github.com/logaretm/vee-validate/commit/b6393f4adce9346cadaf1f423dca29645bf3c2f1)) - initial array values for checkboxes not populated correctly in form ([fb99edc](https://github.com/logaretm/vee-validate/commit/fb99edc309c26f9be2baa71f90ec1ac59ddcdc9d)) - umounting group of checkbox issues ([8c77af5](https://github.com/logaretm/vee-validate/commit/8c77af52955b235a6bd2357a35036097e109e37f)) ### Features - added basic v-model support ([c93d125](https://github.com/logaretm/vee-validate/commit/c93d125b4d6c0af8365920ee577c883493e60648)) - merge ctx.attrs to any rendered root node ([5c9979c](https://github.com/logaretm/vee-validate/commit/5c9979ce45d4ab10cd019ad0c25159e013198301)) - sync the model value with inner value ([57d7923](https://github.com/logaretm/vee-validate/commit/57d79232f490be3525c2576ef83376a2f5643386)) # [4.0.0-alpha.7](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.6...vee-validate@4.0.0-alpha.7) (2020-08-04) ### Bug Fixes - avoid removing array value for a non-group field closes [#2847](https://github.com/logaretm/vee-validate/issues/2847) ([69f2092](https://github.com/logaretm/vee-validate/commit/69f2092db7d53665986dd384cae561d1b13bd8f5)) - bails affects yup non-object validators ([a50645b](https://github.com/logaretm/vee-validate/commit/a50645b1c0206d0e7d85ec6681ff6dc224536fa2)) - initial values on HTML inputs ([c4f4eb9](https://github.com/logaretm/vee-validate/commit/c4f4eb9fe97b13fedb93ac760614eb53c177ffb3)) ### Features - deprecate the skipOptional config ([e62f5ea](https://github.com/logaretm/vee-validate/commit/e62f5ea6d31e82ac9f257627e8544431b933c4f9)) # [4.0.0-alpha.6](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.5...vee-validate@4.0.0-alpha.6) (2020-07-27) ### Bug Fixes - render input tags by default for the field component ([858c47b](https://github.com/logaretm/vee-validate/commit/858c47b4a7fa740611abaf026e6e5db6cdb41050)) # [4.0.0-alpha.5](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.4...vee-validate@4.0.0-alpha.5) (2020-07-24) ### Bug Fixes - unregister fields once they are unmounted ([0d601cb](https://github.com/logaretm/vee-validate/commit/0d601cb60b3ba907e6c0d73dd129c0c7b086316e)) ### Features - **v4:** add checkbox and radio HTML input support ([#2835](https://github.com/logaretm/vee-validate/issues/2835)) ([ab3d499](https://github.com/logaretm/vee-validate/commit/ab3d4998caf5950656dc0476f13215d598b28832)) - render input by default for the field component ([81d055d](https://github.com/logaretm/vee-validate/commit/81d055d704deaa12b392fd9197218733b3a0bb8d)) # [4.0.0-alpha.4](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.3...vee-validate@4.0.0-alpha.4) (2020-07-23) **Note:** Version bump only for package vee-validate # [4.0.0-alpha.3](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.2...vee-validate@4.0.0-alpha.3) (2020-07-21) ### Features - automatic injection of the form controller ([c039831](https://github.com/logaretm/vee-validate/commit/c0398318ec70c925b6bcb2afa859ec89488e1f78)) - remove debounce feature and make it userland ([b7263ce](https://github.com/logaretm/vee-validate/commit/b7263ce0f887388709846975b59965e440636089)) # [4.0.0-alpha.2](https://github.com/logaretm/vee-validate/compare/vee-validate@4.0.0-alpha.1...vee-validate@4.0.0-alpha.2) (2020-07-19) ### Features - always render a from by default ([402603a](https://github.com/logaretm/vee-validate/commit/402603a8f755a377a056debf24815611a01c3037)) # 4.0.0-alpha.1 (2020-07-18) ### Bug Fixes - added check for cross-fields extraction on unsupported schema ([0ff1bad](https://github.com/logaretm/vee-validate/commit/0ff1bad84a90189f11897cada01fd091e5593bb7)) - added errorMessage prop to the field type ([f1553d0](https://github.com/logaretm/vee-validate/commit/f1553d01b94a74580700fd8712b67688f9c89c15)) - added single error message prop to the provider slot props ([bc97d0c](https://github.com/logaretm/vee-validate/commit/bc97d0c6463cd7e466bb7b3555671e7891d4c60b)) - added unwrap util function ([121bffc](https://github.com/logaretm/vee-validate/commit/121bffc05a9c6e2e204b843d5eb8d7678e5d0fff)) - adjust the useField options to be less strict ([7ea8263](https://github.com/logaretm/vee-validate/commit/7ea826373a78b4fa6343f1da9db0e43879fa0e4e)) - check if a form is present before accessing its schema ([3656181](https://github.com/logaretm/vee-validate/commit/3656181b17a6e44c8f470570ee5126cf2a83ae41)) - debouncing not working correctly and move it to hoc only ([86280a1](https://github.com/logaretm/vee-validate/commit/86280a15e9fb1f94ef8c042a9d04d437f38936b0)) - ensure we unwrap the field id if it was reactive ([7f91e93](https://github.com/logaretm/vee-validate/commit/7f91e930ec8cce4f2e17b49ee9d642d7e9343d6f)) - initial validation not respecting the config opts ([2443d44](https://github.com/logaretm/vee-validate/commit/2443d44b1b00eda39ff884f33f85414aa2b1d34e)) - localization default fallback not being interpolated correctly ([165e89c](https://github.com/logaretm/vee-validate/commit/165e89c6136126d6b946640623261f32b299a2a3)) - no clue why this isn't building ([0d3e7fd](https://github.com/logaretm/vee-validate/commit/0d3e7fdea6f28e29d25f488cae527e925608da7e)) - only add novalidate attr if the rendered element is form ([3638cea](https://github.com/logaretm/vee-validate/commit/3638cead19c9501783e23b43248ce49d7bdf51d7)) - param mapping causing target names to resolve incorrectly ([fb77dc6](https://github.com/logaretm/vee-validate/commit/fb77dc673cb1eff72a1508cff7b4aaed60d8450e)) - set pending back to false earlier in the cycle ([a4237a2](https://github.com/logaretm/vee-validate/commit/a4237a2f8dfde5efcc1d39b5a400e988b8740df9)) - temporary fix for the unamed import issue with vue-beta 4 ([62d27e9](https://github.com/logaretm/vee-validate/commit/62d27e9c9293026d26d62709c2e691d3eb15753e)) - unwrap flags before sending them to the observer slot ([19f7886](https://github.com/logaretm/vee-validate/commit/19f7886adae59b4442139f6e1a3f3905ab54f86a)) - use the proper model event name ([5704db8](https://github.com/logaretm/vee-validate/commit/5704db879019b89b001f496f5f113df24ad09bc6)) - watch target fields once they change ([a4184b0](https://github.com/logaretm/vee-validate/commit/a4184b0065c26df77b680cfbda7450a81b6764ef)) ### Features - adapt the changes from the v3 master branch ([2301c5a](https://github.com/logaretm/vee-validate/commit/2301c5ae75eb8590cb2cc919215ffe4ae934b885)) - add name resolution from v3 ([ba77fdd](https://github.com/logaretm/vee-validate/commit/ba77fdde4f7e5400c6755331af4705715ecc885b)) - add native submit alternative to handleSubmit ([bc00888](https://github.com/logaretm/vee-validate/commit/bc008880607f0393c4e6bd9eb2d44ebb40aa3604)) - added 'as' prop to the validation provider ([5c8ae9c](https://github.com/logaretm/vee-validate/commit/5c8ae9cac2dd418c5bf78b8a0c68e7d256dc96ce)) - added alert role to the error message ([714abfe](https://github.com/logaretm/vee-validate/commit/714abfede6cb2cd2ab1dd72319d27630af6fe9b6)) - added aria and a11y improvements ([ca74f16](https://github.com/logaretm/vee-validate/commit/ca74f165988be3c0c5a6f828508b6aed3fd6e3a0)) - added built-in support for yup validation schema ([e436b75](https://github.com/logaretm/vee-validate/commit/e436b75c4b8b7a085adf701d07b54b798da9a774)) - added ErrorMessage component ([9570412](https://github.com/logaretm/vee-validate/commit/957041270b947e1b70301c3935b6d1ac0bb05a5d)) - added support for custom components ([c661c7e](https://github.com/logaretm/vee-validate/commit/c661c7e1f352e2806c2e2da7bc2c860cfa62f3ff)) - added useField and useForm hooks ([c1e9007](https://github.com/logaretm/vee-validate/commit/c1e900736ed9585d8997d2080f001aad28060281)) - allow the as prop to be a component definition ([29790d4](https://github.com/logaretm/vee-validate/commit/29790d47f17fe49c897bf5b2fda0508f57990479)) - allow the observer to render forms and handle submit events ([9e0d59b](https://github.com/logaretm/vee-validate/commit/9e0d59b11d239c7f1e6d4bc287d9e49aa0376f0d)) - allow validation schema to accept other expressions ([ddeeaea](https://github.com/logaretm/vee-validate/commit/ddeeaea8041c3fad894aff0c827dd9f71b65224d)) - change default field value to undefiend ([00c8754](https://github.com/logaretm/vee-validate/commit/00c87549244447423e0833f8294c5c607bdcf105)) - deprecate names option on validate API ([fe90820](https://github.com/logaretm/vee-validate/commit/fe90820b4b0d4d10df81c2bbd019c3b63d371edf)) - deprecate the 'required' flag ([283caa0](https://github.com/logaretm/vee-validate/commit/283caa0fdd353d990680d42e64be8d8362b6aad5)) - enable interaction modes and localization APIs ([8486aaf](https://github.com/logaretm/vee-validate/commit/8486aaf0fadba03f38b5dd8a5ab857c10e7aa49c)) - expose errorMessage prop on useField and Provider ([04eecaa](https://github.com/logaretm/vee-validate/commit/04eecaa13cc8ab0cc18336021bb912f924e37968)) - expose the form values and pass them to the handleSubmit ([de51155](https://github.com/logaretm/vee-validate/commit/de511555c371bef73037d514e19d44eb4d292eae)) - hook up the provider with new observer implementation ([4d18a65](https://github.com/logaretm/vee-validate/commit/4d18a6572af6af4630bdc2508e027e67d3c0d579)) - implement bails for useField and ValidationProvider ([486babd](https://github.com/logaretm/vee-validate/commit/486babd031efd5a71a819ff535a0e0c661bc45fe)) - implement initial values ([8239130](https://github.com/logaretm/vee-validate/commit/82391301152751eb03097dad4521dc1c275c47e7)) - implement validation debounce ([e294409](https://github.com/logaretm/vee-validate/commit/e2944099ef2074d59f908f7949df3a1059ab3b4e)) - implemented disabled prop ([88bf28e](https://github.com/logaretm/vee-validate/commit/88bf28e89d9e635ebbc79e593a326d4dd2025cdb)) - make rules watchable ([90530cd](https://github.com/logaretm/vee-validate/commit/90530cdebede5bf33a62221371380ad8554326ba)) - make the as prop take priority to determine what to render ([d5a033f](https://github.com/logaretm/vee-validate/commit/d5a033fc57b7ddea8aff4a0f4fe802d7c2489a9c)) - new field binding object ([a58a84b](https://github.com/logaretm/vee-validate/commit/a58a84b009fef5dbfffa2a93a54643b3830cb4bc)) - new handleSubmit signature ([63cbeaf](https://github.com/logaretm/vee-validate/commit/63cbeafd1cfb5e1e14ec42e34c0691a26b258897)) - only export the provider for now ([0bf3efe](https://github.com/logaretm/vee-validate/commit/0bf3efe230be2d80b9e4693779e095c04997a52b)) - remove vid from fields ([1b9bded](https://github.com/logaretm/vee-validate/commit/1b9bdedeb68006535c7087aef267906e2f7bed1d)) - support immediate validation ([42cd6ed](https://github.com/logaretm/vee-validate/commit/42cd6edcfc0c11ea05106e66486ed4772c749548)) - support inline rules as functions ([3c74681](https://github.com/logaretm/vee-validate/commit/3c7468186ac5a6e7fa6bb44b30de4102ef5c31cd)) - support yup validation schemas on field-level ([0802512](https://github.com/logaretm/vee-validate/commit/0802512e181a8a33feaa227770f9e203fcf0cea5)) - updated vnode utils to handle Vue 3 VNode API ([29a4fe8](https://github.com/logaretm/vee-validate/commit/29a4fe859823d5a74814c2dabb3b664185e56366)) - use defineComponent to type Provider and Observer definitions ([80980cf](https://github.com/logaretm/vee-validate/commit/80980cfec81447638aa82b42c208f9ec6f9826f8)) - validate yup form schemas using object validation ([bf216dd](https://github.com/logaretm/vee-validate/commit/bf216dde30a6d90c976bac844129ccbd08a00392)) - validation schema support ([523824a](https://github.com/logaretm/vee-validate/commit/523824a0977d599f6ff2a271ee2edebd5aef36ef)) - working draft for the vprovider with composition api ([b830054](https://github.com/logaretm/vee-validate/commit/b8300547cbafa9904f2b769b8309925ad6da180f)) ================================================ FILE: packages/vee-validate/LICENSE ================================================ The MIT License (MIT) Copyright (c) Abdelrahman Awad Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/vee-validate/README.md ================================================

Painless Vue forms



## Features - **🍞 Easy:** Declarative validation that is familiar and easy to setup - **🧘‍♀️ Flexible:** Synchronous, Asynchronous, field-level or form-level validation - **⚡️ Fast:** Build faster forms faster with intuitive API and small footprint - **🏏 Minimal:** Only handles the complicated form concerns, gives you full control over everything else - **😎 UI Agnostic:** Works with native HTML elements or your favorite UI library components - **🦾 Progressive:** Works whether you use Vue.js as a progressive enhancement or in a complex setup - **✅ Built-in Rules:** Companion lib with 25+ Rules that covers most needs in most web applications - **🌐 i18n:** 45+ locales for built-in rules contributed by developers from all over the world ## Getting Started ### Installation ```sh # Install with yarn yarn add vee-validate # Install with npm npm install vee-validate --save ``` ### Vue version support The main v4 version supports Vue 3.x only, for previous versions of Vue, check the following the table | vue Version | vee-validate version | Documentation Link | | ----------- | -------------------- | ---------------------------------------------------------------------------------------- | | `2.x` | `2.x` or `3.x` | [v2](https://vee-validate.logaretm.com/v2) or [v3](https://vee-validate.logaretm.com/v3) | | `3.x` | `4.x` or `5.x` | [v4](https://vee-validate.logaretm.com/v4) or [v5](https://vee-validate.logaretm.com/v5) | ### Usage vee-validate offers two styles to integrate form validation into your Vue.js apps. #### Composition API The fastest way to create a form and manage its validation, behavior, and values is with the composition API. Create your form with `useForm` and then use `defineField` to create your field model and props/attributes and `handleSubmit` to use the values and send them to an API. ```vue ``` You can do so much more than this, for more info [check the composition API documentation](https://vee-validate.logaretm.com/v5/guide/composition-api/getting-started/). #### Declarative Components Higher-order components can also be used to build forms. Register the `Field` and `Form` components and create a simple `required` validator: ```vue ``` The `Field` component renders an `input` of type `text` by default but you can [control that](https://vee-validate.logaretm.com/v5/api/field#rendering-fields) ## 📚 Documentation Read the [documentation and demos](https://vee-validate.logaretm.com/v4). ## Contributing You are welcome to contribute to this project, but before you do, please make sure you read the [contribution guide](/CONTRIBUTING.md). ## Credits - Inspired by Laravel's [validation syntax](https://laravel.com/docs/5.4/validation) - v4 API Inspired by [Formik's](https://github.com/formium/formik) - Nested path types by [react-hook-form](https://github.com/react-hook-form/react-hook-form) - Logo by [Baianat](https://github.com/baianat) ## Emeriti Here we honor past contributors and sponsors who have been a major part on this project. - [Baianat](https://github.com/baianat). ## ⚖️ License Released under [MIT](/LICENSE) by [@logaretm](https://github.com/logaretm). ================================================ FILE: packages/vee-validate/package.json ================================================ { "name": "vee-validate", "version": "5.0.0-beta.1", "description": "Painless forms for Vue.js", "author": "Abdelrahman Awad ", "license": "MIT", "module": "dist/vee-validate.mjs", "unpkg": "dist/vee-validate.iife.js", "main": "dist/vee-validate.mjs", "type": "module", "exports": { ".": { "types": "./dist/vee-validate.d.ts", "import": "./dist/vee-validate.mjs", "require": "./dist/vee-validate.cjs" }, "./dist/*": "./dist/*" }, "types": "dist/vee-validate.d.ts", "homepage": "https://vee-validate.logaretm.com/", "repository": { "url": "https://github.com/logaretm/vee-validate.git", "type": "git", "directory": "packages/vee-validate" }, "sideEffects": false, "keywords": [ "VueJS", "Vue", "validation", "validator", "inputs", "form" ], "files": [ "dist/*.js", "dist/*.d.ts", "dist/*.cjs", "dist/*.mjs" ], "peerDependencies": { "vue": "^3.4.26" }, "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "@vue/devtools-api": "^7.5.2", "type-fest": "^4.8.3" } } ================================================ FILE: packages/vee-validate/src/ErrorMessage.ts ================================================ import { inject, h, defineComponent, computed, resolveDynamicComponent, VNode } from 'vue'; import { FormContextKey } from './symbols'; import { normalizeChildren } from './utils'; export interface ErrorMessageSlotProps { message: string | undefined; } const ErrorMessageImpl = /** #__PURE__ */ defineComponent({ name: 'ErrorMessage', props: { as: { type: String, default: undefined, }, name: { type: String, required: true, }, }, setup(props, ctx) { const form = inject(FormContextKey, undefined); const message = computed(() => { return form?.errors.value[props.name]; }); function slotProps(): ErrorMessageSlotProps { return { message: message.value, }; } return () => { // Renders nothing if there are no messages if (!message.value) { return undefined; } const tag = (props.as ? resolveDynamicComponent(props.as) : props.as) as string; const children = normalizeChildren(tag, ctx, slotProps as any); const attrs = { role: 'alert', ...ctx.attrs, }; // If no tag was specified and there are children // render the slot as is without wrapping it if (!tag && (Array.isArray(children) || !children) && children?.length) { return children; } // If no children in slot // render whatever specified and fallback to a with the message in it's contents if ((Array.isArray(children) || !children) && !children?.length) { return h(tag || 'span', attrs, message.value); } return h(tag, attrs, children); }; }, }); export const ErrorMessage = ErrorMessageImpl as typeof ErrorMessageImpl & { new (): { $slots: { default: (arg: ErrorMessageSlotProps) => VNode[]; }; }; }; ================================================ FILE: packages/vee-validate/src/Field.ts ================================================ import { h, defineComponent, toRef, SetupContext, resolveDynamicComponent, computed, PropType, VNode, UnwrapRef, } from 'vue'; import { getConfig } from './config'; import { RuleExpression, useField } from './useField'; import { normalizeChildren, hasCheckedAttr, shouldHaveValueBinding, isPropPresent } from './utils'; import { IS_ABSENT } from './symbols'; import { FieldMeta, InputType } from './types'; import { FieldContext } from '.'; import { isCallable } from '../../shared'; interface ValidationTriggersProps { validateOnMount: boolean; validateOnBlur: boolean; validateOnChange: boolean; validateOnInput: boolean; validateOnModelUpdate: boolean; } interface SharedBindingObject { name: string; onBlur: (e: Event) => void; onInput: (e: Event | unknown) => void; onChange: (e: Event | unknown) => void; 'onUpdate:modelValue'?: ((e: TValue) => unknown) | undefined; } export interface FieldBindingObject extends SharedBindingObject { value?: TValue; checked?: boolean; } export interface ComponentFieldBindingObject extends SharedBindingObject { modelValue?: TValue; } export interface FieldSlotProps extends Pick< FieldContext, 'validate' | 'resetField' | 'handleChange' | 'handleReset' | 'handleBlur' | 'setTouched' | 'setErrors' | 'setValue' > { field: FieldBindingObject; componentField: ComponentFieldBindingObject; value: TValue; meta: FieldMeta; errors: string[]; errorMessage: string | undefined; handleInput: FieldContext['handleChange']; } const FieldImpl = /** #__PURE__ */ defineComponent({ name: 'Field', inheritAttrs: false, props: { as: { type: [String, Object], default: undefined, }, name: { type: String, required: true, }, rules: { type: [Object, String, Function] as PropType>, default: undefined, }, validateOnMount: { type: Boolean, default: false, }, validateOnBlur: { type: Boolean, default: undefined, }, validateOnChange: { type: Boolean, default: undefined, }, validateOnInput: { type: Boolean, default: undefined, }, validateOnModelUpdate: { type: Boolean, default: undefined, }, bails: { type: Boolean, default: () => getConfig().bails, }, label: { type: String, default: undefined, }, uncheckedValue: { type: null, default: undefined, }, modelValue: { type: null, default: IS_ABSENT, }, modelModifiers: { type: null, default: () => ({}), }, 'onUpdate:modelValue': { type: null as unknown as PropType<((e: any) => unknown) | undefined>, default: undefined, }, controlled: { type: Boolean, default: true, }, keepValue: { type: Boolean, default: undefined, }, }, setup(props, ctx) { const rules = toRef(props, 'rules'); const name = toRef(props, 'name'); const label = toRef(props, 'label'); const uncheckedValue = toRef(props, 'uncheckedValue'); const keepValue = toRef(props, 'keepValue'); const { errors, value, errorMessage, validate: validateField, handleChange, handleBlur, setTouched, resetField, handleReset, meta, checked, setErrors, setValue, } = useField(name, rules, { validateOnMount: props.validateOnMount, bails: props.bails, controlled: props.controlled, type: ctx.attrs.type as InputType, initialValue: resolveInitialValue(props, ctx), // Only for checkboxes and radio buttons checkedValue: ctx.attrs.value, uncheckedValue, label, validateOnValueUpdate: props.validateOnModelUpdate, keepValueOnUnmount: keepValue, syncVModel: true, }); // If there is a v-model applied on the component we need to emit the `update:modelValue` whenever the value binding changes const onChangeHandler = function handleChangeWithModel(e: Event | unknown, shouldValidate = true) { handleChange(e, shouldValidate); }; const sharedProps = computed(() => { const { validateOnInput, validateOnChange, validateOnBlur, validateOnModelUpdate } = resolveValidationTriggers(props); function baseOnBlur(e: Event) { handleBlur(e, validateOnBlur); if (isCallable(ctx.attrs.onBlur)) { ctx.attrs.onBlur(e); } } function baseOnInput(e: Event | unknown) { onChangeHandler(e, validateOnInput); if (isCallable(ctx.attrs.onInput)) { ctx.attrs.onInput(e); } } function baseOnChange(e: Event | unknown) { onChangeHandler(e, validateOnChange); if (isCallable(ctx.attrs.onChange)) { ctx.attrs.onChange(e); } } const attrs: FieldBindingObject = { name: props.name, onBlur: baseOnBlur, onInput: baseOnInput, onChange: baseOnChange, }; attrs['onUpdate:modelValue'] = e => onChangeHandler(e, validateOnModelUpdate); return attrs; }); const fieldProps = computed(() => { const attrs = { ...sharedProps.value, }; if (hasCheckedAttr(ctx.attrs.type) && checked) { attrs.checked = checked.value; } const tag = resolveTag(props, ctx); if (shouldHaveValueBinding(tag, ctx.attrs)) { attrs.value = value.value; } return attrs; }); const componentProps = computed(() => { return { ...sharedProps.value, modelValue: value.value, }; }); function slotProps(): FieldSlotProps { return { field: fieldProps.value, componentField: componentProps.value, value: value.value, meta, errors: errors.value, errorMessage: errorMessage.value, validate: validateField, resetField, handleChange: onChangeHandler, handleInput: e => onChangeHandler(e, false), handleReset, handleBlur: sharedProps.value.onBlur, setTouched, setErrors, setValue, }; } ctx.expose({ value, meta, errors, errorMessage, setErrors, setTouched, setValue, reset: resetField, validate: validateField, handleChange, }); return () => { const tag = resolveDynamicComponent(resolveTag(props, ctx)) as string; const children = normalizeChildren(tag, ctx, slotProps as any); if (tag) { return h( tag, { ...ctx.attrs, ...fieldProps.value, }, children, ); } return children; }; }, }); function resolveTag(props: Record, ctx: SetupContext) { let tag: string = props.as || ''; if (!props.as && !ctx.slots.default) { tag = 'input'; } return tag; } function resolveValidationTriggers(props: Partial) { const { validateOnInput, validateOnChange, validateOnBlur, validateOnModelUpdate } = getConfig(); return { validateOnInput: props.validateOnInput ?? validateOnInput, validateOnChange: props.validateOnChange ?? validateOnChange, validateOnBlur: props.validateOnBlur ?? validateOnBlur, validateOnModelUpdate: props.validateOnModelUpdate ?? validateOnModelUpdate, }; } function resolveInitialValue(props: Record, ctx: SetupContext) { // Gets the initial value either from `value` prop/attr or `v-model` binding (modelValue) // For checkboxes and radio buttons it will always be the model value not the `value` attribute if (!hasCheckedAttr(ctx.attrs.type)) { return isPropPresent(props, 'modelValue') ? props.modelValue : ctx.attrs.value; } return isPropPresent(props, 'modelValue') ? props.modelValue : undefined; } export const Field = FieldImpl as typeof FieldImpl & { new (): { value: UnwrapRef; meta: UnwrapRef; errors: UnwrapRef; errorMessage: UnwrapRef; setErrors: FieldContext['setErrors']; setTouched: FieldContext['setTouched']; reset: FieldContext['resetField']; validate: FieldContext['validate']; setValue: FieldContext['setValue']; handleChange: FieldContext['handleChange']; $slots: { default: (arg: FieldSlotProps) => VNode[]; }; }; }; ================================================ FILE: packages/vee-validate/src/FieldArray.ts ================================================ import { defineComponent, UnwrapRef, VNode } from 'vue'; import { FieldArrayContext } from './types'; import { useFieldArray } from './useFieldArray'; import { normalizeChildren } from './utils'; const FieldArrayImpl = /** #__PURE__ */ defineComponent({ name: 'FieldArray', inheritAttrs: false, props: { name: { type: String, required: true, }, }, setup(props, ctx) { const { push, remove, swap, insert, replace, update, prepend, move, fields } = useFieldArray(() => props.name); function slotProps() { return { fields: fields.value, push, remove, swap, insert, update, replace, prepend, move, }; } ctx.expose({ push, remove, swap, insert, update, replace, prepend, move, }); return () => { const children = normalizeChildren(undefined, ctx, slotProps); return children; }; }, }); export const FieldArray = FieldArrayImpl as typeof FieldArrayImpl & { new (): { push: FieldArrayContext['push']; remove: FieldArrayContext['remove']; swap: FieldArrayContext['swap']; insert: FieldArrayContext['insert']; update: FieldArrayContext['update']; replace: FieldArrayContext['replace']; prepend: FieldArrayContext['prepend']; move: FieldArrayContext['move']; $slots: { default: (arg: UnwrapRef) => VNode[]; }; }; }; ================================================ FILE: packages/vee-validate/src/Form.ts ================================================ import { klona as deepCopy } from 'klona/full'; import { defineComponent, h, PropType, resolveDynamicComponent, toRef, UnwrapRef, VNode } from 'vue'; import { FormContext, FormErrors, FormMeta, GenericObject, InvalidSubmissionHandler, SubmissionHandler } from './types'; import { useForm } from './useForm'; import { isEvent, isFormSubmitEvent, normalizeChildren } from './utils'; export type FormSlotProps = UnwrapRef< Pick< FormContext, | 'meta' | 'errors' | 'errorBag' | 'values' | 'isSubmitting' | 'isValidating' | 'submitCount' | 'validate' | 'validateField' | 'handleReset' | 'setErrors' | 'setFieldError' | 'setFieldValue' | 'setValues' | 'setFieldTouched' | 'setTouched' | 'resetForm' | 'resetField' | 'controlledValues' > > & { handleSubmit: (evt: Event | SubmissionHandler, onSubmit?: SubmissionHandler) => Promise; submitForm(evt?: Event): void; getValues(): TValues; getMeta(): FormMeta; getErrors(): FormErrors; }; const FormImpl = /** #__PURE__ */ defineComponent({ name: 'Form', inheritAttrs: false, props: { as: { type: null as unknown as PropType, default: 'form', }, validationSchema: { type: Object, default: undefined, }, initialValues: { type: Object, default: undefined, }, initialErrors: { type: Object, default: undefined, }, initialTouched: { type: Object, default: undefined, }, validateOnMount: { type: Boolean, default: false, }, onSubmit: { type: Function as PropType>, default: undefined, }, onInvalidSubmit: { type: Function as PropType, default: undefined, }, keepValues: { type: Boolean, default: false, }, name: { type: String, default: 'Form', }, }, setup(props, ctx) { const validationSchema = toRef(props, 'validationSchema'); const keepValues = toRef(props, 'keepValues'); const { errors, errorBag, values, meta, isSubmitting, isValidating, submitCount, controlledValues, validate, validateField, handleReset, resetForm, handleSubmit, setErrors, setFieldError, setFieldValue, setValues, setFieldTouched, setTouched, resetField, } = useForm({ validationSchema: validationSchema.value ? validationSchema : undefined, initialValues: props.initialValues, initialErrors: props.initialErrors, initialTouched: props.initialTouched, validateOnMount: props.validateOnMount, keepValuesOnUnmount: keepValues, name: props.name, }); const submitForm = handleSubmit((_, { evt }) => { if (isFormSubmitEvent(evt)) { evt.target.submit(); } }, props.onInvalidSubmit); const onSubmit = props.onSubmit ? handleSubmit(props.onSubmit, props.onInvalidSubmit) : submitForm; function handleFormReset(e?: Event) { if (isEvent(e)) { // Prevent default form reset behavior e.preventDefault(); } handleReset(); if (typeof ctx.attrs.onReset === 'function') { ctx.attrs.onReset(); } } function handleScopedSlotSubmit(evt: Event | SubmissionHandler, onSubmit?: SubmissionHandler) { const onSuccess = typeof evt === 'function' && !onSubmit ? evt : onSubmit; return handleSubmit(onSuccess as SubmissionHandler>, props.onInvalidSubmit)(evt as Event); } function getValues() { return deepCopy(values) as TValues; } function getMeta() { return deepCopy(meta.value) as FormMeta; } function getErrors() { return deepCopy(errors.value) as FormErrors; } function slotProps(): FormSlotProps { return { meta: meta.value, errors: errors.value, errorBag: errorBag.value, values, isSubmitting: isSubmitting.value, isValidating: isValidating.value, submitCount: submitCount.value, controlledValues: controlledValues.value, validate, validateField, handleSubmit: handleScopedSlotSubmit, handleReset, submitForm, setErrors, setFieldError, setFieldValue, setValues, setFieldTouched, setTouched, resetForm, resetField, getValues, getMeta, getErrors, }; } // expose these functions and methods as part of public API ctx.expose({ setFieldError, setErrors, setFieldValue, setValues, setFieldTouched, setTouched, resetForm, validate, validateField, resetField, getValues, getMeta, getErrors, values, meta, errors, }); return function renderForm() { // avoid resolving the form component as itself const tag = props.as === 'form' ? props.as : !props.as ? null : (resolveDynamicComponent(props.as) as string); const children = normalizeChildren(tag, ctx, slotProps as any); if (!tag) { return children; } // Attributes to add on a native `form` tag const formAttrs = tag === 'form' ? { // Disables native validation as vee-validate will handle it. novalidate: true, } : {}; return h( tag, { ...formAttrs, ...ctx.attrs, onSubmit, onReset: handleFormReset, }, children, ); }; }, }); export const Form = FormImpl as typeof FormImpl & { new (): { setFieldError: FormContext['setFieldError']; setErrors: FormContext['setErrors']; setFieldValue: FormContext['setFieldValue']; setValues: FormContext['setValues']; setFieldTouched: FormContext['setFieldTouched']; setTouched: FormContext['setTouched']; resetForm: FormContext['resetForm']; resetField: FormContext['resetField']; validate: FormContext['validate']; validateField: FormContext['validateField']; getValues: FormSlotProps['getValues']; getMeta: FormSlotProps['getMeta']; getErrors: FormSlotProps['getErrors']; meta: FormSlotProps['meta']; values: FormSlotProps['values']; errors: FormSlotProps['errors']; $slots: { default: (arg: FormSlotProps) => VNode[]; }; }; }; ================================================ FILE: packages/vee-validate/src/config.ts ================================================ import { ValidationMessageGenerator } from '../../shared'; export interface VeeValidateConfig { bails: boolean; generateMessage: ValidationMessageGenerator; validateOnInput: boolean; validateOnChange: boolean; validateOnBlur: boolean; validateOnModelUpdate: boolean; } const DEFAULT_CONFIG: VeeValidateConfig = { generateMessage: ({ field }) => `${field} is not valid.`, bails: true, validateOnBlur: true, validateOnChange: true, validateOnInput: false, validateOnModelUpdate: true, }; export let currentConfig: VeeValidateConfig = { ...DEFAULT_CONFIG }; export const getConfig = () => currentConfig; const setConfig = (newConf: Partial) => { currentConfig = { ...currentConfig, ...newConf }; }; export const configure = setConfig; ================================================ FILE: packages/vee-validate/src/defineRule.ts ================================================ import { ValidationRuleFunction, SimpleValidationRuleFunction, isCallable } from '../../shared'; const RULES: Record = {}; /** * Adds a custom validator to the list of validation rules. */ export function defineRule>( id: string, validator: ValidationRuleFunction | SimpleValidationRuleFunction, ) { // makes sure new rules are properly formatted. guardExtend(id, validator); RULES[id] = validator as SimpleValidationRuleFunction; } /** * Gets an already defined rule */ export function resolveRule(id: string) { return RULES[id]; } /** * Guards from extension violations. */ function guardExtend( id: string, validator: ValidationRuleFunction | SimpleValidationRuleFunction, ) { if (isCallable(validator)) { return; } throw new Error(`Extension Error: The validator '${id}' must be a function.`); } ================================================ FILE: packages/vee-validate/src/devtools.ts ================================================ import { App, ComponentInternalInstance, getCurrentInstance, nextTick, onUnmounted, toValue, unref } from 'vue'; import type { InspectorNodeTag, CustomInspectorState, CustomInspectorNode } from '@vue/devtools-kit'; import { PathState, PrivateFieldContext, PrivateFormContext } from './types'; import { isClient, keysOf, setInPath, throttle } from './utils'; import { isObject } from '../../shared'; const DEVTOOLS_FORMS: Record = {}; const DEVTOOLS_FIELDS: Record = {}; 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: | { type: 'pathState'; form: PrivateFormContext; state: PathState } | { type: 'form'; form: PrivateFormContext & { _vm?: ComponentInternalInstance | null } } | { type: 'field'; field: PrivateFieldContext & { _vm?: ComponentInternalInstance | null } } | null = null; /** * Plugin API */ let API: any; async function installDevtoolsPlugin(app: App) { if (__DEV__) { if (!isClient) { return; } const devtools = await import('@vue/devtools-api'); devtools.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/v5/logo.png', }, 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) { // eslint-disable-next-line no-console console.error('There is not a valid selected vee-validate node or component'); return; } if (SELECTED_NODE.type === 'field') { await SELECTED_NODE.field.validate(); return; } if (SELECTED_NODE.type === 'form') { await SELECTED_NODE.form.validate(); return; } if (SELECTED_NODE.type === 'pathState') { await SELECTED_NODE.form.validateField(SELECTED_NODE.state.path); } }, }, { icon: 'delete_sweep', tooltip: 'Clear validation state of the selected item', action: () => { if (!SELECTED_NODE) { // eslint-disable-next-line no-console console.error('There is not a valid selected vee-validate node or component'); return; } if (SELECTED_NODE.type === 'field') { SELECTED_NODE.field.resetField(); return; } if (SELECTED_NODE.type === 'form') { SELECTED_NODE.form.resetForm(); } if (SELECTED_NODE.type === 'pathState') { SELECTED_NODE.form.resetField(SELECTED_NODE.state.path); } }, }, ], }); 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 => { if (payload.inspectorId !== INSPECTOR_ID) { return; } const { form, field, state, type } = decodeNodeId(payload.nodeId); api.unhighlightElement(); if (form && type === 'form') { payload.state = buildFormState(form); SELECTED_NODE = { type: 'form', form }; api.highlightElement(form._vm); return; } if (state && type === 'pathState' && form) { payload.state = buildFieldState(state); SELECTED_NODE = { type: 'pathState', state, form }; return; } if (field && type === 'field') { payload.state = buildFieldState({ errors: field.errors.value, dirty: field.meta.dirty, valid: field.meta.valid, touched: field.meta.touched, value: field.value.value, initialValue: field.meta.initialValue, }); SELECTED_NODE = { field, type: 'field' }; api.highlightElement(field._vm); return; } SELECTED_NODE = null; api.unhighlightElement(); }); }, ); } } export const refreshInspector = throttle(() => { setTimeout(async () => { await nextTick(); API?.sendInspectorState(INSPECTOR_ID); API?.sendInspectorTree(INSPECTOR_ID); }, 100); }, 100); export function registerFormWithDevTools(form: PrivateFormContext) { if (!__DEV__ || !isClient) { return; } const vm = getCurrentInstance(); if (!API) { const app = vm?.appContext.app; if (!app) { return; } installDevtoolsPlugin(app as unknown as App); } DEVTOOLS_FORMS[form.formId] = { ...form }; DEVTOOLS_FORMS[form.formId]._vm = vm; onUnmounted(() => { delete DEVTOOLS_FORMS[form.formId]; refreshInspector(); }); refreshInspector(); } export function registerSingleFieldWithDevtools(field: PrivateFieldContext) { if (!__DEV__ || !isClient) { return; } const vm = getCurrentInstance(); if (!API) { const app = vm?.appContext.app; if (!app) { return; } installDevtoolsPlugin(app as unknown as App); } DEVTOOLS_FIELDS[field.id] = { ...field }; DEVTOOLS_FIELDS[field.id]._vm = vm; onUnmounted(() => { delete DEVTOOLS_FIELDS[field.id]; refreshInspector(); }); refreshInspector(); } function mapFormForDevtoolsInspector(form: PrivateFormContext): CustomInspectorNode { const { textColor, bgColor } = getValidityColors(form.meta.value.valid); const formTreeNodes = {}; Object.values(form.getAllPathStates()).forEach(state => { setInPath(formTreeNodes, toValue(state.path), mapPathForDevtoolsInspector(state, form)); }); function buildFormTree(tree: any[] | Record, path: string[] = []): CustomInspectorNode { const key = [...path].pop(); if ('id' in tree && typeof tree.id === 'string') { return { ...tree, label: key || tree.label, } as CustomInspectorNode; } if (isObject(tree)) { return { id: `${path.join('.')}`, label: key || '', children: Object.keys(tree).map(key => buildFormTree(tree[key] as any, [...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.name, children, tags: [ { label: 'Form', textColor, backgroundColor: bgColor, }, { label: `${form.getAllPathStates().length} fields`, textColor: COLORS.white, backgroundColor: COLORS.unknown, }, ], }; } function mapPathForDevtoolsInspector(state: PathState, form?: PrivateFormContext): CustomInspectorNode { return { id: encodeNodeId(form, state), label: toValue(state.path), tags: getFieldNodeTags(state.multiple, state.fieldsCount, state.type, state.valid, form), }; } function mapFieldForDevtoolsInspector(field: PrivateFieldContext, form?: PrivateFormContext): CustomInspectorNode { return { id: encodeNodeId(form, field), label: unref(field.name), tags: getFieldNodeTags(false, 1, field.type, field.meta.valid, form), }; } function getFieldNodeTags( multiple: boolean, fieldsCount: number, type: string | undefined, valid: boolean, form: PrivateFormContext | undefined, ) { const { textColor, bgColor } = getValidityColors(valid); return [ multiple ? undefined : { label: 'Field', textColor, backgroundColor: bgColor, }, !form ? { label: 'Standalone', textColor: COLORS.black, backgroundColor: COLORS.gray, } : undefined, type === 'checkbox' ? { label: 'Checkbox', textColor: COLORS.white, backgroundColor: COLORS.blue, } : undefined, type === 'radio' ? { label: 'Radio', textColor: COLORS.white, backgroundColor: COLORS.purple, } : undefined, multiple ? { label: 'Multiple', textColor: COLORS.black, backgroundColor: COLORS.orange, } : undefined, ].filter(Boolean) as InspectorNodeTag[]; } function encodeNodeId(form?: PrivateFormContext, stateOrField?: PathState | PrivateFieldContext): string { const type = stateOrField ? ('path' in stateOrField ? 'pathState' : 'field') : 'form'; const fieldPath = stateOrField ? ('path' in stateOrField ? stateOrField?.path : toValue(stateOrField?.name)) : ''; const ff = type === 'field' ? stateOrField?.id : fieldPath; const idObject = { f: form?.formId, ff, type }; return btoa(encodeURIComponent(JSON.stringify(idObject))); } function decodeNodeId(nodeId: string): { field?: PrivateFieldContext & { _vm?: ComponentInternalInstance | null }; form?: PrivateFormContext & { _vm?: ComponentInternalInstance | null }; state?: PathState; type?: 'form' | 'field' | 'pathState'; } { try { const idObject = JSON.parse(decodeURIComponent(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 state = form.getPathState(idObject.ff); return { type: idObject.type, form, state, }; } catch (err) { // console.error(`Devtools: [vee-validate] Failed to parse node id ${nodeId}`); } return {}; } function buildFieldState( state: Pick, ): CustomInspectorState { return { 'Field state': [ { key: 'errors', value: state.errors }, { key: 'initialValue', value: state.initialValue, }, { key: 'currentValue', value: state.value, }, { key: 'touched', value: state.touched, }, { key: 'dirty', value: state.dirty, }, { key: 'valid', value: state.valid, }, ], }; } function buildFormState(form: PrivateFormContext): CustomInspectorState { const { errorBag, meta, values, isSubmitting, isValidating, submitCount } = form; return { 'Form state': [ { key: 'submitCount', value: submitCount.value, }, { key: 'isSubmitting', value: isSubmitting.value, }, { key: 'isValidating', value: isValidating.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) => { const message = errorBag.value[key]?.[0]; if (message) { acc[key] = message; } return acc; }, {} as Record, ), }, ], }; } /** * Resolves the tag color based on the form state */ function getValidityColors(valid: boolean) { return { bgColor: valid ? COLORS.success : COLORS.error, textColor: valid ? COLORS.black : COLORS.white, }; } ================================================ FILE: packages/vee-validate/src/globals.d.ts ================================================ /* eslint-disable no-var */ declare var __VUE_PROD_DEVTOOLS__: boolean; declare var __DEV__: boolean; ================================================ FILE: packages/vee-validate/src/index.ts ================================================ export { validate, validateObjectSchema as validateObject } from './validate'; export { defineRule } from './defineRule'; export { configure, getConfig } from './config'; export { normalizeRules, isNotNestedPath, cleanupNonNestedPath } from './utils'; export { Field, FieldBindingObject, ComponentFieldBindingObject, FieldSlotProps } from './Field'; export { Form, FormSlotProps } from './Form'; export { FieldArray } from './FieldArray'; export { ErrorMessage } from './ErrorMessage'; export { useField, FieldOptions, RuleExpression } from './useField'; export { useForm, useFormContext, FormOptions } from './useForm'; export { useFieldArray } from './useFieldArray'; export * from './types'; export { useResetForm } from './useResetForm'; export { useIsFieldDirty } from './useIsFieldDirty'; export { useIsFieldTouched } from './useIsFieldTouched'; export { useIsFieldValid } from './useIsFieldValid'; export { useIsSubmitting } from './useIsSubmitting'; export { useIsValidating } from './useIsValidating'; export { useValidateField } from './useValidateField'; export { useIsFormDirty } from './useIsFormDirty'; export { useIsFormTouched } from './useIsFormTouched'; export { useIsFormValid } from './useIsFormValid'; export { useValidateForm } from './useValidateForm'; export { useSubmitCount } from './useSubmitCount'; export { useFieldValue } from './useFieldValue'; export { useFormValues } from './useFormValues'; export { useFormErrors } from './useFormErrors'; export { useFieldError } from './useFieldError'; export { useSubmitForm } from './useSubmitForm'; export { useSetFieldError } from './useSetFieldError'; export { useSetFieldTouched } from './useSetFieldTouched'; export { useSetFieldValue } from './useSetFieldValue'; export { useSetFormErrors } from './useSetFormErrors'; export { useSetFormTouched } from './useSetFormTouched'; export { useSetFormValues } from './useSetFormValues'; export * from './symbols'; ================================================ FILE: packages/vee-validate/src/symbols.ts ================================================ import { InjectionKey } from 'vue'; import { PrivateFormContext, PrivateFieldContext, FormContext } from './types'; export const FormContextKey: InjectionKey = Symbol('vee-validate-form'); export const PublicFormContextKey: InjectionKey = Symbol('vee-validate-form-context'); export const FieldContextKey: InjectionKey> = Symbol('vee-validate-field-instance'); export const IS_ABSENT = Symbol('Default empty value'); ================================================ FILE: packages/vee-validate/src/types/common.ts ================================================ import { MaybeRef, Ref } from 'vue'; import { Path, PathValue } from './paths'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type GenericObject = Record; export type MaybeArray = T | T[]; export type MaybePromise = T | Promise; export type FlattenAndSetPathsType = { [K in Path]: TType }; export type MapValuesPathsToRefs< TValues extends GenericObject, TPaths extends readonly [...MaybeRef>[]], > = { readonly [K in keyof TPaths]: TPaths[K] extends MaybeRef ? TKey extends Path ? Ref> : Ref : Ref; }; ================================================ FILE: packages/vee-validate/src/types/devtools.ts ================================================ import { FieldMeta, FormErrors, FormMeta } from './forms'; export interface DevtoolsPluginFieldState { name: string; value: any; initialValue: any; errors: string[]; meta: FieldMeta; } export interface DevtoolsPluginFormState { meta: FormMeta>; errors: FormErrors>; values: Record; isSubmitting: boolean; isValidating: boolean; submitCount: number; } ================================================ FILE: packages/vee-validate/src/types/forms.ts ================================================ import { ComputedRef, Ref, MaybeRef, MaybeRefOrGetter } from 'vue'; import { GenericObject, MaybeArray, MaybePromise, FlattenAndSetPathsType } from './common'; import { FieldValidationMetaInfo } from '../../../shared'; import { Path, PathValue } from './paths'; import { PartialDeep } from 'type-fest'; import type { StandardSchemaV1 } from '@standard-schema/spec'; export interface ValidationResult { errors: string[]; valid: boolean; value?: TValue; } export type FlattenAndMapPathsValidationResult = { [K in Path]: ValidationResult; }; export type Locator = { __locatorRef: string } & ((values: GenericObject) => unknown); export interface FieldMeta { touched: boolean; dirty: boolean; valid: boolean; validated: boolean; pending: boolean; initialValue?: TValue; } export interface FormMeta { touched: boolean; dirty: boolean; valid: boolean; pending: boolean; initialValues?: Partial; } export interface FieldState { value: TValue; touched: boolean; errors: string[]; } export type InputType = 'checkbox' | 'radio' | 'default'; /** * validated-only: only mutate the previously validated fields * silent: do not mutate any field * force: validate all fields and mutate their state */ export type SchemaValidationMode = 'validated-only' | 'silent' | 'force'; export interface ValidationOptions { mode: SchemaValidationMode; warn: boolean; } export type FieldValidator = (opts?: Partial) => Promise>; export interface PathStateConfig { bails: boolean; label: MaybeRefOrGetter; type: InputType; validate: FieldValidator; schema?: MaybeRefOrGetter; } export type IssueCollection = { path: TPath; messages: string[]; }; export interface PathState { id: number | number[]; path: string; touched: boolean; dirty: boolean; valid: boolean; validated: boolean; pending: boolean; initialValue: TInput | undefined; value: TInput | undefined; errors: string[]; bails: boolean; label: string | undefined; type: InputType; multiple: boolean; fieldsCount: number; __flags: { pendingUnmount: Record; pendingReset: boolean; }; validate?: FieldValidator; } export interface FieldEntry { value: TValue; key: string | number; isFirst: boolean; isLast: boolean; } export interface FieldArrayContext { fields: Ref[]>; remove(idx: number): void; replace(newArray: TValue[]): void; update(idx: number, value: TValue): void; push(value: TValue): void; swap(indexA: number, indexB: number): void; insert(idx: number, value: TValue): void; prepend(value: TValue): void; move(oldIdx: number, newIdx: number): void; } export interface PrivateFieldArrayContext extends FieldArrayContext { reset(): void; path: MaybeRefOrGetter; } export interface PrivateFieldContext { id: number; name: MaybeRef; value: Ref; meta: FieldMeta; errors: Ref; errorMessage: Ref; label?: MaybeRefOrGetter; type?: string; bails?: boolean; keepValueOnUnmount?: MaybeRefOrGetter; checkedValue?: MaybeRefOrGetter; uncheckedValue?: MaybeRefOrGetter; checked?: Ref; resetField(state?: Partial>): void; handleReset(): void; validate: FieldValidator; handleChange(e: Event | unknown, shouldValidate?: boolean): void; handleBlur(e?: Event, shouldValidate?: boolean): void; setState(state: Partial>): void; setTouched(isTouched: boolean): void; setErrors(message: string | string[]): void; setValue(value: TInput, shouldValidate?: boolean): void; } export type FieldContext = Omit, 'id' | 'instances'>; export type GenericValidateFunction = ( value: TValue, ctx: FieldValidationMetaInfo, ) => MaybePromise>; export interface FormState { values: PartialDeep; errors: Partial, string | undefined>>; touched: Partial, boolean>>; submitCount: number; } export type FormErrors = Partial | '', string | undefined>>; export type FormErrorBag = Partial | '', string[]>>; export interface ResetFormOpts { force: boolean; } // eslint-disable-next-line @typescript-eslint/no-unused-vars export interface FormActions { setFieldValue>(field: T, value: PathValue, shouldValidate?: boolean): void; setFieldError(field: Path, message: string | string[] | undefined): void; setErrors(fields: Partial>): void; setValues(fields: PartialDeep, shouldValidate?: boolean): void; setFieldTouched(field: Path, isTouched: boolean): void; setTouched(fields: Partial, boolean>> | boolean): void; resetForm(state?: Partial>, opts?: Partial): void; resetField(field: Path, state?: Partial): void; } export interface FormValidationResult { valid: boolean; results: Partial>; errors: Partial, string>>; values?: Partial; source: 'schema' | 'fields' | 'none'; } export interface SubmissionContext extends FormActions { evt?: Event; controlledValues: Partial; } export type SubmissionHandler = ( values: TOutput, ctx: SubmissionContext, ) => TReturn; export interface InvalidSubmissionContext< TInput extends GenericObject = GenericObject, TOutput extends GenericObject = TInput, > { values: TInput; evt?: Event; errors: Partial, string>>; results: FormValidationResult['results']; } export type InvalidSubmissionHandler< TInput extends GenericObject = GenericObject, TOutput extends GenericObject = TInput, > = (ctx: InvalidSubmissionContext) => void; export type RawFormSchema = Record, string | GenericValidateFunction | GenericObject>; export type FieldPathLookup = Partial< Record, PrivateFieldContext | PrivateFieldContext[]> >; type HandleSubmitFactory = ( cb: SubmissionHandler, onSubmitValidationErrorCb?: InvalidSubmissionHandler, ) => (e?: Event) => Promise; export type PublicPathState = Omit< PathState, 'bails' | 'label' | 'multiple' | 'fieldsCount' | 'validate' | 'id' | 'type' | '__flags' >; export interface BaseFieldProps { onBlur: () => void; onChange: () => void; onInput: () => void; } export interface InputBindsConfig { props: (state: PublicPathState) => TExtraProps; validateOnBlur: boolean; label: MaybeRefOrGetter; validateOnChange: boolean; validateOnInput: boolean; validateOnModelUpdate: boolean; } export type LazyInputBindsConfig = ( state: PublicPathState, ) => Partial<{ props: TExtraProps; validateOnBlur: boolean; validateOnChange: boolean; validateOnInput: boolean; validateOnModelUpdate: boolean; }>; export interface ComponentBindsConfig< TValue = unknown, TExtraProps extends GenericObject = GenericObject, TModel extends string = 'modelValue', > { mapProps: (state: PublicPathState) => TExtraProps; validateOnBlur: boolean; validateOnModelUpdate: boolean; model: TModel; } export type LazyComponentBindsConfig< TValue = unknown, TExtraProps extends GenericObject = GenericObject, TModel extends string = 'modelValue', > = (state: PublicPathState) => Partial<{ props: TExtraProps; validateOnBlur: boolean; validateOnModelUpdate: boolean; model: TModel; }>; export interface ComponentModellessBinds { onBlur: () => void; } export type ComponentModelBinds = ComponentModellessBinds & { [TKey in `onUpdate:${TModel}`]: (value: TValue) => void; }; export type BaseComponentBinds = ComponentModelBinds< TValue, TModel > & { [k in TModel]: TValue; }; export interface BaseInputBinds { value: TValue | undefined; onBlur: (e: Event) => void; onChange: (e: Event) => void; onInput: (e: Event) => void; } export interface PrivateFormContext< TValues extends GenericObject = GenericObject, TOutput extends GenericObject = TValues, > extends FormActions { name: string; formId: number; values: TValues; initialValues: Ref>; controlledValues: Ref; fieldArrays: PrivateFieldArrayContext[]; submitCount: Ref; schema?: MaybeRef | StandardSchemaV1 | undefined>; errorBag: Ref>; errors: ComputedRef>; meta: ComputedRef>; isSubmitting: Ref; isValidating: Ref; keepValuesOnUnmount: MaybeRef; validateSchema?: (mode: SchemaValidationMode) => Promise>; validate(opts?: Partial): Promise>; validateField>( field: TPath, opts?: Partial, ): Promise>; stageInitialValue(path: string, value: unknown, updateOriginal?: boolean): void; unsetInitialValue(path: string): void; handleSubmit: HandleSubmitFactory & { withControlled: HandleSubmitFactory }; setFieldInitialValue(path: string, value: unknown, updateOriginal?: boolean): void; createPathState>( path: MaybeRef, config?: Partial>, ): PathState>; getPathState>(path: TPath): PathState> | undefined; getAllPathStates(): PathState[]; removePathState>(path: TPath, id: number): void; unsetPathValue>(path: TPath): void; destroyPath(path: string): void; isFieldTouched>(path: TPath): boolean; isFieldDirty>(path: TPath): boolean; isFieldValid>(path: TPath): boolean; defineField< TPath extends Path, TValue = PathValue, TExtras extends GenericObject = GenericObject, >( path: MaybeRefOrGetter, config?: Partial> | LazyInputBindsConfig, ): [Ref, Ref]; } export interface FormContext extends Omit< PrivateFormContext, | 'formId' | 'schema' | 'initialValues' | 'getPathState' | 'getAllPathStates' | 'removePathState' | 'unsetPathValue' | 'validateSchema' | 'stageInitialValue' | 'setFieldInitialValue' | 'unsetInitialValue' | 'fieldArrays' | 'markForUnmount' | 'keepValuesOnUnmount' | 'values' > { values: TValues; handleReset: () => void; submitForm: (e?: unknown) => Promise; } ================================================ FILE: packages/vee-validate/src/types/index.ts ================================================ export * from './common'; export * from './paths'; export * from './forms'; export * from './devtools'; ================================================ FILE: packages/vee-validate/src/types/paths.ts ================================================ // Originally implemented in react-hook-form // https://github.com/react-hook-form/react-hook-form/tree/master/src/types/path /* eslint-disable @typescript-eslint/no-explicit-any */ type BrowserNativeObject = Date | FileList | File; type Primitive = null | undefined | string | number | boolean | symbol | bigint; /** * Checks whether the type is any * See {@link https://stackoverflow.com/a/49928360/3406963} * @typeParam T - type which may be any * ``` * IsAny = true * IsAny = false * ``` */ export type IsAny = 0 extends 1 & T ? true : false; /** * Checks whether T1 can be exactly (mutually) assigned to T2 * @typeParam T1 - type to check * @typeParam T2 - type to check against * ``` * IsEqual = true * IsEqual<'foo', 'foo'> = true * IsEqual = false * IsEqual = false * IsEqual = false * IsEqual<'foo', string> = false * IsEqual<'foo' | 'bar', 'foo'> = boolean // 'foo' is assignable, but 'bar' is not (true | false) -> boolean * ``` */ export type IsEqual = T1 extends T2 ? (() => G extends T1 ? 1 : 2) extends () => G extends T2 ? 1 : 2 ? true : false : false; /** * Type to query whether an array type T is a tuple type. * @typeParam T - type which may be an array or tuple * @example * ``` * IsTuple<[number]> = true * IsTuple = false * ``` */ type IsTuple> = number extends T['length'] ? false : true; /** * Type which can be used to index an array or tuple type. */ type ArrayKey = number; /** * Helper function to break apart T1 and check if any are equal to T2 * * See {@link IsEqual} */ type AnyIsEqual = T1 extends T2 ? (IsEqual extends true ? true : never) : never; /** * Type which given a tuple type returns its own keys, i.e. only its indices. * @typeParam T - tuple type * @example * ``` * TupleKeys<[number, string]> = '0' | '1' * ``` */ type TupleKeys> = Exclude; /** * Helper type to construct tuple key paths and recurse into its elements. * * See {@link Path} */ type PathInternalTuple, TraversedTypes> = { [Key in TupleKeys & string]: `[${Key}]` | `[${Key}]${PathInternal}`; }[TupleKeys & string]; /** * Helper type to construct array key paths and recurse into its elements. * * See {@link Path} */ type PathInternalArray, TraversedTypes> = | `[${ArrayKey}]` | `[${ArrayKey}]${PathInternal}`; /** * Helper type to construct object key paths and recurse into its nested values. * * See {@link Path} */ type PathInternalObject = { [Key in keyof TValue & string]: First extends true ? `${Key}` | `${Key}${PathInternal}` : `.${Key}` | `.${Key}${PathInternal}`; }[keyof TValue & string]; /** * Helper type to construct nested any object key paths. * * See {@link Path} */ type PathInternalAny = `.${string}` | `[${string}]` | `[${string}].${string}`; /** * Helper type for recursively constructing paths through a type. * * This obscures internal type params TraversedTypes and First from ed contract. * * See {@link Path} */ type PathInternal = TValue extends Primitive | BrowserNativeObject ? IsAny extends true ? PathInternalAny : never : TValue extends ReadonlyArray ? // Check so that we don't recurse into the same type by ensuring that the // types are mutually assignable mutually required to avoid false // positives of subtypes true extends AnyIsEqual ? never : IsTuple extends true ? PathInternalTuple : PathInternalArray : TValue extends Record ? PathInternalObject : ''; /** * Helper type for recursively constructing paths through a type. * This actually constructs the strings and recurses into nested * object types. * * See {@link ArrayPath} */ type ArrayPathImpl = V extends Primitive | BrowserNativeObject ? IsAny extends true ? string : never : V extends ReadonlyArray ? U extends Primitive | BrowserNativeObject ? IsAny extends true ? string : never : // Check so that we don't recurse into the same type // by ensuring that the types are mutually assignable // mutually required to avoid false positives of subtypes true extends AnyIsEqual ? never : `${K}` | `${K}.${ArrayPathInternal}` : true extends AnyIsEqual ? never : `${K}.${ArrayPathInternal}`; /** * Helper type for recursively constructing paths through a type. * This obscures the internal type param TraversedTypes from ed contract. * * See {@link ArrayPath} */ type ArrayPathInternal = T extends ReadonlyArray ? IsTuple extends true ? { [K in TupleKeys]-?: ArrayPathImpl; }[TupleKeys] : ArrayPathImpl : { [K in keyof T]-?: ArrayPathImpl; }[keyof T]; /** * Type which eagerly collects all paths through a type which point to an array * type. * @typeParam T - type which should be introspected. * @example * ``` * Path<{foo: {bar: string[], baz: number[]}}> = 'foo.bar' | 'foo.baz' * ``` */ // We want to explode the union type and process each individually // so assignable types don't leak onto the stack from the base. type ArrayPath = T extends any ? ArrayPathInternal : never; /** * Type to evaluate the type which the given path points to. * @typeParam T - deeply nested type which is indexed by the path * @typeParam P - path into the deeply nested type * @example * ``` * PathValue<{foo: {bar: string}}, 'foo.bar'> = string * PathValue<[number, string], '1'> = string * ``` */ export type PathValue | ArrayPath> = T extends any ? P extends `${infer K}.${infer R}` ? K extends keyof T ? R extends Path ? PathValue : never : K extends `${ArrayKey}` ? T extends ReadonlyArray ? PathValue> : never : never : P extends keyof T ? T[P] : P extends `${ArrayKey}` ? T extends ReadonlyArray ? V : never : never : never; /** * Type which eagerly collects all paths through a type * @typeParam T - type which should be introspected * @example * ``` * Path<{foo: {bar: string}}> = 'foo' | 'foo.bar' * ``` */ // We want to explode the union type and process each individually // so assignable types don't leak onto the stack from the base. export type Path = T extends any ? PathInternal & string : never; ================================================ FILE: packages/vee-validate/src/useField.ts ================================================ import { watch, isRef, computed, onMounted, provide, getCurrentInstance, Ref, ComponentInternalInstance, onBeforeUnmount, toValue, MaybeRef, MaybeRefOrGetter, unref, } from 'vue'; import { klona as deepCopy } from 'klona/full'; import { validate as validateValue } from './validate'; import { GenericValidateFunction, FieldContext, FieldState, PrivateFieldContext, SchemaValidationMode, ValidationOptions, FormContext, PrivateFormContext, InputType, } from './types'; import { normalizeRules, extractLocators, normalizeEventValue, hasCheckedAttr, getFromPath, injectWithSelf, resolveNextCheckboxValue, applyModelModifiers, withLatest, isEqual, isStandardSchema, } from './utils'; import { isCallable, normalizeFormPath } from '../../shared'; import { FieldContextKey, FormContextKey, IS_ABSENT } from './symbols'; import { useFieldState } from './useFieldState'; import { refreshInspector, registerSingleFieldWithDevtools } from './devtools'; import { StandardSchemaV1 } from '@standard-schema/spec'; export interface FieldOptions { initialValue?: MaybeRef; validateOnValueUpdate: boolean; validateOnMount?: boolean; bails?: boolean; type?: InputType; checkedValue?: MaybeRefOrGetter; uncheckedValue?: MaybeRefOrGetter; label?: MaybeRefOrGetter; controlled?: boolean; keepValueOnUnmount?: MaybeRefOrGetter; syncVModel?: boolean | string; form?: FormContext; } export type RuleExpression = | string | Record | GenericValidateFunction | GenericValidateFunction[] | StandardSchemaV1 | undefined; /** * Creates a field composite. */ export function useField( path: MaybeRefOrGetter, rules?: MaybeRef>, opts?: Partial>, ): FieldContext { if (hasCheckedAttr(opts?.type)) { return useFieldWithChecked(path, rules, opts); } return _useField(path, rules, opts); } function _useField( path: MaybeRefOrGetter, rules?: MaybeRef>, opts?: Partial>, ): FieldContext { 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 as PrivateFormContext | undefined) || injectedForm; const name = computed(() => normalizeFormPath(toValue(path))); const validator = computed(() => { const schema = toValue(form?.schema); if (schema) { return undefined; } const rulesValue = unref(rules); if (isStandardSchema(rulesValue) || isCallable(rulesValue) || Array.isArray(rulesValue)) { return rulesValue; } return normalizeRules(rulesValue); }); const { id, value, initialValue, meta, setState, errors, flags } = useFieldState(name, { modelValue, form, bails, label, type, validate: validator.value ? validate : undefined, }); const errorMessage = computed(() => errors.value[0]); if (syncVModel) { useVModel({ value, prop: syncVModel, handleChange, shouldValidate: () => validateOnValueUpdate && !flags.pendingReset, }); } /** * Handles common onBlur meta update */ const handleBlur = (evt?: unknown, shouldValidate = false) => { meta.touched = true; if (shouldValidate) { validateWithStateMutation(); } }; async function validateCurrentValue(mode: SchemaValidationMode) { if (form?.validateSchema) { const { results } = await form.validateSchema(mode); return results[toValue(name)] ?? { valid: true, errors: [] }; } if (validator.value) { return validateValue(value.value, validator.value, { name: toValue(name), label: toValue(label), values: form?.values ?? {}, 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(opts?: Partial) { if (opts?.mode === 'silent') { return validateValidStateOnly(); } return validateWithStateMutation(); } // Common input/change event handler function handleChange(e: unknown, shouldValidate = true) { const newValue = normalizeEventValue(e) as TValue; setValue(newValue, shouldValidate); } // 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: boolean) { meta.touched = isTouched; } function resetField(state?: Partial>) { const newValue = state && 'value' in state ? (state.value as TValue) : initialValue.value; setState({ value: deepCopy(newValue), initialValue: deepCopy(newValue), touched: state?.touched ?? false, errors: state?.errors || [], }); meta.pending = false; meta.validated = false; validateValidStateOnly(); } const vm = getCurrentInstance(); function setValue(newValue: TValue, shouldValidate = true) { value.value = vm && syncVModel ? applyModelModifiers(newValue, vm.props.modelModifiers) : newValue; const validateFn = shouldValidate ? validateWithStateMutation : validateValidStateOnly; validateFn(); } function setErrors(errors: string[] | string) { setState({ errors: Array.isArray(errors) ? errors : [errors] }); } const valueProxy = computed({ get() { return value.value; }, set(newValue: TValue) { setValue(newValue, validateOnValueUpdate); }, }); const field: PrivateFieldContext = { id, name, label, value: valueProxy, meta, errors, errorMessage, type, checkedValue, uncheckedValue, bails, keepValueOnUnmount, resetField, handleReset: () => resetField(), validate, handleChange, handleBlur, setState, setTouched, setErrors, setValue, }; provide(FieldContextKey, field); if (isRef(rules) && typeof unref(rules) !== 'function') { watch( rules, (value, oldValue) => { if (isEqual(value, oldValue)) { return; } meta.validated ? validateWithStateMutation() : validateValidStateOnly(); }, { deep: true, }, ); } if (__DEV__) { (field as any)._vm = getCurrentInstance(); watch(() => ({ errors: errors.value, ...meta, value: value.value }), refreshInspector, { deep: true, }); if (!form) { registerSingleFieldWithDevtools(field); } } // 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 = computed(() => { const rulesVal = validator.value; // is falsy, a function schema or a standard schema if (!rulesVal || isCallable(rulesVal) || isStandardSchema(rulesVal) || Array.isArray(rulesVal)) { return {}; } return Object.keys(rulesVal).reduce( (acc, rule: string) => { const deps = extractLocators(rulesVal[rule]) .map((dep: any) => dep.__locatorRef) .reduce( (depAcc, depName) => { const depValue = getFromPath(form.values, depName) || form.values[depName]; if (depValue !== undefined) { depAcc[depName] = depValue; } return depAcc; }, {} as Record, ); Object.assign(acc, deps); return acc; }, {} as Record, ); }); // Adds a watcher that runs the validation whenever field dependencies change 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(); } }); onBeforeUnmount(() => { const shouldKeepValue = toValue(field.keepValueOnUnmount) ?? toValue(form.keepValuesOnUnmount); const path = toValue(name); if (shouldKeepValue || !form || flags.pendingUnmount[field.id]) { form?.removePathState(path, id); return; } flags.pendingUnmount[field.id] = true; const pathState = form.getPathState(path); const matchesId = Array.isArray(pathState?.id) && pathState?.multiple ? pathState?.id.includes(field.id) : pathState?.id === field.id; if (!matchesId) { return; } if (pathState?.multiple && Array.isArray(pathState.value)) { const valueIdx = pathState.value.findIndex(i => isEqual(i, 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(toValue(name)); } form.removePathState(path, id); }); return field; } /** * Normalizes partial field options to include the full options */ function normalizeOptions(opts: Partial> | undefined): FieldOptions { const defaults = (): Partial> => ({ initialValue: undefined, validateOnMount: false, bails: true, label: undefined, validateOnValueUpdate: true, keepValueOnUnmount: undefined, syncVModel: false, controlled: true, }); const isVModelSynced = !!opts?.syncVModel; const modelPropName = typeof opts?.syncVModel === 'string' ? opts.syncVModel : 'modelValue'; const initialValue = isVModelSynced && !('initialValue' in (opts || {})) ? getCurrentModelValue(getCurrentInstance(), modelPropName) : opts?.initialValue; if (!opts) { return { ...defaults(), initialValue } as FieldOptions; } const controlled = opts.controlled ?? true; const syncVModel = opts?.syncVModel || false; return { ...defaults(), ...(opts || {}), initialValue, controlled: controlled ?? true, checkedValue: opts?.checkedValue, syncVModel, } as FieldOptions; } function useFieldWithChecked( name: MaybeRefOrGetter, rules?: MaybeRef>, opts?: Partial>, ): FieldContext { const form = opts?.controlled ? injectWithSelf(FormContextKey) : undefined; const checkedValue = opts?.checkedValue; const uncheckedValue = opts?.uncheckedValue; function patchCheckedApi( field: FieldContext & { originalHandleChange?: FieldContext['handleChange'] }, ): FieldContext { const handleChange = field.handleChange; const checked = computed(() => { const currentValue = toValue(field.value); const checkedVal = toValue(checkedValue); return Array.isArray(currentValue) ? currentValue.findIndex(v => isEqual(v, checkedVal)) >= 0 : isEqual(checkedVal, currentValue); }); function handleCheckboxChange(e: unknown, shouldValidate = true) { if (checked.value === ((e as Event)?.target as HTMLInputElement)?.checked) { if (shouldValidate) { field.validate(); } return; } const path = toValue(name); const pathState = form?.getPathState(path); const value = normalizeEventValue(e); let newValue = toValue(checkedValue) ?? value; if (form && pathState?.multiple && pathState.type === 'checkbox') { newValue = resolveNextCheckboxValue(getFromPath(form.values, path) || [], newValue, undefined) as TValue; } else if (opts?.type === 'checkbox') { newValue = resolveNextCheckboxValue(toValue(field.value), newValue, toValue(uncheckedValue)) as TValue; } handleChange(newValue, shouldValidate); } return { ...field, checked, checkedValue, uncheckedValue, handleChange: handleCheckboxChange, }; } return patchCheckedApi(_useField(name, rules, opts)); } interface ModelOpts { prop: string | boolean; value: Ref; handleChange: FieldContext['handleChange']; shouldValidate: () => boolean; } function useVModel({ prop, value, handleChange, shouldValidate }: ModelOpts) { const vm = getCurrentInstance(); /* istanbul ignore next */ if (!vm || !prop) { if (__DEV__) { // eslint-disable-next-line no-console console.warn('Failed to setup model events because `useField` was not called in setup.'); } return; } const propName = typeof prop === 'string' ? prop : 'modelValue'; const emitName = `update:${propName}`; // Component doesn't have a model prop setup (must be defined on the props) if (!(propName in vm.props)) { return; } watch(value, newValue => { if (isEqual(newValue, getCurrentModelValue(vm, propName))) { return; } vm.emit(emitName, newValue); }); watch( () => getCurrentModelValue(vm, propName), propValue => { if ((propValue as any) === IS_ABSENT && value.value === undefined) { return; } const newValue = (propValue as any) === IS_ABSENT ? undefined : propValue; if (isEqual(newValue, value.value)) { return; } handleChange(newValue, shouldValidate()); }, ); } function getCurrentModelValue(vm: ComponentInternalInstance | null, propName: string) { if (!vm) { return undefined; } return vm.props[propName] as TValue; } ================================================ FILE: packages/vee-validate/src/useFieldArray.ts ================================================ import { Ref, unref, ref, onBeforeUnmount, watch, MaybeRefOrGetter, toValue } from 'vue'; import { klona as deepCopy } from 'klona/full'; import { isNullOrUndefined } from '../../shared'; import { FormContextKey } from './symbols'; import { FieldArrayContext, FieldEntry, PrivateFieldArrayContext, PrivateFormContext } from './types'; import { computedDeep, getFromPath, injectWithSelf, warn, isEqual, setInPath } from './utils'; export function useFieldArray(arrayPath: MaybeRefOrGetter): FieldArrayContext { const form = injectWithSelf(FormContextKey, undefined) as PrivateFormContext; const fields: Ref[]> = ref([]); const noOp = () => {}; const noOpApi: FieldArrayContext = { fields, remove: noOp, push: noOp, swap: noOp, insert: noOp, update: noOp, replace: noOp, prepend: noOp, move: noOp, }; if (!form) { if (__DEV__) { warn( 'FieldArray requires being a child of `` or `useForm` being called before it. Array fields may not work correctly', ); } return noOpApi; } if (!unref(arrayPath)) { if (__DEV__) { warn('FieldArray requires a field path to be provided, did you forget to pass the `name` prop?'); } return noOpApi; } const alreadyExists = form.fieldArrays.find(a => unref(a.path) === unref(arrayPath)); if (alreadyExists) { return alreadyExists as PrivateFieldArrayContext; } let entryCounter = 0; function getCurrentValues() { return getFromPath(form?.values, toValue(arrayPath), []) || []; } function initFields() { const currentValues = getCurrentValues(); if (!Array.isArray(currentValues)) { return; } fields.value = currentValues.map((v, idx) => createEntry(v, idx, fields.value)); updateEntryFlags(); } initFields(); function updateEntryFlags() { const fieldsLength = fields.value.length; for (let i = 0; i < fieldsLength; i++) { const entry = fields.value[i]; entry.isFirst = i === 0; entry.isLast = i === fieldsLength - 1; } } function createEntry(value: TValue, idx?: number, currentFields?: FieldEntry[]): FieldEntry { // Skips the work by returning the current entry if it already exists // This should make the `key` prop stable and doesn't cause more re-renders than needed // The value is computed and should update anyways if (currentFields && !isNullOrUndefined(idx) && currentFields[idx]) { return currentFields[idx]; } const key = entryCounter++; const entry: FieldEntry = { key, value: computedDeep({ get() { const currentValues = getFromPath(form?.values, toValue(arrayPath), []) || []; const idx = fields.value.findIndex(e => e.key === key); return idx === -1 ? value : currentValues[idx]; }, set(value: TValue) { const idx = fields.value.findIndex(e => e.key === key); if (idx === -1) { if (__DEV__) { warn(`Attempting to update a non-existent array item`); } return; } update(idx, value); }, }) as TValue, // will be auto unwrapped isFirst: false, isLast: false, }; return entry; } function afterMutation() { updateEntryFlags(); // Should trigger a silent validation since a field may not do that #4096 form?.validate({ mode: 'silent' }); } function remove(idx: number) { const pathName = toValue(arrayPath); const pathValue = getFromPath(form?.values, pathName); if (!pathValue || !Array.isArray(pathValue)) { return; } const newValue = [...pathValue]; newValue.splice(idx, 1); const fieldPath = pathName + `[${idx}]`; form.destroyPath(fieldPath); form.unsetInitialValue(fieldPath); setInPath(form.values, pathName, newValue); fields.value.splice(idx, 1); afterMutation(); } function push(initialValue: TValue) { const value = deepCopy(initialValue); const pathName = toValue(arrayPath); const pathValue = getFromPath(form?.values, pathName); const normalizedPathValue = isNullOrUndefined(pathValue) ? [] : pathValue; if (!Array.isArray(normalizedPathValue)) { return; } const newValue = [...normalizedPathValue]; newValue.push(value); form.stageInitialValue(pathName + `[${newValue.length - 1}]`, value); setInPath(form.values, pathName, newValue); fields.value.push(createEntry(value)); afterMutation(); } function swap(indexA: number, indexB: number) { const pathName = toValue(arrayPath); const pathValue = getFromPath(form?.values, pathName); if (!Array.isArray(pathValue) || !(indexA in pathValue) || !(indexB in pathValue)) { return; } const newValue = [...pathValue]; const newFields = [...fields.value]; // the old switcheroo const temp = newValue[indexA]; newValue[indexA] = newValue[indexB]; newValue[indexB] = temp; const tempEntry = newFields[indexA]; newFields[indexA] = newFields[indexB]; newFields[indexB] = tempEntry; setInPath(form.values, pathName, newValue); fields.value = newFields; updateEntryFlags(); } function insert(idx: number, initialValue: TValue) { const value = deepCopy(initialValue); const pathName = toValue(arrayPath); const pathValue = getFromPath(form?.values, pathName); if (!Array.isArray(pathValue) || pathValue.length < idx) { return; } const newValue = [...pathValue]; const newFields = [...fields.value]; newValue.splice(idx, 0, value); newFields.splice(idx, 0, createEntry(value)); setInPath(form.values, pathName, newValue); fields.value = newFields; afterMutation(); } function replace(arr: TValue[]) { const pathName = toValue(arrayPath); form.stageInitialValue(pathName, arr); setInPath(form.values, pathName, arr); initFields(); afterMutation(); } function update(idx: number, value: TValue) { const pathName = toValue(arrayPath); const pathValue = getFromPath(form?.values, pathName); if (!Array.isArray(pathValue) || pathValue.length - 1 < idx) { return; } setInPath(form.values, `${pathName}[${idx}]`, value); form?.validate({ mode: 'validated-only' }); } function prepend(initialValue: TValue) { const value = deepCopy(initialValue); const pathName = toValue(arrayPath); const pathValue = getFromPath(form?.values, pathName); const normalizedPathValue = isNullOrUndefined(pathValue) ? [] : pathValue; if (!Array.isArray(normalizedPathValue)) { return; } const newValue = [value, ...normalizedPathValue]; setInPath(form.values, pathName, newValue); form.stageInitialValue(pathName + `[0]`, value); fields.value.unshift(createEntry(value)); afterMutation(); } function move(oldIdx: number, newIdx: number) { const pathName = toValue(arrayPath); const pathValue = getFromPath(form?.values, pathName); const newValue = isNullOrUndefined(pathValue) ? [] : [...pathValue]; if (!Array.isArray(pathValue) || !(oldIdx in pathValue) || !(newIdx in pathValue)) { return; } const newFields = [...fields.value]; const movedItem = newFields[oldIdx]; newFields.splice(oldIdx, 1); newFields.splice(newIdx, 0, movedItem); const movedValue = newValue[oldIdx]; newValue.splice(oldIdx, 1); newValue.splice(newIdx, 0, movedValue); setInPath(form.values, pathName, newValue); fields.value = newFields; afterMutation(); } const fieldArrayCtx: FieldArrayContext = { fields, remove, push, swap, insert, update, replace, prepend, move, }; form.fieldArrays.push({ path: arrayPath, reset: initFields, ...fieldArrayCtx, }); onBeforeUnmount(() => { const idx = form.fieldArrays.findIndex(i => toValue(i.path) === toValue(arrayPath)); if (idx >= 0) { form.fieldArrays.splice(idx, 1); } }); // Makes sure to sync the form values with the array value if they go out of sync // #4153 watch(getCurrentValues, formValues => { const fieldsValues = fields.value.map(f => f.value); // If form values are not the same as the current values then something overrode them. if (!isEqual(formValues, fieldsValues)) { initFields(); } }); return fieldArrayCtx; } ================================================ FILE: packages/vee-validate/src/useFieldError.ts ================================================ import { computed, inject, MaybeRefOrGetter, toValue } from 'vue'; import { FieldContextKey, FormContextKey } from './symbols'; import { injectWithSelf } from './utils'; /** * Gives access to a single field error */ export function useFieldError(path?: MaybeRefOrGetter) { const form = injectWithSelf(FormContextKey); // We don't want to use self injected context as it doesn't make sense const field = path ? undefined : inject(FieldContextKey); return computed(() => { if (path) { return form?.errors.value[toValue(path)]; } return field?.errorMessage.value; }); } ================================================ FILE: packages/vee-validate/src/useFieldState.ts ================================================ import { computed, isRef, reactive, ref, Ref, unref, watch, MaybeRef, MaybeRefOrGetter } from 'vue'; import { FieldMeta, FieldState, FieldValidator, InputType, PrivateFormContext, PathState } from './types'; import { getFromPath, isEqual, normalizeErrorItem } from './utils'; export interface StateSetterInit extends FieldState { initialValue: TValue; } export interface FieldStateComposable { id: number; path: MaybeRef; meta: FieldMeta; value: Ref; flags: PathState['__flags']; initialValue: Ref; errors: Ref; setState(state: Partial>): void; } export interface StateInit { modelValue: MaybeRef; form?: PrivateFormContext; bails: boolean; label?: MaybeRefOrGetter; type?: InputType; validate?: FieldValidator; } let ID_COUNTER = 0; export function useFieldState( path: MaybeRef, init: Partial>, ): FieldStateComposable { 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); function setState(state: Partial>) { if ('value' in state) { value.value = state.value as TValue; } if ('errors' in state) { setErrors(state.errors); } if ('touched' in state) { meta.touched = state.touched ?? meta.touched; } if ('initialValue' in state) { setInitialValue(state.initialValue as TValue); } } 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, }); const errors = computed(() => state.errors); function setState(state: Partial>) { if ('value' in state) { value.value = state.value as TValue; } if ('errors' in state) { init.form?.setFieldError(unref(path), state.errors); } if ('touched' in state) { init.form?.setFieldTouched(unref(path), state.touched ?? false); } if ('initialValue' in state) { setInitialValue(state.initialValue as TValue); } } return { id: Array.isArray(state.id) ? state.id[state.id.length - 1] : state.id, path, value, errors, meta: state, initialValue, flags: state.__flags, setState, }; } interface FieldValueComposable { value: Ref; initialValue: Ref; setInitialValue(value: TValue): void; } /** * Creates the field value and resolves the initial value */ export function _useFieldValue( path: MaybeRef, modelValue?: MaybeRef, form?: PrivateFormContext, ): FieldValueComposable { const modelRef = ref(unref(modelValue)) as Ref; function resolveInitialValue() { if (!form) { return unref(modelRef) as TValue; } return getFromPath(form.initialValues.value, unref(path), unref(modelRef)) as TValue; } function setInitialValue(value: TValue) { if (!form) { modelRef.value = value; return; } form.setFieldInitialValue(unref(path), value, true); } const initialValue = computed(resolveInitialValue); // if no form is associated, use a regular ref. if (!form) { const value = ref(resolveInitialValue()) as Ref; 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)) as TValue; }, set(newVal) { form.setFieldValue(unref(path), newVal, false); }, }) as Ref; 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: MaybeRef | undefined, form: PrivateFormContext, initialValue: MaybeRef | undefined, path: MaybeRef, ): TValue { if (isRef(modelValue)) { return unref(modelValue); } if (modelValue !== undefined) { return modelValue; } return getFromPath(form.values, unref(path), unref(initialValue)) as TValue; } /** * Creates meta flags state and some associated effects with them */ function createFieldMeta( currentValue: Ref, initialValue: MaybeRef | undefined, errors: Ref, ) { const meta = reactive({ touched: false, pending: false, valid: true, validated: !!unref(errors).length, initialValue: computed(() => unref(initialValue) as TValue | undefined), dirty: computed(() => { return !isEqual(unref(currentValue), unref(initialValue)); }), }) as FieldMeta; watch( errors, value => { meta.valid = !value.length; }, { immediate: true, flush: 'sync', }, ); return meta; } /** * Creates the error message state for the field state */ export function createFieldErrors() { const errors = ref([]); return { errors, setErrors: (messages: string | string[] | null | undefined) => { errors.value = normalizeErrorItem(messages); }, }; } ================================================ FILE: packages/vee-validate/src/useFieldValue.ts ================================================ import { computed, inject, MaybeRefOrGetter, toValue } from 'vue'; import { FieldContextKey, FormContextKey } from './symbols'; import { getFromPath, injectWithSelf } from './utils'; /** * Gives access to a field's current value */ export function useFieldValue(path?: MaybeRefOrGetter) { const form = injectWithSelf(FormContextKey); // We don't want to use self injected context as it doesn't make sense const field = path ? undefined : inject(FieldContextKey); return computed(() => { if (path) { return getFromPath(form?.values, toValue(path)) as TValue; } return toValue(field?.value) as TValue; }); } ================================================ FILE: packages/vee-validate/src/useForm.ts ================================================ import { computed, ref, Ref, provide, reactive, onMounted, isRef, watch, unref, nextTick, warn, watchEffect, shallowRef, readonly, toValue, MaybeRef, MaybeRefOrGetter, inject, } from 'vue'; import { PartialDeep } from 'type-fest'; import { klona as deepCopy } from 'klona/full'; import { FieldMeta, SubmissionHandler, GenericValidateFunction, ValidationResult, FormState, FormValidationResult, FlattenAndMapPathsValidationResult, PrivateFormContext, FormContext, FormErrors, FormErrorBag, SchemaValidationMode, RawFormSchema, ValidationOptions, PrivateFieldArrayContext, InvalidSubmissionHandler, FieldState, GenericObject, Path, FlattenAndSetPathsType, PathValue, PathState, PathStateConfig, BaseFieldProps, InputBindsConfig, LazyInputBindsConfig, ResetFormOpts, } from './types'; import { getFromPath, keysOf, setInPath, unsetPath, isFormSubmitEvent, debounceAsync, withLatest, isEqual, normalizeErrorItem, omit, debounceNextTick, isStandardSchema, } from './utils'; import { FormContextKey, PublicFormContextKey } from './symbols'; import { validateStandardSchema, validateObjectSchema } from './validate'; import { refreshInspector, registerFormWithDevTools } from './devtools'; import { isCallable, merge, normalizeFormPath } from '../../shared'; import { getConfig } from './config'; import { StandardSchemaV1 } from '@standard-schema/spec'; type FormSchema> = | FlattenAndSetPathsType | undefined; export interface FormOptions< TValues extends GenericObject, TOutput = TValues, TSchema extends StandardSchemaV1 | FormSchema = FormSchema, > { validationSchema?: MaybeRef : any>; initialValues?: PartialDeep | undefined | null; initialErrors?: FlattenAndSetPathsType; initialTouched?: FlattenAndSetPathsType; validateOnMount?: boolean; keepValuesOnUnmount?: MaybeRef; name?: string; } let FORM_COUNTER = 0; const PRIVATE_PATH_STATE_KEYS: (keyof PathState)[] = ['bails', 'fieldsCount', 'id', 'multiple', 'type', 'validate']; function resolveInitialValues(opts?: FormOptions): TValues { const givenInitial = opts?.initialValues || {}; const providedValues = { ...toValue(givenInitial) }; return deepCopy(providedValues) as TValues; } export function useForm< TValues extends GenericObject = GenericObject, TOutput extends GenericObject = TValues, TSchema extends FormSchema | StandardSchemaV1 = FormSchema, >(opts?: FormOptions): FormContext { const formId = FORM_COUNTER++; const name = opts?.name || 'Form'; // Prevents fields from double resetting their values, which causes checkboxes to toggle their initial value let FIELD_ID_COUNTER = 0; // If the form is currently submitting const isSubmitting = ref(false); // If the form is currently validating const isValidating = ref(false); // The number of times the user tried to submit the form const submitCount = ref(0); // field arrays managed by this form const fieldArrays: PrivateFieldArrayContext[] = []; // a private ref for all form values const formValues = reactive(resolveInitialValues(opts)) as TValues; const pathStates = ref[]>([]); const extraErrorsBag: Ref> = ref({}); const pathStateLookup = ref>({}); const rebuildPathLookup = debounceNextTick(() => { pathStateLookup.value = pathStates.value.reduce( (names, state) => { names[normalizeFormPath(toValue(state.path))] = state; return names; }, {} as Record, ); }); /** * Manually sets an error message on a specific field */ function setFieldError(field: Path | PathState, message: string | undefined | string[]) { const state = findPathState(field); if (!state) { if (typeof field === 'string') { extraErrorsBag.value[normalizeFormPath(field) as Path] = normalizeErrorItem(message); } return; } // Move the error from the extras path if exists if (typeof field === 'string') { const normalizedPath = normalizeFormPath(field) as Path; if (extraErrorsBag.value[normalizedPath]) { delete extraErrorsBag.value[normalizedPath]; } } state.errors = normalizeErrorItem(message); state.valid = !state.errors.length; } /** * Sets errors for the fields specified in the object */ function setErrors(paths: Partial>) { keysOf(paths).forEach(path => { setFieldError(path, paths[path]); }); } if (opts?.initialErrors) { setErrors(opts.initialErrors); } const errorBag = computed>(() => { const pathErrors = pathStates.value.reduce((acc, state) => { if (state.errors.length) { acc[toValue(state.path) as Path] = state.errors; } return acc; }, {} as FormErrorBag); return { ...extraErrorsBag.value, ...pathErrors }; }); // Gets the first error of each field const errors = computed>(() => { return keysOf(errorBag.value).reduce((acc, key) => { const errors = errorBag.value[key]; if (errors?.length) { acc[key] = errors[0]; } return acc; }, {} as FormErrors); }); /** * Holds a computed reference to all fields names and labels */ const fieldNames = computed(() => { return pathStates.value.reduce( (names, state) => { names[toValue(state.path)] = { name: toValue(state.path) || '', label: state.label || '' }; return names; }, {} as Record, ); }); const fieldBailsMap = computed(() => { return pathStates.value.reduce( (map, state) => { map[toValue(state.path)] = state.bails ?? true; return map; }, {} as Record, ); }); // mutable non-reactive reference to initial errors // we need this to process initial errors then unset them const initialErrors = { ...(opts?.initialErrors || ({} as FlattenAndSetPathsType)), }; const keepValuesOnUnmount = opts?.keepValuesOnUnmount ?? false; // initial form values const { initialValues, originalInitialValues, setInitialValues } = useFormInitialValues( pathStates, formValues, opts, ); // form meta aggregations const meta = useFormMeta(pathStates, formValues, originalInitialValues, errors); const controlledValues = computed(() => { return pathStates.value.reduce((acc, state) => { const value = getFromPath(formValues, toValue(state.path)); setInPath(acc, toValue(state.path), value); return acc; }, {} as TValues); }); const schema = opts?.validationSchema; function createPathState>( path: MaybeRefOrGetter, config?: Partial>, ): PathState { const initialValue = computed(() => getFromPath(initialValues.value, toValue(path))); const pathStateExists = pathStateLookup.value[toValue(path)]; const isCheckboxOrRadio = config?.type === 'checkbox' || config?.type === 'radio'; if (pathStateExists && isCheckboxOrRadio) { pathStateExists.multiple = true; const id = FIELD_ID_COUNTER++; if (Array.isArray(pathStateExists.id)) { pathStateExists.id.push(id); } else { pathStateExists.id = [pathStateExists.id, id]; } pathStateExists.fieldsCount++; pathStateExists.__flags.pendingUnmount[id] = false; return pathStateExists as PathState; } const currentValue = computed(() => getFromPath(formValues, toValue(path))); const pathValue = toValue(path); const unsetBatchIndex = UNSET_BATCH.findIndex(_path => _path === pathValue); if (unsetBatchIndex !== -1) { UNSET_BATCH.splice(unsetBatchIndex, 1); } const id = FIELD_ID_COUNTER++; const state = reactive({ id, path, touched: false, pending: false, valid: true, validated: !!initialErrors[pathValue]?.length, initialValue, errors: shallowRef([]), bails: config?.bails ?? false, label: config?.label, type: config?.type || 'default', value: currentValue, multiple: false, __flags: { pendingUnmount: { [id]: false }, pendingReset: false, }, fieldsCount: 1, validate: config?.validate, dirty: computed(() => { return !isEqual(unref(currentValue), unref(initialValue)); }), }) as PathState; pathStates.value.push(state); pathStateLookup.value[pathValue] = state; rebuildPathLookup(); if (errors.value[pathValue] && !initialErrors[pathValue]) { nextTick(() => { validateField(pathValue, { mode: 'silent' }); }); } // Handles when a path changes if (isRef(path)) { watch(path, newPath => { rebuildPathLookup(); const nextValue = deepCopy(currentValue.value); pathStateLookup.value[newPath] = state; nextTick(() => { setInPath(formValues, newPath, nextValue); }); }); } return state; } /** * Batches validation runs in 5ms batches * Must have two distinct batch queues to make sure they don't override each other settings #3783 */ const debouncedSilentValidation = debounceAsync(_validateSchema, 5); const debouncedValidation = debounceAsync(_validateSchema, 5); const validateSchema = withLatest( async (mode: SchemaValidationMode) => { return (await (mode === 'silent' ? debouncedSilentValidation() : debouncedValidation())) as FormValidationResult; }, (formResult, [mode]) => { // fields by id lookup // errors fields names, we need it to also check if custom errors are updated const currentErrorsPaths = keysOf(formCtx.errorBag.value); // collect all the keys from the schema and all fields // this ensures we have a complete key map of all the fields const paths = [ ...new Set([...keysOf(formResult.results), ...pathStates.value.map(p => p.path), ...currentErrorsPaths]), ].sort() as string[]; // aggregates the paths into a single result object while applying the results on the fields const results = paths.reduce( (validation, _path) => { const expectedPath = _path as Path; const pathState = findPathState(expectedPath) || findHoistedPath(expectedPath); const messages = formResult.results[expectedPath]?.errors || []; // This is the real path of the field, because it might've been a hoisted field const path = (toValue(pathState?.path) || expectedPath) as Path; // It is possible that multiple paths are collected across loops // We want to merge them to avoid overriding any iteration's results const fieldResult = mergeValidationResults( { errors: messages, valid: !messages.length }, validation.results[path], ); validation.results[path] = fieldResult; if (!fieldResult.valid) { validation.errors[path] = fieldResult.errors[0]; } // clean up extra errors if path state exists if (pathState && extraErrorsBag.value[path]) { delete extraErrorsBag.value[path]; } // field not rendered if (!pathState) { setFieldError(path, messages); return validation; } // always update the valid flag regardless of the mode pathState.valid = fieldResult.valid; if (mode === 'silent') { return validation; } if (mode === 'validated-only' && !pathState.validated) { return validation; } setFieldError(pathState, fieldResult.errors); return validation; }, { valid: formResult.valid, results: {}, errors: {}, source: formResult.source, } as FormValidationResult, ); if (formResult.values) { results.values = formResult.values; results.source = formResult.source; } keysOf(results.results).forEach(path => { const pathState = findPathState(path); if (!pathState) { return; } if (mode === 'silent') { return; } if (mode === 'validated-only' && !pathState.validated) { return; } setFieldError(pathState, results.results[path]?.errors); }); return results; }, ); function mutateAllPathState(mutation: (state: PathState) => void) { pathStates.value.forEach(mutation); } function findPathState>(path: TPath | PathState) { const normalizedPath = typeof path === 'string' ? normalizeFormPath(path) : path; const pathState = typeof normalizedPath === 'string' ? pathStateLookup.value[normalizedPath] : normalizedPath; return pathState as PathState> | undefined; } function findHoistedPath(path: Path) { const candidates = pathStates.value.filter(state => path.startsWith(toValue(state.path))); return candidates.reduce( (bestCandidate, candidate) => { if (!bestCandidate) { return candidate as PathState>>; } return (candidate.path.length > bestCandidate.path.length ? candidate : bestCandidate) as PathState< PathValue> >; }, undefined as PathState>> | undefined, ); } let UNSET_BATCH: Path[] = []; let PENDING_UNSET: Promise | null; function unsetPathValue>(path: TPath) { UNSET_BATCH.push(path); if (!PENDING_UNSET) { PENDING_UNSET = nextTick(() => { const sortedPaths = [...UNSET_BATCH].sort().reverse(); sortedPaths.forEach(p => { unsetPath(formValues, p); }); UNSET_BATCH = []; PENDING_UNSET = null; }); } return PENDING_UNSET; } function makeSubmissionFactory(onlyControlled: boolean) { return function submitHandlerFactory( fn?: SubmissionHandler, onValidationError?: InvalidSubmissionHandler, ) { return function submissionHandler(e: unknown) { if (e instanceof Event) { e.preventDefault(); e.stopPropagation(); } // Touch all fields mutateAllPathState(s => (s.touched = true)); isSubmitting.value = true; submitCount.value++; return validate() .then(result => { const values = deepCopy(formValues); if (result.valid && typeof fn === 'function') { const controlled = deepCopy(controlledValues.value); let submittedValues = (onlyControlled ? controlled : values) as unknown as TOutput; if (result.values) { submittedValues = result.source === 'schema' ? (result.values as TOutput) : Object.assign({}, submittedValues, result.values); } return fn(submittedValues, { evt: e as Event, controlledValues: controlled, setErrors, setFieldError, setTouched, setFieldTouched, setValues, setFieldValue, resetForm, resetField, }); } if (!result.valid && typeof onValidationError === 'function') { onValidationError({ values, evt: e as Event, errors: result.errors, results: result.results, }); } }) .then( returnVal => { isSubmitting.value = false; return returnVal; }, err => { isSubmitting.value = false; // re-throw the err so it doesn't go silent throw err; }, ); }; }; } const handleSubmitImpl = makeSubmissionFactory(false); const handleSubmit: typeof handleSubmitImpl & { withControlled: typeof handleSubmitImpl } = handleSubmitImpl as any; handleSubmit.withControlled = makeSubmissionFactory(true); function removePathState>(path: TPath, id: number) { const idx = pathStates.value.findIndex(s => { return s.path === path && (Array.isArray(s.id) ? s.id.includes(id) : s.id === id); }); const pathState = pathStates.value[idx]; if (idx === -1 || !pathState) { return; } nextTick(() => { validateField(path, { mode: 'silent', warn: false }); }); if (pathState.multiple && pathState.fieldsCount) { pathState.fieldsCount--; } if (Array.isArray(pathState.id)) { const idIndex = pathState.id.indexOf(id); if (idIndex >= 0) { pathState.id.splice(idIndex, 1); } delete pathState.__flags.pendingUnmount[id]; } if (!pathState.multiple || pathState.fieldsCount <= 0) { pathStates.value.splice(idx, 1); unsetInitialValue(path); rebuildPathLookup(); delete pathStateLookup.value[path]; } } function destroyPath(path: string) { keysOf(pathStateLookup.value).forEach(key => { if (key.startsWith(path)) { delete pathStateLookup.value[key]; } }); pathStates.value = pathStates.value.filter(s => !s.path.startsWith(path)); nextTick(() => { rebuildPathLookup(); }); } const formCtx: PrivateFormContext = { name, formId, values: formValues, controlledValues, errorBag, errors, schema, submitCount, meta, isSubmitting, isValidating, fieldArrays, keepValuesOnUnmount, validateSchema: unref(schema) ? validateSchema : undefined, validate, setFieldError, validateField, setFieldValue, setValues, setErrors, setFieldTouched, setTouched, resetForm, resetField, handleSubmit, defineField, stageInitialValue, unsetInitialValue, setFieldInitialValue, createPathState, getPathState: findPathState, unsetPathValue, removePathState, initialValues: initialValues as Ref, getAllPathStates: () => pathStates.value, destroyPath, isFieldTouched, isFieldDirty, isFieldValid, }; /** * Sets a single field value */ function setFieldValue>( field: T | PathState, value: PathValue | undefined, shouldValidate = true, ) { const clonedValue = deepCopy(value); const path = typeof field === 'string' ? field : (field.path as Path); const pathState = findPathState(path); if (!pathState) { createPathState(path); } setInPath(formValues, path, clonedValue); if (shouldValidate) { validateField(path); } } function forceSetValues(fields: PartialDeep, shouldValidate = true) { // clean up old values keysOf(formValues).forEach(key => { delete formValues[key]; }); // set up new values keysOf(fields).forEach(path => { setFieldValue(path as Path, fields[path], false); }); if (shouldValidate) { validate(); } } /** * Sets multiple fields values */ function setValues(fields: PartialDeep, shouldValidate = true) { merge(formValues, fields); // regenerate the arrays when the form values change fieldArrays.forEach(f => f && f.reset()); if (shouldValidate) { validate(); } } function createModel>( path: MaybeRefOrGetter, shouldValidate?: MaybeRefOrGetter, ) { const pathState = findPathState(toValue(path)) || createPathState(path); return computed({ get() { return pathState.value; }, set(value) { const pathValue = toValue(path); setFieldValue(pathValue, value, toValue(shouldValidate) ?? false); }, }) as Ref>; } /** * Sets the touched meta state on a field */ function setFieldTouched(field: Path | PathState, isTouched: boolean) { const pathState = findPathState(field); if (pathState) { pathState.touched = isTouched; } } function isFieldTouched(field: Path) { const pathState = findPathState(field); if (pathState) { return pathState.touched; } // Find all nested paths and consider their touched state return pathStates.value.filter(s => s.path.startsWith(field)).some(s => s.touched); } function isFieldDirty(field: Path) { const pathState = findPathState(field); if (pathState) { return pathState.dirty; } return pathStates.value.filter(s => s.path.startsWith(field)).some(s => s.dirty); } function isFieldValid(field: Path) { const pathState = findPathState(field); if (pathState) { return pathState.valid; } return pathStates.value.filter(s => s.path.startsWith(field)).every(s => s.valid); } /** * Sets the touched meta state on multiple fields */ function setTouched(fields: Partial> | boolean) { if (typeof fields === 'boolean') { mutateAllPathState(state => { state.touched = fields; }); return; } keysOf(fields).forEach(field => { setFieldTouched(field, !!fields[field]); }); } function resetField(field: Path, state?: Partial) { const newValue = state && 'value' in state ? state.value : getFromPath(initialValues.value, field); const pathState = findPathState(field); if (pathState) { pathState.__flags.pendingReset = true; } setFieldInitialValue(field, deepCopy(newValue), true); setFieldValue(field, newValue as PathValue, false); setFieldTouched(field, state?.touched ?? false); setFieldError(field, state?.errors || []); nextTick(() => { if (pathState) { pathState.__flags.pendingReset = false; } }); } /** * Resets all fields */ function resetForm(resetState?: Partial>, opts?: ResetFormOpts) { let newValues = deepCopy(resetState?.values ? resetState.values : originalInitialValues.value); newValues = opts?.force ? newValues : merge(originalInitialValues.value, newValues); setInitialValues(newValues, { force: opts?.force }); mutateAllPathState(state => { state.__flags.pendingReset = true; state.validated = false; state.touched = resetState?.touched?.[toValue(state.path) as Path] || false; setFieldValue(toValue(state.path) as Path, getFromPath(newValues, toValue(state.path)), false); setFieldError(toValue(state.path) as Path, undefined); }); opts?.force ? forceSetValues(newValues, false) : setValues(newValues, false); setErrors(resetState?.errors || {}); submitCount.value = resetState?.submitCount || 0; nextTick(() => { validate({ mode: 'silent' }); mutateAllPathState(state => { state.__flags.pendingReset = false; }); }); } async function validate(opts?: Partial): Promise> { const mode = opts?.mode || 'force'; if (mode === 'force') { mutateAllPathState(f => (f.validated = true)); } if (formCtx.validateSchema) { return formCtx.validateSchema(mode); } isValidating.value = true; // No schema, each field is responsible to validate itself const validations = await Promise.all( pathStates.value.map(state => { if (!state.validate) { return Promise.resolve({ key: toValue(state.path), valid: true, errors: [], value: undefined, }); } return state.validate(opts).then(result => { return { key: toValue(state.path), valid: result.valid, errors: result.errors, value: result.value, }; }); }), ); isValidating.value = false; const results: Partial> = {}; const errors: Partial> = {}; const values: Partial = {}; for (const validation of validations) { results[validation.key as Path] = { valid: validation.valid, errors: validation.errors, }; if (validation.value) { setInPath(values, validation.key, validation.value); } if (validation.errors.length) { errors[validation.key as Path] = validation.errors[0]; } } return { valid: validations.every(r => r.valid), results, errors, values, source: 'fields', }; } async function validateField>( path: TPath, opts?: Partial, ): Promise> { const state = findPathState(path); if (state && opts?.mode !== 'silent') { state.validated = true; } if (schema) { const { results }: FormValidationResult = await validateSchema(opts?.mode || 'validated-only'); return results[path] || { errors: [], valid: true }; } if (state?.validate) { return state.validate(opts); } const shouldWarn = !state && (opts?.warn ?? true); if (shouldWarn) { if (__DEV__) { warn(`field with path ${path} was not found`); } } return Promise.resolve({ errors: [], valid: true }); } function unsetInitialValue(path: string) { unsetPath(initialValues.value, path); } /** * Sneaky function to set initial field values */ function stageInitialValue(path: string, value: unknown, updateOriginal = false) { setFieldInitialValue(path, value); setInPath(formValues, path, value); if (updateOriginal && !opts?.initialValues) { setInPath(originalInitialValues.value, path, deepCopy(value)); } } function setFieldInitialValue(path: string, value: unknown, updateOriginal = false) { setInPath(initialValues.value, path, deepCopy(value)); if (updateOriginal) { setInPath(originalInitialValues.value, path, deepCopy(value)); } } async function _validateSchema(): Promise> { const schemaValue = unref(schema); if (!schemaValue) { return { valid: true, results: {}, errors: {}, source: 'none' }; } isValidating.value = true; const formResult = isStandardSchema(schemaValue) ? await validateStandardSchema(schemaValue, formValues) : await validateObjectSchema(schemaValue as RawFormSchema, formValues, { names: fieldNames.value, bailsMap: fieldBailsMap.value, }); isValidating.value = false; return formResult; } const submitForm = handleSubmit((_, { evt }) => { if (isFormSubmitEvent(evt)) { evt.target.submit(); } }); // Trigger initial validation onMounted(() => { if (opts?.initialErrors) { setErrors(opts.initialErrors); } if (opts?.initialTouched) { setTouched(opts.initialTouched); } // if validate on mount was enabled if (opts?.validateOnMount) { validate(); return; } // otherwise run initial silent validation through schema if available // the useField should skip their own silent validation if a yup schema is present if (formCtx.validateSchema) { formCtx.validateSchema('silent'); } }); if (isRef(schema)) { watch(schema, () => { formCtx.validateSchema?.('validated-only'); }); } // Provide injections provide(FormContextKey, formCtx as PrivateFormContext); if (__DEV__) { registerFormWithDevTools(formCtx as PrivateFormContext); watch( () => ({ errors: errorBag.value, ...meta.value, values: formValues, isSubmitting: isSubmitting.value, isValidating: isValidating.value, submitCount: submitCount.value, }), refreshInspector, { deep: true, }, ); } function defineField< TPath extends Path, TValue = PathValue, TExtras extends GenericObject = GenericObject, >( path: MaybeRefOrGetter, config?: Partial> | LazyInputBindsConfig, ) { const label = isCallable(config) ? undefined : config?.label; const pathState = (findPathState(toValue(path)) || createPathState(path, { label })) as PathState; const evalConfig = () => (isCallable(config) ? config(omit(pathState, PRIVATE_PATH_STATE_KEYS)) : config || {}); function onBlur() { pathState.touched = true; const validateOnBlur = evalConfig().validateOnBlur ?? getConfig().validateOnBlur; if (validateOnBlur) { validateField(toValue(pathState.path) as Path); } } function onInput() { const validateOnInput = evalConfig().validateOnInput ?? getConfig().validateOnInput; if (validateOnInput) { nextTick(() => { validateField(toValue(pathState.path) as Path); }); } } function onChange() { const validateOnChange = evalConfig().validateOnChange ?? getConfig().validateOnChange; if (validateOnChange) { nextTick(() => { validateField(toValue(pathState.path) as Path); }); } } const props = computed(() => { const base: BaseFieldProps = { onChange, onInput, onBlur, }; if (isCallable(config)) { return { ...base, ...(config(omit(pathState, PRIVATE_PATH_STATE_KEYS)).props || {}), } as BaseFieldProps & TExtras; } if (config?.props) { return { ...base, ...config.props(omit(pathState, PRIVATE_PATH_STATE_KEYS)), } as BaseFieldProps & TExtras; } return base as BaseFieldProps & TExtras; }); const model = createModel( path, () => evalConfig().validateOnModelUpdate ?? getConfig()?.validateOnModelUpdate ?? true, ); return [model, props] as [Ref, Ref]; } const ctx: FormContext = { ...formCtx, values: readonly(formValues) as TValues, handleReset: () => resetForm(), submitForm, }; provide(PublicFormContextKey, ctx); return ctx; } /** * Manages form meta aggregation */ function useFormMeta>( pathsState: Ref[]>, currentValues: TValues, initialValues: MaybeRef>, errors: Ref>, ) { const MERGE_STRATEGIES: Record, 'touched' | 'pending' | 'valid'>, 'every' | 'some'> = { touched: 'some', pending: 'some', valid: 'every', }; const isDirty = computed(() => { return !isEqual(currentValues, unref(initialValues)); }); function calculateFlags() { const states = pathsState.value; return keysOf(MERGE_STRATEGIES).reduce( (acc, flag) => { const mergeMethod = MERGE_STRATEGIES[flag]; acc[flag] = states[mergeMethod](s => s[flag]); return acc; }, {} as Record, 'initialValue'>, boolean>, ); } const flags = reactive(calculateFlags()); watchEffect(() => { const value = calculateFlags(); flags.touched = value.touched; flags.valid = value.valid; flags.pending = value.pending; }); return computed(() => { return { initialValues: unref(initialValues) as Partial, ...flags, valid: flags.valid && !keysOf(errors.value).length, dirty: isDirty.value, }; }); } interface SetFormInitialValuesOpts { updateFields?: boolean; force?: boolean; } /** * Manages the initial values prop */ function useFormInitialValues( pathsState: Ref[]>, formValues: TValues, opts?: FormOptions, ) { const values = resolveInitialValues(opts) as PartialDeep; // these are the mutable initial values as the fields are mounted/unmounted const initialValues = ref(values) as Ref>; // these are the original initial value as provided by the user initially, they don't keep track of conditional fields // this is important because some conditional fields will overwrite the initial values for other fields who had the same name // like array fields, any push/insert operation will overwrite the initial values because they "create new fields" // so these are the values that the reset function should use // these only change when the user explicitly changes the initial values or when the user resets them with new values. const originalInitialValues = ref>(deepCopy(values)) as Ref>; function setInitialValues(values: PartialDeep, opts?: SetFormInitialValuesOpts) { if (opts?.force) { initialValues.value = deepCopy(values); originalInitialValues.value = deepCopy(values); } else { initialValues.value = merge(deepCopy(initialValues.value) || {}, deepCopy(values)); originalInitialValues.value = merge(deepCopy(originalInitialValues.value) || {}, deepCopy(values)); } if (!opts?.updateFields) { return; } // update the pristine non-touched fields // those are excluded because it's unlikely you want to change the form values using initial values // we mostly watch them for API population or newly inserted fields // if the user API is taking too much time before user interaction they should consider disabling or hiding their inputs until the values are ready pathsState.value.forEach(state => { const wasTouched = state.touched; if (wasTouched) { return; } const newValue = getFromPath(initialValues.value, toValue(state.path)); setInPath(formValues, toValue(state.path), deepCopy(newValue)); }); } return { initialValues, originalInitialValues, setInitialValues, }; } function mergeValidationResults( a: ValidationResult, b?: ValidationResult, ): ValidationResult { if (!b) { return a; } return { valid: a.valid && b.valid, errors: [...a.errors, ...b.errors], }; } export function useFormContext< TValues extends GenericObject = GenericObject, TOutput extends GenericObject = TValues, >(): FormContext { return inject(PublicFormContextKey) as FormContext; } ================================================ FILE: packages/vee-validate/src/useFormErrors.ts ================================================ import { computed } from 'vue'; import { FormContextKey } from './symbols'; import { FormErrors } from './types'; import { injectWithSelf, warn } from './utils'; /** * Gives access to all form errors */ export function useFormErrors = Record>() { const form = injectWithSelf(FormContextKey); if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } return computed(() => { return (form?.errors.value || {}) as FormErrors; }); } ================================================ FILE: packages/vee-validate/src/useFormValues.ts ================================================ import { computed } from 'vue'; import { FormContextKey } from './symbols'; import { FormContext } from './types'; import { injectWithSelf, warn } from './utils'; /** * Gives access to a form's values */ export function useFormValues = Record>() { const form = injectWithSelf(FormContextKey) as FormContext | undefined; if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } return computed(() => { return form?.values || ({} as Partial); }); } ================================================ FILE: packages/vee-validate/src/useIsFieldDirty.ts ================================================ import { computed, MaybeRefOrGetter } from 'vue'; import { resolveFieldOrPathState } from './utils'; /** * If a field is dirty or not */ export function useIsFieldDirty(path?: MaybeRefOrGetter) { const fieldOrPath = resolveFieldOrPathState(path); return computed(() => { if (!fieldOrPath) { return false; } return ('meta' in fieldOrPath ? fieldOrPath.meta.dirty : fieldOrPath?.value?.dirty) ?? false; }); } ================================================ FILE: packages/vee-validate/src/useIsFieldTouched.ts ================================================ import { MaybeRefOrGetter, computed } from 'vue'; import { resolveFieldOrPathState } from './utils'; /** * If a field is touched or not */ export function useIsFieldTouched(path?: MaybeRefOrGetter) { const fieldOrPath = resolveFieldOrPathState(path); return computed(() => { if (!fieldOrPath) { return false; } return ('meta' in fieldOrPath ? fieldOrPath.meta.touched : fieldOrPath?.value?.touched) ?? false; }); } ================================================ FILE: packages/vee-validate/src/useIsFieldValid.ts ================================================ import { computed, MaybeRefOrGetter } from 'vue'; import { resolveFieldOrPathState } from './utils'; /** * If a field is validated and is valid */ export function useIsFieldValid(path?: MaybeRefOrGetter) { const fieldOrPath = resolveFieldOrPathState(path); return computed(() => { if (!fieldOrPath) { return false; } return ('meta' in fieldOrPath ? fieldOrPath.meta.valid : fieldOrPath?.value?.valid) ?? false; }); } ================================================ FILE: packages/vee-validate/src/useIsFormDirty.ts ================================================ import { computed } from 'vue'; import { FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * If the form is dirty or not */ export function useIsFormDirty() { const form = injectWithSelf(FormContextKey); if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } return computed(() => { return form?.meta.value.dirty ?? false; }); } ================================================ FILE: packages/vee-validate/src/useIsFormTouched.ts ================================================ import { computed } from 'vue'; import { FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * If the form is touched or not */ export function useIsFormTouched() { const form = injectWithSelf(FormContextKey); if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } return computed(() => { return form?.meta.value.touched ?? false; }); } ================================================ FILE: packages/vee-validate/src/useIsFormValid.ts ================================================ import { computed } from 'vue'; import { FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * If the form has been validated and is valid */ export function useIsFormValid() { const form = injectWithSelf(FormContextKey); if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } return computed(() => { return form?.meta.value.valid ?? false; }); } ================================================ FILE: packages/vee-validate/src/useIsSubmitting.ts ================================================ import { computed } from 'vue'; import { FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * If the form is submitting or not */ export function useIsSubmitting() { const form = injectWithSelf(FormContextKey); if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } return computed(() => { return form?.isSubmitting.value ?? false; }); } ================================================ FILE: packages/vee-validate/src/useIsValidating.ts ================================================ import { computed } from 'vue'; import { FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * If the form is validating or not */ export function useIsValidating() { const form = injectWithSelf(FormContextKey); if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } return computed(() => { return form?.isValidating.value ?? false; }); } ================================================ FILE: packages/vee-validate/src/useResetForm.ts ================================================ import { FormContextKey } from './symbols'; import { FormState, ResetFormOpts } from './types'; import { injectWithSelf, warn } from './utils'; export function useResetForm = Record>() { const form = injectWithSelf(FormContextKey); if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } return function resetForm(state?: Partial>, opts?: ResetFormOpts) { if (!form) { return; } return form.resetForm(state, opts); }; } ================================================ FILE: packages/vee-validate/src/useSetFieldError.ts ================================================ import { inject, MaybeRefOrGetter, toValue } from 'vue'; import { FieldContextKey, FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * Sets a field's error message */ export function useSetFieldError(path?: MaybeRefOrGetter) { const form = injectWithSelf(FormContextKey); // We don't want to use self injected context as it doesn't make sense const field = path ? undefined : inject(FieldContextKey); return function setFieldError(message: string | string[] | undefined) { if (path && form) { form.setFieldError(toValue(path), message); return; } if (field) { field.setErrors(message || []); return; } if (__DEV__) { warn( `Could not set error message since there is no form context or a field named "${toValue( path, )}", did you forget to call "useField" or "useForm"?`, ); } }; } ================================================ FILE: packages/vee-validate/src/useSetFieldTouched.ts ================================================ import { inject, MaybeRefOrGetter, toValue } from 'vue'; import { FieldContextKey, FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * Sets a field's touched meta state */ export function useSetFieldTouched(path?: MaybeRefOrGetter) { const form = injectWithSelf(FormContextKey); // We don't want to use self injected context as it doesn't make sense const field = path ? undefined : inject(FieldContextKey); return function setFieldTouched(touched: boolean) { if (path && form) { form.setFieldTouched(toValue(path), touched); return; } if (field) { field.setTouched(touched); return; } if (__DEV__) { warn( `Could not set touched state since there is no form context or a field named "${toValue( path, )}", did you forget to call "useField" or "useForm"?`, ); } }; } ================================================ FILE: packages/vee-validate/src/useSetFieldValue.ts ================================================ import { inject, MaybeRefOrGetter, toValue } from 'vue'; import { FieldContextKey, FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * Sets a field's value */ export function useSetFieldValue(path?: MaybeRefOrGetter) { const form = injectWithSelf(FormContextKey); // We don't want to use self injected context as it doesn't make sense const field = path ? undefined : inject(FieldContextKey); return function setFieldValue(value: TValue, shouldValidate = true) { if (path && form) { form.setFieldValue(toValue(path), value, shouldValidate); return; } if (field) { field.setValue(value, shouldValidate); return; } if (__DEV__) { warn( `Could not set value since there is no form context or a field named "${toValue( path, )}", did you forget to call "useField" or "useForm"?`, ); } }; } ================================================ FILE: packages/vee-validate/src/useSetFormErrors.ts ================================================ import { FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * Sets multiple fields errors */ export function useSetFormErrors() { const form = injectWithSelf(FormContextKey); function setFormErrors(fields: Record) { if (form) { form.setErrors(fields); return; } if (__DEV__) { warn( `Could not set errors because a form was not detected, did you forget to use "useForm" in a parent component?`, ); } } return setFormErrors; } ================================================ FILE: packages/vee-validate/src/useSetFormTouched.ts ================================================ import { FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * Sets multiple fields touched or all fields in the form */ export function useSetFormTouched() { const form = injectWithSelf(FormContextKey); function setFormTouched(fields: Record | boolean) { if (form) { form.setTouched(fields); return; } if (__DEV__) { warn( `Could not set touched state because a form was not detected, did you forget to use "useForm" in a parent component?`, ); } } return setFormTouched; } ================================================ FILE: packages/vee-validate/src/useSetFormValues.ts ================================================ import { FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * Sets multiple fields values */ export function useSetFormValues = Record>() { const form = injectWithSelf(FormContextKey); function setFormValues(fields: TValues, shouldValidate = true) { if (form) { form.setValues(fields, shouldValidate); return; } if (__DEV__) { warn( `Could not set form values because a form was not detected, did you forget to use "useForm" in a parent component?`, ); } } return setFormValues; } ================================================ FILE: packages/vee-validate/src/useSubmitCount.ts ================================================ import { computed } from 'vue'; import { FormContextKey } from './symbols'; import { injectWithSelf, warn } from './utils'; /** * The number of form's submission count */ export function useSubmitCount() { const form = injectWithSelf(FormContextKey); if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } return computed(() => { return form?.submitCount.value ?? 0; }); } ================================================ FILE: packages/vee-validate/src/useSubmitForm.ts ================================================ import { FormContextKey } from './symbols'; import { FormContext, SubmissionHandler } from './types'; import { injectWithSelf, warn } from './utils'; export function useSubmitForm = Record>( cb: SubmissionHandler, ) { const form = injectWithSelf(FormContextKey) as FormContext | undefined; if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } const onSubmit = form ? form.handleSubmit(cb) : undefined; return function submitForm(e?: Event) { if (!onSubmit) { return; } return onSubmit(e); }; } ================================================ FILE: packages/vee-validate/src/useValidateField.ts ================================================ import { MaybeRefOrGetter, inject, toValue, unref } from 'vue'; import { FieldContextKey, FormContextKey } from './symbols'; import { ValidationResult } from './types'; import { injectWithSelf, warn } from './utils'; /** * Validates a single field */ export function useValidateField(path?: MaybeRefOrGetter) { const form = injectWithSelf(FormContextKey); const field = path ? undefined : inject(FieldContextKey); return function validateField(): Promise> { if (field) { return field.validate() as Promise>; } if (form && path) { return form?.validateField(toValue(path)); } if (__DEV__) { warn(`field with name ${unref(path)} was not found`); } return Promise.resolve({ errors: [], valid: true, }); }; } ================================================ FILE: packages/vee-validate/src/useValidateForm.ts ================================================ import { FormContextKey } from './symbols'; import { FormContext, FormValidationResult } from './types'; import { injectWithSelf, warn } from './utils'; /** * Validate multiple fields */ export function useValidateForm = Record>() { const form = injectWithSelf(FormContextKey) as FormContext | undefined; if (!form) { if (__DEV__) { warn('No vee-validate or `useForm` was detected in the component tree'); } } return function validateField(): Promise> { if (!form) { return Promise.resolve({ results: {}, errors: {}, valid: true, source: 'none' }); } return form.validate(); }; } ================================================ FILE: packages/vee-validate/src/utils/assertions.ts ================================================ import { Locator } from '../types'; import { isCallable, isObject } from '../../../shared'; import { IS_ABSENT } from '../symbols'; import { StandardSchemaV1 } from '@standard-schema/spec'; export const isClient = typeof window !== 'undefined'; export function isLocator(value: unknown): value is Locator { return isCallable(value) && !!(value as Locator).__locatorRef; } export function isStandardSchema(value: unknown): value is StandardSchemaV1 { return isObject(value) && '~standard' in (value as unknown as StandardSchemaV1); } export function hasCheckedAttr(type: unknown) { return type === 'checkbox' || type === 'radio'; } export function isContainerValue(value: unknown): value is Record { return isObject(value) || Array.isArray(value); } /** * True if the value is an empty object or array */ export function isEmptyContainer(value: unknown): boolean { 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 */ export function isNotNestedPath(path: string) { return /^\[.+\]$/i.test(path); } /** * Checks if an element is a native HTML5 multi-select input element */ export function isNativeMultiSelect(el: HTMLElement): el is HTMLSelectElement { return isNativeSelect(el) && el.multiple; } /** * Checks if an element is a native HTML5 select input element */ export function isNativeSelect(el: HTMLElement): el is HTMLSelectElement { return el.tagName === 'SELECT'; } /** * Checks if a tag name with attrs object will render a native multi-select element */ export function isNativeMultiSelectNode(tag: string, attrs: Record) { // 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 as boolean) && !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 */ export function shouldHaveValueBinding(tag: string, attrs: Record) { return !isNativeMultiSelectNode(tag, attrs) && attrs.type !== 'file' && !hasCheckedAttr(attrs.type); } export function isFormSubmitEvent(evt: unknown): evt is Event & { target: HTMLFormElement } { return isEvent(evt) && (evt as any).target && 'submit' in (evt as any).target; } export function isEvent(evt: unknown): evt is Event { 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 as Event).srcElement) { return true; } return false; } export function isPropPresent(obj: Record, prop: string) { 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. * */ export function isEqual(a: any, b: any) { 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 as any).length; if (length != (b as any).length) return false; for (i = length; i-- !== 0; ) if ((a as any)[i] !== (b as any)[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: Record) { return Object.fromEntries(Object.entries(a).filter(([, value]) => value !== undefined)); } export function isFile(a: unknown): a is File { if (!isClient) { return false; } return a instanceof File; } ================================================ FILE: packages/vee-validate/src/utils/common.ts ================================================ import { computed, getCurrentInstance, inject, InjectionKey, ref, Ref, warn as vueWarning, watch, nextTick, MaybeRefOrGetter, toValue, } from 'vue'; import { klona as deepCopy } from 'klona/full'; import { isIndex, isNullOrUndefined, isObject, toNumber } from '../../../shared'; import { isContainerValue, isEmptyContainer, isEqual, isNotNestedPath } from './assertions'; import { GenericObject, IssueCollection, MaybePromise } from '../types'; import { FormContextKey, FieldContextKey } from '../symbols'; import { StandardSchemaV1 } from '@standard-schema/spec'; import { getDotPath } from '@standard-schema/utils'; export function cleanupNonNestedPath(path: string) { if (isNotNestedPath(path)) { return path.replace(/\[|\]/gi, ''); } return path; } type NestedRecord = Record | { [k: string]: NestedRecord }; /** * Gets a nested property value from an object */ export function getFromPath(object: NestedRecord | undefined, path: string): TValue | undefined; export function getFromPath( object: NestedRecord | undefined, path: string, fallback?: TFallback, ): TValue | TFallback; export function getFromPath( object: NestedRecord | undefined, path: string, fallback?: TFallback, ): TValue | TFallback | undefined { if (!object) { return fallback; } if (isNotNestedPath(path)) { return object[cleanupNonNestedPath(path)] as TValue | undefined; } const resolvedValue = (path || '') .split(/\.|\[(\d+)\]/) .filter(Boolean) .reduce((acc, propKey) => { if (isContainerValue(acc) && propKey in acc) { return acc[propKey]; } return fallback; }, object as unknown); return resolvedValue as TValue | undefined; } /** * Sets a nested property value in a path, creates the path properties if it doesn't exist */ export function setInPath(object: NestedRecord, path: string, value: unknown): void { if (isNotNestedPath(path)) { object[cleanupNonNestedPath(path)] = value; return; } const keys = path.split(/\.|\[(\d+)\]/).filter(Boolean); let acc: Record = 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]] as Record; } } function unset(object: Record | unknown[], key: string | number) { if (Array.isArray(object) && isIndex(key)) { object.splice(Number(key), 1); return; } if (isObject(object)) { delete object[key]; } } /** * Removes a nested property from object */ export function unsetPath(object: NestedRecord, path: string): void { if (isNotNestedPath(path)) { delete object[cleanupNonNestedPath(path)]; return; } const keys = path.split(/\.|\[(\d+)\]/).filter(Boolean); let acc: Record = 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]] as Record; } const pathValues: (unknown | Record)[] = 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] as Record, keys[i - 1]); } } /** * A typed version of Object.keys */ export function keysOf>(record: TRecord): (keyof TRecord)[] { return Object.keys(record); } // Uses same component provide as its own injections // Due to changes in https://github.com/vuejs/vue-next/pull/2424 export function injectWithSelf(symbol: InjectionKey, def: T | undefined = undefined): T | undefined { const vm = getCurrentInstance() as any; return vm?.provides[symbol as any] || inject(symbol, def); } export function warn(message: string) { vueWarning(`[vee-validate]: ${message}`); } export function resolveNextCheckboxValue(currentValue: T, checkedValue: T, uncheckedValue: T): T; export function resolveNextCheckboxValue(currentValue: T[], checkedValue: T, uncheckedValue: T): T[]; export function resolveNextCheckboxValue(currentValue: T | T[], checkedValue: T, uncheckedValue: T): T | T[] { 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; } // https://github.com/bameyrick/throttle-typescript type ThrottledFunction any> = (...args: Parameters) => ReturnType; /** * Creates a throttled function that only invokes the provided function (`func`) at most once per within a given number of milliseconds * (`limit`) */ export function throttle any>(func: T, limit: number): ThrottledFunction { let inThrottle: boolean; let lastResult: ReturnType; return function (this: any, ...args: any[]): ReturnType { // 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; }; } export function debounceAsync Promise, TResult = ReturnType>( inner: TFunction, ms = 0, ): (...args: Parameters) => Promise { let timer: number | null = null; let resolves: any[] = []; return function (...args: Parameters) { // 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 as any)); resolves.forEach(r => r(result)); resolves = []; }, ms); return new Promise(resolve => resolves.push(resolve)); }; } export function applyModelModifiers(value: TValue, modifiers: unknown): TValue { if (!isObject(modifiers)) { return value; } if (modifiers.number) { return toNumber(value as string) as TValue; } return value; } export function withLatest< TFunction extends (...args: any[]) => Promise, TResult = Awaited>, >(fn: TFunction, onDone: (result: TResult, args: Parameters) => TResult) { let latestRun: Promise | undefined; return async function runLatest(...args: Parameters) { const pending = fn(...args); latestRun = pending; const result = await pending; if (pending !== latestRun) { return result; } latestRun = undefined; return onDone(result, args); }; } export function computedDeep({ get, set }: { get(): TValue; set(value: TValue): void }): Ref { const baseRef = ref(deepCopy(get())) as Ref; watch( get, newValue => { if (isEqual(newValue, baseRef.value)) { return; } baseRef.value = deepCopy(newValue); }, { deep: true, }, ); watch( baseRef, newValue => { if (isEqual(newValue, get())) { return; } set(deepCopy(newValue)); }, { deep: true, }, ); return baseRef; } export function normalizeErrorItem(message: string | string[] | null | undefined) { return Array.isArray(message) ? message : message ? [message] : []; } export function resolveFieldOrPathState(path?: MaybeRefOrGetter) { const form = injectWithSelf(FormContextKey); const state = path ? computed(() => form?.getPathState(toValue(path))) : undefined; const field = path ? undefined : inject(FieldContextKey); if (!field && !state?.value) { if (__DEV__) { warn(`field with name ${toValue(path)} was not found`); } } return state || field; } export function omit(obj: TObj, keys: (keyof GenericObject)[]) { const target = {} as TObj; for (const key in obj) { if (!keys.includes(key)) { target[key] = obj[key]; } } return target; } export function debounceNextTick< TFunction extends (...args: any[]) => MaybePromise, TResult = ReturnType, >(inner: TFunction): (...args: Parameters) => Promise { let lastTick: Promise | null = null; let resolves: any[] = []; return function (...args: Parameters) { // Run the function after a certain amount of time const thisTick = 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 as any)); resolves.forEach(r => r(result)); resolves = []; lastTick = null; }); lastTick = thisTick; return new Promise(resolve => resolves.push(resolve)); }; } function _combineIssueItems( items: TItem[] | readonly TItem[], getPath: (item: TItem) => string, ) { const issueMap: Record = {}; for (const item of items) { const path = getPath(item); if (!issueMap[path]) { issueMap[path] = { path, messages: [], }; } if ('messages' in item) { issueMap[path].messages.push(...item.messages); } else { issueMap[path].messages.push(item.message); } } return Object.values(issueMap); } /** * Aggregates standard schema issues by path. */ export function combineStandardIssues( issues: StandardSchemaV1.Issue[] | readonly StandardSchemaV1.Issue[], ): IssueCollection[] { return _combineIssueItems(issues, issue => (issue.path ? getDotPath(issue) ?? '' : '')); } ================================================ FILE: packages/vee-validate/src/utils/events.ts ================================================ import { hasCheckedAttr, isNativeMultiSelect, isNativeSelect, isEvent } from './assertions'; import { getBoundValue, hasValueBinding } from './vnode'; function parseInputValue(el: HTMLInputElement) { if (el.type === 'range') { return Number.isNaN(el.valueAsNumber) ? el.value : el.valueAsNumber; } return el.value; } export function normalizeEventValue(value: Event | unknown): unknown { if (!isEvent(value)) { return value; } const input = value.target as HTMLInputElement; // 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); } ================================================ FILE: packages/vee-validate/src/utils/index.ts ================================================ export * from './assertions'; export * from './common'; export * from './events'; export * from './rules'; export * from './vnode'; ================================================ FILE: packages/vee-validate/src/utils/rules.ts ================================================ import { isLocator } from './assertions'; import { getFromPath, keysOf } from './common'; import { Locator } from '../types'; import { isObject } from '../../../shared'; /** * Normalizes the given rules expression. */ export function normalizeRules( rules: undefined | string | Record>, ): Record> { const acc: Record> = {}; 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 as typeof acc; } 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: unknown) { if (params === true) { return [] as unknown[]; } if (Array.isArray(params)) { return params as unknown[]; } if (isObject(params)) { return params; } return [params]; } function buildParams(provided: unknown[] | Record) { const mapValueToLocator = (value: unknown) => { // 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; }, {} as Record, ); } /** * Parses a rule string expression. */ export const parseRule = (rule: string) => { let params: string[] = []; const name = rule.split(':')[0]; if (rule.includes(':')) { params = rule.split(':').slice(1).join(':').split(','); } return { name, params }; }; function createLocator(value: string): Locator { const locator: Locator = (crossTable: Record) => { const val = getFromPath(crossTable, value) ?? crossTable[value]; return val; }; locator.__locatorRef = value; return locator; } export function extractLocators(params: Record | unknown[]): Locator[] { if (Array.isArray(params)) { return params.filter(isLocator); } return keysOf(params) .filter(key => isLocator(params[key])) .map(key => params[key] as unknown as Locator); } ================================================ FILE: packages/vee-validate/src/utils/vnode.ts ================================================ import { SetupContext } from 'vue'; type HTMLElementWithValueBinding = HTMLElement & { _value: unknown }; export function normalizeChildren( tag: string | undefined | null, context: SetupContext, slotProps: () => Record, ) { if (!context.slots.default) { return context.slots.default; } if (typeof tag === 'string' || !tag) { return context.slots.default(slotProps()); } return { default: () => context.slots.default?.(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 */ export function getBoundValue(el: HTMLElement): unknown { 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 */ export function hasValueBinding(el: HTMLElement): el is HTMLElementWithValueBinding { return '_value' in el; } ================================================ FILE: packages/vee-validate/src/validate.ts ================================================ import { resolveRule } from './defineRule'; import { klona as deepCopy } from 'klona/full'; import { isLocator, normalizeRules, keysOf, getFromPath, isStandardSchema, combineStandardIssues } from './utils'; import { getConfig } from './config'; import { ValidationResult, GenericValidateFunction, FlattenAndMapPathsValidationResult, FormValidationResult, RawFormSchema, GenericObject, Path, } from './types'; import { isCallable, FieldValidationMetaInfo } from '../../shared'; import { StandardSchemaV1 } from '@standard-schema/spec'; /** * Used internally */ interface FieldValidationContext { name: string; label?: string; rules: | GenericValidateFunction | GenericValidateFunction[] | StandardSchemaV1 | string | Record; bails: boolean; formData: Record; } interface ValidationOptions { name?: string; label?: string; values?: Record; bails?: boolean; } /** * Validates a value against the rules. */ export async function validate( value: TInput, rules: | string | Record | GenericValidateFunction | GenericValidateFunction[] | StandardSchemaV1, options: ValidationOptions = {}, ): Promise> { const shouldBail = options?.bails; const field: FieldValidationContext = { name: options?.name || '{field}', rules, label: options?.label, bails: shouldBail ?? true, formData: options?.values || {}, }; const result = await _validate(field, value); return { ...result, valid: !result.errors.length, }; } /** * Starts the validation process. */ async function _validate( field: FieldValidationContext, value: TInput, ) { const rules = field.rules; if (isStandardSchema(rules)) { return validateFieldWithStandardSchema(value, { ...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: ReturnType[] = []; 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 = { ...field, rules: normalizeRules(rules), }; const errors: ReturnType[] = []; 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, }; } /** * Handles yup validation */ async function validateFieldWithStandardSchema( value: unknown, context: FieldValidationContext & { rules: StandardSchemaV1 }, ) { const result = await context.rules['~standard'].validate(value); if (!result.issues) { return { value: result.value, errors: [], }; } const messages: string[] = []; for (const error of result.issues) { if (error.message) { messages.push(error.message); } } return { errors: messages, }; } /** * Tests a single input value against a rule. */ async function _test( field: FieldValidationContext, value: unknown, rule: { name: string; params: Record | unknown[] }, ) { 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: FieldValidationMetaInfo = { field: field.label || field.name, name: field.name, label: field.label, value, form: field.formData, rule: { ...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: FieldValidationMetaInfo) { const message = getConfig().generateMessage; if (!message) { return 'Field is invalid'; } return message(fieldCtx); } function fillTargetValues(params: unknown[] | Record, crossTable: Record) { const normalize = (value: unknown) => { 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; }, {} as Record, ); } export async function validateStandardSchema( schema: StandardSchemaV1, values: TValues, ): Promise> { const validationResult = await schema['~standard'].validate(deepCopy(values)); const results: Partial> = {}; const errors: Partial, string>> = {}; if (!validationResult.issues) { return { valid: true, results: {}, errors: {}, values: validationResult.value, source: 'schema', }; } const combinedIssues = combineStandardIssues(validationResult.issues || []); for (const error of combinedIssues) { const messages = error.messages; const path = error.path as Path; results[path] = { valid: !messages.length, errors: messages }; if (messages.length) { errors[path] = messages[0]; } } return { valid: !combinedIssues.length, results, errors, values: undefined, source: 'schema', }; } export async function validateObjectSchema( schema: RawFormSchema, values: TValues | undefined, opts?: Partial<{ names: Record; bailsMap: Record }>, ): Promise> { const paths = keysOf(schema) as Path[]; const validations = paths.map(async path => { const strings = opts?.names?.[path]; const fieldResult = await validate(getFromPath(values as any, path), schema[path], { name: strings?.name || path, label: strings?.label, values: values as any, bails: opts?.bailsMap?.[path] ?? true, }); return { ...fieldResult, path, }; }); let isAllValid = true; const validationResults = await Promise.all(validations); const results: Partial> = {}; const errors: Partial, string>> = {}; 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', }; } ================================================ FILE: packages/vee-validate/tests/.eslintrc.json ================================================ { "rules": { "@typescript-eslint/no-explicit-any": "off" } } ================================================ FILE: packages/vee-validate/tests/ErrorMessage.spec.ts ================================================ import { defineRule, ErrorMessage } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; describe('', () => { const REQUIRED_MESSAGE = `This field is required`; defineRule('required', (value: string) => { if (!value) { return REQUIRED_MESSAGE; } return true; }); test('does not render if no errors are present', async () => { const wrapper = mountWithHoc({ components: { ErrorMessage, }, template: ` `, }); await flushPromises(); expect(wrapper.$el.querySelector('#error')).toBe(null); }); test('shows error messages for a field', async () => { const wrapper = mountWithHoc({ components: { ErrorMessage, }, template: ` `, }); const input = wrapper.$el.querySelector('input'); setValue(input, ''); await flushPromises(); const error = wrapper.$el.querySelector('#error'); expect(error.tagName).toBe('SPAN'); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); setValue(input, '12'); await flushPromises(); // was removed expect(wrapper.$el.querySelector('#error')).toBe(null); }); test('render with "as" prop', async () => { const wrapper = mountWithHoc({ components: { ErrorMessage, }, template: ` `, }); wrapper.$el.querySelector('button').click(); await flushPromises(); const error = wrapper.$el.querySelector('#error'); expect(error.tagName).toBe('DIV'); expect(error.textContent).toBe(REQUIRED_MESSAGE); }); test('render with "as" prop and child nodes', async () => { const wrapper = mountWithHoc({ components: { ErrorMessage, }, template: ` icon {{ message }} `, }); wrapper.$el.querySelector('button').click(); await flushPromises(); const error = wrapper.$el.querySelector('#error'); expect(error.tagName).toBe('DIV'); expect(error.textContent).toContain(REQUIRED_MESSAGE); }); test('render with scoped slots', async () => { const wrapper = mountWithHoc({ components: { ErrorMessage, }, template: `

{{ message }}

`, }); wrapper.$el.querySelector('button').click(); await flushPromises(); const error = wrapper.$el.querySelector('#error'); expect(error.tagName).toBe('P'); expect(error.textContent).toBe(REQUIRED_MESSAGE); }); }); ================================================ FILE: packages/vee-validate/tests/Field.spec.ts ================================================ import { defineRule, configure } from '@/vee-validate'; import { mountWithHoc, setValue, dispatchEvent, setChecked, flushPromises, dispatchFileEvent } from './helpers'; import { computed, reactive, ref, Ref } from 'vue'; import { ModelComp } from './helpers/ModelComp'; import { z } from 'zod'; vi.useFakeTimers(); beforeEach(() => { configure({ bails: true, validateOnBlur: true, validateOnChange: true, validateOnInput: false, validateOnModelUpdate: true, generateMessage: undefined, }); }); describe('', () => { const REQUIRED_MESSAGE = `This field is required`; defineRule('required', (value: string) => { if (!value) { return REQUIRED_MESSAGE; } return true; }); defineRule('email', (email: string) => { return email === 'email' ? true : 'The field must be a valid email'; }); defineRule('min', (value: string, [min]: any) => { return value && value.length >= min ? true : 'This field must be at least 3 characters'; }); // FIXME: typing here should be more lax defineRule('confirmed', (value: string, [target]: any) => { return value === target ? true : 'inputs do not match'; }); defineRule('confirmedObj', (value: string, { target }: any) => { return value === target ? true : 'inputs do not match'; }); test('renders an input by default', () => { const wrapper = mountWithHoc({ template: ` `, }); expect(wrapper.$el.tagName).toBe(`INPUT`); }); test('renders the as prop', () => { const wrapper = mountWithHoc({ template: ` `, }); expect(wrapper.$el.tagName).toBe(`DIV`); }); test('renderless if no as prop and default slot exists', () => { const wrapper = mountWithHoc({ template: ` `, }); expect(wrapper.$el.tagName).toBe(undefined); }); test('accepts functions to be passed as rules', async () => { const isRequired = (val: any) => (val ? true : 'Field is required'); const wrapper = mountWithHoc({ setup() { return { rules: isRequired, }; }, template: `
{{ errors[0] }}
`, }); const input = wrapper.$el.querySelector('input'); const error = wrapper.$el.querySelector('span'); expect(error.textContent).toBe(''); setValue(input, ''); await flushPromises(); expect(error.textContent).toBe('Field is required'); }); test('accepts objects to be passed as rules', async () => { const wrapper = mountWithHoc({ setup() { return { rules: { required: true, min: [3], confirmedObj: { target: '@other' } }, }; }, template: ` {{ errors[0] }} {{ errors[0] }} `, }); const input = wrapper.$el.querySelector('input'); const error = wrapper.$el.querySelector('#fieldError'); expect(error.textContent).toBe(''); setValue(input, '1'); await flushPromises(); expect(error.textContent).toBe('This field must be at least 3 characters'); setValue(input, '123'); await flushPromises(); expect(error.textContent).toBe('inputs do not match'); }); test('listens for input and blur events to set meta flags', async () => { const wrapper = mountWithHoc({ template: `
{{ meta }}
`, }); const input = wrapper.$el.querySelector('input'); const pre = wrapper.$el.querySelector('pre'); expect(pre.textContent).toContain('"touched": false'); expect(pre.textContent).toContain('"dirty": false'); dispatchEvent(input, 'blur'); await flushPromises(); expect(pre.textContent).toContain('"touched": true'); expect(pre.textContent).toContain('"dirty": false'); dispatchEvent(input, 'input'); await flushPromises(); expect(pre.textContent).toContain('"dirty": true'); }); test('listens for change events', async () => { const wrapper = mountWithHoc({ template: ` {{ errors.select }} `, }); const select = wrapper.$el.querySelector('select'); const error = wrapper.$el.querySelector('#error'); setValue(select, ''); await flushPromises(); // validation triggered on change. expect(error.textContent).toBe(REQUIRED_MESSAGE); setValue(select, '1'); await flushPromises(); expect(error.textContent).toBe(''); }); test('validates initially with validateOnMount prop', async () => { const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); // flush the pending validation. await flushPromises(); expect(error.textContent).toContain(REQUIRED_MESSAGE); }); test('watches rules and re-validates', async () => { let rules!: Ref; const wrapper = mountWithHoc({ setup() { rules = ref({ required: true }); return { rules, }; }, template: `
{{ errors[0] }}
`, }); const input = wrapper.$el.querySelector('input'); const error = wrapper.$el.querySelector('#error'); setValue(input, '1'); // flush the pending validation. await flushPromises(); expect(error.textContent).toBe(''); rules.value = { required: false, min: 3, }; await flushPromises(); expect(error.textContent).toBe('This field must be at least 3 characters'); }); test('validates custom components', async () => { const wrapper = mountWithHoc({ components: { TextInput: { props: ['value'], template: `
`, }, }, template: `
{{ errors && errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('#input'); expect(error.textContent).toBe(''); setValue(input, ''); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); setValue(input, 'val'); await flushPromises(); expect(error.textContent).toBe(''); }); test('validates target fields using targeted params', async () => { const wrapper = mountWithHoc({ template: ` {{ errors[0] }} `, }); const error = wrapper.$el.querySelector('#err'); const inputs = wrapper.$el.querySelectorAll('input'); expect(error.textContent).toBeFalsy(); setValue(inputs[0], 'val'); await flushPromises(); // the password input hasn't changed yet. expect(error.textContent).toBeFalsy(); setValue(inputs[1], '12'); await flushPromises(); // the password input was interacted with and should be validated. expect(error.textContent).toBeTruthy(); setValue(inputs[1], 'val'); await flushPromises(); // the password input now matches the confirmation. expect(error.textContent).toBeFalsy(); setValue(inputs[0], 'val1'); await flushPromises(); expect(error.textContent).toBeTruthy(); }); test('validates file input in scoped slots', async () => { defineRule('atLeastOne', (files: File[]) => { return files && files.length >= 1; }); const wrapper = mountWithHoc({ template: `

{{ errors[0] }}

`, }); const input = wrapper.$el.querySelector('input'); dispatchEvent(input, 'change'); await flushPromises(); const error = wrapper.$el.querySelector('#error'); expect(error.textContent).toBeTruthy(); }); test('validates file input by rendering', async () => { defineRule('atLeastOne', (files: File[]) => { return files && files.length >= 1; }); const wrapper = mountWithHoc({ template: ` {{ errors.field }} `, }); const input = wrapper.$el.querySelector('input'); dispatchEvent(input, 'change'); await flushPromises(); const error = wrapper.$el.querySelector('#error'); expect(error.textContent).toBeTruthy(); }); test('file values are normalized depending', async () => { const onSubmit = vi.fn(); const wrapper = mountWithHoc({ template: ` `, setup() { return { onSubmit, }; }, }); await flushPromises(); const getInput = (name: string) => wrapper.$el.querySelector(`input[name="${name}"]`) as HTMLInputElement; const button = wrapper.$el.querySelector(`button`) as HTMLElement; await dispatchFileEvent(getInput('single'), 'test.jpg'); await dispatchFileEvent(getInput('multiple'), ['one.jpg', 'two.jpg']); button.click(); await flushPromises(); expect(onSubmit).toHaveBeenLastCalledWith( expect.objectContaining({ single: expect.any(File), multiple: expect.arrayContaining([expect.any(File), expect.any(File)]), }), expect.anything(), ); }); test('setting bails prop to false disables exit on first error', async () => { const wrapper = mountWithHoc({ template: `

{{ error }}

`, }); const input = wrapper.$el.querySelector('input'); setValue(input, '1'); await flushPromises(); const errors = wrapper.$el.querySelectorAll('p'); expect(errors).toHaveLength(2); expect(errors[0].textContent).toBe('The field must be a valid email'); expect(errors[1].textContent).toBe('This field must be at least 3 characters'); }); test('standard schema can be used', async () => { const wrapper = mountWithHoc({ setup() { const rules = z.string().min(8); return { rules, }; }, template: `

{{ errors[0] }}

`, }); const input = wrapper.$el.querySelector('input'); const error = wrapper.$el.querySelector('p'); setValue(input, ''); await flushPromises(); expect(error.textContent).toBe('Too small: expected string to have >=8 characters'); setValue(input, '12345678'); await flushPromises(); expect(error.textContent).toBe(''); }); test('avoids race conditions between successive validations', async () => { // A decreasing timeout (the most recent validation will finish before new ones). defineRule('longRunning', (value: unknown): Promise => { return new Promise(resolve => { setTimeout(() => { resolve(value === 42 ? true : 'No Life'); }, 20); }); }); const wrapper = mountWithHoc({ template: `

{{ errors[0] }}

`, }); const input = wrapper.$el.querySelector('input'); const error = wrapper.$el.querySelector('p'); setValue(input, '123'); setValue(input, '12'); setValue(input, ''); vi.advanceTimersByTime(100); await flushPromises(); // LAST message should be the required one. expect(error.textContent).toBe(REQUIRED_MESSAGE); }); test('resets validation state using handleReset() in slot scope props', async () => { const wrapper = mountWithHoc({ template: `
{{ errors && errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); expect(error.textContent).toBe(''); setValue(input, ''); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); setValue(input, '123'); await flushPromises(); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(error.textContent).toBe(''); expect(input.value).toBe(''); }); test('resets validation state using resetField() in slot scope props', async () => { const resetMessage = 'field is bad'; const resetValue = 'val'; const wrapper = mountWithHoc({ template: `
{{ errors && errors[0] }} {{ meta.touched.toString() }} {{ meta.dirty.toString() }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); const dirty = wrapper.$el.querySelector('#dirty'); const touched = wrapper.$el.querySelector('#touched'); expect(error.textContent).toBe(''); setValue(input, ''); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); expect(dirty.textContent).toBe('true'); expect(touched.textContent).toBe('false'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(error.textContent).toBe(resetMessage); expect(input.value).toBe(resetValue); expect(dirty.textContent).toBe('false'); expect(touched.textContent).toBe('true'); }); test('handleInput() updates the model', async () => { let inputValue!: Ref; const wrapper = mountWithHoc({ setup() { inputValue = ref(''); return { value: inputValue, }; }, template: `
`, }); await flushPromises(); const input = wrapper.$el.querySelector('input'); setValue(input, '1234'); await flushPromises(); expect(inputValue.value).toBe('1234'); inputValue.value = '455'; await flushPromises(); expect(input.value).toBe('455'); }); test('generateMessage is invoked with custom fn rules', async () => { configure({ generateMessage: ({ field }) => `${field} is bad`, }); const wrapper = mountWithHoc({ setup() { return { rules: () => false, }; }, template: `

{{ errors[0] }}

`, }); await flushPromises(); const input = wrapper.$el.querySelector('input'); const error = wrapper.$el.querySelector('#error'); setValue(input, '1234'); await flushPromises(); expect(error.textContent).toBe('field is bad'); }); test('can customize validation triggers via global config', async () => { configure({ validateOnChange: false, validateOnInput: true, }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); input.value = ''; dispatchEvent(input, 'change'); await flushPromises(); // nothing got triggered expect(error.textContent).toBe(''); input.value = ''; dispatchEvent(input, 'input'); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); }); test('can customize validation triggers via props', async () => { const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); input.value = ''; dispatchEvent(input, 'change'); await flushPromises(); // nothing got triggered expect(error.textContent).toBe(''); input.value = ''; dispatchEvent(input, 'input'); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); }); test('can show custom labels for fields in messages', async () => { configure({ generateMessage: ({ field }) => `${field} is bad`, }); defineRule('noMessage', (value: number) => { return value === 48; }); const wrapper = mountWithHoc({ template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); setValue(input, '3'); await flushPromises(); expect(error.textContent).toBe('nice name is bad'); }); test('can set touched meta', async () => { mountWithHoc({ template: ` {{ meta.touched }} `, }); await flushPromises(); const span = document.querySelector('span'); expect(span?.textContent).toBe('false'); document.querySelector('button')?.click(); await flushPromises(); expect(span?.textContent).toBe('true'); }); // #3053 test('labels can be set dynamically', async () => { const label = ref('label'); const message = (field: string) => `${field} is not valid`; mountWithHoc({ setup() { const rules = (_: any, { field }: any) => message(field); return { rules, label, }; }, template: ` {{ errors[0] }} `, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; setValue(input, '1'); await flushPromises(); expect(document.querySelector('span')?.textContent).toBe(message(label.value)); label.value = 'updated'; await flushPromises(); setValue(input, '2'); await flushPromises(); expect(document.querySelector('span')?.textContent).toBe(message(label.value)); }); // #3048 test('proxies native listeners', async () => { const onBlur = vi.fn(); mountWithHoc({ setup() { return { onBlur, }; }, template: ` `, }); await flushPromises(); const input = document.querySelector('input'); dispatchEvent(input as HTMLInputElement, 'blur'); expect(onBlur).toHaveBeenCalledTimes(1); }); test('can customize checkboxes unchecked value', async () => { const spy = vi.fn(); const wrapper = mountWithHoc({ setup() { return { onSubmit: spy }; }, template: ` Coffee `, }); await flushPromises(); const input = wrapper.$el.querySelector('input'); setChecked(input, true); await flushPromises(); setChecked(input, false); await flushPromises(); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(spy).toHaveBeenCalledWith(expect.objectContaining({ terms: false }), expect.anything()); }); // #3105 test('single checkboxes without forms toggles their value with v-model', async () => { let model!: Ref; const wrapper = mountWithHoc({ setup() { model = ref(false); return { model }; }, template: `
Dinner?
`, }); await flushPromises(); const input = wrapper.$el.querySelector('input'); setChecked(input, true); await flushPromises(); expect(model.value).toBe(true); setChecked(input, false); await flushPromises(); expect(model.value).toBe(false); }); // #3107 test('sets initial value with v-model', async () => { const modelValue = 'allo'; const wrapper = mountWithHoc({ setup() { const model = ref(modelValue); return { model }; }, template: `
`, }); await flushPromises(); const input = wrapper.$el.querySelector('input'); expect(input.value).toBe(modelValue); }); test('resetField should reset the valid flag to false if the rules are incorrect', async () => { const wrapper = mountWithHoc({ template: `
{{ meta.valid ? 'valid' : 'invalid' }}
`, }); await flushPromises(); const input = wrapper.$el.querySelector('input'); const meta = wrapper.$el.querySelector('#meta'); expect(meta?.textContent).toBe('invalid'); setValue(input, ''); await flushPromises(); expect(meta?.textContent).toBe('invalid'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(meta?.textContent).toBe('invalid'); }); test('valid flag is synced with the field errors array length', async () => { const wrapper = mountWithHoc({ template: `
{{ meta.valid ? 'valid' : 'invalid' }}
`, }); await flushPromises(); const meta = wrapper.$el.querySelector('#meta'); expect(meta?.textContent).toBe('invalid'); const input = wrapper.$el.querySelector('input'); setValue(input, ''); await flushPromises(); expect(meta?.textContent).toBe('invalid'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(meta?.textContent).toBe('invalid'); }); test('can set multiple field errors', async () => { const wrapper = mountWithHoc({ template: `
  • {{ error }}
`, }); await flushPromises(); const list = document.querySelector('ul'); expect(list?.children).toHaveLength(0); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(list?.children).toHaveLength(2); expect(list?.textContent).toBe('badwrong'); }); // #3230 test('v-model.number should not ignore the validation triggers', async () => { const errorMessage = 'Field is invalid'; let model!: Ref; const wrapper = mountWithHoc({ setup() { model = ref(''); const isAlwaysInvalid = () => errorMessage; return { model, isAlwaysInvalid }; }, template: `
{{ errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('#input'); input.value = '310'; dispatchEvent(input, 'input'); await flushPromises(); expect(model.value).toBe(310); expect(error.textContent).toBe(''); dispatchEvent(input, 'blur'); await flushPromises(); expect(error.textContent).toBe(errorMessage); }); // #3312 test('v-model on a non-existent nested prop should still emit model events', async () => { const form = reactive({}); const wrapper = mountWithHoc({ setup() { return { form }; }, template: `
`, }); await flushPromises(); const input = wrapper.$el.querySelector('input'); input.value = 'hello'; dispatchEvent(input, 'input'); await flushPromises(); expect((form as any).field).toBe('hello'); }); // #3440 test('should preserve select input options value type', async () => { const value = ref(); mountWithHoc({ setup() { return { value, }; }, template: ` `, }); await flushPromises(); const select = document.querySelector('select') as HTMLSelectElement; const optTrue = document.querySelector('#true') as HTMLOptionElement; const optFalse = document.querySelector('#false') as HTMLOptionElement; optTrue.selected = true; dispatchEvent(select, 'change'); await flushPromises(); expect(value.value).toBe(true); optFalse.selected = true; dispatchEvent(select, 'change'); await flushPromises(); expect(value.value).toBe(false); }); // #3468 test('should avoid setting the absent value to Vue', async () => { const form = ref({}); mountWithHoc({ setup() { return { form, }; }, template: ` `, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; setValue(input, '1234'); await flushPromises(); expect(input.value).toBe('1234'); form.value = {}; await flushPromises(); expect(input.value).toBe(''); }); // #3485 test('computed rules should not generate errors unless the field was validated before', async () => { const isRequired = ref(false); const rules = computed(() => (isRequired.value ? 'required' : '')); mountWithHoc({ setup() { return { rules, }; }, template: ` {{ errorMessage }} `, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; const error = document.querySelector('span') as HTMLInputElement; isRequired.value = true; await flushPromises(); expect(error.textContent).toBe(''); isRequired.value = false; await flushPromises(); expect(error.textContent).toBe(''); setValue(input, ''); await flushPromises(); isRequired.value = true; await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); }); test('resets validation state using refs and exposed API', async () => { const wrapper = mountWithHoc({ template: `
{{ errors && errors[0] }}
`, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); expect(error.textContent).toBe(''); setValue(input, ''); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); setValue(input, '123'); await flushPromises(); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(error.textContent).toBe(''); expect(input.value).toBe(''); }); test('should have correct field object binding properties based on file type', async () => { const textualType = vi.fn(); const fileType = vi.fn(); const checkboxType = vi.fn(); const radioType = vi.fn(); mountWithHoc({ template: `
{{ textualType(field) }} {{ fileType(field) }} {{ checkboxType(field) }} {{ radioType(field) }}
`, setup() { return { textualType, fileType, checkboxType, radioType, }; }, }); await flushPromises(); const lastCallOf = (fn: ReturnType) => fn.mock.calls[fn.mock.calls.length - 1][0]; expect(lastCallOf(textualType)).toHaveProperty('value'); expect(lastCallOf(fileType)).not.toHaveProperty('value'); expect(lastCallOf(checkboxType)).toHaveProperty('checked'); expect(lastCallOf(checkboxType)).not.toHaveProperty('value'); expect(lastCallOf(radioType)).toHaveProperty('checked'); expect(lastCallOf(radioType)).not.toHaveProperty('value'); }); describe('componentField', () => { test('provides bindable object to components', async () => { mountWithHoc({ components: { ModelComp, }, template: ` {{ errorMessage }} {{ value }} `, }); await flushPromises(); const errorEl = document.getElementById('errors'); const valuesEl = document.getElementById('values'); const inputEl = document.querySelector('input') as HTMLInputElement; expect(inputEl.getAttribute('name')).toBe('name'); setValue(inputEl, ''); dispatchEvent(inputEl, 'blur'); await flushPromises(); expect(errorEl?.textContent).toBe('This field is required'); setValue(inputEl, '123'); dispatchEvent(inputEl, 'blur'); await flushPromises(); expect(errorEl?.textContent).toBe(''); expect(valuesEl?.textContent).toBe('123'); }); }); // #4285 test('handle blur should respect the validate on blur config', async () => { mountWithHoc({ template: ` {{ errorMessage }} `, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; const error = document.querySelector('span') as HTMLInputElement; dispatchEvent(input, 'blur'); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); }); }); ================================================ FILE: packages/vee-validate/tests/FieldArray.spec.ts ================================================ import { Form, defineRule, useField } from '@/vee-validate'; import { toRef, ref, defineComponent } from 'vue'; import * as z from 'zod'; import { mountWithHoc, setValue, getValue, dispatchEvent, flushPromises } from './helpers'; const REQUIRED_MESSAGE = 'REQUIRED'; test('warns if no form is detected', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ template: ` `, }); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); test('warns if no name path is provided', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ template: ` `, }); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); test('adds items to the end of the array with push()', async () => { const onSubmit = vi.fn(); mountWithHoc({ setup() { const initialValues = { users: [{ name: '111' }, { name: '222' }, { name: '333' }], }; return { onSubmit, initialValues, }; }, template: `
User #{{ idx }}
`, }); await flushPromises(); const submitBtn = document.querySelector('.submit') as HTMLButtonElement; const inputs = Array.from(document.querySelectorAll('input')) as HTMLInputElement[]; const removeBtn = document.querySelectorAll('.remove')[1] as HTMLButtonElement; // remove the second item setValue(inputs[0], '111'); setValue(inputs[1], '222'); setValue(inputs[2], '333'); await flushPromises(); removeBtn.click(); await flushPromises(); (submitBtn as HTMLButtonElement).click(); await flushPromises(); expect(onSubmit).toHaveBeenLastCalledWith( expect.objectContaining({ users: [{ name: '111' }, { name: '333' }], }), expect.anything(), ); dispatchEvent('.add', 'click'); await flushPromises(); (submitBtn as HTMLButtonElement).click(); await flushPromises(); expect(onSubmit).toHaveBeenLastCalledWith( expect.objectContaining({ users: [{ name: '111' }, { name: '333' }, { name: 'new' }], }), expect.anything(), ); }); test('array fields should update their values when swapped', async () => { mountWithHoc({ setup() { const initial = { users: [ { name: 'first', }, { name: 'second', }, { name: 'third', }, ], }; return { initial, }; }, template: `
`, }); await flushPromises(); expect(document.querySelectorAll('input')).toHaveLength(3); dispatchEvent('.remove_0', 'click'); await flushPromises(); expect(document.querySelectorAll('input')).toHaveLength(2); expect(getValue('#name_0')).toBe('second'); dispatchEvent('.add', 'click'); await flushPromises(); expect(document.querySelectorAll('input')).toHaveLength(3); expect(getValue('#name_2')).toBe(''); }); test('can swap array fields with swap helper', async () => { const onSubmit = vi.fn(); mountWithHoc({ setup() { const schema = z.object({ users: z.array( z.object({ name: z.string().min(1, REQUIRED_MESSAGE), }), ), }); const initial = { users: [ { name: 'first', }, { name: 'second', }, { name: 'third', }, ], }; return { initial, schema, onSubmit, }; }, template: `
`, }); await flushPromises(); const inputAt = (idx: number) => (document.querySelectorAll('input') || [])[idx] as HTMLInputElement; const errorAt = (idx: number) => document.querySelector(`.error-${idx}`) as HTMLElement; const upButtonAt = (idx: number) => (document.querySelectorAll('.up') || [])[idx] as HTMLElement; const submitBtn = () => document.querySelector('.submit') as HTMLButtonElement; expect(getValue(inputAt(0))).toBe('first'); expect(getValue(inputAt(1))).toBe('second'); expect(getValue(inputAt(2))).toBe('third'); setValue(inputAt(1), ''); await flushPromises(); expect(errorAt(1)?.textContent).toBe(REQUIRED_MESSAGE); upButtonAt(2).click(); await flushPromises(); expect(getValue(inputAt(1))).toBe('third'); // undefined because it should be unmounted expect(errorAt(1)?.textContent).toBe(undefined); expect(getValue(inputAt(2))).toBe(''); expect(errorAt(2)?.textContent).toBe(REQUIRED_MESSAGE); setValue(inputAt(2), 'edited'); await flushPromises(); submitBtn().click(); await flushPromises(); expect(onSubmit).toHaveBeenCalledTimes(1); expect(onSubmit).toHaveBeenLastCalledWith( expect.objectContaining({ users: [ { name: 'first', }, { name: 'third', }, { name: 'edited', }, ], }), expect.anything(), ); }); test('fields have isFirst and isLast flags to help with conditions', async () => { mountWithHoc({ setup() { const initial = { users: [ { name: 'first', }, { name: 'second', }, { name: 'third', }, ], }; return { initial, }; }, template: `
`, }); await flushPromises(); const upButtonAt = (idx: number) => (document.querySelectorAll('.up') || [])[idx] as HTMLButtonElement; const downButtonAt = (idx: number) => (document.querySelectorAll('.down') || [])[idx] as HTMLButtonElement; const rmButtonAt = (idx: number) => (document.querySelectorAll('.remove') || [])[idx] as HTMLButtonElement; const addButton = () => document.querySelector('.add') as HTMLButtonElement; expect(upButtonAt(0).disabled).toBe(true); expect(upButtonAt(1).disabled).toBe(false); expect(upButtonAt(2).disabled).toBe(false); expect(downButtonAt(0).disabled).toBe(false); expect(downButtonAt(1).disabled).toBe(false); expect(downButtonAt(2).disabled).toBe(true); addButton().click(); await flushPromises(); expect(upButtonAt(0).disabled).toBe(true); expect(upButtonAt(1).disabled).toBe(false); expect(upButtonAt(2).disabled).toBe(false); expect(upButtonAt(3).disabled).toBe(false); expect(downButtonAt(0).disabled).toBe(false); expect(downButtonAt(1).disabled).toBe(false); expect(downButtonAt(2).disabled).toBe(false); expect(downButtonAt(3).disabled).toBe(true); rmButtonAt(2).click(); await flushPromises(); expect(upButtonAt(0).disabled).toBe(true); expect(upButtonAt(1).disabled).toBe(false); expect(upButtonAt(2).disabled).toBe(false); expect(downButtonAt(0).disabled).toBe(false); expect(downButtonAt(1).disabled).toBe(false); expect(downButtonAt(2).disabled).toBe(true); }); test('can insert new items at specific index', async () => { mountWithHoc({ setup() { const initial = { users: [ { name: 'first', }, { name: 'second', }, { name: 'third', }, ], }; return { initial, }; }, template: `
`, }); await flushPromises(); const inputAt = (idx: number) => (document.querySelectorAll('input') || [])[idx] as HTMLInputElement; const insertButton = () => document.querySelector('.insert') as HTMLButtonElement; expect(getValue(inputAt(1))).toBe('second'); expect(getValue(inputAt(2))).toBe('third'); insertButton().click(); await flushPromises(); expect(getValue(inputAt(1))).toBe('inserted'); expect(getValue(inputAt(2))).toBe('second'); expect(getValue(inputAt(3))).toBe('third'); }); test('can replace all the items in the field array', async () => { mountWithHoc({ setup() { const initial = { users: [ { name: 'first', }, { name: 'second', }, { name: 'third', }, ], }; return { initial, }; }, template: `
`, }); await flushPromises(); const inputAt = (idx: number) => (document.querySelectorAll('input') || [])[idx] as HTMLInputElement; const replaceButton = () => document.querySelector('.replace') as HTMLButtonElement; expect(getValue(inputAt(0))).toBe('first'); expect(getValue(inputAt(1))).toBe('second'); expect(getValue(inputAt(2))).toBe('third'); replaceButton().click(); await flushPromises(); expect(getValue(inputAt(0))).toBe('replaced'); expect(getValue(inputAt(1))).toBe(undefined); expect(getValue(inputAt(2))).toBe(undefined); }); test('can update an item value at a given array index', async () => { mountWithHoc({ setup() { const initial = { users: [ { name: 'first', }, { name: 'second', }, { name: 'third', }, ], }; return { initial, }; }, template: `
`, }); await flushPromises(); const inputAt = (idx: number) => (document.querySelectorAll('input') || [])[idx] as HTMLInputElement; const updateButton = () => document.querySelector('.update') as HTMLButtonElement; expect(getValue(inputAt(1))).toBe('second'); updateButton().click(); await flushPromises(); expect(getValue(inputAt(1))).toBe('updated'); }); test('can update an item value directly with .value setter', async () => { const onSubmit = vi.fn(); mountWithHoc({ setup() { const initial = { users: [ { name: 'first', }, ], }; return { initial, onSubmit, }; }, template: `
`, }); await flushPromises(); const inputAt = (idx: number) => (document.querySelectorAll('input') || [])[idx] as HTMLInputElement; expect(getValue(inputAt(0))).toBe('first'); setValue(inputAt(0), 'updated'); await flushPromises(); expect(getValue(inputAt(0))).toBe('updated'); document.querySelector('button')?.click(); await flushPromises(); expect(onSubmit).toHaveBeenLastCalledWith( expect.objectContaining({ users: [ { name: 'updated', }, ], }), expect.anything(), ); }); test('adds items to the start of the array with prepend()', async () => { const onSubmit = vi.fn(); mountWithHoc({ setup() { const initialValues = { users: [{ name: '111' }, { name: '222' }], }; return { onSubmit, initialValues, }; }, template: `
User #{{ idx }}
`, }); await flushPromises(); const submitBtn = document.querySelector('.submit') as HTMLButtonElement; const inputAt = (idx: number) => (document.querySelectorAll('input') || [])[idx] as HTMLInputElement; expect(getValue(inputAt(0))).toBe('111'); expect(getValue(inputAt(1))).toBe('222'); dispatchEvent('.prepend', 'click'); await flushPromises(); expect(getValue(inputAt(0))).toBe('new'); expect(getValue(inputAt(1))).toBe('111'); expect(getValue(inputAt(2))).toBe('222'); (submitBtn as HTMLButtonElement).click(); await flushPromises(); expect(onSubmit).toHaveBeenLastCalledWith( expect.objectContaining({ users: [{ name: 'new' }, { name: '111' }, { name: '222' }], }), expect.anything(), ); }); // #3664 test('clears old errors path when item is removed when no form schema is present', async () => { const onSubmit = vi.fn(); mountWithHoc({ setup() { const initialValues = { users: [{ name: '' }, { name: '' }, { name: '' }], }; const schema = z.string().min(1, REQUIRED_MESSAGE); return { onSubmit, schema, initialValues, }; }, template: `
User #{{ idx }}
  • {{ error }}
`, }); await flushPromises(); const submitBtn = document.querySelector('.submit') as HTMLButtonElement; const errorList = document.querySelector('ul') as HTMLUListElement; const removeBtnAt = (idx: number) => document.querySelectorAll('.remove')[idx] as HTMLButtonElement; // remove the second item submitBtn.click(); await flushPromises(); expect(errorList.children).toHaveLength(3); removeBtnAt(1).click(); await flushPromises(); expect(errorList.children).toHaveLength(2); }); // #3748 test('clears old errors path when last item is removed and value update validation is on', async () => { const onSubmit = vi.fn(); defineRule('required', (v: any) => (v ? true : REQUIRED_MESSAGE)); const InputField = defineComponent({ props: { rules: { type: String, required: true, }, name: { type: String, required: true, }, label: String, type: { type: String, default: 'text' }, }, setup(props) { const { value, handleChange, errors } = useField(toRef(props, 'name'), props.rules, { label: props.label, type: props.type as 'checkbox', }); return { value, errors, handleChange, }; }, template: ` {{ errors[0] }} `, }); mountWithHoc({ components: { InputField, }, setup() { const initialValues = { users: ['first', 'second', 'third'], }; const schema = z.string().min(1, REQUIRED_MESSAGE); return { onSubmit, schema, initialValues, }; }, template: `
User #{{ idx }}
  • {{ error }}
`, }); await flushPromises(); const submitBtn = document.querySelector('.submit') as HTMLButtonElement; const errorList = document.querySelector('ul') as HTMLUListElement; const removeBtnAt = (idx: number) => document.querySelectorAll('.remove')[idx] as HTMLButtonElement; // remove the second item submitBtn.click(); await flushPromises(); expect(errorList.children).toHaveLength(0); removeBtnAt(2).click(); await flushPromises(); expect(errorList.children).toHaveLength(0); }); // 4017 test('keeps the errors intact if an item was removed in the middle of the list', async () => { defineRule('required', (v: any) => (v ? true : REQUIRED_MESSAGE)); const InputField = defineComponent({ props: { rules: { type: null, required: true, }, name: { type: String, required: true, }, label: String, type: { type: String, default: 'text' }, }, setup(props) { const { value, handleChange, errors } = useField(toRef(props, 'name'), props.rules, { label: props.label, }); return { value, errors, handleChange, }; }, template: ` {{ errors[0] }} `, }); mountWithHoc({ components: { InputField, }, setup() { const initialValues = { users: ['', '', ''], }; const schema = z.string().min(1, REQUIRED_MESSAGE); return { schema, initialValues, }; }, template: `
User #{{ idx }}
  • {{ error }}
`, }); await flushPromises(); const errorList = document.querySelector('ul') as HTMLUListElement; const removeBtnAt = (idx: number) => document.querySelectorAll('.remove')[idx] as HTMLButtonElement; // remove the second item await flushPromises(); expect(errorList.children).toHaveLength(0); removeBtnAt(1).click(); await flushPromises(); expect(errorList.children).toHaveLength(0); }); test('moves items around the array with move()', async () => { const onSubmit = vi.fn(); mountWithHoc({ setup() { const initialValues = { users: [{ name: '1' }, { name: '2' }, { name: '3' }, { name: '4' }], }; return { onSubmit, initialValues, }; }, template: `
User #{{ idx }}
`, }); await flushPromises(); const submitBtn = document.querySelector('.submit') as HTMLButtonElement; const inputAt = (idx: number) => (document.querySelectorAll('input') || [])[idx] as HTMLInputElement; const moveElAt = (idx: number) => (document.querySelectorAll('.move') || [])[idx] as HTMLInputElement; expect(getValue(inputAt(0))).toBe('1'); expect(getValue(inputAt(1))).toBe('2'); expect(getValue(inputAt(2))).toBe('3'); expect(getValue(inputAt(3))).toBe('4'); dispatchEvent(moveElAt(3), 'click'); await flushPromises(); expect(getValue(inputAt(0))).toBe('4'); expect(getValue(inputAt(1))).toBe('1'); expect(getValue(inputAt(2))).toBe('2'); expect(getValue(inputAt(3))).toBe('3'); (submitBtn as HTMLButtonElement).click(); await flushPromises(); expect(onSubmit).toHaveBeenLastCalledWith( expect.objectContaining({ users: [{ name: '4' }, { name: '1' }, { name: '2' }, { name: '3' }], }), expect.anything(), ); }); // #3782 test('removing an item marks the form as dirty', async () => { const onSubmit = vi.fn(); mountWithHoc({ setup() { const initialValues = { users: [{ name: '1' }, { name: '2' }, { name: '3' }], }; return { onSubmit, initialValues, }; }, template: `
User #{{ idx }}
{{ meta.dirty }}
`, }); await flushPromises(); const getDirtyPre = () => document.querySelector('pre') as HTMLElement; const removeBtnAt = (idx: number) => (document.querySelectorAll('.remove') || [])[idx] as HTMLInputElement; await expect(getDirtyPre().textContent).toBe('false'); dispatchEvent(removeBtnAt(2), 'click'); await flushPromises(); await expect(getDirtyPre().textContent).toBe('true'); }); test('clean up form registration on unmount', async () => { const shown = ref(true); mountWithHoc({ setup() { const initialValues = { users: [{ name: '1' }, { name: '2' }, { name: '3' }], }; return { initialValues, shown, }; }, template: `
User #{{ idx }}
`, }); await flushPromises(); shown.value = false; await flushPromises(); expect(1).toBe(1); }); // #3874 test('adding or removing fields should update form dirty correctly', async () => { mountWithHoc({ setup() { const initialValues = { users: [''], }; return { initialValues, }; }, template: `
User #{{ idx }}
{{ meta.dirty }}
`, }); await flushPromises(); const pushBtn = document.querySelector('#push') as HTMLElement; const removeBtn = document.querySelector('#remove') as HTMLElement; const dirty = document.querySelector('#dirty') as HTMLElement; expect(dirty?.textContent).toBe('false'); pushBtn.click(); await flushPromises(); expect(dirty.textContent).toBe('true'); removeBtn.click(); await flushPromises(); expect(dirty.textContent).toBe('false'); }); // #4115 test('removing fields with `v-if` should clean up their state properly', async () => { const showFields = ref(true); const formRef = ref>(); const initialValues = { users: [ { name: 'test 1', amount: 123 }, { name: 'test 2', amount: 567 }, ], }; mountWithHoc({ setup() { return { initialValues, formRef, showFields, }; }, template: `
User #{{ idx }}
`, }); await flushPromises(); expect(formRef.value?.getValues()).toEqual(initialValues); showFields.value = false; await flushPromises(); expect(formRef.value?.getValues()).toEqual({}); }); ================================================ FILE: packages/vee-validate/tests/Form.spec.ts ================================================ import { defineRule, Field, Form, useField, useForm, useIsValidating } from '@/vee-validate'; import { computed, defineComponent, onErrorCaptured, ref, Ref } from 'vue'; import * as z from 'zod'; import { InvalidSubmissionContext, PrivateFormContext } from '../src/types'; import { dispatchEvent, flushPromises, mountWithHoc, setChecked, setValue } from './helpers'; describe('', () => { const REQUIRED_MESSAGE = `This field is required`; const MIN_MESSAGE = `This field is short`; defineRule('required', (value: unknown) => { if (!value) { return REQUIRED_MESSAGE; } return true; }); defineRule('min', (value: unknown, args: any[]) => { if (!value || String(value).length <= args[0]) { return MIN_MESSAGE; } return true; }); test('renders the as prop', () => { const wrapper = mountWithHoc({ template: `
`, }); expect(wrapper.$el.innerHTML).toBe(``); }); test('observes the current state of providers', async () => { const wrapper = mountWithHoc({ template: ` {{ meta.valid }} `, }); const stateSpan = wrapper.$el.querySelector('#state'); const input = wrapper.$el.querySelector('input'); setValue(input, ''); await flushPromises(); // initially the field valid flag is false. expect(stateSpan.textContent).toBe('false'); setValue(input, 'value'); await flushPromises(); expect(stateSpan.textContent).toBe('true'); }); test('submit handler only executes if observer is valid', async () => { let calls = 0; const wrapper = mountWithHoc({ setup() { return { submit() { calls++; }, }; }, template: ` {{ errors.field }} `, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); await flushPromises(); expect(error.textContent).toBe(''); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(calls).toBe(0); expect(error.textContent).toBe(REQUIRED_MESSAGE); setValue(input, '12'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(error.textContent).toBe(''); expect(calls).toBe(1); }); test('handles reset event', async () => { let isReset = false; const wrapper = mountWithHoc({ setup() { return { reset: () => { isReset = true; }, }; }, template: ` {{ errors.field }} `, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); expect(error.textContent).toBe(''); wrapper.$el.querySelector('#submit').click(); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); setValue(input, 'value'); await flushPromises(); wrapper.$el.querySelector('#reset').click(); await flushPromises(); // value was reset expect(input.value).toBe(''); // errors were cleared expect(error.textContent).toBe(''); expect(isReset).toBe(true); }); test('handles reset with resetForm slot prop', async () => { const resetError = 'Field is wrong'; const resetValue = 'I was reset'; const wrapper = mountWithHoc({ template: ` {{ errors.field }} {{ meta.dirty.toString() }} {{ meta.touched.toString() }} `, }); const error = wrapper.$el.querySelector('#error'); const input = wrapper.$el.querySelector('input'); expect(error.textContent).toBe(''); wrapper.$el.querySelector('#submit').click(); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); setValue(input, 'value'); await flushPromises(); wrapper.$el.querySelector('#reset').click(); await flushPromises(); // value was reset expect(input.value).toBe(resetValue); // errors were cleared expect(error.textContent).toBe(resetError); expect(wrapper.$el.querySelector('#dirty').textContent).toBe('false'); expect(wrapper.$el.querySelector('#touched').textContent).toBe('true'); }); test('initial values can be set with initialValues prop', async () => { const initialValues = { field: 'hello', }; const wrapper = mountWithHoc({ setup() { return { initialValues, }; }, template: ` `, }); const input = wrapper.$el.querySelector('input'); expect(input.value).toBe(initialValues.field); }); test('having no submit listener will submit the form natively', async () => { const submitMock = vi.fn(); const wrapper = mountWithHoc({ template: ` {{ errors.field }} `, }); const form = wrapper.$el; form.submit = submitMock; const input = wrapper.$el.querySelector('input'); await flushPromises(); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(submitMock).toHaveBeenCalledTimes(0); setValue(input, '12'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(submitMock).toHaveBeenCalledTimes(1); }); test('can be renderless', async () => { const submitMock = vi.fn(); const wrapper = mountWithHoc({ template: `
{{ errors.field }}
`, }); const form = wrapper.$el.querySelector('form'); form.submit = submitMock; const input = wrapper.$el.querySelector('input'); await flushPromises(); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(submitMock).toHaveBeenCalledTimes(0); setValue(input, '12'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(submitMock).toHaveBeenCalledTimes(1); }); test('can be renderless with null', async () => { const submitMock = vi.fn(); const wrapper = mountWithHoc({ template: `
{{ errors.field }}
`, }); const form = wrapper.$el.querySelector('form'); form.submit = submitMock; const input = wrapper.$el.querySelector('input'); await flushPromises(); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(submitMock).toHaveBeenCalledTimes(0); setValue(input, '12'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(submitMock).toHaveBeenCalledTimes(1); }); test('validation schema with yup', async () => { const wrapper = mountWithHoc({ setup() { const schema = z.object({ email: z.string().email(), password: z.string().min(8), }); return { schema, }; }, template: ` {{ errors.email }} {{ errors.password }} `, }); const email = wrapper.$el.querySelector('#email'); const password = wrapper.$el.querySelector('#password'); const emailError = wrapper.$el.querySelector('#emailErr'); const passwordError = wrapper.$el.querySelector('#passwordErr'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(emailError.textContent).toBe('Invalid input: expected string, received undefined'); expect(passwordError.textContent).toBe('Invalid input: expected string, received undefined'); setValue(email, 'hello@'); setValue(password, '1234'); await flushPromises(); expect(emailError.textContent).toBe('Invalid email address'); expect(passwordError.textContent).toBe('Too small: expected string to have >=8 characters'); setValue(email, 'hello@email.com'); setValue(password, '12346789'); await flushPromises(); expect(emailError.textContent).toBe(''); expect(passwordError.textContent).toBe(''); }); test('validation schema to validate form', async () => { const wrapper = mountWithHoc({ setup() { const schema = { field: 'required', other: 'required', }; return { schema, }; }, template: ` {{ errors.field }} {{ errors.other }} `, }); const first = wrapper.$el.querySelector('#field'); const second = wrapper.$el.querySelector('#other'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(first.textContent).toBe(REQUIRED_MESSAGE); expect(second.textContent).toBe(REQUIRED_MESSAGE); }); test('cross field validation with yup schema', async () => { const wrapper = mountWithHoc({ setup() { const schema = z .object({ password: z.string().min(1, REQUIRED_MESSAGE), confirmation: z.string().min(1, REQUIRED_MESSAGE), }) .refine(data => data.password === data.confirmation, { message: 'Passwords must match', path: ['confirmation'], }); return { schema, }; }, template: ` {{ errors.password }} {{ errors.confirmation }} `, }); const password = wrapper.$el.querySelector('#password'); const confirmation = wrapper.$el.querySelector('#confirmation'); const confirmationError = wrapper.$el.querySelector('#confirmationError'); wrapper.$el.querySelector('button').click(); await flushPromises(); setValue(password, 'hello@'); setValue(confirmation, '1234'); await flushPromises(); expect(confirmationError.textContent).toBe('Passwords must match'); setValue(password, '1234'); setValue(confirmation, '1234'); await flushPromises(); expect(confirmationError.textContent).toBe(''); }); test('supports radio inputs', async () => { const wrapper = mountWithHoc({ setup() { const schema = { drink: 'required', }; return { schema, }; }, template: ` Coffee Tea Coke {{ errors.drink }} `, }); const err = wrapper.$el.querySelector('#err'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(err.textContent).toBe(REQUIRED_MESSAGE); setChecked(inputs[2]); await flushPromises(); expect(err.textContent).toBe(''); setChecked(inputs[0]); await flushPromises(); expect(err.textContent).toBe(REQUIRED_MESSAGE); setChecked(inputs[1]); await flushPromises(); expect(err.textContent).toBe(''); }); test('supports radio inputs with check after submit', async () => { const initialValues = { test: 'one' }; const showFields = ref(true); const result = ref(); const wrapper = mountWithHoc({ setup() { const values = ['one', 'two', 'three']; const onSubmit = (formData: Record) => { result.value = formData.test; }; return { values, onSubmit, initialValues, showFields, result, }; }, template: ` `, }); // const err = wrapper.$el.querySelector('#err'); const inputs = wrapper.$el.querySelectorAll('input'); setChecked(inputs[1]); await flushPromises(); wrapper.$el.querySelector('button').click(); await flushPromises(); showFields.value = false; await flushPromises(); expect(result.value).toBe('two'); }); test('supports radio inputs with check after submit (nested)', async () => { const initialValues = { test: { fieldOne: { option: 'one' } } }; const showFields = ref(true); const result = ref(); const wrapper = mountWithHoc({ setup() { const values = ['one', 'two', 'three']; const onSubmit = (formData: Record) => { result.value = formData.test; }; return { values, onSubmit, initialValues, showFields, result, }; }, template: ` `, }); // const err = wrapper.$el.querySelector('#err'); const inputs = wrapper.$el.querySelectorAll('input'); await flushPromises(); expect(inputs[0].checked).toBe(true); setChecked(inputs[1]); await flushPromises(); wrapper.$el.querySelector('button').click(); await flushPromises(); showFields.value = false; await flushPromises(); expect(result.value.fieldOne.option).toBe('two'); }); test('supports checkboxes inputs', async () => { const wrapper = mountWithHoc({ setup() { const schema = { drink: 'required', }; return { schema, }; }, template: ` Coffee Tea Coke {{ errors.drink }} {{ values.drink && values.drink.toString() }} `, }); const err = wrapper.$el.querySelector('#err'); const values = wrapper.$el.querySelector('#values'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(err.textContent).toBe(REQUIRED_MESSAGE); setChecked(inputs[2]); await flushPromises(); expect(err.textContent).toBe(''); setChecked(inputs[0]); await flushPromises(); expect(err.textContent).toBe(''); setChecked(inputs[1]); await flushPromises(); expect(err.textContent).toBe(''); expect(values.textContent).toBe(['Coke', '', 'Tea'].toString()); setChecked(inputs[1], false); await flushPromises(); expect(values.textContent).toBe(['Coke', ''].toString()); }); test('supports a single checkbox', async () => { const wrapper = mountWithHoc({ setup() { const schema = { drink: 'required', }; return { schema, }; }, template: ` Coffee {{ errors.drink }} {{ typeof values.drink }} `, }); const err = wrapper.$el.querySelector('#err'); const value = wrapper.$el.querySelector('#value'); const input = wrapper.$el.querySelector('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(err.textContent).toBe(REQUIRED_MESSAGE); setChecked(input, true); await flushPromises(); expect(err.textContent).toBe(''); expect(value.textContent).toBe('boolean'); setChecked(input, false); await flushPromises(); expect(err.textContent).toBe(REQUIRED_MESSAGE); expect(value.textContent).toBe('undefined'); }); test('unmounted fields gets unregistered and their values cleaned up', async () => { const showFields = ref(true); const wrapper = mountWithHoc({ setup() { const schema = computed(() => ({ field: showFields.value ? 'required' : '', drink: 'required', })); return { schema, showFields, }; }, template: ` Coke {{ errors }} {{ values }} `, }); await flushPromises(); const errors = wrapper.$el.querySelector('#errors'); const values = wrapper.$el.querySelector('#values'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(errors.textContent).toBeTruthy(); setChecked(inputs[4]); setChecked(inputs[5]); setValue(inputs[0], 'test'); setValue(inputs[1], '12'); setValue(inputs[2], '12'); await flushPromises(); expect(JSON.parse(values.textContent)).toEqual({ drink: ['Tea', 'Coke'], field: 'test', 'non-nested.field': '12', nested: { field: '12' }, }); showFields.value = false; await flushPromises(); expect(errors.textContent).toBe('{}'); expect(JSON.parse(values.textContent)).toEqual({ drink: ['Coke'] }); }); test('unmounted fields gets unregistered and submitted values do not include them', async () => { let showFields!: Ref; const spy = vi.fn(); const wrapper = mountWithHoc({ setup() { showFields = ref(true); return { showFields, onSubmit(values: any) { spy(values); }, }; }, template: ` Coke {{ errors }} `, }); await flushPromises(); const errors = wrapper.$el.querySelector('#errors'); const button = wrapper.$el.querySelector('button'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(errors.textContent).toBeTruthy(); setChecked(inputs[4]); setChecked(inputs[5]); setValue(inputs[0], 'test'); setValue(inputs[1], '12'); setValue(inputs[2], '12'); await flushPromises(); button.click(); await flushPromises(); expect(spy).toHaveBeenLastCalledWith({ drink: ['Tea', 'Coke'], field: 'test', 'non-nested.field': '12', nested: { field: '12' }, }); showFields.value = false; await flushPromises(); expect(errors.textContent).toBe('{}'); button.click(); await flushPromises(); expect(spy).toHaveBeenLastCalledWith({ drink: ['Coke'] }); }); test('unmounted fields gets unregistered and their values are kept if configured on the form level', async () => { const showFields = ref(true); const wrapper = mountWithHoc({ setup() { const schema = computed(() => ({ field: showFields.value ? 'required' : '', drink: 'required', })); return { schema, showFields, }; }, template: ` Coke {{ errors }} {{ values }} `, }); await flushPromises(); const errors = wrapper.$el.querySelector('#errors'); const values = wrapper.$el.querySelector('#values'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(errors.textContent).toBeTruthy(); setChecked(inputs[4]); setChecked(inputs[5]); setValue(inputs[0], 'test'); setValue(inputs[1], '12'); setValue(inputs[2], '12'); await flushPromises(); const expected = { drink: ['Tea', 'Coke'], field: 'test', 'non-nested.field': '12', nested: { field: '12' }, }; expect(JSON.parse(values.textContent)).toEqual(expected); showFields.value = false; await flushPromises(); // errors were cleared expect(errors.textContent).toBe('{}'); expect(JSON.parse(values.textContent)).toEqual(expected); }); test('unmounted fields gets unregistered and their submitted values are kept if configured on the form level', async () => { let showFields!: Ref; const spy = vi.fn(); const wrapper = mountWithHoc({ setup() { showFields = ref(true); return { showFields, onSubmit(values: any) { spy(values); }, }; }, template: ` Coke {{ errors }} `, }); await flushPromises(); const errors = wrapper.$el.querySelector('#errors'); const button = wrapper.$el.querySelector('button'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(errors.textContent).toBeTruthy(); setChecked(inputs[4]); setChecked(inputs[5]); setValue(inputs[0], 'test'); setValue(inputs[1], '12'); setValue(inputs[2], '12'); await flushPromises(); button.click(); await flushPromises(); const expected = { drink: ['Tea', 'Coke'], field: 'test', 'non-nested.field': '12', nested: { field: '12' }, }; expect(spy).toHaveBeenLastCalledWith(expected); showFields.value = false; await flushPromises(); expect(errors.textContent).toBe('{}'); button.click(); await flushPromises(); expect(spy).toHaveBeenLastCalledWith(expected); }); test('unmounted fields gets unregistered and their values are kept if configured on the field level', async () => { const showFields = ref(true); const wrapper = mountWithHoc({ setup() { const schema = computed(() => ({ field: showFields.value ? 'required' : '', drink: 'required', })); return { schema, showFields, }; }, template: ` Coke {{ errors }} {{ values }} `, }); await flushPromises(); const errors = wrapper.$el.querySelector('#errors'); const values = wrapper.$el.querySelector('#values'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(errors.textContent).toBeTruthy(); setChecked(inputs[4]); setChecked(inputs[5]); setValue(inputs[0], 'test'); setValue(inputs[1], '12'); setValue(inputs[2], '12'); await flushPromises(); expect(JSON.parse(values.textContent)).toEqual({ drink: ['Tea', 'Coke'], field: 'test', 'non-nested.field': '12', nested: { field: '12' }, }); showFields.value = false; await flushPromises(); // errors were cleared expect(errors.textContent).toBe('{}'); expect(JSON.parse(values.textContent)).toEqual({ drink: ['Tea', 'Coke'], 'non-nested.field': '12', nested: { field: '12' }, }); }); test('unmounted fields gets unregistered and their submitted values are kept if configured on the field level', async () => { let showFields!: Ref; const spy = vi.fn(); const wrapper = mountWithHoc({ setup() { showFields = ref(true); return { showFields, onSubmit(values: any) { spy(values); }, }; }, template: ` Coke {{ errors }} `, }); await flushPromises(); const errors = wrapper.$el.querySelector('#errors'); const button = wrapper.$el.querySelector('button'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(errors.textContent).toBeTruthy(); setChecked(inputs[4]); setChecked(inputs[5]); setValue(inputs[0], 'test'); setValue(inputs[1], '12'); setValue(inputs[2], '12'); await flushPromises(); button.click(); await flushPromises(); expect(spy).toHaveBeenLastCalledWith({ drink: ['Tea', 'Coke'], field: 'test', 'non-nested.field': '12', nested: { field: '12' }, }); showFields.value = false; await flushPromises(); expect(errors.textContent).toBe('{}'); button.click(); await flushPromises(); expect(spy).toHaveBeenLastCalledWith({ drink: ['Tea', 'Coke'], 'non-nested.field': '12', nested: { field: '12' }, }); }); test('can specify which fields get their value kept with field setting priority', async () => { const showFields = ref(true); const wrapper = mountWithHoc({ setup() { const schema = computed(() => ({ field: showFields.value ? 'required' : '', drink: 'required', })); return { schema, showFields, }; }, template: ` {{ errors }} {{ values }} `, }); await flushPromises(); const errors = wrapper.$el.querySelector('#errors'); const values = wrapper.$el.querySelector('#values'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(errors.textContent).toBeTruthy(); setChecked(inputs[4]); setChecked(inputs[5]); setValue(inputs[0], 'test'); setValue(inputs[1], '12'); setValue(inputs[2], '12'); await flushPromises(); expect(JSON.parse(values.textContent)).toEqual({ drink: ['Tea', 'Coke'], field: 'test', 'non-nested.field': '12', nested: { field: '12' }, }); showFields.value = false; await flushPromises(); // errors were cleared expect(errors.textContent).toBe('{}'); expect(JSON.parse(values.textContent)).toEqual({ drink: ['Coke'], field: 'test', }); }); test('checkboxes with zod schema', async () => { const wrapper = mountWithHoc({ setup() { const schema = z.object({ drink: z.array(z.string()).min(1), }); return { schema, }; }, template: ` Coffee Tea Coke {{ errors.drink }} {{ values.drink && values.drink.toString() }} `, }); const err = wrapper.$el.querySelector('#err'); const values = wrapper.$el.querySelector('#values'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(err.textContent).toBe('Invalid input: expected array, received undefined'); setChecked(inputs[2]); await flushPromises(); expect(err.textContent).toBe(''); setChecked(inputs[0]); await flushPromises(); expect(err.textContent).toBe(''); setChecked(inputs[1]); await flushPromises(); expect(err.textContent).toBe(''); expect(values.textContent).toBe(['Coke', '', 'Tea'].toString()); setChecked(inputs[1], false); await flushPromises(); expect(values.textContent).toBe(['Coke', ''].toString()); }); test('checkboxes with object values', async () => { const wrapper = mountWithHoc({ setup() { const schema = z.object({ drink: z.array(z.any()).min(1), }); return { schema, }; }, template: ` Coffee Tea Coke {{ errors.drink }} {{ JSON.stringify(values.drink) }} `, }); const err = wrapper.$el.querySelector('#err'); const values = wrapper.$el.querySelector('#values'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(err.textContent).toBe('Invalid input: expected array, received undefined'); setChecked(inputs[2]); await flushPromises(); expect(err.textContent).toBe(''); setChecked(inputs[0]); await flushPromises(); expect(err.textContent).toBe(''); setChecked(inputs[1]); await flushPromises(); expect(err.textContent).toBe(''); expect(values.textContent).toBe(JSON.stringify([{ id: 'coke' }, { id: '' }, { id: 'tea' }])); setChecked(inputs[1], false); await flushPromises(); expect(values.textContent).toBe(JSON.stringify([{ id: 'coke' }, { id: '' }])); }); test('checkboxes v-model value syncing', async () => { const drinks = ref([]); const wrapper = mountWithHoc({ setup() { const schema = z.object({ drink: z.array(z.string()).min(1), }); return { schema, drinks, }; }, template: ` Coffee Tea Coke {{ errors.drink }} {{ values.drink && values.drink.toString() }} `, }); const err = wrapper.$el.querySelector('#err'); const values = wrapper.$el.querySelector('#values'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(err.textContent).toBe('Too small: expected array to have >=1 items'); setChecked(inputs[1]); await flushPromises(); expect(err.textContent).toBe(''); expect(drinks.value).toEqual(['Tea']); drinks.value = []; await flushPromises(); expect(err.textContent).toBe('Too small: expected array to have >=1 items'); expect(values.textContent).toBe(''); drinks.value = ['Coke']; await flushPromises(); expect(err.textContent).toBe(''); expect(values.textContent).toBe(['Coke'].toString()); }); test('isSubmitting state', async () => { let throws = false; const wrapper = mountWithHoc({ setup() { onErrorCaptured(() => false); return { onSubmit() { return new Promise((resolve, reject) => { if (throws) { setTimeout(() => { reject(new Error('Sorry')); }, 500); return; } setTimeout(resolve, 1000); }); }, }; }, template: ` {{ isSubmitting }} `, }); const submit = wrapper.$el.querySelector('#submit'); const submitting = wrapper.$el.querySelector('#submitting'); submit.click(); await flushPromises(); expect(submitting.textContent).toBe('true'); vi.advanceTimersByTime(1001); await flushPromises(); expect(submitting.textContent).toBe('false'); throws = true; submit.click(); await flushPromises(); expect(submitting.textContent).toBe('true'); vi.advanceTimersByTime(501); await flushPromises(); expect(submitting.textContent).toBe('false'); }); test('isValidating state', async () => { const spy = vi.fn((isValidating: boolean) => isValidating); const Input = defineComponent({ components: { Field, }, template: ``, setup() { const isValidating = useIsValidating(); useField('field', () => { spy(isValidating.value); return true; }); }, }); const wrapper = mountWithHoc({ components: { Input, }, template: ` `, }); await flushPromises(); const button = wrapper.$el.querySelector('button'); button.click(); await flushPromises(); expect(spy).toHaveLastReturnedWith(true); }); test('aggregated meta reactivity', async () => { const wrapper = mountWithHoc({ template: ` `, }); const submitBtn = wrapper.$el.querySelector('#submit'); const input = wrapper.$el.querySelector('input'); await flushPromises(); expect(submitBtn.disabled).toBe(true); setValue(input, '12'); await flushPromises(); expect(submitBtn.disabled).toBe(false); }); test('nested object fields', async () => { const fn = vi.fn(); const wrapper = mountWithHoc({ setup() { return { onSubmit(values: any) { fn(values); }, }; }, template: `
{{ values }}
`, }); const submitBtn = wrapper.$el.querySelector('#submit'); const name = wrapper.$el.querySelector('input'); const address = wrapper.$el.querySelector('#address'); const pre = wrapper.$el.querySelector('pre'); setValue(name, '12'); setValue(address, 'abc'); await flushPromises(); expect(pre.textContent).toBe(JSON.stringify({ user: { name: '12', addresses: ['abc'] } }, null, 2)); submitBtn.click(); await flushPromises(); expect(fn).toHaveBeenCalledWith({ user: { name: '12', addresses: ['abc'] } }); }); test('nested object fields validation with yup nested objects', async () => { const fn = vi.fn(); const wrapper = mountWithHoc({ setup() { return { schema: z.object({ user: z.object({ name: z.string().min(1, REQUIRED_MESSAGE), addresses: z.array(z.string().min(3, REQUIRED_MESSAGE)).min(1, REQUIRED_MESSAGE), }), }), onSubmit(values: any) { fn(values); }, }; }, template: ` {{ errors['user.name'] }} {{ errors['user.addresses[0]'] }} `, }); const submitBtn = wrapper.$el.querySelector('#submit'); const name = wrapper.$el.querySelector('input'); const nameErr = wrapper.$el.querySelector('#nameErr'); const address = wrapper.$el.querySelector('#address'); const addrErr = wrapper.$el.querySelector('#addrErr'); submitBtn.click(); await flushPromises(); expect(fn).not.toHaveBeenCalled(); expect(nameErr.textContent).toBeTruthy(); expect(addrErr.textContent).toBeTruthy(); setValue(name, '12'); setValue(address, 'abc'); await flushPromises(); expect(nameErr.textContent).toBe(''); expect(addrErr.textContent).toBe(''); submitBtn.click(); await flushPromises(); expect(fn).toHaveBeenCalledWith({ user: { name: '12', addresses: ['abc'] } }); }); test('can opt out of nested object fields', async () => { const fn = vi.fn(); const wrapper = mountWithHoc({ setup() { return { onSubmit(values: any) { fn(values); }, }; }, template: `
{{ values }}
`, }); const submitBtn = wrapper.$el.querySelector('#submit'); const name = wrapper.$el.querySelector('input'); const address = wrapper.$el.querySelector('#address'); const pre = wrapper.$el.querySelector('pre'); setValue(name, '12'); setValue(address, 'abc'); await flushPromises(); expect(pre.textContent).toBe(JSON.stringify({ 'user.name': '12', 'user.addresses.0': 'abc' }, null, 2)); submitBtn.click(); await flushPromises(); expect(fn).toHaveBeenCalledWith({ 'user.name': '12', 'user.addresses.0': 'abc' }); }); test('validate fields on mount with validateOnMount = true', async () => { const wrapper = mountWithHoc({ setup() { const schema = z.object({ email: z.string().email(), password: z.string().min(8), }); return { schema, }; }, template: ` {{ errors.email }} {{ errors.password }} `, }); await flushPromises(); const emailError = wrapper.$el.querySelector('#emailErr'); const passwordError = wrapper.$el.querySelector('#passwordErr'); await flushPromises(); expect(emailError.textContent).toBe('Invalid input: expected string, received undefined'); expect(passwordError.textContent).toBe('Invalid input: expected string, received undefined'); }); test('sets individual field error message with setFieldError()', async () => { const wrapper = mountWithHoc({ template: ` {{ errors.email }} `, }); await flushPromises(); const emailError = wrapper.$el.querySelector('#emailErr'); (wrapper.$refs as any)?.form.setFieldError('email', 'WRONG'); await flushPromises(); expect(emailError.textContent).toBe('WRONG'); }); test('sets multiple field error messages with setErrors()', async () => { const wrapper = mountWithHoc({ template: ` {{ errors.email }} {{ errors.password }} `, }); await flushPromises(); const emailError = wrapper.$el.querySelector('#emailErr'); const passwordError = wrapper.$el.querySelector('#passwordErr'); (wrapper.$refs as any)?.form.setErrors({ email: 'WRONG', password: 'WRONG AGAIN', }); await flushPromises(); expect(emailError.textContent).toBe('WRONG'); expect(passwordError.textContent).toBe('WRONG AGAIN'); }); test('sets error message with setFieldError for checkboxes', async () => { const wrapper = mountWithHoc({ template: ` Coffee Tea Coke {{ errors.drink }} `, }); await flushPromises(); const error = wrapper.$el.querySelector('#err'); (wrapper.$refs as any)?.form.setFieldError('drink', 'WRONG'); await flushPromises(); expect(error.textContent).toBe('WRONG'); }); test('sets individual field value with setFieldValue()', async () => { const wrapper = mountWithHoc({ template: ` `, }); await flushPromises(); const value = 'example@gmail.com'; const email = wrapper.$el.querySelector('#email'); (wrapper.$refs as any)?.form.setFieldValue('email', value); await flushPromises(); expect(email.value).toBe(value); }); test('sets multiple fields values with setValues()', async () => { const wrapper = mountWithHoc({ template: ` `, }); await flushPromises(); const values = { email: 'example@gmail.com', password: '12345', }; const inputs = wrapper.$el.querySelectorAll('input'); (wrapper.$refs as any)?.form.setValues(values); await flushPromises(); expect(inputs[0].value).toBe(values.email); expect(inputs[1].value).toBe(values.password); }); test('sets non-plain object field with setValues()', async () => { const wrapper = mountWithHoc({ template: ` `, }); await flushPromises(); class NonPlain { field = 5; } const nonPlain = new NonPlain(); (wrapper.$refs as any)?.form.setValues({ nonPlain, }); const realValues = (wrapper.$refs as any).form.getValues(); await flushPromises(); expect(realValues.nonPlain).toBeInstanceOf(NonPlain); expect(realValues.nonPlain).toStrictEqual(nonPlain); }); test('handles submit with handleSubmit and passing the event object', async () => { const spy = vi.fn(); const wrapper = mountWithHoc({ setup() { const schema = z.object({ email: z.string().email(), password: z.string().min(8), }); return { schema, onSubmit: spy, }; }, template: `
{{ errors.email }} {{ errors.password }}
`, }); const email = wrapper.$el.querySelector('#email'); const password = wrapper.$el.querySelector('#password'); const emailError = wrapper.$el.querySelector('#emailErr'); const passwordError = wrapper.$el.querySelector('#passwordErr'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(emailError.textContent).toBe('Invalid input: expected string, received undefined'); expect(passwordError.textContent).toBe('Invalid input: expected string, received undefined'); expect(spy).toHaveBeenCalledTimes(0); setValue(email, 'hello@email.com'); setValue(password, '12346789'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(emailError.textContent).toBe(''); expect(passwordError.textContent).toBe(''); expect(spy).toHaveBeenCalledTimes(1); }); test('handles submit with handleSubmit without the event object', async () => { const spy = vi.fn(); const wrapper = mountWithHoc({ setup() { const schema = z.object({ email: z.string().email(), password: z.string().min(8), }); return { schema, onSubmit: spy, }; }, template: `
{{ errors.email }} {{ errors.password }}
`, }); const email = wrapper.$el.querySelector('#email'); const password = wrapper.$el.querySelector('#password'); const emailError = wrapper.$el.querySelector('#emailErr'); const passwordError = wrapper.$el.querySelector('#passwordErr'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(emailError.textContent).toBe('Invalid input: expected string, received undefined'); expect(passwordError.textContent).toBe('Invalid input: expected string, received undefined'); expect(spy).toHaveBeenCalledTimes(0); setValue(email, 'hello@email.com'); setValue(password, '12346789'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(emailError.textContent).toBe(''); expect(passwordError.textContent).toBe(''); expect(spy).toHaveBeenCalledTimes(1); }); test('sets meta touched with setFieldTouched for checkboxes', async () => { const wrapper = mountWithHoc({ template: ` Coffee Tea Coke {{ meta.touched }} `, }); await flushPromises(); const meta = wrapper.$el.querySelector('#meta'); expect(meta?.textContent).toBe('false'); (wrapper.$refs as any)?.form.setFieldTouched('drink', true); await flushPromises(); expect(meta?.textContent).toBe('true'); }); test('sets initial errors with initialErrors', async () => { const errors = { password: 'too short', email: 'wrong', }; const wrapper = mountWithHoc({ setup() { return { errors, }; }, template: ` `, }); await flushPromises(); const errorEls = wrapper.$el.querySelectorAll('span'); await flushPromises(); expect(errorEls[0].textContent).toBe(errors.email); expect(errorEls[1].textContent).toBe(errors.password); }); test('sets touched with initial touched', async () => { const touched = { email: true, }; const wrapper = mountWithHoc({ setup() { return { touched, }; }, template: ` {{ meta.touched }} `, }); await flushPromises(); const meta = wrapper.$el.querySelector('span'); await flushPromises(); expect(meta.textContent).toBe('true'); }); test('counts the number of submission attempts', async () => { const spy = vi.fn(); const wrapper = mountWithHoc({ setup() { return { onSubmit: spy, }; }, template: ` {{ submitCount }} `, }); await flushPromises(); const countSpan = wrapper.$el.querySelector('span'); expect(countSpan.textContent).toBe('0'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(countSpan.textContent).toBe('1'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(countSpan.textContent).toBe('2'); expect(spy).toHaveReturnedTimes(2); }); test('can reset the submit count to whatever value with resetForm', async () => { const wrapper = mountWithHoc({ setup() { return { onSubmit: vi.fn(), }; }, template: ` {{ submitCount }} `, }); await flushPromises(); const countSpan = wrapper.$el.querySelector('span'); expect(countSpan.textContent).toBe('0'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(countSpan.textContent).toBe('1'); wrapper.$el.querySelector('#reset').click(); await flushPromises(); expect(countSpan.textContent).toBe('5'); }); // #3084 test('reset should not toggle the checkbox values', async () => { const wrapper = mountWithHoc({ template: ` `, }); await flushPromises(); const input = wrapper.$el.querySelector('input'); const btn = wrapper.$el.querySelector('button'); setChecked(input, true); await flushPromises(); btn.click(); await flushPromises(); expect(input.checked).toBe(false); btn.click(); await flushPromises(); expect(input.checked).toBe(false); }); // #3166 test('fields replacing others with the same name should have their value set correctly', async () => { const data = [ { id: 1, title: 'this is a test no 1', }, { id: 2, title: 'this is a test no 2', }, { id: 3, title: 'this is a test no 3', }, { id: 4, title: 'this is a test no 4', }, ]; let setModified!: (field: { id: number; title: string }) => void; mountWithHoc({ setup() { const fields = ref(data); const modified = ref({ id: -1, title: '' }); setModified = (item: { id: number; title: string }) => { modified.value = { ...item }; }; return { fields, setModified, modified, }; }, template: `
`, }); await flushPromises(); const input = () => document.querySelector('input'); setModified(data[3]); await flushPromises(); expect(input()?.value).toBe(data[3].title); setModified(data[2]); await flushPromises(); expect(input()?.value).toBe(data[2].title); }); test('resetForm should reset the meta flag', async () => { const wrapper = mountWithHoc({ template: ` {{ meta.valid ? 'valid' : 'invalid' }} `, }); await flushPromises(); const span = wrapper.$el.querySelector('#meta'); const input = wrapper.$el.querySelector('input'); expect(span.textContent).toBe('invalid'); setValue(input, ''); await flushPromises(); expect(span.textContent).toBe('invalid'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(span.textContent).toBe('invalid'); }); test('resetForm should reset the meta flag based on the errors length', async () => { const wrapper = mountWithHoc({ template: ` {{ meta.valid ? 'valid' : 'invalid' }} `, }); await flushPromises(); const span = wrapper.$el.querySelector('#meta'); expect(span.textContent).toBe('valid'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(span.textContent).toBe('invalid'); }); test('valid flag should reflect the accurate form validity', async () => { const wrapper = mountWithHoc({ template: ` {{ meta.valid ? 'valid' : 'invalid' }} `, }); await flushPromises(); const span = wrapper.$el.querySelector('#meta'); expect(span.textContent).toBe('invalid'); const email = wrapper.$el.querySelector('#email'); setValue(email, ''); await flushPromises(); // the email field is invalid expect(span.textContent).toBe('invalid'); // should be valid now setValue(email, 'example@test.com'); await flushPromises(); // still invalid because the password is invalid expect(span.textContent).toBe('invalid'); const password = wrapper.$el.querySelector('#password'); setValue(password, '12'); await flushPromises(); expect(span.textContent).toBe('valid'); }); // #3228 test('should not validate touched fields with yup schema if other fields value change', async () => { const wrapper = mountWithHoc({ setup() { const schema = z.object({ email: z.string().min(1, REQUIRED_MESSAGE), password: z.string().min(1, REQUIRED_MESSAGE), }); return { schema, }; }, template: ` {{ errors.email }} `, }); await flushPromises(); const span = wrapper.$el.querySelector('span'); const email = wrapper.$el.querySelector('#email'); const password = wrapper.$el.querySelector('#password'); // the field is now blurred dispatchEvent(email, 'blur'); await flushPromises(); // no error messages for email expect(span.textContent).toBe(''); // should be valid now setValue(password, ''); await flushPromises(); // again there should be no error messages for email, only the password expect(span.textContent).toBe(''); }); test('can set multiple field errors on the form level', async () => { const wrapper = mountWithHoc({ template: `
  • {{ error }}
`, }); await flushPromises(); const list = document.querySelector('ul'); expect(list?.children).toHaveLength(0); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(list?.children).toHaveLength(2); expect(list?.textContent).toBe('badwrong'); }); test('supports computed yup schemas', async () => { mountWithHoc({ setup() { const acceptList = ref(['1', '2']); const schema = computed(() => { return z.object({ password: z.string().refine(val => acceptList.value.includes(val), { message: 'not allowed', }), }); }); return { schema, }; }, template: ` {{ errors.password }} `, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; expect(document.querySelector('span')?.textContent).toBe(''); setValue(input, '3'); await flushPromises(); // 3 is not allowed yet expect(document.querySelector('span')?.textContent).toBeTruthy(); await flushPromises(); // field is re-validated setValue(input, '2'); await flushPromises(); expect(document.querySelector('span')?.textContent).toBe(''); }); test('re-validates when a computed yup schema changes', async () => { const acceptList = ref(['1', '2']); function addItem(item: string) { acceptList.value.push(item); } mountWithHoc({ setup() { const schema = computed(() => { // register dependency acceptList.value.length; return z .object({ password: z.string(), }) .refine(val => acceptList.value.includes(val.password), { message: 'not allowed', path: ['password'], }); }); return { schema, }; }, template: ` {{ errors.password }} `, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; expect(document.querySelector('span')?.textContent).toBe(''); setValue(input, '3'); await flushPromises(); // 3 is not allowed yet expect(document.querySelector('span')?.textContent).toBe('not allowed'); // field is re-validated automatically addItem('3'); await flushPromises(); expect(document.querySelector('span')?.textContent).toBe(''); }); test('submitting forms should touch fields', async () => { const wrapper = mountWithHoc({ setup() { return { onSubmit: vi.fn(), }; }, template: ` {{ fieldProps.meta.touched ? 'touched' : 'untouched' }} {{ meta.touched ? 'touched' : 'untouched' }} `, }); await flushPromises(); const formMeta = wrapper.$el.querySelector('#meta'); const fieldMeta = wrapper.$el.querySelector('#fieldMeta'); expect(formMeta.textContent).toBe('untouched'); expect(fieldMeta.textContent).toBe('untouched'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(formMeta.textContent).toBe('touched'); expect(fieldMeta.textContent).toBe('touched'); }); test('non-rendered fields defined in yup schema are not ignored', async () => { const wrapper = mountWithHoc({ setup() { const schema = z.object({ email: z.string().min(1, REQUIRED_MESSAGE), password: z.string().min(1, REQUIRED_MESSAGE), }); return { onSubmit: vi.fn(), schema, }; }, template: ` {{ errors.password }} `, }); await flushPromises(); const passwordError = wrapper.$el.querySelector('#passwordError'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(passwordError.textContent).toBeTruthy(); }); test('non-rendered fields defined in schema are not ignored', async () => { const submit = vi.fn(); const wrapper = mountWithHoc({ setup() { const schema = { email: 'required', password: 'required', }; return { onSubmit: submit, schema, }; }, template: ` {{ errors.password }} `, }); await flushPromises(); const passwordError = wrapper.$el.querySelector('#passwordError'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(passwordError.textContent).toBeTruthy(); expect(submit).not.toHaveBeenCalled(); }); test('setting errors for non-existing fields creates them as virtual', async () => { const errorMessage = 'bad pw'; const wrapper = mountWithHoc({ template: ` {{ errors.password }} `, }); await flushPromises(); const passwordError = wrapper.$el.querySelector('#passwordError'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(passwordError.textContent).toBe(errorMessage); }); test('setting values for non-existing fields creates them as virtual', async () => { const value = '123'; const wrapper = mountWithHoc({ template: ` {{ values.password }} `, }); await flushPromises(); const passwordValue = wrapper.$el.querySelector('#passwordValue'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(passwordValue.textContent).toBe(value); }); test('reset virtual fields errors and values', async () => { const errorMessage = 'bad pw'; const value = '123'; const wrapper = mountWithHoc({ template: ` {{ errors.password }} {{ values.password }} `, }); await flushPromises(); const passwordError = wrapper.$el.querySelector('#passwordError'); const passwordValue = wrapper.$el.querySelector('#passwordValue'); wrapper.$el.querySelector('#setValue').click(); await flushPromises(); wrapper.$el.querySelector('#setError').click(); await flushPromises(); expect(passwordError.textContent).toBe(errorMessage); expect(passwordValue.textContent).toBe(value); wrapper.$el.querySelector('#reset').click(); await flushPromises(); expect(passwordError.textContent).toBe(''); expect(passwordValue.textContent).toBe(''); }); test('reset virtual field state to specific value and error', async () => { const errorMessage = 'bad pw'; const value = '123'; const wrapper = mountWithHoc({ template: ` {{ errors.password }} {{ values.password }} `, }); await flushPromises(); const passwordError = wrapper.$el.querySelector('#passwordError'); const passwordValue = wrapper.$el.querySelector('#passwordValue'); expect(passwordError.textContent).toBe(''); expect(passwordValue.textContent).toBe(''); wrapper.$el.querySelector('#reset').click(); await flushPromises(); expect(passwordError.textContent).toBe(errorMessage); expect(passwordValue.textContent).toBe(value); }); // #3332 test('field bails prop should work with validation schema', async () => { const wrapper = mountWithHoc({ setup() { return { schema: { fname: 'required|min:3', }, }; }, template: `
{{ error }}
`, }); await flushPromises(); const input = wrapper.$el.querySelector('input'); setValue(input, ''); await flushPromises(); expect(document.querySelectorAll('.error')).toHaveLength(2); }); // #3342 test('field with pre-register errors should be checked on register', async () => { const isShown = ref(false); const modelValue = ref(''); const wrapper = mountWithHoc({ setup() { return { isShown, modelValue, schema: { fname: 'required', }, }; }, template: ` {{ errors.fname }} `, }); await flushPromises(); const span = wrapper.$el.querySelector('span'); expect(span.textContent).toBe(REQUIRED_MESSAGE); modelValue.value = 'hello'; isShown.value = true; await flushPromises(); // field was re-checked expect(span.textContent).toBe(''); }); test('field errors should be removed when its unmounted', async () => { const isShown = ref(true); const wrapper = mountWithHoc({ setup() { return { isShown, }; }, template: ` {{ errors.fname }} `, }); await flushPromises(); const span = wrapper.$el.querySelector('span'); setValue(wrapper.$el.querySelector('input'), ''); await flushPromises(); expect(span.textContent).toBe(REQUIRED_MESSAGE); isShown.value = false; await flushPromises(); // field was re-checked expect(span.textContent).toBe(''); }); test('uncontrolled fields are excluded from form state', async () => { const wrapper = mountWithHoc({ setup() { return {}; }, template: ` {{ errorMessage }} {{ errors.fname }} {{ meta.valid }} `, }); await flushPromises(); const formError = wrapper.$el.querySelector('#formError'); const fieldError = wrapper.$el.querySelector('#fieldError'); const meta = wrapper.$el.querySelector('#meta'); expect(formError.textContent).toBe(''); expect(fieldError.textContent).toBe(''); expect(meta.textContent).toBe('true'); setValue(wrapper.$el.querySelector('input'), ''); await flushPromises(); expect(formError.textContent).toBe(''); expect(fieldError.textContent).toBe(REQUIRED_MESSAGE); expect(meta.textContent).toBe('true'); }); // #3424 test('Checkbox with v-model should not propagate the empty value symbol', async () => { const value = ref(''); mountWithHoc({ setup() { return { value, }; }, template: ` `, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; setChecked(input, true); await flushPromises(); expect(value.value).toBe('CHECKED'); setChecked(input, false); await flushPromises(); expect(value.value).toBe(undefined); }); // #3429 test('Two fields of the same name should not override each other value when either is mounted', async () => { const isHidden = ref(false); const value = ref(''); mountWithHoc({ setup() { return { value, isHidden, }; }, template: ` `, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; setValue(input, '1234'); await flushPromises(); isHidden.value = true; await flushPromises(); expect(input.value).toBe(value.value); }); // #4200 test('Falsy model value should still have priority over form value', async () => { const value = ref(0); mountWithHoc({ setup() { const initials = { age: 2 }; return { value, initials, }; }, template: ` `, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; expect(input.value).toBe('0'); }); test('handles invalid submissions', async () => { const invalidSpy = vi.fn(); const validSpy = vi.fn(); const wrapper = mountWithHoc({ setup() { const schema = z.object({ email: z.string().min(1, REQUIRED_MESSAGE).email(), password: z.string().min(1, REQUIRED_MESSAGE), }); return { schema, onInvalidSubmit: invalidSpy, onSubmit: validSpy, }; }, template: ` {{ errors.email }} {{ errors.password }} `, }); const expectedEmailError = 'Invalid input: expected string, received undefined'; const expectedPasswordError = 'Invalid input: expected string, received undefined'; await flushPromises(); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(invalidSpy).toHaveBeenCalledTimes(1); expect(invalidSpy).toHaveBeenLastCalledWith({ values: { email: undefined, password: undefined, }, errors: { email: expectedEmailError, password: expectedPasswordError, }, evt: expect.anything(), results: { email: { valid: false, errors: [expectedEmailError], }, password: { valid: false, errors: [expectedPasswordError], }, }, } as InvalidSubmissionContext); expect(validSpy).not.toHaveBeenCalled(); }); test('handles invalid submissions with submitForm', async () => { const invalidSpy = vi.fn(); const validSpy = vi.fn(); const wrapper = mountWithHoc({ setup() { const schema = z.object({ email: z.string().min(1, REQUIRED_MESSAGE).email(), password: z.string().min(1, REQUIRED_MESSAGE), }); return { schema, onInvalidSubmit: invalidSpy, onSubmit: validSpy, }; }, template: `
{{ errors.email }} {{ errors.password }}
`, }); const expectedEmailError = 'Invalid input: expected string, received undefined'; const expectedPasswordError = 'Invalid input: expected string, received undefined'; await flushPromises(); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(invalidSpy).toHaveBeenCalledTimes(1); expect(invalidSpy).toHaveBeenLastCalledWith({ values: { email: undefined, password: undefined, }, errors: { email: expectedEmailError, password: expectedPasswordError, }, evt: expect.anything(), results: { email: { valid: false, errors: [expectedEmailError], }, password: { valid: false, errors: [expectedPasswordError], }, }, } as InvalidSubmissionContext); expect(validSpy).not.toHaveBeenCalled(); }); // #3551 test('resets checkboxes according to initial values', async () => { const wrapper = mountWithHoc({ setup() { return { values: { terms: true, termsUnslotted: true, array: ['coffee', 'tea'], }, }; }, template: ` `, }); const inputAt = (idx: number) => wrapper.$el.querySelectorAll('input')[idx] as HTMLInputElement; expect(inputAt(0).checked).toBe(true); expect(inputAt(1).checked).toBe(true); expect(inputAt(2).checked).toBe(true); expect(inputAt(3).checked).toBe(true); dispatchEvent('#reset1', 'click'); await flushPromises(); expect(inputAt(0).checked).toBe(true); expect(inputAt(1).checked).toBe(true); expect(inputAt(2).checked).toBe(true); expect(inputAt(3).checked).toBe(true); dispatchEvent('#reset2', 'click'); await flushPromises(); expect(inputAt(0).checked).toBe(false); expect(inputAt(1).checked).toBe(true); expect(inputAt(2).checked).toBe(true); expect(inputAt(3).checked).toBe(false); }); // #3895 #3894 test('single checkbox component with v-model in a form', async () => { const value = ref(false); const Checkbox = defineComponent({ props: { value: Boolean, modelValue: Boolean }, template: ``, setup() { const { handleChange, checked } = useField('field', undefined, { type: 'checkbox', uncheckedValue: false, checkedValue: true, syncVModel: true, }); return { handleChange, checked, }; }, }); const wrapper = mountWithHoc({ components: { Checkbox, }, setup() { return { value, }; }, template: ` `, }); await flushPromises(); const inputAt = (idx: number) => wrapper.$el.querySelectorAll('input')[idx] as HTMLInputElement; expect(value.value).toBe(false); setChecked(inputAt(0), true); await flushPromises(); expect(value.value).toBe(true); }); test('resets a single field resetField() to initial state in slot scope props', async () => { const wrapper = mountWithHoc({ template: ` {{ errors && errors[0] }} {{ meta.touched }} `, }); const error = wrapper.$el.querySelector('#error'); const touched = wrapper.$el.querySelector('#touched'); const input = wrapper.$el.querySelector('input'); expect(error.textContent).toBe(''); setValue(input, ''); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); setValue(input, '123'); dispatchEvent(input, 'blur'); await flushPromises(); expect(touched.textContent).toBe('true'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(error.textContent).toBe(''); expect(input.value).toBe(''); expect(touched.textContent).toBe('false'); }); test('resets a single field resetField() to specific state in slot scope props', async () => { const wrapper = mountWithHoc({ template: ` {{ errors && errors[0] }} {{ meta.touched }} `, }); const error = wrapper.$el.querySelector('#error'); const touched = wrapper.$el.querySelector('#touched'); const input = wrapper.$el.querySelector('input'); expect(error.textContent).toBe(''); setValue(input, ''); await flushPromises(); expect(error.textContent).toBe(REQUIRED_MESSAGE); setValue(input, '123'); dispatchEvent(input, 'blur'); await flushPromises(); expect(touched.textContent).toBe('true'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(error.textContent).toBe(''); expect(input.value).toBe('test'); expect(touched.textContent).toBe('true'); }); test('exposes values and meta with getValues and getMeta exposed APIs', async () => { const formRef = ref>(); const wrapper = mountWithHoc({ template: ` `, setup() { return { formRef, }; }, }); const input = wrapper.$el.querySelector('input'); setValue(input, '1'); dispatchEvent(input, 'blur'); await flushPromises(); expect(formRef.value?.getValues()).toEqual({ field: '1' }); expect(formRef.value?.getMeta()).toEqual({ touched: true, dirty: true, valid: false, pending: false, initialValues: {}, }); expect(formRef.value?.getErrors()).toEqual({ field: MIN_MESSAGE }); }); }); // #3963 test('unmounted radio fields gets unregistered and their submitted values are kept if configured on the form level', async () => { let showFields!: Ref; const spy = vi.fn(); const wrapper = mountWithHoc({ setup() { showFields = ref(true); return { showFields, onSubmit(values: any) { spy(values); }, }; }, template: ` Coke {{ errors }} `, }); await flushPromises(); const errors = wrapper.$el.querySelector('#errors'); const button = wrapper.$el.querySelector('button'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(errors.textContent).toBeTruthy(); setChecked(inputs[1]); await flushPromises(); button.click(); await flushPromises(); const expected = { drink: 'Tea', }; expect(spy).toHaveBeenLastCalledWith(expected); showFields.value = false; await flushPromises(); expect(errors.textContent).toBe('{}'); button.click(); await flushPromises(); expect(spy).toHaveBeenLastCalledWith(expected); }); // #3963 test('unmounted radio fields gets unregistered and their submitted values are removed', async () => { let showFields!: Ref; const spy = vi.fn(); const wrapper = mountWithHoc({ setup() { showFields = ref(true); return { showFields, onSubmit(values: any) { spy(values); }, }; }, template: ` Coke {{ errors }} `, }); await flushPromises(); const errors = wrapper.$el.querySelector('#errors'); const button = wrapper.$el.querySelector('button'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(errors.textContent).toBeTruthy(); setChecked(inputs[1]); await flushPromises(); button.click(); await flushPromises(); expect(spy).toHaveBeenLastCalledWith({ drink: 'Tea' }); showFields.value = false; await flushPromises(); expect(errors.textContent).toBe('{}'); button.click(); await flushPromises(); expect(spy).toHaveBeenLastCalledWith({}); }); // #3963 test('unmounted radio fields gets unregistered and their values are removed if configured on the field level', async () => { const showFields = ref(true); const wrapper = mountWithHoc({ setup() { return { showFields, }; }, template: ` Coke {{ errors }} {{ values }} `, }); await flushPromises(); const errors = wrapper.$el.querySelector('#errors'); const values = wrapper.$el.querySelector('#values'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); expect(errors.textContent).toBeTruthy(); setChecked(inputs[1]); await flushPromises(); expect(JSON.parse(values.textContent)).toEqual({ drink: 'Tea', }); showFields.value = false; await flushPromises(); // errors were cleared expect(errors.textContent).toBe('{}'); expect(JSON.parse(values.textContent)).toEqual({}); }); // #4247 test('unmounted fields should not be validated when keep-values is on', async () => { let showFields!: Ref; const spy = vi.fn(); const wrapper = mountWithHoc({ setup() { showFields = ref(true); return { showFields, onSubmit(values: any) { spy(values); }, }; }, template: ` `, }); await flushPromises(); const button = wrapper.$el.querySelector('button'); const inputs = wrapper.$el.querySelectorAll('input'); wrapper.$el.querySelector('button').click(); await flushPromises(); setValue(inputs[0], ''); showFields.value = false; await flushPromises(); button.click(); await flushPromises(); const expected = { test: '', }; expect(spy).toHaveBeenLastCalledWith(expected); }); // #4308 test('radio fields with single field component binding', async () => { const submit = vi.fn(); const model = ref(''); const wrapper = mountWithHoc({ setup() { return { onSubmit: submit, model, }; }, template: ` `, }); const inputs = wrapper.$el.querySelectorAll('input'); const button = wrapper.$el.querySelector('button'); wrapper.$el.querySelector('button').click(); await flushPromises(); setChecked(inputs[0]); await flushPromises(); button.click(); await flushPromises(); expect(model.value).toBe('Coffee'); expect(submit).toHaveBeenLastCalledWith({ drink: 'Coffee' }, expect.anything()); setChecked(inputs[1]); await flushPromises(); button.click(); await flushPromises(); expect(model.value).toBe('Tea'); expect(submit).toHaveBeenLastCalledWith({ drink: 'Tea' }, expect.anything()); }); // #4643 test('removes proper pathState when field is unmounting', async () => { const renderTemplateField = ref(false); let form!: PrivateFormContext; mountWithHoc({ template: `
`, setup() { form = useForm() as unknown as PrivateFormContext; useField('foo'); return { renderTemplateField }; }, }); expect(form.meta.value.valid).toBe(true); expect(form.getAllPathStates()).toMatchObject([{ id: 0, path: 'foo' }]); renderTemplateField.value = true; await flushPromises(); expect(form.meta.value.valid).toBe(false); expect(form.getAllPathStates()).toMatchObject([ { id: 0, path: 'foo' }, { id: 1, path: 'foo' }, ]); renderTemplateField.value = false; await flushPromises(); expect(form.meta.value.valid).toBe(true); expect(form.getAllPathStates()).toMatchObject([{ id: 0, path: 'foo' }]); }); test('handles onSubmit with generic object from zod schema', async () => { const schema = z.object({ email: z.string().email(), password: z.string().min(8), }); type FormValues = z.infer; const submitSpy = vi.fn((values: FormValues) => { void values.email; void values.password; }); mountWithHoc({ setup() { return { schema, onSubmit: submitSpy, }; }, template: ` `, }); await flushPromises(); const email = document.querySelector('input[type="email"]') as HTMLInputElement; const password = document.querySelector('input[type="password"]') as HTMLInputElement; setValue(email, 'test@example.com'); setValue(password, 'password123'); document.querySelector('button')?.click(); await flushPromises(); expect(submitSpy).toHaveBeenCalledWith( { email: 'test@example.com', password: 'password123', }, expect.anything(), ); }); ================================================ FILE: packages/vee-validate/tests/define.spec.ts ================================================ import { defineRule, validate } from '@/vee-validate'; test('passing a non-function as the validate method will throw', () => { expect(() => { defineRule('noFn', '' as unknown as (value: any) => boolean); }).toThrow(); }); test('define global validations using string ids', async () => { defineRule('test-direct', (value, _, { field }) => { if (value === '1') { return 'Cannot be 1'; } if (value === '2') { return `${field} Cannot be 2`; } return true; }); let result = await validate('1', 'test-direct'); expect(result.errors[0]).toBe('Cannot be 1'); result = await validate('2', 'test-direct', { name: 'test' }); expect(result.errors[0]).toBe('test Cannot be 2'); }); ================================================ FILE: packages/vee-validate/tests/helpers/ModelComp.ts ================================================ export const ModelComp = { props: ['modelValue', 'name', 'test'], emits: ['blur', 'update:modelValue'], inheritAttrs: false, template: `
{{ test }}
`, }; export const CustomModelComp = { props: ['value', 'name', 'test'], emits: ['blur', 'update:value'], inheritAttrs: false, template: `
{{ test }}
`, }; ================================================ FILE: packages/vee-validate/tests/helpers/index.ts ================================================ import { createApp, ComponentPublicInstance } from 'vue'; import flushP from 'flush-promises'; import { Field, Form, ErrorMessage, FieldArray } from '@/vee-validate'; export function mount(component: Record) { const app = createApp(component); // app.config.devtools = false; document.body.innerHTML = `
`; const vm = app.mount('#app'); return vm; } export function mountWithHoc(component: Record) { component.components = { ...(component.components || {}), Field, VForm: Form, ErrorMessage, FieldArray, }; return mount(component); } const HTML_TAGS = ['INPUT', 'SELECT']; export function setValue(node: ComponentPublicInstance | HTMLInputElement, value: any) { if (HTML_TAGS.includes((node as any).tagName)) { const input = node as HTMLInputElement; input.value = value; input.dispatchEvent(new window.Event('input')); input.dispatchEvent(new window.Event('change')); return; } (node as any).$emit('input', value); } export function getValue(selectorOrNode: HTMLElement | string) { if (typeof selectorOrNode === 'string') { const el = document.querySelector(selectorOrNode) as HTMLInputElement | null; return el?.value; } return (selectorOrNode as HTMLInputElement)?.value; } export function setChecked(node: HTMLInputElement, status?: boolean) { node.checked = status !== undefined ? status : !node.checked; node.dispatchEvent(new window.Event('change')); node.dispatchEvent(new window.Event('input')); } export function dispatchEvent(node: ComponentPublicInstance | HTMLElement | string, eventName: string) { if (typeof node === 'string') { const el = document.querySelector(node) as HTMLElement | null; el?.dispatchEvent(new window.Event(eventName)); return; } if ('tagName' in node) { const input = node as HTMLElement; input.dispatchEvent(new window.Event(eventName)); return; } (node as any).$emit(eventName); } /** * Ensures promises and timers are flushed properly including debounce time */ export async function flushPromises() { await flushP(); vi.advanceTimersByTime(5); await flushP(); } export async function dispatchFileEvent(input: HTMLInputElement, name: string | string[]) { const createFile = (filename: string) => new File([new ArrayBuffer(2e5)], filename, { lastModified: 0, type: 'image/jpeg' }); const files = Array.isArray(name) ? name.map(createFile) : [createFile(name)]; const event = new Event('change'); Object.defineProperty(event, 'target', { get() { return { type: 'file', multiple: input.multiple, files, }; }, }); input.dispatchEvent(event); await flushPromises(); } export async function runInSetup(cb: () => any) { mountWithHoc({ setup() { cb(); }, template: `
`, }); } ================================================ FILE: packages/vee-validate/tests/useField.spec.ts ================================================ import { FieldContext, FormContext, useField, useForm } from '@/vee-validate'; import { defineComponent, onMounted, ref } from 'vue'; import { mountWithHoc, setValue, flushPromises } from './helpers'; describe('useField()', () => { const REQUIRED_MESSAGE = 'Field is required'; const MIN_MESSAGE = 'Field must be at least 3'; test('validates when value changes', async () => { mountWithHoc({ setup() { const { value, errorMessage } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); return { value, errorMessage, }; }, template: ` {{ errorMessage }} `, }); const input = document.querySelector('input'); const error = document.querySelector('span'); setValue(input as any, ''); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); }); test.skip('warns when nested value changes', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const { value, errorMessage } = useField('field', undefined, { initialValue: { name: 'test' }, }); onMounted(() => { value.value.name = ''; }); return { value, errorMessage, }; }, template: ` {{ errorMessage }} `, }); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); test('valid flag is correct after reset', async () => { mountWithHoc({ setup() { const { value: value1, meta: meta1, resetField: reset1, } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); const { value: value2, meta: meta2, resetField: reset2, } = useField('field', val => (!val || (val as string).length >= 3 ? true : MIN_MESSAGE)); return { value1, value2, meta1, meta2, reset1, reset2, }; }, template: ` {{ meta1.valid ? 'valid' : 'invalid' }} {{ meta2.valid ? 'valid' : 'invalid' }} `, }); const input1 = document.querySelector('#input1') as HTMLInputElement; const meta1 = document.querySelector('#meta1'); const input2 = document.querySelector('#input2') as HTMLInputElement; const meta2 = document.querySelector('#meta2'); await flushPromises(); expect(meta1?.textContent).toBe('invalid'); expect(meta2?.textContent).toBe('valid'); setValue(input1, '12'); setValue(input2, '12'); await flushPromises(); expect(meta1?.textContent).toBe('valid'); expect(meta2?.textContent).toBe('invalid'); // trigger reset (document.querySelector('#r1') as HTMLButtonElement).click(); (document.querySelector('#r2') as HTMLButtonElement).click(); await flushPromises(); expect(meta1?.textContent).toBe('invalid'); expect(meta2?.textContent).toBe('valid'); }); test('valid flag is synced with fields errors length', async () => { mountWithHoc({ setup() { const { value, meta, resetField } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); return { value, meta, resetField, }; }, template: ` {{ meta.valid ? 'valid' : 'invalid' }} `, }); await flushPromises(); const meta = document.querySelector('#meta'); const input = document.querySelector('input') as HTMLInputElement; expect(meta?.textContent).toBe('invalid'); setValue(input, '12'); await flushPromises(); expect(meta?.textContent).toBe('valid'); // trigger reset document.querySelector('button')?.click(); await flushPromises(); expect(meta?.textContent).toBe('invalid'); }); test('dirty flag is false after reset', async () => { mountWithHoc({ setup() { const { value, meta, resetField } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); return { value, meta, resetField, }; }, template: ` {{ meta.dirty ? 'dirty' : 'clean' }} `, }); const input = document.querySelector('input') as HTMLInputElement; const meta = document.querySelector('#meta'); await flushPromises(); expect(meta?.textContent).toBe('clean'); setValue(input, ''); await flushPromises(); expect(meta?.textContent).toBe('dirty'); // trigger reset document.querySelector('button')?.click(); await flushPromises(); expect(meta?.textContent).toBe('clean'); }); test('dirty flag is false after reset with a new value', async () => { mountWithHoc({ setup() { const { value, meta, resetField } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); return { value, meta, resetField, }; }, template: ` {{ meta.dirty ? 'dirty' : 'clean' }} `, }); const input = document.querySelector('input') as HTMLInputElement; const meta = document.querySelector('#meta'); await flushPromises(); expect(meta?.textContent).toBe('clean'); setValue(input, ''); await flushPromises(); expect(meta?.textContent).toBe('dirty'); // trigger reset document.querySelector('button')?.click(); await flushPromises(); expect(meta?.textContent).toBe('clean'); }); // #3891 test('dirty flag is false after reset with a new value when a form is present', async () => { mountWithHoc({ setup() { useForm(); const { value, meta, resetField } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); return { value, meta, resetField, }; }, template: ` {{ meta.dirty ? 'dirty' : 'clean' }} `, }); const input = document.querySelector('input') as HTMLInputElement; const meta = document.querySelector('#meta'); await flushPromises(); expect(meta?.textContent).toBe('clean'); setValue(input, ''); await flushPromises(); expect(meta?.textContent).toBe('dirty'); // trigger reset document.querySelector('button')?.click(); await flushPromises(); expect(meta?.textContent).toBe('clean'); }); describe('has validation modes', () => { test('silent mode does not generate messages', async () => { let validateFn!: ReturnType['validate']; mountWithHoc({ setup() { const { errorMessage, validate } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); validateFn = validate; return { errorMessage, }; }, template: ` {{ errorMessage }} `, }); const error = document.querySelector('span'); expect(error?.textContent).toBe(''); // won't show any errors await validateFn({ mode: 'silent' }); await flushPromises(); expect(error?.textContent).toBe(''); }); test('validated-only mode only generates messages when it was validated before by user action', async () => { let validateFn!: ReturnType['validate']; mountWithHoc({ setup() { const { errorMessage, validate, value, setErrors } = useField('field', val => val ? true : REQUIRED_MESSAGE, ); validateFn = validate; // marks it as dirty/touched value.value = ''; onMounted(() => { setErrors(''); }); return { errorMessage, }; }, template: ` {{ errorMessage }} `, }); const error = document.querySelector('span'); expect(error?.textContent).toBe(''); // won't show any errors await validateFn({ mode: 'validated-only' }); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); }); test('force mode always generates new error messages', async () => { let validateFn!: ReturnType['validate']; mountWithHoc({ setup() { const { errorMessage, validate } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); validateFn = validate; return { errorMessage, }; }, template: ` {{ errorMessage }} `, }); const error = document.querySelector('span'); expect(error?.textContent).toBe(''); // won't show any errors await validateFn({ mode: 'force' }); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); }); }); describe('generic function chains', () => { test('when bails is true', async () => { mountWithHoc({ setup() { const { value: value1, meta: meta1, errors: errors1, resetField: reset1, } = useField('field', [ val => (val ? true : REQUIRED_MESSAGE), val => ((val as string)?.length >= 3 ? true : MIN_MESSAGE), ]); const { value: value2, meta: meta2, errors: errors2, resetField: reset2, } = useField('field', [ val => ((val as string)?.length >= 3 ? true : MIN_MESSAGE), val => (val ? true : REQUIRED_MESSAGE), ]); return { value1, value2, meta1, meta2, errors1, errors2, reset1, reset2, }; }, template: ` {{ meta1.valid ? 'valid' : 'invalid' }} {{ errors1.length }} {{ e }} {{ meta2.valid ? 'valid' : 'invalid' }} {{ errors2.length }} {{ e }} `, }); const input1 = document.querySelector('#input1') as HTMLInputElement; const meta1 = document.querySelector('#meta1'); const errors1 = document.querySelector('#errors1'); const input2 = document.querySelector('#input2') as HTMLInputElement; const meta2 = document.querySelector('#meta2'); const errors2 = document.querySelector('#errors2'); await flushPromises(); expect(meta1?.textContent).toBe('invalid'); expect(meta2?.textContent).toBe('invalid'); setValue(input1, ''); setValue(input2, ''); await flushPromises(); let errorMessage10 = document.querySelector('#errormessage10'); let errorMessage20 = document.querySelector('#errormessage20'); expect(meta1?.textContent).toBe('invalid'); expect(meta2?.textContent).toBe('invalid'); expect(errors1?.textContent).toBe('1'); expect(errors2?.textContent).toBe('1'); expect(errorMessage10?.textContent).toBe(REQUIRED_MESSAGE); expect(errorMessage20?.textContent).toBe(MIN_MESSAGE); setValue(input1, '12'); setValue(input2, '12'); await flushPromises(); errorMessage10 = document.querySelector('#errormessage10'); errorMessage20 = document.querySelector('#errormessage20'); expect(meta1?.textContent).toBe('invalid'); expect(meta2?.textContent).toBe('invalid'); expect(errors1?.textContent).toBe('1'); expect(errors2?.textContent).toBe('1'); expect(errorMessage10?.textContent).toBe(MIN_MESSAGE); expect(errorMessage20?.textContent).toBe(MIN_MESSAGE); setValue(input1, '123'); setValue(input2, '123'); await flushPromises(); expect(meta1?.textContent).toBe('valid'); expect(meta2?.textContent).toBe('valid'); expect(errors1?.textContent).toBe('0'); expect(errors2?.textContent).toBe('0'); // trigger reset (document.querySelector('#r1') as HTMLButtonElement).click(); (document.querySelector('#r2') as HTMLButtonElement).click(); await flushPromises(); expect(meta1?.textContent).toBe('invalid'); expect(meta2?.textContent).toBe('invalid'); }); test('when bails is false', async () => { mountWithHoc({ setup() { const { value: value1, meta: meta1, errors: errors1, resetField: reset1, } = useField( 'field', [val => (val ? true : REQUIRED_MESSAGE), val => ((val as string)?.length >= 3 ? true : MIN_MESSAGE)], { bails: false }, ); const { value: value2, meta: meta2, errors: errors2, resetField: reset2, } = useField( 'field', [val => ((val as string)?.length >= 3 ? true : MIN_MESSAGE), val => (val ? true : REQUIRED_MESSAGE)], { bails: false }, ); return { value1, value2, meta1, meta2, errors1, errors2, reset1, reset2, }; }, template: ` {{ meta1.valid ? 'valid' : 'invalid' }} {{ errors1.length }} {{ e }} {{ meta2.valid ? 'valid' : 'invalid' }} {{ errors2.length }} {{ e }} `, }); const input1 = document.querySelector('#input1') as HTMLInputElement; const meta1 = document.querySelector('#meta1'); const errors1 = document.querySelector('#errors1'); const input2 = document.querySelector('#input2') as HTMLInputElement; const meta2 = document.querySelector('#meta2'); const errors2 = document.querySelector('#errors2'); await flushPromises(); expect(meta1?.textContent).toBe('invalid'); expect(meta2?.textContent).toBe('invalid'); setValue(input1, ''); setValue(input2, ''); await flushPromises(); let errorMessage10 = document.querySelector('#errormessage10'); const errorMessage11 = document.querySelector('#errormessage11'); let errorMessage20 = document.querySelector('#errormessage20'); const errorMessage21 = document.querySelector('#errormessage21'); expect(meta1?.textContent).toBe('invalid'); expect(meta2?.textContent).toBe('invalid'); expect(errors1?.textContent).toBe('2'); expect(errors2?.textContent).toBe('2'); expect(errorMessage10?.textContent).toBe(REQUIRED_MESSAGE); expect(errorMessage11?.textContent).toBe(MIN_MESSAGE); expect(errorMessage20?.textContent).toBe(MIN_MESSAGE); expect(errorMessage21?.textContent).toBe(REQUIRED_MESSAGE); setValue(input1, '12'); setValue(input2, '12'); await flushPromises(); errorMessage10 = document.querySelector('#errormessage10'); errorMessage20 = document.querySelector('#errormessage20'); expect(meta1?.textContent).toBe('invalid'); expect(meta2?.textContent).toBe('invalid'); expect(errors1?.textContent).toBe('1'); expect(errors2?.textContent).toBe('1'); expect(errorMessage10?.textContent).toBe(MIN_MESSAGE); expect(errorMessage20?.textContent).toBe(MIN_MESSAGE); setValue(input1, '123'); setValue(input2, '123'); await flushPromises(); expect(meta1?.textContent).toBe('valid'); expect(meta2?.textContent).toBe('valid'); expect(errors1?.textContent).toBe('0'); expect(errors2?.textContent).toBe('0'); // trigger reset (document.querySelector('#r1') as HTMLButtonElement).click(); (document.querySelector('#r2') as HTMLButtonElement).click(); await flushPromises(); expect(meta1?.textContent).toBe('invalid'); expect(meta2?.textContent).toBe('invalid'); }); }); test('emits model events for v-model support and syncing', async () => { const model = ref(''); const InputComponent = defineComponent({ props: { modelValue: String, }, setup() { const { value, errorMessage } = useField('field', undefined, { syncVModel: true }); return { value, errorMessage, }; }, template: ` `, }); mountWithHoc({ components: { InputComponent, }, setup() { return { model, }; }, template: ` `, }); const input = document.querySelector('input'); setValue(input as any, '123'); await flushPromises(); expect(model.value).toBe('123'); model.value = '321'; await flushPromises(); expect(input?.value).toBe('321'); }); test('uses initial model value when sync v-model is set', async () => { const model = ref('test'); const InputComponent = defineComponent({ props: { modelValue: String, }, setup() { const { value, errorMessage } = useField('field', undefined, { syncVModel: true }); return { value, errorMessage, }; }, template: ` `, }); mountWithHoc({ components: { InputComponent, }, setup() { return { model, }; }, template: ` `, }); const input = document.querySelector('input'); await flushPromises(); expect(model.value).toBe('test'); expect(input?.value).toBe('test'); }); // #4333 test('should emit modified values with model modifiers being applied as a prop', async () => { const model = ref(''); const InputComponent = defineComponent({ props: { modelValue: String, modelModifiers: null, }, setup() { const { value, errorMessage } = useField('field', undefined, { syncVModel: true }); return { value, errorMessage, }; }, template: ` `, }); const onModelUpdated = vi.fn(); mountWithHoc({ components: { InputComponent, }, setup() { return { onModelUpdated, model, }; }, template: ` `, }); const input = document.querySelector('input'); setValue(input as any, '123'); await flushPromises(); expect(onModelUpdated).toHaveBeenLastCalledWith(123); }); test('can disable model events', async () => { const model = ref(''); const InputComponent = defineComponent({ props: { modelValue: String, }, setup() { const { value, errorMessage } = useField('field', undefined, { syncVModel: false, }); return { value, errorMessage, }; }, template: ` `, }); mountWithHoc({ components: { InputComponent, }, setup() { return { model, }; }, template: ` `, }); const input = document.querySelector('input'); setValue(input as any, '123'); await flushPromises(); expect(model.value).toBe(''); model.value = '321'; await flushPromises(); expect(input?.value).toBe('123'); }); test('emits model events for custom models support and syncing', async () => { const model = ref(''); const InputComponent = defineComponent({ props: { textVal: String, }, setup() { const { value, errorMessage } = useField('field', undefined, { syncVModel: 'textVal', }); return { value, errorMessage, }; }, template: ` `, }); mountWithHoc({ components: { InputComponent, }, setup() { return { model, }; }, template: ` `, }); const input = document.querySelector('input'); setValue(input as any, '123'); await flushPromises(); expect(model.value).toBe('123'); model.value = '321'; await flushPromises(); expect(input?.value).toBe('321'); }); // #3906 test('only latest validation run messages are used', async () => { function validator(value: string | undefined) { if (!value) { return true; } if (value.toLowerCase().startsWith('b')) { return 'not b'; } return new Promise(resolve => { setTimeout(() => { if (value.toLowerCase().startsWith('a')) { resolve('not a'); return; } resolve(true); }, 100); }); } mountWithHoc({ setup() { const { value, errorMessage } = useField('field', validator); return { value, errorMessage, }; }, template: ` {{ errorMessage }} `, }); const input = document.querySelector('input'); const error = document.querySelector('span'); setValue(input as any, 'a'); await flushPromises(); setValue(input as any, 'b'); await flushPromises(); vi.advanceTimersByTime(200); await flushPromises(); expect(error?.textContent).toBe('not b'); }); test('allows explicit forms to be provided via the form option', async () => { let form1!: FormContext; let form2!: FormContext; let field1!: FieldContext; let field2!: FieldContext; mountWithHoc({ setup() { form1 = useForm(); form2 = useForm(); field1 = useField('field', undefined, { form: form1, }); field2 = useField('field', undefined, { form: form2, }); return {}; }, template: `
`, }); await flushPromises(); field1.value.value = '1'; field2.value.value = '2'; await flushPromises(); expect(form1.values.field).toBe('1'); expect(form2.values.field).toBe('2'); }); test('allows lazy name expressions', async () => { const nameRef = ref('first'); mountWithHoc({ setup() { const { name } = useField(() => nameRef.value); return { name, }; }, template: ` {{ name }} `, }); const name = document.querySelector('span'); await flushPromises(); expect(name?.textContent).toBe('first'); nameRef.value = 'second'; await flushPromises(); expect(name?.textContent).toBe('second'); }); test('handle change validates the field by default', async () => { let field!: FieldContext; const validator = vi.fn(val => (val ? true : REQUIRED_MESSAGE)); mountWithHoc({ setup() { field = useField('field', validator); }, template: `
`, }); await flushPromises(); expect(validator).toHaveBeenCalledTimes(1); expect(field.errorMessage.value).toBe(undefined); field.handleChange(''); await flushPromises(); expect(field.errorMessage.value).toBe(REQUIRED_MESSAGE); }); test('handle change can be configured to not validate the field', async () => { let field!: FieldContext; const validator = vi.fn(val => (val ? true : REQUIRED_MESSAGE)); mountWithHoc({ setup() { field = useField('field', validator, { validateOnValueUpdate: false }); }, template: `
`, }); await flushPromises(); expect(validator).toHaveBeenCalledTimes(1); expect(field.errorMessage.value).toBe(undefined); field.handleChange('', false); await flushPromises(); expect(validator).toHaveBeenCalledTimes(2); expect(field.errorMessage.value).toBe(undefined); }); test('handleChange parses input[type=number] value', async () => { let field!: FieldContext; mountWithHoc({ setup() { field = useField('field', undefined); const { handleChange } = field; return { handleChange, }; }, template: ``, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; setValue(input, '0.00'); await flushPromises(); expect(field.value.value).toBe('0.00'); setValue(input, ''); await flushPromises(); expect(field.value.value).toBe(''); }); test('handleChange parses input[type=range] value', async () => { let field!: FieldContext; mountWithHoc({ setup() { field = useField('field', undefined); const { handleChange } = field; return { handleChange, }; }, template: ``, }); await flushPromises(); const input = document.querySelector('input') as HTMLInputElement; setValue(input, '500'); await flushPromises(); expect(field.value.value).toBe(500); setValue(input, '0'); await flushPromises(); expect(field.value.value).toBe(0); }); test('a validator can return multiple messages', async () => { let field!: FieldContext; const validator = vi.fn(val => (val ? true : [REQUIRED_MESSAGE, 'second'])); mountWithHoc({ setup() { field = useField('field', validator); return {}; }, }); await flushPromises(); expect(field.errors.value).toHaveLength(0); await field.validate(); await flushPromises(); expect(field.errors.value).toHaveLength(2); expect(field.errors.value).toEqual([REQUIRED_MESSAGE, 'second']); }); // #4323 test('resetField should not validate', async () => { let field!: FieldContext; const validator = vi.fn(val => (val ? true : REQUIRED_MESSAGE)); mountWithHoc({ setup() { useForm(); field = useField('field', validator); return {}; }, }); await flushPromises(); expect(field.errors.value).toHaveLength(0); field.resetField({ value: '' }); await flushPromises(); expect(field.errors.value).toHaveLength(0); }); // #4603 test('should not remove field value if field with same path was created between scheduling and execution of previous field unset operation', async () => { vi.useFakeTimers(); let form!: FormContext; mountWithHoc({ setup() { form = useForm(); const toggle = ref(false); const value = ref(''); onMounted(async () => { await new Promise(resolve => { setTimeout(() => resolve(null), 1000); }); toggle.value = true; value.value = 'test'; }); return { form, toggle, value }; }, template: ` `, components: { CustomField: { props: { name: String, modelValue: String, }, setup(props: any) { useField(props.name, undefined, { initialValue: props.modelValue, }); }, template: ``, }, }, }); await flushPromises(); vi.advanceTimersByTime(1000); await flushPromises(); expect(form.values.field).toEqual('test'); }); }); ================================================ FILE: packages/vee-validate/tests/useFieldArray.spec.ts ================================================ import { useForm, useFieldArray, FieldEntry, FormContext, FieldArrayContext, useField } from '@/vee-validate'; import { defineComponent, nextTick, onMounted, Ref } from 'vue'; import * as z from 'zod'; import { mountWithHoc, flushPromises, setValue } from './helpers'; test('can update a field entry model directly', async () => { mountWithHoc({ setup() { useForm({ initialValues: { users: ['1'], }, }); const { fields } = useFieldArray('users'); onMounted(() => { const item = fields.value[0]; item.value = 'test'; }); return { fields, }; }, template: `

{{ fields[0].value }}

`, }); await flushPromises(); expect(document.querySelector('p')?.innerHTML).toBe('test'); }); test('can update a field entry deep model directly and validate it', async () => { let fields!: Ref[]>; mountWithHoc({ setup() { const { errors } = useForm({ validateOnMount: true, validationSchema: z.object({ users: z.array( z.object({ name: z.string().min(1), }), ), }), initialValues: { users: [{ name: '' }], }, }); fields = useFieldArray<{ name: string }>('users').fields; return { fields, errors, }; }, template: `

{{ fields[0].value.name }}

{{ errors }} `, }); await flushPromises(); expect(document.querySelector('p')?.innerHTML).toBe(''); expect(document.querySelector('span')?.innerHTML).toBeTruthy(); const item = fields.value[0]; item.value.name = 'test'; await flushPromises(); expect(document.querySelector('p')?.innerHTML).toBe('test'); expect(document.querySelector('span')?.innerHTML).toBe('{}'); }); test('warns when updating a no-longer existing item', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { useForm({ initialValues: { users: ['1'], }, }); const { remove, fields } = useFieldArray('users'); onMounted(async () => { const item = fields.value[0]; remove(0); await nextTick(); item.value = 'test'; }); }, template: `
`, }); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); test('warns when no form context is present', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const { push } = useFieldArray('users'); push(''); }, template: `
`, }); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); test('duplicate calls yields the same instance', async () => { let removeFn!: (idx: number) => void; mountWithHoc({ setup() { useForm({ initialValues: { users: ['one'], }, }); const { fields, push } = useFieldArray('users'); const { fields: fields2, remove } = useFieldArray('users'); removeFn = remove; onMounted(() => { push('two'); }); return { fields, fields2, }; }, template: `

{{ fields.map(f => f.value).join(', ') }}

{{ fields2.map(f => f.value).join(', ') }}

`, }); await flushPromises(); expect(document.querySelector('#arr1')?.innerHTML).toBe('one, two'); expect(document.querySelector('#arr2')?.innerHTML).toBe('one, two'); removeFn(0); await flushPromises(); expect(document.querySelector('#arr1')?.innerHTML).toBe('two'); expect(document.querySelector('#arr2')?.innerHTML).toBe('two'); }); // #4096 test('array push should trigger a silent validation', async () => { let form!: FormContext; let arr!: FieldArrayContext; mountWithHoc({ setup() { form = useForm({ initialValues: { users: ['one'], }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(form.meta.value.valid).toBe(true); arr.push(''); await flushPromises(); expect(form.meta.value.valid).toBe(false); }); test('array push noop when path is not an array', async () => { let arr!: FieldArrayContext; mountWithHoc({ setup() { useForm({ initialValues: { users: 'test', }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(arr.fields.value).toHaveLength(0); arr.push(''); await flushPromises(); expect(arr.fields.value).toHaveLength(0); }); // #4096 test('array prepend should trigger a silent validation', async () => { let form!: FormContext; let arr!: FieldArrayContext; mountWithHoc({ setup() { form = useForm({ initialValues: { users: ['one'], }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(form.meta.value.valid).toBe(true); arr.prepend(''); await flushPromises(); expect(form.meta.value.valid).toBe(false); }); test('array prepend noop when path is not an array', async () => { let arr!: FieldArrayContext; mountWithHoc({ setup() { useForm({ initialValues: { users: 'test', }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(arr.fields.value).toHaveLength(0); arr.prepend(''); await flushPromises(); expect(arr.fields.value).toHaveLength(0); }); // #4096 test('array insert should trigger a silent validation', async () => { let form!: FormContext; let arr!: FieldArrayContext; mountWithHoc({ setup() { form = useForm({ initialValues: { users: ['one', 'two'], }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(form.meta.value.valid).toBe(true); arr.insert(1, ''); await flushPromises(); expect(form.meta.value.valid).toBe(false); }); test('array insert noop when path is not an array', async () => { let arr!: FieldArrayContext; mountWithHoc({ setup() { useForm({ initialValues: { users: 'test', }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(arr.fields.value).toHaveLength(0); arr.insert(0, ''); await flushPromises(); expect(arr.fields.value).toHaveLength(0); }); test('array move noop when path is not an array', async () => { let arr!: FieldArrayContext; mountWithHoc({ setup() { useForm({ initialValues: { users: 'test', }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(arr.fields.value).toHaveLength(0); arr.move(0, 1); await flushPromises(); expect(arr.fields.value).toHaveLength(0); }); test('array swap noop when path is not an array', async () => { let arr!: FieldArrayContext; mountWithHoc({ setup() { useForm({ initialValues: { users: 'test', }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(arr.fields.value).toHaveLength(0); arr.swap(0, 1); await flushPromises(); expect(arr.fields.value).toHaveLength(0); }); test('array remove noop when path is not an array', async () => { let arr!: FieldArrayContext; mountWithHoc({ setup() { useForm({ initialValues: { users: 'test', }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(arr.fields.value).toHaveLength(0); arr.remove(0); await flushPromises(); expect(arr.fields.value).toHaveLength(0); }); test('array update noop when path is not an array', async () => { let arr!: FieldArrayContext; mountWithHoc({ setup() { useForm({ initialValues: { users: 'test', }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(arr.fields.value).toHaveLength(0); arr.update(0, ''); await flushPromises(); expect(arr.fields.value).toHaveLength(0); }); test('array push initializes the array if undefined', async () => { let arr!: FieldArrayContext; mountWithHoc({ setup() { useForm({ initialValues: { users: undefined, }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(arr.fields.value).toHaveLength(0); arr.push(''); await flushPromises(); expect(arr.fields.value).toHaveLength(1); }); test('array prepend initializes the array if undefined', async () => { let arr!: FieldArrayContext; mountWithHoc({ setup() { useForm({ initialValues: { users: undefined, }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(arr.fields.value).toHaveLength(0); arr.prepend(''); await flushPromises(); expect(arr.fields.value).toHaveLength(1); }); test('array move initializes the array if undefined', async () => { let arr!: FieldArrayContext; mountWithHoc({ setup() { useForm({ initialValues: { users: undefined, }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); }, template: `
`, }); await flushPromises(); expect(arr.fields.value).toHaveLength(0); arr.move(0, 0); await flushPromises(); expect(arr.fields.value).toHaveLength(0); }); // #4557 test('errors are available to the newly inserted items', async () => { let arr!: FieldArrayContext; const InputText = defineComponent({ props: { name: { type: String, required: true, }, }, setup(props) { const { value, errorMessage } = useField(() => props.name); return { value, errorMessage, }; }, template: ' {{errorMessage}}', }); mountWithHoc({ components: { InputText }, setup() { useForm({ initialValues: { users: ['one', 'three'], }, validationSchema: z.object({ users: z.array(z.string().min(1)), }), }); arr = useFieldArray('users'); return { fields: arr.fields, }; }, template: `
`, }); const inputAt = (idx: number) => (document.querySelectorAll('input') || [])[idx] as HTMLInputElement; const spanAt = (idx: number) => (document.querySelectorAll('span') || [])[idx] as HTMLSpanElement; await flushPromises(); expect(arr.fields.value).toHaveLength(2); arr.insert(1, ''); await flushPromises(); expect(arr.fields.value).toHaveLength(3); setValue(inputAt(1), ''); await flushPromises(); expect(spanAt(1).textContent).toBeTruthy(); }); ================================================ FILE: packages/vee-validate/tests/useFieldError.spec.ts ================================================ import { useField, useFieldError, useForm } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; import { defineComponent } from 'vue'; describe('useFieldError()', () => { const REQUIRED_MESSAGE = 'Field is required'; const validate = (val: any) => (val ? true : REQUIRED_MESSAGE); test('gives access to a single field error message', async () => { mountWithHoc({ setup() { useForm(); const { value } = useField('test', validate); const message = useFieldError('test'); return { value, message, }; }, template: ` {{ message }} `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); setValue(input as any, ''); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); }); test('gives access to a single field error message in a child component with specifying a path', async () => { const CustomErrorComponent = defineComponent({ template: '{{ message }}', setup() { const message = useFieldError(); return { message, }; }, }); mountWithHoc({ components: { CustomErrorComponent, }, setup() { useForm(); const { value } = useField('test', validate); return { value, }; }, template: ` `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); setValue(input as any, ''); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); }); test('gives access to array fields error message', async () => { mountWithHoc({ setup() { useForm(); const { value } = useField('test', validate); useField('test', validate); const message = useFieldError('test'); return { value, message, }; }, template: ` {{ message }} `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); setValue(input as any, ''); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); }); test('returns undefined if field not found', async () => { mountWithHoc({ setup() { useForm(); const message = useFieldError('something'); return { message, }; }, template: ` {{ message }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe(''); }); test('returns undefined if form is not found', async () => { mountWithHoc({ setup() { const message = useFieldError('something'); return { message, }; }, template: ` {{ message }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe(''); }); }); ================================================ FILE: packages/vee-validate/tests/useFieldValue.spec.ts ================================================ import { useField, useFieldValue, useForm } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; import { defineComponent } from 'vue'; describe('useFieldValue()', () => { const REQUIRED_MESSAGE = 'Field is required'; const validate = (val: any) => (val ? true : REQUIRED_MESSAGE); test('gives access to a single field value', async () => { mountWithHoc({ setup() { useForm(); const { value, setValue } = useField('test', validate); const currValue = useFieldValue('test'); return { value, currValue, setValue, }; }, template: ` {{ currValue }} `, }); await flushPromises(); const input = document.querySelector('input'); const valueSpan = document.querySelector('span'); const inputValue = '1234'; setValue(input as any, inputValue); await flushPromises(); expect(valueSpan?.textContent).toBe(inputValue); // test value setting const btn = document.querySelector('button'); btn?.click(); await flushPromises(); expect(input?.value).toBe('5'); }); test('gives access to a single field value in a child component without specifying a path', async () => { const CustomChildValueDisplay = defineComponent({ template: '{{ value }}', setup() { const value = useFieldValue(); return { value, }; }, }); mountWithHoc({ components: { CustomChildValueDisplay, }, setup() { useForm(); const { value } = useField('test'); return { value, }; }, template: ` `, }); await flushPromises(); const input = document.querySelector('input'); const inputValue = '1234'; setValue(input as any, inputValue); await flushPromises(); const valueSpan = document.querySelector('span'); expect(valueSpan?.textContent).toBe(inputValue); }); test('returns undefined if field not found', async () => { mountWithHoc({ setup() { useForm(); const value = useFieldValue('something'); return { value, }; }, template: ` {{ value }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe(''); }); test('returns undefined if form is not found', async () => { mountWithHoc({ setup() { const value = useFieldValue('something'); return { value, }; }, template: ` {{ value }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe(''); }); }); ================================================ FILE: packages/vee-validate/tests/useForm.spec.ts ================================================ import { FieldMeta, FormContext, FormMeta, useField, useForm, defineRule, configure, FieldContext, } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises, dispatchEvent } from './helpers'; import * as z from 'zod'; import { onMounted, ref, Ref } from 'vue'; import { ModelComp, CustomModelComp } from './helpers/ModelComp'; describe('useForm()', () => { const REQUIRED_MESSAGE = 'Field is required'; test('sets individual field error message', async () => { let fieldMeta!: FieldMeta; mountWithHoc({ setup() { const { setFieldError } = useForm({ initialValues: { field: 'test' } }); const { errorMessage, meta } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); fieldMeta = meta; return { errorMessage, setFieldError, }; }, template: ` {{ errorMessage }} `, }); const error = document.querySelector('span'); await flushPromises(); expect(error?.textContent).toBe(''); document.querySelector('button')?.click(); await flushPromises(); expect(error?.textContent).toBe('WRONG'); expect(fieldMeta.valid).toBe(false); }); test('can clear individual field error messages', async () => { let setFieldError!: FormContext['setFieldError']; mountWithHoc({ setup() { const form = useForm(); setFieldError = form.setFieldError; const { errorMessage } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); return { errorMessage, setFieldError, }; }, template: ` {{ errorMessage }} `, }); await flushPromises(); const error = document.querySelector('span'); setFieldError('field', 'WRONG'); await flushPromises(); expect(error?.textContent).toBe('WRONG'); setFieldError('field', undefined); await flushPromises(); expect(error?.textContent).toBe(''); }); test('sets multiple field error messages', async () => { mountWithHoc({ setup() { const { setErrors } = useForm(); const { errorMessage: err1 } = useField('field1', val => (val ? true : REQUIRED_MESSAGE)); const { errorMessage: err2 } = useField('field2', val => (val ? true : REQUIRED_MESSAGE)); return { err1, err2, setErrors, }; }, template: ` {{ err1 }} {{ err2 }} `, }); const errors = document.querySelectorAll('span'); await flushPromises(); expect(errors[0]?.textContent).toBe(''); expect(errors[1]?.textContent).toBe(''); document.querySelector('button')?.click(); await flushPromises(); expect(errors[0]?.textContent).toBe('WRONG'); expect(errors[1]?.textContent).toBe('WRONG AGAIN'); }); test('sets individual field touched meta', async () => { mountWithHoc({ setup() { const { setFieldTouched, meta: formMeta } = useForm(); const { meta } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); return { meta, formMeta, setFieldTouched, }; }, template: ` {{ meta.touched }} {{ formMeta.touched }} `, }); const fieldMeta = document.querySelector('#field'); const formMeta = document.querySelector('#form'); await flushPromises(); expect(fieldMeta?.textContent).toBe('false'); expect(formMeta?.textContent).toBe('false'); document.querySelector('button')?.click(); await flushPromises(); expect(fieldMeta?.textContent).toBe('true'); expect(formMeta?.textContent).toBe('true'); }); test('sets multiple fields touched meta', async () => { mountWithHoc({ setup() { const { setTouched, meta: formMeta } = useForm(); const { meta: meta1 } = useField('field1', val => (val ? true : REQUIRED_MESSAGE)); const { meta: meta2 } = useField('field2', val => (val ? true : REQUIRED_MESSAGE)); return { meta1, meta2, formMeta, setTouched, }; }, template: ` {{ meta1.touched }} {{ meta2.touched }} {{ formMeta.touched }} `, }); const meta = document.querySelectorAll('span'); await flushPromises(); expect(meta[0]?.textContent).toBe('false'); expect(meta[1]?.textContent).toBe('false'); expect(meta[2]?.textContent).toBe('false'); document.querySelector('button')?.click(); await flushPromises(); expect(meta[0]?.textContent).toBe('true'); expect(meta[1]?.textContent).toBe('false'); expect(meta[2]?.textContent).toBe('true'); }); // #4359 test('setValues should validate by default', async () => { let form!: FormContext; mountWithHoc({ setup() { form = useForm({ validationSchema: z.object({ field: z.string().min(1, REQUIRED_MESSAGE) }) }); form.defineField('field'); return {}; }, template: `
`, }); await flushPromises(); expect(form.errors.value.field).toBe(undefined); form.setValues({ field: '' }); await flushPromises(); expect(form.errors.value.field).toBe(REQUIRED_MESSAGE); }); test('setValues should not validate if passed false as second arg', async () => { let form!: FormContext; mountWithHoc({ setup() { form = useForm({ validationSchema: z.object({ field: z.string().min(1, REQUIRED_MESSAGE) }) }); form.defineField('field'); return {}; }, template: `
`, }); await flushPromises(); expect(form.errors.value.field).toBe(undefined); form.setValues({ field: '' }, false); await flushPromises(); expect(form.errors.value.field).toBe(undefined); }); test('has a validate() method that returns an aggregate of validation results using field rules', async () => { let validate: any; mountWithHoc({ setup() { const form = useForm(); validate = form.validate; useField('field1', val => (val ? true : REQUIRED_MESSAGE)); useField('field2', val => (val ? true : REQUIRED_MESSAGE)); return {}; }, template: `
`, }); await flushPromises(); const result = await validate(); expect(result).toEqual({ valid: false, source: 'fields', errors: { field1: REQUIRED_MESSAGE, field2: REQUIRED_MESSAGE, }, results: { field1: { valid: false, errors: [REQUIRED_MESSAGE], }, field2: { valid: false, errors: [REQUIRED_MESSAGE], }, }, values: {}, }); }); test('has a validate method that returns an aggregate of validation results using validation schema', async () => { let validate: any; mountWithHoc({ setup() { const form = useForm({ validationSchema: z.object({ field1: z.string().min(1, 'Required'), field2: z.string().min(1, 'Required'), }), }); validate = form.validate; useField('field1'); useField('field2'); return {}; }, template: `
`, }); await flushPromises(); const pending = validate(); await flushPromises(); const result = await pending; expect(result).toEqual({ valid: false, source: 'schema', errors: { field1: 'Invalid input: expected string, received undefined', field2: 'Invalid input: expected string, received undefined', }, results: { field1: { valid: false, errors: ['Invalid input: expected string, received undefined'], }, field2: { valid: false, errors: ['Invalid input: expected string, received undefined'], }, }, }); }); test('has a validateField() method that validates a specific field', async () => { let validateField: any; mountWithHoc({ setup() { const form = useForm(); validateField = form.validateField; const { errorMessage: f1 } = useField('field1', val => (val ? true : REQUIRED_MESSAGE)); const { errorMessage: f2 } = useField('field2', val => (val ? true : REQUIRED_MESSAGE)); return { f1, f2 }; }, template: `
{{ f1 }} {{ f2 }}
`, }); await flushPromises(); const result = await validateField('field2'); expect(result).toEqual({ valid: false, errors: [REQUIRED_MESSAGE], }); expect(document.querySelector('#f2')?.textContent).toBe(REQUIRED_MESSAGE); expect(document.querySelector('#f1')?.textContent).toBe(''); }); test('warns when validateField() is called on a non-existent field', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let validateField: any; mountWithHoc({ setup() { const form = useForm(); validateField = form.validateField; return {}; }, template: `
`, }); await flushPromises(); const result = await validateField('field2'); expect(result).toEqual({ valid: true, errors: [], }); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); test('hoists nested field errors to their parent if no field has it', async () => { let form!: FormContext; mountWithHoc({ setup() { form = useForm({ validationSchema: z.object({ name: z.object({ value: z.string().min(1, 'Required'), }), }), validateOnMount: true, }); useField('name'); return {}; }, template: `
`, }); await flushPromises(); expect(form.errors.value.name).toBe('Invalid input: expected object, received undefined'); expect(form.meta.value.valid).toBe(false); }); test('selects the deepest candidate for hoisted errors', async () => { let form!: FormContext; mountWithHoc({ setup() { form = useForm({ validationSchema: z.object({ names: z.object({ value: z.array(z.object({ name: z.string().min(1, REQUIRED_MESSAGE) })), }), }), validateOnMount: true, initialValues: { names: { value: [{ name: '' }, { name: '' }, { name: '' }], }, }, }); useField('names.value'); useField('names'); return {}; }, template: `
`, }); await flushPromises(); expect(form.errors.value.names).toBe(undefined); expect(form.errors.value['names.value']).toBe(REQUIRED_MESSAGE); expect(form.meta.value.valid).toBe(false); }); test('resets the meta valid state on reset', async () => { let passwordValue!: Ref; mountWithHoc({ setup() { const { meta: formMeta, resetForm, errors } = useForm(); const { value } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); const { value: pwValue } = useField('password', val => (val ? true : REQUIRED_MESSAGE)); passwordValue = pwValue; return { value, formMeta, resetForm, errors, }; }, template: ` {{ formMeta.valid ? 'valid': 'invalid' }} {{ errors }} `, }); await flushPromises(); const span = document.querySelector('#meta'); const errors = document.querySelector('#errors'); const input = document.querySelector('input') as HTMLInputElement; expect(span?.textContent).toBe('invalid'); setValue(input, '12'); await flushPromises(); // still other field is invalid expect(span?.textContent).toBe('invalid'); // but the error is silent so errors should be empty expect(errors?.textContent).toBe('{}'); passwordValue.value = '12'; await flushPromises(); // now both should be valid expect(span?.textContent).toBe('valid'); expect(errors?.textContent).toBe('{}'); document.querySelector('button')?.click(); await flushPromises(); // validation was run again silently expect(span?.textContent).toBe('invalid'); expect(errors?.textContent).toBe('{}'); }); test('resets the meta valid state on reset with the errors length', async () => { mountWithHoc({ setup() { const { meta: formMeta, resetForm } = useForm(); const { value } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); return { value, formMeta, resetForm, }; }, template: ` {{ formMeta.valid ? 'valid': 'invalid' }} `, }); await flushPromises(); const span = document.querySelector('#meta'); expect(span?.textContent).toBe('invalid'); const input = document.querySelector('input') as HTMLInputElement; setValue(input, '12'); await flushPromises(); expect(span?.textContent).toBe('valid'); document.querySelector('button')?.click(); await flushPromises(); expect(span?.textContent).toBe('invalid'); }); test('resets the meta dirty on reset', async () => { mountWithHoc({ setup() { const { meta: formMeta, resetForm } = useForm(); const { meta: meta1, value } = useField('field1', val => (val ? true : REQUIRED_MESSAGE)); const { meta: meta2 } = useField('field2', val => (val ? true : REQUIRED_MESSAGE)); return { meta1, meta2, formMeta, resetForm, value, }; }, template: ` {{ meta1.dirty }} {{ meta2.dirty }} {{ formMeta.dirty }} `, }); const meta = document.querySelectorAll('span'); const input = document.querySelector('input') as HTMLInputElement; await flushPromises(); expect(meta[0]?.textContent).toBe('false'); expect(meta[1]?.textContent).toBe('false'); expect(meta[2]?.textContent).toBe('false'); setValue(input, '1'); await flushPromises(); expect(meta[0]?.textContent).toBe('true'); expect(meta[1]?.textContent).toBe('false'); expect(meta[2]?.textContent).toBe('true'); document.querySelector('button')?.click(); await flushPromises(); expect(meta[0]?.textContent).toBe('false'); expect(meta[1]?.textContent).toBe('false'); expect(meta[2]?.textContent).toBe('false'); }); // #3906 test('only latest schema validation run messages are used', async () => { function validator(value: string | undefined) { if (!value) { return true; } if (value.toLowerCase().startsWith('b')) { return 'not b'; } return new Promise(resolve => { setTimeout(() => { if (value.toLowerCase().startsWith('a')) { resolve('not a'); return; } resolve(true); }, 100); }); } mountWithHoc({ setup() { const { errors, defineField } = useForm({ validationSchema: { test: validator, }, }); const [model, props] = defineField('test'); return { model, errors, props, }; }, template: ` {{ errors.test }} `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); setValue(input as any, 'a'); await flushPromises(); setValue(input as any, 'b'); await flushPromises(); vi.advanceTimersByTime(200); await flushPromises(); expect(error?.textContent).toBe('not b'); }); // #3862 test('exposes controlled only values', async () => { const spy = vi.fn(); const initial = { field: '111', createdAt: Date.now(), }; mountWithHoc({ setup() { const { controlledValues, handleSubmit } = useForm({ initialValues: initial, }); const onSubmit = handleSubmit(values => { spy({ values, controlled: controlledValues.value }); }); useField('field'); onMounted(onSubmit); return {}; }, template: `
`, }); await flushPromises(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenLastCalledWith( expect.objectContaining({ values: initial, controlled: { field: initial.field }, }), ); }); // #3862 test('exposes controlled only via submission handler withControlled', async () => { const spy = vi.fn(); const initial = { field: '111', createdAt: Date.now(), }; mountWithHoc({ setup() { const { handleSubmit } = useForm({ initialValues: initial, }); const onSubmit = handleSubmit.withControlled(values => { spy({ values }); }); useField('field'); onMounted(onSubmit); return {}; }, template: `
`, }); await flushPromises(); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenLastCalledWith( expect.objectContaining({ values: { field: initial.field }, }), ); }); // #3981 #3982 test('fields validated meta should not be mutated when silently validating fields', async () => { let meta!: FieldMeta; mountWithHoc({ setup() { const { validate } = useForm({ validationSchema: z.object({ name: z.string().min(1), }), }); const field = useField('name'); meta = field.meta; onMounted(() => { validate({ mode: 'silent' }); validate({ mode: 'validated-only' }); }); return {}; }, template: `
`, }); await flushPromises(); expect(meta.validated).toBe(false); }); // #4320 test('Initial values are merged with previous values to ensure meta.dirty is stable', async () => { let meta!: Ref>; mountWithHoc({ setup() { const { resetForm, meta: fm } = useForm(); useField('name'); useField('email'); meta = fm; onMounted(() => { resetForm({ values: { name: 'test' } }); }); return {}; }, template: `
`, }); await flushPromises(); expect(meta.value.dirty).toBe(false); }); // #3991 test('initial value should not be mutable if nested field model is used', async () => { let model!: Ref<{ name: string }>; let formMeta!: Ref>; let reset!: () => void; mountWithHoc({ setup() { const { meta, resetForm } = useForm({ initialValues: { field: { name: '1' } }, validationSchema: z.object({ name: z.string().min(1), field: z.object({ name: z.string().min(1), }), }), }); const field = useField<{ name: string }>('field'); model = field.value; formMeta = meta; reset = resetForm; return {}; }, template: `
`, }); await flushPromises(); expect(formMeta.value.initialValues?.field?.name).toBe('1'); model.value.name = 'test'; await flushPromises(); expect(model.value).toEqual({ name: 'test' }); expect(formMeta.value.initialValues?.field?.name).toBe('1'); reset(); await flushPromises(); expect(model.value).toEqual({ name: '1' }); expect(formMeta.value.initialValues?.field?.name).toBe('1'); model.value.name = 'test'; await flushPromises(); expect(model.value).toEqual({ name: 'test' }); expect(formMeta.value.initialValues?.field?.name).toBe('1'); }); describe('defineField', () => { test('creates bindable object to components', async () => { mountWithHoc({ components: { ModelComp, }, setup() { const { defineField, values, errors } = useForm({ validationSchema: z.object({ name: z.string().min(1), }), }); const [model, props] = defineField('name'); return { props, model, values, errors }; }, template: ` {{ errors.name }} {{ values.name }} `, }); await flushPromises(); const errorEl = document.getElementById('errors'); const valuesEl = document.getElementById('values'); setValue(document.querySelector('input') as any, ''); dispatchEvent(document.querySelector('input') as any, 'blur'); await flushPromises(); expect(errorEl?.textContent).toBe('Too small: expected string to have >=1 characters'); setValue(document.querySelector('input') as any, '123'); dispatchEvent(document.querySelector('input') as any, 'blur'); await flushPromises(); expect(errorEl?.textContent).toBe(''); expect(valuesEl?.textContent).toBe('123'); }); test('can configure the validation events', async () => { mountWithHoc({ components: { ModelComp, }, setup() { const { defineField, values, errors } = useForm({ validationSchema: z.object({ name: z.string().min(1), }), }); const [model, props] = defineField('name', { validateOnModelUpdate: false }); return { props, model, values, errors }; }, template: ` {{ errors.name }} {{ values.name }} `, }); await flushPromises(); const errorEl = document.getElementById('errors'); const valuesEl = document.getElementById('values'); const input = document.querySelector('input') as HTMLInputElement; setValue(input, ''); await flushPromises(); expect(errorEl?.textContent).toBe(''); dispatchEvent(input, 'blur'); await flushPromises(); expect(errorEl?.textContent).toBe('Too small: expected string to have >=1 characters'); setValue(input, '123'); dispatchEvent(input, 'blur'); await flushPromises(); expect(errorEl?.textContent).toBe(''); expect(valuesEl?.textContent).toBe('123'); }); test('can pass extra props', async () => { mountWithHoc({ components: { ModelComp, }, setup() { const { defineField } = useForm({ validationSchema: z.object({ name: z.string().min(1), }), }); const [model, props] = defineField('name', { validateOnModelUpdate: true, props: state => ({ test: state.valid ? 'valid' : 'invalid' }), }); return { model, props }; }, template: ` `, }); await flushPromises(); setValue(document.querySelector('input') as any, ''); await flushPromises(); expect(document.body.innerHTML).toContain('invalid'); setValue(document.querySelector('input') as any, '123'); await flushPromises(); expect(document.body.innerHTML).toContain('valid'); }); test('can have lazy config', async () => { mountWithHoc({ components: { ModelComp, }, setup() { const { defineField } = useForm({ validationSchema: z.object({ name: z.string().min(1), }), }); const [model, props] = defineField('name', state => ({ props: { test: state.valid ? 'valid' : 'invalid' }, validateOnModelUpdate: true, })); return { model, props }; }, template: ` `, }); await flushPromises(); setValue(document.querySelector('input') as any, ''); await flushPromises(); expect(document.body.innerHTML).toContain('invalid'); setValue(document.querySelector('input') as any, '123'); await flushPromises(); expect(document.body.innerHTML).toContain('valid'); }); test('works with custom model', async () => { mountWithHoc({ components: { CustomModelComp, }, setup() { const { defineField, values, errors } = useForm({ validationSchema: z.object({ name: z.string().min(1), }), }); const [model, props] = defineField('name'); return { model, props, values, errors }; }, template: ` {{ errors.name }} {{ values.name }} `, }); await flushPromises(); const errorEl = document.getElementById('errors'); const valuesEl = document.getElementById('values'); setValue(document.querySelector('input') as any, ''); dispatchEvent(document.querySelector('input') as any, 'blur'); await flushPromises(); expect(errorEl?.textContent).toBe('Too small: expected string to have >=1 characters'); setValue(document.querySelector('input') as any, '123'); dispatchEvent(document.querySelector('input') as any, 'blur'); await flushPromises(); expect(errorEl?.textContent).toBe(''); expect(valuesEl?.textContent).toBe('123'); }); test('creates bindable object to HTML inputs', async () => { mountWithHoc({ setup() { const { defineField, values, errors } = useForm({ validationSchema: z.object({ name: z.string().min(1), }), }); const [model, props] = defineField('name'); return { model, props, values, errors }; }, template: ` {{ errors.name }} {{ values.name }} `, }); await flushPromises(); const errorEl = document.getElementById('errors'); const valuesEl = document.getElementById('values'); setValue(document.querySelector('input') as any, ''); dispatchEvent(document.querySelector('input') as any, 'blur'); await flushPromises(); expect(errorEl?.textContent).toBe('Too small: expected string to have >=1 characters'); setValue(document.querySelector('input') as any, '123'); dispatchEvent(document.querySelector('input') as any, 'blur'); await flushPromises(); expect(errorEl?.textContent).toBe(''); expect(valuesEl?.textContent).toBe('123'); }); test('can configure the validation events', async () => { mountWithHoc({ setup() { const { defineField, values, errors } = useForm({ validationSchema: z.object({ name: z.string().min(1), }), }); const [model, props] = defineField('name', { validateOnInput: true }); return { model, props, values, errors }; }, template: ` {{ errors.name }} {{ values.name }} `, }); await flushPromises(); const errorEl = document.getElementById('errors'); const valuesEl = document.getElementById('values'); setValue(document.querySelector('input') as any, ''); await flushPromises(); expect(errorEl?.textContent).toBe('Too small: expected string to have >=1 characters'); setValue(document.querySelector('input') as any, '123'); await flushPromises(); expect(errorEl?.textContent).toBe(''); expect(valuesEl?.textContent).toBe('123'); }); test('can pass extra props', async () => { mountWithHoc({ setup() { const { defineField } = useForm({ validationSchema: z.object({ name: z.string().min(1), }), }); const [model, props] = defineField('name', { validateOnInput: true, props: state => ({ 'aria-valid': state.valid ? 'true' : 'false' }), }); return { model, props }; }, template: ` `, }); await flushPromises(); setValue(document.querySelector('input') as any, ''); await flushPromises(); expect(document.body.innerHTML).toContain('aria-valid="false"'); setValue(document.querySelector('input') as any, '123'); await flushPromises(); expect(document.body.innerHTML).toContain('aria-valid="true"'); }); test('can have lazy config', async () => { mountWithHoc({ components: { ModelComp, }, setup() { const { defineField } = useForm({ validationSchema: z.object({ name: z.string().min(1), }), }); const [model, props] = defineField('name', state => ({ props: { 'aria-valid': state.valid ? 'true' : 'false' }, validateOnModelUpdate: true, })); return { model, props }; }, template: ` `, }); await flushPromises(); setValue(document.querySelector('input') as any, ''); await flushPromises(); expect(document.body.innerHTML).toContain('aria-valid="false"'); setValue(document.querySelector('input') as any, '123'); await flushPromises(); expect(document.body.innerHTML).toContain('aria-valid="true"'); }); test('can specify a label', async () => { defineRule('required', (value: string) => { return !!value; }); configure({ generateMessage: ({ field }) => `${field} is bad`, }); mountWithHoc({ setup() { const { defineField, values, errors } = useForm({ validationSchema: { name: 'required', }, }); const [model, props] = defineField('name', { validateOnInput: true, label: 'First Name' }); return { model, props, values, errors }; }, template: ` {{ errors.name }} `, }); await flushPromises(); const errorEl = document.getElementById('errors'); setValue(document.querySelector('input') as any, ''); await flushPromises(); expect(errorEl?.textContent).toBe('First Name is bad'); }); }); // #4341 test('undefined field value should be the same as missing value when it comes to dirty', async () => { let form!: FormContext; mountWithHoc({ setup() { form = useForm({ initialValues: { fname: '', }, }); useField('fname'); useField('lname'); return {}; }, template: `
`, }); await flushPromises(); expect(form.meta.value.dirty).toBe(false); }); // #4678 test('form is marked as dirty when key is removed', async () => { let form!: FormContext; mountWithHoc({ setup() { form = useForm({ initialValues: { fname: { key1: 'value1', key2: 'value2', }, }, }); useField('fname'); return {}; }, template: `
`, }); form.setFieldValue('fname', { key1: 'value1' }); await flushPromises(); expect(form.meta.value.dirty).toBe(true); }); describe('error paths can have dot or square bracket for the same field', () => { test('path is bracket, mutations are dot', async () => { let field!: FieldContext; let errorSetter!: FormContext['setFieldError']; mountWithHoc({ setup() { const { setFieldError } = useForm(); field = useField('users[0].test'); errorSetter = setFieldError; return {}; }, template: `
`, }); await flushPromises(); expect(field.errorMessage.value).toBe(undefined); await flushPromises(); errorSetter('users.0.test', 'error'); await flushPromises(); expect(field.errorMessage.value).toBe('error'); }); test('path is dot, mutations are bracket', async () => { let field!: FieldContext; let errorSetter!: FormContext['setFieldError']; mountWithHoc({ setup() { const { setFieldError } = useForm(); field = useField('users.0.test'); errorSetter = setFieldError; return {}; }, template: `
`, }); await flushPromises(); expect(field.errorMessage.value).toBe(undefined); await flushPromises(); errorSetter('users[0].test', 'error'); await flushPromises(); expect(field.errorMessage.value).toBe('error'); }); }); test('can query field touched state', async () => { let form!: FormContext; mountWithHoc({ setup() { form = useForm(); useField('fname'); useField('nested.lname'); useField('nested.fname'); return {}; }, template: `
`, }); await flushPromises(); expect(form.meta.value.touched).toBe(false); expect(form.isFieldTouched('fname')).toBe(false); expect(form.isFieldTouched('nested')).toBe(false); form.setFieldTouched('fname', true); form.setFieldTouched('nested.lname', true); await flushPromises(); expect(form.meta.value.touched).toBe(true); expect(form.isFieldTouched('fname')).toBe(true); expect(form.isFieldTouched('nested')).toBe(true); }); test('can query field dirty state', async () => { let form!: FormContext; mountWithHoc({ setup() { form = useForm(); useField('fname'); useField('nested.lname'); useField('nested.fname'); return {}; }, template: `
`, }); await flushPromises(); expect(form.meta.value.dirty).toBe(false); expect(form.isFieldDirty('fname')).toBe(false); expect(form.isFieldDirty('nested')).toBe(false); form.setFieldValue('fname', 'value'); form.setFieldValue('nested.lname', 'value'); await flushPromises(); expect(form.meta.value.dirty).toBe(true); expect(form.isFieldDirty('fname')).toBe(true); expect(form.isFieldDirty('nested')).toBe(true); }); test('can query field valid state', async () => { let form!: FormContext; mountWithHoc({ setup() { form = useForm(); useField('fname'); useField('nested.lname'); useField('nested.fname'); return {}; }, template: `
`, }); await flushPromises(); expect(form.meta.value.valid).toBe(true); expect(form.isFieldValid('fname')).toBe(true); expect(form.isFieldValid('nested')).toBe(true); form.setFieldError('fname', 'ERROR'); form.setFieldError('nested.lname', 'ERROR'); await flushPromises(); expect(form.meta.value.valid).toBe(false); expect(form.isFieldValid('fname')).toBe(false); expect(form.isFieldValid('nested')).toBe(false); }); // #4438 test('silent validation should not mark a field as validated', async () => { let form!: FormContext; const showFields = ref(false); mountWithHoc({ setup() { form = useForm({ validationSchema: z.object({ fname: z.string().min(1), lname: z.string().min(1), }), }); return { showFields, }; }, template: `
`, }); await flushPromises(); showFields.value = true; await flushPromises(); expect(form.errors.value.fname).toBe(undefined); expect(form.errors.value.lname).toBe(undefined); setValue(document.querySelector('input') as any, '123'); await flushPromises(); expect(form.errors.value.fname).toBe(undefined); expect(form.errors.value.lname).toBe(undefined); }); test('values can be reset to specifically only include the provided fields', async () => { let form!: FormContext<{ fname: string; lname: string }>; mountWithHoc({ setup() { form = useForm({ initialValues: { fname: '123', lname: '456' }, }); return {}; }, template: `
`, }); await flushPromises(); form.resetForm({ values: { fname: 'test' } }, { force: true }); expect(form.values.lname).toBeUndefined(); expect(form.values.fname).toBe('test'); }); test('reset should be able to set initial values to undefined with force: true', async () => { let form!: FormContext<{ lname: string; fname: string }>; mountWithHoc({ setup() { form = useForm({ initialValues: { fname: '123', lname: '456' }, }); return {}; }, template: `
`, }); await flushPromises(); // FAIL form.resetForm({ values: { lname: '789' } }, { force: true }); expect(form.meta.value.initialValues?.fname).toBeUndefined(); // This is not undefined. It stayed at value '123' expect(form.meta.value.initialValues?.lname).toBe('789'); // FAIL form.resetForm({ values: {} }, { force: true }); expect(form.meta.value.initialValues?.fname).toBeUndefined(); expect(form.meta.value.initialValues?.lname).toBeUndefined(); // PASS form.resetForm({ values: { lname: undefined, fname: undefined } }, { force: true }); expect(form.meta.value.initialValues?.fname).toBeUndefined(); expect(form.meta.value.initialValues?.lname).toBeUndefined(); }); test('reset should not make unspecified values undefined', async () => { let form!: FormContext<{ fname: string; lname: string }>; mountWithHoc({ setup() { form = useForm({ initialValues: { fname: '123', lname: '456' }, }); form.defineField('fname'); form.defineField('lname'); return {}; }, template: `
`, }); await flushPromises(); form.resetForm({ values: { fname: 'test' } }); expect(form.values.lname).toBe('456'); expect(form.values.fname).toBe('test'); }); test('reset field should make the dirty state false', async () => { let form!: FormContext<{ fname: string; lname: string }>; mountWithHoc({ setup() { form = useForm({ initialValues: { fname: '123', lname: '456' }, }); form.defineField('fname'); form.defineField('lname'); return {}; }, template: `
`, }); await flushPromises(); form.resetField('fname', { value: 'test' }); expect(form.meta.value.dirty).toBe(false); }); test('defineField respects global model config', async () => { let form!: FormContext<{ fname: string; lname: string }>; let model!: Ref; configure({ validateOnModelUpdate: false, }); mountWithHoc({ setup() { form = useForm({ initialValues: { fname: '123', lname: '456' }, validationSchema: z.object({ fname: z.string().min(1), lname: z.string().min(1), }), }); const field = form.defineField('fname'); model = field[0]; return {}; }, template: `
`, }); await flushPromises(); model.value = ''; await flushPromises(); await expect(form.errors.value.fname).toBe(undefined); }); test('checks if both source and target are POJO before setting properties', async () => { let form!: FormContext<{ file: { name: string; size: number } }>; const f1 = new File([''], 'f1.text'); const f2 = { name: 'f2.text', size: 123 }; mountWithHoc({ setup() { form = useForm({ initialValues: { file: f1 }, }); form.defineField('file'); return {}; }, template: `
`, }); await flushPromises(); expect(form.values.file).toBeInstanceOf(File); expect(form.values.file).toBe(f1); form.setValues({ file: f2 }); expect(form.values.file).toEqual(f2); }); }); ================================================ FILE: packages/vee-validate/tests/useFormErrors.spec.ts ================================================ import { useField, useForm, useFormErrors } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; describe('useFormErrors()', () => { const REQUIRED_MESSAGE = 'Field is required'; const validate = (val: any) => (val ? true : REQUIRED_MESSAGE); test('gives access to all form errors', async () => { mountWithHoc({ setup() { useForm(); const { value } = useField('test', validate); const messages = useFormErrors(); return { value, messages, }; }, template: ` {{ messages.test }} `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); setValue(input as any, ''); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); }); test('returns empty object and warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const messages = useFormErrors(); return { messages, }; }, template: ` {{ messages }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe('{}'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useFormValues.spec.ts ================================================ import { useField, useForm, useFormValues } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; describe('useFormValues()', () => { const REQUIRED_MESSAGE = 'Field is required'; const validate = (val: any) => (val ? true : REQUIRED_MESSAGE); test('gives access to all form values', async () => { mountWithHoc({ setup() { useForm(); const { value } = useField('test', validate); const values = useFormValues(); return { value, values, }; }, template: ` {{ values.test }} `, }); await flushPromises(); const input = document.querySelector('input'); const valueSpan = document.querySelector('span'); const inputValue = '1234'; setValue(input as any, inputValue); await flushPromises(); expect(valueSpan?.textContent).toBe(inputValue); }); test('returns empty object and warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const values = useFormValues(); return { values, }; }, template: ` {{ values }} `, }); await flushPromises(); const valuesSpan = document.querySelector('span'); expect(valuesSpan?.textContent).toBe('{}'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useIsFieldDirty.spec.ts ================================================ import { useField, useForm, useIsFieldDirty } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; import { defineComponent } from 'vue'; describe('useIsFieldDirty()', () => { test('gives access to a single field isDirty status', async () => { mountWithHoc({ setup() { useForm(); const { value, handleChange } = useField('test'); const isDirty = useIsFieldDirty('test'); return { value, isDirty, handleInput: (e: any) => handleChange(e, false), }; }, template: ` {{ isDirty.toString() }} `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); setValue(input as any, ''); await flushPromises(); expect(error?.textContent).toBe('true'); }); test('gives access to a single field isDirty status in a child component without specifying a path', async () => { const DirtyIcon = defineComponent({ template: '{{ isDirty.toString() }}', setup() { const isDirty = useIsFieldDirty(); return { isDirty, }; }, }); mountWithHoc({ components: { DirtyIcon, }, setup() { useForm(); const { value, handleChange } = useField('test'); return { value, handleInput: (e: any) => handleChange(e, false), }; }, template: ` `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); setValue(input as any, ''); await flushPromises(); expect(error?.textContent).toBe('true'); }); test('gives access to array fields isDirty status', async () => { mountWithHoc({ setup() { useForm(); const { value, handleChange } = useField('test'); useField('test'); const isDirty = useIsFieldDirty('test'); return { value, isDirty, handleInput: (e: any) => handleChange(e, false), }; }, template: ` {{ isDirty.toString() }} `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); setValue(input as any, ''); await flushPromises(); expect(error?.textContent).toBe('true'); }); test('returns false and warns if field does not exist', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { useForm(); const isDirty = useIsFieldDirty('something'); return { isDirty, }; }, template: ` {{ isDirty.toString() }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); test('returns false and warns if form does not exist', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const isDirty = useIsFieldDirty('something'); return { isDirty, }; }, template: ` {{ isDirty.toString() }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useIsFieldTouched.spec.ts ================================================ import { useField, useForm, useIsFieldTouched } from '@/vee-validate'; import { dispatchEvent, mountWithHoc, flushPromises } from './helpers'; import { defineComponent } from 'vue'; describe('useIsFieldTouched()', () => { test('gives access to a single field isTouched status', async () => { mountWithHoc({ setup() { useForm(); const { value, handleBlur } = useField('test'); const isTouched = useIsFieldTouched('test'); return { value, isTouched, handleBlur, }; }, template: ` {{ isTouched.toString() }} `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); dispatchEvent(input as any, 'blur'); await flushPromises(); expect(error?.textContent).toBe('true'); }); test('gives access to a single field isTouched status in child components without path prop', async () => { const TouchedIcon = defineComponent({ template: '{{ isTouched.toString() }}', setup() { const isTouched = useIsFieldTouched(); return { isTouched, }; }, }); mountWithHoc({ components: { TouchedIcon, }, setup() { useForm(); const { value, handleBlur } = useField('test'); return { value, handleBlur, }; }, template: ` `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); dispatchEvent(input as any, 'blur'); await flushPromises(); expect(error?.textContent).toBe('true'); }); test('gives access to array fields isTouched status', async () => { mountWithHoc({ setup() { useForm(); const { value, handleBlur } = useField('test', undefined, { type: 'checkbox' }); useField('test', undefined, { type: 'checkbox' }); const isTouched = useIsFieldTouched('test'); return { value, isTouched, handleBlur, }; }, template: ` {{ isTouched.toString() }} `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); dispatchEvent(input as any, 'blur'); await flushPromises(); expect(error?.textContent).toBe('true'); }); test('returns false and warns if field does not exist', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { useForm(); const isTouched = useIsFieldTouched('something'); return { isTouched, }; }, template: ` {{ isTouched.toString() }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); test('returns false and warns if form does not exist', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const isTouched = useIsFieldTouched('something'); return { isTouched, }; }, template: ` {{ isTouched.toString() }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useIsFieldValid.spec.ts ================================================ import { useField, useIsFieldValid, useForm } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; import { defineComponent } from 'vue'; describe('useIsFieldValid()', () => { const REQUIRED_MESSAGE = 'Field is required'; const validate = (val: any) => (val ? true : REQUIRED_MESSAGE); test('returns the validity of a single field', async () => { mountWithHoc({ setup() { useForm(); const { value } = useField('test', validate); const isValid = useIsFieldValid('test'); return { value, isValid, }; }, template: ` {{ isValid.toString() }} `, }); await flushPromises(); const input = document.querySelector('input'); const span = document.querySelector('span'); setValue(input as any, ''); await flushPromises(); expect(span?.textContent).toBe('false'); setValue(input as any, '12'); await flushPromises(); expect(span?.textContent).toBe('true'); }); test('returns the validity of a single field in child components without specifying a path', async () => { const ValidIcon = defineComponent({ template: '{{ isValid.toString() }}', setup() { const isValid = useIsFieldValid(); return { isValid, }; }, }); mountWithHoc({ components: { ValidIcon, }, setup() { useForm(); const { value } = useField('test', validate); return { value, }; }, template: ` `, }); await flushPromises(); const input = document.querySelector('input'); const span = document.querySelector('span'); setValue(input as any, ''); await flushPromises(); expect(span?.textContent).toBe('false'); setValue(input as any, '12'); await flushPromises(); expect(span?.textContent).toBe('true'); }); test('returns the validity of array fields', async () => { mountWithHoc({ setup() { useForm(); const { value } = useField('test', validate); useField('test', validate); const isValid = useIsFieldValid('test'); return { value, isValid, }; }, template: ` {{ isValid.toString() }} `, }); await flushPromises(); const input = document.querySelector('input'); const span = document.querySelector('span'); setValue(input as any, ''); await flushPromises(); expect(span?.textContent).toBe('false'); setValue(input as any, '12'); await flushPromises(); expect(span?.textContent).toBe('true'); }); test('returns false and warns if field is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { useForm(); const isValid = useIsFieldValid('test'); return { isValid, }; }, template: ` {{ isValid.toString() }} `, }); await flushPromises(); const span = document.querySelector('span'); expect(span?.textContent).toBe('false'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); test('returns false and warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const isValid = useIsFieldValid('test'); return { isValid, }; }, template: ` {{ isValid.toString() }} `, }); await flushPromises(); const span = document.querySelector('span'); expect(span?.textContent).toBe('false'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useIsFormDirty.spec.ts ================================================ import { useField, useForm, useIsFormDirty } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; describe('useIsFormDirty()', () => { test('gives access to the forms isDirty status', async () => { mountWithHoc({ setup() { useForm(); const { value, handleChange } = useField('test'); const isDirty = useIsFormDirty(); return { value, isDirty, handleInput: (e: any) => handleChange(e, false), }; }, template: ` {{ isDirty.toString() }} `, }); const input = document.querySelector('input'); const dirty = document.querySelector('span'); expect(dirty?.textContent).toBe('false'); setValue(input as any, ''); await flushPromises(); expect(dirty?.textContent).toBe('true'); }); test('returns false and warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const isDirty = useIsFormDirty(); return { isDirty, }; }, template: ` {{ isDirty.toString() }} `, }); const dirty = document.querySelector('span'); await flushPromises(); expect(dirty?.textContent).toBe('false'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useIsFormTouched.spec.ts ================================================ import { useField, useForm, useIsFormTouched } from '@/vee-validate'; import { dispatchEvent, mountWithHoc, flushPromises } from './helpers'; describe('useIsFormTouched()', () => { test('gives access to the forms isTouched status', async () => { mountWithHoc({ setup() { useForm(); const { value, handleBlur } = useField('test'); const isTouched = useIsFormTouched(); return { value, isTouched, handleBlur, }; }, template: ` {{ isTouched.toString() }} `, }); const input = document.querySelector('input'); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); dispatchEvent(input as any, 'blur'); await flushPromises(); expect(error?.textContent).toBe('true'); }); test('returns false and warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const isTouched = useIsFormTouched(); return { isTouched, }; }, template: ` {{ isTouched.toString() }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe('false'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useIsFormValid.spec.ts ================================================ import { useIsFormValid, useField, useForm } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; describe('useIsFormValid()', () => { const REQUIRED_MESSAGE = 'Field is required'; const validate = (val: any) => (val ? true : REQUIRED_MESSAGE); test('returns validity of the form', async () => { mountWithHoc({ setup() { useForm(); const { value } = useField('test', validate); const isValid = useIsFormValid(); return { value, isValid, }; }, template: ` {{ isValid.toString() }} `, }); await flushPromises(); const input = document.querySelector('input'); const span = document.querySelector('span'); setValue(input as any, ''); await flushPromises(); expect(span?.textContent).toBe('false'); setValue(input as any, '12'); await flushPromises(); expect(span?.textContent).toBe('true'); }); test('returns false and warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const isValid = useIsFormValid(); return { isValid, }; }, template: ` {{ isValid.toString() }} `, }); await flushPromises(); const span = document.querySelector('span'); expect(span?.textContent).toBe('false'); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useIsSubmitting.spec.ts ================================================ import { useField, useForm, useIsSubmitting } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; describe('useIsSubmitting()', () => { const validate = (): Promise => new Promise(resolve => { setTimeout(() => { resolve(false); }, 10); }); test('indicates if a form is submitting', async () => { mountWithHoc({ setup() { const { submitForm } = useForm(); useField('test', validate); const isSubmitting = useIsSubmitting(); return { isSubmitting, submitForm, }; }, template: ` {{ isSubmitting.toString() }} `, }); await flushPromises(); const button = document.querySelector('button'); const submitText = document.querySelector('span'); expect(submitText?.textContent).toBe('false'); button?.click(); await flushPromises(); expect(submitText?.textContent).toBe('true'); vi.runAllTimers(); await flushPromises(); expect(submitText?.textContent).toBe('false'); }); test('returns false and warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const isSubmitting = useIsSubmitting(); return { isSubmitting, }; }, template: ` {{ isSubmitting.toString() }} `, }); await flushPromises(); const submitText = document.querySelector('span'); expect(submitText?.textContent).toBe('false'); expect(console.warn).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useIsValidating.spec.ts ================================================ import { useForm, useIsValidating } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; import { expect } from 'vitest'; import * as z from 'zod'; describe('useIsValidating()', () => { test.skip('indicates if a form is validating', async () => { const spy = vi.fn((isValidating: boolean) => isValidating); mountWithHoc({ setup() { const isValidating = useIsValidating(); const { validate } = useForm({ validationSchema: z .object({ name: z.string(), }) .superRefine(() => { spy(isValidating.value); }) .default({ name: '' }), }); return { validate, }; }, template: ` `, }); await flushPromises(); // triggered by validateObjectSchema method expect(spy).toHaveBeenCalledTimes(1); const button = document.querySelector('button'); button?.click(); await flushPromises(); // triggered by formCtx validate method expect(spy).toHaveBeenCalledTimes(2); expect(spy).toHaveLastReturnedWith(true); }); test('returns false and warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const isValidating = useIsValidating(); return { isValidating, }; }, template: ` {{ isValidating.toString() }} `, }); await flushPromises(); const validatingText = document.querySelector('span'); expect(validatingText?.textContent).toBe('false'); expect(console.warn).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useResetForm.spec.ts ================================================ import { FormContext, useField, useForm, useResetForm } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; describe('useResetForm()', () => { const REQUIRED_MESSAGE = 'Field is required'; const validate = (val: any) => (val ? true : REQUIRED_MESSAGE); test('resets a form', async () => { let resetForm: any; let value: any; let errorMessage: any; mountWithHoc({ setup() { useForm(); const field = useField('test', validate); value = field.value; errorMessage = field.errorMessage; resetForm = useResetForm(); return { value, errorMessage, }; }, template: ` {{ errorMessage }} `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); setValue(input as any, ''); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); const inputValue = '123'; resetForm({ values: { test: inputValue, }, }); await flushPromises(); expect(error?.textContent).toBe(''); expect(input?.value).toBe(inputValue); }); test('warns if the form does not exist', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let resetForm: any; mountWithHoc({ setup() { resetForm = useResetForm(); return {}; }, template: `
`, }); resetForm({ values: { test: 'someValue', }, }); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); test('resets a form with passed options', async () => { let resetForm: any; let form!: FormContext<{ fname: string; lname: string }>; mountWithHoc({ setup() { form = useForm({ initialValues: { fname: '123', lname: '456' }, }); resetForm = useResetForm(); return {}; }, template: `
`, }); await flushPromises(); resetForm({ values: { fname: 'test' } }, { force: true }); expect(form.values.lname).toBeUndefined(); expect(form.values.fname).toBe('test'); }); }); ================================================ FILE: packages/vee-validate/tests/useSetFieldError.spec.ts ================================================ import { useField, useSetFieldError, useForm } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; import { defineComponent } from 'vue'; describe('useSetFieldError()', () => { test('sets a single field error message', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let setError!: ReturnType; mountWithHoc({ setup() { useForm(); const { value, errorMessage } = useField('test'); setError = useSetFieldError('test'); return { value, errorMessage, }; }, template: ` {{ errorMessage }} `, }); await flushPromises(); const msg = 'Field is required'; const error = document.querySelector('span'); setError(msg); await flushPromises(); expect(error?.textContent).toBe(msg); expect(spy).not.toHaveBeenCalled(); spy.mockRestore(); }); test('sets a single field error message in a child component without specifying a path', async () => { let setError!: ReturnType; const CustomSetErrorComponent = defineComponent({ template: '', setup() { setError = useSetFieldError(); return {}; }, }); mountWithHoc({ components: { CustomSetErrorComponent, }, setup() { useForm(); const { errorMessage } = useField('test'); return { errorMessage, }; }, template: ` {{ errorMessage }} `, }); await flushPromises(); const msg = 'Field is required'; const error = document.querySelector('span'); setError(msg); await flushPromises(); expect(error?.textContent).toBe(msg); }); test('warns if field not found', async () => { let setError!: ReturnType; const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { setError = useSetFieldError('something'); return {}; }, template: `
`, }); await flushPromises(); setError('ERROR'); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useSetFieldTouched.spec.ts ================================================ import { useField, useForm, useSetFieldTouched } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; import { defineComponent } from 'vue'; describe('useSetFieldTouched()', () => { test('sets a single field touched status', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let setTouched!: ReturnType; mountWithHoc({ setup() { useForm(); const { meta } = useField('test'); setTouched = useSetFieldTouched('test'); return { meta, }; }, template: ` {{ meta.touched.toString() }} `, }); await flushPromises(); const touched = document.querySelector('span'); expect(touched?.textContent).toBe('false'); setTouched(true); await flushPromises(); expect(touched?.textContent).toBe('true'); expect(spy).not.toHaveBeenCalled(); spy.mockRestore(); }); test('sets a single field isTouched status in child components without path prop', async () => { let setTouched!: ReturnType; const TouchedButton = defineComponent({ template: '', setup() { setTouched = useSetFieldTouched(); return {}; }, }); mountWithHoc({ components: { TouchedButton, }, setup() { useForm(); const { meta } = useField('test'); return { meta, }; }, template: ` {{ meta.touched.toString() }} `, }); await flushPromises(); const touched = document.querySelector('span'); expect(touched?.textContent).toBe('false'); setTouched(true); await flushPromises(); expect(touched?.textContent).toBe('true'); }); test('warns if field not found', async () => { let setTouched!: ReturnType; const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { setTouched = useSetFieldTouched('something'); return {}; }, template: `
`, }); await flushPromises(); setTouched(true); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useSetFieldValue.spec.ts ================================================ import { useField, useForm, useSetFieldValue } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; import { defineComponent } from 'vue'; describe('useSetFieldTouched()', () => { test('sets a single field value', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let setValue!: ReturnType; mountWithHoc({ setup() { useForm(); const { value } = useField('test'); setValue = useSetFieldValue('test'); return { value, }; }, template: ` {{ value }} `, }); await flushPromises(); const valueSpan = document.querySelector('span'); expect(valueSpan?.textContent).toBe(''); setValue('test 123'); await flushPromises(); expect(valueSpan?.textContent).toBe('test 123'); expect(spy).not.toHaveBeenCalled(); spy.mockRestore(); }); test('sets a single field value in child components without path prop', async () => { let setValue!: ReturnType; const ValueBtn = defineComponent({ template: '', setup() { setValue = useSetFieldValue(); return {}; }, }); mountWithHoc({ components: { ValueBtn, }, setup() { useForm(); const { value } = useField('test'); return { value, }; }, template: ` {{ value }} `, }); await flushPromises(); const valueSpan = document.querySelector('span'); expect(valueSpan?.textContent).toBe(''); setValue('test 123'); await flushPromises(); expect(valueSpan?.textContent).toBe('test 123'); }); test('warns if field not found', async () => { let setValue!: ReturnType; const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { setValue = useSetFieldValue('something'); return {}; }, template: `
`, }); await flushPromises(); setValue('test 123'); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useSetFormErrors.spec.ts ================================================ import { useForm, useSetFormErrors } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; describe('useSetFormErrors()', () => { const REQUIRED_MESSAGE = 'Field is required'; test('sets multiple fields errors', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let setErrors!: ReturnType; mountWithHoc({ setup() { const { errors } = useForm(); setErrors = useSetFormErrors(); return { errors, }; }, template: ` {{ errors.test }} `, }); await flushPromises(); const error = document.querySelector('span'); setErrors({ test: REQUIRED_MESSAGE }); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); expect(spy).not.toHaveBeenCalled(); spy.mockRestore(); }); test('warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let setErrors!: ReturnType; mountWithHoc({ setup() { setErrors = useSetFormErrors(); return {}; }, template: ` `, }); await flushPromises(); setErrors({}); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useSetFormTouched.spec.ts ================================================ import { useField, useForm, useSetFormTouched } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; describe('useSetFormTouched()', () => { test('sets multiple fields touched state', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let setTouched!: ReturnType; mountWithHoc({ setup() { useForm(); const { meta: test1 } = useField('test1'); const { meta: test2 } = useField('test2'); setTouched = useSetFormTouched(); return { test1, test2, }; }, template: ` {{ test1.touched }} {{ test2.touched }} `, }); await flushPromises(); const touched1 = document.querySelector('#t1'); const touched2 = document.querySelector('#t2'); setTouched({ test1: true, test2: false }); await flushPromises(); expect(touched1?.textContent).toBe('true'); expect(touched2?.textContent).toBe('false'); expect(spy).not.toHaveBeenCalled(); spy.mockRestore(); }); test('sets all fields touched state', async () => { let setTouched!: ReturnType; mountWithHoc({ setup() { useForm(); const { meta: test1 } = useField('test1'); const { meta: test2 } = useField('test2'); setTouched = useSetFormTouched(); return { test1, test2, }; }, template: ` {{ test1.touched }} {{ test2.touched }} `, }); await flushPromises(); const touched1 = document.querySelector('#t1'); const touched2 = document.querySelector('#t2'); setTouched(true); await flushPromises(); expect(touched1?.textContent).toBe('true'); expect(touched2?.textContent).toBe('true'); }); test('warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let setTouched!: ReturnType; mountWithHoc({ setup() { setTouched = useSetFormTouched(); return {}; }, template: ` `, }); await flushPromises(); setTouched({}); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useSetFormValues.spec.ts ================================================ import { useField, useForm, useSetFormValues } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; describe('useSetFormValues()', () => { test('sets form values', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let setValues!: ReturnType; mountWithHoc({ setup() { useForm(); const { value: test1 } = useField('test1'); const { value: test2 } = useField('test2'); setValues = useSetFormValues(); return { test1, test2, }; }, template: ` {{ test1 }} {{ test2 }} `, }); await flushPromises(); const value1 = document.querySelector('#t1'); const value2 = document.querySelector('#t2'); setValues({ test1: 'test1', test2: 'test2' }); await flushPromises(); expect(value1?.textContent).toBe('test1'); expect(value2?.textContent).toBe('test2'); expect(spy).not.toHaveBeenCalled(); spy.mockRestore(); }); test('warns if form is not found', async () => { let setValues!: ReturnType; const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { setValues = useSetFormValues(); return {}; }, template: ` `, }); await flushPromises(); setValues({}); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useSubmitCount.spec.ts ================================================ import { useField, useForm, useSubmitCount } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; describe('useSubmitCount()', () => { test('indicates the number of submissions', async () => { mountWithHoc({ setup() { const { submitForm } = useForm(); useField('test'); const submitCount = useSubmitCount(); return { submitCount, submitForm, }; }, template: ` {{ submitCount }} `, }); await flushPromises(); const button = document.querySelector('button'); const submitText = document.querySelector('span'); expect(submitText?.textContent).toBe('0'); button?.click(); await flushPromises(); expect(submitText?.textContent).toBe('1'); }); test('returns 0 and warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { const submitCount = useSubmitCount(); return { submitCount, }; }, template: ` {{ submitCount }} `, }); await flushPromises(); const submitText = document.querySelector('span'); expect(submitText?.textContent).toBe('0'); expect(console.warn).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useSubmitForm.spec.ts ================================================ import { useField, useForm, useSubmitForm } from '@/vee-validate'; import { mountWithHoc, setValue, flushPromises } from './helpers'; describe('useSubmitForm()', () => { const REQUIRED_MESSAGE = 'Field is required'; const validate = (val: any) => (val ? true : REQUIRED_MESSAGE); test('executes a form submission callback', async () => { const spy = vi.fn(); mountWithHoc({ setup() { useForm(); const field = useField('test', validate); const value = field.value; const errorMessage = field.errorMessage; const submitForm = useSubmitForm(spy); return { value, errorMessage, submitForm, }; }, template: ` {{ errorMessage }} `, }); await flushPromises(); const input = document.querySelector('input'); const error = document.querySelector('span'); const submitBtn = document.querySelector('button'); submitBtn?.click(); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); expect(spy).not.toHaveBeenCalled(); const inputValue = '123'; setValue(input as any, inputValue); submitBtn?.click(); await flushPromises(); expect(spy).toHaveBeenCalledWith({ test: inputValue }, expect.anything()); }); test('warns if the form does not exist', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); mountWithHoc({ setup() { useSubmitForm(() => { // nothing... }); return {}; }, template: `
`, }); await flushPromises(); expect(spy).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useValidateField.spec.ts ================================================ import { useField, useForm, useValidateField } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; import { defineComponent } from 'vue'; describe('useValidateField()', () => { const REQUIRED_MESSAGE = 'Field is required'; const rules = (val: any) => (val ? true : REQUIRED_MESSAGE); test('validates a single field', async () => { let validate!: ReturnType; mountWithHoc({ setup() { useForm(); const { errorMessage } = useField('test', rules); validate = useValidateField('test'); return { errorMessage, }; }, template: ` {{ errorMessage }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe(''); await validate(); await flushPromises(); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); }); test('validates a single field from a child component without specifying a path', async () => { const ValidateBtn = defineComponent({ setup() { const validate = useValidateField(); return { validate, }; }, template: '', }); mountWithHoc({ components: { ValidateBtn, }, setup() { useForm(); const { errorMessage } = useField('test', rules); return { errorMessage, }; }, template: ` {{ errorMessage }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe(''); document.querySelector('button')?.click(); await flushPromises(); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); }); test.skip('validates array fields', async () => { let validate!: ReturnType; mountWithHoc({ setup() { useForm(); const { errorMessage } = useField('test', rules); useField('test', rules); validate = useValidateField('test'); return { errorMessage, }; }, template: ` {{ errorMessage }} `, }); await flushPromises(); const error = document.querySelector('span'); expect(error?.textContent).toBe(''); await validate(); await flushPromises(); await flushPromises(); expect(error?.textContent).toBe(REQUIRED_MESSAGE); }); test('warns if the field does not exist', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let validate!: ReturnType; mountWithHoc({ setup() { useForm(); validate = useValidateField('something'); return {}; }, template: `
`, }); await validate(); await flushPromises(); expect(console.warn).toHaveBeenCalled(); spy.mockRestore(); }); test('warns if the form does not exist', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let validate!: ReturnType; mountWithHoc({ setup() { validate = useValidateField('something'); return {}; }, template: `
`, }); await validate(); await flushPromises(); expect(console.warn).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/useValidateForm.spec.ts ================================================ import { useField, useForm, useValidateForm } from '@/vee-validate'; import { mountWithHoc, flushPromises } from './helpers'; describe('useValidateForm()', () => { const REQUIRED_MESSAGE = 'Field is required'; const rules = (val: any) => (val ? true : REQUIRED_MESSAGE); test('validates all fields', async () => { let validate!: ReturnType; mountWithHoc({ setup() { useForm(); const { errorMessage: e1 } = useField('test', rules); const { errorMessage: e2 } = useField('another', rules); validate = useValidateForm(); return { e1, e2, }; }, template: ` {{ e1 }} {{ e2 }} `, }); await flushPromises(); const errors = Array.from(document.querySelectorAll('span')); expect(errors.map(span => span.textContent)).toEqual(['', '']); await validate(); await flushPromises(); expect(errors.map(span => span.textContent)).toEqual([REQUIRED_MESSAGE, REQUIRED_MESSAGE]); }); test('warns if form is not found', async () => { const spy = vi.spyOn(console, 'warn').mockImplementation(() => { // NOOP }); let validate!: ReturnType; mountWithHoc({ setup() { validate = useValidateForm(); return {}; }, template: `
`, }); await validate(); await flushPromises(); expect(console.warn).toHaveBeenCalled(); spy.mockRestore(); }); }); ================================================ FILE: packages/vee-validate/tests/utils/assertions.spec.ts ================================================ import { isEqual } from 'packages/vee-validate/src/utils'; describe('assertions', () => { test('equal objects are equal', () => { const a1 = { field1: undefined, field2: 'value2', field3: 'value3' }; const a2 = { field1: undefined, field3: 'value3', field2: 'value2' }; const a3 = { field2: 'value2', field3: 'value3', field1: undefined }; const a4 = { field2: 'value2', field1: undefined, field3: 'value3' }; const a5 = { field3: 'value3', field1: undefined, field2: 'value2' }; const a6 = { field3: 'value3', field2: 'value2', field1: undefined }; const b1 = { field1: undefined, field2: 'value2', field3: 'value3' }; const b2 = { field1: undefined, field3: 'value3', field2: 'value2' }; const b3 = { field2: 'value2', field3: 'value3', field1: undefined }; const b4 = { field2: 'value2', field1: undefined, field3: 'value3' }; const b5 = { field3: 'value3', field1: undefined, field2: 'value2' }; const b6 = { field3: 'value3', field2: 'value2', field1: undefined }; expect(isEqual(a1, b1)).toBe(true); expect(isEqual(a1, b2)).toBe(true); expect(isEqual(a1, b3)).toBe(true); expect(isEqual(a1, b4)).toBe(true); expect(isEqual(a1, b5)).toBe(true); expect(isEqual(a1, b6)).toBe(true); expect(isEqual(a2, b1)).toBe(true); expect(isEqual(a2, b2)).toBe(true); expect(isEqual(a2, b3)).toBe(true); expect(isEqual(a2, b4)).toBe(true); expect(isEqual(a2, b5)).toBe(true); expect(isEqual(a2, b6)).toBe(true); expect(isEqual(a3, b1)).toBe(true); expect(isEqual(a3, b2)).toBe(true); expect(isEqual(a3, b3)).toBe(true); expect(isEqual(a3, b4)).toBe(true); expect(isEqual(a3, b5)).toBe(true); expect(isEqual(a3, b6)).toBe(true); expect(isEqual(a4, b1)).toBe(true); expect(isEqual(a4, b2)).toBe(true); expect(isEqual(a4, b3)).toBe(true); expect(isEqual(a4, b4)).toBe(true); expect(isEqual(a4, b5)).toBe(true); expect(isEqual(a4, b6)).toBe(true); expect(isEqual(a5, b1)).toBe(true); expect(isEqual(a5, b2)).toBe(true); expect(isEqual(a5, b3)).toBe(true); expect(isEqual(a5, b4)).toBe(true); expect(isEqual(a5, b5)).toBe(true); expect(isEqual(a5, b6)).toBe(true); expect(isEqual(a6, b1)).toBe(true); expect(isEqual(a6, b2)).toBe(true); expect(isEqual(a6, b3)).toBe(true); expect(isEqual(a6, b4)).toBe(true); expect(isEqual(a6, b5)).toBe(true); expect(isEqual(a6, b6)).toBe(true); }); test('equal objects where A has missing keys are equal', () => { const a1 = { field2: 'value2', field3: 'value3' }; const a2 = { field3: 'value3', field2: 'value2' }; const b1 = { field1: undefined, field2: 'value2', field3: 'value3' }; const b2 = { field1: undefined, field3: 'value3', field2: 'value2' }; const b3 = { field2: 'value2', field3: 'value3', field1: undefined }; const b4 = { field2: 'value2', field1: undefined, field3: 'value3' }; const b5 = { field3: 'value3', field1: undefined, field2: 'value2' }; const b6 = { field3: 'value3', field2: 'value2', field1: undefined }; expect(isEqual(a1, b1)).toBe(true); expect(isEqual(a1, b2)).toBe(true); expect(isEqual(a1, b3)).toBe(true); expect(isEqual(a1, b4)).toBe(true); expect(isEqual(a1, b5)).toBe(true); expect(isEqual(a1, b6)).toBe(true); expect(isEqual(a2, b1)).toBe(true); expect(isEqual(a2, b2)).toBe(true); expect(isEqual(a2, b3)).toBe(true); expect(isEqual(a2, b4)).toBe(true); expect(isEqual(a2, b5)).toBe(true); expect(isEqual(a2, b6)).toBe(true); }); test('equal objects where B has missing keys are equal', () => { const a1 = { field1: undefined, field2: 'value2', field3: 'value3' }; const a2 = { field1: undefined, field3: 'value3', field2: 'value2' }; const a3 = { field2: 'value2', field3: 'value3', field1: undefined }; const a4 = { field2: 'value2', field1: undefined, field3: 'value3' }; const a5 = { field3: 'value3', field1: undefined, field2: 'value2' }; const a6 = { field3: 'value3', field2: 'value2', field1: undefined }; const b1 = { field2: 'value2', field3: 'value3' }; const b2 = { field3: 'value3', field2: 'value2' }; expect(isEqual(a1, b1)).toBe(true); expect(isEqual(a1, b2)).toBe(true); expect(isEqual(a2, b1)).toBe(true); expect(isEqual(a2, b2)).toBe(true); expect(isEqual(a3, b1)).toBe(true); expect(isEqual(a3, b2)).toBe(true); expect(isEqual(a4, b1)).toBe(true); expect(isEqual(a4, b2)).toBe(true); expect(isEqual(a5, b1)).toBe(true); expect(isEqual(a5, b2)).toBe(true); expect(isEqual(a6, b1)).toBe(true); expect(isEqual(a6, b2)).toBe(true); }); test('different objects are not equal', () => { const a1 = { field1: undefined, field2: 'value2', field3: 'value3' }; const a2 = { field1: undefined, field3: 'value3', field2: 'value2' }; const a3 = { field2: 'value2', field3: 'value3', field1: undefined }; const a4 = { field2: 'value2', field1: undefined, field3: 'value3' }; const a5 = { field3: 'value3', field1: undefined, field2: 'value2' }; const a6 = { field3: 'value3', field2: 'value2', field1: undefined }; const b1 = { field1: undefined, field2: 'value2', field3: 'DIFF' }; const b2 = { field1: undefined, field3: 'DIFF', field2: 'value2' }; const b3 = { field2: 'value2', field3: 'DIFF', field1: undefined }; const b4 = { field2: 'value2', field1: undefined, field3: 'DIFF' }; const b5 = { field3: 'DIFF', field1: undefined, field2: 'value2' }; const b6 = { field3: 'DIFF', field2: 'value2', field1: undefined }; expect(isEqual(a1, b1)).toBe(false); expect(isEqual(a1, b2)).toBe(false); expect(isEqual(a1, b3)).toBe(false); expect(isEqual(a1, b4)).toBe(false); expect(isEqual(a1, b5)).toBe(false); expect(isEqual(a1, b6)).toBe(false); expect(isEqual(a2, b1)).toBe(false); expect(isEqual(a2, b2)).toBe(false); expect(isEqual(a2, b3)).toBe(false); expect(isEqual(a2, b4)).toBe(false); expect(isEqual(a2, b5)).toBe(false); expect(isEqual(a2, b6)).toBe(false); expect(isEqual(a3, b1)).toBe(false); expect(isEqual(a3, b2)).toBe(false); expect(isEqual(a3, b3)).toBe(false); expect(isEqual(a3, b4)).toBe(false); expect(isEqual(a3, b5)).toBe(false); expect(isEqual(a3, b6)).toBe(false); expect(isEqual(a4, b1)).toBe(false); expect(isEqual(a4, b2)).toBe(false); expect(isEqual(a4, b3)).toBe(false); expect(isEqual(a4, b4)).toBe(false); expect(isEqual(a4, b5)).toBe(false); expect(isEqual(a4, b6)).toBe(false); expect(isEqual(a5, b1)).toBe(false); expect(isEqual(a5, b2)).toBe(false); expect(isEqual(a5, b3)).toBe(false); expect(isEqual(a5, b4)).toBe(false); expect(isEqual(a5, b5)).toBe(false); expect(isEqual(a5, b6)).toBe(false); expect(isEqual(a6, b1)).toBe(false); expect(isEqual(a6, b2)).toBe(false); expect(isEqual(a6, b3)).toBe(false); expect(isEqual(a6, b4)).toBe(false); expect(isEqual(a6, b5)).toBe(false); expect(isEqual(a6, b6)).toBe(false); }); test('different objects where A has missing keys are not equal', () => { const a1 = { field2: 'value2', field3: 'value3' }; const a2 = { field3: 'value3', field2: 'value2' }; const b1 = { field1: undefined, field2: 'value2', field3: 'DIFF' }; const b2 = { field1: undefined, field3: 'DIFF', field2: 'value2' }; const b3 = { field2: 'value2', field3: 'DIFF', field1: undefined }; const b4 = { field2: 'value2', field1: undefined, field3: 'DIFF' }; const b5 = { field3: 'DIFF', field1: undefined, field2: 'value2' }; const b6 = { field3: 'DIFF', field2: 'value2', field1: undefined }; expect(isEqual(a1, b1)).toBe(false); expect(isEqual(a1, b2)).toBe(false); expect(isEqual(a1, b3)).toBe(false); expect(isEqual(a1, b4)).toBe(false); expect(isEqual(a1, b5)).toBe(false); expect(isEqual(a1, b6)).toBe(false); expect(isEqual(a2, b1)).toBe(false); expect(isEqual(a2, b2)).toBe(false); expect(isEqual(a2, b3)).toBe(false); expect(isEqual(a2, b4)).toBe(false); expect(isEqual(a2, b5)).toBe(false); expect(isEqual(a2, b6)).toBe(false); }); test('different objects where B has missing keys not equal', () => { const a1 = { field1: undefined, field2: 'value2', field3: 'value3' }; const a2 = { field1: undefined, field3: 'value3', field2: 'value2' }; const a3 = { field2: 'value2', field3: 'value3', field1: undefined }; const a4 = { field2: 'value2', field1: undefined, field3: 'value3' }; const a5 = { field3: 'value3', field1: undefined, field2: 'value2' }; const a6 = { field3: 'value3', field2: 'value2', field1: undefined }; const b1 = { field2: 'value2', field3: 'DIFF' }; const b2 = { field3: 'DIFF', field2: 'value2' }; expect(isEqual(a1, b1)).toBe(false); expect(isEqual(a1, b2)).toBe(false); expect(isEqual(a2, b1)).toBe(false); expect(isEqual(a2, b2)).toBe(false); expect(isEqual(a3, b1)).toBe(false); expect(isEqual(a3, b2)).toBe(false); expect(isEqual(a4, b1)).toBe(false); expect(isEqual(a4, b2)).toBe(false); expect(isEqual(a5, b1)).toBe(false); expect(isEqual(a5, b2)).toBe(false); expect(isEqual(a6, b1)).toBe(false); expect(isEqual(a6, b2)).toBe(false); }); }); ================================================ FILE: packages/vee-validate/tests/validate.spec.ts ================================================ import { validate, defineRule, configure } from '@/vee-validate'; import { numeric } from '@/rules'; import { getConfig } from '../src/config'; test('allows empty rules for the string format', async () => { defineRule('numeric', numeric); let result = await validate(100, '|numeric'); expect(result.errors).toHaveLength(0); result = await validate(100, '||||numeric'); expect(result.errors).toHaveLength(0); }); test('handles targets expressed in objects', async () => { defineRule('confirmed', (value: string, { target }: any) => { return value === target ? true : 'must match'; }); let result = await validate('test', { confirmed: { target: '@other' } }, { values: { other: '' } }); expect(result.errors).toHaveLength(1); result = await validate('test', { confirmed: { target: '@other' } }, { values: { other: 'test' } }); expect(result.errors).toHaveLength(0); }); // #3077 test('target params are filled in the params in message context', async () => { defineRule('lessThan', (value: number, params: any) => Number(value) < Number(params[0])); const { generateMessage: original } = getConfig(); configure({ generateMessage: context => { const params = context.rule?.params as any; return `This value must be less than ${params[0]}`; }, }); const result = await validate(2, 'lessThan:@other', { values: { other: 1 } }); expect(result.errors).toContain(`This value must be less than 1`); configure({ generateMessage: original, }); }); ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - 'packages/**' - 'docs' - '!**/tests/**' ================================================ FILE: scripts/build.mjs ================================================ import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs-extra'; import { exec as execCb } from 'child_process'; import { promisify } from 'util'; import { rollup } from 'rollup'; import chalk from 'chalk'; import * as Terser from 'terser'; import { createConfig } from './config.mjs'; import { reportSize } from './info.mjs'; import { generateDts } from './generate-dts.mjs'; const exec = promisify(execCb); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); async function buildLocales() { const localesDir = path.join(__dirname, '../packages/i18n/src/locale'); const files = fs.readdirSync(localesDir); console.log(chalk.cyan('Building i18n...')); for (let i = 0; i < files.length; i++) { const file = files[i]; process.stdout.write(`${chalk.green(`Output File ${i}/${files.length}: `)} ${file}`); const input = path.join(__dirname, '../packages/i18n/src/locale', file); const out = path.join(__dirname, '../packages/i18n/dist/locale', file); fs.copySync(input, out); console.log('/n'); } } async function minify({ code, pkg, bundleName }) { const pkgout = path.join(__dirname, `../packages/${pkg}/dist`); const output = await Terser.minify(code, { compress: true, mangle: true, }); const fileName = bundleName.replace(/\.js$/, '.prod.js'); const filePath = `${pkgout}/${fileName}.mjs`; fs.outputFileSync(filePath, output.code); const stats = reportSize({ code: output.code, path: filePath }); console.log(`${chalk.green('Output File:')} ${fileName} ${stats}`); } async function build(pkg) { if (pkg === 'nuxt') { console.log(chalk.magenta(`Generating bundle for ${pkg}`)); const result = process.platform === 'win32' ? await exec('cd packages/nuxt && pnpm build && cd ../..') : await exec('cd packages/nuxt && pnpm build && cd -'); console.log(result.stdout); if (result.stderr) { console.error(result.stderr); process.exit(1); } console.log(`${chalk.magenta('✅ Bundled ' + pkg)}`); return; } console.log(chalk.magenta(`Generating bundle for ${pkg}`)); const pkgout = path.join(__dirname, `../packages/${pkg}/dist`); await fs.emptyDir(pkgout); for (const format of ['esm', 'iife', 'cjs']) { const { input, output, bundleName } = await createConfig(pkg, format); const bundle = await rollup(input); const { output: [{ code }], } = await bundle.generate(output); const outputPath = path.join(pkgout, bundleName); fs.outputFileSync(outputPath, code); const stats = reportSize({ code, path: outputPath }); console.log(`${chalk.green('Output File:')} ${bundleName} ${stats}`); if (format === 'iife') { await minify({ bundleName, pkg, code }); } } await generateDts(pkg); console.log(`${chalk.magenta('✅ Bundled ' + pkg)}`); return true; } (async function Bundle() { const arg = [...process.argv][2]; if (arg === 'vee-validate' || !arg) { await build('vee-validate'); } if (arg === 'rules' || !arg) { await build('rules'); } if (arg === 'nuxt' || !arg) { await build('nuxt'); } if (arg === 'i18n' || !arg) { await build('i18n'); await buildLocales(); } if (process.platform === 'win32') process.exit(0); })(); ================================================ FILE: scripts/config.mjs ================================================ import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; import typescript from '@rollup/plugin-typescript'; import replace from '@rollup/plugin-replace'; import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import { normalizePath, slashes } from './normalize-path.mjs'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const formatNameMap = { 'vee-validate': 'VeeValidate', rules: 'VeeValidateRules', i18n: 'VeeValidateI18n', }; const pkgNameMap = { 'vee-validate': 'vee-validate', rules: 'vee-validate-rules', i18n: 'vee-validate-i18n', }; const formatExt = { esm: 'mjs', iife: 'iife.js', cjs: 'cjs', }; async function createConfig(pkg, format) { const tsPlugin = typescript({ declarationDir: normalizePath(path.resolve(__dirname, `../packages/${pkg}/dist`)), }); // An import assertion in a dynamic import const { default: info } = await import(normalizePath(path.resolve(__dirname, `../packages/${pkg}/package.json`)), { with: { type: 'json', }, }); const { version } = info; const isEsm = format === 'esm'; const config = { input: { input: slashes(path.resolve(__dirname, `../packages/${pkg}/src/index.ts`)), external: [ 'vue', isEsm ? '@vue/devtools-api' : undefined, isEsm ? '@vue/devtools-kit' : undefined, 'vee-validate', ].filter(Boolean), plugins: [ replace({ preventAssignment: true, values: { __VERSION__: version, __DEV__: isEsm ? `(process.env.NODE_ENV !== 'production')` : 'false', }, }), tsPlugin, resolve({ dedupe: ['klona', 'klona/full'], }), commonjs(), ], }, output: { banner: `/** * vee-validate v${version} * (c) ${new Date().getFullYear()} Abdelrahman Awad * @license MIT */`, format, name: format === 'iife' ? formatNameMap[pkg] : undefined, globals: { vue: 'Vue', 'vee-validate': 'VeeValidate', }, }, }; config.bundleName = `${pkgNameMap[pkg]}.${formatExt[format] ?? 'js'}`; // if (options.env) { // config.input.plugins.unshift( // replace({ // 'process.env.NODE_ENV': JSON.stringify(options.env) // }) // ); // } return config; } export { formatNameMap, pkgNameMap, formatExt, createConfig }; ================================================ FILE: scripts/copy-mds.mjs ================================================ import fs from 'fs-extra'; import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; import chalk from 'chalk'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); fs.copyFileSync(path.resolve(__dirname, '../README.md'), path.resolve(__dirname, '../packages/vee-validate/README.md')); console.log(chalk.green('📄 README.md copied to packages/vee-validate')); fs.copyFileSync(path.resolve(__dirname, '../LICENSE'), path.resolve(__dirname, '../packages/vee-validate/LICENSE')); console.log(chalk.green('📄 LICENSE copied to packages/vee-validate')); fs.copyFileSync( path.resolve(__dirname, '../packages/vee-validate/CHANGELOG.md'), path.resolve(__dirname, '../CHANGELOG.md'), ); console.log(chalk.green('📄 CHANGELOG.md copied to root')); ================================================ FILE: scripts/generate-dts.mjs ================================================ import ts from 'typescript'; import chalk from 'chalk'; import path, { dirname } from 'path'; import { fileURLToPath } from 'url'; import fs from 'fs-extra'; import { rollup } from 'rollup'; import dts from 'rollup-plugin-dts'; import tsconfig from '../tsconfig.json' with { type: 'json' }; import { pkgNameMap } from './config.mjs'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export async function generateDts(pkg) { console.log(chalk.cyan(`Generating Declaration Files for ${pkg} ...`)); const declarationDir = `../packages/${pkg}/dist/types`; const options = { ...tsconfig, declaration: true, declarationMap: false, emitDeclarationOnly: true, declarationDir, }; const host = ts.createCompilerHost(options); const createdFiles = {}; host.writeFile = (fileName, contents) => { createdFiles[fileName] = contents; }; // Prepare and emit the d.ts files const program = ts.createProgram([path.resolve(__dirname, `../packages/${pkg}/src/index.ts`)], options, host); program.emit(); for (const [file, contents] of Object.entries(createdFiles)) { fs.outputFileSync(path.resolve(__dirname, file), contents); } await bundleDts(declarationDir, pkg); } async function bundleDts(declarationDir, pkg) { let entry = path.join(__dirname, declarationDir, 'index.d.ts'); // if it doesn't exist then probably was nested cause of relative imports if (!fs.existsSync(entry)) { entry = path.resolve(__dirname, declarationDir, pkg, 'src', 'index.d.ts'); } // If we cannot find the 'index.d.ts', panic! if (!fs.existsSync(entry)) { throw new Error('Cannot find index.d.ts at ' + entry); } // Generate .d.ts rollup const config = { input: entry, output: { file: `packages/${pkg}/dist/${pkgNameMap[pkg]}.d.ts`, format: 'es' }, plugins: [dts()], }; const bundle = await rollup(config); await bundle.write(config.output); await fs.remove(`packages/${pkg}/dist/types`); console.log(`${chalk.cyan('Bundled ' + pkg + ' Declaration Files...')}`); } ================================================ FILE: scripts/info.mjs ================================================ import fs from 'fs-extra'; import { filesize } from 'filesize'; import { gzipSizeSync } from 'gzip-size'; function reportSize({ path, code }) { const { size } = fs.statSync(path); const gzipped = gzipSizeSync(code); return `| Size: ${filesize(size)} | Gzip: ${filesize(gzipped)}`; } export { reportSize }; ================================================ FILE: scripts/normalize-path.mjs ================================================ const windows = process.platform === 'win32'; export function slashes(path) { return path.replace(/\\/g, '/'); } export function normalizePath(path) { if (!windows) return path; const normalized = slashes(path); // check for absolute path: C:/... return /^\w:\//.test(normalized) ? `file:///${normalized}` : normalized; } ================================================ FILE: scripts/release.sh ================================================ #!/bin/bash pnpm build pnpm changeset version pnpm install git add . git commit -m "chore(release): publish" pnpm changeset publish git push --follow-tags ================================================ FILE: scripts/tag-release.mjs ================================================ import pkg from '../packages/vee-validate/package.json' with { type: 'json' }; import { execSync } from 'node:child_process'; import chalk from 'chalk'; try { execSync(`git tag v${pkg.version}`); console.log(chalk.green(`🔖 Tagged release v${pkg.version}`)); } catch (error) { console.log(chalk.red(`❌ Failed to tag release v${pkg.version}`)); console.log(error); process.exit(1); } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "baseUrl": ".", "rootDir": ".", "moduleResolution": "node", "target": "es2017", "module": "esnext", "lib": ["esnext", "es2017", "dom"], "sourceMap": true, "declaration": true, "declarationMap": true, "noImplicitAny": true, "strict": true, "strictNullChecks": true, "strictBindCallApply": true, "strictFunctionTypes": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "paths": { "@/*": ["packages/*/src"] }, "types": ["vitest/globals"], "typeRoots": ["node_modules/@types", "node_modules"] }, "include": ["packages/*/src", "packages/*/tests", "vitest.setup.ts", "vitest.setup.ts"] } ================================================ FILE: vitest.config.ts ================================================ import { defineConfig, configDefaults } from 'vitest/config'; import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ plugins: [tsconfigPaths()], test: { setupFiles: ['./vitest.setup.ts'], environment: 'jsdom', globals: true, exclude: ['docs/*', ...configDefaults.exclude], coverage: { exclude: ['**/*/devtools.ts', 'packages/**/dist/**', 'docs/*', ...(configDefaults.coverage.exclude || [])], }, }, define: { __DEV__: JSON.stringify(true), }, }); ================================================ FILE: vitest.setup.ts ================================================ beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); });