mirror of
https://github.com/LukeHagar/form.git
synced 2025-12-09 20:37:47 +00:00
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:
@@ -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
9
examples/vue/valibot/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
6
examples/vue/valibot/README.md
Normal file
6
examples/vue/valibot/README.md
Normal 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`
|
||||
12
examples/vue/valibot/index.html
Normal file
12
examples/vue/valibot/index.html
Normal 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>
|
||||
42
examples/vue/valibot/package.json
Normal file
42
examples/vue/valibot/package.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
examples/vue/valibot/src/App.vue
Normal file
99
examples/vue/valibot/src/App.vue
Normal 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>
|
||||
12
examples/vue/valibot/src/FieldInfo.vue
Normal file
12
examples/vue/valibot/src/FieldInfo.vue
Normal 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>
|
||||
5
examples/vue/valibot/src/main.ts
Normal file
5
examples/vue/valibot/src/main.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
5
examples/vue/valibot/src/shims-vue.d.ts
vendored
Normal file
5
examples/vue/valibot/src/shims-vue.d.ts
vendored
Normal 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
6
examples/vue/valibot/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface Post {
|
||||
userId: number
|
||||
id: number
|
||||
title: string
|
||||
body: string
|
||||
}
|
||||
15
examples/vue/valibot/tsconfig.json
Normal file
15
examples/vue/valibot/tsconfig.json
Normal 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"]
|
||||
}
|
||||
10
examples/vue/valibot/vite.config.ts
Normal file
10
examples/vue/valibot/vite.config.ts
Normal 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'],
|
||||
},
|
||||
})
|
||||
@@ -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'],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user