@reformer/core 9.0.0-beta.1 → 9.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,11 @@
1
- function n(r, t) {
2
- return (e) => e ? r.test(e) ? null : {
1
+ function s(e, r) {
2
+ const t = new RegExp(e.source, e.flags);
3
+ return (n) => n ? (t.lastIndex = 0, t.test(n) ? null : {
3
4
  code: "pattern",
4
- message: t?.message ?? "invalid",
5
- params: { pattern: r.source, ...t?.params }
6
- } : null;
5
+ message: r?.message ?? "",
6
+ params: { pattern: e.source, ...r?.params }
7
+ }) : null;
7
8
  }
8
9
  export {
9
- n as pattern
10
+ s as pattern
10
11
  };
@@ -1,14 +1,10 @@
1
- function a(r) {
2
- return (e) => e == null || e === "" ? {
1
+ function n(e) {
2
+ return (r) => r == null || r === "" || Array.isArray(r) && r.length === 0 || typeof r == "boolean" && r !== !0 ? {
3
3
  code: "required",
4
- message: r?.message ?? "invalid",
5
- params: r?.params
6
- } : typeof e == "boolean" && e !== !0 ? {
7
- code: "required",
8
- message: r?.message ?? "invalid",
9
- params: r?.params
4
+ message: e?.message ?? "",
5
+ params: e?.params
10
6
  } : null;
11
7
  }
12
8
  export {
13
- a as required
9
+ n as required
14
10
  };
package/llms.txt CHANGED
@@ -3815,6 +3815,23 @@ export interface ArrayControlState<T> {
3815
3815
  * ```
3816
3816
  */
3817
3817
  dirty: boolean;
3818
+
3819
+ /**
3820
+ * Флаг отключения массива.
3821
+ * `true` когда массив отключён (`ArrayNode.disable()`), в том числе через
3822
+ * распространение disable от родительской группы.
3823
+ *
3824
+ * Используйте для отключения UI-действий добавления/удаления, когда массив
3825
+ * структурно неизменяем.
3826
+ *
3827
+ * @example
3828
+ * ```tsx
3829
+ * const { disabled } = useFormControl(itemsArray);
3830
+ *
3831
+ * return <button disabled={disabled} onClick={() => control.push()}>Add</button>;
3832
+ * ```
3833
+ */
3834
+ disabled: boolean;
3818
3835
  }
3819
3836
  ```
3820
3837
 
@@ -3913,8 +3930,7 @@ _Source: src/core/types/index.ts_
3913
3930
  **Kind:** `type`
3914
3931
 
3915
3932
  Чистый асинхронный валидатор поля. Та же тройка аргументов `(value, scope, root)`, что у
3916
- {@link Validator}, но возвращает `Promise`. Регистрируется через
3917
- `validateAsync(path, validator, { debounce })`.
3933
+ {@link Validator}, но возвращает `Promise`.
3918
3934
 
3919
3935
  **Signature:**
3920
3936
  ```typescript
@@ -3925,6 +3941,10 @@ export type AsyncValidator<TForm, TField> = (
3925
3941
  ) => Promise<ValidationError | null>;
3926
3942
  ```
3927
3943
 
3944
+ **Deprecated:** Осиротевший остаток удалённого оператора `validateAsync` (Ф7). Рантаймом не
3945
+ потребляется — живой async-путь узла поля использует `AsyncValidatorFn` `(value, { signal })`.
3946
+ Экспортируется только ради обратной совместимости.
3947
+
3928
3948
  _Source: src/core/types/validation-schema.ts_
3929
3949
 
3930
3950
  ### AsyncValidatorFn
@@ -4034,13 +4054,16 @@ _Source: src/core/model/behaviors.ts_
4034
4054
 
4035
4055
  **Kind:** `type`
4036
4056
 
4037
- Функция условия для applyWhen.
4057
+ Функция-предикат `(value) => boolean`.
4038
4058
 
4039
4059
  **Signature:**
4040
4060
  ```typescript
4041
4061
  export type ConditionFn<T> = (value: T) => boolean;
4042
4062
  ```
4043
4063
 
4064
+ **Deprecated:** Осиротевший остаток удалённого оператора `applyWhen` (Ф7). Рантаймом не
4065
+ потребляется; экспортируется только ради обратной совместимости.
4066
+
4044
4067
  _Source: src/core/types/validation-schema.ts_
4045
4068
 
4046
4069
  ### ConfigWithSchema
@@ -4346,6 +4369,15 @@ _Source: src/core/validation/validators/email.ts_
4346
4369
  ⚠️ Поле должно быть материализовано в форме (`createForm`) — иначе ноды в реестре нет (например,
4347
4370
  элемент массива, который строится per-item).
4348
4371
 
4372
+ ⚠️ Если то же поле одновременно является целью `compute`/`computeFrom`, обязательно ограничь
4373
+ compute тем же условием (`when`), что и это `enableWhen`. Механизмы не согласованы: `enableWhen`
4374
+ работает со статус-машиной ноды (enable/disable/reset), а `compute` пишет `target.value` напрямую
4375
+ в сигнал модели и проверяет только собственный `options.when` — состояние `disabled` он НЕ смотрит.
4376
+ Поэтому при выключенном поле любое изменение зависимости compute молча заново заполнит его (а с
4377
+ `resetOnDisable` — перезапишет только что сброшенное значение), и так как `getValue()`/`submit`
4378
+ включают disabled-поля, мусор утечёт в payload. Это аналог правила #13 гайда add-behavior,
4379
+ которое сегодня сформулировано только для `copyFrom`; для `compute` то же ограничение обязательно.
4380
+
4349
4381
  **Signature:**
4350
4382
  ```typescript
4351
4383
  export function enableWhen(
@@ -4359,6 +4391,12 @@ export function enableWhen(
4359
4391
 
4360
4392
  ```typescript
4361
4393
  enableWhen(model.$.propertyValue, () => model.loanType === 'mortgage', { resetOnDisable: true });
4394
+
4395
+ // Поле-цель compute, которое ТАКЖЕ в resetOnDisable enableWhen: compute обязан нести тот же when,
4396
+ // иначе initialPayment заново заполнится в выключенном состоянии и утечёт в submit.
4397
+ compute(model.$.initialPayment, () => model.propertyValue * 0.2, {
4398
+ when: () => model.loanType === 'mortgage',
4399
+ });
4362
4400
  ```
4363
4401
 
4364
4402
  _Source: src/core/model/behaviors.ts_
@@ -4633,6 +4671,21 @@ export interface FieldControlState<T> {
4633
4671
  * ```
4634
4672
  */
4635
4673
  componentProps: Record<string, unknown>;
4674
+
4675
+ /**
4676
+ * Флаг изменения поля.
4677
+ * `true` когда значение поля отличается от начального.
4678
+ *
4679
+ * Реактивный аналог `control.dirty` — паритет с {@link ArrayControlState.dirty}.
4680
+ *
4681
+ * @example
4682
+ * ```tsx
4683
+ * const { dirty } = useFormControl(field);
4684
+ *
4685
+ * return dirty ? <span className="badge">Изменено</span> : null;
4686
+ * ```
4687
+ */
4688
+ dirty: boolean;
4636
4689
  }
4637
4690
  ```
4638
4691
 
@@ -4960,7 +5013,27 @@ _Source: src/core/nodes/form-node.ts_
4960
5013
 
4961
5014
  **Signature:**
4962
5015
  ```typescript
4963
- export type FormProxy<T> = GroupNode<T> & FormControlsProxy<T>;
5016
+ export type FormProxy<T> = GroupNode<T> &
5017
+ // Исключаем имена, совпадающие с членами GroupNode: для них `form.<name>` возвращает член узла,
5018
+ // а не поле. Оставлять их в полевом пространстве — значит пересекать, напр., ReadonlySignal<FieldStatus>
5019
+ // с FieldNode<string> и молча компилировать невозможный тип. Escape-hatch — `form.$` ниже.
5020
+ Omit<FormControlsProxy<T>, keyof GroupNode<T>> & {
5021
+ /**
5022
+ * Escape-hatch пространство имён «controls»: типобезопасный доступ ко ВСЕМ полям формы
5023
+ * по имени, включая поля, чьи имена совпадают с членами {@link GroupNode}
5024
+ * (`value`/`status`/`id`/`errors`/…), недостижимые через `form.<name>`.
5025
+ *
5026
+ * @example
5027
+ * ```typescript
5028
+ * // модель: { status: string; email: string }
5029
+ * form.status; // ReadonlySignal<FieldStatus> — агрегат GroupNode (не поле!)
5030
+ * form.$.status; // FieldNode<string> — поле пользователя
5031
+ * form.$.status.setValue('active');
5032
+ * form.$.email; // FieldNode<string> — так же доступны и незатенённые поля
5033
+ * ```
5034
+ */
5035
+ readonly $: FormControlsProxy<T>;
5036
+ };
4964
5037
  ```
4965
5038
 
4966
5039
  **Examples:**
@@ -5007,16 +5080,18 @@ _Source: src/core/types/form-proxy.ts_
5007
5080
  **Signature:**
5008
5081
  ```typescript
5009
5082
  export type FormSchema<T> = {
5083
+ // Листовые позиции используют NonEmptyFieldConfig: пустой `{}` (частая опечатка) отклоняется
5084
+ // на этапе компиляции, но каждое свойство FieldConfig по отдельности остаётся опциональным.
5010
5085
  [K in keyof T]: NonNullable<T[K]> extends string | number | boolean
5011
- ? FieldConfig<T[K]>
5086
+ ? NonEmptyFieldConfig<T[K]>
5012
5087
  : NonNullable<T[K]> extends Array<infer U>
5013
5088
  ? U extends string | number | boolean
5014
- ? FieldConfig<T[K]>
5089
+ ? NonEmptyFieldConfig<T[K]>
5015
5090
  : U extends Date | File | Blob | AnyFunction
5016
- ? FieldConfig<T[K]>
5091
+ ? NonEmptyFieldConfig<T[K]>
5017
5092
  : [FormSchema<U>]
5018
5093
  : NonNullable<T[K]> extends Date | File | Blob | AnyFunction
5019
- ? FieldConfig<T[K]>
5094
+ ? NonEmptyFieldConfig<T[K]>
5020
5095
  : FormSchema<NonNullable<T[K]>>;
5021
5096
  };
5022
5097
  ```
@@ -5628,6 +5703,9 @@ _Source: src/core/validation/validators/is-number.ts_
5628
5703
  bulk-сеттеры (`model.set`/`model.patch`, `patchValue`/`setValue`) пропускают это поле, чтобы
5629
5704
  значение из payload не затирало вычисляемое. Прикладной код напрямую обычно не вызывает.
5630
5705
 
5706
+ Идемпотентна на уровне флага: повторные вызовы наращивают счётчик ссылок, чтобы каждый оператор,
5707
+ пишущий в этот сигнал, был сбалансирован своим {@link unmarkDerived} при dispose.
5708
+
5631
5709
  **Signature:**
5632
5710
  ```typescript
5633
5711
  export function markDerived(signal: Signal<any>): void
@@ -5647,6 +5725,7 @@ markDerived(model.$.total);
5647
5725
 
5648
5726
  **See also:**
5649
5727
  - {@link isDerived} - проверка пометки
5728
+ - {@link unmarkDerived} - снять пометку при dispose оператора
5650
5729
 
5651
5730
  _Source: src/core/utils/derived-registry.ts_
5652
5731
 
@@ -6607,7 +6686,8 @@ _Source: src/core/utils/signal-node-registry.ts_
6607
6686
 
6608
6687
  Возвращает чистую функцию-валидатор `(value, control, root)`. Передаётся в `validate()`.
6609
6688
 
6610
- Пустыми считаются: `null`, `undefined`, `''` (пустая строка).
6689
+ Пустыми считаются: `null`, `undefined`, `''` (пустая строка), `[]` (пустой массив —
6690
+ обязательный multi-select / FormArray без выбранных элементов).
6611
6691
  Для boolean полей требуется значение `true`.
6612
6692
 
6613
6693
  **Signature:**
@@ -6707,26 +6787,31 @@ _Source: src/core/model/behaviors.ts_
6707
6787
 
6708
6788
  **Kind:** `function`
6709
6789
 
6710
- Выполняет функцию вне контекста effect
6790
+ Выполняет функцию вне контекста effect (отложенная запись на микротаск).
6711
6791
 
6712
- Более простой вариант для одиночного вызова
6792
+ Сбои fn (синхронный throw и async-rejection) маршрутизируются через {@link FormErrorHandler}, а не
6793
+ всплывают неперехваченными. Возвращает отменитель: вызов до срабатывания микротаска отменяет запись
6794
+ (liveness-охрана — не писать против снесённого узла при dispose в том же тике).
6713
6795
 
6714
6796
  **Signature:**
6715
6797
  ```typescript
6716
- export function runOutsideEffect(fn: () => void | Promise<void>): void
6798
+ export function runOutsideEffect(fn: () => void | Promise<void>): () => void
6717
6799
  ```
6718
6800
 
6719
6801
  **Parameters:**
6720
6802
  - `fn` — - Функция для выполнения
6721
6803
 
6804
+ **Returns:** Отменитель ещё не выполненной записи (no-op, если запись уже выполнена)
6805
+
6722
6806
  **Examples:**
6723
6807
 
6724
6808
  ```typescript
6725
6809
  effect(() => {
6726
6810
  const value = signal.value;
6727
- runOutsideEffect(() => {
6811
+ const cancel = runOutsideEffect(() => {
6728
6812
  otherSignal.value = transform(value);
6729
6813
  });
6814
+ onDispose(cancel); // отменить ожидающую запись, если узел снесут в этом же тике
6730
6815
  });
6731
6816
  ```
6732
6817
 
@@ -6738,8 +6823,8 @@ _Source: src/core/utils/safe-effect.ts_
6738
6823
 
6739
6824
  Создает callback, который выполняется вне контекста effect
6740
6825
 
6741
- Использует queueMicrotask для отложенного выполнения,
6742
- что позволяет модифицировать сигналы без вызова "Cycle detected"
6826
+ Откладывает вызов через {@link runOutsideEffect} — то есть получает маршрутизацию ошибок и
6827
+ обработку async-rejection «бесплатно».
6743
6828
 
6744
6829
  **Signature:**
6745
6830
  ```typescript
@@ -6777,7 +6862,9 @@ _Source: src/core/utils/safe-effect.ts_
6777
6862
 
6778
6863
  Создает версию callback с поддержкой debounce, безопасную для effect
6779
6864
 
6780
- Комбинирует debounce логику с выходом из effect контекста
6865
+ `withDebounce` уже откладывает вызов (таймер) — этого достаточно для выхода из effect-контекста,
6866
+ поэтому дополнительного `queueMicrotask` нет (устранён двойной defer). Сбои callback (в т.ч.
6867
+ async-rejection) маршрутизируются через {@link FormErrorHandler}.
6781
6868
 
6782
6869
  **Signature:**
6783
6870
  ```typescript
@@ -7104,6 +7191,42 @@ export type UnknownRecord = Record<string, unknown>;
7104
7191
 
7105
7192
  _Source: src/core/types/index.ts_
7106
7193
 
7194
+ ### unmarkDerived
7195
+
7196
+ **Kind:** `function`
7197
+
7198
+ Снять пометку производного сигнала (обратная операция к {@link markDerived}).
7199
+
7200
+ Вызывается из очистки (`onDispose`) оператора `compute`/`computeFrom`, когда поведение снимается,
7201
+ но модель/сигнал продолжают жить (динамическая перекоммутация: удалить под-схему → сохранить модель
7202
+ → bulk-set). Без этого сигнал остаётся помеченным навсегда, и bulk-сеттеры навсегда пропускают
7203
+ поле, которое больше ничем не вычисляется.
7204
+
7205
+ Учитывает счётчик ссылок: пометка снимается только когда снят последний владелец. Вызов на
7206
+ непомеченном сигнале — безопасный no-op.
7207
+
7208
+ **Signature:**
7209
+ ```typescript
7210
+ export function unmarkDerived(signal: Signal<any>): void
7211
+ ```
7212
+
7213
+ **Parameters:**
7214
+ - `signal` — - Сигнал значения из {@link FormModel}, ранее помеченный {@link markDerived}
7215
+
7216
+ **Examples:**
7217
+
7218
+ ```typescript
7219
+ import { markDerived, unmarkDerived } from '@reformer/core';
7220
+
7221
+ markDerived(model.$.total);
7222
+ onDispose(() => unmarkDerived(model.$.total)); // при снятии compute снова разрешаем bulk-set
7223
+ ```
7224
+
7225
+ **See also:**
7226
+ - {@link markDerived} - пометить сигнал производным
7227
+
7228
+ _Source: src/core/utils/derived-registry.ts_
7229
+
7107
7230
  ### url
7108
7231
 
7109
7232
  **Kind:** `function`
@@ -7605,16 +7728,18 @@ _Source: src/hooks/useFormControlValue.ts_
7605
7728
 
7606
7729
  **Kind:** `interface`
7607
7730
 
7608
- Опции для функции validateAsync.
7609
-
7610
7731
  **Signature:**
7611
7732
  ```typescript
7612
7733
  export interface ValidateAsyncOptions extends ValidateOptions {
7613
- /** Задержка перед выполнением валидации (в мс) */
7734
+ /** Задержка перед выполнением валидации (в мс). */
7614
7735
  debounce?: number;
7615
7736
  }
7616
7737
  ```
7617
7738
 
7739
+ **Deprecated:** Осиротевший остаток удалённого оператора `validateAsync` (Ф7): опция `debounce`
7740
+ подключалась тем оператором, которого больше нет. Рантаймом не потребляется; экспортируется
7741
+ только ради обратной совместимости.
7742
+
7618
7743
  _Source: src/core/types/validation-schema.ts_
7619
7744
 
7620
7745
  ### validateFormModel
@@ -7709,14 +7834,16 @@ _Source: src/core/model/validate-model.ts_
7709
7834
 
7710
7835
  **Kind:** `interface`
7711
7836
 
7712
- Опции для функции validate.
7837
+ Опции валидатора-фабрики (`required()`/`pattern()`/…). Передаются вторым (или последним)
7838
+ аргументом в фабрику и попадают в возвращаемую {@link ValidationError}.
7713
7839
 
7714
7840
  **Signature:**
7715
7841
  ```typescript
7716
7842
  export interface ValidateOptions {
7717
- /** Сообщение об ошибке */
7843
+ /** Готовое сообщение об ошибке. Если не задано, валидаторы кладут `''`, и отображаемый текст
7844
+ * резолвится из `code` (см. резолвер сообщений в `@reformer/cdk`). */
7718
7845
  message?: string;
7719
- /** Параметры ошибки */
7846
+ /** Параметры ошибки (подстановка в шаблон сообщения / i18n). */
7720
7847
  params?: Record<string, FormValue>;
7721
7848
  }
7722
7849
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reformer/core",
3
- "version": "9.0.0-beta.1",
3
+ "version": "9.0.0-beta.2",
4
4
  "description": "Reactive form state management library for React with signals-based architecture",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,147 +0,0 @@
1
- import { effect as u } from "@preact/signals-core";
2
- const o = /* @__PURE__ */ new WeakSet();
3
- function h(e) {
4
- o.add(e);
5
- }
6
- function p(e) {
7
- return o.has(e);
8
- }
9
- const d = /* @__PURE__ */ new WeakMap();
10
- function y(e, n) {
11
- d.set(e, n);
12
- }
13
- function v(e) {
14
- return d.get(e);
15
- }
16
- function b(e) {
17
- return (...n) => {
18
- queueMicrotask(() => e(...n));
19
- };
20
- }
21
- function s(e) {
22
- queueMicrotask(e);
23
- }
24
- function w(e, n) {
25
- return () => {
26
- n(() => {
27
- queueMicrotask(e);
28
- });
29
- };
30
- }
31
- function W(e, n, r, a) {
32
- return u(() => {
33
- const t = e.map((f) => f.value);
34
- if (a?.when && !a.when(...t)) return;
35
- const l = r(...t);
36
- n.peek() !== l && (n.value = l);
37
- });
38
- }
39
- function x(e, n, r) {
40
- return u(() => {
41
- const a = e.value;
42
- if (r?.when && !r.when()) return;
43
- const t = r?.transform ? r.transform(a) : a;
44
- n.peek() !== t && (n.value = t);
45
- });
46
- }
47
- function F(e, n, r) {
48
- let a = !0;
49
- return u(() => {
50
- const t = e.value;
51
- a && (a = !1, !r?.immediate) || n(t);
52
- });
53
- }
54
- function k(e, n, r) {
55
- return u(() => {
56
- const a = n(), t = v(e);
57
- t && s(() => {
58
- a ? t.enable() : (t.disable(), r?.resetOnDisable && t.reset());
59
- });
60
- });
61
- }
62
- function g(e, n, r) {
63
- return k(e, () => !n(), r);
64
- }
65
- function D(e, n) {
66
- let r = !1;
67
- return u(() => {
68
- const a = e.value;
69
- r || s(() => {
70
- const t = n(a);
71
- if (e.peek() !== t) {
72
- r = !0;
73
- try {
74
- e.value = t;
75
- } finally {
76
- r = !1;
77
- }
78
- }
79
- });
80
- });
81
- }
82
- function M(e, n, r) {
83
- const a = r?.resetValue ?? null;
84
- return u(() => {
85
- const t = n();
86
- s(() => {
87
- t && e.peek() !== a && (e.value = a);
88
- });
89
- });
90
- }
91
- function q(e, n, r) {
92
- const a = r?.transform;
93
- let t = !1;
94
- const l = u(() => {
95
- const i = e.value;
96
- t || s(() => {
97
- t = !0;
98
- try {
99
- const c = a ? a(i) : i;
100
- n.peek() !== c && (n.value = c);
101
- } finally {
102
- t = !1;
103
- }
104
- });
105
- }), f = u(() => {
106
- const i = n.value;
107
- t || s(() => {
108
- t = !0;
109
- try {
110
- e.peek() !== i && (e.value = i);
111
- } finally {
112
- t = !1;
113
- }
114
- });
115
- });
116
- return () => {
117
- l(), f();
118
- };
119
- }
120
- function S(e, n) {
121
- let r = !0;
122
- return u(() => {
123
- if (e.forEach((a) => a.value), r) {
124
- r = !1;
125
- return;
126
- }
127
- s(() => n());
128
- });
129
- }
130
- export {
131
- M as a,
132
- S as b,
133
- x as c,
134
- y as d,
135
- k as e,
136
- b as f,
137
- v as g,
138
- w as h,
139
- p as i,
140
- W as j,
141
- g as k,
142
- h as m,
143
- s as r,
144
- q as s,
145
- D as t,
146
- F as w
147
- };