@reformer/core 11.0.0-beta.5 → 11.0.0-beta.7
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.
- package/dist/behaviors.js +124 -124
- package/dist/create-model-B8U-COv-.js +103 -0
- package/dist/en-CjAqn7h0.js +41 -0
- package/dist/form/behaviors/internals.d.ts +9 -0
- package/dist/form/create-form.d.ts +6 -6
- package/dist/form/factories/node-factory.d.ts +16 -16
- package/dist/form/nodes/array-node.d.ts +3 -3
- package/dist/form/nodes/field-node.d.ts +3 -3
- package/dist/form/nodes/group-node.d.ts +7 -7
- package/dist/form/nodes/model-array-node.d.ts +2 -2
- package/dist/form/types/contracts.d.ts +6 -0
- package/dist/form/types/deep-schema.d.ts +5 -5
- package/dist/form/types/validation-schema.d.ts +13 -1
- package/dist/form/validation/operators.d.ts +6 -1
- package/dist/form/validators/validation-error.d.ts +8 -0
- package/dist/i18n/descriptor.d.ts +54 -0
- package/dist/i18n/en.json.d.ts +45 -0
- package/dist/i18n/loader.d.ts +71 -0
- package/dist/i18n/locale-store.d.ts +39 -0
- package/dist/i18n/locale.d.ts +90 -0
- package/dist/i18n/message-format.d.ts +116 -0
- package/dist/i18n/resolve-localized.d.ts +17 -0
- package/dist/i18n/ru.json.d.ts +45 -0
- package/dist/i18n/translator.d.ts +50 -0
- package/dist/i18n/validation-message.d.ts +18 -0
- package/dist/i18n.d.ts +31 -0
- package/dist/i18n.js +303 -0
- package/dist/index-BrNkQj1y.js +105 -0
- package/dist/index.js +548 -601
- package/dist/loader-Dv_Hl7JC.js +216 -0
- package/dist/locale/en.d.ts +2 -0
- package/dist/locale/en.js +5 -0
- package/dist/locale/en.json +42 -0
- package/dist/locale/index.d.ts +18 -0
- package/dist/locale/ru.d.ts +2 -0
- package/dist/locale/ru.js +41 -0
- package/dist/locale/ru.json +42 -0
- package/dist/locale.d.ts +2 -0
- package/dist/locale.js +9 -0
- package/dist/model/create-model.d.ts +17 -3
- package/dist/model/index.d.ts +3 -3
- package/dist/model/model-signals-proxy.d.ts +25 -0
- package/dist/model/types.d.ts +19 -5
- package/dist/model-signals-proxy-2vqS243J.js +250 -0
- package/dist/model.js +18 -16
- package/dist/platforms/react/field-binding/field-adapter.d.ts +87 -0
- package/dist/platforms/react/field-binding/field-handle.d.ts +50 -0
- package/dist/platforms/react/hooks/types.d.ts +6 -3
- package/dist/platforms/react/hooks/useFormControlValue.d.ts +2 -1
- package/dist/platforms/react/i18n/context.d.ts +13 -0
- package/dist/platforms/react/i18n/hooks.d.ts +59 -0
- package/dist/platforms/react/i18n/provider.d.ts +52 -0
- package/dist/platforms/react/index.d.ts +4 -0
- package/dist/{strategy-DA3JwxsR.js → strategy-BXS_gBfz.js} +7 -7
- package/dist/validation-error-B-U3RsV0.js +11 -0
- package/dist/validation.js +1 -1
- package/dist/validators/email.js +5 -8
- package/dist/validators/future-date.js +8 -11
- package/dist/validators/integer.js +4 -7
- package/dist/validators/is-date.js +5 -8
- package/dist/validators/is-number.js +4 -7
- package/dist/validators/max-age.js +8 -11
- package/dist/validators/max-date.js +8 -11
- package/dist/validators/max-length.js +5 -8
- package/dist/validators/max.js +4 -7
- package/dist/validators/min-age.js +8 -11
- package/dist/validators/min-date.js +8 -11
- package/dist/validators/min-length.js +5 -8
- package/dist/validators/min.js +4 -7
- package/dist/validators/multiple-of.js +5 -8
- package/dist/validators/non-negative.js +4 -7
- package/dist/validators/non-zero.js +4 -7
- package/dist/validators/past-date.js +8 -11
- package/dist/validators/pattern.js +5 -8
- package/dist/validators/phone.js +6 -9
- package/dist/validators/required.js +4 -7
- package/dist/validators/url.js +9 -14
- package/dist/validators.js +84 -106
- package/docs/llms/02-quick-start.md +7 -7
- package/docs/llms/03-api-signatures.md +11 -5
- package/docs/llms/05-common-mistakes.md +1 -1
- package/docs/llms/09-formschema.md +12 -12
- package/docs/llms/10-arrays.md +34 -4
- package/docs/llms/14-extended-mistakes.md +1 -1
- package/docs/llms/15-project-structure.md +15 -13
- package/docs/llms/16-ui-components.md +36 -9
- package/docs/llms/17-nonexistent-api.md +16 -2
- package/docs/llms/29-async-preload.md +4 -4
- package/docs/llms/30-type-safety-recipes.md +1 -1
- package/docs/llms/34-i18n.md +272 -0
- package/llms-index.json +1 -1
- package/llms.txt +1925 -175
- package/package.json +22 -3
- package/dist/create-model-Dxh9cfLp.js +0 -97
- package/dist/model-signals-proxy-mV3WGdlO.js +0 -233
|
@@ -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,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,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
|
+
}
|
package/dist/locale.d.ts
ADDED
package/dist/locale.js
ADDED
|
@@ -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
|
-
*
|
|
8
|
-
*
|
|
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
|
*
|
package/dist/model/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Состав (реэкспорты — из тех же файлов, что использует зонтик `.`, поэтому `.` и `/model` — один
|
|
14
14
|
* module-инстанс: общий `derived`-WeakMap, единая идентичность `Signal`):
|
|
15
15
|
* - **модель** — `createModel` + типы `FormModel`/`ModelArray`/… + `PathAwareSignal`;
|
|
16
|
-
* - **обход
|
|
16
|
+
* - **обход значений** — `eachLeafSignal` (листья) и `eachValueSignal` (листья и массивы целиком);
|
|
17
17
|
* - **value-операции** (реактивные правила) — `computeFrom`/`copyFrom`/`watchField`/`transformValue`/
|
|
18
18
|
* `resetWhen`/`syncFields`/`revalidateWhen`;
|
|
19
19
|
* - **producer-owned флаг** — `markDerived`/`isDerived`/`unmarkDerived`;
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
* @group Model
|
|
23
23
|
* @module model
|
|
24
24
|
*/
|
|
25
|
-
export { createModel, eachLeafSignal } from './create-model';
|
|
25
|
+
export { createModel, eachLeafSignal, eachValueSignal } from './create-model';
|
|
26
26
|
export type { FormModel, ModelArray, ModelArraySignals, ModelGroupSignals, ModelObject, ModelValue, ModelSignals, ModelApi, PathAwareSignal, } from './types';
|
|
27
|
-
export { isModelContainerSignal } from './model-signals-proxy';
|
|
27
|
+
export { isModelContainerSignal, isValueSignal } from './model-signals-proxy';
|
|
28
28
|
export { computeFrom, copyFrom, watchField, transformValue, resetWhen, syncFields, revalidateWhen, } from './behaviors-value';
|
|
29
29
|
export type { BehaviorCleanup } from './behaviors-value';
|
|
30
30
|
export { markDerived, isDerived, unmarkDerived } from './derived-registry';
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { Signal } from '@preact/signals-core';
|
|
1
2
|
import { PathAwareSignal } from './types';
|
|
2
3
|
import { ModelNode, GroupNode } 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` де-факто
|
|
@@ -29,4 +34,24 @@ export declare function signalsProxy(node: ModelNode): any;
|
|
|
29
34
|
* ```
|
|
30
35
|
*/
|
|
31
36
|
export declare function isModelContainerSignal(value: unknown): boolean;
|
|
37
|
+
/** Узел-массив дерева `model.$` — ручка значения массива целиком. */
|
|
38
|
+
export declare function isModelArraySignal(value: unknown): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* «Ручка значения», к которой можно привязать поле: лист (`instanceof Signal`) либо узел-массив
|
|
41
|
+
* дерева `model.$` (значение — массив целиком, `.value` читается и пишется).
|
|
42
|
+
*
|
|
43
|
+
* Группа сюда не входит: она читается целиком, но не пишется и полем не бывает. Этой проверкой
|
|
44
|
+
* `createForm` и рендереры отличают узел-поле схемы (`{ value, component }`) от контейнера —
|
|
45
|
+
* вместо голого `value instanceof Signal`, который узел-массив не проходит.
|
|
46
|
+
*
|
|
47
|
+
* @group Model
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const model = createModel({ name: '', tags: [] as string[], address: { city: '' } });
|
|
51
|
+
* isValueSignal(model.$.name); // true — лист
|
|
52
|
+
* isValueSignal(model.$.tags); // true — массив как значение
|
|
53
|
+
* isValueSignal(model.$.address); // false — группа
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export declare function isValueSignal(value: unknown): value is Signal<unknown>;
|
|
32
57
|
export declare function resolveSignalAt(root: GroupNode, path: string): PathAwareSignal<unknown> | undefined;
|
package/dist/model/types.d.ts
CHANGED
|
@@ -108,12 +108,23 @@ type ContainerSignal<T, TKeys extends PropertyKey> = Omit<ReadonlySignal<T>, TKe
|
|
|
108
108
|
export type ModelGroupSignals<T> = ModelSignals<T> & ContainerSignal<T, keyof T>;
|
|
109
109
|
/**
|
|
110
110
|
* Узел-массив дерева `model.$`: индексируемый доступ к под-сигналам элементов, реактивная `length`
|
|
111
|
-
* и
|
|
112
|
-
*
|
|
111
|
+
* и сигнал значения массива целиком (реагирует и на правку элемента, и на изменение состава —
|
|
112
|
+
* push/removeAt/move).
|
|
113
|
+
*
|
|
114
|
+
* В отличие от группы, узел ЗАПИСЫВАЕМ: `model.$.tags.value = ['a']` заменяет массив целиком. Это
|
|
115
|
+
* делает его {@link PathAwareSignal} — к нему привязывают поле (`{ value: model.$.tags, component }`),
|
|
116
|
+
* правило (`validate(model.$.tags, …)`) и поведение, как к листу. Так массив служит ОДНИМ значением
|
|
117
|
+
* поля: мультивыбор, теги, список файлов.
|
|
118
|
+
*
|
|
119
|
+
* `V` — объявленный тип поля, включая `null`/`undefined`: поле `string[] | null` с начальным `null`
|
|
120
|
+
* в рантайме — лист с тем же `.value`/`peek`/`subscribe`, поэтому общая часть честна для обоих
|
|
121
|
+
* случаев. `length` и индексы есть только у настоящего узла-массива (начальное значение — массив).
|
|
113
122
|
*
|
|
114
123
|
* @group Model
|
|
115
124
|
*/
|
|
116
|
-
export type ModelArraySignals<U, V> =
|
|
125
|
+
export type ModelArraySignals<U, V> = Signal<V> & {
|
|
126
|
+
/** Путь массива в модели (dot-нотация). */
|
|
127
|
+
readonly __path: string;
|
|
117
128
|
readonly length: number;
|
|
118
129
|
readonly [index: number]: ModelSignalNode<U>;
|
|
119
130
|
};
|
|
@@ -124,7 +135,7 @@ export type ModelArraySignals<U, V> = ContainerSignal<V, 'length'> & {
|
|
|
124
135
|
* `PathAwareSignal<string>`, а не `ModelGroupSignals<never>`.
|
|
125
136
|
* @internal
|
|
126
137
|
*/
|
|
127
|
-
type ModelSignalNode<V> = NonNullable<V> extends ReadonlyArray<infer U> ? ModelArraySignals<U,
|
|
138
|
+
type ModelSignalNode<V> = NonNullable<V> extends ReadonlyArray<infer U> ? ModelArraySignals<U, V> : NonNullable<V> extends Opaque ? PathAwareSignal<V> : NonNullable<V> extends object ? ModelGroupSignals<NonNullable<V>> : PathAwareSignal<V>;
|
|
128
139
|
/**
|
|
129
140
|
* API уровня модели (доступно на корне, под-моделях вложенных объектов-групп и элементов массива).
|
|
130
141
|
*
|
|
@@ -158,7 +169,10 @@ export interface ModelApi<T> {
|
|
|
158
169
|
reset(): void;
|
|
159
170
|
/** Зафиксировать текущие значения как новый initial-снимок («точка отсчёта»). */
|
|
160
171
|
captureInitial(): void;
|
|
161
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* Резолв строкового пути в сигнал (для error-routing/мостов): лист либо массив целиком
|
|
174
|
+
* (ручка значения массива — см. {@link ModelArraySignals}). Для пути группы — `undefined`.
|
|
175
|
+
*/
|
|
162
176
|
signalAt(path: string): PathAwareSignal<unknown> | undefined;
|
|
163
177
|
}
|
|
164
178
|
/**
|