@reformer/core 10.2.0 → 11.0.0-beta.2

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.
Files changed (63) hide show
  1. package/dist/behaviors.js +134 -134
  2. package/dist/form/form-submitter.d.ts +0 -4
  3. package/dist/form/hooks/types.d.ts +3 -3
  4. package/dist/form/index.d.ts +0 -3
  5. package/dist/form/nodes/array-node.d.ts +0 -84
  6. package/dist/form/nodes/form-node.d.ts +0 -14
  7. package/dist/form/nodes/group-node.d.ts +0 -4
  8. package/dist/form/status-machine.d.ts +0 -38
  9. package/dist/form/types/contracts.d.ts +0 -6
  10. package/dist/form/types/deep-schema.d.ts +0 -9
  11. package/dist/form/types/index.d.ts +4 -38
  12. package/dist/form/types/schema-node.d.ts +13 -16
  13. package/dist/form/types/validation-schema.d.ts +0 -25
  14. package/dist/form/unique-id.d.ts +3 -11
  15. package/dist/form/validators.d.ts +8 -3
  16. package/dist/form-model-DhW2HZOB.js +323 -0
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +448 -636
  19. package/dist/runtime-token.d.ts +23 -0
  20. package/dist/state/form-model.d.ts +14 -0
  21. package/dist/state/index.d.ts +2 -1
  22. package/dist/state/types.d.ts +39 -11
  23. package/dist/state.js +18 -18
  24. package/dist/validation.js +1 -1
  25. package/dist/validators.d.ts +0 -4
  26. package/dist/validators.js +88 -122
  27. package/docs/llms/01-api-reference.md +63 -0
  28. package/docs/llms/02-quick-start.md +143 -0
  29. package/docs/llms/03-api-signatures.md +273 -0
  30. package/docs/llms/04-common-patterns.md +202 -0
  31. package/docs/llms/05-common-mistakes.md +167 -0
  32. package/docs/llms/06-troubleshooting.md +11 -0
  33. package/docs/llms/07-complete-import.md +99 -0
  34. package/docs/llms/08-form-types.md +47 -0
  35. package/docs/llms/09-formschema.md +143 -0
  36. package/docs/llms/10-arrays.md +161 -0
  37. package/docs/llms/11-async-watchfield.md +53 -0
  38. package/docs/llms/12-array-cleanup.md +31 -0
  39. package/docs/llms/13-multi-step.md +91 -0
  40. package/docs/llms/14-extended-mistakes.md +98 -0
  41. package/docs/llms/15-project-structure.md +125 -0
  42. package/docs/llms/16-ui-components.md +189 -0
  43. package/docs/llms/17-nonexistent-api.md +93 -0
  44. package/docs/llms/18-conditional-fields.md +183 -0
  45. package/docs/llms/19-reading-values.md +88 -0
  46. package/docs/llms/20-compute-vs-watch.md +147 -0
  47. package/docs/llms/21-array-operations.md +113 -0
  48. package/docs/llms/22-cycle-detection.md +86 -0
  49. package/docs/llms/23-copy-from.md +141 -0
  50. package/docs/llms/24-sync-fields.md +114 -0
  51. package/docs/llms/25-reset-when.md +115 -0
  52. package/docs/llms/26-transform-value.md +136 -0
  53. package/docs/llms/27-revalidate-when.md +132 -0
  54. package/docs/llms/28-submit-and-reset.md +259 -0
  55. package/docs/llms/29-async-preload.md +193 -0
  56. package/docs/llms/30-type-safety-recipes.md +159 -0
  57. package/docs/llms/31-async-validator-debounce.md +135 -0
  58. package/docs/llms/32-async-options-loading.md +108 -0
  59. package/docs/llms/33-validation-strategy.md +193 -0
  60. package/llms.txt +219 -1533
  61. package/package.json +33 -10
  62. package/dist/derived-registry-CluZCQ7U.js +0 -16
  63. package/dist/form-model-DYCzGGKF.js +0 -286
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Штамп копии рантайма ядра.
3
+ *
4
+ * Единственное назначение — быть объектом, уникальным в пределах ОДНОЙ копии модуля. Две
5
+ * независимо загруженные копии `@reformer/core` дадут два разных объекта, и по их несовпадению
6
+ * guard распознаёт дубль рантайма.
7
+ *
8
+ * Почему именно объект, а не строка с версией: две копии ОДНОЙ версии ломают всё ровно так же,
9
+ * как две копии разных версий, — раздваиваются модульные `WeakMap` (`derived-registry`,
10
+ * `signal-node-registry`) и перестают работать проверки `instanceof Signal`. Сравнение версий
11
+ * такой случай пропустило бы.
12
+ *
13
+ * @see `@reformer/form-registry/guard`
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { CORE_RUNTIME_TOKEN } from '@reformer/core';
18
+ * import { stampRuntime } from '@reformer/form-registry/guard';
19
+ *
20
+ * stampRuntime('@reformer/core', CORE_RUNTIME_TOKEN);
21
+ * ```
22
+ */
23
+ export declare const CORE_RUNTIME_TOKEN: object;
@@ -1,4 +1,18 @@
1
1
  import { FormModel, PathAwareSignal } from './types';
2
+ /**
3
+ * Узел дерева `model.$` — контейнер (группа/массив), а не лист?
4
+ *
5
+ * Контейнерный узел структурно совместим с `ReadonlySignal` (`peek`/`value`/`subscribe`), поэтому
6
+ * duck-typing «есть `peek` ⇒ это лист» на нём даёт ложное срабатывание. Этот guard — надёжный
7
+ * способ различить: обходчикам дерева нужно спускаться в контейнер, а не читать его целиком.
8
+ *
9
+ * @group Model
10
+ * @example
11
+ * ```typescript
12
+ * const isLeaf = (v: unknown) => isSignalLike(v) && !isModelContainerSignal(v);
13
+ * ```
14
+ */
15
+ export declare function isModelContainerSignal(value: unknown): boolean;
2
16
  /**
3
17
  * Обойти ВСЕ листовые сигналы модели (включая элементы массивов), вызвав `visit` на каждом.
4
18
  *
@@ -23,7 +23,8 @@
23
23
  * @module state
24
24
  */
25
25
  export { createModel } from './form-model';
26
- export type { FormModel, ModelArray, ModelObject, ModelValue, ModelSignals, ModelApi, PathAwareSignal, } from './types';
26
+ export type { FormModel, ModelArray, ModelArraySignals, ModelGroupSignals, ModelObject, ModelValue, ModelSignals, ModelApi, PathAwareSignal, } from './types';
27
+ export { isModelContainerSignal } from './form-model';
27
28
  export { computeFrom, copyFrom, watchField, transformValue, resetWhen, syncFields, revalidateWhen, } from './behaviors-value';
28
29
  export type { BehaviorCleanup } from './behaviors-value';
29
30
  export { markDerived, isDerived, unmarkDerived } from './derived-registry';
@@ -1,4 +1,4 @@
1
- import { Signal } from '@preact/signals-core';
1
+ import { ReadonlySignal, Signal } from '@preact/signals-core';
2
2
  /**
3
3
  * Сигнал, который знает свой путь в модели (`'personalData.lastName'`).
4
4
  * Используется как «ручка поля»: и привязка value в схеме, и идентичность для testId/devtools.
@@ -81,8 +81,8 @@ export interface ModelArray<U> {
81
81
  [index: number]: ModelValue<U>;
82
82
  }
83
83
  /**
84
- * Дерево сигналов (escape-hatch `model.$`): листья → {@link PathAwareSignal},
85
- * объекты → вложенное дерево, массивы → индексируемое дерево под-сигналов.
84
+ * Карта под-сигналов объекта в дереве `model.$` (без свойств самого узла — их добавляет
85
+ * {@link ModelGroupSignals}).
86
86
  *
87
87
  * @group Model
88
88
  */
@@ -90,16 +90,41 @@ export type ModelSignals<T> = {
90
90
  [K in keyof T]: ModelSignalNode<T[K]>;
91
91
  };
92
92
  /**
93
- * Узел дерева сигналов `model.$`: массив → индексируемый узел под-сигналов, Opaque (Date/File/Blob)
94
- * и примитив → {@link PathAwareSignal}, объект → вложенное дерево {@link ModelSignals}. Для примитивных
95
- * и Opaque массивов элемент — сам сигнал листа (а не под-дерево), поэтому `model.$.tags[0]` — это
96
- * `PathAwareSignal<string>`, а не `ModelSignals<never>`.
93
+ * Свойства контейнерного узла дерева `$`, «поверх» карты детей. Ключи детей вырезаются из
94
+ * {@link ReadonlySignal}: доступ к полю формы всегда выигрывает у одноимённого свойства сигнала
95
+ * (так же ведёт себя рантайм), поэтому `{ value: string }` не порождает конфликта типов.
97
96
  * @internal
98
97
  */
99
- type ModelSignalNode<V> = NonNullable<V> extends ReadonlyArray<infer U> ? {
98
+ type ContainerSignal<T, TKeys extends PropertyKey> = Omit<ReadonlySignal<T>, TKeys>;
99
+ /**
100
+ * Узел-группа дерева `model.$`: доступ к под-сигналам полей ({@link ModelSignals}) И одновременно
101
+ * {@link ReadonlySignal} агрегированного значения группы — `.value`/`.peek()`/`.subscribe()`.
102
+ *
103
+ * ⚠️ Поле формы с именем `value`/`peek`/`subscribe`/`valueOf`/`toString`/`toJSON`/`brand` затеняет
104
+ * одноимённое свойство сигнала (редкий краевой случай); `subscribe` при этом продолжает работать.
105
+ *
106
+ * @group Model
107
+ */
108
+ export type ModelGroupSignals<T> = ModelSignals<T> & ContainerSignal<T, keyof T>;
109
+ /**
110
+ * Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length`
111
+ * и {@link ReadonlySignal} значения массива целиком (реагирует и на правку элемента, и на изменение
112
+ * состава — push/removeAt/move).
113
+ *
114
+ * @group Model
115
+ */
116
+ export type ModelArraySignals<U, V> = ContainerSignal<V, 'length'> & {
100
117
  readonly length: number;
101
118
  readonly [index: number]: ModelSignalNode<U>;
102
- } : NonNullable<V> extends Opaque ? PathAwareSignal<V> : NonNullable<V> extends object ? ModelSignals<NonNullable<V>> : PathAwareSignal<V>;
119
+ };
120
+ /**
121
+ * Узел дерева сигналов `model.$`: массив → {@link ModelArraySignals}, Opaque (Date/File/Blob)
122
+ * и примитив → {@link PathAwareSignal}, объект → {@link ModelGroupSignals}. Для примитивных
123
+ * и Opaque массивов элемент — сам сигнал листа (а не под-дерево), поэтому `model.$.tags[0]` — это
124
+ * `PathAwareSignal<string>`, а не `ModelGroupSignals<never>`.
125
+ * @internal
126
+ */
127
+ type ModelSignalNode<V> = NonNullable<V> extends ReadonlyArray<infer U> ? ModelArraySignals<U, NonNullable<V>> : NonNullable<V> extends Opaque ? PathAwareSignal<V> : NonNullable<V> extends object ? ModelGroupSignals<NonNullable<V>> : PathAwareSignal<V>;
103
128
  /**
104
129
  * API уровня модели (доступно на корне, под-моделях вложенных объектов-групп и элементов массива).
105
130
  *
@@ -109,8 +134,11 @@ type ModelSignalNode<V> = NonNullable<V> extends ReadonlyArray<infer U> ? {
109
134
  * @group Model
110
135
  */
111
136
  export interface ModelApi<T> {
112
- /** Escape-hatch к сигналам: `model.$.loanType` → `PathAwareSignal<LoanType>`. */
113
- readonly $: ModelSignals<T>;
137
+ /**
138
+ * Escape-hatch к сигналам: `model.$.loanType` → `PathAwareSignal<LoanType>`.
139
+ * Сам узел — {@link ReadonlySignal} модели целиком: `model.$.subscribe(v => …)`.
140
+ */
141
+ readonly $: ModelGroupSignals<T>;
114
142
  /** Снимок значений (без подписки) — для submit. */
115
143
  get(): T;
116
144
  /**
package/dist/state.js CHANGED
@@ -1,21 +1,21 @@
1
- import { c as s } from "./form-model-DYCzGGKF.js";
2
- import { c as o, a as t, r as c, b as i, d as m, e as d, f, s as l, t as n, w as u } from "./behaviors-value-C7dSEhCc.js";
3
- import { i as b, m as k, u as v } from "./derived-registry-CluZCQ7U.js";
4
- import { S as D } from "./subscription-manager-705bls97.js";
1
+ import { c as s, a as r, i as o, m as i, u as t } from "./form-model-DhW2HZOB.js";
2
+ import { c as n, a as d, r as l, b as m, d as f, e as u, f as p, s as b, t as k, w as v } from "./behaviors-value-C7dSEhCc.js";
3
+ import { S as F } from "./subscription-manager-705bls97.js";
5
4
  export {
6
- D as SubscriptionManager,
7
- o as computeFrom,
8
- t as copyFrom,
5
+ F as SubscriptionManager,
6
+ n as computeFrom,
7
+ d as copyFrom,
9
8
  s as createModel,
10
- b as isDerived,
11
- k as markDerived,
12
- c as resetWhen,
13
- i as revalidateWhen,
14
- m as runOutsideEffect,
15
- d as safeCallback,
16
- f as safeDebouncedCallback,
17
- l as syncFields,
18
- n as transformValue,
19
- v as unmarkDerived,
20
- u as watchField
9
+ r as isDerived,
10
+ o as isModelContainerSignal,
11
+ i as markDerived,
12
+ l as resetWhen,
13
+ m as revalidateWhen,
14
+ f as runOutsideEffect,
15
+ u as safeCallback,
16
+ p as safeDebouncedCallback,
17
+ b as syncFields,
18
+ k as transformValue,
19
+ t as unmarkDerived,
20
+ v as watchField
21
21
  };
@@ -1,5 +1,5 @@
1
1
  import { signal as M, effect as E } from "@preact/signals-core";
2
- import { e as L } from "./form-model-DYCzGGKF.js";
2
+ import { e as L } from "./form-model-DhW2HZOB.js";
3
3
  import { g as p } from "./signal-node-registry-tiwel-TV.js";
4
4
  let v = null;
5
5
  function f(e) {
@@ -1,6 +1,2 @@
1
1
  export * from './form/validators'
2
2
  export {}
3
- import _default from './form/validators'
4
- export default _default
5
- export * from './form/validators'
6
- export {}
@@ -1,200 +1,166 @@
1
- import { required as u } from "./validators/required.js";
2
- import { min as a } from "./validators/min.js";
3
- import { max as p } from "./validators/max.js";
4
- import { minLength as c } from "./validators/min-length.js";
5
- import { maxLength as g } from "./validators/max-length.js";
6
- import { email as d } from "./validators/email.js";
7
- import { pattern as z } from "./validators/pattern.js";
8
- import { url as h } from "./validators/url.js";
9
- import { phone as y } from "./validators/phone.js";
10
- import { isNumber as F } from "./validators/is-number.js";
11
- import { integer as x } from "./validators/integer.js";
12
- import { multipleOf as S } from "./validators/multiple-of.js";
13
- import { nonNegative as b } from "./validators/non-negative.js";
14
- import { nonZero as A } from "./validators/non-zero.js";
15
- import { isDate as L } from "./validators/is-date.js";
16
- import { minDate as T } from "./validators/min-date.js";
17
- import { maxDate as C } from "./validators/max-date.js";
18
- import { pastDate as D } from "./validators/past-date.js";
19
- import { futureDate as N } from "./validators/future-date.js";
20
- import { minAge as W } from "./validators/min-age.js";
21
- import { maxAge as _ } from "./validators/max-age.js";
22
- function m(r) {
1
+ import { required as z } from "./validators/required.js";
2
+ import { min as y } from "./validators/min.js";
3
+ import { max as b } from "./validators/max.js";
4
+ import { minLength as L } from "./validators/min-length.js";
5
+ import { maxLength as D } from "./validators/max-length.js";
6
+ import { email as T } from "./validators/email.js";
7
+ import { pattern as w } from "./validators/pattern.js";
8
+ import { url as k } from "./validators/url.js";
9
+ import { phone as B } from "./validators/phone.js";
10
+ import { isNumber as Z } from "./validators/is-number.js";
11
+ import { integer as G } from "./validators/integer.js";
12
+ import { multipleOf as I } from "./validators/multiple-of.js";
13
+ import { nonNegative as K } from "./validators/non-negative.js";
14
+ import { nonZero as P } from "./validators/non-zero.js";
15
+ import { isDate as R } from "./validators/is-date.js";
16
+ import { minDate as V } from "./validators/min-date.js";
17
+ import { maxDate as Y } from "./validators/max-date.js";
18
+ import { pastDate as $ } from "./validators/past-date.js";
19
+ import { futureDate as ee } from "./validators/future-date.js";
20
+ import { minAge as ne } from "./validators/min-age.js";
21
+ import { maxAge as ie } from "./validators/max-age.js";
22
+ function s(r) {
23
23
  if (typeof r != "object" || r === null) return !1;
24
- const i = r;
25
- return !(typeof i.name != "string" || i.size !== void 0 && typeof i.size != "number" || i.type !== void 0 && typeof i.type != "string");
24
+ const t = r;
25
+ return !(typeof t.name != "string" || t.size !== void 0 && typeof t.size != "number" || t.type !== void 0 && typeof t.type != "string");
26
26
  }
27
27
  function o(r) {
28
- return Array.isArray(r) ? r.filter(m) : m(r) ? [r] : null;
28
+ return Array.isArray(r) ? r.filter(s) : s(r) ? [r] : null;
29
29
  }
30
- function s(r, i) {
31
- const e = i.split(",").map((l) => l.trim().toLowerCase()).filter(Boolean);
30
+ function u(r, t) {
31
+ const e = t.split(",").map((l) => l.trim().toLowerCase()).filter(Boolean);
32
32
  if (e.length === 0) return !0;
33
- const n = r.name.toLowerCase(), t = (r.type ?? "").toLowerCase();
34
- return e.some((l) => l.startsWith(".") ? n.endsWith(l) : l.endsWith("/*") ? t.startsWith(l.slice(0, -1)) : l.includes("/") ? t === l : !1);
33
+ const n = r.name.toLowerCase(), i = (r.type ?? "").toLowerCase();
34
+ return e.some((l) => l.startsWith(".") ? n.endsWith(l) : l.endsWith("/*") ? i.startsWith(l.slice(0, -1)) : l.includes("/") ? i === l : !1);
35
35
  }
36
- function j(r, i) {
36
+ function m(r, t) {
37
37
  return (e) => {
38
38
  if (e == null || e === "")
39
39
  return null;
40
40
  const n = o(e);
41
41
  if (n === null || n.length === 0) return null;
42
- for (const t of n)
43
- if (typeof t.size == "number" && t.size > r)
42
+ for (const i of n)
43
+ if (typeof i.size == "number" && i.size > r)
44
44
  return {
45
45
  code: "maxFileSize",
46
- message: i?.message ?? "invalid",
46
+ message: t?.message ?? "invalid",
47
47
  params: {
48
48
  maxFileSize: r,
49
- fileName: t.name,
50
- actualSize: t.size,
51
- ...i?.params
49
+ fileName: i.name,
50
+ actualSize: i.size,
51
+ ...t?.params
52
52
  }
53
53
  };
54
54
  return null;
55
55
  };
56
56
  }
57
- function w(r, i) {
57
+ function a(r, t) {
58
58
  return (e) => {
59
59
  if (e == null || e === "")
60
60
  return null;
61
61
  const n = o(e);
62
62
  if (n === null || n.length === 0) return null;
63
- for (const t of n)
64
- if (typeof t.size == "number" && t.size < r)
63
+ for (const i of n)
64
+ if (typeof i.size == "number" && i.size < r)
65
65
  return {
66
66
  code: "minFileSize",
67
- message: i?.message ?? "invalid",
67
+ message: t?.message ?? "invalid",
68
68
  params: {
69
69
  minFileSize: r,
70
- fileName: t.name,
71
- actualSize: t.size,
72
- ...i?.params
70
+ fileName: i.name,
71
+ actualSize: i.size,
72
+ ...t?.params
73
73
  }
74
74
  };
75
75
  return null;
76
76
  };
77
77
  }
78
- function O(r, i) {
78
+ function p(r, t) {
79
79
  return (e) => {
80
80
  if (e == null || e === "")
81
81
  return null;
82
82
  const n = o(e);
83
83
  if (n === null || n.length === 0) return null;
84
- for (const t of n)
85
- if (!s(t, r))
84
+ for (const i of n)
85
+ if (!u(i, r))
86
86
  return {
87
87
  code: "fileType",
88
- message: i?.message ?? "invalid",
89
- params: { accept: r, fileName: t.name, ...i?.params }
88
+ message: t?.message ?? "invalid",
89
+ params: { accept: r, fileName: i.name, ...t?.params }
90
90
  };
91
91
  return null;
92
92
  };
93
93
  }
94
- function k(r, i) {
94
+ function c(r, t) {
95
95
  return (e) => {
96
96
  if (e == null || e === "")
97
97
  return null;
98
98
  const n = o(e);
99
99
  return n === null || n.length === 0 ? null : n.length > r ? {
100
100
  code: "maxFiles",
101
- message: i?.message ?? "invalid",
102
- params: { maxFiles: r, actualCount: n.length, ...i?.params }
101
+ message: t?.message ?? "invalid",
102
+ params: { maxFiles: r, actualCount: n.length, ...t?.params }
103
103
  } : null;
104
104
  };
105
105
  }
106
- function q(r, i) {
106
+ function x(r, t) {
107
107
  return (e) => {
108
108
  if (e == null || e === "")
109
109
  return null;
110
110
  const n = o(e);
111
111
  return n === null || n.length === 0 ? null : n.length < r ? {
112
112
  code: "minFiles",
113
- message: i?.message ?? "invalid",
114
- params: { minFiles: r, actualCount: n.length, ...i?.params }
113
+ message: t?.message ?? "invalid",
114
+ params: { minFiles: r, actualCount: n.length, ...t?.params }
115
115
  } : null;
116
116
  };
117
117
  }
118
- function B(r, i) {
118
+ function g(r, t) {
119
119
  return (e) => {
120
120
  if (e == null || e === "")
121
121
  return null;
122
122
  const n = o(e);
123
123
  if (n === null || n.length === 0) return null;
124
- const t = n.reduce(
124
+ const i = n.reduce(
125
125
  (l, f) => l + (typeof f.size == "number" ? f.size : 0),
126
126
  0
127
127
  );
128
- return t > r ? {
128
+ return i > r ? {
129
129
  code: "maxTotalFileSize",
130
- message: i?.message ?? "invalid",
131
- params: { maxTotalFileSize: r, actualTotal: t, ...i?.params }
130
+ message: t?.message ?? "invalid",
131
+ params: { maxTotalFileSize: r, actualTotal: i, ...t?.params }
132
132
  } : null;
133
133
  };
134
134
  }
135
- const te = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
136
- __proto__: null,
137
- email: d,
138
- fileType: O,
139
- futureDate: N,
140
- integer: x,
141
- isDate: L,
142
- isFileLike: m,
143
- isNumber: F,
144
- matchesFileAccept: s,
145
- max: p,
146
- maxAge: _,
147
- maxDate: C,
148
- maxFileSize: j,
149
- maxFiles: k,
150
- maxLength: g,
151
- maxTotalFileSize: B,
152
- min: a,
153
- minAge: W,
154
- minDate: T,
155
- minFileSize: w,
156
- minFiles: q,
157
- minLength: c,
158
- multipleOf: S,
159
- nonNegative: b,
160
- nonZero: A,
161
- pastDate: D,
162
- pattern: z,
163
- phone: y,
164
- required: u,
165
- toFileArray: o,
166
- url: h
167
- }, Symbol.toStringTag, { value: "Module" }));
168
135
  export {
169
- te as default,
170
- d as email,
171
- O as fileType,
172
- N as futureDate,
173
- x as integer,
174
- L as isDate,
175
- m as isFileLike,
176
- F as isNumber,
177
- s as matchesFileAccept,
178
- p as max,
179
- _ as maxAge,
180
- C as maxDate,
181
- j as maxFileSize,
182
- k as maxFiles,
183
- g as maxLength,
184
- B as maxTotalFileSize,
185
- a as min,
186
- W as minAge,
187
- T as minDate,
188
- w as minFileSize,
189
- q as minFiles,
190
- c as minLength,
191
- S as multipleOf,
192
- b as nonNegative,
193
- A as nonZero,
194
- D as pastDate,
195
- z as pattern,
196
- y as phone,
197
- u as required,
136
+ T as email,
137
+ p as fileType,
138
+ ee as futureDate,
139
+ G as integer,
140
+ R as isDate,
141
+ s as isFileLike,
142
+ Z as isNumber,
143
+ u as matchesFileAccept,
144
+ b as max,
145
+ ie as maxAge,
146
+ Y as maxDate,
147
+ m as maxFileSize,
148
+ c as maxFiles,
149
+ D as maxLength,
150
+ g as maxTotalFileSize,
151
+ y as min,
152
+ ne as minAge,
153
+ V as minDate,
154
+ a as minFileSize,
155
+ x as minFiles,
156
+ L as minLength,
157
+ I as multipleOf,
158
+ K as nonNegative,
159
+ P as nonZero,
160
+ $ as pastDate,
161
+ w as pattern,
162
+ B as phone,
163
+ z as required,
198
164
  o as toFileArray,
199
- h as url
165
+ k as url
200
166
  };
@@ -0,0 +1,63 @@
1
+ ## 1. API Reference
2
+
3
+ ### Imports (CRITICALLY IMPORTANT)
4
+
5
+ Архитектура M1: значения живут в **модели** (`createModel`), а форма (`createForm`) строит ноды поверх сигналов модели. Behaviors работают на сигналах (`model.$.field`), а не на строковых путях.
6
+
7
+ | What | Where |
8
+ | ------------------------------------------------------------------------------------------------------ | --------------------------- |
9
+ | `createModel`, `createForm` | `@reformer/core` |
10
+ | `validateModel`, `defineValidationSchema`, `validate`, `validateAsync`, `validateWhen`, `cross`, `each`, `apply` | `@reformer/core/validation` |
11
+ | `Rule`, `AsyncRule`, `ValidationSchema` (типы) | `@reformer/core/validation` |
12
+ | `useFormControl`, `useFormControlValue`, `useArrayLength` | `@reformer/core` |
13
+ | `FormModel`, `FormProxy`, `FieldNode`, `GroupNode`, `ArrayNode`, `ModelArrayNode` | `@reformer/core` |
14
+ | `ModelSignals`, `ModelArray`, `ModelValue`, `ModelObject`, `PathAwareSignal` | `@reformer/core` |
15
+ | `ValidationError`, `FieldConfig`, `FormSchema`, `FieldControlState` | `@reformer/core` |
16
+ | `computeFrom`, `copyFrom`, `watchField`, `enableWhen`, `disableWhen` | `@reformer/core` (примитивы) |
17
+ | `transformValue`, `resetWhen`, `syncFields`, `revalidateWhen` | `@reformer/core` (примитивы) |
18
+ | `required`, `min`, `max`, `minLength`, `maxLength`, `email`, `pattern`, `url`, `phone` | `@reformer/core/validators` |
19
+ | `isNumber`, `integer`, `multipleOf`, `nonNegative`, `nonZero` | `@reformer/core/validators` |
20
+ | `isDate`, `minDate`, `maxDate`, `pastDate`, `futureDate`, `minAge`, `maxAge` | `@reformer/core/validators` |
21
+ | `defineFormBehavior`, `compute`, `computeFrom`, `copyFrom`, `onChange`, `enableWhen`, `disableWhen` | `@reformer/core/behaviors` |
22
+ | `transformValue`, `resetWhen`, `syncFields`, `revalidateWhen`, `apply`, `applyEach`, `aggregateInto` | `@reformer/core/behaviors` |
23
+ | `exclusiveFlag`, `onDispose`, `getScope`, `effect`, `defer` | `@reformer/core/behaviors` |
24
+
25
+ > **Два способа писать behaviors.** Низкоуровневые примитивы (`computeFrom`, `copyFrom`, `watchField`,
26
+ > `enableWhen`, …) экспортируются из `@reformer/core`, принимают **сигналы** (`model.$.x`), возвращают
27
+ > **cleanup-функцию** и вызываются императивно (например, в `useEffect`). Декларативный DSL
28
+ > (`defineFormBehavior` + операторы) экспортируется из `@reformer/core/behaviors`, регистрирует cleanup
29
+ > сам и передаётся в `createForm({ behavior })`. См. `20-compute-vs-watch.md`.
30
+
31
+ > **Валидация — отдельный слой.** Layout-схема `createForm` НЕ несёт валидаторов. Правила живут в
32
+ > `defineValidationSchema<T>(({ model }) => { validate(model.$.x, [required(), min(50000)]); ... })`
33
+ > из `@reformer/core/validation`; фабрики `required()`, `min(50000)`, `email()` возвращают
34
+ > `Rule<T> = (value) => ValidationError | null` и передаются массивом в `validate(sig, [...])`.
35
+ > Запуск — только внешним раннером `await validateModel(model, schema)` (`Promise<boolean>`, ошибки
36
+ > сам роутит в ноды; `form.validate()`/`form.submit()` schema-валидацию НЕ гоняют).
37
+
38
+ ### Type Values
39
+
40
+ - Опциональные числа: `number | null` (конвенция «пользователь очистил поле»)
41
+ - Опциональные строки: `string` (по умолчанию пустая строка) или `string | null`
42
+ - Form-shape тип объявляй как `type`-alias — см. `30-type-safety-recipes.md`
43
+
44
+ ### React Hooks Comparison (CRITICALLY IMPORTANT)
45
+
46
+ | Hook | Return Type | Subscribes To | Use Case |
47
+ |------|-------------|---------------|----------|
48
+ | `useFormControl(field)` | `{ value, errors, disabled, touched, valid, invalid, pending, shouldShowError, componentProps }` | Все сигналы поля | Полное состояние поля, инпуты |
49
+ | `useFormControlValue(field)` | `T` (значение напрямую) | Только сигнал value | Условный рендеринг |
50
+ | `useArrayLength(array)` | `number` | Только длина массива | Реактивная длина массива |
51
+
52
+ **CRITICAL**: Не деструктурируй `useFormControlValue`! Он возвращает `T` напрямую, НЕ `{ value: T }`.
53
+
54
+ ```typescript
55
+ // WRONG - will always be undefined!
56
+ const { value: loanType } = useFormControlValue(control.loanType);
57
+
58
+ // CORRECT
59
+ const loanType = useFormControlValue(control.loanType);
60
+
61
+ // CORRECT - useFormControl returns object, destructuring OK
62
+ const { value, errors, disabled } = useFormControl(control.loanType);
63
+ ```