fieldwise 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 +21 -0
- package/README.md +474 -0
- package/dist/Form.d.ts +63 -0
- package/dist/Form.d.ts.map +1 -0
- package/dist/Form.js +162 -0
- package/dist/changeHandlers.d.ts +3 -0
- package/dist/changeHandlers.d.ts.map +1 -0
- package/dist/changeHandlers.js +19 -0
- package/dist/errorHandlers.d.ts +3 -0
- package/dist/errorHandlers.d.ts.map +1 -0
- package/dist/errorHandlers.js +5 -0
- package/dist/fieldwise.d.ts +38 -0
- package/dist/fieldwise.d.ts.map +1 -0
- package/dist/fieldwise.js +86 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/logFormEvents.d.ts +3 -0
- package/dist/logFormEvents.d.ts.map +1 -0
- package/dist/logFormEvents.js +22 -0
- package/dist/test/Form.test.d.ts +2 -0
- package/dist/test/Form.test.d.ts.map +1 -0
- package/dist/test/Form.test.js +218 -0
- package/dist/test/fieldwise.test.d.ts +2 -0
- package/dist/test/fieldwise.test.d.ts.map +1 -0
- package/dist/test/fieldwise.test.js +177 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +9 -0
- package/dist/validateZodSchema.d.ts +4 -0
- package/dist/validateZodSchema.d.ts.map +1 -0
- package/dist/validateZodSchema.js +29 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Artem Kuzko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# Fieldwise
|
|
2
|
+
|
|
3
|
+
**Type-safe, reactive form management for React with fine-grained field subscriptions.**
|
|
4
|
+
|
|
5
|
+
Fieldwise is a lightweight, event-driven form library that provides precise control over component re-renders through field-level subscriptions. No more unnecessary re-renders from unrelated field changes.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ✨ **Fine-grained reactivity** - Subscribe to specific fields, not entire form state
|
|
10
|
+
- 🎯 **Type-safe** - Full TypeScript support with type inference
|
|
11
|
+
- 🪶 **Lightweight** - Event-driven architecture with no state in React components
|
|
12
|
+
- 🔌 **Plugin system** - Extensible with custom validation and behavior
|
|
13
|
+
- ⚡ **Performance** - Automatic microtask batching for synchronous updates
|
|
14
|
+
- 🛡️ **Zod validation** - Built-in Zod schema validation
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install fieldwise zod
|
|
20
|
+
# or
|
|
21
|
+
yarn add fieldwise zod
|
|
22
|
+
# or
|
|
23
|
+
pnpm add fieldwise zod
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Peer dependencies:**
|
|
27
|
+
|
|
28
|
+
- React 18+ or React 19+
|
|
29
|
+
- Zod 3.x (optional, only if using validation)
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { fieldwise, validateZodSchema } from 'fieldwise';
|
|
35
|
+
import { z } from 'zod';
|
|
36
|
+
|
|
37
|
+
// Define your schema
|
|
38
|
+
const userSchema = z.object({
|
|
39
|
+
name: z.string().min(1, 'Name is required'),
|
|
40
|
+
email: z.string().email('Invalid email address')
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Infer Form values type
|
|
44
|
+
type UserFormValues = z.infer<typeof schema>;
|
|
45
|
+
|
|
46
|
+
// Define initial values
|
|
47
|
+
const emptyUser: UserFormValues = { name: '', email: '' };
|
|
48
|
+
|
|
49
|
+
// Create form hooks
|
|
50
|
+
const { useForm, useSlice } = fieldwise(emptyUser)
|
|
51
|
+
.use(validateZodSchema(userSchema))
|
|
52
|
+
.hooks();
|
|
53
|
+
|
|
54
|
+
// Export for use in components
|
|
55
|
+
export { useForm as useUserForm, useSlice as useUserSlice };
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// In your component
|
|
60
|
+
import { useUserForm } from './userForm';
|
|
61
|
+
|
|
62
|
+
function UserForm() {
|
|
63
|
+
const { fields, emit, once, i } = useUserForm();
|
|
64
|
+
|
|
65
|
+
const handleSubmit = (e) => {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
|
|
68
|
+
emit.later('validate'); // Defer validation to microtask
|
|
69
|
+
|
|
70
|
+
once('validated', ({ values, errors }) => {
|
|
71
|
+
if (errors) return; // Validation failed
|
|
72
|
+
|
|
73
|
+
// Submit the form
|
|
74
|
+
console.log('Submitting:', values);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<form onSubmit={handleSubmit}>
|
|
80
|
+
<input {...i('name')} placeholder="Name" />
|
|
81
|
+
{fields.name.error && <span>{fields.name.error}</span>}
|
|
82
|
+
|
|
83
|
+
<input {...i('email')} type="email" placeholder="Email" />
|
|
84
|
+
{fields.email.error && <span>{fields.email.error}</span>}
|
|
85
|
+
|
|
86
|
+
<button type="submit">Submit</button>
|
|
87
|
+
</form>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Core Concepts
|
|
93
|
+
|
|
94
|
+
### Fine-Grained Subscriptions
|
|
95
|
+
|
|
96
|
+
Unlike traditional form libraries, Fieldwise allows you to subscribe to specific fields:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Subscribe to ALL fields (re-renders on any change)
|
|
100
|
+
const { fields } = useUserForm();
|
|
101
|
+
|
|
102
|
+
// Subscribe to SPECIFIC fields only (re-renders only when email changes)
|
|
103
|
+
const { fields } = useUserSlice(['email']);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Event-Driven Architecture
|
|
107
|
+
|
|
108
|
+
Fieldwise uses an event system for all state changes:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const { emit, once, fields } = useUserForm();
|
|
112
|
+
|
|
113
|
+
// Update a field
|
|
114
|
+
emit('change', 'name', 'John Doe');
|
|
115
|
+
|
|
116
|
+
// Trigger validation
|
|
117
|
+
emit.later('validate');
|
|
118
|
+
|
|
119
|
+
// Listen for validation results (one-time)
|
|
120
|
+
once('validated', ({ values, errors }) => {
|
|
121
|
+
// Handle result
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Reset form
|
|
125
|
+
emit('reset'); // to initial values
|
|
126
|
+
emit('reset', newValues); // to specific values
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Input Helper
|
|
130
|
+
|
|
131
|
+
The `i()` function generates all necessary props for controlled inputs:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
<input {...i('email')} />
|
|
135
|
+
|
|
136
|
+
// Expands to:
|
|
137
|
+
{
|
|
138
|
+
name: 'email',
|
|
139
|
+
value: fields.email.value,
|
|
140
|
+
onChange: (value) => emit('change', 'email', value),
|
|
141
|
+
error: fields.email.error
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## API Reference
|
|
146
|
+
|
|
147
|
+
### `fieldwise(initialValues)`
|
|
148
|
+
|
|
149
|
+
Creates a form builder with the specified initial values.
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const builder = fieldwise({ name: '', email: '' });
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `.use(plugin)`
|
|
156
|
+
|
|
157
|
+
Applies a plugin to the form. Plugins can add validation, logging, or custom behavior.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
builder.use(validateZodSchema(schema)).use(myEventHandler); // Chain multiple plugins
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### `.hooks()`
|
|
164
|
+
|
|
165
|
+
Generates React hooks for the form.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const { useForm, useSlice } = builder.hooks();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### `useForm()`
|
|
172
|
+
|
|
173
|
+
Hook that subscribes to all form fields.
|
|
174
|
+
|
|
175
|
+
**Returns:**
|
|
176
|
+
|
|
177
|
+
- `fields: FieldSet<T>` - Object containing all fields with `{ value, error, isTouched }`
|
|
178
|
+
- `emit: EmitFn` - Function to trigger events
|
|
179
|
+
- `once: OneTimeFn` - Function to listen to events once
|
|
180
|
+
- `isTouched: boolean` - Whether any field has been modified
|
|
181
|
+
- `i: InputHelper` - Function to generate input props
|
|
182
|
+
|
|
183
|
+
### `useSlice(keys)`
|
|
184
|
+
|
|
185
|
+
Hook that subscribes to specific form fields.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const { fields, emit, i } = useUserSlice(['email', 'name']);
|
|
189
|
+
// Only re-renders when email or name changes
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Events
|
|
193
|
+
|
|
194
|
+
Available events:
|
|
195
|
+
|
|
196
|
+
- `change` - Field value changed: `emit('change', key, value)`
|
|
197
|
+
- `changeSome` - Multiple fields changed: `emit('changeSome', { field1: value1, field2: value2 })`
|
|
198
|
+
- `touch` - Mark field as touched: `emit('touch', key)`
|
|
199
|
+
- `touchSome` - Mark multiple fields as touched: `emit('touchSome', [key1, key2])`
|
|
200
|
+
- `validate` - Validation requested: `emit('validate')`
|
|
201
|
+
- `validated` - Validation completed: `once('validated', ({ values, errors }) => {})`
|
|
202
|
+
- `reset` - Form reset: `emit('reset', snapshot?)`
|
|
203
|
+
|
|
204
|
+
## Validation
|
|
205
|
+
|
|
206
|
+
### Zod Schema Validation
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { validateZodSchema } from 'fieldwise';
|
|
210
|
+
import { z } from 'zod';
|
|
211
|
+
|
|
212
|
+
const schema = z
|
|
213
|
+
.object({
|
|
214
|
+
email: z.email(),
|
|
215
|
+
password: z.string().min(8, 'Must be at least 8 characters'),
|
|
216
|
+
confirmPassword: z.string()
|
|
217
|
+
})
|
|
218
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
219
|
+
message: 'Passwords must match',
|
|
220
|
+
path: ['confirmPassword']
|
|
221
|
+
});
|
|
222
|
+
type UserValues = z.infer<typeof schema>;
|
|
223
|
+
|
|
224
|
+
const emptyUser: UserValues = { email: '', password: '', confirmPassword: '' };
|
|
225
|
+
const { useForm } = fieldwise(emptyUser).use(validateZodSchema(schema)).hooks();
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The validation plugin:
|
|
229
|
+
|
|
230
|
+
- Handles schema refinements with custom paths
|
|
231
|
+
- Returns errors as strings (can be integrated with i18n libraries if needed)
|
|
232
|
+
- Supports `z.coerce` for HTML input type coercion
|
|
233
|
+
- Error format: `{ field: 'error message' }` as `Record<keyof T, string | null>`
|
|
234
|
+
|
|
235
|
+
### Custom Validation Plugin
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const customValidation = (form) => {
|
|
239
|
+
form.on('validate', async () => {
|
|
240
|
+
const values = form.getValues();
|
|
241
|
+
|
|
242
|
+
// Your validation logic
|
|
243
|
+
const errors = await validateAsync(values);
|
|
244
|
+
|
|
245
|
+
form.emit('validated', { values, errors });
|
|
246
|
+
});
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
fieldwise(initialValues).use(customValidation).hooks();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Advanced Usage
|
|
253
|
+
|
|
254
|
+
### Conditional Fields
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
function RegistrationForm() {
|
|
258
|
+
const { fields, emit, i } = useForm();
|
|
259
|
+
|
|
260
|
+
// Show/hide based on field value
|
|
261
|
+
return (
|
|
262
|
+
<form>
|
|
263
|
+
<select {...i('accountType')}>
|
|
264
|
+
<option value="personal">Personal</option>
|
|
265
|
+
<option value="business">Business</option>
|
|
266
|
+
</select>
|
|
267
|
+
|
|
268
|
+
{fields.accountType.value === 'business' && (
|
|
269
|
+
<input {...i('companyName')} placeholder="Company Name" />
|
|
270
|
+
)}
|
|
271
|
+
</form>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Async Validation
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
const asyncValidation = (form) => {
|
|
280
|
+
form.on('validate', async () => {
|
|
281
|
+
const values = form.getValues();
|
|
282
|
+
|
|
283
|
+
// Async check (e.g., username availability)
|
|
284
|
+
const isAvailable = await checkUsernameAvailability(values.username);
|
|
285
|
+
|
|
286
|
+
const errors = isAvailable
|
|
287
|
+
? null
|
|
288
|
+
: { username: 'Username is already taken' };
|
|
289
|
+
|
|
290
|
+
form.emit('validated', { values, errors });
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Debug Mode
|
|
296
|
+
|
|
297
|
+
Enable debug logging by setting `Form.debugMode`:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { Form } from 'fieldwise';
|
|
301
|
+
|
|
302
|
+
// Log all events
|
|
303
|
+
Form.debugMode = true;
|
|
304
|
+
|
|
305
|
+
// Log only specific events
|
|
306
|
+
Form.debugMode = { only: ['reset', 'validate', 'validated'] };
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Debug plugin is attached automatically when debug mode is enabled.
|
|
310
|
+
|
|
311
|
+
### Material-UI Integration
|
|
312
|
+
|
|
313
|
+
**Note**: Material-UI inputs require custom wrappers since their API differs from standard HTML inputs. You'll need to create wrapper components that adapt the `i()` helper props to Material-UI's expected props.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import TextField from '@mui/material/TextField';
|
|
317
|
+
|
|
318
|
+
const TextFieldWrapper = ({ name, value, onChange, error }) => (
|
|
319
|
+
<TextField
|
|
320
|
+
name={name}
|
|
321
|
+
value={value}
|
|
322
|
+
onChange={(e) => onChange(e.target.value)}
|
|
323
|
+
label={name}
|
|
324
|
+
helperText={error}
|
|
325
|
+
error={!!error}
|
|
326
|
+
/>
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
function MyForm() {
|
|
330
|
+
const { i } = useMyForm();
|
|
331
|
+
|
|
332
|
+
return <TextFieldWrapper {...i('email')} />;
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Performance Optimization
|
|
337
|
+
|
|
338
|
+
### Prevent Unnecessary Re-renders
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// ❌ Bad: Re-renders on ANY field change
|
|
342
|
+
const { fields } = useUserForm();
|
|
343
|
+
|
|
344
|
+
// ✅ Good: Only re-renders when email or password changes
|
|
345
|
+
const { fields } = useUserSlice(['email', 'password']);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Microtask Batching
|
|
349
|
+
|
|
350
|
+
Fieldwise automatically batches synchronous updates:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
emit('change', 'name', 'John');
|
|
354
|
+
emit('change', 'email', 'john@example.com');
|
|
355
|
+
// Both updates trigger only ONE re-render
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Validation Deferral
|
|
359
|
+
|
|
360
|
+
Use `emit.later()` to defer validation to the microtask queue:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
const handleSubmit = () => {
|
|
364
|
+
emit.later('validate'); // Defers to microtask
|
|
365
|
+
|
|
366
|
+
once('validated', ({ values, errors }) => {
|
|
367
|
+
// Runs after all synchronous updates complete
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## TypeScript Support
|
|
373
|
+
|
|
374
|
+
Fieldwise is written in TypeScript and provides full type inference:
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
type User = {
|
|
378
|
+
name: string;
|
|
379
|
+
email: string;
|
|
380
|
+
age: number;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const { useForm } = fieldwise<User>({
|
|
384
|
+
name: '',
|
|
385
|
+
email: '',
|
|
386
|
+
age: 0
|
|
387
|
+
}).hooks();
|
|
388
|
+
|
|
389
|
+
const { fields, emit, i } = useForm();
|
|
390
|
+
|
|
391
|
+
// ✅ Type-safe
|
|
392
|
+
emit('change', 'name', 'John');
|
|
393
|
+
fields.name.value; // string
|
|
394
|
+
|
|
395
|
+
// ❌ Type errors
|
|
396
|
+
emit('change', 'invalid', 'value'); // Error: 'invalid' is not a valid key
|
|
397
|
+
emit('change', 'age', 'not a number'); // Error: expected number
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Migration Guide
|
|
401
|
+
|
|
402
|
+
### From Formik
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
// Formik
|
|
406
|
+
const formik = useFormik({
|
|
407
|
+
initialValues: { email: '' },
|
|
408
|
+
validationSchema: schema,
|
|
409
|
+
onSubmit: (values) => { ... }
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Fieldwise
|
|
413
|
+
const { fields, emit, once, i } = useForm();
|
|
414
|
+
const handleSubmit = () => {
|
|
415
|
+
emit.later('validate');
|
|
416
|
+
once('validated', ({ values, errors }) => {
|
|
417
|
+
if (!errors) onSubmit(values);
|
|
418
|
+
});
|
|
419
|
+
};
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### From React Hook Form
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
// React Hook Form
|
|
426
|
+
const {
|
|
427
|
+
register,
|
|
428
|
+
handleSubmit,
|
|
429
|
+
formState: { errors }
|
|
430
|
+
} = useForm();
|
|
431
|
+
|
|
432
|
+
// Fieldwise
|
|
433
|
+
const { i, emit, once, fields } = useForm();
|
|
434
|
+
// Errors available at fields.fieldName.error
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Plugin Development
|
|
438
|
+
|
|
439
|
+
Create custom plugins to extend Fieldwise:
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
const myPlugin = (form) => {
|
|
443
|
+
// Listen to events
|
|
444
|
+
form.on('change', (key, value) => {
|
|
445
|
+
console.log(`${key} changed to ${value}`);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Emit events
|
|
449
|
+
form.on('validate', () => {
|
|
450
|
+
const values = form.getValues();
|
|
451
|
+
// Custom validation logic
|
|
452
|
+
form.emit('validated', { values, errors: null });
|
|
453
|
+
});
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
fieldwise(initialValues).use(myPlugin).hooks();
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## Contributing
|
|
460
|
+
|
|
461
|
+
Contributions are welcome! Please follow these guidelines:
|
|
462
|
+
|
|
463
|
+
- Maintain zero React state in Form class
|
|
464
|
+
- Keep plugins composable and single-responsibility
|
|
465
|
+
- Add tests for new features
|
|
466
|
+
- Document all public API changes
|
|
467
|
+
|
|
468
|
+
## License
|
|
469
|
+
|
|
470
|
+
MIT
|
|
471
|
+
|
|
472
|
+
## Credits
|
|
473
|
+
|
|
474
|
+
Extracted from a production application managing 15+ complex forms with dynamic validation, conditional fields, and multi-step flows.
|
package/dist/Form.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type Field<T> = {
|
|
2
|
+
value: T;
|
|
3
|
+
error: string | null;
|
|
4
|
+
isTouched: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type FieldSet<T extends Values> = {
|
|
7
|
+
[K in keyof T]: Field<T[K]>;
|
|
8
|
+
};
|
|
9
|
+
export type FieldSubscriber<T> = (field: Field<T>) => void;
|
|
10
|
+
export type FieldUnsubscribeFn = () => void;
|
|
11
|
+
export type EventHandler<TArgs extends unknown[] = []> = (...args: TArgs) => void;
|
|
12
|
+
export type EventUnsubscribeFn = () => void;
|
|
13
|
+
export type Values = Record<string, unknown>;
|
|
14
|
+
export type Errors<T extends Values> = Partial<Record<keyof T, string>>;
|
|
15
|
+
export type EventMap<T extends Values> = {
|
|
16
|
+
change: [key: keyof T, value: T[keyof T]];
|
|
17
|
+
changeSome: [payload: Partial<T>];
|
|
18
|
+
touch: [key: keyof T];
|
|
19
|
+
touchSome: [keys: (keyof T)[]];
|
|
20
|
+
reset: [snapshot?: T];
|
|
21
|
+
errors: [errors: Errors<T>];
|
|
22
|
+
validate: [];
|
|
23
|
+
validated: [
|
|
24
|
+
payload: {
|
|
25
|
+
values: T;
|
|
26
|
+
errors: Errors<T> | null;
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
};
|
|
30
|
+
export type EmitFn<T extends Values> = <K extends keyof EventMap<T>>(event: K, ...args: EventMap<T>[K]) => void;
|
|
31
|
+
export type DebugMode = boolean | DebugModeConfig;
|
|
32
|
+
export type DebugModeConfig = {
|
|
33
|
+
only: (keyof EventMap<Values>)[];
|
|
34
|
+
};
|
|
35
|
+
export declare class Form<T extends Values> {
|
|
36
|
+
static debugMode: DebugMode;
|
|
37
|
+
initialValues: T;
|
|
38
|
+
private fields;
|
|
39
|
+
private fieldSubscribers;
|
|
40
|
+
private eventHandlers;
|
|
41
|
+
private eventQueue;
|
|
42
|
+
constructor(initialValues: T);
|
|
43
|
+
getValue<K extends keyof T>(key: K): T[K];
|
|
44
|
+
getValues(): T;
|
|
45
|
+
get<K extends keyof T>(key: K): Field<T[K]>;
|
|
46
|
+
setValue<K extends keyof T>(key: K, value: T[K]): void;
|
|
47
|
+
setValues(newValues: Partial<T>): void;
|
|
48
|
+
touch<K extends keyof T>(key: K): void;
|
|
49
|
+
setError<K extends keyof T>(key: K, error: string | null): void;
|
|
50
|
+
setErrors(newErrors: Partial<Record<keyof T, string>>): void;
|
|
51
|
+
reset(snapshot: T): void;
|
|
52
|
+
getSlice<K extends keyof T>(keys: readonly K[]): Pick<FieldSet<T>, K>;
|
|
53
|
+
subscribeField<K extends keyof T>(key: K, callback: FieldSubscriber<T[K]>): FieldUnsubscribeFn;
|
|
54
|
+
on<E extends keyof EventMap<T>>(event: E, handler: EventHandler<EventMap<T>[E]>): EventUnsubscribeFn;
|
|
55
|
+
once<E extends keyof EventMap<T>>(event: E, handler: EventHandler<EventMap<T>[E]>): void;
|
|
56
|
+
emit: EmitFn<T>;
|
|
57
|
+
emitLater: EmitFn<T>;
|
|
58
|
+
private doEmit;
|
|
59
|
+
private processQueuedEvents;
|
|
60
|
+
private notify;
|
|
61
|
+
private valuesToFields;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=Form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Form.d.ts","sourceRoot":"","sources":["../src/Form.ts"],"names":[],"mappings":"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"}
|