@reformer/core 1.1.0-beta.1 → 1.1.0-beta.3
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/llms.txt +250 -29
- package/package.json +1 -1
package/llms.txt
CHANGED
|
@@ -6,9 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
| What | Where |
|
|
8
8
|
| ------------------------------------------------------------------------------------------- | --------------------------- |
|
|
9
|
+
| `createForm`, `useFormControl`, `useFormControlValue` | `@reformer/core` |
|
|
9
10
|
| `ValidationSchemaFn`, `BehaviorSchemaFn`, `FieldPath`, `GroupNodeWithControls`, `FieldNode` | `@reformer/core` |
|
|
10
|
-
| `
|
|
11
|
-
| `
|
|
11
|
+
| `FormSchema`, `FieldConfig`, `ArrayNode` | `@reformer/core` |
|
|
12
|
+
| `required`, `min`, `max`, `minLength`, `maxLength`, `email` | `@reformer/core/validators` |
|
|
13
|
+
| `pattern`, `url`, `phone`, `number`, `date` | `@reformer/core/validators` |
|
|
14
|
+
| `validate`, `validateAsync`, `validateTree`, `applyWhen` | `@reformer/core/validators` |
|
|
15
|
+
| `notEmpty`, `validateItems` | `@reformer/core/validators` |
|
|
16
|
+
| `computeFrom`, `enableWhen`, `disableWhen`, `watchField`, `copyFrom` | `@reformer/core/behaviors` |
|
|
17
|
+
| `resetWhen`, `revalidateWhen`, `syncFields` | `@reformer/core/behaviors` |
|
|
18
|
+
| `transformValue`, `transformers` | `@reformer/core/behaviors` |
|
|
12
19
|
|
|
13
20
|
### Type Values
|
|
14
21
|
|
|
@@ -21,31 +28,68 @@
|
|
|
21
28
|
### Validators
|
|
22
29
|
|
|
23
30
|
```typescript
|
|
31
|
+
// Basic validators
|
|
24
32
|
required(path, options?: { message?: string })
|
|
25
33
|
min(path, value: number, options?: { message?: string })
|
|
26
34
|
max(path, value: number, options?: { message?: string })
|
|
27
35
|
minLength(path, length: number, options?: { message?: string })
|
|
28
36
|
maxLength(path, length: number, options?: { message?: string })
|
|
29
37
|
email(path, options?: { message?: string })
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
|
|
39
|
+
// Additional validators
|
|
40
|
+
pattern(path, regex: RegExp, options?: { message?: string })
|
|
41
|
+
url(path, options?: { message?: string })
|
|
42
|
+
phone(path, options?: { message?: string; format?: PhoneFormat })
|
|
43
|
+
number(path, options?: { message?: string })
|
|
44
|
+
date(path, options?: { message?: string; minAge?: number; maxAge?: number; noFuture?: boolean; noPast?: boolean })
|
|
45
|
+
|
|
46
|
+
// Custom validators
|
|
47
|
+
validate(path, validator: (value, ctx) => ValidationError | null)
|
|
48
|
+
validateAsync(path, validator: async (value, ctx) => ValidationError | null)
|
|
49
|
+
validateTree(validator: (ctx) => ValidationError | null, options?: { targetField?: string })
|
|
50
|
+
|
|
51
|
+
// Conditional validation
|
|
32
52
|
applyWhen(fieldPath, condition: (fieldValue) => boolean, validatorsFn: (path) => void)
|
|
53
|
+
|
|
54
|
+
// Array validators
|
|
55
|
+
notEmpty(path, options?: { message?: string })
|
|
56
|
+
validateItems(arrayPath, itemValidatorsFn: (itemPath) => void)
|
|
33
57
|
```
|
|
34
58
|
|
|
35
59
|
### Behaviors
|
|
36
60
|
|
|
37
61
|
```typescript
|
|
62
|
+
// Enable/disable fields conditionally
|
|
38
63
|
enableWhen(path, condition: (form) => boolean, options?: { resetOnDisable?: boolean })
|
|
39
64
|
disableWhen(path, condition: (form) => boolean)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
65
|
+
|
|
66
|
+
// Computed fields (same nesting level)
|
|
67
|
+
computeFrom(sourcePaths[], targetPath, compute: (values) => result, options?: { debounce?: number; condition?: (form) => boolean })
|
|
68
|
+
|
|
69
|
+
// Watch field changes
|
|
70
|
+
watchField(path, callback: (value, ctx: BehaviorContext) => void, options?: { immediate?: boolean; debounce?: number })
|
|
71
|
+
|
|
72
|
+
// Copy values between fields
|
|
73
|
+
copyFrom(sourcePath, targetPath, options?: { when?: (form) => boolean; fields?: string[]; transform?: (value) => value })
|
|
74
|
+
|
|
75
|
+
// Reset field when condition met
|
|
76
|
+
resetWhen(path, condition: (form) => boolean, options?: { toValue?: any })
|
|
77
|
+
|
|
78
|
+
// Re-validate when another field changes
|
|
79
|
+
revalidateWhen(triggerPath, targetPath)
|
|
80
|
+
|
|
81
|
+
// Sync multiple fields
|
|
82
|
+
syncFields(paths[], options?: { bidirectional?: boolean })
|
|
83
|
+
|
|
84
|
+
// Transform values
|
|
85
|
+
transformValue(path, transformer: (value) => value, options?: { on?: 'change' | 'blur' })
|
|
86
|
+
transformers.trim, transformers.toUpperCase, transformers.toLowerCase, transformers.toNumber
|
|
43
87
|
|
|
44
88
|
// BehaviorContext interface:
|
|
45
89
|
interface BehaviorContext<TForm> {
|
|
46
|
-
form: TForm
|
|
47
|
-
setFieldValue: (path, value) => void;
|
|
48
|
-
getFieldValue: (path) => unknown;
|
|
90
|
+
form: GroupNodeWithControls<TForm>; // Form proxy with typed field access
|
|
91
|
+
setFieldValue: (path: string, value: any) => void;
|
|
92
|
+
getFieldValue: (path: string) => unknown;
|
|
49
93
|
}
|
|
50
94
|
```
|
|
51
95
|
|
|
@@ -184,47 +228,174 @@ interface MyForm {
|
|
|
184
228
|
}
|
|
185
229
|
```
|
|
186
230
|
|
|
187
|
-
## 8.
|
|
231
|
+
## 8. FORMSCHEMA FORMAT (CRITICALLY IMPORTANT)
|
|
232
|
+
|
|
233
|
+
⚠️ **Every field MUST have `value` and `component` properties!**
|
|
234
|
+
|
|
235
|
+
### FieldConfig Interface
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
interface FieldConfig<T> {
|
|
239
|
+
value: T | null; // Initial value (REQUIRED)
|
|
240
|
+
component: ComponentType; // React component (REQUIRED)
|
|
241
|
+
componentProps?: object; // Props passed to component
|
|
242
|
+
disabled?: boolean; // Disable field initially
|
|
243
|
+
validators?: ValidatorFn[]; // Sync validators
|
|
244
|
+
asyncValidators?: AsyncValidatorFn[]; // Async validators
|
|
245
|
+
updateOn?: 'change' | 'blur' | 'submit';
|
|
246
|
+
debounce?: number;
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Primitive Fields
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import { Input, Select, Checkbox } from '@/components/ui';
|
|
254
|
+
|
|
255
|
+
const schema: FormSchema<MyForm> = {
|
|
256
|
+
// String field
|
|
257
|
+
name: {
|
|
258
|
+
value: '', // Initial value (REQUIRED)
|
|
259
|
+
component: Input, // React component (REQUIRED)
|
|
260
|
+
componentProps: {
|
|
261
|
+
label: 'Name',
|
|
262
|
+
placeholder: 'Enter name',
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
// Number field (optional)
|
|
267
|
+
age: {
|
|
268
|
+
value: undefined, // Use undefined, NOT null
|
|
269
|
+
component: Input,
|
|
270
|
+
componentProps: { type: 'number', label: 'Age' },
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
// Boolean field
|
|
274
|
+
agree: {
|
|
275
|
+
value: false,
|
|
276
|
+
component: Checkbox,
|
|
277
|
+
componentProps: { label: 'I agree to terms' },
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
// Enum/Select field
|
|
281
|
+
status: {
|
|
282
|
+
value: 'active',
|
|
283
|
+
component: Select,
|
|
284
|
+
componentProps: {
|
|
285
|
+
label: 'Status',
|
|
286
|
+
options: [
|
|
287
|
+
{ value: 'active', label: 'Active' },
|
|
288
|
+
{ value: 'inactive', label: 'Inactive' },
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Nested Objects
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
const schema: FormSchema<MyForm> = {
|
|
299
|
+
address: {
|
|
300
|
+
street: { value: '', component: Input, componentProps: { label: 'Street' } },
|
|
301
|
+
city: { value: '', component: Input, componentProps: { label: 'City' } },
|
|
302
|
+
zip: { value: '', component: Input, componentProps: { label: 'ZIP' } },
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Arrays (Tuple Format)
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
const itemSchema = {
|
|
311
|
+
id: { value: '', component: Input, componentProps: { label: 'ID' } },
|
|
312
|
+
name: { value: '', component: Input, componentProps: { label: 'Name' } },
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const schema: FormSchema<MyForm> = {
|
|
316
|
+
items: [itemSchema], // Array with ONE template item
|
|
317
|
+
};
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### ❌ WRONG - This will NOT compile
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// Missing value and component - TypeScript will error!
|
|
324
|
+
const schema = {
|
|
325
|
+
name: '', // ❌ Wrong
|
|
326
|
+
email: '', // ❌ Wrong
|
|
327
|
+
};
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### createForm API
|
|
188
331
|
|
|
189
332
|
```typescript
|
|
190
333
|
// Full config with behavior and validation
|
|
191
334
|
const form = createForm<MyForm>({
|
|
192
|
-
form: formSchema, // Required: form schema
|
|
335
|
+
form: formSchema, // Required: form schema with FieldConfig
|
|
193
336
|
behavior: behaviorSchema, // Optional: behavior rules
|
|
194
337
|
validation: validationSchema, // Optional: validation rules
|
|
195
338
|
});
|
|
196
339
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
const formSchema: FormSchema<MyForm> = {
|
|
202
|
-
name: '',
|
|
203
|
-
email: '',
|
|
204
|
-
address: {
|
|
205
|
-
street: '',
|
|
206
|
-
city: '',
|
|
207
|
-
},
|
|
208
|
-
// Arrays use tuple format
|
|
209
|
-
items: [{ id: '', name: '' }] as [{ id: string; name: string }],
|
|
210
|
-
};
|
|
340
|
+
// Access form controls
|
|
341
|
+
form.name.setValue('John');
|
|
342
|
+
form.address.city.value.value; // Get current value
|
|
343
|
+
form.items.push({ id: '1', name: 'Item' }); // Array operations
|
|
211
344
|
```
|
|
212
345
|
|
|
213
346
|
## 9. ARRAY SCHEMA FORMAT
|
|
214
347
|
|
|
348
|
+
**Array items are sub-forms!** Each array element is a complete sub-form with its own fields, validation, and behavior.
|
|
349
|
+
|
|
215
350
|
```typescript
|
|
216
351
|
// ✅ CORRECT - use tuple format for arrays
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
352
|
+
// The template item defines the sub-form schema for each array element
|
|
353
|
+
const itemSchema = {
|
|
354
|
+
id: { value: '', component: Input },
|
|
355
|
+
name: { value: '', component: Input },
|
|
356
|
+
price: { value: 0, component: Input, componentProps: { type: 'number' } },
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const schema: FormSchema<MyForm> = {
|
|
360
|
+
items: [itemSchema], // Array of sub-forms
|
|
220
361
|
};
|
|
221
362
|
|
|
363
|
+
// Each array item is a GroupNode (sub-form) with its own controls:
|
|
364
|
+
form.items.map((item) => {
|
|
365
|
+
// item is a sub-form (GroupNode) - access fields like nested form
|
|
366
|
+
item.name.setValue('New Name');
|
|
367
|
+
item.price.value.value; // Get current value
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
222
372
|
// ❌ WRONG - object format is NOT supported
|
|
223
373
|
const schema = {
|
|
224
374
|
items: { schema: itemSchema, initialItems: [] }, // This will NOT work
|
|
225
375
|
};
|
|
226
376
|
```
|
|
227
377
|
|
|
378
|
+
### Array Item as Sub-Form
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
// Validation for array items (each item is a sub-form)
|
|
382
|
+
validateItems(path.items, (itemPath) => {
|
|
383
|
+
// itemPath provides paths to sub-form fields
|
|
384
|
+
required(itemPath.name);
|
|
385
|
+
min(itemPath.price, 0);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Render array items - each item is a sub-form
|
|
389
|
+
{form.items.map((item, index) => (
|
|
390
|
+
<div key={item.id}>
|
|
391
|
+
{/* item is a sub-form - use FormField for each field */}
|
|
392
|
+
<FormField control={item.name} />
|
|
393
|
+
<FormField control={item.price} />
|
|
394
|
+
<button onClick={() => form.items.removeAt(index)}>Remove</button>
|
|
395
|
+
</div>
|
|
396
|
+
))}
|
|
397
|
+
```
|
|
398
|
+
|
|
228
399
|
## 10. ASYNC WATCHFIELD (CRITICALLY IMPORTANT)
|
|
229
400
|
|
|
230
401
|
```typescript
|
|
@@ -421,3 +592,53 @@ export const creditApplicationValidation: ValidationSchemaFn<Form> = (path) => {
|
|
|
421
592
|
| Simple | Single file: `ContactForm.tsx` |
|
|
422
593
|
| Medium | Separate files: `type.ts`, `schema.ts`, `validators.ts`, `Form.tsx` |
|
|
423
594
|
| Complex | Full colocation with `steps/` and `sub-forms/` |
|
|
595
|
+
|
|
596
|
+
## 15. NON-EXISTENT API (DO NOT USE)
|
|
597
|
+
|
|
598
|
+
⚠️ **The following APIs do NOT exist in @reformer/core:**
|
|
599
|
+
|
|
600
|
+
| ❌ Wrong | ✅ Correct | Notes |
|
|
601
|
+
|----------|-----------|-------|
|
|
602
|
+
| `useForm` | `createForm` | There is no useForm hook |
|
|
603
|
+
| `FieldSchema` | `FieldConfig<T>` | Type for individual field config |
|
|
604
|
+
| `when()` | `applyWhen()` | Conditional validation function |
|
|
605
|
+
| `FormFields` | `FieldNode<T>` | Type for field nodes |
|
|
606
|
+
|
|
607
|
+
### Common Import Errors
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
// ❌ WRONG - These do NOT exist
|
|
611
|
+
import { useForm } from '@reformer/core'; // NO!
|
|
612
|
+
import { when } from '@reformer/core/validators'; // NO!
|
|
613
|
+
import type { FieldSchema } from '@reformer/core'; // NO!
|
|
614
|
+
import type { FormFields } from '@reformer/core'; // NO!
|
|
615
|
+
|
|
616
|
+
// ✅ CORRECT
|
|
617
|
+
import { createForm, useFormControl } from '@reformer/core';
|
|
618
|
+
import { applyWhen } from '@reformer/core/validators';
|
|
619
|
+
import type { FieldConfig, FieldNode } from '@reformer/core';
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### FormSchema Common Mistakes
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
// ❌ WRONG - Simple values don't work
|
|
626
|
+
const schema = {
|
|
627
|
+
name: '', // Missing { value, component }
|
|
628
|
+
email: '', // Missing { value, component }
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// ✅ CORRECT - Every field needs value and component
|
|
632
|
+
const schema: FormSchema<MyForm> = {
|
|
633
|
+
name: {
|
|
634
|
+
value: '',
|
|
635
|
+
component: Input,
|
|
636
|
+
componentProps: { label: 'Name' },
|
|
637
|
+
},
|
|
638
|
+
email: {
|
|
639
|
+
value: '',
|
|
640
|
+
component: Input,
|
|
641
|
+
componentProps: { label: 'Email', type: 'email' },
|
|
642
|
+
},
|
|
643
|
+
};
|
|
644
|
+
```
|