@page-speed/forms 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +469 -0
- package/dist/builder.cjs +4 -0
- package/dist/builder.cjs.map +1 -0
- package/dist/builder.d.cts +2 -0
- package/dist/builder.d.ts +2 -0
- package/dist/builder.js +3 -0
- package/dist/builder.js.map +1 -0
- package/dist/chunk-2FXAQT7S.cjs +236 -0
- package/dist/chunk-2FXAQT7S.cjs.map +1 -0
- package/dist/chunk-A3UV7BIN.js +357 -0
- package/dist/chunk-A3UV7BIN.js.map +1 -0
- package/dist/chunk-P37YLBFA.cjs +138 -0
- package/dist/chunk-P37YLBFA.cjs.map +1 -0
- package/dist/chunk-WHQMBQNI.js +127 -0
- package/dist/chunk-WHQMBQNI.js.map +1 -0
- package/dist/chunk-YTTOWHBZ.js +217 -0
- package/dist/chunk-YTTOWHBZ.js.map +1 -0
- package/dist/chunk-ZQCPEOB6.cjs +382 -0
- package/dist/chunk-ZQCPEOB6.cjs.map +1 -0
- package/dist/core.cjs +28 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +143 -0
- package/dist/core.d.ts +143 -0
- package/dist/core.js +3 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +28 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/inputs.cjs +555 -0
- package/dist/inputs.cjs.map +1 -0
- package/dist/inputs.d.cts +433 -0
- package/dist/inputs.d.ts +433 -0
- package/dist/inputs.js +529 -0
- package/dist/inputs.js.map +1 -0
- package/dist/integration.cjs +4 -0
- package/dist/integration.cjs.map +1 -0
- package/dist/integration.d.cts +2 -0
- package/dist/integration.d.ts +2 -0
- package/dist/integration.js +3 -0
- package/dist/integration.js.map +1 -0
- package/dist/types-Cw5CeZP-.d.cts +387 -0
- package/dist/types-Cw5CeZP-.d.ts +387 -0
- package/dist/upload.cjs +4 -0
- package/dist/upload.cjs.map +1 -0
- package/dist/upload.d.cts +2 -0
- package/dist/upload.d.ts +2 -0
- package/dist/upload.js +3 -0
- package/dist/upload.js.map +1 -0
- package/dist/validation-rules.cjs +80 -0
- package/dist/validation-rules.cjs.map +1 -0
- package/dist/validation-rules.d.cts +123 -0
- package/dist/validation-rules.d.ts +123 -0
- package/dist/validation-rules.js +3 -0
- package/dist/validation-rules.js.map +1 -0
- package/dist/validation-utils.cjs +48 -0
- package/dist/validation-utils.cjs.map +1 -0
- package/dist/validation-utils.d.cts +166 -0
- package/dist/validation-utils.d.ts +166 -0
- package/dist/validation-utils.js +3 -0
- package/dist/validation-utils.js.map +1 -0
- package/dist/validation-valibot.cjs +94 -0
- package/dist/validation-valibot.cjs.map +1 -0
- package/dist/validation-valibot.d.cts +92 -0
- package/dist/validation-valibot.d.ts +92 -0
- package/dist/validation-valibot.js +91 -0
- package/dist/validation-valibot.js.map +1 -0
- package/dist/validation.cjs +121 -0
- package/dist/validation.cjs.map +1 -0
- package/dist/validation.d.cts +4 -0
- package/dist/validation.d.ts +4 -0
- package/dist/validation.js +4 -0
- package/dist/validation.js.map +1 -0
- package/package.json +133 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, OpenSite AI. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
# @page-speed/forms
|
|
2
|
+
|
|
3
|
+
Ultra-high-performance React form library with field-level reactivity and tree-shakable architecture.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- =� **Field-Level Reactivity**: Only re-render the specific field that changed (~1 re-render per change vs ~10 for traditional hooks)
|
|
8
|
+
- =� **Tree-Shakable**: Import only what you need - Core module starts at 13 KB gzipped
|
|
9
|
+
- � **Built on @legendapp/state**: Observable-based state management for optimal performance
|
|
10
|
+
- **Valibot Integration**: Lightweight validation (95% smaller than Zod)
|
|
11
|
+
- <� **TypeScript-First**: Full type safety with comprehensive type definitions
|
|
12
|
+
- **Accessible**: ARIA attributes and semantic HTML out of the box
|
|
13
|
+
- = **Progressive Enhancement**: Forms work without JavaScript
|
|
14
|
+
- <� **Unstyled**: Bring your own styles - no CSS to override
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Using pnpm (recommended)
|
|
20
|
+
pnpm add @page-speed/forms
|
|
21
|
+
|
|
22
|
+
# Optional: Add validation library
|
|
23
|
+
pnpm add valibot
|
|
24
|
+
|
|
25
|
+
# Optional: Add state management (peer dependency)
|
|
26
|
+
pnpm add @legendapp/state
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Bundle Sizes
|
|
30
|
+
|
|
31
|
+
All sizes shown are **minified + gzipped** with dependencies:
|
|
32
|
+
|
|
33
|
+
- **Core** (useForm, Form, Field, useField): 13.11 KB
|
|
34
|
+
- **TextInput**: 502 B
|
|
35
|
+
- **Valibot Adapter**: 392 B
|
|
36
|
+
- **Full Bundle**: 13.25 KB
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### Basic Form
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { useForm, Form, Field } from '@page-speed/forms';
|
|
44
|
+
import { TextInput } from '@page-speed/forms/inputs';
|
|
45
|
+
|
|
46
|
+
function LoginForm() {
|
|
47
|
+
const form = useForm({
|
|
48
|
+
initialValues: {
|
|
49
|
+
email: '',
|
|
50
|
+
password: '',
|
|
51
|
+
},
|
|
52
|
+
validationSchema: {
|
|
53
|
+
email: (value) => !value ? 'Required' : undefined,
|
|
54
|
+
password: (value) => value.length < 8 ? 'Too short' : undefined,
|
|
55
|
+
},
|
|
56
|
+
onSubmit: async (values) => {
|
|
57
|
+
await login(values);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Form form={form}>
|
|
63
|
+
<Field name="email" label="Email">
|
|
64
|
+
{({ field, meta }) => (
|
|
65
|
+
<>
|
|
66
|
+
<TextInput {...field} type="email" error={!!meta.error} />
|
|
67
|
+
{meta.error && <span>{meta.error}</span>}
|
|
68
|
+
</>
|
|
69
|
+
)}
|
|
70
|
+
</Field>
|
|
71
|
+
|
|
72
|
+
<Field name="password" label="Password">
|
|
73
|
+
{({ field, meta }) => (
|
|
74
|
+
<>
|
|
75
|
+
<TextInput {...field} type="password" error={!!meta.error} />
|
|
76
|
+
{meta.error && <span>{meta.error}</span>}
|
|
77
|
+
</>
|
|
78
|
+
)}
|
|
79
|
+
</Field>
|
|
80
|
+
|
|
81
|
+
<button type="submit" disabled={form.isSubmitting}>
|
|
82
|
+
Submit
|
|
83
|
+
</button>
|
|
84
|
+
</Form>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### With Valibot Validation
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { useForm } from '@page-speed/forms';
|
|
93
|
+
import { createValibotSchema } from '@page-speed/forms/validation/valibot';
|
|
94
|
+
import * as v from 'valibot';
|
|
95
|
+
|
|
96
|
+
const LoginSchema = v.object({
|
|
97
|
+
email: v.pipe(v.string(), v.email('Invalid email')),
|
|
98
|
+
password: v.pipe(v.string(), v.minLength(8, 'Too short')),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
function LoginForm() {
|
|
102
|
+
const form = useForm({
|
|
103
|
+
initialValues: { email: '', password: '' },
|
|
104
|
+
validationSchema: createValibotSchema(LoginSchema),
|
|
105
|
+
onSubmit: async (values) => {
|
|
106
|
+
await login(values);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ... rest of form
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## API Reference
|
|
115
|
+
|
|
116
|
+
### `useForm(options)`
|
|
117
|
+
|
|
118
|
+
Main hook for creating form state.
|
|
119
|
+
|
|
120
|
+
**Options:**
|
|
121
|
+
|
|
122
|
+
- `initialValues` (required): Initial form values
|
|
123
|
+
- `validationSchema`: Schema mapping field names to validators
|
|
124
|
+
- `validateOn`: When to validate - `"onBlur"` (default) | `"onChange"` | `"onSubmit"`
|
|
125
|
+
- `revalidateOn`: When to revalidate after first validation - `"onChange"` (default) | `"onBlur"`
|
|
126
|
+
- `onSubmit` (required): Submit handler function
|
|
127
|
+
- `onError`: Error handler for validation failures
|
|
128
|
+
- `debug`: Enable debug logging
|
|
129
|
+
|
|
130
|
+
**Returns:**
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
{
|
|
134
|
+
// State
|
|
135
|
+
values: T;
|
|
136
|
+
errors: FormErrors<T>;
|
|
137
|
+
touched: TouchedFields<T>;
|
|
138
|
+
isSubmitting: boolean;
|
|
139
|
+
isValid: boolean;
|
|
140
|
+
isDirty: boolean;
|
|
141
|
+
status: 'idle' | 'submitting' | 'success' | 'error';
|
|
142
|
+
|
|
143
|
+
// Actions
|
|
144
|
+
handleSubmit: (e?: React.FormEvent) => Promise<void>;
|
|
145
|
+
setFieldValue: (field: keyof T, value: T[field]) => void;
|
|
146
|
+
setFieldError: (field: keyof T, error: string) => void;
|
|
147
|
+
setFieldTouched: (field: keyof T, touched: boolean) => void;
|
|
148
|
+
validateForm: () => Promise<FormErrors<T>>;
|
|
149
|
+
validateField: (field: keyof T) => Promise<string | undefined>;
|
|
150
|
+
resetForm: () => void;
|
|
151
|
+
getFieldProps: (field: keyof T) => FieldInputProps;
|
|
152
|
+
getFieldMeta: (field: keyof T) => FieldMeta;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### `<Form>`
|
|
157
|
+
|
|
158
|
+
Progressive enhancement wrapper component.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<Form
|
|
162
|
+
form={form}
|
|
163
|
+
action="/api/endpoint" // Fallback for no-JS
|
|
164
|
+
method="post" // Fallback for no-JS
|
|
165
|
+
className="my-form"
|
|
166
|
+
>
|
|
167
|
+
{/* fields */}
|
|
168
|
+
</Form>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### `<Field>`
|
|
172
|
+
|
|
173
|
+
Field wrapper with label, description, and error display.
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<Field
|
|
177
|
+
name="email"
|
|
178
|
+
label="Email Address"
|
|
179
|
+
description="We'll never share your email"
|
|
180
|
+
validate={(value) => !value ? 'Required' : undefined}
|
|
181
|
+
>
|
|
182
|
+
{({ field, meta, helpers }) => (
|
|
183
|
+
<TextInput {...field} error={!!meta.error} />
|
|
184
|
+
)}
|
|
185
|
+
</Field>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### `useField(options)`
|
|
189
|
+
|
|
190
|
+
Field-level hook for accessing field state.
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
const { field, meta, helpers } = useField({
|
|
194
|
+
name: 'email',
|
|
195
|
+
validate: (value) => !value ? 'Required' : undefined,
|
|
196
|
+
transform: (value) => value.toLowerCase(),
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### `<TextInput>`
|
|
201
|
+
|
|
202
|
+
Lightweight, accessible text input component.
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<TextInput
|
|
206
|
+
name="email"
|
|
207
|
+
value={value}
|
|
208
|
+
onChange={onChange}
|
|
209
|
+
onBlur={onBlur}
|
|
210
|
+
type="email"
|
|
211
|
+
placeholder="you@example.com"
|
|
212
|
+
error={hasError}
|
|
213
|
+
disabled={false}
|
|
214
|
+
required={true}
|
|
215
|
+
/>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Validation
|
|
219
|
+
|
|
220
|
+
### Inline Validators
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
const form = useForm({
|
|
224
|
+
initialValues: { email: '' },
|
|
225
|
+
validationSchema: {
|
|
226
|
+
email: (value) => {
|
|
227
|
+
if (!value) return 'Required';
|
|
228
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
229
|
+
return 'Invalid email';
|
|
230
|
+
}
|
|
231
|
+
return undefined;
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
onSubmit: async (values) => { /* ... */ },
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Async Validation
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
const form = useForm({
|
|
242
|
+
initialValues: { username: '' },
|
|
243
|
+
validationSchema: {
|
|
244
|
+
username: async (value) => {
|
|
245
|
+
const exists = await checkUsernameExists(value);
|
|
246
|
+
return exists ? 'Username taken' : undefined;
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
onSubmit: async (values) => { /* ... */ },
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Multiple Validators per Field
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
const form = useForm({
|
|
257
|
+
initialValues: { password: '' },
|
|
258
|
+
validationSchema: {
|
|
259
|
+
password: [
|
|
260
|
+
(value) => !value ? 'Required' : undefined,
|
|
261
|
+
(value) => value.length < 8 ? 'Too short' : undefined,
|
|
262
|
+
(value) => !/[A-Z]/.test(value) ? 'Needs uppercase' : undefined,
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
onSubmit: async (values) => { /* ... */ },
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Valibot Integration
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
import { createValibotSchema } from '@page-speed/forms/validation/valibot';
|
|
273
|
+
import * as v from 'valibot';
|
|
274
|
+
|
|
275
|
+
const schema = v.object({
|
|
276
|
+
email: v.pipe(
|
|
277
|
+
v.string(),
|
|
278
|
+
v.email('Invalid email'),
|
|
279
|
+
v.endsWith('@company.com', 'Must be company email')
|
|
280
|
+
),
|
|
281
|
+
age: v.pipe(
|
|
282
|
+
v.number(),
|
|
283
|
+
v.minValue(18, 'Must be 18+')
|
|
284
|
+
),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const form = useForm({
|
|
288
|
+
initialValues: { email: '', age: 0 },
|
|
289
|
+
validationSchema: createValibotSchema(schema),
|
|
290
|
+
onSubmit: async (values) => { /* ... */ },
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Advanced Usage
|
|
295
|
+
|
|
296
|
+
### Conditional Validation
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
const form = useForm({
|
|
300
|
+
initialValues: {
|
|
301
|
+
contactMethod: 'email',
|
|
302
|
+
email: '',
|
|
303
|
+
phone: '',
|
|
304
|
+
},
|
|
305
|
+
validationSchema: {
|
|
306
|
+
email: (value, allValues) => {
|
|
307
|
+
if (allValues.contactMethod === 'email' && !value) {
|
|
308
|
+
return 'Email required when email is selected';
|
|
309
|
+
}
|
|
310
|
+
return undefined;
|
|
311
|
+
},
|
|
312
|
+
phone: (value, allValues) => {
|
|
313
|
+
if (allValues.contactMethod === 'phone' && !value) {
|
|
314
|
+
return 'Phone required when phone is selected';
|
|
315
|
+
}
|
|
316
|
+
return undefined;
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
onSubmit: async (values) => { /* ... */ },
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Dynamic Forms
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
function DynamicForm() {
|
|
327
|
+
const form = useForm({
|
|
328
|
+
initialValues: {
|
|
329
|
+
contacts: [{ name: '', email: '' }],
|
|
330
|
+
},
|
|
331
|
+
onSubmit: async (values) => { /* ... */ },
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
return (
|
|
335
|
+
<Form form={form}>
|
|
336
|
+
{form.values.contacts.map((contact, index) => (
|
|
337
|
+
<div key={index}>
|
|
338
|
+
<Field name={`contacts.${index}.name`}>
|
|
339
|
+
{({ field }) => <TextInput {...field} />}
|
|
340
|
+
</Field>
|
|
341
|
+
<Field name={`contacts.${index}.email`}>
|
|
342
|
+
{({ field }) => <TextInput {...field} type="email" />}
|
|
343
|
+
</Field>
|
|
344
|
+
</div>
|
|
345
|
+
))}
|
|
346
|
+
<button
|
|
347
|
+
type="button"
|
|
348
|
+
onClick={() => {
|
|
349
|
+
form.setFieldValue('contacts', [
|
|
350
|
+
...form.values.contacts,
|
|
351
|
+
{ name: '', email: '' },
|
|
352
|
+
]);
|
|
353
|
+
}}
|
|
354
|
+
>
|
|
355
|
+
Add Contact
|
|
356
|
+
</button>
|
|
357
|
+
</Form>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Form Helpers in onSubmit
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
const form = useForm({
|
|
366
|
+
initialValues: { email: '' },
|
|
367
|
+
onSubmit: async (values, helpers) => {
|
|
368
|
+
try {
|
|
369
|
+
await api.submit(values);
|
|
370
|
+
helpers.resetForm();
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (error.code === 'EMAIL_EXISTS') {
|
|
373
|
+
helpers.setFieldError('email', 'Email already exists');
|
|
374
|
+
} else {
|
|
375
|
+
helpers.setErrors({ email: 'Unexpected error' });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## Tree-Shaking
|
|
383
|
+
|
|
384
|
+
The library is designed for optimal tree-shaking. Import only what you need:
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
// Import core functionality (13.11 KB)
|
|
388
|
+
import { useForm, Form, Field } from '@page-speed/forms/core';
|
|
389
|
+
|
|
390
|
+
// Import input components separately (502 B)
|
|
391
|
+
import { TextInput } from '@page-speed/forms/inputs';
|
|
392
|
+
|
|
393
|
+
// Import validation adapters separately (392 B)
|
|
394
|
+
import { createValibotSchema } from '@page-speed/forms/validation/valibot';
|
|
395
|
+
|
|
396
|
+
// Or import from main entry point
|
|
397
|
+
import { useForm } from '@page-speed/forms';
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Performance
|
|
401
|
+
|
|
402
|
+
The library uses @legendapp/state for optimal performance:
|
|
403
|
+
|
|
404
|
+
- **~1 re-render per change** vs ~10 for traditional hooks
|
|
405
|
+
- **Observable-based state**: Fine-grained reactivity at the field level
|
|
406
|
+
- **No unnecessary re-renders**: Parent form doesn't re-render when child field changes
|
|
407
|
+
- **Efficient validation**: Debounced and memoized by default
|
|
408
|
+
|
|
409
|
+
## Progressive Enhancement
|
|
410
|
+
|
|
411
|
+
Forms work without JavaScript by using native HTML form submission:
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
<Form
|
|
415
|
+
form={form}
|
|
416
|
+
action="/api/endpoint" // Used when JS disabled
|
|
417
|
+
method="post"
|
|
418
|
+
>
|
|
419
|
+
{/* fields */}
|
|
420
|
+
</Form>
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
When JavaScript is available, `onSubmit` handles the submission. When JavaScript is disabled, the native HTML form submission takes over.
|
|
424
|
+
|
|
425
|
+
## Browser Support
|
|
426
|
+
|
|
427
|
+
- Modern browsers (Chrome, Firefox, Safari, Edge)
|
|
428
|
+
- Requires ES2020 support
|
|
429
|
+
- No IE11 support
|
|
430
|
+
|
|
431
|
+
## TypeScript
|
|
432
|
+
|
|
433
|
+
Fully typed with comprehensive type definitions:
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
import type {
|
|
437
|
+
FormValues,
|
|
438
|
+
FormErrors,
|
|
439
|
+
TouchedFields,
|
|
440
|
+
ValidationSchema,
|
|
441
|
+
FieldValidator,
|
|
442
|
+
UseFormOptions,
|
|
443
|
+
UseFormReturn,
|
|
444
|
+
} from '@page-speed/forms/core';
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Examples
|
|
448
|
+
|
|
449
|
+
See the [`examples/`](./examples) directory for more complete examples including:
|
|
450
|
+
- Basic forms with inline validation
|
|
451
|
+
- Valibot schema integration
|
|
452
|
+
- Dynamic forms with conditional fields
|
|
453
|
+
- Progressive enhancement
|
|
454
|
+
- Async validation
|
|
455
|
+
|
|
456
|
+
## License
|
|
457
|
+
|
|
458
|
+
MIT
|
|
459
|
+
|
|
460
|
+
## Contributing
|
|
461
|
+
|
|
462
|
+
Contributions welcome! Please read our contributing guidelines first.
|
|
463
|
+
|
|
464
|
+
## Credits
|
|
465
|
+
|
|
466
|
+
Built with:
|
|
467
|
+
- [@legendapp/state](https://legendapp.com/open-source/state/) - Observable state management
|
|
468
|
+
- [Valibot](https://valibot.dev/) - Lightweight validation library
|
|
469
|
+
- [tsup](https://tsup.egoist.dev/) - TypeScript bundler
|
package/dist/builder.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"builder.cjs"}
|
package/dist/builder.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"builder.js"}
|