@reformer/core 11.0.0-beta.3 → 11.0.0-beta.5

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 (130) hide show
  1. package/dist/behaviors.d.ts +1 -1
  2. package/dist/behaviors.js +37 -37
  3. package/dist/create-model-Dxh9cfLp.js +97 -0
  4. package/dist/form/behaviors/collections.d.ts +50 -0
  5. package/dist/form/behaviors/context.d.ts +23 -0
  6. package/dist/form/behaviors/index.d.ts +28 -0
  7. package/dist/form/behaviors/internals.d.ts +26 -0
  8. package/dist/form/{behaviors-node.d.ts → behaviors/node.d.ts} +3 -3
  9. package/dist/form/behaviors/operators.d.ts +51 -0
  10. package/dist/form/behaviors/types.d.ts +17 -0
  11. package/dist/form/create-core-form.d.ts +3 -3
  12. package/dist/form/create-form.d.ts +12 -10
  13. package/dist/form/factories/node-factory.d.ts +17 -16
  14. package/dist/form/index.d.ts +10 -16
  15. package/dist/form/nodes/array-node.d.ts +3 -3
  16. package/dist/form/nodes/field-node.d.ts +6 -3
  17. package/dist/form/nodes/group-node.d.ts +7 -7
  18. package/dist/form/nodes/model-array-node.d.ts +3 -3
  19. package/dist/form/types/contracts.d.ts +9 -6
  20. package/dist/form/types/deep-schema.d.ts +5 -5
  21. package/dist/form/types/validation-schema.d.ts +1 -1
  22. package/dist/form/{validation-config.d.ts → validation/config.d.ts} +3 -3
  23. package/dist/form/validation/context.d.ts +27 -0
  24. package/dist/form/{error-handler.d.ts → validation/error-handler.d.ts} +1 -1
  25. package/dist/form/validation/index.d.ts +23 -28
  26. package/dist/form/validation/operators.d.ts +42 -0
  27. package/dist/form/validation/run.d.ts +45 -0
  28. package/dist/form/{validation-strategy.d.ts → validation/strategy.d.ts} +2 -2
  29. package/dist/form/validation/types.d.ts +28 -0
  30. package/dist/form/{validation/validators → validators}/date-utils.d.ts +1 -1
  31. package/dist/form/{validation/validators → validators}/email.d.ts +6 -7
  32. package/dist/form/{validation/validators → validators}/file-type.d.ts +6 -7
  33. package/dist/form/{validation/validators → validators}/file-utils.d.ts +1 -1
  34. package/dist/form/{validation/validators → validators}/future-date.d.ts +6 -7
  35. package/dist/form/{validation/validators → validators}/index.d.ts +7 -4
  36. package/dist/form/{validation/validators → validators}/integer.d.ts +6 -7
  37. package/dist/form/{validation/validators → validators}/is-date.d.ts +6 -7
  38. package/dist/form/{validation/validators → validators}/is-number.d.ts +6 -7
  39. package/dist/form/{validation/validators → validators}/max-age.d.ts +6 -7
  40. package/dist/form/{validation/validators → validators}/max-date.d.ts +6 -7
  41. package/dist/form/{validation/validators → validators}/max-file-size.d.ts +6 -7
  42. package/dist/form/{validation/validators → validators}/max-files.d.ts +6 -7
  43. package/dist/form/{validation/validators → validators}/max-length.d.ts +7 -8
  44. package/dist/form/{validation/validators → validators}/max-total-file-size.d.ts +6 -7
  45. package/dist/form/{validation/validators → validators}/max.d.ts +7 -8
  46. package/dist/form/{validation/validators → validators}/min-age.d.ts +6 -7
  47. package/dist/form/{validation/validators → validators}/min-date.d.ts +6 -7
  48. package/dist/form/{validation/validators → validators}/min-file-size.d.ts +6 -7
  49. package/dist/form/{validation/validators → validators}/min-files.d.ts +6 -7
  50. package/dist/form/{validation/validators → validators}/min-length.d.ts +7 -8
  51. package/dist/form/{validation/validators → validators}/min.d.ts +7 -8
  52. package/dist/form/{validation/validators → validators}/multiple-of.d.ts +6 -7
  53. package/dist/form/{validation/validators → validators}/non-negative.d.ts +6 -7
  54. package/dist/form/{validation/validators → validators}/non-zero.d.ts +6 -7
  55. package/dist/form/{validation/validators → validators}/past-date.d.ts +6 -7
  56. package/dist/form/{validation/validators → validators}/pattern.d.ts +7 -15
  57. package/dist/form/{validation/validators → validators}/phone.d.ts +6 -7
  58. package/dist/form/{validation/validators → validators}/required.d.ts +8 -13
  59. package/dist/form/{validation/validators → validators}/url.d.ts +8 -13
  60. package/dist/index.d.ts +7 -4
  61. package/dist/index.js +272 -94
  62. package/dist/{state/form-model.d.ts → model/create-model.d.ts} +0 -14
  63. package/dist/{state → model}/index.d.ts +10 -11
  64. package/dist/model/model-nodes.d.ts +60 -0
  65. package/dist/model/model-signals-proxy.d.ts +32 -0
  66. package/dist/model/model-value-proxy.d.ts +3 -0
  67. package/dist/{state → model}/safe-effect.d.ts +1 -1
  68. package/dist/model-signals-proxy-mV3WGdlO.js +233 -0
  69. package/dist/model.d.ts +2 -0
  70. package/dist/model.js +21 -0
  71. package/dist/{form → platforms/react}/hooks/types.d.ts +1 -1
  72. package/dist/{form → platforms/react}/hooks/use-form-bundle.d.ts +1 -1
  73. package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +3 -3
  74. package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +1 -1
  75. package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +3 -3
  76. package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +2 -2
  77. package/dist/platforms/react/index.d.ts +23 -0
  78. package/dist/signals.d.ts +1 -1
  79. package/dist/strategy-DA3JwxsR.js +170 -0
  80. package/dist/validation.d.ts +1 -1
  81. package/dist/validation.js +10 -165
  82. package/dist/validators/email.d.ts +1 -1
  83. package/dist/validators/future-date.d.ts +1 -1
  84. package/dist/validators/integer.d.ts +1 -1
  85. package/dist/validators/is-date.d.ts +1 -1
  86. package/dist/validators/is-number.d.ts +1 -1
  87. package/dist/validators/max-age.d.ts +1 -1
  88. package/dist/validators/max-date.d.ts +1 -1
  89. package/dist/validators/max-length.d.ts +1 -1
  90. package/dist/validators/max.d.ts +1 -1
  91. package/dist/validators/min-age.d.ts +1 -1
  92. package/dist/validators/min-date.d.ts +1 -1
  93. package/dist/validators/min-length.d.ts +1 -1
  94. package/dist/validators/min.d.ts +1 -1
  95. package/dist/validators/multiple-of.d.ts +1 -1
  96. package/dist/validators/non-negative.d.ts +1 -1
  97. package/dist/validators/non-zero.d.ts +1 -1
  98. package/dist/validators/past-date.d.ts +1 -1
  99. package/dist/validators/pattern.d.ts +1 -1
  100. package/dist/validators/phone.d.ts +1 -1
  101. package/dist/validators/required.d.ts +1 -1
  102. package/dist/validators/url.d.ts +1 -1
  103. package/dist/validators.d.ts +1 -1
  104. package/docs/llms/02-quick-start.md +6 -6
  105. package/docs/llms/03-api-signatures.md +3 -3
  106. package/docs/llms/05-common-mistakes.md +1 -1
  107. package/docs/llms/09-formschema.md +11 -11
  108. package/docs/llms/10-arrays.md +4 -4
  109. package/docs/llms/14-extended-mistakes.md +1 -1
  110. package/docs/llms/15-project-structure.md +8 -7
  111. package/docs/llms/16-ui-components.md +6 -6
  112. package/docs/llms/17-nonexistent-api.md +2 -2
  113. package/docs/llms/29-async-preload.md +4 -4
  114. package/docs/llms/30-type-safety-recipes.md +1 -1
  115. package/llms-index.json +1 -0
  116. package/llms.txt +2102 -262
  117. package/package.json +5 -4
  118. package/dist/form/behaviors.d.ts +0 -133
  119. package/dist/form/validation-schema.d.ts +0 -106
  120. package/dist/form/validators.d.ts +0 -11
  121. package/dist/form-model-DhW2HZOB.js +0 -323
  122. package/dist/state.d.ts +0 -2
  123. package/dist/state.js +0 -21
  124. package/dist/subscription-manager-705bls97.js +0 -171
  125. /package/dist/{state → form/nodes}/subscription-manager.d.ts +0 -0
  126. /package/dist/{state → model}/behaviors-value.d.ts +0 -0
  127. /package/dist/{state → model}/derived-registry.d.ts +0 -0
  128. /package/dist/{state → model}/types.d.ts +0 -0
  129. /package/dist/{behaviors-node-DGEFMTQu.js → node-DGEFMTQu.js} +0 -0
  130. /package/dist/{form → platforms/react}/hooks/useSignalSubscription.d.ts +0 -0
@@ -1,2 +1,2 @@
1
- export * from './form/behaviors'
1
+ export * from './form/behaviors/index'
2
2
  export {}
package/dist/behaviors.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { effect as j } from "@preact/signals-core";
2
- import { m as $, u as k, i as C } from "./form-model-DhW2HZOB.js";
3
- import { d as x, a as M, w as F, t as O, r as A, s as D, b as G } from "./behaviors-value-C7dSEhCc.js";
4
- import { e as T } from "./behaviors-node-DGEFMTQu.js";
2
+ import { d as $, a as C, w as F, t as M, r as O, s as A, b as D } from "./behaviors-value-C7dSEhCc.js";
3
+ import { i as G, m as k, u as x } from "./model-signals-proxy-mV3WGdlO.js";
4
+ import { e as T } from "./node-DGEFMTQu.js";
5
5
  let p = null;
6
6
  function P(e) {
7
7
  if (!p)
@@ -37,9 +37,9 @@ function h(e) {
37
37
  s(j(e));
38
38
  }
39
39
  function g(e) {
40
- x(e);
40
+ $(e);
41
41
  }
42
- const m = (e) => typeof e == "object" && e !== null && typeof e.peek == "function" && !C(e), B = (e) => Array.isArray(e) ? e : [e];
42
+ const m = (e) => typeof e == "object" && e !== null && typeof e.peek == "function" && !G(e), B = (e) => Array.isArray(e) ? e : [e];
43
43
  function E(e) {
44
44
  const t = {};
45
45
  for (const n of Object.keys(e)) {
@@ -54,26 +54,7 @@ function W(e, t) {
54
54
  m(o) ? o.value = t?.[n] : W(o, t?.[n] ?? {});
55
55
  }
56
56
  }
57
- function R(e) {
58
- if (!e) return;
59
- const { form: t } = y();
60
- return t.getFieldByPath(e);
61
- }
62
57
  function S(e) {
63
- return new Proxy(
64
- {},
65
- {
66
- get: (t, n) => {
67
- if (n === "$") return e;
68
- if (typeof n != "string") return;
69
- const o = e[n];
70
- if (o != null)
71
- return m(o) ? o.value : S(o);
72
- }
73
- }
74
- );
75
- }
76
- function V(e) {
77
58
  return (t) => {
78
59
  try {
79
60
  t();
@@ -88,9 +69,17 @@ function V(e) {
88
69
  }
89
70
  };
90
71
  }
72
+ function _(e, t) {
73
+ return t.split(".").reduce((n, o) => n?.[o], e);
74
+ }
75
+ function R(e) {
76
+ if (!e) return;
77
+ const { form: t } = y();
78
+ return t.getFieldByPath(e);
79
+ }
91
80
  function X(e, t, n) {
92
- $(e), s(() => k(e));
93
- const o = V(e);
81
+ k(e), s(() => x(e));
82
+ const o = S(e);
94
83
  h(() => {
95
84
  if (n?.when && !n.when()) return;
96
85
  const r = t();
@@ -100,8 +89,8 @@ function X(e, t, n) {
100
89
  });
101
90
  }
102
91
  function Y(e, t, n, o) {
103
- $(t), s(() => k(t));
104
- const r = V(t);
92
+ k(t), s(() => x(t));
93
+ const r = S(t);
105
94
  h(() => {
106
95
  const c = e.map((f) => f.value);
107
96
  if (o?.when && !o.when(...c)) return;
@@ -113,7 +102,7 @@ function Y(e, t, n, o) {
113
102
  }
114
103
  function Z(e, t, n) {
115
104
  if (m(e) && m(t)) {
116
- s(M(e, t, n));
105
+ s(C(e, t, n));
117
106
  return;
118
107
  }
119
108
  const o = e, r = t;
@@ -127,7 +116,7 @@ function q(e, t, n) {
127
116
  const r = (c) => {
128
117
  o?.abort(), o = new AbortController();
129
118
  const { signal: i } = o;
130
- x(() => t(c, { signal: i }));
119
+ $(() => t(c, { signal: i }));
131
120
  };
132
121
  if (!n?.debounce)
133
122
  s(F(e, r, { immediate: n?.immediate }));
@@ -163,19 +152,30 @@ function L(e, t, n) {
163
152
  });
164
153
  }
165
154
  function ne(e, t) {
166
- s(O(e, t));
155
+ s(M(e, t));
167
156
  }
168
157
  function te(e, t, n) {
169
- s(A(e, t, n));
158
+ s(O(e, t, n));
170
159
  }
171
160
  function oe(e, t, n) {
172
- s(D(e, t, n));
161
+ s(A(e, t, n));
173
162
  }
174
163
  function re(e, t) {
175
- s(G(e, t));
164
+ s(D(e, t));
176
165
  }
177
- function _(e, t) {
178
- return t.split(".").reduce((n, o) => n?.[o], e);
166
+ function V(e) {
167
+ return new Proxy(
168
+ {},
169
+ {
170
+ get: (t, n) => {
171
+ if (n === "$") return e;
172
+ if (typeof n != "string") return;
173
+ const o = e[n];
174
+ if (o != null)
175
+ return m(o) ? o.value : V(o);
176
+ }
177
+ }
178
+ );
179
179
  }
180
180
  function N(e) {
181
181
  return new Proxy(
@@ -276,7 +276,7 @@ function fe(e, t) {
276
276
  const r = o, c = r.__path;
277
277
  if (!c) continue;
278
278
  const i = n ? n.getFieldByPath(c) : void 0, f = i ? i.getProxy?.() ?? i : void 0;
279
- s(t.__run(S(r), f));
279
+ s(t.__run(V(r), f));
280
280
  }
281
281
  }
282
282
  export {
@@ -0,0 +1,97 @@
1
+ import { s as p, r as v, b as f, G as g } from "./model-signals-proxy-mV3WGdlO.js";
2
+ function a(t) {
3
+ if (t)
4
+ return t.kind === "group" ? d(t) : t.kind === "array" ? w(t) : t.read();
5
+ }
6
+ function w(t) {
7
+ const s = {
8
+ get length() {
9
+ return t.items.value.length;
10
+ },
11
+ push: (e) => t.push(e),
12
+ insertAt: (e, n) => t.insertAt(e, n),
13
+ removeAt: (e) => t.removeAt(e),
14
+ move: (e, n) => t.move(e, n),
15
+ swap: (e, n) => t.swap(e, n),
16
+ clear: () => t.clear(),
17
+ at: (e) => a(t.items.value[e]),
18
+ map: (e) => t.items.value.map((n, i) => e(a(n), i)),
19
+ forEach: (e) => t.items.value.forEach((n, i) => e(a(n), i)),
20
+ toArray: () => t.peek(),
21
+ [Symbol.iterator]: function* () {
22
+ const e = t.items.value;
23
+ for (let n = 0; n < e.length; n++) yield a(e[n]);
24
+ }
25
+ };
26
+ return new Proxy(s, {
27
+ get: (e, n, i) => n === "__path" ? t.path : typeof n == "string" && f(n) ? a(t.items.value[Number(n)]) : Reflect.get(e, n, i),
28
+ has: (e, n) => typeof n == "string" && f(n) ? Number(n) < t.items.value.length : Reflect.has(e, n)
29
+ });
30
+ }
31
+ const u = /* @__PURE__ */ new Set([
32
+ "$",
33
+ "get",
34
+ "set",
35
+ "patch",
36
+ "isDirty",
37
+ "reset",
38
+ "captureInitial",
39
+ "signalAt"
40
+ ]), h = /* @__PURE__ */ new WeakMap(), m = /* @__PURE__ */ new WeakMap();
41
+ function d(t) {
42
+ const s = h.get(t);
43
+ if (s) return s;
44
+ const e = {
45
+ $: p(t),
46
+ get: () => t.peek(),
47
+ set: (i) => t.set(i),
48
+ patch: (i) => t.set(i),
49
+ isDirty: () => t.dirty(),
50
+ reset: () => t.resetToInitial(),
51
+ captureInitial: () => t.captureInitial(),
52
+ signalAt: (i) => v(t, i)
53
+ }, n = new Proxy(
54
+ {},
55
+ {
56
+ get: (i, r) => {
57
+ if (r === "__path") return t.path;
58
+ if (typeof r != "string") return;
59
+ const c = t.children.get(r);
60
+ if (c) return a(c);
61
+ if (u.has(r)) return e[r];
62
+ },
63
+ set: (i, r, c) => {
64
+ if (typeof r != "string") return !1;
65
+ const o = t.children.get(r);
66
+ return o ? (o.set(c), !0) : !1;
67
+ },
68
+ has: (i, r) => typeof r == "string" && (t.children.has(r) || u.has(r)),
69
+ ownKeys: () => [...t.children.keys()],
70
+ getOwnPropertyDescriptor: (i, r) => typeof r == "string" && t.children.has(r) ? { enumerable: !0, configurable: !0 } : void 0
71
+ }
72
+ );
73
+ return h.set(t, n), m.set(n, t), n;
74
+ }
75
+ function l(t, s) {
76
+ if (t.kind === "leaf") {
77
+ s(t.signal);
78
+ return;
79
+ }
80
+ if (t.kind === "group") {
81
+ for (const e of t.children.values()) l(e, s);
82
+ return;
83
+ }
84
+ for (const e of t.items.value) l(e, s);
85
+ }
86
+ function A(t, s) {
87
+ const e = m.get(t);
88
+ e && l(e, s);
89
+ }
90
+ function _(t) {
91
+ const s = new g(t, "");
92
+ return d(s);
93
+ }
94
+ export {
95
+ _ as c,
96
+ A as e
97
+ };
@@ -0,0 +1,50 @@
1
+ import { Signal } from '@preact/signals-core';
2
+ import { FormModel } from '../../index';
3
+ import { FormBehavior } from './types';
4
+ /**
5
+ * Применить под-схему к КАЖДОМУ элементу динамического массива (per-item поведение).
6
+ * Реагирует на добавление/удаление строк: новым строкам поведение применяется, удалённым — отписывается.
7
+ *
8
+ * Под-схема получает scope строки: `model` (под-модель строки — `row.$.field`) и `form` (нода строки).
9
+ * - Value-операции (`compute`/`copyFrom`/`transformValue` на `row.$.*`) работают всегда.
10
+ * - Node-операции (`enableWhen`/`updateComponentProps`/`reset` через `form.*`) требуют, чтобы массив был
11
+ * МАТЕРИАЛИЗОВАН в форме (узел `{ array, item }` в схеме) — тогда `form` строки = та же нода, что
12
+ * рендерится, а её сигналы зарегистрированы (`enableWhen` резолвит ноду). Без материализации доступ
13
+ * к `form.*` бросит понятную ошибку (см. {@link unmaterializedRowForm}).
14
+ *
15
+ * @example
16
+ * applyEach(model.$.items, defineFormBehavior<Item>(({ model: row, form }) => {
17
+ * compute(row.$.lineTotal, () => row.qty * row.price); // value-op — всегда
18
+ * enableWhen(row.$.discount, () => row.qty > 10); // node-op — нужна материализация массива
19
+ * }));
20
+ */
21
+ export declare function applyEach<TItem>(array: object, itemSchema: FormBehavior<TItem>): void;
22
+ /**
23
+ * Взаимное исключение булева флага среди строк массива (single-selection — «единственный primary»).
24
+ * Когда флаг строки становится true, у остальных строк он сбрасывается в false. Не хрупок к push:
25
+ * новые строки с флагом false исключения не запускают.
26
+ *
27
+ * @example
28
+ * exclusiveFlag(model.$.contacts, (row) => row.$.primary);
29
+ */
30
+ export declare function exclusiveFlag<TItem>(array: object, getFlag: (row: FormModel<TItem>) => Signal<boolean>): void;
31
+ /**
32
+ * Агрегатная запись в строки массива. `derive(snapshot)` получает СНИМОК строк и возвращает список
33
+ * `{ index, patch }`, который применяется к строкам. Записи КОАЛЕСИРУЮТСЯ в один отложенный проход на
34
+ * финальном состоянии — поэтому массовые синхронные мутации (push в цикле) не вызывают каскад на
35
+ * промежуточных состояниях. `derive` должна сходиться (на фикспоинте возвращать те же значения).
36
+ *
37
+ * @example
38
+ * // последняя строка = 100 − Σ(остальные)
39
+ * aggregateInto(model.$.rows, (rows) => {
40
+ * const n = rows.length; if (n === 0) return [];
41
+ * const others = rows.slice(0, n - 1).reduce((s, r) => s + r.percent, 0);
42
+ * return [{ index: n - 1, patch: { percent: 100 - others } }];
43
+ * });
44
+ */
45
+ export declare function aggregateInto<TItem>(array: object, derive: (rows: TItem[]) => Array<{
46
+ index: number;
47
+ patch: Partial<TItem>;
48
+ }>): void;
49
+ /** Применить под-схему к одному или нескольким полям-группам (переиспользование). */
50
+ export declare function apply<TField>(targets: object | object[], subSchema: FormBehavior<TField>): void;
@@ -0,0 +1,23 @@
1
+ import { BehaviorCleanup } from '../../index';
2
+ import { BehaviorScope, FormBehavior } from './types';
3
+ /** Зарегистрировать произвольную отписку в активной схеме. */
4
+ export declare function onDispose(cleanup: BehaviorCleanup): void;
5
+ /** Текущий scope ({ model, form }) активной схемы (escape hatch для кросс-операторов). */
6
+ export declare function getScope<T>(): BehaviorScope<T>;
7
+ /**
8
+ * Описать поведение формы декларативно. Возвращает {@link FormBehavior} для `createForm({ behavior })`.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * export const myBehavior = defineFormBehavior<MyForm>(({ model, form }) => {
13
+ * compute(model.$.total, () => model.price * model.qty);
14
+ * enableWhen([model.$.city], () => Boolean(model.country));
15
+ * onChange(model.$.country, async (c) => form.city.updateComponentProps({ options: await load(c) }));
16
+ * });
17
+ * ```
18
+ */
19
+ export declare function defineFormBehavior<T>(setup: (scope: BehaviorScope<T>) => void): FormBehavior<T>;
20
+ /** Реактивный эффект (авто-dispose). Колбэк может вернуть собственный cleanup. */
21
+ export declare function effect(fn: () => void | (() => void)): void;
22
+ /** Отложенная запись вне effect-контекста (микротаск) — защита от «Cycle detected». */
23
+ export declare function defer(fn: () => void): void;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Декларативный контракт схемы поведения формы — `@reformer/core/behaviors`.
3
+ *
4
+ * Свободные операторы (`compute`/`copyFrom`/`enableWhen`/`onChange`/…) вызываются внутри
5
+ * `defineFormBehavior(({ model, form }) => { … })` и САМИ регистрируют свои отписки в ambient-стоке —
6
+ * автор схемы не видит ни массива `cleanups`, ни `.push`, ни вызовов через точку. Жизненным циклом
7
+ * владеет форма (`createForm({ behavior })`). Операторы — тонкие обёртки над примитивами слоя данных
8
+ * ({@link module:model/behaviors-value}); пользовательские операторы пишутся так же и неотличимы от встроенных.
9
+ *
10
+ * Граница ответственности: контракт НЕ управляет валидацией — это отдельный слой
11
+ * (`defineValidationSchema` + раннер `validateModel` из `@reformer/core/validation`), он остаётся
12
+ * владельцем `errors`. Это сделано намеренно. Для редких крайних случаев
13
+ * behavior-driven валидации (cross-field, async-uniqueness) можно написать собственный оператор поверх
14
+ * `effect`/`onChange` + `node.setErrors(...)` — он будет неотличим от встроенного; примеры см. в
15
+ * tests/behaviors/web-scenarios (W1/W3). В общем случае правила валидности держите в слое валидации.
16
+ *
17
+ * Модуль разложен по зонам ответственности: `./types` — контракт схемы, `./context` — ambient-сток
18
+ * и низкоуровневый набор авторинга, `./internals` — чистые утилиты, `./operators` — операторы над
19
+ * полем и группой, `./collections` — операторы над массивами и под-моделями.
20
+ *
21
+ * @group Behaviors
22
+ * @module form/behaviors
23
+ */
24
+ export type { BehaviorScope, FormBehavior, ChangeContext } from './types';
25
+ export type { Signal, ReadonlySignal, BehaviorCleanup } from './types';
26
+ export { onDispose, getScope, defineFormBehavior, effect, defer } from './context';
27
+ export { compute, computeFrom, copyFrom, onChange, enableWhen, disableWhen, transformValue, resetWhen, syncFields, revalidateWhen, } from './operators';
28
+ export { applyEach, exclusiveFlag, aggregateInto, apply } from './collections';
@@ -0,0 +1,26 @@
1
+ import { Signal } from '@preact/signals-core';
2
+ /** Групповой узел дерева `$`: набор дочерних сигналов плюс служебный путь. */
3
+ export type GroupSignals = Record<string, unknown> & {
4
+ __path?: string;
5
+ };
6
+ /**
7
+ * Лист модели, а не контейнерный узел дерева `$`.
8
+ *
9
+ * Узлы-группы/массивы тоже структурно совместимы с `ReadonlySignal` (у них есть `peek`/`value`/
10
+ * `subscribe` над агрегатом поддерева), поэтому одного duck-typing по `peek` мало: без отсечки по
11
+ * `isModelContainerSignal` группа уходила бы в скалярные ветки операторов — `enableWhen` молча
12
+ * терял бы `enableGroup`, `apply` получал бы значение вместо под-модели, а `copyFrom` — запись в
13
+ * read-only агрегат вместо рекурсивного `writeGroup`.
14
+ */
15
+ export declare const isLeafSignal: (v: unknown) => v is Signal<unknown>;
16
+ export declare const asArray: <X>(v: X | X[]) => X[];
17
+ export declare function readGroup(g: GroupSignals): Record<string, unknown>;
18
+ export declare function writeGroup(g: GroupSignals, val: Record<string, unknown>): void;
19
+ /**
20
+ * Guard от расходящихся циклов пересчёта (F7). Расходящийся взаимный compute/computeFrom (без
21
+ * стабилизации) preact обрывает невнятным «Cycle detected» — перехватываем и заменяем понятной
22
+ * ошибкой с именем поля и подсказкой. Сходящиеся compute (упираются в peek-guard) и массовые
23
+ * синхронные мутации цикла не порождают → не затрагиваются.
24
+ */
25
+ export declare function makeCycleGuard(target: Signal<unknown>): (write: () => void) => void;
26
+ export declare function getByPath(root: unknown, path: string): any;
@@ -1,5 +1,5 @@
1
1
  import { ReadonlySignal } from '@preact/signals-core';
2
- import { BehaviorCleanup } from '../state/behaviors-value';
2
+ import { BehaviorCleanup } from '../../model/behaviors-value';
3
3
  /**
4
4
  * Условное включение поля (state-операция). Резолвит ноду по сигналу-цели через реестр
5
5
  * сигнал→нода (заполняется `createForm`) и вызывает `enable()`/`disable()` (+`reset()` при
@@ -18,7 +18,7 @@ import { BehaviorCleanup } from '../state/behaviors-value';
18
18
  * включают disabled-поля, мусор утечёт в payload. Это аналог правила #13 гайда add-behavior,
19
19
  * которое сегодня сформулировано только для `copyFrom`; для `compute` то же ограничение обязательно.
20
20
  *
21
- * @group Model
21
+ * @group Form
22
22
  * @example
23
23
  * ```typescript
24
24
  * enableWhen(model.$.propertyValue, () => model.loanType === 'mortgage', { resetOnDisable: true });
@@ -38,7 +38,7 @@ export declare function enableWhen(target: ReadonlySignal<unknown>, condition: (
38
38
  * сигнал→нода и вызывает `disable()`, когда `condition` истинно (+`reset()` при `resetOnDisable`).
39
39
  * `condition` реактивен (читает свои сигналы модели).
40
40
  *
41
- * @group Model
41
+ * @group Form
42
42
  * @param target Сигнал-цель поля (`model.$.<path>`).
43
43
  * @param condition Реактивное условие; при `true` поле выключается.
44
44
  * @param options.resetOnDisable Сбросить значение при выключении (по умолчанию `false`).
@@ -0,0 +1,51 @@
1
+ import { Signal, ReadonlySignal } from '@preact/signals-core';
2
+ import { ChangeContext } from './types';
3
+ /** Вычисляемое поле с auto-tracking: `target = read()` при изменении прочитанных сигналов. */
4
+ export declare function compute<R>(target: Signal<R>, read: () => R, options?: {
5
+ when?: () => boolean;
6
+ }): void;
7
+ /** Вычисляемое поле с явным списком зависимостей (escape hatch). */
8
+ export declare function computeFrom<R>(sources: ReadonlySignal<unknown>[], target: Signal<R>, fn: (...values: any[]) => R, options?: {
9
+ when?: (...values: any[]) => boolean;
10
+ }): void;
11
+ /** Копирование `source → target` — скаляр или группа (объект целиком). */
12
+ export declare function copyFrom<T>(source: ReadonlySignal<T> | object, target: Signal<T> | object, options?: {
13
+ when?: () => boolean;
14
+ transform?: (value: T) => T;
15
+ }): void;
16
+ /**
17
+ * Реакция на изменение поля; `{ debounce, immediate }`.
18
+ *
19
+ * Колбэк выполняется ВНЕ effect-контекста (микротаск/таймер) — можно безопасно писать сигналы и ноды
20
+ * (`updateComponentProps`/`reset`/`clear`) без ручного `defer` и без «Cycle detected».
21
+ *
22
+ * Для async-колбэков 2-м аргументом приходит `{ signal }` (AbortSignal): при следующей смене значения
23
+ * предыдущий `signal` аннулируется. Передавай его в `fetch` (сетевая отмена) или проверяй
24
+ * `signal.aborted` перед применением результата — это убирает гонки устаревших ответов (F2).
25
+ */
26
+ export declare function onChange<T>(source: ReadonlySignal<T>, cb: (value: T, ctx: ChangeContext) => void, options?: {
27
+ immediate?: boolean;
28
+ debounce?: number;
29
+ }): void;
30
+ type EnableTarget = ReadonlySignal<unknown> | object;
31
+ /** Условное включение поля(ей) — скаляр, массив целей или группа. `resetOnDisable=false` по умолчанию. */
32
+ export declare function enableWhen(target: EnableTarget | EnableTarget[], condition: () => boolean, options?: {
33
+ resetOnDisable?: boolean;
34
+ }): void;
35
+ /** Условное выключение поля(ей) (инверсия {@link enableWhen}). */
36
+ export declare function disableWhen(target: EnableTarget | EnableTarget[], condition: () => boolean, options?: {
37
+ resetOnDisable?: boolean;
38
+ }): void;
39
+ /** Трансформация значения поля (идемпотентная). */
40
+ export declare function transformValue<T>(target: Signal<T>, transformer: (value: T) => T): void;
41
+ /** Сброс значения по условию. */
42
+ export declare function resetWhen<T>(target: Signal<T>, condition: () => boolean, options?: {
43
+ resetValue?: T;
44
+ }): void;
45
+ /** Двусторонняя синхронизация двух полей. */
46
+ export declare function syncFields<T>(a: Signal<T>, b: Signal<T>, options?: {
47
+ transform?: (value: T) => T;
48
+ }): void;
49
+ /** Ревалидация при изменении зависимостей. */
50
+ export declare function revalidateWhen(deps: ReadonlySignal<unknown>[], revalidate: () => void): void;
51
+ export {};
@@ -0,0 +1,17 @@
1
+ import { Signal, ReadonlySignal } from '@preact/signals-core';
2
+ import { BehaviorCleanup, FormModel, FormProxy } from '../../index';
3
+ /** Контекст схемы поведения: модель (значения/сигналы) + форма (ноды). */
4
+ export type BehaviorScope<T> = {
5
+ model: FormModel<T>;
6
+ form: FormProxy<T>;
7
+ };
8
+ /** Результат {@link defineFormBehavior}; передаётся в `createForm({ behavior })`. */
9
+ export interface FormBehavior<T> {
10
+ /** @internal Запускается `createForm` после построения нод и заполнения реестра сигнал→нода. */
11
+ __run(model: FormModel<T>, form: FormProxy<T>): BehaviorCleanup;
12
+ }
13
+ export type { Signal, ReadonlySignal, BehaviorCleanup };
14
+ /** Контекст async-реакции: `signal` аннулируется, когда поле меняется снова до завершения колбэка. */
15
+ export interface ChangeContext {
16
+ signal: AbortSignal;
17
+ }
@@ -1,9 +1,9 @@
1
- import { FormModel } from '../state/types';
1
+ import { FormModel } from '../model/types';
2
2
  import { FormSchemaNode } from './types/schema-node';
3
3
  import { FormBehavior } from './behaviors';
4
4
  import { FormProxy } from './types/index';
5
- import { FormValidation, FormValidationBundle } from './validation-config';
6
- import { ValidationSchema } from './validation-schema';
5
+ import { FormValidation, FormValidationBundle } from './validation/config';
6
+ import { ValidationSchema } from './validation';
7
7
  /**
8
8
  * Общая часть конфига всех фабрик формы.
9
9
  *
@@ -1,10 +1,10 @@
1
1
  import { FormProxy, GroupNodeConfig, FormSchema } from './types/index';
2
2
  import { FormSchemaNode } from './types/schema-node';
3
- import { FormModel } from '../state/types';
3
+ import { FormModel } from '../model/types';
4
4
  import { FormBehavior } from './behaviors';
5
5
  /**
6
6
  * Аргументы createForm под архитектуру M1: данные приходят из {@link FormModel},
7
- * конфиг полей (component/componentProps/validators) — из единой схемы.
7
+ * конфиг полей (component/componentProps) — из единой схемы.
8
8
  *
9
9
  * @group Utilities
10
10
  */
@@ -27,7 +27,7 @@ export interface CreateFormFromModelArgs<T> {
27
27
  *
28
28
  * Значения принадлежат модели (источник истины), ноды формы держат UI/валидационное состояние и
29
29
  * ссылаются на сигналы модели по идентичности (`node.value === model.$.path`). Обходит структуру
30
- * модели, привязывает конфиг поля (component/componentProps/validators) из схемы, материализует
30
+ * модели, привязывает конфиг поля (component/componentProps) из схемы, материализует
31
31
  * top-level массивы как {@link ModelArrayNode}, заполняет реестр сигнал→нода (для `enableWhen`/
32
32
  * роутинга ошибок) и, при наличии, запускает декларативное поведение (cleanup живёт на форме).
33
33
  *
@@ -51,9 +51,10 @@ export interface CreateFormFromModelArgs<T> {
51
51
  * const schema = {
52
52
  * component: Section,
53
53
  * children: [
54
- * { value: model.$.email, component: Input, validators: [required] },
54
+ * // Layout несёт только component/componentProps; правила — в отдельной ValidationSchema.
55
+ * { value: model.$.email, component: InputField },
55
56
  * // вложенная группа: `model.$.profile.name` (≡ под-модель `model.profile.$.name` — тот же сигнал)
56
- * { value: model.$.profile.name, component: Input },
57
+ * { value: model.$.profile.name, component: InputField },
57
58
  * ],
58
59
  * };
59
60
  *
@@ -92,8 +93,8 @@ export declare function createFormFromModel<T>(args: CreateFormFromModelArgs<T>)
92
93
  * // Layout-схема НЕ несёт validators — только component/componentProps.
93
94
  * const schema = {
94
95
  * children: [
95
- * { value: model.$.email, component: Input },
96
- * { value: model.$.password, component: Input },
96
+ * { value: model.$.email, component: InputField },
97
+ * { value: model.$.password, component: InputField },
97
98
  * ],
98
99
  * };
99
100
  *
@@ -115,7 +116,8 @@ export declare function createForm<T>(config: GroupNodeConfig<T>): FormProxy<T>;
115
116
  *
116
117
  * @deprecated Legacy / back-compat: плоская `FormSchema` с инлайн-значениями (`value: ''`) — путь
117
118
  * ДО архитектуры M1. Для нового кода используйте перегрузку `createForm({ model, schema })`
118
- * (значения принадлежат {@link FormModel}, валидаторы — фабрики в `validators: [...]`).
119
+ * (значения принадлежат {@link FormModel}, а правила — отдельной схеме `defineValidationSchema`,
120
+ * которую прогоняет `validateModel`; поля `validators` у узла layout-схемы нет).
119
121
  *
120
122
  * @param schema - Схема полей формы
121
123
  * @returns Типизированная форма с Proxy-доступом к полям
@@ -123,8 +125,8 @@ export declare function createForm<T>(config: GroupNodeConfig<T>): FormProxy<T>;
123
125
  * @example Legacy (back-compat) — плоская схема без модели
124
126
  * ```typescript
125
127
  * const form = createForm<UserForm>({
126
- * email: { value: '', component: Input },
127
- * password: { value: '', component: Input },
128
+ * email: { value: '', component: InputField },
129
+ * password: { value: '', component: InputField },
128
130
  * });
129
131
  * ```
130
132
  */