@reformer/core 11.0.0-beta.2 → 11.0.0-beta.4

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 (115) 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 +70 -0
  12. package/dist/form/create-form.d.ts +1 -1
  13. package/dist/form/index.d.ts +12 -12
  14. package/dist/form/nodes/model-array-node.d.ts +1 -1
  15. package/dist/form/types/contracts.d.ts +9 -6
  16. package/dist/form/types/validation-schema.d.ts +1 -1
  17. package/dist/form/validation/config.d.ts +73 -0
  18. package/dist/form/validation/context.d.ts +27 -0
  19. package/dist/form/{error-handler.d.ts → validation/error-handler.d.ts} +1 -1
  20. package/dist/form/validation/index.d.ts +23 -28
  21. package/dist/form/validation/operators.d.ts +42 -0
  22. package/dist/form/validation/run.d.ts +45 -0
  23. package/dist/form/{validation-strategy.d.ts → validation/strategy.d.ts} +10 -4
  24. package/dist/form/validation/types.d.ts +28 -0
  25. package/dist/form/{validation/validators → validators}/date-utils.d.ts +1 -1
  26. package/dist/form/{validation/validators → validators}/email.d.ts +1 -1
  27. package/dist/form/{validation/validators → validators}/file-type.d.ts +1 -1
  28. package/dist/form/{validation/validators → validators}/file-utils.d.ts +1 -1
  29. package/dist/form/{validation/validators → validators}/future-date.d.ts +1 -1
  30. package/dist/form/{validation/validators → validators}/index.d.ts +6 -4
  31. package/dist/form/{validation/validators → validators}/integer.d.ts +1 -1
  32. package/dist/form/{validation/validators → validators}/is-date.d.ts +1 -1
  33. package/dist/form/{validation/validators → validators}/is-number.d.ts +1 -1
  34. package/dist/form/{validation/validators → validators}/max-age.d.ts +1 -1
  35. package/dist/form/{validation/validators → validators}/max-date.d.ts +1 -1
  36. package/dist/form/{validation/validators → validators}/max-file-size.d.ts +1 -1
  37. package/dist/form/{validation/validators → validators}/max-files.d.ts +1 -1
  38. package/dist/form/{validation/validators → validators}/max-length.d.ts +1 -1
  39. package/dist/form/{validation/validators → validators}/max-total-file-size.d.ts +1 -1
  40. package/dist/form/{validation/validators → validators}/max.d.ts +1 -1
  41. package/dist/form/{validation/validators → validators}/min-age.d.ts +1 -1
  42. package/dist/form/{validation/validators → validators}/min-date.d.ts +1 -1
  43. package/dist/form/{validation/validators → validators}/min-file-size.d.ts +1 -1
  44. package/dist/form/{validation/validators → validators}/min-files.d.ts +1 -1
  45. package/dist/form/{validation/validators → validators}/min-length.d.ts +1 -1
  46. package/dist/form/{validation/validators → validators}/min.d.ts +1 -1
  47. package/dist/form/{validation/validators → validators}/multiple-of.d.ts +1 -1
  48. package/dist/form/{validation/validators → validators}/non-negative.d.ts +1 -1
  49. package/dist/form/{validation/validators → validators}/non-zero.d.ts +1 -1
  50. package/dist/form/{validation/validators → validators}/past-date.d.ts +1 -1
  51. package/dist/form/{validation/validators → validators}/pattern.d.ts +1 -1
  52. package/dist/form/{validation/validators → validators}/phone.d.ts +1 -1
  53. package/dist/form/{validation/validators → validators}/required.d.ts +1 -1
  54. package/dist/form/{validation/validators → validators}/url.d.ts +1 -1
  55. package/dist/index.d.ts +7 -4
  56. package/dist/index.js +708 -479
  57. package/dist/{state/form-model.d.ts → model/create-model.d.ts} +0 -14
  58. package/dist/{state → model}/index.d.ts +10 -11
  59. package/dist/model/model-nodes.d.ts +60 -0
  60. package/dist/model/model-signals-proxy.d.ts +32 -0
  61. package/dist/model/model-value-proxy.d.ts +3 -0
  62. package/dist/{state → model}/safe-effect.d.ts +1 -1
  63. package/dist/model-signals-proxy-mV3WGdlO.js +233 -0
  64. package/dist/model.d.ts +2 -0
  65. package/dist/model.js +21 -0
  66. package/dist/{form → platforms/react}/hooks/types.d.ts +1 -1
  67. package/dist/platforms/react/hooks/use-form-bundle.d.ts +26 -0
  68. package/dist/{form → platforms/react}/hooks/use-form-validation.d.ts +3 -3
  69. package/dist/{form → platforms/react}/hooks/useArrayLength.d.ts +1 -1
  70. package/dist/{form → platforms/react}/hooks/useFormControl.d.ts +3 -3
  71. package/dist/{form → platforms/react}/hooks/useFormControlValue.d.ts +2 -2
  72. package/dist/platforms/react/index.d.ts +23 -0
  73. package/dist/signals.d.ts +1 -1
  74. package/dist/strategy-DA3JwxsR.js +170 -0
  75. package/dist/validation.d.ts +1 -1
  76. package/dist/validation.js +10 -165
  77. package/dist/validators/email.d.ts +1 -1
  78. package/dist/validators/future-date.d.ts +1 -1
  79. package/dist/validators/integer.d.ts +1 -1
  80. package/dist/validators/is-date.d.ts +1 -1
  81. package/dist/validators/is-number.d.ts +1 -1
  82. package/dist/validators/max-age.d.ts +1 -1
  83. package/dist/validators/max-date.d.ts +1 -1
  84. package/dist/validators/max-length.d.ts +1 -1
  85. package/dist/validators/max.d.ts +1 -1
  86. package/dist/validators/min-age.d.ts +1 -1
  87. package/dist/validators/min-date.d.ts +1 -1
  88. package/dist/validators/min-length.d.ts +1 -1
  89. package/dist/validators/min.d.ts +1 -1
  90. package/dist/validators/multiple-of.d.ts +1 -1
  91. package/dist/validators/non-negative.d.ts +1 -1
  92. package/dist/validators/non-zero.d.ts +1 -1
  93. package/dist/validators/past-date.d.ts +1 -1
  94. package/dist/validators/pattern.d.ts +1 -1
  95. package/dist/validators/phone.d.ts +1 -1
  96. package/dist/validators/required.d.ts +1 -1
  97. package/dist/validators/url.d.ts +1 -1
  98. package/dist/validators.d.ts +1 -1
  99. package/docs/llms/02-quick-start.md +14 -7
  100. package/docs/llms/15-project-structure.md +12 -8
  101. package/llms.txt +329 -53
  102. package/package.json +4 -4
  103. package/dist/form/behaviors.d.ts +0 -133
  104. package/dist/form/validation-schema.d.ts +0 -106
  105. package/dist/form/validators.d.ts +0 -11
  106. package/dist/form-model-DhW2HZOB.js +0 -323
  107. package/dist/state.d.ts +0 -2
  108. package/dist/state.js +0 -21
  109. package/dist/subscription-manager-705bls97.js +0 -171
  110. /package/dist/{state → form/nodes}/subscription-manager.d.ts +0 -0
  111. /package/dist/{state → model}/behaviors-value.d.ts +0 -0
  112. /package/dist/{state → model}/derived-registry.d.ts +0 -0
  113. /package/dist/{state → model}/types.d.ts +0 -0
  114. /package/dist/{behaviors-node-DGEFMTQu.js → node-DGEFMTQu.js} +0 -0
  115. /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
+ }
@@ -0,0 +1,70 @@
1
+ import { FormModel } from '../model/types';
2
+ import { FormSchemaNode } from './types/schema-node';
3
+ import { FormBehavior } from './behaviors';
4
+ import { FormProxy } from './types/index';
5
+ import { FormValidation, FormValidationBundle } from './validation/config';
6
+ import { ValidationSchema } from './validation';
7
+ /**
8
+ * Общая часть конфига всех фабрик формы.
9
+ *
10
+ * @typeParam T - Форма данных модели.
11
+ * @typeParam B - Бандл конкретной фабрики (его получает {@link CreateFormConfigBase.setup}).
12
+ */
13
+ export interface CreateFormConfigBase<T, B> {
14
+ /** Начальные значения — из них создаётся модель. Взаимоисключимо с `model`. */
15
+ initial?: T;
16
+ /** Готовая модель (если создана отдельной фабрикой). Приоритетнее `initial`. */
17
+ model?: FormModel<T>;
18
+ /** Декларативное поведение модели (compute/copyFrom/enableWhen/onChange). */
19
+ behavior?: FormBehavior<T>;
20
+ /** Правила валидации: готовая схема либо {@link FormValidation} со стратегией и шагами. */
21
+ validation?: FormValidation<T> | ValidationSchema<T>;
22
+ /**
23
+ * Правка модели ДО сборки формы. Нужна там, где значение обязано существовать к моменту
24
+ * построения нод — например, массив, наполняемый из вычислений (реактивные `onChange` на
25
+ * инициализации не срабатывают).
26
+ */
27
+ seed?: (model: FormModel<T>) => void;
28
+ /**
29
+ * Донастройка ПОСЛЕ сборки. Единственное место для правок, которые обязаны идти следом за
30
+ * `createForm`: поле, чьё значение уже массив, ноды не получает (см. `buildModelConfig`),
31
+ * поэтому такой префилл выполняется здесь.
32
+ */
33
+ setup?: (bundle: B) => void;
34
+ }
35
+ /** Результат {@link createCoreForm}: модель, форма и (если заданы правила) собранная валидация. */
36
+ export interface CoreForm<T> {
37
+ model: FormModel<T>;
38
+ form: FormProxy<T>;
39
+ validation?: FormValidationBundle<T>;
40
+ }
41
+ /** Конфиг {@link createCoreForm}. */
42
+ export interface CreateCoreFormConfig<T> extends CreateFormConfigBase<T, CoreForm<T>> {
43
+ /**
44
+ * Билдер схемы формы. Именно функция, а не готовое дерево: листья схемы держат сами сигналы
45
+ * модели (`value: model.$.email`), поэтому построить дерево до модели нечем.
46
+ */
47
+ schema?: (model: FormModel<T>) => FormSchemaNode;
48
+ }
49
+ /**
50
+ * Собрать модель, форму и валидацию за один проход.
51
+ *
52
+ * @typeParam T - Форма данных модели.
53
+ * @param config - {@link CreateCoreFormConfig}: (`initial` | `model`) + опц. `schema`, `behavior`,
54
+ * `validation`, `seed`, `setup`.
55
+ * @returns {@link CoreForm}` <T>` — `{ model, form, validation? }`.
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * const credit = useFormBundle(() =>
60
+ * createCoreForm<CreditForm>({
61
+ * model: createCreditModel(),
62
+ * schema: buildCreditSchema,
63
+ * behavior: creditBehavior,
64
+ * validation: { steps: { loan: loanRules, confirm: null }, extras: crossRules },
65
+ * })
66
+ * );
67
+ * return <FormWizard form={credit.form} config={credit.validation} steps={STEPS} />;
68
+ * ```
69
+ */
70
+ export declare function createCoreForm<T extends object>(config: CreateCoreFormConfig<T>): CoreForm<T>;
@@ -1,6 +1,6 @@
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},
@@ -1,10 +1,11 @@
1
1
  /**
2
- * Модуль `form` — доменный слой формы поверх state-субстрата (`model`).
2
+ * Модуль `form` — доменный слой формы поверх реактивной модели (`model`).
3
3
  *
4
4
  * Ноды (value/touched/dirty/status/errors/componentProps), сборка формы из модели по идентичности
5
- * сигнала (`createForm`), state-операции над нодами (`enableWhen`/`disableWhen`), submit/статус,
6
- * React-хуки. Schema-валидация — внешний контракт `@reformer/core/validation` (`validateModel`),
7
- * роутит ошибки в ноды через реестр сигнал→нода. Зависит от `model` (form→state разрешено);
5
+ * сигнала (`createForm`), state-операции над нодами (`enableWhen`/`disableWhen`), submit/статус.
6
+ * React-биндинги вынесены в `platforms/react` — этот слой от React не зависит.
7
+ * Schema-валидация — внешний контракт `@reformer/core/validation` (`validateModel`),
8
+ * роутит ошибки в ноды через реестр сигнал→нода. Зависит от `model` (form→model разрешено);
8
9
  * обратной зависимости нет.
9
10
  *
10
11
  * @group Form
@@ -19,15 +20,14 @@ export { ModelArrayNode } from './nodes/model-array-node';
19
20
  export type { ModelArrayControl } from './nodes/model-array-node';
20
21
  export { createForm, createFormFromModel } from './create-form';
21
22
  export type { CreateFormFromModelArgs } from './create-form';
22
- export { enableWhen, disableWhen } from './behaviors-node';
23
- export { FormErrorHandler, ErrorStrategy } from './error-handler';
23
+ export { createCoreForm } from './create-core-form';
24
+ export type { CoreForm, CreateCoreFormConfig, CreateFormConfigBase } from './create-core-form';
25
+ export { buildValidation } from './validation/config';
26
+ export type { FormValidation, FormValidationBundle } from './validation/config';
27
+ export { enableWhen, disableWhen } from './behaviors/node';
28
+ export { FormErrorHandler, ErrorStrategy } from './validation/error-handler';
24
29
  export { registerSignalNode, getNodeForSignal } from './signal-node-registry';
25
30
  export { FormSubmitter } from './form-submitter';
31
+ export { SubscriptionManager } from './nodes/subscription-manager';
26
32
  export type { SubmittableForm, SubmitOptions, SubmitResult } from './form-submitter';
27
33
  export { isFormNode, isFieldNode, isGroupNode, isArrayNode, getNodeType } from './type-guards';
28
- export { useFormControl } from './hooks/useFormControl';
29
- export { useFormControlValue } from './hooks/useFormControlValue';
30
- export { useArrayLength } from './hooks/useArrayLength';
31
- export { useFormValidation } from './hooks/use-form-validation';
32
- export type { UseFormValidationArgs, UseFormValidationResult } from './hooks/use-form-validation';
33
- export type { FieldControlState, ArrayControlState } from './hooks/types';
@@ -1,7 +1,7 @@
1
1
  import { Signal, ReadonlySignal } from '@preact/signals-core';
2
2
  import { FormNode } from './form-node';
3
3
  import { FieldStatus, ValidationError } from '../types/index';
4
- import { FormModel } from '../../state/types';
4
+ import { FormModel } from '../../model/types';
5
5
  import { FormProxy } from '../types/form-proxy';
6
6
  /** Минимальный контракт реактивного массива модели, используемый узлом. */
7
7
  export interface ModelArrayControl<TItem extends object> {
@@ -1,13 +1,16 @@
1
1
  /**
2
- * Общие контракт-типы значения и валидации (state-слой).
2
+ * Общие контракт-типы значения и валидации.
3
3
  *
4
- * Эти типы нужны и headless-движку валидации данных (`validate-model-core`, `schema-node`,
5
- * `error-handler` — state), и form-слою. Живут в `model/`, поэтому state их импортирует напрямую,
6
- * а form-слой реэкспортирует из `form/types` (form→state разрешено). Так граница state⇏form не
7
- * нарушается: общие контракты принадлежат нижнему слою.
4
+ * Базовый словарь «значение + ошибка + статус», на который опираются узлы формы
5
+ * (`FormNode.getErrors`, `FieldNode`, `FormStatusMachine`, `aggregate-signals`), слой валидации
6
+ * и платформенные биндинги. Файл не импортирует ничего — это лист графа зависимостей.
7
+ *
8
+ * Живёт в `form/types/`, а не в слое модели: `FieldStatus`/`ErrorFilterOptions` — понятия формы,
9
+ * а модель этих типов не использует (и импортировать их оттуда запрещено границей model⇏form).
10
+ * Наружу отдаётся через `form/types/index`.
8
11
  *
9
12
  * @group Types
10
- * @module model/contracts
13
+ * @module form/types/contracts
11
14
  */
12
15
  /**
13
16
  * Represents any valid form value type
@@ -1,5 +1,5 @@
1
1
  import { FormValue, ValidationError } from './index';
2
- import { FormModel } from '../../state/types';
2
+ import { FormModel } from '../../model/types';
3
3
  /**
4
4
  * Чистый синхронный валидатор поля (legacy-сигнатура `(value, scope, root)`).
5
5
  *