feat: valibot form adapter (#499)

* feat: mount method on FormApi

* fix solid-form test case

* fix: added form.mount() to tests

* feat: valibot-form-adapter

* chore: add missing config items

* docs: add Valibot React example

* docs: add Solid Valibot example

* docs: add valibot Vue example

* fix: valibot async adapter now works

* docs: add docs for valibot adapter

---------

Co-authored-by: Corbin Crutchley <git@crutchcorn.dev>
This commit is contained in:
Aadit Olkar
2023-11-05 19:57:45 +05:30
committed by GitHub
parent 1a37fabcf4
commit 31f6261208
57 changed files with 2162 additions and 1507 deletions

View File

@@ -5,6 +5,6 @@ import createVuePlugin from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [createVuePlugin()],
optimizeDeps: {
exclude: ['@tanstack/vue-query', 'vue-demi'],
exclude: ['@tanstack/vue-form', 'vue-demi'],
},
})

9
examples/vue/valibot/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
package-lock.json
yarn.lock
pnpm-lock.yaml

View File

@@ -0,0 +1,6 @@
# Basic example
To run this example:
- `npm install` or `yarn` or `pnpm i`
- `npm run dev` or `yarn dev` or `pnpm dev`

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TanStack Form Vue Valibot Example App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -0,0 +1,42 @@
{
"name": "@tanstack/form-example-vue-valibot",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"build:dev": "vite build -m development",
"test:types": "vue-tsc --noEmit",
"serve": "vite preview"
},
"dependencies": {
"@tanstack/form-core": "0.7.2",
"@tanstack/vue-form": "0.7.2",
"@tanstack/valibot-form-adapter": "0.7.2",
"vue": "^3.3.4",
"valibot": "^0.20.1",
"@tanstack/zod-form-adapter": "0.7.2",
"@tanstack/react-form": "0.7.2",
"@tanstack/yup-form-adapter": "0.7.2",
"@tanstack/solid-form": "0.7.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.4",
"typescript": "^5.0.4",
"vite": "^4.4.9",
"vue-tsc": "^1.8.10"
},
"nx": {
"implicitDependencies": [
"@tanstack/form-core",
"@tanstack/vue-form",
"@tanstack/zod-form-adapter"
],
"targets": {
"test:types": {
"dependsOn": [
"build"
]
}
}
}
}

View File

@@ -0,0 +1,99 @@
<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'
import FieldInfo from './FieldInfo.vue'
import { valibotValidator } from '@tanstack/valibot-form-adapter'
import { string, minLength, stringAsync, PipeResult } from 'valibot'
const form = useForm({
defaultValues: {
firstName: '',
lastName: '',
},
onSubmit: async (values) => {
// Do something with form data
alert(JSON.stringify(values))
},
// Add a validator to support Valibot usage in Form and Field
validator: valibotValidator,
})
form.provideFormContext()
const onChangeFirstName = stringAsync([
async (value) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return (
value.includes('error')
? {
issues: [
{
input: value,
validation: 'firstName',
message: "No 'error' allowed in first name",
},
],
}
: { output: value }
) as PipeResult<string>
},
])
</script>
<template>
<form
@submit="
(e) => {
e.preventDefault()
e.stopPropagation()
void form.handleSubmit()
}
"
>
<div>
<form.Field
name="firstName"
:onChange="
string([minLength(3, 'First name must be at least 3 characters')])
"
:onChangeAsyncDebounceMs="500"
:onChangeAsync="onChangeFirstName"
>
<template v-slot="{ field, state }">
<label :htmlFor="field.name">First Name:</label>
<input
:name="field.name"
:value="field.state.value"
@input="
(e) => field.handleChange((e.target as HTMLInputElement).value)
"
@blur="field.handleBlur"
/>
<FieldInfo :state="state" />
</template>
</form.Field>
</div>
<div>
<form.Field name="lastName">
<template v-slot="{ field, state }">
<label :htmlFor="field.name">Last Name:</label>
<input
:name="field.name"
:value="field.state.value"
@input="
(e) => field.handleChange((e.target as HTMLInputElement).value)
"
@blur="field.handleBlur"
/>
<FieldInfo :state="state" />
</template>
</form.Field>
</div>
<form.Subscribe>
<template v-slot="{ canSubmit, isSubmitting }">
<button type="submit" :disabled="!canSubmit">
{{ isSubmitting ? '...' : 'Submit' }}
</button>
</template>
</form.Subscribe>
</form>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import { FieldApi } from '@tanstack/vue-form'
const props = defineProps<{
state: FieldApi<any, any, unknown, unknown>['state']
}>()
</script>
<template>
<em v-for="error of props.state.meta.touchedErrors">{{ error }}</em>
{{ props.state.meta.isValidating ? 'Validating...' : null }}
</template>

View File

@@ -0,0 +1,5 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

View File

@@ -0,0 +1,5 @@
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

6
examples/vue/valibot/src/types.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
export interface Post {
userId: number
id: number
title: string
body: string
}

View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["vite/client"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"]
}

View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'vite'
import createVuePlugin from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [createVuePlugin()],
optimizeDeps: {
exclude: ['@tanstack/vue-form', 'vue-demi'],
},
})

View File

@@ -5,6 +5,6 @@ import createVuePlugin from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [createVuePlugin()],
optimizeDeps: {
exclude: ['@tanstack/vue-query', 'vue-demi'],
exclude: ['@tanstack/vue-form', 'vue-demi'],
},
})

View File

@@ -5,6 +5,6 @@ import createVuePlugin from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [createVuePlugin()],
optimizeDeps: {
exclude: ['@tanstack/vue-query', 'vue-demi'],
exclude: ['@tanstack/vue-form', 'vue-demi'],
},
})