@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.
Files changed (74) hide show
  1. package/README.md +123 -0
  2. package/dist/default/FeatureBadges.d.ts +33 -0
  3. package/dist/default/FeatureBadges.d.ts.map +1 -0
  4. package/dist/default/FeatureBadges.js +56 -0
  5. package/dist/default/FeatureBadges.js.map +1 -0
  6. package/dist/default/FeatureFields.d.ts +52 -0
  7. package/dist/default/FeatureFields.d.ts.map +1 -0
  8. package/dist/default/FeatureFields.js +60 -0
  9. package/dist/default/FeatureFields.js.map +1 -0
  10. package/dist/default/editors.d.ts +24 -0
  11. package/dist/default/editors.d.ts.map +1 -0
  12. package/dist/default/editors.js +354 -0
  13. package/dist/default/editors.js.map +1 -0
  14. package/dist/default/index.d.ts +27 -0
  15. package/dist/default/index.d.ts.map +1 -0
  16. package/dist/default/index.js +25 -0
  17. package/dist/default/index.js.map +1 -0
  18. package/dist/dto.d.ts +40 -0
  19. package/dist/dto.d.ts.map +1 -0
  20. package/dist/dto.js +64 -0
  21. package/dist/dto.js.map +1 -0
  22. package/dist/errors.d.ts +57 -0
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +98 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/format.d.ts +46 -0
  27. package/dist/format.d.ts.map +1 -0
  28. package/dist/format.js +159 -0
  29. package/dist/format.js.map +1 -0
  30. package/dist/i18n/es.d.ts +13 -0
  31. package/dist/i18n/es.d.ts.map +1 -0
  32. package/dist/i18n/es.js +36 -0
  33. package/dist/i18n/es.js.map +1 -0
  34. package/dist/i18n/keys.d.ts +56 -0
  35. package/dist/i18n/keys.d.ts.map +1 -0
  36. package/dist/i18n/keys.js +80 -0
  37. package/dist/i18n/keys.js.map +1 -0
  38. package/dist/i18n/ru.d.ts +19 -0
  39. package/dist/i18n/ru.d.ts.map +1 -0
  40. package/dist/i18n/ru.js +42 -0
  41. package/dist/i18n/ru.js.map +1 -0
  42. package/dist/index.d.ts +58 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +54 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/registry.d.ts +122 -0
  47. package/dist/registry.d.ts.map +1 -0
  48. package/dist/registry.js +82 -0
  49. package/dist/registry.js.map +1 -0
  50. package/dist/types.d.ts +134 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +35 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/validate.d.ts +88 -0
  55. package/dist/validate.d.ts.map +1 -0
  56. package/dist/validate.js +410 -0
  57. package/dist/validate.js.map +1 -0
  58. package/manifest.json +96 -0
  59. package/package.json +107 -0
  60. package/src/default/FeatureBadges.tsx +123 -0
  61. package/src/default/FeatureFields.tsx +140 -0
  62. package/src/default/editors.tsx +578 -0
  63. package/src/default/index.ts +34 -0
  64. package/src/dto.ts +78 -0
  65. package/src/errors.ts +127 -0
  66. package/src/format.ts +210 -0
  67. package/src/i18n/es.ts +41 -0
  68. package/src/i18n/keys.ts +89 -0
  69. package/src/i18n/ru.ts +48 -0
  70. package/src/index.ts +98 -0
  71. package/src/registry.ts +167 -0
  72. package/src/types.ts +166 -0
  73. package/src/validate.ts +507 -0
  74. package/tsconfig.json +26 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * `@stapel/attributes-react` — the React value layer for `stapel-attributes`'
3
+ * dynamic feature types.
4
+ *
5
+ * ── What this package is, and what it deliberately is not ──────────────────
6
+ *
7
+ * It is an **L0 package, not a pair**, modelled on `@stapel/image`: no client,
8
+ * no queries, no `docs/schema.json`. Its backend counterpart is an L1 library
9
+ * with no HTTP surface whatsoever (`stapel-attributes/docs/readme.md`) — there
10
+ * is no `/attributes/api/v1/` to pair with. Feature definitions and validation
11
+ * verdicts reach a browser inside the responses of the modules that OWN them
12
+ * (categories, listings), and those pairs depend on this one to draw and check
13
+ * what they carry.
14
+ *
15
+ * It switches on **`config.type`** — the value type (`string`, `int`,
16
+ * `select`, `date`, `hex_color`, `hierarchical_select`, `convertible_unit`,
17
+ * `bool`, `float`, `header`). That is a DIFFERENT axis from the one
18
+ * `@stapel/forms-react` works on, which is `FormField.kind`: the field kinds
19
+ * of the admin form that CONFIGURES a type. Same upstream library, two
20
+ * vocabularies, and the storefront needs this one. A consequence worth
21
+ * stating because it removes an upstream ask: a storefront needs **no
22
+ * catalogue endpoint** for these types — the type arrives in the data, on
23
+ * every feature.
24
+ *
25
+ * ── The three-rung ladder, verbatim from forms-react ───────────────────────
26
+ *
27
+ * explicit `registerValueEditor(type, …)` ← a host's, always wins
28
+ * → the skin's `BUILTIN_VALUE_EDITORS` ← `/default`, ten types
29
+ * → `<UnsupportedValueEditor/>` ← loud, never a skipped field
30
+ *
31
+ * and, while an undrawable feature is on screen, `unsupportedTypeGate` blocks
32
+ * the submit with the reason NAMED. A category can legally carry a type this
33
+ * build has no editor for; drawing nothing would silently drop a feature that
34
+ * may be MANDATORY, and the person would submit a listing they could not
35
+ * complete and be told, by the server, that an attribute they never saw is
36
+ * missing.
37
+ *
38
+ * ── Layout ─────────────────────────────────────────────────────────────────
39
+ *
40
+ * `.` registry + mirror + DTO helpers + display formatting.
41
+ * React types only; no antd, no react-router, no fetch.
42
+ * `./default` the antd skin: ten builtin editors, `<FeatureFields>`,
43
+ * `<FeatureBadges>`, `<FeatureValueList>`.
44
+ * `./i18n/ru` opt-in locale bundles.
45
+ * `./i18n/es`
46
+ */
47
+ export type { FeatureConfig, FeatureDef, FeatureValueDto, FeatureValidationResult, FeaturesDto, ValidationBatchResult, ValidationErrorCode, ValidationStatus, } from "./types.js";
48
+ export { featureConfig, featureName, featureType } from "./types.js";
49
+ export { UNTYPED_FEATURE, registerValueEditor, registeredValueEditorTypes, resolveValueEditor, unregisterValueEditor, unsupportedTypeGate, unsupportedTypes, } from "./registry.js";
50
+ export type { ValueEditor, ValueEditorProps } from "./registry.js";
51
+ export { ERROR_CODE_TO_KEY, VALIDATION_ERROR_CODES, featureErrorsBySlug, resultErrorKey, } from "./errors.js";
52
+ export { SIMPLE_COLORS, codePointLength, isBlank, mirrorValidate, patternFullMatch, validateFeatureValue, } from "./validate.js";
53
+ export { fromFeaturesDto, toFeaturesDto } from "./dto.js";
54
+ export { FORMATTABLE_TYPES, formatFeatureValue, hexColorSwatch } from "./format.js";
55
+ export type { FormatOptions } from "./format.js";
56
+ export { ATTRIBUTES_ERROR_BUNDLE_EN, ATTRIBUTES_I18N_KEYS, attributesI18nBundleEn, registerAttributesI18n, } from "./i18n/keys.js";
57
+ export type { AttributesI18nKey } from "./i18n/keys.js";
58
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,YAAY,EACV,aAAa,EACb,UAAU,EACV,eAAe,EACf,uBAAuB,EACvB,WAAW,EACX,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,aAAa,EACb,eAAe,EACf,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACpF,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `@stapel/attributes-react` — the React value layer for `stapel-attributes`'
3
+ * dynamic feature types.
4
+ *
5
+ * ── What this package is, and what it deliberately is not ──────────────────
6
+ *
7
+ * It is an **L0 package, not a pair**, modelled on `@stapel/image`: no client,
8
+ * no queries, no `docs/schema.json`. Its backend counterpart is an L1 library
9
+ * with no HTTP surface whatsoever (`stapel-attributes/docs/readme.md`) — there
10
+ * is no `/attributes/api/v1/` to pair with. Feature definitions and validation
11
+ * verdicts reach a browser inside the responses of the modules that OWN them
12
+ * (categories, listings), and those pairs depend on this one to draw and check
13
+ * what they carry.
14
+ *
15
+ * It switches on **`config.type`** — the value type (`string`, `int`,
16
+ * `select`, `date`, `hex_color`, `hierarchical_select`, `convertible_unit`,
17
+ * `bool`, `float`, `header`). That is a DIFFERENT axis from the one
18
+ * `@stapel/forms-react` works on, which is `FormField.kind`: the field kinds
19
+ * of the admin form that CONFIGURES a type. Same upstream library, two
20
+ * vocabularies, and the storefront needs this one. A consequence worth
21
+ * stating because it removes an upstream ask: a storefront needs **no
22
+ * catalogue endpoint** for these types — the type arrives in the data, on
23
+ * every feature.
24
+ *
25
+ * ── The three-rung ladder, verbatim from forms-react ───────────────────────
26
+ *
27
+ * explicit `registerValueEditor(type, …)` ← a host's, always wins
28
+ * → the skin's `BUILTIN_VALUE_EDITORS` ← `/default`, ten types
29
+ * → `<UnsupportedValueEditor/>` ← loud, never a skipped field
30
+ *
31
+ * and, while an undrawable feature is on screen, `unsupportedTypeGate` blocks
32
+ * the submit with the reason NAMED. A category can legally carry a type this
33
+ * build has no editor for; drawing nothing would silently drop a feature that
34
+ * may be MANDATORY, and the person would submit a listing they could not
35
+ * complete and be told, by the server, that an attribute they never saw is
36
+ * missing.
37
+ *
38
+ * ── Layout ─────────────────────────────────────────────────────────────────
39
+ *
40
+ * `.` registry + mirror + DTO helpers + display formatting.
41
+ * React types only; no antd, no react-router, no fetch.
42
+ * `./default` the antd skin: ten builtin editors, `<FeatureFields>`,
43
+ * `<FeatureBadges>`, `<FeatureValueList>`.
44
+ * `./i18n/ru` opt-in locale bundles.
45
+ * `./i18n/es`
46
+ */
47
+ export { featureConfig, featureName, featureType } from "./types.js";
48
+ export { UNTYPED_FEATURE, registerValueEditor, registeredValueEditorTypes, resolveValueEditor, unregisterValueEditor, unsupportedTypeGate, unsupportedTypes, } from "./registry.js";
49
+ export { ERROR_CODE_TO_KEY, VALIDATION_ERROR_CODES, featureErrorsBySlug, resultErrorKey, } from "./errors.js";
50
+ export { SIMPLE_COLORS, codePointLength, isBlank, mirrorValidate, patternFullMatch, validateFeatureValue, } from "./validate.js";
51
+ export { fromFeaturesDto, toFeaturesDto } from "./dto.js";
52
+ export { FORMATTABLE_TYPES, formatFeatureValue, hexColorSwatch } from "./format.js";
53
+ export { ATTRIBUTES_ERROR_BUNDLE_EN, ATTRIBUTES_I18N_KEYS, attributesI18nBundleEn, registerAttributesI18n, } from "./i18n/keys.js";
54
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAYH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,mBAAmB,EACnB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,aAAa,EACb,eAAe,EACf,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE1D,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGpF,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The value-editor registry — THE customer seam of this package, and the
3
+ * direct descendant of forms-react's field-widget registry.
4
+ *
5
+ * It keys on `config.type` (the VALUE type: `string`, `int`, `select`, …),
6
+ * not on `FormField.kind` (the admin config form's field kinds). That is the
7
+ * whole reason this package exists next to `@stapel/forms-react` rather than
8
+ * inside it: two different vocabularies, and L2 pairs may not import each
9
+ * other anyway (`stapel-react/README.md` — dependency direction is strictly
10
+ * downward).
11
+ *
12
+ * The type vocabulary is an OPEN registry server-side (builtins →
13
+ * `STAPEL_ATTRIBUTES["EXTRA_TYPES"]` → runtime `register_feature_type`), so a
14
+ * host that adds a type on the backend must be able to draw it on the front
15
+ * without forking the skin:
16
+ *
17
+ * ```tsx
18
+ * registerValueEditor("size_grid", SizeGridEditor); // at startup
19
+ * ```
20
+ *
21
+ * ── Resolution, and what happens when nothing matches ──────────────────────
22
+ *
23
+ * This module answers only the FIRST rung: an explicit registration, or
24
+ * `null`. The `/default` skin completes the ladder — explicit registration >
25
+ * skin builtin (antd) > **loud unsupported notice** — verbatim the ladder
26
+ * forms-react's `<StapelForm>` runs.
27
+ *
28
+ * The last rung is a notice, deliberately, and never a skipped field. A
29
+ * category can legally carry a type this build has no editor for; rendering
30
+ * nothing would silently drop a feature that may be MANDATORY, and the person
31
+ * would submit a listing they could not complete and be told, by the server,
32
+ * that an attribute they never saw is missing. `unsupportedTypeGate` blocks
33
+ * the submit with the reason NAMED while such a feature is present — degrade
34
+ * loudly.
35
+ *
36
+ * The registry lives in the main entry, not in `/default`, so a host building
37
+ * its own renderer uses the same seam the skin does rather than a parallel
38
+ * one.
39
+ */
40
+ import type { ComponentType } from "react";
41
+ import type { FlowError } from "@stapel/core";
42
+ import type { ActionAvailability } from "@stapel/core";
43
+ import type { FeatureDef } from "./types.js";
44
+ /**
45
+ * What every value editor receives. One feature, one value, one setter, one
46
+ * error — the editor never touches an API layer or the rest of the form.
47
+ */
48
+ export interface ValueEditorProps<T = unknown> {
49
+ /** The feature being edited. `feature.config` carries the type's camelCase
50
+ * options (`maxLength`, `minSelected`, `precision`, `multiline`, …). */
51
+ readonly feature: FeatureDef;
52
+ /** The current answer, or `undefined` while unanswered. */
53
+ readonly value: T | undefined;
54
+ /** Report a new answer. Pass the type's own DTO `value` — bare for the
55
+ * scalar types, an array for `select`/`hierarchical_select`, an object for
56
+ * `hex_color` and `convertible_unit` (see `toFeaturesDto`). */
57
+ onChange(value: T | undefined): void;
58
+ /** The feature's current refusal (client mirror or server verdict), or
59
+ * `undefined`. */
60
+ readonly error?: FlowError | undefined;
61
+ /** True while a submit is in flight — editors should go read-only. */
62
+ readonly disabled?: boolean;
63
+ /**
64
+ * DOM id the editor MUST put on its primary control. The field row points
65
+ * its `<label for>` at this, so the label actually names the input for a
66
+ * screen reader (and for a click). An editor that drops it renders an
67
+ * unlabelled control — which antd's `Form.Item` cannot detect and will
68
+ * happily draw a label beside.
69
+ */
70
+ readonly id: string;
71
+ }
72
+ export type ValueEditor = ComponentType<ValueEditorProps>;
73
+ /**
74
+ * Register (or override) the editor for a value type. Call at startup, before
75
+ * the first render — the registry is module-global, like the i18n bundle
76
+ * registration and forms-react's widget registry.
77
+ */
78
+ export declare function registerValueEditor(type: string, editor: ValueEditor): void;
79
+ /** Remove an explicit registration (the skin's builtin, if any, resolves
80
+ * again). */
81
+ export declare function unregisterValueEditor(type: string): void;
82
+ /**
83
+ * The EXPLICIT registration for a type, or `null`. Named for what it is: the
84
+ * skin calls this FIRST and only falls back to its own builtin when it
85
+ * returns `null`, so a host registration always wins.
86
+ */
87
+ export declare function resolveValueEditor(type: string): ValueEditor | null;
88
+ /** Every type with an explicit registration, sorted. */
89
+ export declare function registeredValueEditorTypes(): readonly string[];
90
+ /**
91
+ * The value types present in `features` that NOTHING can draw — neither an
92
+ * explicit registration nor the caller's builtin set.
93
+ *
94
+ * Pure and React-free on purpose: the headless half must be able to judge
95
+ * renderability without importing the skin, which is how forms-react's
96
+ * `<FormFill>` avoids pulling antd into a headless bundle. `builtinTypes` is
97
+ * therefore passed IN (the skin exports `BUILTIN_VALUE_EDITOR_TYPES`), not
98
+ * imported from here.
99
+ *
100
+ * A feature whose config declares no `type` at all counts as unsupported and
101
+ * is reported under `"(none)"` — an unnamed hole is still a hole, and a
102
+ * silently dropped mandatory attribute is the exact failure this returns
103
+ * data to prevent.
104
+ */
105
+ export declare function unsupportedTypes(features: readonly FeatureDef[], builtinTypes: readonly string[]): readonly string[];
106
+ /** What `unsupportedTypes` reports for a feature whose config names no type
107
+ * at all. Exported so a caller can tell "we cannot draw `size_grid`" from
108
+ * "this row has no type" without string-matching a message. */
109
+ export declare const UNTYPED_FEATURE = "(none)";
110
+ /**
111
+ * The submit gate for an unsupported type — blocked with the reason named,
112
+ * never a disabled button with no explanation.
113
+ *
114
+ * This package owns its own key (`attributes.submit.blocked.unsupported_type`)
115
+ * because it owns the fact. A pair with its own submit — listings-react's
116
+ * composer, say — raises its own `listings.compose.blocked.unsupported_type`
117
+ * from the SAME `unsupportedTypes` call and never re-derives the fact; both
118
+ * spellings say the same thing to the same person, and neither invents a
119
+ * silent third behaviour.
120
+ */
121
+ export declare function unsupportedTypeGate(features: readonly FeatureDef[], builtinTypes: readonly string[]): ActionAvailability;
122
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C;;;GAGG;AACH,MAAM,WAAW,gBAAgB,CAAC,CAAC,GAAG,OAAO;IAC3C;4EACwE;IACxE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;IAC9B;;mEAE+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;IACrC;sBACkB;IAClB,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACvC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,WAAW,GAAG,aAAa,CAAC,gBAAgB,CAAC,CAAC;AAI1D;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI,CAE3E;AAED;aACa;AACb,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAExD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAEnE;AAED,wDAAwD;AACxD,wBAAgB,0BAA0B,IAAI,SAAS,MAAM,EAAE,CAE9D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,SAAS,UAAU,EAAE,EAC/B,YAAY,EAAE,SAAS,MAAM,EAAE,GAC9B,SAAS,MAAM,EAAE,CAYnB;AAED;;+DAE+D;AAC/D,eAAO,MAAM,eAAe,WAAW,CAAC;AAExC;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,UAAU,EAAE,EAC/B,YAAY,EAAE,SAAS,MAAM,EAAE,GAC9B,kBAAkB,CAMpB"}
@@ -0,0 +1,82 @@
1
+ import { actionAvailable, actionBlocked } from "@stapel/core";
2
+ import { featureType } from "./types.js";
3
+ import { ATTRIBUTES_I18N_KEYS } from "./i18n/keys.js";
4
+ const registered = new Map();
5
+ /**
6
+ * Register (or override) the editor for a value type. Call at startup, before
7
+ * the first render — the registry is module-global, like the i18n bundle
8
+ * registration and forms-react's widget registry.
9
+ */
10
+ export function registerValueEditor(type, editor) {
11
+ registered.set(type, editor);
12
+ }
13
+ /** Remove an explicit registration (the skin's builtin, if any, resolves
14
+ * again). */
15
+ export function unregisterValueEditor(type) {
16
+ registered.delete(type);
17
+ }
18
+ /**
19
+ * The EXPLICIT registration for a type, or `null`. Named for what it is: the
20
+ * skin calls this FIRST and only falls back to its own builtin when it
21
+ * returns `null`, so a host registration always wins.
22
+ */
23
+ export function resolveValueEditor(type) {
24
+ return registered.get(type) ?? null;
25
+ }
26
+ /** Every type with an explicit registration, sorted. */
27
+ export function registeredValueEditorTypes() {
28
+ return [...registered.keys()].sort();
29
+ }
30
+ /**
31
+ * The value types present in `features` that NOTHING can draw — neither an
32
+ * explicit registration nor the caller's builtin set.
33
+ *
34
+ * Pure and React-free on purpose: the headless half must be able to judge
35
+ * renderability without importing the skin, which is how forms-react's
36
+ * `<FormFill>` avoids pulling antd into a headless bundle. `builtinTypes` is
37
+ * therefore passed IN (the skin exports `BUILTIN_VALUE_EDITOR_TYPES`), not
38
+ * imported from here.
39
+ *
40
+ * A feature whose config declares no `type` at all counts as unsupported and
41
+ * is reported under `"(none)"` — an unnamed hole is still a hole, and a
42
+ * silently dropped mandatory attribute is the exact failure this returns
43
+ * data to prevent.
44
+ */
45
+ export function unsupportedTypes(features, builtinTypes) {
46
+ const builtin = new Set(builtinTypes);
47
+ const out = new Set();
48
+ for (const feature of features) {
49
+ const type = featureType(feature);
50
+ if (type === undefined) {
51
+ out.add(UNTYPED_FEATURE);
52
+ continue;
53
+ }
54
+ if (resolveValueEditor(type) === null && !builtin.has(type))
55
+ out.add(type);
56
+ }
57
+ return [...out].sort();
58
+ }
59
+ /** What `unsupportedTypes` reports for a feature whose config names no type
60
+ * at all. Exported so a caller can tell "we cannot draw `size_grid`" from
61
+ * "this row has no type" without string-matching a message. */
62
+ export const UNTYPED_FEATURE = "(none)";
63
+ /**
64
+ * The submit gate for an unsupported type — blocked with the reason named,
65
+ * never a disabled button with no explanation.
66
+ *
67
+ * This package owns its own key (`attributes.submit.blocked.unsupported_type`)
68
+ * because it owns the fact. A pair with its own submit — listings-react's
69
+ * composer, say — raises its own `listings.compose.blocked.unsupported_type`
70
+ * from the SAME `unsupportedTypes` call and never re-derives the fact; both
71
+ * spellings say the same thing to the same person, and neither invents a
72
+ * silent third behaviour.
73
+ */
74
+ export function unsupportedTypeGate(features, builtinTypes) {
75
+ const types = unsupportedTypes(features, builtinTypes);
76
+ if (types.length === 0)
77
+ return actionAvailable();
78
+ return actionBlocked(ATTRIBUTES_I18N_KEYS.submitBlockedUnsupportedType, {
79
+ types: types.join(", "),
80
+ });
81
+ }
82
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAyCA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG9D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAiCtD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,MAAmB;IACnE,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC/B,CAAC;AAED;aACa;AACb,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;AACtC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,0BAA0B;IACxC,OAAO,CAAC,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACvC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAA+B,EAC/B,YAA+B;IAE/B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QACD,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAED;;+DAE+D;AAC/D,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC;AAExC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAA+B,EAC/B,YAA+B;IAE/B,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACvD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,eAAe,EAAE,CAAC;IACjD,OAAO,aAAa,CAAC,oBAAoB,CAAC,4BAA4B,EAAE;QACtE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;KACxB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The wire shapes of `stapel_attributes`, as this package sees them.
3
+ *
4
+ * There is no generated `schema.ts` here and there never will be:
5
+ * stapel-attributes is an **L1 library with no HTTP surface at all** — no
6
+ * models, no views, no urls (`stapel-attributes/docs/readme.md`), so it emits
7
+ * no `docs/schema.json` for `pnpm gen:api` to read. Its shapes reach a browser
8
+ * embedded in someone ELSE's response: a category's features arrive from
9
+ * `GET /categories/api/v1/categories/{id}/features/`, a validation verdict
10
+ * from `POST /categories/{pk}/validate-dto/` and from
11
+ * `POST /listings/{pk}/publish/`. The owning pair generates those; this
12
+ * package types the payload they carry.
13
+ *
14
+ * Every field here was read off the Python it mirrors, and the mirrors are
15
+ * named in the doc comments so the next person can check rather than trust.
16
+ */
17
+ /**
18
+ * A feature's type-specific configuration. `type` is the discriminator — the
19
+ * **value type's slug**, and the axis this whole package switches on.
20
+ *
21
+ * ── The distinction that must not be swallowed ─────────────────────────────
22
+ *
23
+ * stapel-attributes has TWO field vocabularies, and `@stapel/forms-react`
24
+ * works with the other one:
25
+ *
26
+ * - `FormField.kind` (`stapel_attributes/config_form.py`) — the field kinds
27
+ * of the ADMIN form that configures a type. That is what
28
+ * `GET /forms/api/v1/field-kinds` enumerates and what forms-react's widget
29
+ * registry keys on.
30
+ * - `config["type"]` — the VALUE type: `int`, `float`, `string`, `bool`,
31
+ * `hex_color`, `select`, `date`, `header`, `hierarchical_select`,
32
+ * `convertible_unit`. That is what a person filling in a listing actually
33
+ * edits, and it is what this package keys on.
34
+ *
35
+ * A storefront therefore needs **no catalogue endpoint**: the type arrives in
36
+ * the data, on every feature.
37
+ *
38
+ * `type` is optional here because the wire can omit it. The features endpoint
39
+ * serializes `obj.config` verbatim (`stapel-categories`
40
+ * `FeatureCompactSerializer.get_config`), NOT `get_config_with_defaults()`, so
41
+ * a config saved without its defaults arrives without them — including,
42
+ * for a malformed row, without a `type`. A missing type is an unsupported
43
+ * type, loudly, rather than a crash.
44
+ */
45
+ export interface FeatureConfig {
46
+ readonly type?: string;
47
+ readonly [key: string]: unknown;
48
+ }
49
+ /**
50
+ * One feature of a category — the browser's view of `stapel_attributes.base
51
+ * .FeatureDef`, as `stapel-categories`' `FeatureCompactSerializer` sends it
52
+ * (`fields = [id, tn_parent, name, slug, icon, comment, config, mandatory,
53
+ * show_as_badge, show_at_title, translate]`).
54
+ */
55
+ export interface FeatureDef {
56
+ /** Payload key this feature's value is submitted under. */
57
+ readonly slug: string;
58
+ readonly config?: FeatureConfig;
59
+ readonly id?: number | string | null;
60
+ /** Display name or translation key. Falls back to `slug` server-side. */
61
+ readonly name?: string | null;
62
+ readonly mandatory?: boolean;
63
+ readonly show_at_title?: boolean;
64
+ readonly show_as_badge?: boolean;
65
+ readonly translate?: string | null;
66
+ readonly icon?: string | null;
67
+ readonly comment?: string | null;
68
+ readonly tn_parent?: number | string | null;
69
+ }
70
+ /**
71
+ * One submitted value: `{type, value}` plus whatever else the type's DTO
72
+ * carries. Exactly one builtin adds a key — `convertible_unit`, whose DTO is
73
+ * `{type, value, unit}` because the number has to be tagged with the unit it
74
+ * was typed in before the server converts to the family's base unit.
75
+ */
76
+ export interface FeatureValueDto {
77
+ readonly type: string;
78
+ readonly value: unknown;
79
+ readonly [key: string]: unknown;
80
+ }
81
+ /** The `features_draft`-shaped payload: `{slug: {type, value}}`. */
82
+ export type FeaturesDto = Readonly<Record<string, FeatureValueDto>>;
83
+ /** `stapel_attributes.results.ValidationStatus`. */
84
+ export type ValidationStatus = "ok" | "validation_failed";
85
+ /**
86
+ * `stapel_attributes.results.ValidationErrorCode`, mirrored.
87
+ *
88
+ * Pinned against the engine's own generated corpus
89
+ * (`stapel-attributes/tests/golden/error_codes.json`, itself generated from
90
+ * the enum and asserted by both the Python and the TypeScript half of the
91
+ * cross-language bridge) — see `test/contract.test.ts`.
92
+ */
93
+ export type ValidationErrorCode = "above_maximum" | "below_minimum" | "description_too_long" | "description_too_short" | "duplicate_slug" | "empty_options" | "invalid_config" | "invalid_format" | "invalid_type" | "mandatory_missing" | "min_greater_than_max" | "not_allowed" | "not_in_options" | "unknown_feature" | "unknown_feature_type";
94
+ /**
95
+ * One row of a batch verdict — `stapel_attributes.results
96
+ * .FeatureValidationResult` as its DRF serializer sends it.
97
+ *
98
+ * `slug` is the routing key: it is what puts a refusal on a control. The
99
+ * server's `params` carry `{feature, slug}` (never `field`), so
100
+ * `featureErrorsBySlug` adds `field` when folding a row into a `FlowError`,
101
+ * which is what the fleet's `useFieldError` convention reads.
102
+ */
103
+ export interface FeatureValidationResult {
104
+ readonly slug: string;
105
+ readonly status: ValidationStatus;
106
+ readonly id?: number | string | null;
107
+ readonly error?: ValidationErrorCode | null;
108
+ /** The constraint that was violated (a limit, a list of options). */
109
+ readonly ref_value?: unknown;
110
+ readonly message?: string | null;
111
+ /** `error.400.feature_*` — the key a person's sentence comes from. */
112
+ readonly localizable_error?: string | null;
113
+ readonly params?: Readonly<Record<string, unknown>> | null;
114
+ /** Non-blocking findings (e.g. config keys the parser dropped). Never
115
+ * flips `valid` — a warning is not a refusal. */
116
+ readonly warnings?: readonly string[] | null;
117
+ }
118
+ /** `stapel_attributes.results.ValidationBatchResult`. */
119
+ export interface ValidationBatchResult {
120
+ readonly valid: boolean;
121
+ readonly results: readonly FeatureValidationResult[];
122
+ }
123
+ /**
124
+ * A feature's declared value type, or `undefined` when the config carries
125
+ * none. The ONE place `config.type` is read, so "which axis is this?" has a
126
+ * single answer in this package.
127
+ */
128
+ export declare function featureType(feature: FeatureDef): string | undefined;
129
+ /** A feature's config, never `undefined` — saves every reader a `?? {}`. */
130
+ export declare function featureConfig(feature: FeatureDef): FeatureConfig;
131
+ /** A feature's display name, falling back to its slug exactly as
132
+ * `FeatureDef.__post_init__` does server-side. */
133
+ export declare function featureName(feature: FeatureDef): string;
134
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The wire shapes of `stapel_attributes`, as this package sees them.
3
+ *
4
+ * There is no generated `schema.ts` here and there never will be:
5
+ * stapel-attributes is an **L1 library with no HTTP surface at all** — no
6
+ * models, no views, no urls (`stapel-attributes/docs/readme.md`), so it emits
7
+ * no `docs/schema.json` for `pnpm gen:api` to read. Its shapes reach a browser
8
+ * embedded in someone ELSE's response: a category's features arrive from
9
+ * `GET /categories/api/v1/categories/{id}/features/`, a validation verdict
10
+ * from `POST /categories/{pk}/validate-dto/` and from
11
+ * `POST /listings/{pk}/publish/`. The owning pair generates those; this
12
+ * package types the payload they carry.
13
+ *
14
+ * Every field here was read off the Python it mirrors, and the mirrors are
15
+ * named in the doc comments so the next person can check rather than trust.
16
+ */
17
+ /**
18
+ * A feature's declared value type, or `undefined` when the config carries
19
+ * none. The ONE place `config.type` is read, so "which axis is this?" has a
20
+ * single answer in this package.
21
+ */
22
+ export function featureType(feature) {
23
+ const type = feature.config?.type;
24
+ return typeof type === "string" && type.length > 0 ? type : undefined;
25
+ }
26
+ /** A feature's config, never `undefined` — saves every reader a `?? {}`. */
27
+ export function featureConfig(feature) {
28
+ return feature.config ?? {};
29
+ }
30
+ /** A feature's display name, falling back to its slug exactly as
31
+ * `FeatureDef.__post_init__` does server-side. */
32
+ export function featureName(feature) {
33
+ return feature.name ?? feature.slug;
34
+ }
35
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAmIH;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAmB;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;IAClC,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACxE,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,OAAmB;IAC/C,OAAO,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;AAC9B,CAAC;AAED;kDACkD;AAClD,MAAM,UAAU,WAAW,CAAC,OAAmB;IAC7C,OAAO,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;AACtC,CAAC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The client-side validation MIRROR — instant feedback, never a verdict.
3
+ *
4
+ * "Client-side validation mirrors, server decides." Every rule here is
5
+ * derived from the feature's own `config` so a person sees a problem as they
6
+ * type instead of after a round trip; none of it is trusted. The server
7
+ * re-runs `stapel_attributes.validate_dto_structured` on
8
+ * `POST /categories/{pk}/validate-dto/` and again on
9
+ * `POST /listings/{pk}/publish/`, and its `ValidationBatchResult` is the one
10
+ * that counts. This function returns the SAME shape so a server answer can
11
+ * replace a mirrored one with no translation step.
12
+ *
13
+ * ── Two contract details that are easy to get subtly wrong ─────────────────
14
+ *
15
+ * 1. **`pattern` matches the WHOLE value.** The engine uses `re.fullmatch`,
16
+ * and the admin JS mirrors it as `^(?:<pattern>)$`
17
+ * (`stapel-attributes/MODULE.md`, "Pattern contract"). A bare
18
+ * `RegExp.test` is a PREFIX match, so `^\d{4}$`-less patterns would pass
19
+ * here and fail there — the mirror telling a person their input is fine
20
+ * right before the server refuses it.
21
+ *
22
+ * 2. **String length is counted in Unicode CODE POINTS**, on both sides —
23
+ * same source. JavaScript's `String.length` counts UTF-16 code units, so
24
+ * one emoji is 2 and one astral CJK ideograph is 2. A `maxLength: 10`
25
+ * field would refuse ten emoji locally and accept them server-side.
26
+ *
27
+ * ── What the mirror deliberately does NOT judge ────────────────────────────
28
+ *
29
+ * - **A value type it does not know.** An unknown `config.type` may be a
30
+ * perfectly valid `EXTRA_TYPES` registration whose rules live only in
31
+ * Python. The mirror runs the type-independent checks (mandatory/empty) and
32
+ * leaves the rest to the server — a mirror that invented a refusal for a
33
+ * type it cannot read would block a submit the backend would have accepted.
34
+ * Whether such a type can be DRAWN is a separate question, answered loudly
35
+ * by `unsupportedTypes`/`unsupportedTypeGate`.
36
+ *
37
+ * - **`convertible_unit` range.** `min`/`max` are expressed in the unit
38
+ * family's canonical base unit and the value is converted before comparison
39
+ * (`stapel_attributes.types.convertible_unit`), and the family table is
40
+ * Python-side. Mirroring the comparison without the conversion would report
41
+ * "too large" for a perfectly good number in the other unit system. The
42
+ * number and the unit code ARE checked; the range is the server's.
43
+ */
44
+ import type { FeatureDef, FeatureValidationResult, FeaturesDto, FeatureValueDto, ValidationBatchResult } from "./types.js";
45
+ /** `stapel_attributes.types.hex_color.constants.SIMPLE_COLORS` — the closed
46
+ * set of colour categories a `hex_color` value must name. */
47
+ export declare const SIMPLE_COLORS: readonly string[];
48
+ /** Length in Unicode code points — the unit BOTH sides count in. */
49
+ export declare function codePointLength(text: string): number;
50
+ /**
51
+ * Does `pattern` match the WHOLE of `text`? `re.fullmatch`, in JavaScript.
52
+ *
53
+ * A pattern JS cannot compile (a Python-only construct) is NOT the person's
54
+ * problem: the mirror stands down and lets the server, which compiled it, be
55
+ * the one to refuse. The `u` flag is tried first because it is what makes `.`
56
+ * and quantifiers count code points rather than surrogate halves, and dropped
57
+ * when a pattern is only valid without it.
58
+ */
59
+ export declare function patternFullMatch(pattern: string, text: string): boolean | undefined;
60
+ /**
61
+ * "Nothing was entered" — the one shape the mandatory rule fires on, and the
62
+ * engine's own predicate: `raw_value is None or raw_value == '' or
63
+ * raw_value == []` (`validation.py`). An explicit `false` is an ANSWER for a
64
+ * `bool`, and `0` is an answer for an `int`, which is why this is not a
65
+ * falsiness check.
66
+ */
67
+ export declare function isBlank(value: unknown): boolean;
68
+ /**
69
+ * Validate one submitted value against its feature's config. Returns the
70
+ * mirrored refusal, or `undefined` when this side of the wire is satisfied.
71
+ *
72
+ * `header` is never validated: the engine regenerates a header's DAO from its
73
+ * config and skips it outright in the batch validator, so a header has
74
+ * nothing to check and must never carry a value.
75
+ */
76
+ export declare function validateFeatureValue(feature: FeatureDef, dto: FeatureValueDto): FeatureValidationResult | undefined;
77
+ /**
78
+ * Validate a whole answer set against a category's features — the client-side
79
+ * twin of `POST /categories/{pk}/validate-dto/`.
80
+ *
81
+ * Mirrors the engine's two passes and their order, because the order is what
82
+ * a caller sees: first every SUBMITTED entry whose slug the category allows
83
+ * (an unknown slug is ignored, not refused — the engine's documented
84
+ * behaviour), then every allowed feature that was never submitted, of which
85
+ * only a mandatory non-header one produces a row.
86
+ */
87
+ export declare function mirrorValidate(features: readonly FeatureDef[], dto: FeaturesDto): ValidationBatchResult;
88
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,OAAO,KAAK,EACV,UAAU,EACV,uBAAuB,EACvB,WAAW,EACX,eAAe,EACf,qBAAqB,EAEtB,MAAM,YAAY,CAAC;AAIpB;6DAC6D;AAC7D,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAmB1C,CAAC;AAaF,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAUnF;AAqBD;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK/C;AAmPD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,eAAe,GACnB,uBAAuB,GAAG,SAAS,CAsCrC;AAyBD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,UAAU,EAAE,EAC/B,GAAG,EAAE,WAAW,GACf,qBAAqB,CA+BvB"}