mirror of
https://github.com/LukeHagar/form.git
synced 2025-12-06 04:19:43 +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:
@@ -101,6 +101,10 @@
|
||||
{
|
||||
"label": "Zod",
|
||||
"to": "framework/react/examples/zod"
|
||||
},
|
||||
{
|
||||
"label": "Valibot",
|
||||
"to": "framework/react/examples/valibot"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -153,6 +157,10 @@
|
||||
{
|
||||
"label": "Zod",
|
||||
"to": "framework/vue/examples/zod"
|
||||
},
|
||||
{
|
||||
"label": "Valibot",
|
||||
"to": "framework/vue/examples/valibot"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -205,6 +213,10 @@
|
||||
{
|
||||
"label": "Zod",
|
||||
"to": "framework/solid/examples/zod"
|
||||
},
|
||||
{
|
||||
"label": "Valibot",
|
||||
"to": "framework/solid/examples/valibot"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ Example:
|
||||
|
||||
## Validation Adapters
|
||||
|
||||
In addition to hand-rolled validation options, we also provide adapters like `@tanstack/zod-form-adapter` and `@tanstack/yup-form-adapter` to enable usage with common schema validation tools like [Yup](https://github.com/jquense/yup) and [Zod](https://zod.dev/).
|
||||
In addition to hand-rolled validation options, we also provide adapters like `@tanstack/zod-form-adapter`, `@tanstack/yup-form-adapter`, and `@tanstack/valibot-form-adapter` to enable usage with common schema validation tools like [Zod](https://zod.dev/), [Yup](https://github.com/jquense/yup), and [Valibot](https://valibot.dev/).
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -246,9 +246,9 @@ This will debounce every async call with a 500ms delay. You can even override th
|
||||
> This will run `onChangeAsync` every 1500ms while `onBlurAsync` will run every 500ms.
|
||||
|
||||
|
||||
## Adapter-Based Validation (Zod, Yup)
|
||||
## Adapter-Based Validation (Zod, Yup, Valibot)
|
||||
|
||||
While functions provide more flexibility and customization over your validation, they can be a bit verbose. To help solve this, there are libraries like [Yup](https://github.com/jquense/yup) and [Zod](https://zod.dev/) that provide schema-based validation to make shorthand and type-strict validation substantially easier.
|
||||
While functions provide more flexibility and customization over your validation, they can be a bit verbose. To help solve this, there are libraries like [Valibot](https://valibot.dev/), [Yup](https://github.com/jquense/yup), and [Zod](https://zod.dev/) that provide schema-based validation to make shorthand and type-strict validation substantially easier.
|
||||
|
||||
Luckily, we support both of these libraries through official adapters:
|
||||
|
||||
@@ -256,6 +256,8 @@ Luckily, we support both of these libraries through official adapters:
|
||||
$ npm install @tanstack/zod-form-adapter zod
|
||||
# or
|
||||
$ npm install @tanstack/yup-form-adapter yup
|
||||
# or
|
||||
$ npm install @tanstack/valibot-form-adapter valibot
|
||||
```
|
||||
|
||||
Once done, we can add the adapter to the `validator` property on the form or field:
|
||||
|
||||
@@ -21,4 +21,6 @@ In addition, we support both Zod and Yup as validators through official validato
|
||||
$ npm i @tanstack/zod-form-adapter zod
|
||||
# or
|
||||
$ npm i @tanstack/yup-form-adapter yup
|
||||
# or
|
||||
$ npm i @tanstack/valibot-form-adapter valibot
|
||||
```
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-form": "0.8.1",
|
||||
"axios": "^0.26.1",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"@tanstack/form-core": "0.8.1",
|
||||
|
||||
15
examples/react/valibot/.eslintrc.cjs
Normal file
15
examples/react/valibot/.eslintrc.cjs
Normal file
@@ -0,0 +1,15 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
const config = {
|
||||
extends: ["plugin:react/recommended", "plugin:react-hooks/recommended"],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
rules: {
|
||||
"react/no-children-prop": "off",
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
27
examples/react/valibot/.gitignore
vendored
Normal file
27
examples/react/valibot/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
1
examples/react/valibot/.prettierrc
Normal file
1
examples/react/valibot/.prettierrc
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
6
examples/react/valibot/README.md
Normal file
6
examples/react/valibot/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Example
|
||||
|
||||
To run this example:
|
||||
|
||||
- `npm install`
|
||||
- `npm run dev`
|
||||
16
examples/react/valibot/index.html
Normal file
16
examples/react/valibot/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/emblem-light.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<title>TanStack Form React Valibot Example App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
52
examples/react/valibot/package.json
Normal file
52
examples/react/valibot/package.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@tanstack/form-example-react-valibot",
|
||||
"version": "0.0.1",
|
||||
"main": "src/index.jsx",
|
||||
"scripts": {
|
||||
"dev": "vite --port=3001",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-form": "0.7.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"valibot": "^0.20.1",
|
||||
"@tanstack/valibot-form-adapter": "0.7.2",
|
||||
"@tanstack/form-core": "0.7.2",
|
||||
"@tanstack/zod-form-adapter": "0.7.2",
|
||||
"@tanstack/vue-form": "0.7.2",
|
||||
"@tanstack/yup-form-adapter": "0.7.2",
|
||||
"@tanstack/solid-form": "0.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.0.4",
|
||||
"vite": "^4.4.9"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"nx": {
|
||||
"implicitDependencies": [
|
||||
"@tanstack/form-core",
|
||||
"@tanstack/react-form",
|
||||
"@tanstack/zod-form-adapter"
|
||||
],
|
||||
"targets": {
|
||||
"test:types": {
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
examples/react/valibot/public/emblem-light.svg
Normal file
13
examples/react/valibot/public/emblem-light.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="190px" height="190px" viewBox="0 0 190 190" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
|
||||
<title>emblem-light</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g>
|
||||
<path d="M39.7239712,61.3436237 C36.631224,46.362877 35.9675112,34.8727722 37.9666331,26.5293551 C39.1555965,21.5671678 41.3293088,17.5190846 44.6346064,14.5984631 C48.1241394,11.5150478 52.5360327,10.0020122 57.493257,10.0020122 C65.6712013,10.0020122 74.2682602,13.7273214 83.4557246,20.8044264 C87.2031203,23.6910458 91.0924366,27.170411 95.1316515,31.2444746 C95.4531404,30.8310265 95.8165416,30.4410453 96.2214301,30.0806152 C107.64098,19.9149716 117.255245,13.5989272 125.478408,11.1636507 C130.367899,9.715636 134.958526,9.57768202 139.138936,10.983031 C143.551631,12.4664684 147.06766,15.5329489 149.548314,19.8281091 C153.642288,26.9166735 154.721918,36.2310983 153.195595,47.7320243 C152.573451,52.4199112 151.50985,57.5263831 150.007094,63.0593153 C150.574045,63.1277086 151.142416,63.2532808 151.705041,63.4395297 C166.193932,68.2358678 176.453582,73.3937462 182.665021,79.2882839 C186.360669,82.7953831 188.773972,86.6998434 189.646365,91.0218204 C190.567176,95.5836746 189.669313,100.159332 187.191548,104.451297 C183.105211,111.529614 175.591643,117.11221 164.887587,121.534031 C160.589552,123.309539 155.726579,124.917559 150.293259,126.363748 C150.541176,126.92292 150.733521,127.516759 150.862138,128.139758 C153.954886,143.120505 154.618598,154.61061 152.619477,162.954027 C151.430513,167.916214 149.256801,171.964297 145.951503,174.884919 C142.46197,177.968334 138.050077,179.48137 133.092853,179.48137 C124.914908,179.48137 116.31785,175.756061 107.130385,168.678956 C103.343104,165.761613 99.4108655,162.238839 95.3254337,158.108619 C94.9050753,158.765474 94.3889681,159.376011 93.7785699,159.919385 C82.3590198,170.085028 72.744755,176.401073 64.5215915,178.836349 C59.6321009,180.284364 55.0414736,180.422318 50.8610636,179.016969 C46.4483686,177.533532 42.9323404,174.467051 40.4516862,170.171891 C36.3577116,163.083327 35.2780823,153.768902 36.8044053,142.267976 C37.449038,137.410634 38.56762,132.103898 40.1575891,126.339009 C39.5361041,126.276104 38.9120754,126.144816 38.2949591,125.940529 C23.8060684,121.144191 13.5464184,115.986312 7.33497892,110.091775 C3.63933121,106.584675 1.22602752,102.680215 0.353635235,98.3582381 C-0.567176333,93.7963839 0.330686581,89.2207269 2.80845236,84.9287618 C6.89478863,77.8504443 14.4083565,72.2678481 25.1124133,67.8460273 C29.5385143,66.0176154 34.5637208,64.366822 40.1939394,62.8874674 C39.9933393,62.3969171 39.8349374,61.8811235 39.7239712,61.3436237 Z" fill="#002C4B" fill-rule="nonzero" transform="translate(95.000000, 95.000000) scale(-1, 1) translate(-95.000000, -95.000000) "></path>
|
||||
<path d="M80.3968824,64 L109.608177,64 C111.399254,64 113.053521,64.958025 113.944933,66.5115174 L128.577138,92.0115174 C129.461464,93.5526583 129.461464,95.4473417 128.577138,96.9884826 L113.944933,122.488483 C113.053521,124.041975 111.399254,125 109.608177,125 L80.3968824,125 C78.6058059,125 76.9515387,124.041975 76.0601262,122.488483 L61.4279211,96.9884826 C60.543596,95.4473417 60.543596,93.5526583 61.4279211,92.0115174 L76.0601262,66.5115174 C76.9515387,64.958025 78.6058059,64 80.3968824,64 Z M105.987827,70.2765273 C107.779849,70.2765273 109.434839,71.2355558 110.325899,72.7903404 L121.343038,92.0138131 C122.225607,93.5537825 122.225607,95.4462175 121.343038,96.9861869 L110.325899,116.20966 C109.434839,117.764444 107.779849,118.723473 105.987827,118.723473 L84.0172329,118.723473 C82.2252106,118.723473 80.5702207,117.764444 79.6791602,116.20966 L68.6620219,96.9861869 C67.7794521,95.4462175 67.7794521,93.5537825 68.6620219,92.0138131 L79.6791602,72.7903404 C80.5702207,71.2355558 82.2252106,70.2765273 84.0172329,70.2765273 L105.987827,70.2765273 Z M102.080648,77.1414791 L87.9244113,77.1414791 C86.1342282,77.1414791 84.4806439,78.0985567 83.5888998,79.6508285 L83.5888998,79.6508285 L76.4892166,92.0093494 C75.6032319,93.5515958 75.6032319,95.4484042 76.4892166,96.9906506 L76.4892166,96.9906506 L83.5888998,109.349172 C84.4806439,110.901443 86.1342282,111.858521 87.9244113,111.858521 L87.9244113,111.858521 L102.080648,111.858521 C103.870831,111.858521 105.524416,110.901443 106.41616,109.349172 L106.41616,109.349172 L113.515843,96.9906506 C114.401828,95.4484042 114.401828,93.5515958 113.515843,92.0093494 L113.515843,92.0093494 L106.41616,79.6508285 C105.524416,78.0985567 103.870831,77.1414791 102.080648,77.1414791 L102.080648,77.1414791 Z M98.3191856,83.7122186 C100.108028,83.7122186 101.760587,84.6678753 102.652827,86.2183156 L105.983552,92.0060969 C106.87203,93.5500005 106.87203,95.4499995 105.983552,96.9939031 L102.652827,102.781684 C101.760587,104.332125 100.108028,105.287781 98.3191856,105.287781 L91.685874,105.287781 C89.8970316,105.287781 88.2444725,104.332125 87.3522326,102.781684 L84.021508,96.9939031 C83.1330298,95.4499995 83.1330298,93.5500005 84.021508,92.0060969 L87.3522326,86.2183156 C88.2444725,84.6678753 89.8970316,83.7122186 91.685874,83.7122186 L98.3191856,83.7122186 Z M95.0037937,90.1848875 C93.459294,90.1848875 92.0343817,91.0072828 91.2630046,92.3424437 C90.4917325,93.6774232 90.4917325,95.3225768 91.2630046,96.6575563 C92.0343817,97.9927172 93.459294,98.8151125 95.0012659,98.8151125 L95.0012659,98.8151125 C96.5457656,98.8151125 97.9706779,97.9927172 98.7420549,96.6575563 C99.5133271,95.3225768 99.5133271,93.6774232 98.7420549,92.3424437 C97.9706779,91.0072828 96.5457656,90.1848875 95.0037937,90.1848875 L95.0037937,90.1848875 Z M60,94.5009646 L67.7677636,94.5009646" fill="#FFD94C"></path>
|
||||
<path d="M54.8601729,108.357758 C56.1715224,107.608286 57.8360246,108.074601 58.5779424,109.399303 L58.5779424,109.399303 L59.0525843,110.24352 C62.8563392,116.982993 66.8190116,123.380176 70.9406016,129.435068 C75.8078808,136.585427 81.28184,143.82411 87.3624792,151.151115 C88.3168778,152.30114 88.1849437,154.011176 87.065686,154.997937 L87.065686,154.997937 L86.4542085,155.534625 C66.3465389,173.103314 53.2778188,176.612552 47.2480482,166.062341 C41.3500652,155.742717 43.4844915,136.982888 53.6513274,109.782853 C53.876818,109.179582 54.3045861,108.675291 54.8601729,108.357758 Z M140.534177,129.041504 C141.986131,128.785177 143.375496,129.742138 143.65963,131.194242 L143.65963,131.194242 L143.812815,131.986376 C148.782365,157.995459 145.283348,171 133.315764,171 C121.609745,171 106.708724,159.909007 88.6127018,137.727022 C88.2113495,137.235047 87.9945723,136.617371 88,135.981509 C88.013158,134.480686 89.2357854,133.274651 90.730918,133.287756 L90.730918,133.287756 L91.6846544,133.294531 C99.3056979,133.335994 106.714387,133.071591 113.910723,132.501323 C122.409039,131.82788 131.283523,130.674607 140.534177,129.041504 Z M147.408726,73.8119663 C147.932139,72.4026903 149.508386,71.6634537 150.954581,72.149012 L150.954581,72.149012 L151.742552,72.4154854 C177.583763,81.217922 187.402356,90.8916805 181.198332,101.436761 C175.129904,111.751366 157.484347,119.260339 128.26166,123.963678 C127.613529,124.067994 126.948643,123.945969 126.382735,123.618843 C125.047025,122.846729 124.602046,121.158214 125.388848,119.847438 L125.388848,119.847438 L125.889328,119.0105 C129.877183,112.31633 133.481358,105.654262 136.701854,99.0242957 C140.50501,91.1948179 144.073967,82.7907081 147.408726,73.8119663 Z M61.7383398,66.0363218 C62.3864708,65.9320063 63.0513565,66.0540315 63.6172646,66.3811573 C64.9529754,67.153271 65.3979538,68.8417862 64.6111517,70.1525615 L64.6111517,70.1525615 L64.1106718,70.9895001 C60.1228168,77.6836699 56.5186416,84.3457379 53.2981462,90.9757043 C49.49499,98.8051821 45.9260328,107.209292 42.5912744,116.188034 C42.0678608,117.59731 40.4916142,118.336546 39.045419,117.850988 L39.045419,117.850988 L38.2574475,117.584515 C12.4162372,108.782078 2.59764398,99.1083195 8.80166786,88.5632391 C14.8700957,78.2486335 32.515653,70.7396611 61.7383398,66.0363218 Z M103.545792,34.4653746 C123.653461,16.8966864 136.722181,13.3874478 142.751952,23.9376587 C148.649935,34.2572826 146.515508,53.0171122 136.348673,80.2171474 C136.123182,80.8204179 135.695414,81.324709 135.139827,81.6422422 C133.828478,82.3917144 132.163975,81.9253986 131.422058,80.6006966 L131.422058,80.6006966 L130.947416,79.7564798 C127.143661,73.0170065 123.180988,66.6198239 119.059398,60.564932 C114.192119,53.4145727 108.71816,46.1758903 102.637521,38.8488847 C101.683122,37.6988602 101.815056,35.9888243 102.934314,35.0020629 L102.934314,35.0020629 Z M57.6842361,18 C69.3902551,18 84.2912758,29.0909926 102.387298,51.2729777 C102.788651,51.7649527 103.005428,52.3826288 103,53.0184911 C102.986842,54.5193144 101.764215,55.7253489 100.269082,55.7122445 L100.269082,55.7122445 L99.3153456,55.7054689 C91.6943021,55.6640063 84.2856126,55.9284091 77.0892772,56.4986773 C68.5909612,57.17212 59.7164767,58.325393 50.4658235,59.9584962 C49.0138691,60.2148231 47.6245044,59.2578618 47.3403697,57.805758 L47.3403697,57.805758 L47.1871846,57.0136235 C42.2176347,31.0045412 45.7166519,18 57.6842361,18 Z" fill="#FF4154"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.1 KiB |
120
examples/react/valibot/src/index.tsx
Normal file
120
examples/react/valibot/src/index.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import * as React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { useForm } from "@tanstack/react-form";
|
||||
import { valibotValidator } from "@tanstack/valibot-form-adapter";
|
||||
import { string, minLength, stringAsync, PipeResult } from "valibot";
|
||||
import type { FieldApi } from "@tanstack/react-form";
|
||||
|
||||
function FieldInfo({ field }: { field: FieldApi<any, any, unknown, unknown> }) {
|
||||
return (
|
||||
<>
|
||||
{field.state.meta.touchedErrors ? (
|
||||
<em>{field.state.meta.touchedErrors}</em>
|
||||
) : null}
|
||||
{field.state.meta.isValidating ? "Validating..." : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
},
|
||||
onSubmit: async (values) => {
|
||||
// Do something with form data
|
||||
console.log(values);
|
||||
},
|
||||
// Add a validator to support Valibot usage in Form and Field
|
||||
validator: valibotValidator,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Valibot Form Example</h1>
|
||||
<form.Provider>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
void form.handleSubmit();
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{/* A type-safe field component*/}
|
||||
<form.Field
|
||||
name="firstName"
|
||||
onChange={string([
|
||||
minLength(3, "First name must be at least 3 characters"),
|
||||
])}
|
||||
onChangeAsyncDebounceMs={500}
|
||||
onChangeAsync={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>;
|
||||
},
|
||||
])}
|
||||
children={(field) => {
|
||||
// Avoid hasty abstractions. Render props are great!
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={field.name}>First Name:</label>
|
||||
<input
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
/>
|
||||
<FieldInfo field={field} />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<form.Field
|
||||
name="lastName"
|
||||
children={(field) => (
|
||||
<>
|
||||
<label htmlFor={field.name}>Last Name:</label>
|
||||
<input
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
/>
|
||||
<FieldInfo field={field} />
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<form.Subscribe
|
||||
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||
children={([canSubmit, isSubmitting]) => (
|
||||
<button type="submit" disabled={!canSubmit}>
|
||||
{isSubmitting ? "..." : "Submit"}
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</form.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const rootElement = document.getElementById("root")!;
|
||||
|
||||
createRoot(rootElement).render(<App />);
|
||||
7
examples/react/valibot/tsconfig.json
Normal file
7
examples/react/valibot/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"noEmit": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2020"]
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-form": "0.8.1",
|
||||
"axios": "^0.26.1",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"yup": "^1.3.2",
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-form": "0.8.1",
|
||||
"axios": "^0.26.1",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"zod": "^3.21.4",
|
||||
|
||||
24
examples/solid/valibot/.gitignore
vendored
Normal file
24
examples/solid/valibot/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
28
examples/solid/valibot/README.md
Normal file
28
examples/solid/valibot/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
$ npm install # or pnpm install or yarn install
|
||||
```
|
||||
|
||||
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm run dev`
|
||||
|
||||
Runs the app in the development mode.<br>
|
||||
Open [http://localhost:5173](http://localhost:5173) to view it in the browser.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `dist` folder.<br>
|
||||
It correctly bundles Solid in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br>
|
||||
Your app is ready to be deployed!
|
||||
|
||||
## Deployment
|
||||
|
||||
Learn more about deploying your application with the [documentations](https://vitejs.dev/guide/static-deploy.html)
|
||||
12
examples/solid/valibot/index.html
Normal file
12
examples/solid/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 Solid Valibot Example App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
40
examples/solid/valibot/package.json
Normal file
40
examples/solid/valibot/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@tanstack/form-example-solid-valibot",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"test:types": "tsc --noEmit",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/form-core": "0.4.1",
|
||||
"@tanstack/solid-form": "0.4.1",
|
||||
"@tanstack/valibot-form-adapter": "0.4.1",
|
||||
"solid-js": "^1.7.8",
|
||||
"valibot": "^0.20.1",
|
||||
"@tanstack/zod-form-adapter": "0.4.1",
|
||||
"@tanstack/react-form": "0.4.1",
|
||||
"@tanstack/yup-form-adapter": "0.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5",
|
||||
"vite-plugin-solid": "^2.7.0"
|
||||
},
|
||||
"nx": {
|
||||
"implicitDependencies": [
|
||||
"@tanstack/form-core",
|
||||
"@tanstack/solid-form",
|
||||
"@tanstack/zod-form-adapter"
|
||||
],
|
||||
"targets": {
|
||||
"test:types": {
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
129
examples/solid/valibot/src/index.tsx
Normal file
129
examples/solid/valibot/src/index.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
/* @refresh reload */
|
||||
import { render } from 'solid-js/web'
|
||||
|
||||
import { createForm, type FieldApi } from '@tanstack/solid-form'
|
||||
import { valibotValidator } from '@tanstack/valibot-form-adapter'
|
||||
import { string, minLength, stringAsync, PipeResult } from 'valibot'
|
||||
|
||||
interface FieldInfoProps {
|
||||
field: FieldApi<any, any, unknown, unknown>
|
||||
}
|
||||
|
||||
function FieldInfo(props: FieldInfoProps) {
|
||||
return (
|
||||
<>
|
||||
{props.field.state.meta.touchedErrors ? (
|
||||
<em>{props.field.state.meta.touchedErrors}</em>
|
||||
) : null}
|
||||
{props.field.state.meta.isValidating ? 'Validating...' : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
const form = createForm(() => ({
|
||||
defaultValues: {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
},
|
||||
onSubmit: async (values) => {
|
||||
// Do something with form data
|
||||
console.log(values)
|
||||
},
|
||||
// Add a validator to support Valibot usage in Form and Field
|
||||
validator: valibotValidator,
|
||||
}))
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Valibot Form Example</h1>
|
||||
<form.Provider>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
void form.handleSubmit()
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
{/* A type-safe field component*/}
|
||||
<form.Field
|
||||
name="firstName"
|
||||
onChange={string([
|
||||
minLength(3, 'First name must be at least 3 characters'),
|
||||
])}
|
||||
onChangeAsyncDebounceMs={500}
|
||||
onChangeAsync={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>
|
||||
},
|
||||
])}
|
||||
children={(field) => {
|
||||
// Avoid hasty abstractions. Render props are great!
|
||||
return (
|
||||
<>
|
||||
<label for={field().name}>First Name:</label>
|
||||
<input
|
||||
name={field().name}
|
||||
value={field().state.value}
|
||||
onBlur={field().handleBlur}
|
||||
onInput={(e) => field().handleChange(e.target.value)}
|
||||
/>
|
||||
<FieldInfo field={field()} />
|
||||
</>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<form.Field
|
||||
name="lastName"
|
||||
children={(field) => (
|
||||
<>
|
||||
<label for={field().name}>Last Name:</label>
|
||||
<input
|
||||
name={field().name}
|
||||
value={field().state.value}
|
||||
onBlur={field().handleBlur}
|
||||
onInput={(e) => field().handleChange(e.target.value)}
|
||||
/>
|
||||
<FieldInfo field={field()} />
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<form.Subscribe
|
||||
selector={(state) => ({
|
||||
canSubmit: state.canSubmit,
|
||||
isSubmitting: state.isSubmitting,
|
||||
})}
|
||||
children={(state) => {
|
||||
return (
|
||||
<button type="submit" disabled={!state().canSubmit}>
|
||||
{state().isSubmitting ? '...' : 'Submit'}
|
||||
</button>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</form>
|
||||
</form.Provider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const root = document.getElementById('root')
|
||||
|
||||
render(() => <App />, root!)
|
||||
1
examples/solid/valibot/src/vite-env.d.ts
vendored
Normal file
1
examples/solid/valibot/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
26
examples/solid/valibot/tsconfig.json
Normal file
26
examples/solid/valibot/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "solid-js",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
10
examples/solid/valibot/tsconfig.node.json
Normal file
10
examples/solid/valibot/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
6
examples/solid/valibot/vite.config.ts
Normal file
6
examples/solid/valibot/vite.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import solid from 'vite-plugin-solid'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [solid()],
|
||||
})
|
||||
@@ -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'],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -118,7 +118,8 @@
|
||||
"@tanstack/vue-form": "workspace:*",
|
||||
"@tanstack/solid-form": "workspace:*",
|
||||
"@tanstack/yup-form-adapter": "workspace:*",
|
||||
"@tanstack/zod-form-adapter": "workspace:*"
|
||||
"@tanstack/zod-form-adapter": "workspace:*",
|
||||
"@tanstack/valibot-form-adapter": "workspace:*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
packages/valibot-form-adapter/.eslintrc.cjs
Normal file
11
packages/valibot-form-adapter/.eslintrc.cjs
Normal file
@@ -0,0 +1,11 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
const config = {
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.eslint.json',
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
62
packages/valibot-form-adapter/package.json
Normal file
62
packages/valibot-form-adapter/package.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "@tanstack/valibot-form-adapter",
|
||||
"version": "0.6.1",
|
||||
"description": "The Valibot adapter for TanStack Form.",
|
||||
"author": "tannerlinsley",
|
||||
"license": "MIT",
|
||||
"repository": "tanstack/form",
|
||||
"homepage": "https://tanstack.com/form",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"type": "module",
|
||||
"types": "build/legacy/index.d.ts",
|
||||
"main": "build/legacy/index.cjs",
|
||||
"module": "build/legacy/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./build/modern/index.d.ts",
|
||||
"default": "./build/modern/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./build/modern/index.d.cts",
|
||||
"default": "./build/modern/index.cjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"build",
|
||||
"src"
|
||||
],
|
||||
"nx": {
|
||||
"targets": {
|
||||
"test:build": {
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf ./build && rimraf ./coverage",
|
||||
"test:eslint": "eslint --ext .ts,.tsx ./src",
|
||||
"test:types": "tsc --noEmit && vitest typecheck",
|
||||
"test:lib": "vitest run --coverage",
|
||||
"test:lib:dev": "pnpm run test:lib --watch",
|
||||
"test:build": "publint --strict",
|
||||
"build": "tsup"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/form-core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"valibot": "^0.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"valibot": "^0.20.0"
|
||||
}
|
||||
}
|
||||
1
packages/valibot-form-adapter/src/index.ts
Normal file
1
packages/valibot-form-adapter/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './validator'
|
||||
110
packages/valibot-form-adapter/src/tests/FieldApi.spec.ts
Normal file
110
packages/valibot-form-adapter/src/tests/FieldApi.spec.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { expect } from 'vitest'
|
||||
|
||||
import { FormApi, FieldApi } from '@tanstack/form-core'
|
||||
import { valibotValidator } from '../validator'
|
||||
import { customAsync, minLength, string, stringAsync } from 'valibot'
|
||||
import { sleep } from './utils'
|
||||
|
||||
describe('valibot field api', () => {
|
||||
it('should run an onChange with string validation', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
validator: valibotValidator,
|
||||
name: 'name',
|
||||
onChange: string([minLength(3, 'You must have a length of at least 3')]),
|
||||
})
|
||||
|
||||
field.mount()
|
||||
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
field.setValue('a', { touch: true })
|
||||
expect(field.getMeta().errors).toEqual([
|
||||
'You must have a length of at least 3',
|
||||
])
|
||||
field.setValue('asdf', { touch: true })
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
})
|
||||
|
||||
it('should run an onChange fn with valibot validation option enabled', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
validator: valibotValidator,
|
||||
name: 'name',
|
||||
onChange: (val) => (val === 'a' ? 'Test' : undefined),
|
||||
})
|
||||
|
||||
field.mount()
|
||||
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
field.setValue('a', { touch: true })
|
||||
expect(field.getMeta().errors).toEqual(['Test'])
|
||||
field.setValue('asdf', { touch: true })
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
})
|
||||
|
||||
it('should run an onChangeAsync with string validation', async () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
validator: valibotValidator,
|
||||
name: 'name',
|
||||
onChangeAsync: stringAsync([
|
||||
customAsync(async (val) => val.length > 3, 'Testing 123'),
|
||||
]),
|
||||
onChangeAsyncDebounceMs: 0,
|
||||
})
|
||||
|
||||
field.mount()
|
||||
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
field.setValue('a', { touch: true })
|
||||
await sleep(10)
|
||||
expect(field.getMeta().errors).toEqual(['Testing 123'])
|
||||
field.setValue('asdf', { touch: true })
|
||||
await sleep(10)
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
})
|
||||
|
||||
it('should run an onChangeAsyc fn with valibot validation option enabled', async () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
validator: valibotValidator,
|
||||
name: 'name',
|
||||
onChangeAsync: async (val) => (val === 'a' ? 'Test' : undefined),
|
||||
onChangeAsyncDebounceMs: 0,
|
||||
})
|
||||
|
||||
field.mount()
|
||||
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
field.setValue('a', { touch: true })
|
||||
await sleep(10)
|
||||
expect(field.getMeta().errors).toEqual(['Test'])
|
||||
field.setValue('asdf', { touch: true })
|
||||
await sleep(10)
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
})
|
||||
})
|
||||
97
packages/valibot-form-adapter/src/tests/FieldApi.test-d.ts
Normal file
97
packages/valibot-form-adapter/src/tests/FieldApi.test-d.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { valibotValidator } from '../validator'
|
||||
import { string, object } from 'valibot'
|
||||
import { FieldApi, FormApi } from '@tanstack/form-core'
|
||||
import { assertType } from 'vitest'
|
||||
|
||||
it('should allow a Valibot validator to be passed in', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
} as const)
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
validator: valibotValidator,
|
||||
} as const)
|
||||
})
|
||||
|
||||
it('should allow a Valibot validator to handle the correct Valibot type', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
validator: valibotValidator,
|
||||
onChange: string(),
|
||||
} as const)
|
||||
})
|
||||
|
||||
it('should allow a Valibot validator to handle the correct Valibot type for an async method', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
validator: valibotValidator,
|
||||
onChangeAsync: string(),
|
||||
} as const)
|
||||
})
|
||||
|
||||
it('should allow a functional onChange to be passed when using a validator', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
validator: valibotValidator,
|
||||
onChange: (val) => {
|
||||
assertType<'test'>(val)
|
||||
return undefined
|
||||
},
|
||||
} as const)
|
||||
})
|
||||
|
||||
it('should not allow a validator onChange to be passed when not using a validator', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
// @ts-expect-error Requires a validator
|
||||
onChange: string(),
|
||||
} as const)
|
||||
})
|
||||
|
||||
// This is not possible without higher-kinded types AFAIK
|
||||
it.skip('should allow not a Valibot validator with the wrong Valibot type', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
validator: valibotValidator,
|
||||
onChange: object({}),
|
||||
} as const)
|
||||
})
|
||||
55
packages/valibot-form-adapter/src/tests/FormApi.spec.ts
Normal file
55
packages/valibot-form-adapter/src/tests/FormApi.spec.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { expect } from 'vitest'
|
||||
|
||||
import { FieldApi, FormApi } from '@tanstack/form-core'
|
||||
import { minLength, string } from 'valibot'
|
||||
import { valibotValidator } from '../validator'
|
||||
|
||||
describe('valibot form api', () => {
|
||||
it('should run an onChange with string validation', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
validator: valibotValidator,
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
onChange: string([minLength(3, 'You must have a length of at least 3')]),
|
||||
})
|
||||
|
||||
field.mount()
|
||||
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
field.setValue('a', { touch: true })
|
||||
expect(field.getMeta().errors).toEqual([
|
||||
'You must have a length of at least 3',
|
||||
])
|
||||
field.setValue('asdf', { touch: true })
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
})
|
||||
|
||||
it('should run an onChange fn with valibot validation option enabled', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
validator: valibotValidator,
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
onChange: (val) => (val === 'a' ? 'Test' : undefined),
|
||||
})
|
||||
|
||||
field.mount()
|
||||
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
field.setValue('a', { touch: true })
|
||||
expect(field.getMeta().errors).toEqual(['Test'])
|
||||
field.setValue('asdf', { touch: true })
|
||||
expect(field.getMeta().errors).toEqual([])
|
||||
})
|
||||
})
|
||||
92
packages/valibot-form-adapter/src/tests/FormApi.test-d.ts
Normal file
92
packages/valibot-form-adapter/src/tests/FormApi.test-d.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { string, object } from 'valibot'
|
||||
import { valibotValidator } from '../validator'
|
||||
import { FieldApi, FormApi } from '@tanstack/form-core'
|
||||
import { assertType } from 'vitest'
|
||||
|
||||
it('should allow a Valibot validator to be passed in', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
validator: valibotValidator,
|
||||
} as const)
|
||||
})
|
||||
|
||||
it('should allow a Valibot validator to handle the correct Valibot type', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
validator: valibotValidator,
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
onChange: string(),
|
||||
} as const)
|
||||
})
|
||||
|
||||
it('should allow a Valibot validator to handle the correct Valibot type on async methods', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
validator: valibotValidator,
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
onChangeAsync: string(),
|
||||
} as const)
|
||||
})
|
||||
|
||||
it('should allow a functional onChange to be passed when using a validator', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
validator: valibotValidator,
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
onChange: (val) => {
|
||||
assertType<'test'>(val)
|
||||
return undefined
|
||||
},
|
||||
} as const)
|
||||
})
|
||||
|
||||
it('should not allow a validator onChange to be passed when not using a validator', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
// @ts-expect-error Requires a validator
|
||||
onChange: string(),
|
||||
} as const)
|
||||
})
|
||||
|
||||
// This is not possible without higher-kinded types AFAIK
|
||||
it.skip('should allow not a Valibot validator with the wrong Valibot type', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
const field = new FieldApi({
|
||||
form,
|
||||
name: 'name',
|
||||
validator: valibotValidator,
|
||||
onChange: object({}),
|
||||
} as const)
|
||||
})
|
||||
5
packages/valibot-form-adapter/src/tests/utils.ts
Normal file
5
packages/valibot-form-adapter/src/tests/utils.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export function sleep(timeout: number): Promise<void> {
|
||||
return new Promise((resolve, _reject) => {
|
||||
setTimeout(resolve, timeout)
|
||||
})
|
||||
}
|
||||
21
packages/valibot-form-adapter/src/validator.ts
Normal file
21
packages/valibot-form-adapter/src/validator.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { safeParse, safeParseAsync } from 'valibot'
|
||||
import type { BaseSchema, BaseSchemaAsync } from 'valibot'
|
||||
import type { ValidationError, Validator } from '@tanstack/form-core'
|
||||
|
||||
export const valibotValidator = (<
|
||||
Fn extends BaseSchema | BaseSchemaAsync = BaseSchema | BaseSchemaAsync,
|
||||
>() => {
|
||||
return {
|
||||
validate(value: unknown, fn: Fn): ValidationError {
|
||||
if (fn.async) return
|
||||
const result = safeParse(fn, value)
|
||||
if (result.success) return
|
||||
return result.issues.map((i) => i.message).join(', ')
|
||||
},
|
||||
async validateAsync(value: unknown, fn: Fn): Promise<ValidationError> {
|
||||
const result = await safeParseAsync(fn, value)
|
||||
if (result.success) return
|
||||
return result.issues.map((i) => i.message).join(', ')
|
||||
},
|
||||
}
|
||||
}) satisfies Validator<unknown>
|
||||
7
packages/valibot-form-adapter/tsconfig.eslint.json
Normal file
7
packages/valibot-form-adapter/tsconfig.eslint.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", ".eslintrc.cjs", "tsup.config.js"]
|
||||
}
|
||||
9
packages/valibot-form-adapter/tsconfig.json
Normal file
9
packages/valibot-form-adapter/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"composite": true,
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build/lib",
|
||||
"types": ["vitest/globals"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
9
packages/valibot-form-adapter/tsup.config.js
Normal file
9
packages/valibot-form-adapter/tsup.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
// @ts-check
|
||||
|
||||
import { defineConfig } from 'tsup'
|
||||
import { legacyConfig, modernConfig } from '../../getTsupConfig.js'
|
||||
|
||||
export default defineConfig([
|
||||
modernConfig({ entry: ['src/*.ts'] }),
|
||||
legacyConfig({ entry: ['src/*.ts'] }),
|
||||
])
|
||||
12
packages/valibot-form-adapter/vitest.config.ts
Normal file
12
packages/valibot-form-adapter/vitest.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
name: 'valibot-form-adapter',
|
||||
dir: './src',
|
||||
watch: false,
|
||||
environment: 'jsdom',
|
||||
globals: true,
|
||||
coverage: { provider: 'istanbul' },
|
||||
},
|
||||
})
|
||||
2376
pnpm-lock.yaml
generated
2376
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,10 @@ export const packages: Package[] = [
|
||||
name: '@tanstack/yup-form-adapter',
|
||||
packageDir: 'yup-form-adapter',
|
||||
},
|
||||
{
|
||||
name: '@tanstack/valibot-form-adapter',
|
||||
packageDir: 'valibot-form-adapter',
|
||||
},
|
||||
{
|
||||
name: '@tanstack/solid-form',
|
||||
packageDir: 'solid-form',
|
||||
@@ -58,6 +62,6 @@ export const rootDir = path.resolve(__dirname, '..')
|
||||
export const examplesDirs = [
|
||||
'examples/react',
|
||||
'examples/vue',
|
||||
// 'examples/solid',
|
||||
'examples/solid',
|
||||
// 'examples/svelte',
|
||||
]
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
"@tanstack/vue-form": ["packages/vue-form"],
|
||||
"@tanstack/solid-form": ["packages/solid-form"],
|
||||
"@tanstack/yup-form-adapter": ["packages/yup-form-adapter"],
|
||||
"@tanstack/zod-form-adapter": ["packages/zod-form-adapter"]
|
||||
"@tanstack/zod-form-adapter": ["packages/zod-form-adapter"],
|
||||
"@tanstack/valibot-form-adapter": ["packages/valibot-form-adapter"]
|
||||
}
|
||||
},
|
||||
"ts-node": {
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
{ "path": "packages/form-core" },
|
||||
{ "path": "packages/react-form" },
|
||||
{ "path": "packages/vue-form" },
|
||||
{ "path": "packages/solid-form" }
|
||||
{ "path": "packages/solid-form" },
|
||||
{ "path": "packages/yup-form-adapter" },
|
||||
{ "path": "packages/zod-form-adapter" },
|
||||
{ "path": "packages/valibot-form-adapter" }
|
||||
]
|
||||
// "include": ["examples/*"]
|
||||
// "exclude": ["node_modules"]
|
||||
|
||||
Reference in New Issue
Block a user