@reformer/core 11.0.0-beta.8 → 11.0.0-beta.9

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 (224) hide show
  1. package/README.md +91 -73
  2. package/dist/behaviors-value-HA_Q03hc.js +108 -0
  3. package/dist/behaviors.js +370 -242
  4. package/dist/{create-model-BBRHhHwK.js → create-model-BvcbNPgL.js} +6 -6
  5. package/dist/date-utils-90-qZbse.js +43 -0
  6. package/dist/{en-CjAqn7h0.js → en-HP8XEych.js} +6 -3
  7. package/dist/form/aggregate-signals.d.ts +21 -63
  8. package/dist/form/behaviors/collections.d.ts +28 -7
  9. package/dist/form/behaviors/context.d.ts +19 -2
  10. package/dist/form/behaviors/index.d.ts +3 -3
  11. package/dist/form/behaviors/internals.d.ts +9 -2
  12. package/dist/form/behaviors/node.d.ts +25 -2
  13. package/dist/form/behaviors/operators.d.ts +17 -9
  14. package/dist/form/behaviors/schema-operators.d.ts +6 -4
  15. package/dist/form/behaviors/types.d.ts +12 -4
  16. package/dist/form/create-form.d.ts +47 -35
  17. package/dist/form/flow/flow.d.ts +87 -0
  18. package/dist/form/form-bundle.d.ts +45 -28
  19. package/dist/form/form-config-base.d.ts +46 -0
  20. package/dist/form/form-level.d.ts +121 -0
  21. package/dist/form/form-proxy-builder.d.ts +6 -22
  22. package/dist/form/index.d.ts +16 -21
  23. package/dist/form/nodes/form-array-node.d.ts +100 -0
  24. package/dist/form/nodes/form-control-node.d.ts +73 -0
  25. package/dist/form/nodes/form-group-node.d.ts +66 -0
  26. package/dist/form/nodes/node-kind.d.ts +29 -0
  27. package/dist/form/nodes/state/availability.d.ts +6 -0
  28. package/dist/form/nodes/state/component.d.ts +6 -0
  29. package/dist/form/nodes/state/index.d.ts +12 -0
  30. package/dist/form/nodes/state/interaction.d.ts +13 -0
  31. package/dist/form/nodes/state/issues.d.ts +9 -0
  32. package/dist/form/nodes/state/pending.d.ts +6 -0
  33. package/dist/form/schema/compile.d.ts +59 -0
  34. package/dist/form/schema/compiled.d.ts +143 -0
  35. package/dist/form/schema/row-key.d.ts +11 -0
  36. package/dist/form/schema-controller.d.ts +30 -14
  37. package/dist/form/signal-node-registry.d.ts +6 -7
  38. package/dist/form/submission.d.ts +93 -0
  39. package/dist/form/type-guards.d.ts +14 -73
  40. package/dist/form/types/contracts.d.ts +19 -92
  41. package/dist/form/types/field-config.d.ts +27 -0
  42. package/dist/form/types/index.d.ts +5 -57
  43. package/dist/form/types/nodes.d.ts +159 -0
  44. package/dist/form/types/schema-node.d.ts +139 -63
  45. package/dist/form/types/validation-schema.d.ts +23 -27
  46. package/dist/form/validation/context.d.ts +42 -7
  47. package/dist/form/validation/index.d.ts +9 -8
  48. package/dist/form/validation/operators.d.ts +22 -30
  49. package/dist/form/validation/run.d.ts +93 -18
  50. package/dist/form/validation/strategy.d.ts +41 -13
  51. package/dist/form/validation/types.d.ts +74 -20
  52. package/dist/form/validators/date-utils.d.ts +13 -1
  53. package/dist/form/validators/email.d.ts +8 -7
  54. package/dist/form/validators/file-type.d.ts +8 -7
  55. package/dist/form/validators/file-utils.d.ts +2 -1
  56. package/dist/form/validators/future-date.d.ts +8 -7
  57. package/dist/form/validators/index.d.ts +1 -1
  58. package/dist/form/validators/integer.d.ts +8 -7
  59. package/dist/form/validators/is-date.d.ts +11 -9
  60. package/dist/form/validators/is-number.d.ts +11 -9
  61. package/dist/form/validators/max-age.d.ts +13 -9
  62. package/dist/form/validators/max-date.d.ts +8 -7
  63. package/dist/form/validators/max-file-size.d.ts +8 -7
  64. package/dist/form/validators/max-files.d.ts +8 -7
  65. package/dist/form/validators/max-length.d.ts +14 -7
  66. package/dist/form/validators/max-total-file-size.d.ts +8 -7
  67. package/dist/form/validators/max.d.ts +10 -8
  68. package/dist/form/validators/min-age.d.ts +12 -9
  69. package/dist/form/validators/min-date.d.ts +8 -7
  70. package/dist/form/validators/min-file-size.d.ts +8 -7
  71. package/dist/form/validators/min-files.d.ts +11 -9
  72. package/dist/form/validators/min-length.d.ts +14 -7
  73. package/dist/form/validators/min.d.ts +10 -8
  74. package/dist/form/validators/multiple-of.d.ts +10 -8
  75. package/dist/form/validators/non-negative.d.ts +8 -7
  76. package/dist/form/validators/non-zero.d.ts +8 -7
  77. package/dist/form/validators/past-date.d.ts +8 -7
  78. package/dist/form/validators/pattern.d.ts +9 -8
  79. package/dist/form/validators/phone.d.ts +8 -7
  80. package/dist/form/validators/required.d.ts +11 -9
  81. package/dist/form/validators/url.d.ts +20 -13
  82. package/dist/form/validators/validation-issue.d.ts +8 -0
  83. package/dist/i18n/descriptor.d.ts +1 -1
  84. package/dist/i18n/en.json.d.ts +5 -1
  85. package/dist/i18n/locale-store.d.ts +7 -0
  86. package/dist/i18n/message-format.d.ts +24 -4
  87. package/dist/i18n/ru.json.d.ts +5 -1
  88. package/dist/i18n/translator.d.ts +11 -0
  89. package/dist/i18n/validation-message.d.ts +42 -7
  90. package/dist/i18n.d.ts +7 -12
  91. package/dist/i18n.js +66 -283
  92. package/dist/index.d.ts +16 -7
  93. package/dist/index.js +924 -2855
  94. package/dist/loader-BWkigpVe.js +52 -0
  95. package/dist/locale/en.js +1 -1
  96. package/dist/locale/en.json +4 -1
  97. package/dist/locale/ru.js +4 -1
  98. package/dist/locale/ru.json +4 -1
  99. package/dist/locale-CG54AU2Q.js +219 -0
  100. package/dist/locale.js +1 -1
  101. package/dist/model/behaviors-value.d.ts +24 -6
  102. package/dist/model/create-model.d.ts +2 -2
  103. package/dist/model/derived-registry.d.ts +1 -1
  104. package/dist/model/index.d.ts +11 -5
  105. package/dist/model/model-nodes.d.ts +79 -12
  106. package/dist/model/model-signals-proxy.d.ts +5 -6
  107. package/dist/model/model-value-proxy.d.ts +20 -15
  108. package/dist/model/safe-effect.d.ts +33 -1
  109. package/dist/model/types.d.ts +63 -13
  110. package/dist/model.js +20 -18
  111. package/dist/node-kind-C-r6ZMdo.js +15 -0
  112. package/dist/platforms/react/field-binding/field-adapter.d.ts +1 -1
  113. package/dist/platforms/react/field-binding/field-handle.d.ts +5 -2
  114. package/dist/platforms/react/form-bundle-context.d.ts +2 -2
  115. package/dist/platforms/react/hooks/types.d.ts +57 -38
  116. package/dist/platforms/react/hooks/use-form-bundle.d.ts +19 -4
  117. package/dist/platforms/react/hooks/use-form-flow.d.ts +40 -0
  118. package/dist/platforms/react/hooks/use-form-validation.d.ts +12 -6
  119. package/dist/platforms/react/hooks/useArrayLength.d.ts +6 -6
  120. package/dist/platforms/react/hooks/useFormControl.d.ts +7 -9
  121. package/dist/platforms/react/hooks/useFormControlValue.d.ts +18 -19
  122. package/dist/platforms/react/hooks/useSignalSubscription.d.ts +9 -1
  123. package/dist/platforms/react/i18n/context.d.ts +6 -1
  124. package/dist/platforms/react/i18n/hooks.d.ts +16 -10
  125. package/dist/platforms/react/i18n/provider.d.ts +9 -2
  126. package/dist/platforms/react/index.d.ts +6 -4
  127. package/dist/react.d.ts +22 -0
  128. package/dist/react.js +505 -0
  129. package/dist/runtime-token.d.ts +1 -2
  130. package/dist/safe-effect-BbtX3KuN.js +643 -0
  131. package/dist/schema-controller-CpiO2JoK.js +143 -0
  132. package/dist/signals.d.ts +15 -9
  133. package/dist/signals.js +11 -6
  134. package/dist/strategy-CoFSemuP.js +375 -0
  135. package/dist/type-guards-D-N_5Ddh.js +19 -0
  136. package/dist/validation-issue-B9mjI8jz.js +7 -0
  137. package/dist/validation-message-Ced9MGxs.js +194 -0
  138. package/dist/validation.js +9 -9
  139. package/dist/validators/email.js +5 -5
  140. package/dist/validators/future-date.js +8 -8
  141. package/dist/validators/integer.js +3 -3
  142. package/dist/validators/is-date.js +4 -4
  143. package/dist/validators/is-number.js +4 -4
  144. package/dist/validators/max-age.js +8 -9
  145. package/dist/validators/max-date.js +8 -8
  146. package/dist/validators/max-length.js +7 -7
  147. package/dist/validators/max.js +4 -4
  148. package/dist/validators/min-age.js +10 -8
  149. package/dist/validators/min-date.js +3 -3
  150. package/dist/validators/min-length.js +5 -5
  151. package/dist/validators/min.js +4 -4
  152. package/dist/validators/multiple-of.js +11 -7
  153. package/dist/validators/non-negative.js +3 -3
  154. package/dist/validators/non-zero.js +3 -3
  155. package/dist/validators/past-date.js +8 -8
  156. package/dist/validators/pattern.js +5 -5
  157. package/dist/validators/phone.js +11 -9
  158. package/dist/validators/required.js +2 -2
  159. package/dist/validators/url.js +48 -12
  160. package/dist/validators.js +17 -17
  161. package/docs/llms/01-api-reference.md +60 -34
  162. package/docs/llms/02-quick-start.md +111 -80
  163. package/docs/llms/03-api-signatures.md +368 -153
  164. package/docs/llms/04-common-patterns.md +58 -56
  165. package/docs/llms/05-common-mistakes.md +69 -44
  166. package/docs/llms/06-troubleshooting.md +14 -9
  167. package/docs/llms/07-complete-import.md +74 -43
  168. package/docs/llms/08-form-types.md +11 -4
  169. package/docs/llms/09-formschema.md +129 -81
  170. package/docs/llms/10-arrays.md +106 -75
  171. package/docs/llms/11-async-watchfield.md +2 -2
  172. package/docs/llms/13-multi-step.md +134 -63
  173. package/docs/llms/14-extended-mistakes.md +20 -17
  174. package/docs/llms/15-project-structure.md +221 -74
  175. package/docs/llms/16-ui-components.md +55 -42
  176. package/docs/llms/17-nonexistent-api.md +199 -32
  177. package/docs/llms/18-conditional-fields.md +66 -32
  178. package/docs/llms/19-reading-values.md +15 -3
  179. package/docs/llms/20-compute-vs-watch.md +16 -10
  180. package/docs/llms/21-array-operations.md +51 -27
  181. package/docs/llms/22-cycle-detection.md +1 -1
  182. package/docs/llms/23-copy-from.md +2 -2
  183. package/docs/llms/24-sync-fields.md +6 -5
  184. package/docs/llms/25-reset-when.md +14 -7
  185. package/docs/llms/26-transform-value.md +4 -4
  186. package/docs/llms/27-revalidate-when.md +16 -15
  187. package/docs/llms/28-submit-and-reset.md +276 -128
  188. package/docs/llms/29-async-preload.md +39 -29
  189. package/docs/llms/30-type-safety-recipes.md +105 -51
  190. package/docs/llms/31-async-validator-debounce.md +96 -53
  191. package/docs/llms/33-validation-strategy.md +177 -65
  192. package/docs/llms/34-i18n.md +69 -43
  193. package/docs/llms/35-unified-contract.md +100 -39
  194. package/docs/llms/36-issue-messages.md +284 -0
  195. package/docs/llms/37-compiled-schema.md +332 -0
  196. package/docs/llms/38-form-flow.md +397 -0
  197. package/llms-index.json +1 -1
  198. package/llms.txt +6084 -3420
  199. package/package.json +23 -4
  200. package/dist/behaviors-value-C7dSEhCc.js +0 -134
  201. package/dist/date-utils-dTT_x_Be.js +0 -31
  202. package/dist/form/create-core-form.d.ts +0 -72
  203. package/dist/form/factories/index.d.ts +0 -6
  204. package/dist/form/factories/node-factory.d.ts +0 -189
  205. package/dist/form/form-submitter.d.ts +0 -121
  206. package/dist/form/nodes/array-node.d.ts +0 -276
  207. package/dist/form/nodes/field-node.d.ts +0 -297
  208. package/dist/form/nodes/form-node.d.ts +0 -326
  209. package/dist/form/nodes/group-node.d.ts +0 -284
  210. package/dist/form/nodes/model-array-node.d.ts +0 -126
  211. package/dist/form/nodes/subscription-manager.d.ts +0 -180
  212. package/dist/form/status-machine.d.ts +0 -115
  213. package/dist/form/types/deep-schema.d.ts +0 -107
  214. package/dist/form/types/form-proxy.d.ts +0 -141
  215. package/dist/form/unique-id.d.ts +0 -45
  216. package/dist/form/validation/config.d.ts +0 -73
  217. package/dist/form/validation/error-handler.d.ts +0 -166
  218. package/dist/form/validators/validation-error.d.ts +0 -8
  219. package/dist/index-BrNkQj1y.js +0 -105
  220. package/dist/loader-Dv_Hl7JC.js +0 -216
  221. package/dist/model-value-proxy-caTmAiUd.js +0 -419
  222. package/dist/node-BeV_lV5i.js +0 -100
  223. package/dist/strategy-DgzELWno.js +0 -204
  224. package/dist/validation-error-B-U3RsV0.js +0 -11
@@ -1,126 +0,0 @@
1
- import { Signal, ReadonlySignal } from '@preact/signals-core';
2
- import { FormNode } from './form-node';
3
- import { FieldStatus, ValidationError } from '../types/index';
4
- import { FormModel } from '../../model/types';
5
- import { FormProxy } from '../types/form-proxy';
6
- /** Минимальный контракт реактивного массива модели, используемый узлом. */
7
- export interface ModelArrayControl<TItem extends object> {
8
- readonly length: number;
9
- at(index: number): FormModel<TItem> | undefined;
10
- push(item?: TItem): void;
11
- insertAt(index: number, item?: TItem): void;
12
- removeAt(index: number): void;
13
- move(from: number, to: number): void;
14
- swap(a: number, b: number): void;
15
- clear(): void;
16
- toArray(): TItem[];
17
- }
18
- /**
19
- * Узел массива, делегирующий данные массиву {@link FormModel} (архитектура M1).
20
- *
21
- * В отличие от {@link ArrayNode} (владеет элементами сам), `ModelArrayNode` НЕ владеет данными:
22
- * массив принадлежит модели, а узел держит per-item формы элементов (привязанные к сигналам
23
- * под-моделей) и синхронизирует их с длиной массива модели. Мутации (`push`/`removeAt`/`move`/…)
24
- * делегируются массиву модели; per-item формы кэшируются по идентичности под-модели, поэтому при
25
- * reorder/повторном рендере не пересоздаются (состояние и валидация сохраняются). Реализует тот же
26
- * контракт, что ждут секции массива и `useFormControl` (`length`/`value`/`valid`/`errors`/`at`/`push`/…).
27
- *
28
- * Обычно создаётся не напрямую, а `createFormFromModel({ model, schema })`: когда в схеме встречается узел
29
- * массива `{ array: model.<field>, item: (item) => itemSchema }`, форма материализует его как
30
- * `ModelArrayNode` и кладёт под `form.<field>` (совместим с `FormArraySection`).
31
- *
32
- * @group Nodes
33
- *
34
- * @example Массив как часть формы (через createForm)
35
- * ```typescript
36
- * const model = createModel<{ rows: { name: string; qty: number }[] }>({ rows: [] });
37
- *
38
- * const rowItem = (item: FormModel<{ name: string; qty: number }>) => ({
39
- * name: { value: item.$.name, component: Input },
40
- * qty: { value: item.$.qty, component: Input },
41
- * });
42
- *
43
- * const form = createFormFromModel({
44
- * model,
45
- * schema: {
46
- * children: [{ array: model.rows, item: rowItem }],
47
- * },
48
- * });
49
- *
50
- * const rows = form.rows as unknown as ModelArrayNode<{ name: string; qty: number }>;
51
- * rows.push({ name: 'A', qty: 1 }); // мутация уезжает в model.rows
52
- * rows.at(0)?.name.setValue('B'); // правка поля элемента доезжает в под-модель
53
- * rows.length.value; // 1 (реактивная длина)
54
- * ```
55
- */
56
- export declare class ModelArrayNode<T extends object> extends FormNode<T[]> {
57
- private readonly control;
58
- private readonly buildItem;
59
- private readonly itemNodes;
60
- private readonly cache;
61
- private mountedModels;
62
- private readonly initial;
63
- private readonly _arrayErrors;
64
- private readonly disposeSync;
65
- /**
66
- * Сигнал per-item форм. Публичен в т.ч. чтобы `isArrayNode` распознавал узел как массив
67
- * (duck-typing проверяет наличие `items`).
68
- */
69
- get items(): Signal<FormProxy<T>[]>;
70
- readonly value: ReadonlySignal<T[]>;
71
- readonly valid: ReadonlySignal<boolean>;
72
- readonly invalid: ReadonlySignal<boolean>;
73
- readonly pending: ReadonlySignal<boolean>;
74
- readonly touched: ReadonlySignal<boolean>;
75
- readonly dirty: ReadonlySignal<boolean>;
76
- readonly errors: ReadonlySignal<ValidationError[]>;
77
- readonly status: ReadonlySignal<FieldStatus>;
78
- readonly length: ReadonlySignal<number>;
79
- /**
80
- * @param control Реактивный массив модели (`model.<path>`).
81
- * @param buildItem Строитель формы элемента по его под-модели (инъекция, чтобы избежать цикла
82
- * импорта с `create-form`). Обычно `(item) => createFormFromModel({ model: item, schema: itemSchema(item) })`.
83
- */
84
- constructor(control: ModelArrayControl<T>, buildItem: (item: FormModel<T>) => FormProxy<T>);
85
- at(index: number): FormProxy<T> | undefined;
86
- map<R>(fn: (item: FormProxy<T>, index: number) => R): R[];
87
- forEach(fn: (item: FormProxy<T>, index: number) => void): void;
88
- /**
89
- * Добавить элемент. Без значения — элемент по шаблону массива модели (`arrayOf(blank)` либо
90
- * `initialValue` узла-массива схемы); шаблона нет — ошибка модели.
91
- */
92
- push(item?: Partial<T>): void;
93
- /** Вставить элемент по индексу; без значения — по шаблону, как у {@link push}. */
94
- insert(index: number, item?: Partial<T>): void;
95
- removeAt(index: number): void;
96
- /**
97
- * Переместить элемент модель-массива. Делегирует мутацию массиву модели; внутренний `effect`
98
- * пересоберёт `itemNodes` в новом порядке (per-item формы сохраняются по кешу идентичности).
99
- */
100
- move(from: number, to: number): void;
101
- /** Поменять местами два элемента модель-массива (см. {@link move}). */
102
- swap(a: number, b: number): void;
103
- clear(): void;
104
- getValue(): T[];
105
- setValue(values: T[]): void;
106
- patchValue(values: Partial<T[]>): void;
107
- reset(values?: T[]): void;
108
- resetToInitial(): void;
109
- validate(): Promise<boolean>;
110
- setErrors(errors: ValidationError[]): void;
111
- clearErrors(): void;
112
- protected onMarkAsTouched(): void;
113
- protected onMarkAsUntouched(): void;
114
- protected onMarkAsDirty(): void;
115
- protected onMarkAsPristine(): void;
116
- protected onDisable(): void;
117
- protected onEnable(): void;
118
- /** Диспозит форму элемента (эффекты/поведения/вложенные подписки); терпима к не-GroupNode. */
119
- private disposeItemNode;
120
- /**
121
- * Очистка подписки синхронизации И детерминированный teardown всех форм элементов.
122
- * Без пробрасывания dispose в itemNodes эффекты/поведения каждой формы элемента (в т.ч. disposeSync
123
- * вложенных ModelArrayNode) остались бы жить после teardown формы — утечка подписок.
124
- */
125
- dispose(): void;
126
- }
@@ -1,180 +0,0 @@
1
- /**
2
- * Менеджер подписок для FormNode
3
- *
4
- * Централизует управление effect-подписками в узлах формы,
5
- * предотвращает утечки памяти и упрощает отладку.
6
- *
7
- * Каждая подписка имеет уникальный ключ, что позволяет:
8
- * - Отписываться от конкретной подписки по ключу
9
- * - Автоматически заменять существующие подписки
10
- * - Отслеживать количество активных подписок (для отладки)
11
- *
12
- * @internal
13
- *
14
- * @example
15
- * ```typescript
16
- * class FieldNode {
17
- * private subscriptions = new SubscriptionManager();
18
- *
19
- * watch(callback: Function) {
20
- * const dispose = effect(() => callback(this.value.value));
21
- * return this.subscriptions.add('watch', dispose);
22
- * }
23
- *
24
- * dispose() {
25
- * this.subscriptions.clear();
26
- * }
27
- * }
28
- * ```
29
- */
30
- export declare class SubscriptionManager {
31
- /**
32
- * Хранилище подписок
33
- * Ключ: уникальный идентификатор подписки
34
- * Значение: функция отписки (dispose)
35
- */
36
- private subscriptions;
37
- /**
38
- * Добавляет подписку
39
- *
40
- * Если подписка с таким ключом уже существует, отписывается от неё
41
- * и заменяет новой. Это предотвращает утечки памяти при повторной
42
- * регистрации подписки с тем же ключом.
43
- *
44
- * @param key Уникальный ключ подписки
45
- * @param dispose Функция отписки (обычно возвращаемая из effect())
46
- * @returns Функция для отписки от этой конкретной подписки
47
- *
48
- * @example
49
- * ```typescript
50
- * const manager = new SubscriptionManager();
51
- *
52
- * // Добавление подписки
53
- * const unsubscribe = manager.add('mySubscription', () => {
54
- * console.log('Disposing subscription');
55
- * });
56
- *
57
- * // Отписка через возвращаемую функцию
58
- * unsubscribe();
59
- *
60
- * // Или через manager.remove()
61
- * manager.add('anotherSub', disposeFn);
62
- * manager.remove('anotherSub');
63
- * ```
64
- */
65
- add(key: string, dispose: () => void): () => void;
66
- /**
67
- * Удаляет подписку по ключу
68
- *
69
- * Вызывает функцию отписки и удаляет подписку из хранилища.
70
- * Если подписка с таким ключом не найдена, ничего не делает.
71
- *
72
- * @param key Ключ подписки
73
- * @returns true, если подписка была удалена, false если не найдена
74
- *
75
- * @example
76
- * ```typescript
77
- * const manager = new SubscriptionManager();
78
- * manager.add('mySub', disposeFn);
79
- *
80
- * // Удаление подписки
81
- * const removed = manager.remove('mySub'); // true
82
- *
83
- * // Попытка удалить несуществующую подписку
84
- * const removed2 = manager.remove('nonExistent'); // false
85
- * ```
86
- */
87
- remove(key: string): boolean;
88
- /**
89
- * Очищает все подписки
90
- *
91
- * Вызывает функции отписки для всех активных подписок
92
- * и очищает хранилище. Обычно используется при dispose узла формы.
93
- *
94
- * @example
95
- * ```typescript
96
- * class FieldNode {
97
- * private subscriptions = new SubscriptionManager();
98
- *
99
- * dispose() {
100
- * // Отписываемся от всех effect
101
- * this.subscriptions.clear();
102
- * }
103
- * }
104
- * ```
105
- */
106
- clear(): void;
107
- /**
108
- * Возвращает количество активных подписок
109
- *
110
- * Полезно для отладки утечек памяти. Если количество подписок
111
- * растет без ограничений, это может указывать на то, что
112
- * компоненты не отписываются должным образом.
113
- *
114
- * @returns Количество активных подписок
115
- *
116
- * @example
117
- * ```typescript
118
- * const manager = new SubscriptionManager();
119
- * console.log(manager.size()); // 0
120
- *
121
- * manager.add('sub1', disposeFn1);
122
- * manager.add('sub2', disposeFn2);
123
- * console.log(manager.size()); // 2
124
- *
125
- * manager.clear();
126
- * console.log(manager.size()); // 0
127
- * ```
128
- */
129
- size(): number;
130
- /**
131
- * Проверяет, есть ли подписка с данным ключом
132
- *
133
- * @param key Ключ подписки
134
- * @returns true, если подписка существует
135
- *
136
- * @example
137
- * ```typescript
138
- * const manager = new SubscriptionManager();
139
- * manager.add('mySub', disposeFn);
140
- *
141
- * console.log(manager.has('mySub')); // true
142
- * console.log(manager.has('nonExistent')); // false
143
- * ```
144
- */
145
- has(key: string): boolean;
146
- /**
147
- * Возвращает список всех ключей активных подписок
148
- *
149
- * Полезно для отладки: можно увидеть, какие подписки активны.
150
- *
151
- * @returns Массив ключей всех активных подписок
152
- *
153
- * @example
154
- * ```typescript
155
- * const manager = new SubscriptionManager();
156
- * manager.add('watch-value', disposeFn1);
157
- * manager.add('watch-errors', disposeFn2);
158
- *
159
- * console.log(manager.getKeys()); // ['watch-value', 'watch-errors']
160
- * ```
161
- */
162
- getKeys(): string[];
163
- /**
164
- * Отписывается от всех подписок (алиас для clear())
165
- *
166
- * Используется при dispose() узла формы для совместимости с ожидаемым API.
167
- *
168
- * @example
169
- * ```typescript
170
- * class FieldNode {
171
- * private subscriptions = new SubscriptionManager();
172
- *
173
- * dispose() {
174
- * this.subscriptions.dispose();
175
- * }
176
- * }
177
- * ```
178
- */
179
- dispose(): void;
180
- }
@@ -1,115 +0,0 @@
1
- import { ReadonlySignal } from '@preact/signals-core';
2
- import { FieldStatus } from './types/index';
3
- /**
4
- * FormStatusMachine - управляет состоянием поля формы
5
- *
6
- * Предоставляет:
7
- * - Единый источник истины для статуса
8
- * - Computed signals для derived состояний (valid, invalid, pending, disabled)
9
- * - Валидацию переходов между состояниями
10
- *
11
- * @example
12
- * ```typescript
13
- * const statusMachine = new FormStatusMachine('valid');
14
- *
15
- * // Начало валидации
16
- * statusMachine.startValidation();
17
- * console.log(statusMachine.pending.value); // true
18
- *
19
- * // Завершение валидации с ошибками
20
- * statusMachine.completeValidation(true);
21
- * console.log(statusMachine.invalid.value); // true
22
- *
23
- * // Отключение поля
24
- * statusMachine.disable();
25
- * console.log(statusMachine.disabled.value); // true
26
- * ```
27
- */
28
- export declare class FormStatusMachine {
29
- /** Внутренний сигнал статуса */
30
- private readonly _status;
31
- /** Публичный read-only сигнал статуса */
32
- readonly status: ReadonlySignal<FieldStatus>;
33
- /** Поле валидно */
34
- readonly valid: ReadonlySignal<boolean>;
35
- /** Поле невалидно */
36
- readonly invalid: ReadonlySignal<boolean>;
37
- /** Идет валидация */
38
- readonly pending: ReadonlySignal<boolean>;
39
- /** Поле отключено */
40
- readonly disabled: ReadonlySignal<boolean>;
41
- /**
42
- * @param initial - Начальный статус (по умолчанию 'valid')
43
- */
44
- constructor(initial?: FieldStatus);
45
- /**
46
- * Начать валидацию
47
- *
48
- * Переводит статус в 'pending' если поле не отключено
49
- *
50
- * @example
51
- * ```typescript
52
- * statusMachine.startValidation();
53
- * // status: 'pending'
54
- * ```
55
- */
56
- startValidation(): void;
57
- /**
58
- * Завершить валидацию
59
- *
60
- * @param hasErrors - Есть ли ошибки валидации
61
- *
62
- * @example
63
- * ```typescript
64
- * // Валидация успешна
65
- * statusMachine.completeValidation(false);
66
- * // status: 'valid'
67
- *
68
- * // Есть ошибки
69
- * statusMachine.completeValidation(true);
70
- * // status: 'invalid'
71
- * ```
72
- */
73
- completeValidation(hasErrors: boolean): void;
74
- /**
75
- * Установить ошибки напрямую (без перехода через pending)
76
- *
77
- * Используется для синхронной валидации или установки ошибок извне
78
- *
79
- * @param hasErrors - Есть ли ошибки
80
- *
81
- * @example
82
- * ```typescript
83
- * statusMachine.setErrors(true);
84
- * // status: 'invalid'
85
- * ```
86
- */
87
- setErrors(hasErrors: boolean): void;
88
- /**
89
- * Отключить поле
90
- *
91
- * Переводит статус в 'disabled'
92
- *
93
- * @example
94
- * ```typescript
95
- * statusMachine.disable();
96
- * // status: 'disabled'
97
- * ```
98
- */
99
- disable(): void;
100
- /**
101
- * Включить поле
102
- *
103
- * @param hasErrors - Есть ли ошибки (определяет valid/invalid)
104
- *
105
- * @example
106
- * ```typescript
107
- * statusMachine.enable(false);
108
- * // status: 'valid'
109
- *
110
- * statusMachine.enable(true);
111
- * // status: 'invalid'
112
- * ```
113
- */
114
- enable(hasErrors?: boolean): void;
115
- }
@@ -1,107 +0,0 @@
1
- import { ComponentType } from 'react';
2
- import { Signal } from '@preact/signals-core';
3
- import { ValidatorFn, AsyncValidatorFn, AnyFunction } from './index';
4
- /**
5
- * Конфигурация поля
6
- * @group Types
7
- * @category Configuration Types
8
- * @remarks
9
- * В схеме `createFormFromModel({ model, schema })` лист связывают через **`value: model.$.field`**: harvest
10
- * ловит узел по идентичности `Signal` (`node.value instanceof Signal` — см. `create-form.ts`) и
11
- * нормализует его в `valueSignal`. Писать `valueSignal:` **в самой схеме `createForm` нельзя** —
12
- * harvest его не подхватит (проверка идёт по `node.value`), и `component`/`componentProps` этого
13
- * узла молча отбросятся. `value`/`valueSignal` на прямом FieldNode-конфиге — это
14
- * post-normalization / legacy-слой, а не форма записи в схеме `createForm`.
15
- */
16
- export interface FieldConfig<T> {
17
- /**
18
- * Начальное значение-литерал (legacy-путь). Под архитектурой M1 значение приходит из
19
- * {@link FieldConfig.valueSignal} (сигнал {@link FormModel}); тогда `value` не требуется.
20
- */
21
- value?: T | null;
22
- /**
23
- * Сигнал значения из {@link FormModel} (M1). Если задан — служит источником истины значения
24
- * поля (нода не владеет значением, а ссылается на этот сигнал). Имеет приоритет над `value`.
25
- */
26
- valueSignal?: Signal<T>;
27
- /**
28
- * UI-компонент поля. Опционален: core-часть можно использовать без ссылки на компонент
29
- * (значение/валидация работают без UI; компонент нужен только для рендеринга).
30
- */
31
- component?: ComponentType<any>;
32
- componentProps?: any;
33
- validators?: ValidatorFn<T>[];
34
- asyncValidators?: AsyncValidatorFn<T>[];
35
- disabled?: boolean;
36
- updateOn?: 'change' | 'blur' | 'submit';
37
- /** Задержка (в мс) перед запуском асинхронной валидации */
38
- debounce?: number;
39
- }
40
- /**
41
- * «Хотя бы одно поле обязано присутствовать»: превращает объект со всеми опциональными
42
- * свойствами в объединение вариантов, где ровно один ключ обязателен, а остальные опциональны.
43
- * Пустой объект `{}` не assignable ни к одному варианту и потому отклоняется.
44
- *
45
- * @internal
46
- */
47
- type RequireAtLeastOne<T> = {
48
- [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
49
- }[keyof T];
50
- /**
51
- * {@link FieldConfig}, из которого нельзя сделать пустой `{}`.
52
- *
53
- * Все свойства {@link FieldConfig} опциональны, поэтому в {@link FormSchema} опечатка вида
54
- * `{ email: {} }` молча проходит проверку типов и в рантайме становится пустой подгруппой
55
- * вместо поля. `NonEmptyFieldConfig` требует наличия хотя бы одного свойства (value/valueSignal/
56
- * component/validators/…), оставляя каждое из них опциональным по отдельности.
57
- *
58
- * @internal
59
- */
60
- type NonEmptyFieldConfig<T> = RequireAtLeastOne<FieldConfig<T>>;
61
- /**
62
- * **Data-shaped** конфиг формы: ключи повторяют структуру данных `T`, значения — {@link FieldConfig}
63
- * (или вложенный `FormSchema`). Форма конфига, из которой строится {@link GroupNode}.
64
- * - `T[] -> [FormSchema<T>]` (массив с одним элементом)
65
- * - `object -> FormSchema<T>` (группа)
66
- * - `primitive -> FieldConfig<T>` (поле)
67
- *
68
- * Использует NonNullable для корректной обработки опциональных полей.
69
- *
70
- * ⚠️ Не путать с {@link FormSchemaNode} — тот описывает **узел дерева** M1-схемы (лист/массив/
71
- * контейнер), передаваемой в `createFormFromModel({ model, schema })`. `FormSchema` — это data-shaped конфиг
72
- * (ключи = поля данных), а не узел дерева.
73
- *
74
- * @group Types
75
- * @category Configuration Types
76
- *
77
- * @example
78
- * ```typescript
79
- * interface Form {
80
- * name: string; // → FieldConfig<string>
81
- * address: { // → FormSchema<Address>
82
- * city: string;
83
- * street: string;
84
- * };
85
- * items?: Array<{ // → [FormSchema<Item>] (опциональный)
86
- * title: string;
87
- * price: number;
88
- * }>;
89
- * }
90
- *
91
- * const schema: FormSchema<Form> = {
92
- * name: { value: '', component: Input },
93
- * address: {
94
- * city: { value: '', component: Input },
95
- * street: { value: '', component: Input },
96
- * },
97
- * items: [{
98
- * title: { value: '', component: Input },
99
- * price: { value: 0, component: Input },
100
- * }],
101
- * };
102
- * ```
103
- */
104
- export type FormSchema<T> = {
105
- [K in keyof T]: NonNullable<T[K]> extends string | number | boolean ? NonEmptyFieldConfig<T[K]> : NonNullable<T[K]> extends Array<infer U> ? U extends string | number | boolean ? NonEmptyFieldConfig<T[K]> : U extends Date | File | Blob | AnyFunction ? NonEmptyFieldConfig<T[K]> : [FormSchema<U>] : NonNullable<T[K]> extends Date | File | Blob | AnyFunction ? NonEmptyFieldConfig<T[K]> : FormSchema<NonNullable<T[K]>>;
106
- };
107
- export {};
@@ -1,141 +0,0 @@
1
- import { FieldNode } from '../nodes/field-node';
2
- import { GroupNode } from '../nodes/group-node';
3
- import { ArrayNode } from '../nodes/array-node';
4
- /**
5
- * Признак «объект-группа» — обычный объект, который нужно обернуть в под-форму.
6
- *
7
- * Исключает массивы, спец-объекты (`Date`/`File`/`Blob`) и примитивы.
8
- * Используется как дискриминатор вместо `extends FormFields` (`Record<string, FormValue>`):
9
- * пользовательские модели почти всегда объявлены через `interface`, который НЕ assignable
10
- * к Record-типу (у интерфейса нет неявной index signature), и потому ошибочно проваливался
11
- * бы в ветку `FieldNode`. Проверка `extends object` корректно ловит и `interface`, и `type`.
12
- *
13
- * @internal
14
- */
15
- type IsGroupObject<V> = V extends ReadonlyArray<unknown> ? false : V extends Date | File | Blob ? false : V extends (...args: never[]) => unknown ? false : V extends object ? true : false;
16
- /**
17
- * Мапит тип модели данных T на правильные типы узлов формы
18
- *
19
- * Рекурсивно определяет типы узлов на основе структуры данных:
20
- * - `T[K] extends Array<infer U>` где U - объект → `FormArrayProxy<U>`
21
- * - `T[K] extends Array<infer U>` где U - примитив → `FieldNode<T[K]>` (массив как обычное поле)
22
- * - `T[K] extends object` → `FormProxy<T[K]>` (вложенная форма с типизацией)
23
- * - `T[K]` примитив → `FieldNode<T[K]>` (простое поле)
24
- *
25
- * Использует NonNullable для правильной обработки опциональных полей
26
- *
27
- * @group Types
28
- * @category Proxy Types
29
- *
30
- * @template T - Тип модели данных формы
31
- */
32
- export type FormControlsProxy<T> = {
33
- [K in keyof T]-?: NonNullable<T[K]> extends ReadonlyArray<infer U> ? IsGroupObject<U> extends true ? FormArrayProxy<U & object> : FieldNode<T[K]> : IsGroupObject<NonNullable<T[K]>> extends true ? FormProxy<NonNullable<T[K]>> : FieldNode<T[K]>;
34
- };
35
- /**
36
- * Комбинированный тип для GroupNode с Proxy доступом к полям
37
- *
38
- * Объединяет методы и свойства GroupNode с типизированными полями формы.
39
- * Это позволяет использовать как API GroupNode, так и прямой доступ к полям.
40
- *
41
- * @group Types
42
- * @category Proxy Types
43
- *
44
- * @template T - Тип модели данных формы
45
- *
46
- * @example
47
- * ```typescript
48
- * interface UserForm {
49
- * email: string;
50
- * profile: {
51
- * name: string;
52
- * age: number;
53
- * };
54
- * }
55
- *
56
- * const form = createLegacyForm<UserForm>(schema);
57
- *
58
- * // Доступ к методам GroupNode
59
- * await form.validate();
60
- * const values = form.getValue();
61
- * console.log(form.valid.value);
62
- *
63
- * // Прямой доступ к полям (через Proxy)
64
- * form.email.setValue('test@mail.com');
65
- * form.profile.name.setValue('John');
66
- * ```
67
- *
68
- * @remarks
69
- * **Зарезервированные имена.** Прямой доступ `form.<name>` отдаёт приоритет собственным
70
- * членам {@link GroupNode} (`value`, `status`, `id`, `errors`, `disabled`, `reset`, `submit`, …).
71
- * Поэтому поле модели, названное любым из этих имён, недостижимо как `form.<name>` — там будет
72
- * агрегированный сигнал/метод узла, а не {@link FieldNode}. Такие имена исключены из полевого
73
- * пространства имён на уровне типов (через `Omit<…, keyof GroupNode>`), чтобы затенение не
74
- * компилировалось молча. Для доступа к любому полю по имени — включая затенённые — используйте
75
- * escape-hatch пространство {@link FormProxy.$}: `form.$.status`, `form.$.id`, `form.$.value`.
76
- */
77
- export type FormProxy<T> = GroupNode<T> & Omit<FormControlsProxy<T>, keyof GroupNode<T>> & {
78
- /**
79
- * Escape-hatch пространство имён «controls»: типобезопасный доступ ко ВСЕМ полям формы
80
- * по имени, включая поля, чьи имена совпадают с членами {@link GroupNode}
81
- * (`value`/`status`/`id`/`errors`/…), недостижимые через `form.<name>`.
82
- *
83
- * @example
84
- * ```typescript
85
- * // модель: { status: string; email: string }
86
- * form.status; // ReadonlySignal<FieldStatus> — агрегат GroupNode (не поле!)
87
- * form.$.status; // FieldNode<string> — поле пользователя
88
- * form.$.status.setValue('active');
89
- * form.$.email; // FieldNode<string> — так же доступны и незатенённые поля
90
- * ```
91
- */
92
- readonly $: FormControlsProxy<T>;
93
- };
94
- /**
95
- * Комбинированный тип для ArrayNode с Proxy доступом к элементам
96
- *
97
- * Объединяет методы и свойства ArrayNode с типизированным доступом к элементам массива.
98
- *
99
- * @group Types
100
- * @category Proxy Types
101
- *
102
- * @template T - Тип модели данных элемента массива
103
- *
104
- * @example
105
- * ```typescript
106
- * interface TodoItem {
107
- * title: string;
108
- * completed: boolean;
109
- * }
110
- *
111
- * const todos: FormArrayProxy<TodoItem> = new ArrayNode(schema);
112
- *
113
- * // Доступ к методам ArrayNode
114
- * todos.push({ title: 'New todo', completed: false });
115
- * todos.removeAt(0);
116
- *
117
- * // Доступ к элементам (через Proxy)
118
- * todos.at(0)?.title.setValue('Updated title');
119
- *
120
- * // Итерация
121
- * todos.forEach((item, i) => {
122
- * console.log(item.title.value.value);
123
- * });
124
- * ```
125
- */
126
- export type FormArrayProxy<T extends object> = ArrayNode<T> & {
127
- /**
128
- * Безопасный доступ к элементу массива по индексу
129
- * Возвращает GroupNode с типизированными полями или undefined
130
- */
131
- at(index: number): FormProxy<T> | undefined;
132
- /**
133
- * Итерация по элементам массива с типизированными элементами
134
- */
135
- forEach(callback: (item: FormProxy<T>, index: number) => void): void;
136
- /**
137
- * Маппинг элементов массива с типизированными элементами
138
- */
139
- map<R>(callback: (item: FormProxy<T>, index: number) => R): R[];
140
- };
141
- export {};