@reformer/core 1.0.0-beta.9 → 1.1.0-beta.1

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.
@@ -13,13 +13,19 @@ import { TreeValidatorFn, ValidateTreeOptions } from '../../types';
13
13
  * @group Validation
14
14
  * @category Core Functions
15
15
  *
16
+ * @remarks
17
+ * Параметр `ctx` в callback требует явной типизации для корректного вывода типов:
18
+ * ```typescript
19
+ * validateTree((ctx: { form: MyFormType }) => { ... });
20
+ * ```
21
+ *
16
22
  * @example
17
23
  * ```typescript
24
+ * // Явная типизация ctx для избежания implicit any
18
25
  * validateTree(
19
- * (ctx: ValidationContext<TForm, TField>) => {
20
- * const form = ctx.formValue();
21
- * if (form.initialPayment && form.propertyValue) {
22
- * if (form.initialPayment > form.propertyValue) {
26
+ * (ctx: { form: MyForm }) => {
27
+ * if (ctx.form.initialPayment && ctx.form.propertyValue) {
28
+ * if (ctx.form.initialPayment > ctx.form.propertyValue) {
23
29
  * return {
24
30
  * code: 'initialPaymentTooHigh',
25
31
  * message: 'Первоначальный взнос не может превышать стоимость',
@@ -13,13 +13,19 @@ import { getCurrentValidationRegistry } from '../../utils/registry-helpers';
13
13
  * @group Validation
14
14
  * @category Core Functions
15
15
  *
16
+ * @remarks
17
+ * Параметр `ctx` в callback требует явной типизации для корректного вывода типов:
18
+ * ```typescript
19
+ * validateTree((ctx: { form: MyFormType }) => { ... });
20
+ * ```
21
+ *
16
22
  * @example
17
23
  * ```typescript
24
+ * // Явная типизация ctx для избежания implicit any
18
25
  * validateTree(
19
- * (ctx: ValidationContext<TForm, TField>) => {
20
- * const form = ctx.formValue();
21
- * if (form.initialPayment && form.propertyValue) {
22
- * if (form.initialPayment > form.propertyValue) {
26
+ * (ctx: { form: MyForm }) => {
27
+ * if (ctx.form.initialPayment && ctx.form.propertyValue) {
28
+ * if (ctx.form.initialPayment > ctx.form.propertyValue) {
23
29
  * return {
24
30
  * code: 'initialPaymentTooHigh',
25
31
  * message: 'Первоначальный взнос не может превышать стоимость',
package/llms.txt CHANGED
@@ -29,7 +29,7 @@ maxLength(path, length: number, options?: { message?: string })
29
29
  email(path, options?: { message?: string })
30
30
  validate(path, validator: (value) => ValidationError | null)
31
31
  validateTree(validator: (ctx) => ValidationError | null)
32
- when(condition: (form) => boolean, validatorsFn: () => void)
32
+ applyWhen(fieldPath, condition: (fieldValue) => boolean, validatorsFn: (path) => void)
33
33
  ```
34
34
 
35
35
  ### Behaviors
@@ -38,8 +38,15 @@ when(condition: (form) => boolean, validatorsFn: () => void)
38
38
  enableWhen(path, condition: (form) => boolean, options?: { resetOnDisable?: boolean })
39
39
  disableWhen(path, condition: (form) => boolean)
40
40
  computeFrom(sourcePaths[], targetPath, compute: (values) => result)
41
- watchField(path, callback: (value, ctx) => void)
41
+ watchField(path, callback: (value, ctx: BehaviorContext) => void)
42
42
  copyFrom(sourcePath, targetPath, options?: { when?, fields?, transform? })
43
+
44
+ // BehaviorContext interface:
45
+ interface BehaviorContext<TForm> {
46
+ form: TForm; // Current form state
47
+ setFieldValue: (path, value) => void; // Set field value
48
+ getFieldValue: (path) => unknown; // Get field value
49
+ }
43
50
  ```
44
51
 
45
52
  ## 3. COMMON PATTERNS
@@ -141,7 +148,7 @@ import type {
141
148
  import { createForm, useFormControl } from '@reformer/core';
142
149
 
143
150
  // Validators - from /validators submodule
144
- import { required, min, max, email, validate, when } from '@reformer/core/validators';
151
+ import { required, min, max, email, validate, applyWhen } from '@reformer/core/validators';
145
152
 
146
153
  // Behaviors - from /behaviors submodule
147
154
  import { computeFrom, enableWhen, watchField, copyFrom } from '@reformer/core/behaviors';
@@ -169,10 +176,248 @@ interface MyForm {
169
176
  city: string;
170
177
  };
171
178
 
172
- // Arrays
179
+ // Arrays - use tuple format for schema
173
180
  items: Array<{
174
181
  id: string;
175
182
  name: string;
176
183
  }>;
177
184
  }
178
185
  ```
186
+
187
+ ## 8. CREATEFORM API
188
+
189
+ ```typescript
190
+ // Full config with behavior and validation
191
+ const form = createForm<MyForm>({
192
+ form: formSchema, // Required: form schema
193
+ behavior: behaviorSchema, // Optional: behavior rules
194
+ validation: validationSchema, // Optional: validation rules
195
+ });
196
+
197
+ // Legacy format (schema only)
198
+ const form = createForm<MyForm>(formSchema);
199
+
200
+ // Form schema example
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
+ };
211
+ ```
212
+
213
+ ## 9. ARRAY SCHEMA FORMAT
214
+
215
+ ```typescript
216
+ // ✅ CORRECT - use tuple format for arrays
217
+ const schema = {
218
+ items: [itemSchema] as [typeof itemSchema],
219
+ properties: [propertySchema] as [typeof propertySchema],
220
+ };
221
+
222
+ // ❌ WRONG - object format is NOT supported
223
+ const schema = {
224
+ items: { schema: itemSchema, initialItems: [] }, // This will NOT work
225
+ };
226
+ ```
227
+
228
+ ## 10. ASYNC WATCHFIELD (CRITICALLY IMPORTANT)
229
+
230
+ ```typescript
231
+ // ✅ CORRECT - async watchField with ALL safeguards
232
+ watchField(
233
+ path.parentField,
234
+ async (value, ctx) => {
235
+ if (!value) return; // Guard clause
236
+
237
+ try {
238
+ const { data } = await fetchData(value);
239
+ ctx.form.dependentField.updateComponentProps({ options: data });
240
+ } catch (error) {
241
+ console.error('Failed:', error);
242
+ ctx.form.dependentField.updateComponentProps({ options: [] });
243
+ }
244
+ },
245
+ { immediate: false, debounce: 300 } // REQUIRED options
246
+ );
247
+
248
+ // ❌ WRONG - missing safeguards
249
+ watchField(path.field, async (value, ctx) => {
250
+ const { data } = await fetchData(value); // Will fail silently!
251
+ });
252
+ ```
253
+
254
+ ### Required Options for async watchField:
255
+ - `immediate: false` - prevents execution during initialization
256
+ - `debounce: 300` - prevents excessive API calls (300-500ms recommended)
257
+ - Guard clause - skip if value is empty
258
+ - try-catch - handle errors explicitly
259
+
260
+ ## 11. ARRAY CLEANUP PATTERN
261
+
262
+ ```typescript
263
+ // ✅ CORRECT - cleanup array when checkbox unchecked
264
+ watchField(
265
+ path.hasItems,
266
+ (hasItems, ctx) => {
267
+ if (!hasItems && ctx.form.items) {
268
+ ctx.form.items.clear();
269
+ }
270
+ },
271
+ { immediate: false }
272
+ );
273
+
274
+ // ❌ WRONG - no immediate: false, no null check
275
+ watchField(path.hasItems, (hasItems, ctx) => {
276
+ if (!hasItems) ctx.form.items.clear(); // May crash on init!
277
+ });
278
+ ```
279
+
280
+ ## 12. MULTI-STEP FORM VALIDATION
281
+
282
+ ```typescript
283
+ // Step-specific validation schemas
284
+ const step1Validation: ValidationSchemaFn<Form> = (path) => {
285
+ required(path.loanType);
286
+ required(path.loanAmount);
287
+ };
288
+
289
+ const step2Validation: ValidationSchemaFn<Form> = (path) => {
290
+ required(path.personalData.firstName);
291
+ required(path.personalData.lastName);
292
+ };
293
+
294
+ // STEP_VALIDATIONS map for useStepForm hook
295
+ export const STEP_VALIDATIONS = {
296
+ 1: step1Validation,
297
+ 2: step2Validation,
298
+ };
299
+
300
+ // Full validation (combines all steps)
301
+ export const fullValidation: ValidationSchemaFn<Form> = (path) => {
302
+ step1Validation(path);
303
+ step2Validation(path);
304
+ };
305
+ ```
306
+
307
+ ## 13. ⚠️ EXTENDED COMMON MISTAKES
308
+
309
+ ### Behavior Composition (Cycle Error)
310
+
311
+ ```typescript
312
+ // ❌ WRONG - apply() in behavior causes "Cycle detected"
313
+ const mainBehavior: BehaviorSchemaFn<Form> = (path) => {
314
+ apply(addressBehavior, path.address); // WILL FAIL!
315
+ };
316
+
317
+ // ✅ CORRECT - inline or use setup function
318
+ const setupAddressBehavior = (path: FieldPath<Address>) => {
319
+ watchField(path.region, async (region, ctx) => {
320
+ // ...
321
+ }, { immediate: false });
322
+ };
323
+
324
+ const mainBehavior: BehaviorSchemaFn<Form> = (path) => {
325
+ setupAddressBehavior(path.address); // Works!
326
+ };
327
+ ```
328
+
329
+ ### Infinite Loop in watchField
330
+
331
+ ```typescript
332
+ // ❌ WRONG - causes infinite loop
333
+ watchField(path.field, (value, ctx) => {
334
+ ctx.form.field.setValue(value.toUpperCase()); // Loop!
335
+ });
336
+
337
+ // ✅ CORRECT - write to different field OR add guard
338
+ watchField(path.input, (value, ctx) => {
339
+ const upper = value?.toUpperCase() || '';
340
+ if (ctx.form.display.value.value !== upper) {
341
+ ctx.form.display.setValue(upper);
342
+ }
343
+ }, { immediate: false });
344
+ ```
345
+
346
+ ### validateTree Typing
347
+
348
+ ```typescript
349
+ // ❌ WRONG - implicit any
350
+ validateTree((ctx) => { ... });
351
+
352
+ // ✅ CORRECT - explicit typing
353
+ validateTree((ctx: { form: MyForm }) => {
354
+ if (ctx.form.field1 > ctx.form.field2) {
355
+ return { code: 'error', message: 'Invalid' };
356
+ }
357
+ return null;
358
+ });
359
+ ```
360
+
361
+ ## 14. PROJECT STRUCTURE (COLOCATION)
362
+
363
+ ```
364
+ src/
365
+ ├── components/ui/ # Reusable UI components
366
+ │ ├── FormField.tsx
367
+ │ └── FormArrayManager.tsx
368
+
369
+ ├── forms/
370
+ │ └── [form-name]/ # Form module
371
+ │ ├── type.ts # Main form type
372
+ │ ├── schema.ts # Main schema
373
+ │ ├── validators.ts # Validators
374
+ │ ├── behaviors.ts # Behaviors
375
+ │ ├── [FormName]Form.tsx # Main component
376
+ │ │
377
+ │ ├── steps/ # Multi-step wizard
378
+ │ │ ├── loan-info/
379
+ │ │ │ ├── type.ts
380
+ │ │ │ ├── schema.ts
381
+ │ │ │ ├── validators.ts
382
+ │ │ │ ├── behaviors.ts
383
+ │ │ │ └── LoanInfoForm.tsx
384
+ │ │ └── ...
385
+ │ │
386
+ │ └── sub-forms/ # Reusable sub-forms
387
+ │ ├── address/
388
+ │ └── personal-data/
389
+ ```
390
+
391
+ ### Key Files
392
+
393
+ ```typescript
394
+ // forms/credit-application/type.ts
395
+ export type { LoanInfoStep } from './steps/loan-info/type';
396
+ export interface CreditApplicationForm {
397
+ loanType: LoanType;
398
+ loanAmount: number;
399
+ // ...
400
+ }
401
+
402
+ // forms/credit-application/schema.ts
403
+ import { loanInfoSchema } from './steps/loan-info/schema';
404
+ export const creditApplicationSchema = {
405
+ ...loanInfoSchema,
406
+ monthlyPayment: { value: 0, disabled: true },
407
+ };
408
+
409
+ // forms/credit-application/validators.ts
410
+ import { loanValidation } from './steps/loan-info/validators';
411
+ export const creditApplicationValidation: ValidationSchemaFn<Form> = (path) => {
412
+ loanValidation(path);
413
+ // Cross-step validation...
414
+ };
415
+ ```
416
+
417
+ ### Scaling
418
+
419
+ | Complexity | Structure |
420
+ |------------|-----------|
421
+ | Simple | Single file: `ContactForm.tsx` |
422
+ | Medium | Separate files: `type.ts`, `schema.ts`, `validators.ts`, `Form.tsx` |
423
+ | Complex | Full colocation with `steps/` and `sub-forms/` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reformer/core",
3
- "version": "1.0.0-beta.9",
3
+ "version": "1.1.0-beta.1",
4
4
  "description": "Reactive form state management library for React with signals-based architecture",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -77,8 +77,8 @@
77
77
  "@types/uuid": "^10.0.0",
78
78
  "@vitejs/plugin-react": "^5.1.0",
79
79
  "@vitest/utils": "^4.0.8",
80
- "react": "^19.2.0",
81
- "react-dom": "^19.2.0",
80
+ "react": "^19.2.1",
81
+ "react-dom": "^19.2.1",
82
82
  "typescript": "^5.9.3",
83
83
  "vite": "^7.2.2",
84
84
  "vite-plugin-dts": "^4.5.4",