@stapel/attributes-react 0.1.0
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/README.md +123 -0
- package/dist/default/FeatureBadges.d.ts +33 -0
- package/dist/default/FeatureBadges.d.ts.map +1 -0
- package/dist/default/FeatureBadges.js +56 -0
- package/dist/default/FeatureBadges.js.map +1 -0
- package/dist/default/FeatureFields.d.ts +52 -0
- package/dist/default/FeatureFields.d.ts.map +1 -0
- package/dist/default/FeatureFields.js +60 -0
- package/dist/default/FeatureFields.js.map +1 -0
- package/dist/default/editors.d.ts +24 -0
- package/dist/default/editors.d.ts.map +1 -0
- package/dist/default/editors.js +354 -0
- package/dist/default/editors.js.map +1 -0
- package/dist/default/index.d.ts +27 -0
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +25 -0
- package/dist/default/index.js.map +1 -0
- package/dist/dto.d.ts +40 -0
- package/dist/dto.d.ts.map +1 -0
- package/dist/dto.js +64 -0
- package/dist/dto.js.map +1 -0
- package/dist/errors.d.ts +57 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +98 -0
- package/dist/errors.js.map +1 -0
- package/dist/format.d.ts +46 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +159 -0
- package/dist/format.js.map +1 -0
- package/dist/i18n/es.d.ts +13 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +36 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/keys.d.ts +56 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +80 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/i18n/ru.d.ts +19 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +42 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +122 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +82 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +35 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +88 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +410 -0
- package/dist/validate.js.map +1 -0
- package/manifest.json +96 -0
- package/package.json +107 -0
- package/src/default/FeatureBadges.tsx +123 -0
- package/src/default/FeatureFields.tsx +140 -0
- package/src/default/editors.tsx +578 -0
- package/src/default/index.ts +34 -0
- package/src/dto.ts +78 -0
- package/src/errors.ts +127 -0
- package/src/format.ts +210 -0
- package/src/i18n/es.ts +41 -0
- package/src/i18n/keys.ts +89 -0
- package/src/i18n/ru.ts +48 -0
- package/src/index.ts +98 -0
- package/src/registry.ts +167 -0
- package/src/types.ts +166 -0
- package/src/validate.ts +507 -0
- package/tsconfig.json +26 -0
package/dist/validate.js
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { featureConfig, featureName, featureType } from "./types.js";
|
|
2
|
+
import { ERROR_CODE_TO_KEY } from "./errors.js";
|
|
3
|
+
/** `stapel_attributes.types.hex_color.constants.SIMPLE_COLORS` — the closed
|
|
4
|
+
* set of colour categories a `hex_color` value must name. */
|
|
5
|
+
export const SIMPLE_COLORS = [
|
|
6
|
+
"black",
|
|
7
|
+
"white",
|
|
8
|
+
"gray",
|
|
9
|
+
"silver",
|
|
10
|
+
"red",
|
|
11
|
+
"pink",
|
|
12
|
+
"orange",
|
|
13
|
+
"yellow",
|
|
14
|
+
"green",
|
|
15
|
+
"blue",
|
|
16
|
+
"purple",
|
|
17
|
+
"brown",
|
|
18
|
+
"gold",
|
|
19
|
+
"beige",
|
|
20
|
+
"turquoise",
|
|
21
|
+
"clear",
|
|
22
|
+
"multicolor",
|
|
23
|
+
"custom",
|
|
24
|
+
];
|
|
25
|
+
const HEX_PATTERN = /^#(?:[0-9a-fA-F]{3}){1,2}$/;
|
|
26
|
+
const TRUE_STRINGS = new Set(["true", "1", "yes", "on"]);
|
|
27
|
+
const FALSE_STRINGS = new Set(["false", "0", "no", "off"]);
|
|
28
|
+
/** Length in Unicode code points — the unit BOTH sides count in. */
|
|
29
|
+
export function codePointLength(text) {
|
|
30
|
+
return [...text].length;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Does `pattern` match the WHOLE of `text`? `re.fullmatch`, in JavaScript.
|
|
34
|
+
*
|
|
35
|
+
* A pattern JS cannot compile (a Python-only construct) is NOT the person's
|
|
36
|
+
* problem: the mirror stands down and lets the server, which compiled it, be
|
|
37
|
+
* the one to refuse. The `u` flag is tried first because it is what makes `.`
|
|
38
|
+
* and quantifiers count code points rather than surrogate halves, and dropped
|
|
39
|
+
* when a pattern is only valid without it.
|
|
40
|
+
*/
|
|
41
|
+
export function patternFullMatch(pattern, text) {
|
|
42
|
+
const anchored = `^(?:${pattern})$`;
|
|
43
|
+
for (const flags of ["u", ""]) {
|
|
44
|
+
try {
|
|
45
|
+
return new RegExp(anchored, flags).test(text);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
function num(config, key) {
|
|
54
|
+
const raw = config[key];
|
|
55
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : undefined;
|
|
56
|
+
}
|
|
57
|
+
function list(config, key) {
|
|
58
|
+
const raw = config[key];
|
|
59
|
+
return Array.isArray(raw) ? raw : [];
|
|
60
|
+
}
|
|
61
|
+
/** An option's value, from either shape the engine's types allow (a bare
|
|
62
|
+
* scalar, or an object with `value`). */
|
|
63
|
+
function optionValue(option) {
|
|
64
|
+
if (option !== null && typeof option === "object" && "value" in option) {
|
|
65
|
+
return option.value;
|
|
66
|
+
}
|
|
67
|
+
return option;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* "Nothing was entered" — the one shape the mandatory rule fires on, and the
|
|
71
|
+
* engine's own predicate: `raw_value is None or raw_value == '' or
|
|
72
|
+
* raw_value == []` (`validation.py`). An explicit `false` is an ANSWER for a
|
|
73
|
+
* `bool`, and `0` is an answer for an `int`, which is why this is not a
|
|
74
|
+
* falsiness check.
|
|
75
|
+
*/
|
|
76
|
+
export function isBlank(value) {
|
|
77
|
+
if (value === undefined || value === null)
|
|
78
|
+
return true;
|
|
79
|
+
if (typeof value === "string")
|
|
80
|
+
return value === "";
|
|
81
|
+
if (Array.isArray(value))
|
|
82
|
+
return value.length === 0;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
// ── per-type rules ───────────────────────────────────────────────────────────
|
|
86
|
+
function validateString(config, value) {
|
|
87
|
+
const text = typeof value === "string" ? value : String(value);
|
|
88
|
+
const length = codePointLength(text);
|
|
89
|
+
const minLength = num(config, "minLength");
|
|
90
|
+
const maxLength = num(config, "maxLength");
|
|
91
|
+
if (minLength !== undefined && length < minLength) {
|
|
92
|
+
return { code: "below_minimum", ref_value: minLength };
|
|
93
|
+
}
|
|
94
|
+
if (maxLength !== undefined && length > maxLength) {
|
|
95
|
+
return { code: "above_maximum", ref_value: maxLength };
|
|
96
|
+
}
|
|
97
|
+
const pattern = config["pattern"];
|
|
98
|
+
if (typeof pattern === "string" && pattern.length > 0) {
|
|
99
|
+
const matched = patternFullMatch(pattern, text);
|
|
100
|
+
if (matched === false)
|
|
101
|
+
return { code: "invalid_format", ref_value: pattern };
|
|
102
|
+
}
|
|
103
|
+
const options = list(config, "options");
|
|
104
|
+
// `allowCustom` absent means TRUE for `string` (the dataclass default), so
|
|
105
|
+
// an options list without an explicit `allowCustom: false` constrains
|
|
106
|
+
// nothing. Reading an absent key as "closed set" would refuse values the
|
|
107
|
+
// server accepts.
|
|
108
|
+
if (options.length > 0 && config["allowCustom"] === false) {
|
|
109
|
+
if (!options.some((option) => optionValue(option) === text)) {
|
|
110
|
+
return { code: "not_in_options", ref_value: [...options].map(optionValue) };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
function validateNumber(config, value, isInt) {
|
|
116
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
117
|
+
if (typeof value === "boolean" || !Number.isFinite(parsed)) {
|
|
118
|
+
return { code: "invalid_type" };
|
|
119
|
+
}
|
|
120
|
+
// `int`'s normalizer is Python's `int()`, which TRUNCATES toward zero
|
|
121
|
+
// rather than refusing a fractional input — so the range check runs on the
|
|
122
|
+
// truncated number, exactly as it does server-side.
|
|
123
|
+
const number = isInt ? Math.trunc(parsed) : parsed;
|
|
124
|
+
const min = num(config, "min");
|
|
125
|
+
const max = num(config, "max");
|
|
126
|
+
if (min !== undefined && number < min)
|
|
127
|
+
return { code: "below_minimum", ref_value: min };
|
|
128
|
+
if (max !== undefined && number > max)
|
|
129
|
+
return { code: "above_maximum", ref_value: max };
|
|
130
|
+
const options = list(config, "options");
|
|
131
|
+
if (options.length > 0 && config["allowCustom"] === false) {
|
|
132
|
+
const precision = isInt ? 0 : (num(config, "precision") ?? 2);
|
|
133
|
+
const round = (n) => Number(n.toFixed(precision));
|
|
134
|
+
if (!options.some((option) => round(Number(optionValue(option))) === round(number))) {
|
|
135
|
+
return { code: "not_in_options", ref_value: [...options].map(optionValue) };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
function validateBool(value) {
|
|
141
|
+
if (typeof value === "boolean")
|
|
142
|
+
return undefined;
|
|
143
|
+
if (typeof value === "number")
|
|
144
|
+
return undefined; // bool(n), server-side
|
|
145
|
+
if (typeof value === "string") {
|
|
146
|
+
const lower = value.toLowerCase();
|
|
147
|
+
return TRUE_STRINGS.has(lower) || FALSE_STRINGS.has(lower)
|
|
148
|
+
? undefined
|
|
149
|
+
: { code: "invalid_type" };
|
|
150
|
+
}
|
|
151
|
+
return { code: "invalid_type" };
|
|
152
|
+
}
|
|
153
|
+
function validateSelect(config, value) {
|
|
154
|
+
if (!Array.isArray(value))
|
|
155
|
+
return { code: "invalid_type" };
|
|
156
|
+
const minSelected = num(config, "minSelected") ?? 0;
|
|
157
|
+
const maxSelected = num(config, "maxSelected");
|
|
158
|
+
if (value.length < minSelected) {
|
|
159
|
+
return { code: "below_minimum", ref_value: minSelected };
|
|
160
|
+
}
|
|
161
|
+
if (maxSelected !== undefined && value.length > maxSelected) {
|
|
162
|
+
return { code: "above_maximum", ref_value: maxSelected };
|
|
163
|
+
}
|
|
164
|
+
if (new Set(value).size !== value.length)
|
|
165
|
+
return { code: "invalid_format" };
|
|
166
|
+
const allowed = list(config, "options").map((option) => optionValue(option));
|
|
167
|
+
for (const item of value) {
|
|
168
|
+
if (typeof item !== "string")
|
|
169
|
+
return { code: "invalid_type" };
|
|
170
|
+
if (!allowed.includes(item)) {
|
|
171
|
+
return {
|
|
172
|
+
code: "not_in_options",
|
|
173
|
+
ref_value: [...allowed].map(String).sort(),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
function validateDate(config, value) {
|
|
180
|
+
// The engine's `normalize_dto` coerces int/float/numeric-string to an int
|
|
181
|
+
// and turns ANYTHING else into `None`, which its `validate_dto` then
|
|
182
|
+
// accepts. So a garbage date is silently dropped rather than refused — a
|
|
183
|
+
// known upstream wart, mirrored rather than "improved", because a mirror
|
|
184
|
+
// that refuses what the server accepts is a mirror that blocks a valid
|
|
185
|
+
// submit. (`stapel_attributes.types.date.type.DateFeatureType`.)
|
|
186
|
+
const timestamp = typeof value === "number" && Number.isFinite(value)
|
|
187
|
+
? Math.trunc(value)
|
|
188
|
+
: typeof value === "string" && /^-?\d+$/.test(value.trim())
|
|
189
|
+
? Number.parseInt(value.trim(), 10)
|
|
190
|
+
: undefined;
|
|
191
|
+
if (timestamp === undefined)
|
|
192
|
+
return undefined;
|
|
193
|
+
const now = Math.floor(Date.now() / 1000);
|
|
194
|
+
if (config["allowFuture"] === false && timestamp > now) {
|
|
195
|
+
return { code: "above_maximum", ref_value: now };
|
|
196
|
+
}
|
|
197
|
+
if (config["allowPast"] === false && timestamp < now) {
|
|
198
|
+
return { code: "below_minimum", ref_value: now };
|
|
199
|
+
}
|
|
200
|
+
const minDate = num(config, "minDate");
|
|
201
|
+
const maxDate = num(config, "maxDate");
|
|
202
|
+
if (minDate !== undefined && timestamp < minDate) {
|
|
203
|
+
return { code: "below_minimum", ref_value: minDate };
|
|
204
|
+
}
|
|
205
|
+
if (maxDate !== undefined && timestamp > maxDate) {
|
|
206
|
+
return { code: "above_maximum", ref_value: maxDate };
|
|
207
|
+
}
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
function validateHexColor(config, value) {
|
|
211
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
212
|
+
return { code: "invalid_type" };
|
|
213
|
+
}
|
|
214
|
+
const entry = value;
|
|
215
|
+
if (entry.hex !== undefined && entry.hex !== null) {
|
|
216
|
+
if (typeof entry.hex !== "string")
|
|
217
|
+
return { code: "invalid_type" };
|
|
218
|
+
const hex = entry.hex.trim();
|
|
219
|
+
if (hex.length > 0 && !HEX_PATTERN.test(hex))
|
|
220
|
+
return { code: "invalid_format" };
|
|
221
|
+
}
|
|
222
|
+
if (entry.simple === undefined || entry.simple === null || entry.simple === "") {
|
|
223
|
+
return { code: "invalid_format" };
|
|
224
|
+
}
|
|
225
|
+
if (typeof entry.simple !== "string")
|
|
226
|
+
return { code: "invalid_type" };
|
|
227
|
+
if (!SIMPLE_COLORS.includes(entry.simple)) {
|
|
228
|
+
return { code: "not_in_options", ref_value: [...SIMPLE_COLORS] };
|
|
229
|
+
}
|
|
230
|
+
const options = list(config, "options");
|
|
231
|
+
if (options.length > 0 && config["allowCustom"] !== true) {
|
|
232
|
+
const matched = options.some((option) => option !== null &&
|
|
233
|
+
typeof option === "object" &&
|
|
234
|
+
option.simple === entry.simple);
|
|
235
|
+
if (!matched) {
|
|
236
|
+
return {
|
|
237
|
+
code: "not_in_options",
|
|
238
|
+
ref_value: options.map((option) => option !== null && typeof option === "object"
|
|
239
|
+
? option.simple
|
|
240
|
+
: option),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
function findOption(options, value) {
|
|
247
|
+
for (const option of options) {
|
|
248
|
+
if (option !== null && typeof option === "object") {
|
|
249
|
+
if (option.value === value) {
|
|
250
|
+
return option;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else if (option === value) {
|
|
254
|
+
return {};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
function validateHierarchicalSelect(config, value) {
|
|
260
|
+
if (!Array.isArray(value))
|
|
261
|
+
return { code: "invalid_type" };
|
|
262
|
+
if (value.length === 0)
|
|
263
|
+
return undefined; // the empty pre-check already ruled
|
|
264
|
+
const minDepth = num(config, "minDepth") ?? 1;
|
|
265
|
+
const maxDepth = num(config, "maxDepth");
|
|
266
|
+
if (value.length < minDepth)
|
|
267
|
+
return { code: "below_minimum", ref_value: minDepth };
|
|
268
|
+
if (maxDepth !== undefined && value.length > maxDepth) {
|
|
269
|
+
return { code: "above_maximum", ref_value: maxDepth };
|
|
270
|
+
}
|
|
271
|
+
let level = list(config, "options");
|
|
272
|
+
for (const step of value) {
|
|
273
|
+
if (typeof step !== "string")
|
|
274
|
+
return { code: "invalid_type" };
|
|
275
|
+
const option = findOption(level, step);
|
|
276
|
+
if (option === undefined) {
|
|
277
|
+
return { code: "not_in_options", ref_value: level.map(optionValue) };
|
|
278
|
+
}
|
|
279
|
+
level = Array.isArray(option.children) ? option.children : [];
|
|
280
|
+
}
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
function validateConvertibleUnit(config, dto) {
|
|
284
|
+
if (dto.value !== null && dto.value !== undefined) {
|
|
285
|
+
const parsed = typeof dto.value === "number" ? dto.value : Number(dto.value);
|
|
286
|
+
if (typeof dto.value === "boolean" || !Number.isFinite(parsed)) {
|
|
287
|
+
return { code: "invalid_type" };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const unit = dto["unit"];
|
|
291
|
+
if (unit === undefined || unit === null)
|
|
292
|
+
return undefined;
|
|
293
|
+
const allowed = [config["unit_m"], config["unit_i"]].filter((code) => typeof code === "string" && code.length > 0);
|
|
294
|
+
if (!allowed.includes(String(unit))) {
|
|
295
|
+
return { code: "not_in_options", ref_value: allowed };
|
|
296
|
+
}
|
|
297
|
+
// `min`/`max` are in the family's BASE unit and the conversion table is
|
|
298
|
+
// server-side — see this module's header.
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Validate one submitted value against its feature's config. Returns the
|
|
303
|
+
* mirrored refusal, or `undefined` when this side of the wire is satisfied.
|
|
304
|
+
*
|
|
305
|
+
* `header` is never validated: the engine regenerates a header's DAO from its
|
|
306
|
+
* config and skips it outright in the batch validator, so a header has
|
|
307
|
+
* nothing to check and must never carry a value.
|
|
308
|
+
*/
|
|
309
|
+
export function validateFeatureValue(feature, dto) {
|
|
310
|
+
const type = featureType(feature);
|
|
311
|
+
if (type === "header")
|
|
312
|
+
return undefined;
|
|
313
|
+
const config = featureConfig(feature);
|
|
314
|
+
if (isBlank(dto.value)) {
|
|
315
|
+
return feature.mandatory === true
|
|
316
|
+
? failed(feature, { code: "mandatory_missing" })
|
|
317
|
+
: ok(feature);
|
|
318
|
+
}
|
|
319
|
+
const refusal = (() => {
|
|
320
|
+
switch (type) {
|
|
321
|
+
case "string":
|
|
322
|
+
return validateString(config, dto.value);
|
|
323
|
+
case "int":
|
|
324
|
+
return validateNumber(config, dto.value, true);
|
|
325
|
+
case "float":
|
|
326
|
+
return validateNumber(config, dto.value, false);
|
|
327
|
+
case "bool":
|
|
328
|
+
return validateBool(dto.value);
|
|
329
|
+
case "select":
|
|
330
|
+
return validateSelect(config, dto.value);
|
|
331
|
+
case "date":
|
|
332
|
+
return validateDate(config, dto.value);
|
|
333
|
+
case "hex_color":
|
|
334
|
+
return validateHexColor(config, dto.value);
|
|
335
|
+
case "hierarchical_select":
|
|
336
|
+
return validateHierarchicalSelect(config, dto.value);
|
|
337
|
+
case "convertible_unit":
|
|
338
|
+
return validateConvertibleUnit(config, dto);
|
|
339
|
+
default:
|
|
340
|
+
// An unknown type is the server's to judge — see the module header.
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
})();
|
|
344
|
+
return refusal === undefined ? ok(feature) : failed(feature, refusal);
|
|
345
|
+
}
|
|
346
|
+
function ok(feature) {
|
|
347
|
+
return {
|
|
348
|
+
slug: feature.slug,
|
|
349
|
+
status: "ok",
|
|
350
|
+
...(feature.id === undefined || feature.id === null ? {} : { id: feature.id }),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function failed(feature, refusal) {
|
|
354
|
+
return {
|
|
355
|
+
slug: feature.slug,
|
|
356
|
+
status: "validation_failed",
|
|
357
|
+
error: refusal.code,
|
|
358
|
+
localizable_error: ERROR_CODE_TO_KEY[refusal.code],
|
|
359
|
+
// The engine's own params, verbatim: `{feature, slug}`. `field` is added
|
|
360
|
+
// later, by `featureErrorsBySlug`, so the mirror's rows and the server's
|
|
361
|
+
// rows go through the same one step.
|
|
362
|
+
params: { feature: featureName(feature), slug: feature.slug },
|
|
363
|
+
...(feature.id === undefined || feature.id === null ? {} : { id: feature.id }),
|
|
364
|
+
...(refusal.ref_value === undefined ? {} : { ref_value: refusal.ref_value }),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Validate a whole answer set against a category's features — the client-side
|
|
369
|
+
* twin of `POST /categories/{pk}/validate-dto/`.
|
|
370
|
+
*
|
|
371
|
+
* Mirrors the engine's two passes and their order, because the order is what
|
|
372
|
+
* a caller sees: first every SUBMITTED entry whose slug the category allows
|
|
373
|
+
* (an unknown slug is ignored, not refused — the engine's documented
|
|
374
|
+
* behaviour), then every allowed feature that was never submitted, of which
|
|
375
|
+
* only a mandatory non-header one produces a row.
|
|
376
|
+
*/
|
|
377
|
+
export function mirrorValidate(features, dto) {
|
|
378
|
+
const bySlug = new Map();
|
|
379
|
+
for (const feature of features) {
|
|
380
|
+
bySlug.set(feature.slug, feature);
|
|
381
|
+
if (feature.id !== undefined && feature.id !== null) {
|
|
382
|
+
bySlug.set(String(feature.id), feature);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const results = [];
|
|
386
|
+
const seen = new Set();
|
|
387
|
+
for (const [key, entry] of Object.entries(dto)) {
|
|
388
|
+
const feature = bySlug.get(key);
|
|
389
|
+
if (feature === undefined)
|
|
390
|
+
continue; // unknown slug — ignored, per the engine
|
|
391
|
+
seen.add(feature.slug);
|
|
392
|
+
const result = validateFeatureValue(feature, entry);
|
|
393
|
+
if (result !== undefined)
|
|
394
|
+
results.push(result);
|
|
395
|
+
}
|
|
396
|
+
for (const feature of features) {
|
|
397
|
+
if (seen.has(feature.slug))
|
|
398
|
+
continue;
|
|
399
|
+
if (featureType(feature) === "header")
|
|
400
|
+
continue;
|
|
401
|
+
if (feature.mandatory !== true)
|
|
402
|
+
continue;
|
|
403
|
+
results.push(failed(feature, { code: "mandatory_missing" }));
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
valid: results.every((result) => result.status === "ok"),
|
|
407
|
+
results,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
|
package/manifest.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$generated": "hand-authored, not scripts/gen-manifest.mjs (that driver derives backend.contract from a sibling Django module's docs/schema.json + pyproject.toml; stapel-attributes is an L1 library with NO HTTP surface and emits neither — see backend below)",
|
|
3
|
+
"package": "@stapel/attributes-react",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"backend": {
|
|
6
|
+
"module": "stapel-attributes",
|
|
7
|
+
"kind": "L1 library, no HTTP surface",
|
|
8
|
+
"contract": null,
|
|
9
|
+
"pin": "test/fixtures/attributes-registry.json — the builtin type set and the ValidationErrorCode vocabulary, taken from the engine's own tests/golden/ corpora and asserted by test/contract.test.ts"
|
|
10
|
+
},
|
|
11
|
+
"layers": ["headless", "default", "i18n"],
|
|
12
|
+
"axis": "config.type — the VALUE type of an attribute. NOT FormField.kind, which is the admin config form's field kind and is what @stapel/forms-react keys on.",
|
|
13
|
+
"valueTypes": [
|
|
14
|
+
"bool",
|
|
15
|
+
"convertible_unit",
|
|
16
|
+
"date",
|
|
17
|
+
"float",
|
|
18
|
+
"header",
|
|
19
|
+
"hex_color",
|
|
20
|
+
"hierarchical_select",
|
|
21
|
+
"int",
|
|
22
|
+
"select",
|
|
23
|
+
"string"
|
|
24
|
+
],
|
|
25
|
+
"exports": {
|
|
26
|
+
"runtime": [
|
|
27
|
+
"registerValueEditor",
|
|
28
|
+
"unregisterValueEditor",
|
|
29
|
+
"resolveValueEditor",
|
|
30
|
+
"registeredValueEditorTypes",
|
|
31
|
+
"unsupportedTypes",
|
|
32
|
+
"unsupportedTypeGate",
|
|
33
|
+
"UNTYPED_FEATURE",
|
|
34
|
+
"mirrorValidate",
|
|
35
|
+
"validateFeatureValue",
|
|
36
|
+
"isBlank",
|
|
37
|
+
"codePointLength",
|
|
38
|
+
"patternFullMatch",
|
|
39
|
+
"SIMPLE_COLORS",
|
|
40
|
+
"toFeaturesDto",
|
|
41
|
+
"fromFeaturesDto",
|
|
42
|
+
"formatFeatureValue",
|
|
43
|
+
"hexColorSwatch",
|
|
44
|
+
"FORMATTABLE_TYPES",
|
|
45
|
+
"featureType",
|
|
46
|
+
"featureConfig",
|
|
47
|
+
"featureName",
|
|
48
|
+
"featureErrorsBySlug",
|
|
49
|
+
"resultErrorKey",
|
|
50
|
+
"ERROR_CODE_TO_KEY",
|
|
51
|
+
"VALIDATION_ERROR_CODES",
|
|
52
|
+
"ATTRIBUTES_I18N_KEYS",
|
|
53
|
+
"ATTRIBUTES_ERROR_BUNDLE_EN",
|
|
54
|
+
"attributesI18nBundleEn",
|
|
55
|
+
"registerAttributesI18n",
|
|
56
|
+
"BUILTIN_VALUE_EDITORS",
|
|
57
|
+
"BUILTIN_VALUE_EDITOR_TYPES",
|
|
58
|
+
"FeatureFields",
|
|
59
|
+
"UnsupportedValueEditor",
|
|
60
|
+
"featureControlId",
|
|
61
|
+
"FeatureBadges",
|
|
62
|
+
"FeatureValueList",
|
|
63
|
+
"timestampToInputValue",
|
|
64
|
+
"inputValueToTimestamp"
|
|
65
|
+
],
|
|
66
|
+
"types": [
|
|
67
|
+
"FeatureDef",
|
|
68
|
+
"FeatureConfig",
|
|
69
|
+
"FeatureValueDto",
|
|
70
|
+
"FeaturesDto",
|
|
71
|
+
"FeatureValidationResult",
|
|
72
|
+
"ValidationBatchResult",
|
|
73
|
+
"ValidationErrorCode",
|
|
74
|
+
"ValidationStatus",
|
|
75
|
+
"ValueEditor",
|
|
76
|
+
"ValueEditorProps",
|
|
77
|
+
"FormatOptions",
|
|
78
|
+
"AttributesI18nKey",
|
|
79
|
+
"FeatureFieldsProps",
|
|
80
|
+
"FeatureRowProps",
|
|
81
|
+
"FeatureDisplayProps",
|
|
82
|
+
"UnsupportedValueEditorProps"
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
"i18nKeys": [
|
|
86
|
+
"attributes.unsupported_type",
|
|
87
|
+
"attributes.submit.blocked.unsupported_type",
|
|
88
|
+
"attributes.untyped_feature",
|
|
89
|
+
"attributes.value.not_set",
|
|
90
|
+
"attributes.value.unreadable",
|
|
91
|
+
"attributes.bool.yes",
|
|
92
|
+
"attributes.bool.no",
|
|
93
|
+
"attributes.select.placeholder",
|
|
94
|
+
"attributes.required"
|
|
95
|
+
]
|
|
96
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stapel/attributes-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React value layer for stapel-attributes' dynamic feature types: a value-editor registry keyed on config.type (explicit registration > skin builtin > loud unsupported notice, with the submit blocked and the reason named), a client-side validation mirror that speaks the engine's own error.400.feature_* keys (pattern as fullmatch, string length in Unicode code points), DTO helpers for the {slug: {type, value}} wire shape, and display formatting. Zero visual opinion in the main entry; an opt-in /default subpath ships the antd editors for all ten builtin types plus <FeatureFields> and <FeatureBadges>.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/usestapel/stapel-react.git",
|
|
9
|
+
"directory": "packages/attributes-react"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./default": {
|
|
21
|
+
"types": "./dist/default/index.d.ts",
|
|
22
|
+
"default": "./dist/default/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./i18n/ru": {
|
|
25
|
+
"types": "./dist/i18n/ru.d.ts",
|
|
26
|
+
"default": "./dist/i18n/ru.js"
|
|
27
|
+
},
|
|
28
|
+
"./i18n/es": {
|
|
29
|
+
"types": "./dist/i18n/es.d.ts",
|
|
30
|
+
"default": "./dist/i18n/es.js"
|
|
31
|
+
},
|
|
32
|
+
"./manifest": "./manifest.json",
|
|
33
|
+
"./manifest.json": "./manifest.json",
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src",
|
|
39
|
+
"tsconfig.json",
|
|
40
|
+
"README.md",
|
|
41
|
+
"manifest.json"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc -p tsconfig.json",
|
|
45
|
+
"test": "tsc -p tsconfig.test.json && vitest run",
|
|
46
|
+
"lint": "eslint .",
|
|
47
|
+
"size": "size-limit"
|
|
48
|
+
},
|
|
49
|
+
"size-limit": [
|
|
50
|
+
{
|
|
51
|
+
"name": "index — the registry + mirror + DTO helpers must stay React-only: no antd",
|
|
52
|
+
"path": "dist/index.js",
|
|
53
|
+
"limit": "8 KB"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "default — the antd value editors must stay out of the main bundle",
|
|
57
|
+
"path": "dist/default/index.js",
|
|
58
|
+
"limit": "14 KB"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "i18n/ru — the Russian bundle is opt-in, not carried by the main entry",
|
|
62
|
+
"path": "dist/i18n/ru.js",
|
|
63
|
+
"limit": "3 KB"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "i18n/es — the Spanish bundle is opt-in, not carried by the main entry",
|
|
67
|
+
"path": "dist/i18n/es.js",
|
|
68
|
+
"limit": "3 KB"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"@stapel/core": ">=0.14.0 <1.0.0",
|
|
73
|
+
"@stapel/tokens-antd": ">=0.5.0",
|
|
74
|
+
"antd": ">=5.20.0 <7",
|
|
75
|
+
"react": ">=19"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"@stapel/tokens-antd": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"antd": {
|
|
82
|
+
"optional": true
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"devDependencies": {
|
|
86
|
+
"@size-limit/preset-small-lib": "^11.2.0",
|
|
87
|
+
"@stapel/core": "workspace:^",
|
|
88
|
+
"@stapel/tokens": "workspace:^",
|
|
89
|
+
"@stapel/tokens-antd": "workspace:^",
|
|
90
|
+
"@testing-library/react": "^16.3.0",
|
|
91
|
+
"@types/react": "^19.1.0",
|
|
92
|
+
"@types/react-dom": "^19.1.0",
|
|
93
|
+
"antd": "^6.5.0",
|
|
94
|
+
"jsdom": "^26.1.0",
|
|
95
|
+
"react": "^19.1.0",
|
|
96
|
+
"react-dom": "^19.1.0",
|
|
97
|
+
"size-limit": "^11.2.0",
|
|
98
|
+
"typescript": "^5.8.3",
|
|
99
|
+
"vitest": "^3.2.4"
|
|
100
|
+
},
|
|
101
|
+
"engines": {
|
|
102
|
+
"node": ">=22"
|
|
103
|
+
},
|
|
104
|
+
"publishConfig": {
|
|
105
|
+
"access": "public"
|
|
106
|
+
}
|
|
107
|
+
}
|