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

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 (113) hide show
  1. package/README.md +7 -3
  2. package/dist/behaviors.js +216 -179
  3. package/dist/create-model-BBRHhHwK.js +30 -0
  4. package/dist/en-CjAqn7h0.js +41 -0
  5. package/dist/form/behaviors/collections.d.ts +14 -1
  6. package/dist/form/behaviors/context.d.ts +18 -2
  7. package/dist/form/behaviors/index.d.ts +3 -1
  8. package/dist/form/behaviors/internals.d.ts +9 -1
  9. package/dist/form/behaviors/schema-operators.d.ts +76 -0
  10. package/dist/form/behaviors/types.d.ts +15 -3
  11. package/dist/form/create-core-form.d.ts +5 -3
  12. package/dist/form/create-form.d.ts +28 -53
  13. package/dist/form/form-bundle.d.ts +136 -0
  14. package/dist/form/index.d.ts +6 -1
  15. package/dist/form/nodes/field-node.d.ts +2 -2
  16. package/dist/form/nodes/group-node.d.ts +1 -1
  17. package/dist/form/nodes/model-array-node.d.ts +10 -5
  18. package/dist/form/schema-controller.d.ts +137 -0
  19. package/dist/form/schema-subtree.d.ts +28 -0
  20. package/dist/form/signal-node-registry.d.ts +22 -16
  21. package/dist/form/types/contracts.d.ts +6 -0
  22. package/dist/form/types/deep-schema.d.ts +2 -2
  23. package/dist/form/types/form-proxy.d.ts +1 -1
  24. package/dist/form/types/schema-node.d.ts +37 -12
  25. package/dist/form/types/validation-schema.d.ts +13 -1
  26. package/dist/form/validation/context.d.ts +6 -1
  27. package/dist/form/validation/index.d.ts +1 -1
  28. package/dist/form/validation/operators.d.ts +74 -7
  29. package/dist/form/validation/types.d.ts +2 -1
  30. package/dist/form/validators/validation-error.d.ts +8 -0
  31. package/dist/i18n/descriptor.d.ts +54 -0
  32. package/dist/i18n/en.json.d.ts +45 -0
  33. package/dist/i18n/loader.d.ts +71 -0
  34. package/dist/i18n/locale-store.d.ts +39 -0
  35. package/dist/i18n/locale.d.ts +90 -0
  36. package/dist/i18n/message-format.d.ts +116 -0
  37. package/dist/i18n/resolve-localized.d.ts +17 -0
  38. package/dist/i18n/ru.json.d.ts +45 -0
  39. package/dist/i18n/translator.d.ts +50 -0
  40. package/dist/i18n/validation-message.d.ts +18 -0
  41. package/dist/i18n.d.ts +31 -0
  42. package/dist/i18n.js +303 -0
  43. package/dist/index-BrNkQj1y.js +105 -0
  44. package/dist/index.js +737 -743
  45. package/dist/loader-Dv_Hl7JC.js +216 -0
  46. package/dist/locale/en.d.ts +2 -0
  47. package/dist/locale/en.js +5 -0
  48. package/dist/locale/en.json +42 -0
  49. package/dist/locale/index.d.ts +18 -0
  50. package/dist/locale/ru.d.ts +2 -0
  51. package/dist/locale/ru.js +41 -0
  52. package/dist/locale/ru.json +42 -0
  53. package/dist/locale.d.ts +2 -0
  54. package/dist/locale.js +9 -0
  55. package/dist/model/create-model.d.ts +17 -3
  56. package/dist/model/index.d.ts +8 -3
  57. package/dist/model/model-nodes.d.ts +67 -4
  58. package/dist/model/model-signals-proxy.d.ts +33 -1
  59. package/dist/model/model-value-proxy.d.ts +76 -0
  60. package/dist/model/types.d.ts +26 -9
  61. package/dist/model-value-proxy-caTmAiUd.js +419 -0
  62. package/dist/model.js +21 -17
  63. package/dist/node-BeV_lV5i.js +100 -0
  64. package/dist/platforms/react/form-bundle-context.d.ts +27 -0
  65. package/dist/platforms/react/hooks/types.d.ts +6 -3
  66. package/dist/platforms/react/hooks/useFormControlValue.d.ts +2 -1
  67. package/dist/platforms/react/i18n/context.d.ts +13 -0
  68. package/dist/platforms/react/i18n/hooks.d.ts +59 -0
  69. package/dist/platforms/react/i18n/provider.d.ts +52 -0
  70. package/dist/platforms/react/index.d.ts +1 -0
  71. package/dist/strategy-DgzELWno.js +204 -0
  72. package/dist/validation-error-B-U3RsV0.js +11 -0
  73. package/dist/validation.js +7 -6
  74. package/dist/validators/email.js +5 -8
  75. package/dist/validators/future-date.js +8 -11
  76. package/dist/validators/integer.js +4 -7
  77. package/dist/validators/is-date.js +5 -8
  78. package/dist/validators/is-number.js +4 -7
  79. package/dist/validators/max-age.js +8 -11
  80. package/dist/validators/max-date.js +8 -11
  81. package/dist/validators/max-length.js +5 -8
  82. package/dist/validators/max.js +4 -7
  83. package/dist/validators/min-age.js +8 -11
  84. package/dist/validators/min-date.js +8 -11
  85. package/dist/validators/min-length.js +5 -8
  86. package/dist/validators/min.js +4 -7
  87. package/dist/validators/multiple-of.js +5 -8
  88. package/dist/validators/non-negative.js +4 -7
  89. package/dist/validators/non-zero.js +4 -7
  90. package/dist/validators/past-date.js +8 -11
  91. package/dist/validators/pattern.js +5 -8
  92. package/dist/validators/phone.js +6 -9
  93. package/dist/validators/required.js +4 -7
  94. package/dist/validators/url.js +9 -14
  95. package/dist/validators.js +84 -106
  96. package/docs/llms/03-api-signatures.md +10 -4
  97. package/docs/llms/09-formschema.md +5 -5
  98. package/docs/llms/10-arrays.md +52 -3
  99. package/docs/llms/16-ui-components.md +2 -2
  100. package/docs/llms/18-conditional-fields.md +1 -1
  101. package/docs/llms/21-array-operations.md +26 -0
  102. package/docs/llms/28-submit-and-reset.md +3 -3
  103. package/docs/llms/29-async-preload.md +4 -4
  104. package/docs/llms/33-validation-strategy.md +2 -2
  105. package/docs/llms/34-i18n.md +272 -0
  106. package/docs/llms/35-unified-contract.md +246 -0
  107. package/llms-index.json +1 -1
  108. package/llms.txt +2746 -229
  109. package/package.json +22 -3
  110. package/dist/create-model-Dxh9cfLp.js +0 -97
  111. package/dist/model-signals-proxy-mV3WGdlO.js +0 -233
  112. package/dist/node-DGEFMTQu.js +0 -18
  113. package/dist/strategy-DA3JwxsR.js +0 -170
@@ -0,0 +1,216 @@
1
+ class $ extends Error {
2
+ name = "MessageSyntaxError";
3
+ }
4
+ const v = (t) => `⟦${t}⟧`, b = /* @__PURE__ */ new Map(), y = /* @__PURE__ */ new Map();
5
+ function g(t) {
6
+ let e = 0;
7
+ const s = (r) => {
8
+ throw new $(`${r} (позиция ${e}) в сообщении: ${t}`);
9
+ }, o = (r, i) => r === "{" || r === "}" || i && r === "#", n = () => {
10
+ for (; e < t.length && /\s/.test(t[e]); ) e++;
11
+ }, f = () => {
12
+ const r = e;
13
+ for (; e < t.length && !/[\s,{}#]/.test(t[e]); ) e++;
14
+ return e === r && s("ожидалось имя"), t.slice(r, e);
15
+ }, l = (r) => {
16
+ if (e++, t[e] === "'")
17
+ return e++, "'";
18
+ if (!o(t[e], r)) return "'";
19
+ let i = "";
20
+ for (; e < t.length; )
21
+ if (t[e] !== "'") i += t[e++];
22
+ else if (t[e + 1] === "'")
23
+ i += "'", e += 2;
24
+ else
25
+ return e++, i;
26
+ return i;
27
+ }, h = (r, i) => {
28
+ const u = [];
29
+ let m = "";
30
+ const p = () => {
31
+ m !== "" && (u.push({ kind: "text", value: m }), m = "");
32
+ };
33
+ for (; e < t.length; ) {
34
+ const d = t[e];
35
+ if (d === "}") {
36
+ r || s("непарная «}» — литерал пишется как '}'");
37
+ break;
38
+ }
39
+ d === "'" ? m += l(i !== null) : d === "#" && i !== null ? (e++, p(), u.push({ kind: "number", name: i })) : d === "{" ? (p(), u.push(a(i))) : (m += d, e++);
40
+ }
41
+ return p(), u;
42
+ }, c = (r, i, u) => {
43
+ const m = /* @__PURE__ */ new Map();
44
+ for (n(); e < t.length && t[e] !== "}"; ) {
45
+ const p = f();
46
+ n(), t[e] !== "{" && s(`ожидалась «{» после ветки «${p}»`), e++, m.set(p, h(!0, u)), t[e] !== "}" && s(`ветка «${p}» не закрыта`), e++, n();
47
+ }
48
+ return t[e] !== "}" && s(`«${r}, ${i}» не закрыт «}»`), e++, m.has("other") || s(`в «${r}, ${i}» нет обязательной ветки other`), m;
49
+ }, a = (r) => {
50
+ e++, n();
51
+ const i = f();
52
+ if (n(), t[e] === "}")
53
+ return e++, { kind: "arg", name: i };
54
+ t[e] !== "," && s(`ожидались «,» или «}» после аргумента «${i}»`), e++, n();
55
+ const u = f();
56
+ return u !== "plural" && u !== "select" && s(`неизвестный тип «${u}» у «${i}» — поддерживаются plural и select`), n(), t[e] !== "," && s(`ожидалась «,» после «${u}»`), e++, {
57
+ kind: u,
58
+ name: i,
59
+ branches: c(i, u, u === "plural" ? i : r)
60
+ };
61
+ };
62
+ return h(!1, null);
63
+ }
64
+ function M(t, e, s, o = {}) {
65
+ const n = o.onMissingArgument ?? v, f = (h) => {
66
+ const c = e[h];
67
+ return typeof c == "number" && Number.isFinite(c) ? c : null;
68
+ }, l = (h) => {
69
+ let c = "";
70
+ for (const a of h)
71
+ if (a.kind === "text") c += a.value;
72
+ else if (a.kind === "arg")
73
+ c += e[a.name] === void 0 ? n(a.name) : String(e[a.name]);
74
+ else if (a.kind === "number") {
75
+ const r = f(a.name);
76
+ c += r === null ? n(a.name) : j(s).format(r);
77
+ } else if (a.kind === "plural") {
78
+ const r = f(a.name);
79
+ c += r === null ? n(a.name) : l(
80
+ a.branches.get(`=${r}`) ?? a.branches.get(x(s).select(r)) ?? a.branches.get("other")
81
+ );
82
+ } else {
83
+ const r = e[a.name];
84
+ c += r === void 0 ? n(a.name) : l(a.branches.get(String(r)) ?? a.branches.get("other"));
85
+ }
86
+ return c;
87
+ };
88
+ return l(t);
89
+ }
90
+ function x(t) {
91
+ let e = b.get(t);
92
+ return e === void 0 && b.set(t, e = new Intl.PluralRules(t)), e;
93
+ }
94
+ function j(t) {
95
+ let e = y.get(t);
96
+ return e === void 0 && y.set(t, e = new Intl.NumberFormat(t)), e;
97
+ }
98
+ function E(t, e = {}) {
99
+ const s = /* @__PURE__ */ new Map();
100
+ return (o, n = {}) => {
101
+ let f = s.get(o);
102
+ return f === void 0 && s.set(o, f = g(o)), M(f, n, t, e);
103
+ };
104
+ }
105
+ const P = Object.freeze({
106
+ code: "en",
107
+ messages: Object.freeze({})
108
+ });
109
+ function O(t, e) {
110
+ const { messages: s, ...o } = e;
111
+ return {
112
+ ...t,
113
+ ...o,
114
+ messages: s === void 0 ? t.messages : { ...t.messages, ...s }
115
+ };
116
+ }
117
+ function k(t, e) {
118
+ return Object.prototype.hasOwnProperty.call(t, e);
119
+ }
120
+ function w(t, e = /* @__PURE__ */ new Set()) {
121
+ for (const s of t)
122
+ if (s.kind !== "text" && (e.add(s.name), s.kind === "plural" || s.kind === "select"))
123
+ for (const o of s.branches.values()) w(o, e);
124
+ return e;
125
+ }
126
+ function F(t, e) {
127
+ const s = [], o = /* @__PURE__ */ new Map();
128
+ for (const [n, f] of Object.entries(t))
129
+ try {
130
+ o.set(n, g(f));
131
+ } catch (l) {
132
+ s.push({
133
+ key: n,
134
+ kind: "syntax",
135
+ message: l instanceof Error ? l.message : String(l)
136
+ });
137
+ }
138
+ if (e === void 0) return s;
139
+ for (const n of Object.keys(t))
140
+ k(e, n) || s.push({ key: n, kind: "extra", message: "ключа нет в образце" });
141
+ for (const [n, f] of Object.entries(e)) {
142
+ if (!k(t, n)) {
143
+ s.push({ key: n, kind: "missing", message: "ключ есть в образце, но не переведён" });
144
+ continue;
145
+ }
146
+ const l = o.get(n);
147
+ if (l === void 0) continue;
148
+ const h = [...w(g(f))].sort().join(", "), c = [...w(l)].sort().join(", ");
149
+ h !== c && s.push({
150
+ key: n,
151
+ kind: "arguments",
152
+ message: `образец ждёт {${h}}, словарь — {${c}}`
153
+ });
154
+ }
155
+ return s;
156
+ }
157
+ function S(t) {
158
+ const e = t.messages;
159
+ return e !== null && typeof e == "object";
160
+ }
161
+ function L(t) {
162
+ if (t != null) {
163
+ if (typeof t != "object" || Array.isArray(t))
164
+ throw new TypeError("источник локали вернул не объект");
165
+ return S(t) ? t : { messages: t };
166
+ }
167
+ }
168
+ function A(t) {
169
+ const e = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map(), o = (n) => {
170
+ const f = e.get(n);
171
+ if (f !== void 0) return Promise.resolve(f);
172
+ const l = s.get(n);
173
+ if (l !== void 0) return l;
174
+ const h = Promise.all(t.map(async (c) => L(await c(n)))).then((c) => {
175
+ let a = { code: n, messages: {} };
176
+ for (const r of c)
177
+ r !== void 0 && (a = O(a, r));
178
+ return a = { ...a, code: n }, e.set(n, a), a;
179
+ }).finally(() => {
180
+ s.delete(n);
181
+ });
182
+ return s.set(n, h), h;
183
+ };
184
+ return Object.assign(o, {
185
+ peek: (n) => e.get(n),
186
+ preload: o
187
+ });
188
+ }
189
+ function N(t, e) {
190
+ return async (s) => {
191
+ const o = t(s), n = await fetch(o, e);
192
+ if (!n.ok)
193
+ throw new Error(`локаль «${s}»: ${o} ответил ${n.status}`);
194
+ return await n.json();
195
+ };
196
+ }
197
+ function C(t) {
198
+ return (e) => {
199
+ const s = e.split("-")[0].toLowerCase(), n = (Object.prototype.hasOwnProperty.call(t, e) ? t[e] : void 0) ?? (Object.prototype.hasOwnProperty.call(t, s) ? t[s] : void 0);
200
+ return n === void 0 ? null : n();
201
+ };
202
+ }
203
+ export {
204
+ P as D,
205
+ $ as M,
206
+ A as a,
207
+ C as b,
208
+ E as c,
209
+ N as d,
210
+ O as e,
211
+ M as f,
212
+ k as h,
213
+ w as m,
214
+ g as p,
215
+ F as v
216
+ };
@@ -0,0 +1,2 @@
1
+ import { FormLocale } from '../i18n/locale';
2
+ export declare const en: FormLocale;
@@ -0,0 +1,5 @@
1
+ import { m as e } from "../en-CjAqn7h0.js";
2
+ const o = { code: "en", weekStartsOn: 0, messages: e };
3
+ export {
4
+ o as en
5
+ };
@@ -0,0 +1,42 @@
1
+ {
2
+ "code": "en",
3
+ "weekStartsOn": 0,
4
+ "messages": {
5
+ "format.fileSize.b": "{value} B",
6
+ "format.fileSize.kb": "{value} KB",
7
+ "format.fileSize.mb": "{value} MB",
8
+ "format.fileSize.gb": "{value} GB",
9
+ "format.fileSize.tb": "{value} TB",
10
+ "validation.required": "This field is required",
11
+ "validation.pattern": "Invalid format",
12
+ "validation.email": "Enter a valid email address",
13
+ "validation.url": "Enter a valid URL",
14
+ "validation.url_protocol": "The URL must use one of these protocols: {allowedProtocols}",
15
+ "validation.phone": "Enter a valid phone number",
16
+ "validation.min": "Must be at least {min}",
17
+ "validation.max": "Must be at most {max}",
18
+ "validation.minLength": "Enter at least {minLength, plural, one{# character} other{# characters}}",
19
+ "validation.maxLength": "Enter no more than {maxLength, plural, one{# character} other{# characters}}",
20
+ "validation.isNumber": "Enter a number",
21
+ "validation.integer": "Enter a whole number",
22
+ "validation.multipleOf": "Must be a multiple of {multipleOf}",
23
+ "validation.nonNegative": "Must not be negative",
24
+ "validation.nonZero": "Must not be zero",
25
+ "validation.date_invalid": "Enter a valid date",
26
+ "validation.date_min": "The date must not be earlier than {minDate}",
27
+ "validation.date_max": "The date must not be later than {maxDate}",
28
+ "validation.date_past": "The date must not be in the past",
29
+ "validation.date_future": "The date must not be in the future",
30
+ "validation.date_min_age": "Age must be at least {minAge}",
31
+ "validation.date_max_age": "Age must be at most {maxAge}",
32
+ "validation.fileType": "File {fileName} has a type that is not allowed. Allowed: {accept}",
33
+ "validation.maxFileSize": "File {fileName} is larger than {maxFileSize}",
34
+ "validation.minFileSize": "File {fileName} is smaller than {minFileSize}",
35
+ "validation.maxFiles": "No more than {maxFiles, plural, one{# file} other{# files}}",
36
+ "validation.minFiles": "At least {minFiles, plural, one{# file} other{# files}}",
37
+ "validation.maxTotalFileSize": "The total size of the files exceeds {maxTotalFileSize}",
38
+ "validation.fileExists": "File {fileName} has already been added",
39
+ "validation.uploadAborted": "Upload cancelled",
40
+ "validation.uploadFailed": "Failed to upload {fileName}"
41
+ }
42
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Подпуть `@reformer/core/locale` — загрузчик встроенных локалей ядра (тексты ошибок валидации,
3
+ * единицы размера файла). Приложению с ui-kit он не нужен: `loadKitLocale` из
4
+ * `@reformer/ui-kit/locale` уже включает словари ядра и cdk.
5
+ *
6
+ * @module locale
7
+ */
8
+ /** Языки, которые ядро поставляет само. */
9
+ export declare const CORE_LOCALES: readonly string[];
10
+ /**
11
+ * Источник локалей ядра для `createLocaleLoader`: чанк нужного языка по запросу.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const loadLocale = createLocaleLoader([loadCoreLocale, fetchMessages((code) => `/locales/${code}.json`)]);
16
+ * ```
17
+ */
18
+ export declare const loadCoreLocale: import('../i18n').LocaleSource;
@@ -0,0 +1,2 @@
1
+ import { FormLocale } from '../i18n/locale';
2
+ export declare const ru: FormLocale;
@@ -0,0 +1,41 @@
1
+ const a = {
2
+ "format.fileSize.b": "{value} Б",
3
+ "format.fileSize.kb": "{value} КБ",
4
+ "format.fileSize.mb": "{value} МБ",
5
+ "format.fileSize.gb": "{value} ГБ",
6
+ "format.fileSize.tb": "{value} ТБ",
7
+ "validation.required": "Обязательное поле",
8
+ "validation.pattern": "Неверный формат",
9
+ "validation.email": "Введите корректный email",
10
+ "validation.url": "Введите корректный адрес (URL)",
11
+ "validation.url_protocol": "Адрес должен начинаться с одного из протоколов: {allowedProtocols}",
12
+ "validation.phone": "Введите корректный номер телефона",
13
+ "validation.min": "Не меньше {min}",
14
+ "validation.max": "Не больше {max}",
15
+ "validation.minLength": "Не меньше {minLength, plural, one{# символа} few{# символов} many{# символов} other{# символа}}",
16
+ "validation.maxLength": "Не больше {maxLength, plural, one{# символа} few{# символов} many{# символов} other{# символа}}",
17
+ "validation.isNumber": "Введите число",
18
+ "validation.integer": "Введите целое число",
19
+ "validation.multipleOf": "Число должно быть кратно {multipleOf}",
20
+ "validation.nonNegative": "Число не может быть отрицательным",
21
+ "validation.nonZero": "Число не может быть нулём",
22
+ "validation.date_invalid": "Введите корректную дату",
23
+ "validation.date_min": "Дата не раньше {minDate}",
24
+ "validation.date_max": "Дата не позже {maxDate}",
25
+ "validation.date_past": "Дата не может быть в прошлом",
26
+ "validation.date_future": "Дата не может быть в будущем",
27
+ "validation.date_min_age": "Возраст не меньше {minAge, plural, one{# года} few{# лет} many{# лет} other{# года}}",
28
+ "validation.date_max_age": "Возраст не больше {maxAge, plural, one{# года} few{# лет} many{# лет} other{# года}}",
29
+ "validation.fileType": "Недопустимый тип файла {fileName}. Разрешено: {accept}",
30
+ "validation.maxFileSize": "Файл {fileName} больше {maxFileSize}",
31
+ "validation.minFileSize": "Файл {fileName} меньше {minFileSize}",
32
+ "validation.maxFiles": "Не больше {maxFiles, plural, one{# файла} few{# файлов} many{# файлов} other{# файла}}",
33
+ "validation.minFiles": "Не меньше {minFiles, plural, one{# файла} few{# файлов} many{# файлов} other{# файла}}",
34
+ "validation.maxTotalFileSize": "Суммарный размер файлов больше {maxTotalFileSize}",
35
+ "validation.fileExists": "Файл {fileName} уже добавлен",
36
+ "validation.uploadAborted": "Загрузка прервана",
37
+ "validation.uploadFailed": "Не удалось загрузить файл {fileName}"
38
+ }, i = a, e = { code: "ru", weekStartsOn: 1, messages: i };
39
+ export {
40
+ e as ru
41
+ };
@@ -0,0 +1,42 @@
1
+ {
2
+ "code": "ru",
3
+ "weekStartsOn": 1,
4
+ "messages": {
5
+ "format.fileSize.b": "{value} Б",
6
+ "format.fileSize.kb": "{value} КБ",
7
+ "format.fileSize.mb": "{value} МБ",
8
+ "format.fileSize.gb": "{value} ГБ",
9
+ "format.fileSize.tb": "{value} ТБ",
10
+ "validation.required": "Обязательное поле",
11
+ "validation.pattern": "Неверный формат",
12
+ "validation.email": "Введите корректный email",
13
+ "validation.url": "Введите корректный адрес (URL)",
14
+ "validation.url_protocol": "Адрес должен начинаться с одного из протоколов: {allowedProtocols}",
15
+ "validation.phone": "Введите корректный номер телефона",
16
+ "validation.min": "Не меньше {min}",
17
+ "validation.max": "Не больше {max}",
18
+ "validation.minLength": "Не меньше {minLength, plural, one{# символа} few{# символов} many{# символов} other{# символа}}",
19
+ "validation.maxLength": "Не больше {maxLength, plural, one{# символа} few{# символов} many{# символов} other{# символа}}",
20
+ "validation.isNumber": "Введите число",
21
+ "validation.integer": "Введите целое число",
22
+ "validation.multipleOf": "Число должно быть кратно {multipleOf}",
23
+ "validation.nonNegative": "Число не может быть отрицательным",
24
+ "validation.nonZero": "Число не может быть нулём",
25
+ "validation.date_invalid": "Введите корректную дату",
26
+ "validation.date_min": "Дата не раньше {minDate}",
27
+ "validation.date_max": "Дата не позже {maxDate}",
28
+ "validation.date_past": "Дата не может быть в прошлом",
29
+ "validation.date_future": "Дата не может быть в будущем",
30
+ "validation.date_min_age": "Возраст не меньше {minAge, plural, one{# года} few{# лет} many{# лет} other{# года}}",
31
+ "validation.date_max_age": "Возраст не больше {maxAge, plural, one{# года} few{# лет} many{# лет} other{# года}}",
32
+ "validation.fileType": "Недопустимый тип файла {fileName}. Разрешено: {accept}",
33
+ "validation.maxFileSize": "Файл {fileName} больше {maxFileSize}",
34
+ "validation.minFileSize": "Файл {fileName} меньше {minFileSize}",
35
+ "validation.maxFiles": "Не больше {maxFiles, plural, one{# файла} few{# файлов} many{# файлов} other{# файла}}",
36
+ "validation.minFiles": "Не меньше {minFiles, plural, one{# файла} few{# файлов} many{# файлов} other{# файла}}",
37
+ "validation.maxTotalFileSize": "Суммарный размер файлов больше {maxTotalFileSize}",
38
+ "validation.fileExists": "Файл {fileName} уже добавлен",
39
+ "validation.uploadAborted": "Загрузка прервана",
40
+ "validation.uploadFailed": "Не удалось загрузить файл {fileName}"
41
+ }
42
+ }
@@ -0,0 +1,2 @@
1
+ export * from './locale/index'
2
+ export {}
package/dist/locale.js ADDED
@@ -0,0 +1,9 @@
1
+ import { b as e } from "./loader-Dv_Hl7JC.js";
2
+ const n = ["en", "ru"], t = e({
3
+ en: () => import("./locale/en.js").then((o) => o.en),
4
+ ru: () => import("./locale/ru.js").then((o) => o.ru)
5
+ });
6
+ export {
7
+ n as CORE_LOCALES,
8
+ t as loadCoreLocale
9
+ };
@@ -3,13 +3,27 @@ import { FormModel, PathAwareSignal } from './types';
3
3
  * Обойти ВСЕ листовые сигналы модели (включая элементы массивов), вызвав `visit` на каждом.
4
4
  *
5
5
  * Внутри реактивного `effect` служит подпиской «любое поле изменилось»: `visit(sig => void sig.value)`
6
- * подписывает на значения листьев, а обход массивов через `items.value` — на их состав. Так строятся
7
- * триггеры `change`/`blur` стратегий валидации (см. `createFormValidation`), тем же паттерном, что
8
- * `revalidateWhen`, но без ручного перечисления зависимостей.
6
+ * подписывает на значения листьев, а обход массивов через `items.value` — на их состав. Тот же
7
+ * паттерн, что `revalidateWhen`, но без ручного перечисления зависимостей.
8
+ *
9
+ * Сами массивы обход не посещает — только их элементы. Поле, привязанное к массиву целиком
10
+ * (мультивыбор), живёт на сигнале массива; чтобы увидеть и его, берите {@link eachValueSignal}.
9
11
  *
10
12
  * @group Model
11
13
  */
12
14
  export declare function eachLeafSignal<T>(model: FormModel<T>, visit: (signal: PathAwareSignal<unknown>) => void): void;
15
+ /**
16
+ * Обойти ВСЕ ручки значений модели: листья (включая элементы массивов) И сами массивы.
17
+ *
18
+ * Отличие от {@link eachLeafSignal} — узлы-массивы: массив бывает одним значением поля
19
+ * (`{ value: model.$.tags, component }`), и нода такого поля привязана к сигналу массива, а не
20
+ * к сигналам элементов. Обходчику, которому нужна именно НОДА поля (стратегия `blur` подписывается
21
+ * на её `touched`), одних листьев мало — он не увидел бы мультивыбор. Так строятся триггеры
22
+ * `change`/`blur` стратегий валидации (см. `createFormValidation`).
23
+ *
24
+ * @group Model
25
+ */
26
+ export declare function eachValueSignal<T>(model: FormModel<T>, visit: (signal: PathAwareSignal<unknown>) => void): void;
13
27
  /**
14
28
  * Создать реактивную модель данных формы (слой M1).
15
29
  *
@@ -13,7 +13,9 @@
13
13
  * Состав (реэкспорты — из тех же файлов, что использует зонтик `.`, поэтому `.` и `/model` — один
14
14
  * module-инстанс: общий `derived`-WeakMap, единая идентичность `Signal`):
15
15
  * - **модель** — `createModel` + типы `FormModel`/`ModelArray`/… + `PathAwareSignal`;
16
- * - **обход листьев** — `eachLeafSignal` (подписка «любое поле изменилось»);
16
+ * - **шаблон элемента массива** — `arrayOf(blank)`: что кладёт `push()` без значения;
17
+ * - **обход значений** — `eachLeafSignal` (листья) и `eachValueSignal` (листья и массивы целиком);
18
+ * - **ручка → фасад** — `modelOf(model.$.<группа | массив>)`: под-модель или фасад массива;
17
19
  * - **value-операции** (реактивные правила) — `computeFrom`/`copyFrom`/`watchField`/`transformValue`/
18
20
  * `resetWhen`/`syncFields`/`revalidateWhen`;
19
21
  * - **producer-owned флаг** — `markDerived`/`isDerived`/`unmarkDerived`;
@@ -22,9 +24,12 @@
22
24
  * @group Model
23
25
  * @module model
24
26
  */
25
- export { createModel, eachLeafSignal } from './create-model';
27
+ export { createModel, eachLeafSignal, eachValueSignal } from './create-model';
28
+ export { arrayOf } from './model-nodes';
26
29
  export type { FormModel, ModelArray, ModelArraySignals, ModelGroupSignals, ModelObject, ModelValue, ModelSignals, ModelApi, PathAwareSignal, } from './types';
27
- export { isModelContainerSignal } from './model-signals-proxy';
30
+ export { isModelContainerSignal, isValueSignal } from './model-signals-proxy';
31
+ export { modelOf } from './model-value-proxy';
32
+ export type { ModelOf } from './model-value-proxy';
28
33
  export { computeFrom, copyFrom, watchField, transformValue, resetWhen, syncFields, revalidateWhen, } from './behaviors-value';
29
34
  export type { BehaviorCleanup } from './behaviors-value';
30
35
  export { markDerived, isDerived, unmarkDerived } from './derived-registry';
@@ -1,6 +1,50 @@
1
1
  import { Signal } from '@preact/signals-core';
2
2
  import { PathAwareSignal } from './types';
3
3
  export declare const isIndexKey: (key: string) => boolean;
4
+ /** Фабрика нового элемента массива. */
5
+ type Blank = () => unknown;
6
+ /**
7
+ * Образец того же места модели внутри шаблона родительского массива. Ленивый: шаблон родителя
8
+ * ради него вызывается, только когда образец действительно понадобился.
9
+ */
10
+ type Shape = () => unknown;
11
+ /**
12
+ * Массив модели с шаблоном нового элемента.
13
+ *
14
+ * Шаблон — фабрика значения, которое получает `push()` / `insertAt(i)` без аргумента: кнопка
15
+ * «Добавить» секции массива, `model.items.push()` в поведении. Объявляется там же, где остальные
16
+ * начальные значения, — в модели; схеме и JSX знать его не нужно.
17
+ *
18
+ * Возвращает обычный массив: тип поля остаётся `U[]`. Шаблон переживает `set` / `patch` / `reset`
19
+ * модели. Вложенные массивы объявляются внутри шаблона элемента — тогда и элементы, пришедшие
20
+ * обычными данными (загрузка с сервера), получают их шаблоны.
21
+ *
22
+ * Шаблон привязан к самому массиву, поэтому до `createModel` его нельзя копировать:
23
+ * `structuredClone`, `JSON.parse(JSON.stringify(…))` и `[...array]` отдают массив без шаблона, и
24
+ * `push()` без значения бросит ошибку. Начальные значения с `arrayOf` держат в фабрике
25
+ * (`const initial = () => ({ items: arrayOf(blank) })`), а не в константе, которую клонируют.
26
+ *
27
+ * @typeParam U - Тип элемента массива.
28
+ * @param blank - Фабрика нового элемента. Вызывается на каждое добавление — значение не делится
29
+ * между элементами.
30
+ * @param items - Начальные элементы. По умолчанию массив пуст.
31
+ * @returns Массив начальных значений с шаблоном.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const blankPhone = () => ({ number: '' });
36
+ * const blankCoBorrower = () => ({ name: '', phones: arrayOf(blankPhone) });
37
+ *
38
+ * const model = createModel({ coBorrowers: arrayOf(blankCoBorrower) });
39
+ *
40
+ * model.coBorrowers.push(); // { name: '', phones: [] }
41
+ * model.coBorrowers.at(0).phones.push(); // { number: '' }
42
+ * model.coBorrowers.push({ name: 'Анна', phones: [] }); // значение целиком — как раньше
43
+ * ```
44
+ *
45
+ * @group Model
46
+ */
47
+ export declare function arrayOf<U>(blank: () => U, items?: readonly U[]): U[];
4
48
  export type ModelNode = LeafNode | GroupNode | ArrayNode;
5
49
  declare class LeafNode {
6
50
  path: string;
@@ -22,7 +66,11 @@ export declare class GroupNode {
22
66
  path: string;
23
67
  readonly kind: "group";
24
68
  readonly children: Map<string, ModelNode>;
25
- constructor(initial: Record<string, unknown>, path: string);
69
+ /**
70
+ * @param shape Образец той же группы из шаблона родительского массива: из него вложенные
71
+ * массивы берут шаблоны, если значение пришло обычными данными (см. {@link ArrayNode}).
72
+ */
73
+ constructor(initial: Record<string, unknown>, path: string, shape?: Shape);
26
74
  rebase(path: string): void;
27
75
  /** Реактивное чтение поддерева (внутри effect/computed подписывает на все листья группы). */
28
76
  read(): Record<string, unknown>;
@@ -34,14 +82,27 @@ export declare class GroupNode {
34
82
  }
35
83
  export declare class ArrayNode {
36
84
  path: string;
85
+ private readonly shape?;
37
86
  readonly kind: "array";
38
87
  readonly items: Signal<ModelNode[]>;
39
88
  private initial;
40
- constructor(initial: unknown[], path: string);
89
+ private blank;
90
+ private fallback;
91
+ private sample;
92
+ /** @param shape Образец того же массива из шаблона родителя (см. {@link GroupNode}). */
93
+ constructor(initial: unknown[], path: string, shape?: Shape | undefined);
94
+ /** Шаблон по старшинству: свой (`arrayOf`) → из шаблона родителя → из схемы. */
95
+ private template;
96
+ private readonly itemShape;
97
+ private build;
98
+ /** Значение нового элемента: переданное либо из шаблона. */
99
+ private fresh;
100
+ /** Шаблон из схемы (`initialValue` узла-массива) — запасной: шаблон модели главнее. */
101
+ provideBlank(blank: Blank): void;
41
102
  rebase(path: string): void;
42
103
  private reindex;
43
- push(value: unknown): void;
44
- insertAt(index: number, value: unknown): void;
104
+ push(value?: unknown): void;
105
+ insertAt(index: number, value?: unknown): void;
45
106
  removeAt(index: number): void;
46
107
  move(from: number, to: number): void;
47
108
  swap(a: number, b: number): void;
@@ -57,4 +118,6 @@ export declare class ArrayNode {
57
118
  captureInitial(): void;
58
119
  dirty(): boolean;
59
120
  }
121
+ /** Значение `initialValue` узла-массива схемы как шаблон: фабрика — как есть, значение — копией. */
122
+ export declare const blankFrom: (initialValue: unknown) => Blank;
60
123
  export {};
@@ -1,9 +1,14 @@
1
+ import { Signal } from '@preact/signals-core';
1
2
  import { PathAwareSignal } from './types';
2
- import { ModelNode, GroupNode } from './model-nodes';
3
+ import { ModelNode, GroupNode, ArrayNode } from './model-nodes';
3
4
  /**
4
5
  * Узел дерева `$`: лист → сам {@link PathAwareSignal}, группа/массив → контейнерный узел —
5
6
  * {@link ReadonlySignal} агрегированного значения ПЛЮС доступ к детям по имени/индексу.
6
7
  *
8
+ * Узел массива вдобавок принимает запись `.value` — замену массива целиком (см. шапку модуля).
9
+ * Любая другая запись в контейнер — ошибка: свойство, молча осевшее на прокси, выглядело бы как
10
+ * удачная запись, а модель осталась бы прежней.
11
+ *
7
12
  * Порядок разрешения ключа: служебные `__path`/`__kind` → ребёнок → свойство сигнала. Дети идут
8
13
  * раньше свойств сигнала — тот же приоритет, что у {@link makeFormModel} (поле формы затеняет метод
9
14
  * API). Поэтому имена `value`/`peek`/`subscribe`/`valueOf`/`toString`/`toJSON`/`brand` де-факто
@@ -15,6 +20,13 @@ import { ModelNode, GroupNode } from './model-nodes';
15
20
  * полей, а потребители, перечисляющие модель, не видят служебных ключей.
16
21
  */
17
22
  export declare function signalsProxy(node: ModelNode): any;
23
+ /**
24
+ * Узел модели за контейнерной ручкой дерева `$` (группа или массив). Для листа и для всего, что
25
+ * ручкой не является, — `undefined`.
26
+ *
27
+ * @internal
28
+ */
29
+ export declare function containerNodeOf(handle: unknown): GroupNode | ArrayNode | undefined;
18
30
  /**
19
31
  * Узел дерева `model.$` — контейнер (группа/массив), а не лист?
20
32
  *
@@ -29,4 +41,24 @@ export declare function signalsProxy(node: ModelNode): any;
29
41
  * ```
30
42
  */
31
43
  export declare function isModelContainerSignal(value: unknown): boolean;
44
+ /** Узел-массив дерева `model.$` — ручка значения массива целиком. */
45
+ export declare function isModelArraySignal(value: unknown): boolean;
46
+ /**
47
+ * «Ручка значения», к которой можно привязать поле: лист (`instanceof Signal`) либо узел-массив
48
+ * дерева `model.$` (значение — массив целиком, `.value` читается и пишется).
49
+ *
50
+ * Группа сюда не входит: она читается целиком, но не пишется и полем не бывает. Этой проверкой
51
+ * `createForm` и рендереры отличают узел-поле схемы (`{ value, component }`) от контейнера —
52
+ * вместо голого `value instanceof Signal`, который узел-массив не проходит.
53
+ *
54
+ * @group Model
55
+ * @example
56
+ * ```typescript
57
+ * const model = createModel({ name: '', tags: [] as string[], address: { city: '' } });
58
+ * isValueSignal(model.$.name); // true — лист
59
+ * isValueSignal(model.$.tags); // true — массив как значение
60
+ * isValueSignal(model.$.address); // false — группа
61
+ * ```
62
+ */
63
+ export declare function isValueSignal(value: unknown): value is Signal<unknown>;
32
64
  export declare function resolveSignalAt(root: GroupNode, path: string): PathAwareSignal<unknown> | undefined;