Skip to content

Commit 4a4de7f

Browse files
authored
docs: add standard schema example to Lit (#1669)
* docs: add standard schema example to Lit * chore: add standard schema to docs pages
1 parent 99a576c commit 4a4de7f

File tree

9 files changed

+308
-0
lines changed

9 files changed

+308
-0
lines changed

docs/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,10 @@
618618
{
619619
"label": "UI Libraries",
620620
"to": "framework/lit/examples/ui-libraries"
621+
},
622+
{
623+
"label": "Standard Schema",
624+
"to": "framework/lit/examples/standard-schema"
621625
}
622626
]
623627
},
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @ts-check
2+
3+
/** @type {import('eslint').Linter.Config} */
4+
const config = {}
5+
6+
module.exports = config
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
pnpm-lock.yaml
15+
yarn.lock
16+
package-lock.json
17+
18+
# misc
19+
.DS_Store
20+
.env.local
21+
.env.development.local
22+
.env.test.local
23+
.env.production.local
24+
25+
npm-debug.log*
26+
yarn-debug.log*
27+
yarn-error.log*
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Example
2+
3+
To run this example:
4+
5+
- `npm install`
6+
- `npm run dev`
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite + Lit + TS</title>
8+
<script type="module" src="/src/index.ts"></script>
9+
</head>
10+
<body>
11+
<tanstack-form-demo> </tanstack-form-demo>
12+
</body>
13+
</html>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "@tanstack/form-example-lit-standard-schema",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite --port=3001",
7+
"build": "vite build",
8+
"preview": "vite preview",
9+
"test:types": "tsc"
10+
},
11+
"dependencies": {
12+
"@tanstack/lit-form": "^1.15.1",
13+
"arktype": "^2.1.20",
14+
"effect": "^3.16.7",
15+
"lit": "^3.3.0",
16+
"valibot": "^1.1.0",
17+
"zod": "^3.25.64"
18+
},
19+
"devDependencies": {
20+
"vite": "^6.3.5"
21+
},
22+
"browserslist": {
23+
"production": [
24+
">0.2%",
25+
"not dead",
26+
"not op_mini all"
27+
],
28+
"development": [
29+
"last 1 chrome version",
30+
"last 1 firefox version",
31+
"last 1 safari version"
32+
]
33+
}
34+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { LitElement, html, nothing } from 'lit'
2+
import { customElement } from 'lit/decorators.js'
3+
4+
import { TanStackFormController } from '@tanstack/lit-form'
5+
import { repeat } from 'lit/directives/repeat.js'
6+
7+
import { type } from 'arktype'
8+
import * as v from 'valibot'
9+
import { z } from 'zod'
10+
import { Schema as S } from 'effect'
11+
12+
const ZodSchema = z.object({
13+
firstName: z
14+
.string()
15+
.min(3, '[Zod] You must have a length of at least 3')
16+
.startsWith('A', "[Zod] First name must start with 'A'"),
17+
lastName: z.string().min(3, '[Zod] You must have a length of at least 3'),
18+
})
19+
20+
const ValibotSchema = v.object({
21+
firstName: v.pipe(
22+
v.string(),
23+
v.minLength(3, '[Valibot] You must have a length of at least 3'),
24+
v.startsWith('A', "[Valibot] First name must start with 'A'"),
25+
),
26+
lastName: v.pipe(
27+
v.string(),
28+
v.minLength(3, '[Valibot] You must have a length of at least 3'),
29+
),
30+
})
31+
32+
const ArkTypeSchema = type({
33+
firstName: 'string >= 3',
34+
lastName: 'string >= 3',
35+
})
36+
37+
const EffectSchema = S.standardSchemaV1(
38+
S.Struct({
39+
firstName: S.String.pipe(
40+
S.minLength(3),
41+
S.annotations({
42+
message: () => '[Effect/Schema] You must have a length of at least 3',
43+
}),
44+
),
45+
lastName: S.String.pipe(
46+
S.minLength(3),
47+
S.annotations({
48+
message: () => '[Effect/Schema] You must have a length of at least 3',
49+
}),
50+
),
51+
}),
52+
)
53+
54+
@customElement('tanstack-form-demo')
55+
export class TanStackFormDemo extends LitElement {
56+
#form = new TanStackFormController(this, {
57+
defaultValues: {
58+
firstName: '',
59+
lastName: '',
60+
},
61+
validators: {
62+
// DEMO: You can switch between schemas seamlessly
63+
onChange: ZodSchema,
64+
// onChange: ValibotSchema,
65+
// onChange: ArkTypeSchema,
66+
// onChange: EffectSchema,
67+
},
68+
onSubmit({ value }) {
69+
// Do something with form data
70+
console.log(value)
71+
},
72+
})
73+
74+
render() {
75+
return html`
76+
<form
77+
@submit=${(e: Event) => {
78+
e.preventDefault()
79+
e.stopPropagation()
80+
this.#form.api.handleSubmit()
81+
}}
82+
>
83+
${this.#form.field(
84+
{
85+
name: `firstName`,
86+
},
87+
(field) => {
88+
return html` <div>
89+
<label for="${field.name}">First Name:</label>
90+
<input
91+
id="${field.name}"
92+
name="${field.name}"
93+
.value="${field.state.value}"
94+
@blur="${() => field.handleBlur()}"
95+
@input="${(e: Event) => {
96+
const target = e.target as HTMLInputElement
97+
field.handleChange(target.value)
98+
}}"
99+
/>
100+
${field.state.meta.isTouched && !field.state.meta.isValid
101+
? html`${repeat(
102+
field.state.meta.errors,
103+
(__, idx) => idx,
104+
(error) => {
105+
return html`<div style="color: red;">
106+
${error?.message}
107+
</div>`
108+
},
109+
)}`
110+
: nothing}
111+
${field.state.meta.isValidating
112+
? html`<p>Validating...</p>`
113+
: nothing}
114+
</div>`
115+
},
116+
)}
117+
</div>
118+
<div>
119+
${this.#form.field(
120+
{
121+
name: `lastName`,
122+
},
123+
(field) => {
124+
return html` <div>
125+
<label for="${field.name}">Last Name:</label>
126+
<input
127+
id="${field.name}"
128+
name="${field.name}"
129+
.value="${field.state.value}"
130+
@blur="${() => field.handleBlur()}"
131+
@input="${(e: Event) => {
132+
const target = e.target as HTMLInputElement
133+
field.handleChange(target.value)
134+
}}"
135+
/>
136+
${field.state.meta.isTouched && !field.state.meta.isValid
137+
? html`${repeat(
138+
field.state.meta.errors,
139+
(__, idx) => idx,
140+
(error) => {
141+
return html`<div style="color: red;">
142+
${error?.message}
143+
</div>`
144+
},
145+
)}`
146+
: nothing}
147+
${field.state.meta.isValidating
148+
? html`<p>Validating...</p>`
149+
: nothing}
150+
</div>`
151+
},
152+
)}
153+
</div>
154+
155+
<button type="submit" ?disabled=${this.#form.api.state.isSubmitting}>
156+
${this.#form.api.state.isSubmitting ? '...' : 'Submit'}
157+
</button>
158+
<button
159+
type="button"
160+
@click=${() => {
161+
this.#form.api.reset()
162+
}}
163+
>
164+
Reset
165+
</button>
166+
</form>
167+
`
168+
}
169+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"experimentalDecorators": true,
5+
"useDefineForClassFields": false,
6+
"module": "ESNext",
7+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
8+
"skipLibCheck": true,
9+
10+
/* Bundler mode */
11+
"moduleResolution": "Bundler",
12+
"allowImportingTsExtensions": true,
13+
"resolveJsonModule": true,
14+
"isolatedModules": true,
15+
"noEmit": true,
16+
17+
/* Linting */
18+
"strict": true,
19+
"noUnusedLocals": false,
20+
"noUnusedParameters": true,
21+
"noFallthroughCasesInSwitch": true
22+
},
23+
"include": ["src"]
24+
}

pnpm-lock.yaml

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)