@ereo/forms 0.2.7 → 0.2.8
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/README.md +233 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# @ereo/forms
|
|
2
|
+
|
|
3
|
+
Type-safe, signal-powered form management for React. Built on `@ereo/state` with per-field reactivity, schema validation, wizards, and accessibility out of the box.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @ereo/forms @ereo/state
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { useForm, useField } from '@ereo/forms';
|
|
15
|
+
|
|
16
|
+
interface LoginForm {
|
|
17
|
+
email: string;
|
|
18
|
+
password: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function LoginPage() {
|
|
22
|
+
const form = useForm<LoginForm>({
|
|
23
|
+
initialValues: { email: '', password: '' },
|
|
24
|
+
onSubmit: async (values) => {
|
|
25
|
+
await login(values);
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const email = useField(form, 'email', { validate: required('Email is required') });
|
|
30
|
+
const password = useField(form, 'password', { validate: required('Password is required') });
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<form onSubmit={form.handleSubmit}>
|
|
34
|
+
<input {...email.inputProps} type="email" />
|
|
35
|
+
{email.error && <span>{email.error}</span>}
|
|
36
|
+
|
|
37
|
+
<input {...password.inputProps} type="password" />
|
|
38
|
+
{password.error && <span>{password.error}</span>}
|
|
39
|
+
|
|
40
|
+
<button type="submit" disabled={form.isSubmitting}>Log In</button>
|
|
41
|
+
</form>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Key Features
|
|
47
|
+
|
|
48
|
+
- **Per-Field Signals** - Each field gets its own reactive signal for fine-grained updates
|
|
49
|
+
- **Type-Safe Paths** - `FormPath<T>` constrains all field names at compile time
|
|
50
|
+
- **Proxy Access** - Read/write values via `form.values.user.email`
|
|
51
|
+
- **Validation Engine** - Sync, async, schema, and cross-field validation with smart triggers
|
|
52
|
+
- **Schema Support** - Zod, Valibot, Standard Schema V1, and built-in `ereoSchema`
|
|
53
|
+
- **Components** - Pre-built `Field`, `TextareaField`, `SelectField`, and `FieldArray`
|
|
54
|
+
- **Wizard** - Multi-step forms with `createWizard`, step validation, and progress tracking
|
|
55
|
+
- **Server Actions** - `createFormAction` + `ActionForm` for server-side form handling
|
|
56
|
+
- **Accessibility** - ARIA attributes, focus management, live region announcements
|
|
57
|
+
|
|
58
|
+
## Validation
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { required, email, minLength, compose, matches, async as asyncValidator } from '@ereo/forms';
|
|
62
|
+
|
|
63
|
+
const form = useForm<SignUpForm>({
|
|
64
|
+
initialValues: { email: '', password: '', confirmPassword: '' },
|
|
65
|
+
onSubmit: handleSignUp,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Compose multiple validators
|
|
69
|
+
const emailField = useField(form, 'email', {
|
|
70
|
+
validate: compose(
|
|
71
|
+
required('Email is required'),
|
|
72
|
+
email('Invalid email address'),
|
|
73
|
+
asyncValidator(checkEmailAvailable, 'Email already taken'),
|
|
74
|
+
),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Cross-field validation with matches()
|
|
78
|
+
const confirmField = useField(form, 'confirmPassword', {
|
|
79
|
+
validate: matches('password', 'Passwords must match'),
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Schema Validation
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { useForm } from '@ereo/forms';
|
|
87
|
+
import { zodAdapter } from '@ereo/forms';
|
|
88
|
+
import { z } from 'zod';
|
|
89
|
+
|
|
90
|
+
const schema = z.object({
|
|
91
|
+
name: z.string().min(1),
|
|
92
|
+
age: z.number().min(18),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const form = useForm({
|
|
96
|
+
initialValues: { name: '', age: 0 },
|
|
97
|
+
schema: zodAdapter(schema),
|
|
98
|
+
onSubmit: handleSubmit,
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Field Arrays
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { useForm, useFieldArray } from '@ereo/forms';
|
|
106
|
+
|
|
107
|
+
interface TodoForm {
|
|
108
|
+
todos: { text: string; done: boolean }[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function TodoList() {
|
|
112
|
+
const form = useForm<TodoForm>({
|
|
113
|
+
initialValues: { todos: [{ text: '', done: false }] },
|
|
114
|
+
onSubmit: handleSubmit,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const { items, append, remove, swap, move } = useFieldArray(form, 'todos');
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div>
|
|
121
|
+
{items.map((item, i) => (
|
|
122
|
+
<div key={item.id}>
|
|
123
|
+
<input {...useField(form, `todos.${i}.text`).inputProps} />
|
|
124
|
+
<button onClick={() => remove(i)}>Remove</button>
|
|
125
|
+
</div>
|
|
126
|
+
))}
|
|
127
|
+
<button onClick={() => append({ text: '', done: false })}>Add</button>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Wizard (Multi-Step Forms)
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { createWizard, useWizard, WizardProvider, WizardStep, WizardNavigation, WizardProgress } from '@ereo/forms';
|
|
137
|
+
|
|
138
|
+
const wizard = createWizard({
|
|
139
|
+
steps: [
|
|
140
|
+
{ id: 'info', label: 'Info' },
|
|
141
|
+
{ id: 'address', label: 'Address' },
|
|
142
|
+
{ id: 'confirm', label: 'Confirm' },
|
|
143
|
+
],
|
|
144
|
+
onComplete: async (data) => {
|
|
145
|
+
await submitRegistration(data);
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
function RegistrationWizard() {
|
|
150
|
+
return (
|
|
151
|
+
<WizardProvider wizard={wizard}>
|
|
152
|
+
<WizardProgress />
|
|
153
|
+
<WizardStep step="info"><InfoFields /></WizardStep>
|
|
154
|
+
<WizardStep step="address"><AddressFields /></WizardStep>
|
|
155
|
+
<WizardStep step="confirm"><ConfirmFields /></WizardStep>
|
|
156
|
+
<WizardNavigation />
|
|
157
|
+
</WizardProvider>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Built-in Validators
|
|
163
|
+
|
|
164
|
+
| Validator | Description |
|
|
165
|
+
|-----------|-------------|
|
|
166
|
+
| `required(msg)` | Field must have a value |
|
|
167
|
+
| `email(msg)` | Valid email format |
|
|
168
|
+
| `url(msg)` | Valid URL format |
|
|
169
|
+
| `minLength(n, msg)` | Minimum string length |
|
|
170
|
+
| `maxLength(n, msg)` | Maximum string length |
|
|
171
|
+
| `min(n, msg)` | Minimum numeric value |
|
|
172
|
+
| `max(n, msg)` | Maximum numeric value |
|
|
173
|
+
| `pattern(regex, msg)` | Matches a regex pattern |
|
|
174
|
+
| `number(msg)` | Must be a valid number |
|
|
175
|
+
| `integer(msg)` | Must be an integer |
|
|
176
|
+
| `positive(msg)` | Must be positive |
|
|
177
|
+
| `matches(field, msg)` | Must match another field's value |
|
|
178
|
+
| `oneOf(values, msg)` | Must be one of the listed values |
|
|
179
|
+
| `fileSize(max, msg)` | File size limit |
|
|
180
|
+
| `fileType(types, msg)` | Allowed file MIME types |
|
|
181
|
+
| `compose(...fns)` | Compose multiple validators |
|
|
182
|
+
| `when(cond, validator)` | Conditional validation |
|
|
183
|
+
| `async(fn, msg)` | Async validation with debounce |
|
|
184
|
+
| `custom(fn)` | Custom validation function |
|
|
185
|
+
|
|
186
|
+
## API Reference
|
|
187
|
+
|
|
188
|
+
### Hooks
|
|
189
|
+
- `useForm(config)` - Create a form store (React hook)
|
|
190
|
+
- `useField(form, name, opts?)` - Register and subscribe to a field
|
|
191
|
+
- `useFieldArray(form, name)` - Manage dynamic array fields
|
|
192
|
+
- `useWatch(form, name)` - Observe a field value without registering
|
|
193
|
+
- `useFormStatus(form)` - Get form-level status (dirty, valid, submitting)
|
|
194
|
+
|
|
195
|
+
### Core
|
|
196
|
+
- `createFormStore(config)` - Create a form store (non-hook)
|
|
197
|
+
- `FormStore` - The form store class
|
|
198
|
+
- `createValuesProxy(form)` - Create a proxy for `form.values.x.y` access
|
|
199
|
+
- `getPath(obj, path)` / `setPath(obj, path, val)` - Deep path get/set
|
|
200
|
+
|
|
201
|
+
### Components
|
|
202
|
+
- `Field` - Renders an input field with label, error, and ARIA props
|
|
203
|
+
- `TextareaField` - Textarea variant
|
|
204
|
+
- `SelectField` - Select dropdown variant
|
|
205
|
+
- `FieldArray` - Render prop for array fields
|
|
206
|
+
|
|
207
|
+
### Context
|
|
208
|
+
- `FormProvider` / `useFormContext` - React context for form store
|
|
209
|
+
|
|
210
|
+
### Schema Adapters
|
|
211
|
+
- `zodAdapter(schema)` - Zod schema adapter
|
|
212
|
+
- `valibotAdapter(schema)` - Valibot schema adapter
|
|
213
|
+
- `standardSchemaAdapter(schema)` - Standard Schema V1 adapter
|
|
214
|
+
- `ereoSchema(definition)` - Built-in lightweight schema
|
|
215
|
+
- `createSchemaValidator(schema)` - Generic schema validator factory
|
|
216
|
+
|
|
217
|
+
### Server Actions
|
|
218
|
+
- `createFormAction(config)` - Create a server action handler
|
|
219
|
+
- `ActionForm` - Form component wired to server actions
|
|
220
|
+
- `useFormAction(form, action)` - Hook for action-based submission
|
|
221
|
+
- `parseActionResult(response)` - Parse server action response
|
|
222
|
+
|
|
223
|
+
## Documentation
|
|
224
|
+
|
|
225
|
+
For full documentation, visit [https://ereojs.dev/docs/forms](https://ereojs.dev/docs/forms)
|
|
226
|
+
|
|
227
|
+
## Part of EreoJS
|
|
228
|
+
|
|
229
|
+
This package is part of the [EreoJS](https://github.com/ereoJS/ereoJS) monorepo - a modern full-stack framework built for Bun.
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ereo/forms",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"author": "Ereo Team",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"main": "./dist/index.js",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@ereo/state": "^0.2.
|
|
12
|
+
"@ereo/state": "^0.2.8"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@happy-dom/global-registrator": "20.5.0",
|