@stonecrop/schema 0.21.0 → 0.23.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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { A as c, D as A, F as p, G as y, I as u, a as S, L as T, T as m, b as F, d as I, V as D, W as L, e as N, g as C, h as f, i as O, j as k, k as E, c as b, l as x, n as _, o as g, f as P, p as R, q as h, r as M, s as v, t as w, u as K, m as W, w as G, x as j, y as Q, z as V, B as Y, C as q, E as z, H as X, v as B, J as H } from "./validation-1DGAGmuU.js";
2
- const a = {
1
+ import { A as E, D as N, F as h, G as _, I as p, a as j, L as x, T as P, b as R, d as B, V as w, W as M, e as k, g as G, h as K, i as V, j as W, k as Y, c as z, l as Q, n as q, o as J, f as X, p as H, q as U, r as Z, s as $, t as ee, u as re, m as ae, w as te, x as ne, y as ie, z as se, B as oe, C as le, E as ce, H as ue, v as de, J as fe } from "./validation-1DGAGmuU.js";
2
+ const l = {
3
3
  ATextInput: "text",
4
4
  ATextboxInput: "text",
5
5
  ANumericInput: "number",
@@ -17,69 +17,140 @@ const a = {
17
17
  AQuantityInput: "quantity",
18
18
  ACurrencyInput: "currency"
19
19
  };
20
- function s(e) {
21
- return e ? a[e] : void 0;
20
+ function g(e) {
21
+ return e ? l[e] : void 0;
22
22
  }
23
- const t = {
23
+ const c = {
24
24
  AFormLink: "inline",
25
25
  AForm: "expand",
26
26
  ATable: "expand"
27
27
  };
28
+ function f(e) {
29
+ return e ? c[e] : void 0;
30
+ }
31
+ function v(e, r) {
32
+ return f(e.component ?? r) === "inline" ? "inline" : e.cardinality === "noneOrMany" || e.cardinality === "atLeastOne" ? "table" : "record";
33
+ }
34
+ const O = [
35
+ .../* @__PURE__ */ new Set([...Object.keys(l), ...Object.keys(c)])
36
+ ].toSorted(), A = /* @__PURE__ */ new Set(["neutral", "success", "warning", "danger", "brand"]), y = /* @__PURE__ */ new Set(["variant", "color", "label"]);
37
+ function t(e) {
38
+ return !Array.isArray(e);
39
+ }
28
40
  function i(e) {
29
- return e ? t[e] : void 0;
41
+ return typeof e == "string" && A.has(e);
42
+ }
43
+ function u(e) {
44
+ if (e === null || typeof e != "object" || Array.isArray(e)) return !1;
45
+ for (const a of Object.keys(e))
46
+ if (!y.has(a)) return !1;
47
+ const r = e;
48
+ return !(r.variant !== void 0 && !i(r.variant) || r.color !== void 0 && typeof r.color != "string" || r.label !== void 0 && typeof r.label != "string");
49
+ }
50
+ function o(e) {
51
+ return i(e) || u(e);
52
+ }
53
+ function n(e) {
54
+ return Array.isArray(e.choices);
55
+ }
56
+ function T(e) {
57
+ if (e === null || typeof e != "object" || Array.isArray(e)) return !1;
58
+ const r = e;
59
+ return !(typeof r.label != "string" || r.variant !== void 0 && !i(r.variant) || r.color !== void 0 && typeof r.color != "string");
60
+ }
61
+ function s(e) {
62
+ if (e === void 0 || Array.isArray(e) || !t(e)) return !1;
63
+ if (n(e)) {
64
+ const a = e.badges;
65
+ return a === void 0 ? !1 : Object.values(a).every(o);
66
+ }
67
+ const r = Object.entries(e);
68
+ return r.length === 0 ? !1 : r.every(([, a]) => o(a));
69
+ }
70
+ function m(e) {
71
+ return e === void 0 || Array.isArray(e) || !t(e) ? !1 : n(e);
72
+ }
73
+ function D(e) {
74
+ return e === void 0 ? [] : Array.isArray(e) ? e : t(e) ? n(e) ? e.choices : s(e) ? Object.keys(e) : [] : [];
75
+ }
76
+ function d(e, r) {
77
+ return i(e) ? { label: r, variant: e } : u(e) ? {
78
+ label: e.label ?? r,
79
+ variant: e.variant,
80
+ color: e.color
81
+ } : { label: r };
82
+ }
83
+ function b(e, r) {
84
+ if (!(r === void 0 || r === "") && r in e)
85
+ return d(e[r], r);
86
+ }
87
+ function S(e, r) {
88
+ if (r === void 0 || r === "") return;
89
+ const a = e.badges;
90
+ if (!(!a || !(r in a)))
91
+ return d(a[r], r);
92
+ }
93
+ function F(e, r) {
94
+ if (!(e === void 0 || r === void 0 || r === "") && !Array.isArray(e) && t(e)) {
95
+ if (n(e)) return S(e, r);
96
+ if (s(e)) return b(e, r);
97
+ }
30
98
  }
31
- function o(e, n) {
32
- return i(e.component ?? n) === "inline" ? "inline" : e.cardinality === "noneOrMany" || e.cardinality === "atLeastOne" ? "table" : "record";
99
+ function I(e) {
100
+ return e === void 0 || Array.isArray(e) || !t(e) ? !1 : n(e) ? e.badges !== void 0 && Object.keys(e.badges).length > 0 : s(e);
33
101
  }
34
- const l = [
35
- .../* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(t)])
36
- ].toSorted();
37
102
  export {
38
- c as ActionDefinition,
39
- l as CANONICAL_COMPONENTS,
40
- a as COMPONENT_CATEGORY,
41
- t as COMPONENT_LINK_EXPANSION,
42
- A as DoctypeFieldSchema,
43
- p as FieldsetFieldSchema,
44
- y as GQL_SCALAR_MAP,
45
- u as INTERNAL_SCALARS,
46
- S as INTROSPECTED_IDENTITY_PROPS,
47
- T as LINK_DISPLAY_SUFFIX,
48
- m as TableFieldSchema,
49
- F as TableViewConfig,
50
- I as TriggerDefinition,
51
- D as ValueFieldSchema,
52
- L as WELL_KNOWN_SCALARS,
53
- N as WorkflowLayout,
54
- C as WorkflowMeta,
55
- f as buildScalarMap,
56
- O as camelToLabel,
57
- k as camelToSnake,
58
- E as classifyFieldType,
59
- s as componentCategory,
60
- i as componentLinkExpansion,
61
- b as convertGraphQLSchema,
62
- x as defaultIsEntityField,
63
- _ as defaultIsEntityType,
64
- g as flattenFields,
65
- P as formatDoctypeDrift,
66
- R as getDisplayField,
67
- h as getPrimaryKeyField,
68
- M as getRecordIdField,
69
- v as getRecordIdentity,
70
- w as isActionAllowedInState,
71
- K as linkDisplayFieldname,
72
- W as mergeIntrospectedDoctype,
73
- G as normalizeFieldKind,
74
- j as parseDoctype,
75
- Q as parseField,
76
- V as pascalToSnake,
77
- o as resolveLinkRenderMode,
78
- Y as snakeToCamel,
79
- q as snakeToLabel,
80
- z as toPascalCase,
81
- X as toSlug,
82
- B as validateDoctype,
83
- H as validateField
103
+ E as ActionDefinition,
104
+ O as CANONICAL_COMPONENTS,
105
+ l as COMPONENT_CATEGORY,
106
+ c as COMPONENT_LINK_EXPANSION,
107
+ N as DoctypeFieldSchema,
108
+ h as FieldsetFieldSchema,
109
+ _ as GQL_SCALAR_MAP,
110
+ p as INTERNAL_SCALARS,
111
+ j as INTROSPECTED_IDENTITY_PROPS,
112
+ x as LINK_DISPLAY_SUFFIX,
113
+ P as TableFieldSchema,
114
+ R as TableViewConfig,
115
+ B as TriggerDefinition,
116
+ w as ValueFieldSchema,
117
+ M as WELL_KNOWN_SCALARS,
118
+ k as WorkflowLayout,
119
+ G as WorkflowMeta,
120
+ K as buildScalarMap,
121
+ V as camelToLabel,
122
+ W as camelToSnake,
123
+ Y as classifyFieldType,
124
+ g as componentCategory,
125
+ f as componentLinkExpansion,
126
+ z as convertGraphQLSchema,
127
+ Q as defaultIsEntityField,
128
+ q as defaultIsEntityType,
129
+ J as flattenFields,
130
+ X as formatDoctypeDrift,
131
+ H as getDisplayField,
132
+ U as getPrimaryKeyField,
133
+ Z as getRecordIdField,
134
+ $ as getRecordIdentity,
135
+ I as hasBadgeOptions,
136
+ ee as isActionAllowedInState,
137
+ T as isBadgeDescriptor,
138
+ s as isSelectChoiceMap,
139
+ m as isSelectOptions,
140
+ re as linkDisplayFieldname,
141
+ F as lookupBadge,
142
+ ae as mergeIntrospectedDoctype,
143
+ te as normalizeFieldKind,
144
+ ne as parseDoctype,
145
+ ie as parseField,
146
+ se as pascalToSnake,
147
+ v as resolveLinkRenderMode,
148
+ D as selectChoices,
149
+ oe as snakeToCamel,
150
+ le as snakeToLabel,
151
+ ce as toPascalCase,
152
+ ue as toSlug,
153
+ de as validateDoctype,
154
+ fe as validateField
84
155
  };
85
156
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/component-meta.ts"],"sourcesContent":["/**\n * Semantic category for a rendering component.\n *\n * `component` is the primary field axis, so the runtime consumers that need to know what a field\n * *means* (atable cell formatting / filter widgets, record-default init) derive it from here. This\n * is the single source of \"what kind of value does this component render\", keyed by the canonical\n * registered component names — each consumer maps the category to its own concern (filter widget,\n * default value, …).\n *\n * @public\n */\nexport type ComponentCategory =\n\t'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'select' | 'code' | 'link' | 'attach' | 'quantity' | 'currency'\n\n/**\n * Canonical component → semantic category. Only the components Stonecrop ships with appear here;\n * custom/unknown component names have no category and consumers fall back to their default.\n * @public\n */\nexport const COMPONENT_CATEGORY: Record<string, ComponentCategory> = {\n\tATextInput: 'text',\n\tATextboxInput: 'text',\n\tANumericInput: 'number',\n\tACheckbox: 'boolean',\n\tADate: 'date',\n\tADatePicker: 'date',\n\tADateSelection: 'date',\n\tADateTime: 'datetime',\n\tADuration: 'text',\n\tADateRange: 'date',\n\tADropdown: 'select',\n\tACodeEditor: 'code',\n\tAFormLink: 'link',\n\tAFileAttach: 'attach',\n\tAQuantityInput: 'quantity',\n\tACurrencyInput: 'currency',\n}\n\n/**\n * Resolve a component's semantic category, or `undefined` for an unknown (custom) component —\n * callers treat that as \"no opinion\" and use their own default.\n * @public\n */\nexport function componentCategory(component?: string): ComponentCategory | undefined {\n\treturn component ? COMPONENT_CATEGORY[component] : undefined\n}\n\n/**\n * Whether a link component expands its target doctype, or renders the link inline.\n *\n * This is the *only* axis the component decides. It deliberately does not choose between an\n * embedded record and an embedded table: `cardinality` states whether the value is a scalar or\n * an array, which is a fact about the data rather than a rendering preference, so a component\n * must not be able to override it (an `AForm` over a `noneOrMany` link would be handed an array\n * it cannot render). Component names encode both axes — `AFormLink`/`ATableLink` are the inline\n * pair, `AForm`/`ATable` the expanding pair — but only the inline/expand half is authoritative.\n *\n * @public\n */\nexport type LinkExpansion = 'inline' | 'expand'\n\n/**\n * Canonical link component → expansion. Only components Stonecrop ships with appear here; an\n * unmapped (custom) component has none, and callers treat that as `expand` — the behaviour that\n * predates this map, so a custom component can never silently collapse a link to a picker.\n * @public\n */\nexport const COMPONENT_LINK_EXPANSION: Record<string, LinkExpansion> = {\n\tAFormLink: 'inline',\n\tAForm: 'expand',\n\tATable: 'expand',\n}\n\n/**\n * Resolve a component's link expansion, or `undefined` for an absent/unmapped component.\n * @public\n */\nexport function componentLinkExpansion(component?: string): LinkExpansion | undefined {\n\treturn component ? COMPONENT_LINK_EXPANSION[component] : undefined\n}\n\n/**\n * How a link field renders.\n *\n * - `inline` — a scalar id-picker; the target is *not* expanded (the field keeps its own value\n * and carries a `doctype` prop for async display-text resolution and navigation).\n * - `record` — the target doctype is resolved and embedded as a nested form.\n * - `table` — the target doctype is resolved and embedded as a child table.\n *\n * @public\n */\nexport type LinkRenderMode = 'inline' | 'record' | 'table'\n\n/**\n * Decide how a *declared* link (one with a `LinkDeclaration`) renders.\n *\n * Two independent axes: the **component** picks inline vs expand, and when expanding the\n * **cardinality** picks record vs table (many → table). The declaration's component wins over the\n * field's, matching the precedence the resolver already uses for the rendered component.\n *\n * This is the single definition of \"does this link expand\" — it is consumed by both the client\n * resolver (which builds the nested schema) and the server column builder (which must still\n * SELECT an `inline` link's FK column). Call it; never re-derive the rule at the call site, or\n * the two will drift and the client will render a table for a column the server never selected.\n *\n * @param link - the link declaration (only `component` and `cardinality` are consulted)\n * @param fieldComponent - the linked field's own `component`, used when the declaration names none\n * @public\n */\nexport function resolveLinkRenderMode(\n\tlink: { component?: string; cardinality?: string },\n\tfieldComponent?: string\n): LinkRenderMode {\n\tif (componentLinkExpansion(link.component ?? fieldComponent) === 'inline') return 'inline'\n\treturn link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne' ? 'table' : 'record'\n}\n\n/**\n * Every component Stonecrop ships with that can render a value field, sorted by name.\n *\n * The union of the two maps above is the definition, not a copy of it: a shipped component either\n * categorises a value ({@link COMPONENT_CATEGORY}) or is one of the link containers that has no\n * value of its own ({@link COMPONENT_LINK_EXPANSION}'s `AForm`/`ATable`). `AFieldset` is absent by\n * the same rule — it is a `kind: 'fieldset'` container, so it is never a value field's component.\n *\n * `component` is an **open** axis: any string is valid, and naming a custom component is how an app\n * renders a field Stonecrop ships no widget for. This list is therefore the set to *suggest* to an\n * author, and to check first-party data against — never a set to validate arbitrary input against.\n *\n * @public\n */\nexport const CANONICAL_COMPONENTS: readonly string[] = [\n\t...new Set([...Object.keys(COMPONENT_CATEGORY), ...Object.keys(COMPONENT_LINK_EXPANSION)]),\n].toSorted()\n"],"names":["COMPONENT_CATEGORY","componentCategory","component","COMPONENT_LINK_EXPANSION","componentLinkExpansion","resolveLinkRenderMode","link","fieldComponent","CANONICAL_COMPONENTS"],"mappings":";AAmBO,MAAMA,IAAwD;AAAA,EACpE,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,eAAe;AAAA,EACf,WAAW;AAAA,EACX,OAAO;AAAA,EACP,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,aAAa;AAAA,EACb,WAAW;AAAA,EACX,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AACjB;AAOO,SAASC,EAAkBC,GAAmD;AACpF,SAAOA,IAAYF,EAAmBE,CAAS,IAAI;AACpD;AAsBO,MAAMC,IAA0D;AAAA,EACtE,WAAW;AAAA,EACX,OAAO;AAAA,EACP,QAAQ;AACT;AAMO,SAASC,EAAuBF,GAA+C;AACrF,SAAOA,IAAYC,EAAyBD,CAAS,IAAI;AAC1D;AA8BO,SAASG,EACfC,GACAC,GACiB;AACjB,SAAIH,EAAuBE,EAAK,aAAaC,CAAc,MAAM,WAAiB,WAC3ED,EAAK,gBAAgB,gBAAgBA,EAAK,gBAAgB,eAAe,UAAU;AAC3F;AAgBO,MAAME,IAA0C;AAAA,EACtD,GAAG,oBAAI,IAAI,CAAC,GAAG,OAAO,KAAKR,CAAkB,GAAG,GAAG,OAAO,KAAKG,CAAwB,CAAC,CAAC;AAC1F,EAAE,SAAA;"}
1
+ {"version":3,"file":"index.js","sources":["../src/component-meta.ts","../src/badge.ts"],"sourcesContent":["/**\n * Semantic category for a rendering component.\n *\n * `component` is the primary field axis, so the runtime consumers that need to know what a field\n * *means* (atable cell formatting / filter widgets, record-default init) derive it from here. This\n * is the single source of \"what kind of value does this component render\", keyed by the canonical\n * registered component names — each consumer maps the category to its own concern (filter widget,\n * default value, …).\n *\n * @public\n */\nexport type ComponentCategory =\n\t'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'select' | 'code' | 'link' | 'attach' | 'quantity' | 'currency'\n\n/**\n * Canonical component → semantic category. Only the components Stonecrop ships with appear here;\n * custom/unknown component names have no category and consumers fall back to their default.\n * @public\n */\nexport const COMPONENT_CATEGORY: Record<string, ComponentCategory> = {\n\tATextInput: 'text',\n\tATextboxInput: 'text',\n\tANumericInput: 'number',\n\tACheckbox: 'boolean',\n\tADate: 'date',\n\tADatePicker: 'date',\n\tADateSelection: 'date',\n\tADateTime: 'datetime',\n\tADuration: 'text',\n\tADateRange: 'date',\n\tADropdown: 'select',\n\tACodeEditor: 'code',\n\tAFormLink: 'link',\n\tAFileAttach: 'attach',\n\tAQuantityInput: 'quantity',\n\tACurrencyInput: 'currency',\n}\n\n/**\n * Resolve a component's semantic category, or `undefined` for an unknown (custom) component —\n * callers treat that as \"no opinion\" and use their own default.\n * @public\n */\nexport function componentCategory(component?: string): ComponentCategory | undefined {\n\treturn component ? COMPONENT_CATEGORY[component] : undefined\n}\n\n/**\n * Whether a link component expands its target doctype, or renders the link inline.\n *\n * This is the *only* axis the component decides. It deliberately does not choose between an\n * embedded record and an embedded table: `cardinality` states whether the value is a scalar or\n * an array, which is a fact about the data rather than a rendering preference, so a component\n * must not be able to override it (an `AForm` over a `noneOrMany` link would be handed an array\n * it cannot render). Component names encode both axes — `AFormLink`/`ATableLink` are the inline\n * pair, `AForm`/`ATable` the expanding pair — but only the inline/expand half is authoritative.\n *\n * @public\n */\nexport type LinkExpansion = 'inline' | 'expand'\n\n/**\n * Canonical link component → expansion. Only components Stonecrop ships with appear here; an\n * unmapped (custom) component has none, and callers treat that as `expand` — the behaviour that\n * predates this map, so a custom component can never silently collapse a link to a picker.\n * @public\n */\nexport const COMPONENT_LINK_EXPANSION: Record<string, LinkExpansion> = {\n\tAFormLink: 'inline',\n\tAForm: 'expand',\n\tATable: 'expand',\n}\n\n/**\n * Resolve a component's link expansion, or `undefined` for an absent/unmapped component.\n * @public\n */\nexport function componentLinkExpansion(component?: string): LinkExpansion | undefined {\n\treturn component ? COMPONENT_LINK_EXPANSION[component] : undefined\n}\n\n/**\n * How a link field renders.\n *\n * - `inline` — a scalar id-picker; the target is *not* expanded (the field keeps its own value\n * and carries a `doctype` prop for async display-text resolution and navigation).\n * - `record` — the target doctype is resolved and embedded as a nested form.\n * - `table` — the target doctype is resolved and embedded as a child table.\n *\n * @public\n */\nexport type LinkRenderMode = 'inline' | 'record' | 'table'\n\n/**\n * Decide how a *declared* link (one with a `LinkDeclaration`) renders.\n *\n * Two independent axes: the **component** picks inline vs expand, and when expanding the\n * **cardinality** picks record vs table (many → table). The declaration's component wins over the\n * field's, matching the precedence the resolver already uses for the rendered component.\n *\n * This is the single definition of \"does this link expand\" — it is consumed by both the client\n * resolver (which builds the nested schema) and the server column builder (which must still\n * SELECT an `inline` link's FK column). Call it; never re-derive the rule at the call site, or\n * the two will drift and the client will render a table for a column the server never selected.\n *\n * @param link - the link declaration (only `component` and `cardinality` are consulted)\n * @param fieldComponent - the linked field's own `component`, used when the declaration names none\n * @public\n */\nexport function resolveLinkRenderMode(\n\tlink: { component?: string; cardinality?: string },\n\tfieldComponent?: string\n): LinkRenderMode {\n\tif (componentLinkExpansion(link.component ?? fieldComponent) === 'inline') return 'inline'\n\treturn link.cardinality === 'noneOrMany' || link.cardinality === 'atLeastOne' ? 'table' : 'record'\n}\n\n/**\n * Every component Stonecrop ships with that can render a value field, sorted by name.\n *\n * The union of the two maps above is the definition, not a copy of it: a shipped component either\n * categorises a value ({@link COMPONENT_CATEGORY}) or is one of the link containers that has no\n * value of its own ({@link COMPONENT_LINK_EXPANSION}'s `AForm`/`ATable`). `AFieldset` is absent by\n * the same rule — it is a `kind: 'fieldset'` container, so it is never a value field's component.\n *\n * `component` is an **open** axis: any string is valid, and naming a custom component is how an app\n * renders a field Stonecrop ships no widget for. This list is therefore the set to *suggest* to an\n * author, and to check first-party data against — never a set to validate arbitrary input against.\n *\n * @public\n */\nexport const CANONICAL_COMPONENTS: readonly string[] = [\n\t...new Set([...Object.keys(COMPONENT_CATEGORY), ...Object.keys(COMPONENT_LINK_EXPANSION)]),\n].toSorted()\n","import type { FieldOptions } from './field'\n\n/**\n * Semantic badge variant. Maps to theme tokens `--sc-badge-{variant}-*`.\n * @public\n */\nexport type BadgeVariant = 'neutral' | 'success' | 'warning' | 'danger' | 'brand'\n\n/**\n * Where ABadge paints the same descriptor.\n * @public\n */\nexport type BadgePresentation = 'cell-fill' | 'input-accent'\n\n/**\n * Per-choice badge configuration in a Select options map.\n * @public\n */\nexport interface BadgeSpecObject {\n\tvariant?: BadgeVariant\n\tcolor?: string\n\tlabel?: string\n}\n\n/**\n * Value in a choice→badge map: shorthand variant or object form.\n * @public\n */\nexport type BadgeSpec = BadgeVariant | BadgeSpecObject\n\n/**\n * Select field options when choices carry badge colors.\n * @public\n */\nexport interface SelectOptions extends Record<string, unknown> {\n\tchoices: string[]\n\tbadges?: Record<string, BadgeSpec>\n}\n\n/**\n * Resolved badge for rendering. Returned by `format` or built from an options map.\n * @public\n */\nexport interface BadgeDescriptor {\n\tlabel: string\n\tvariant?: BadgeVariant\n\tcolor?: string\n}\n\nconst BADGE_VARIANTS = new Set<string>(['neutral', 'success', 'warning', 'danger', 'brand'])\n\nconst BADGE_SPEC_OBJECT_KEYS = new Set(['variant', 'color', 'label'])\n\nfunction isOptionsRecord(options: FieldOptions): options is Record<string, unknown> {\n\treturn !Array.isArray(options)\n}\n\nfunction isBadgeVariant(value: unknown): value is BadgeVariant {\n\treturn typeof value === 'string' && BADGE_VARIANTS.has(value)\n}\n\nfunction isBadgeSpecObject(value: unknown): value is BadgeSpecObject {\n\tif (value === null || typeof value !== 'object' || Array.isArray(value)) return false\n\tfor (const key of Object.keys(value)) {\n\t\tif (!BADGE_SPEC_OBJECT_KEYS.has(key)) return false\n\t}\n\tconst obj = value as BadgeSpecObject\n\tif (obj.variant !== undefined && !isBadgeVariant(obj.variant)) return false\n\tif (obj.color !== undefined && typeof obj.color !== 'string') return false\n\tif (obj.label !== undefined && typeof obj.label !== 'string') return false\n\treturn true\n}\n\nfunction isBadgeSpec(value: unknown): value is BadgeSpec {\n\treturn isBadgeVariant(value) || isBadgeSpecObject(value)\n}\n\n/**\n * Narrows an options record to the structured `{ choices, badges }` form. A type predicate rather\n * than a cast: `SelectOptions` requires `choices`, so asserting into it trips `no-unsafe-type-assertion`.\n */\nfunction isStructuredSelectOptions(options: Record<string, unknown>): options is SelectOptions {\n\treturn Array.isArray(options.choices)\n}\n\n/**\n * True when `value` is a resolved badge descriptor for ACell / ADropdown.\n * @public\n */\nexport function isBadgeDescriptor(value: unknown): value is BadgeDescriptor {\n\tif (value === null || typeof value !== 'object' || Array.isArray(value)) return false\n\t// Partial<> keeps this a widening assertion; asserting into BadgeDescriptor itself (required\n\t// `label`) narrows, which `no-unsafe-type-assertion` rejects.\n\tconst obj = value as Partial<BadgeDescriptor>\n\tif (typeof obj.label !== 'string') return false\n\tif (obj.variant !== undefined && !isBadgeVariant(obj.variant)) return false\n\tif (obj.color !== undefined && typeof obj.color !== 'string') return false\n\treturn true\n}\n\n/**\n * True when `options` is a Select choice map (`{ Open: \"warning\", ... }` or\n * `{ choices: [...], badges: {...} }`), not a quantity/currency/code config bag.\n * @public\n */\nexport function isSelectChoiceMap(options: FieldOptions | undefined): options is Record<string, BadgeSpec> {\n\tif (options === undefined || Array.isArray(options) || !isOptionsRecord(options)) return false\n\tif (isStructuredSelectOptions(options)) {\n\t\tconst badges = options.badges\n\t\tif (badges === undefined) return false\n\t\treturn Object.values(badges).every(isBadgeSpec)\n\t}\n\tconst entries = Object.entries(options)\n\tif (entries.length === 0) return false\n\treturn entries.every(([, value]) => isBadgeSpec(value))\n}\n\n/**\n * True when `options` uses the structured SelectOptions shape.\n * @public\n */\nexport function isSelectOptions(options: FieldOptions | undefined): options is SelectOptions {\n\tif (options === undefined || Array.isArray(options) || !isOptionsRecord(options)) return false\n\treturn isStructuredSelectOptions(options)\n}\n\n/**\n * Dropdown / filter choice strings.\n * @public\n */\nexport function selectChoices(options: FieldOptions | undefined): string[] {\n\tif (options === undefined) return []\n\tif (Array.isArray(options)) return options\n\tif (!isOptionsRecord(options)) return []\n\tif (isStructuredSelectOptions(options)) return options.choices\n\tif (isSelectChoiceMap(options)) return Object.keys(options)\n\treturn []\n}\n\nfunction normalizeBadgeSpec(spec: unknown, key: string): BadgeDescriptor {\n\tif (isBadgeVariant(spec)) {\n\t\treturn { label: key, variant: spec }\n\t}\n\t// A malformed spec still names a real choice, so keep the label and drop only the styling.\n\t// Reading `.variant`/`.color` off an unvalidated value is what let a misspelt variant reach the\n\t// DOM as an unmatched class, and threw outright on null.\n\tif (!isBadgeSpecObject(spec)) {\n\t\treturn { label: key }\n\t}\n\treturn {\n\t\tlabel: spec.label ?? key,\n\t\tvariant: spec.variant,\n\t\tcolor: spec.color,\n\t}\n}\n\nfunction lookupFromBareMap(map: Record<string, BadgeSpec>, key: string | undefined): BadgeDescriptor | undefined {\n\tif (key === undefined || key === '') return undefined\n\tif (!(key in map)) return undefined\n\treturn normalizeBadgeSpec(map[key], key)\n}\n\nfunction lookupFromStructured(options: SelectOptions, key: string | undefined): BadgeDescriptor | undefined {\n\tif (key === undefined || key === '') return undefined\n\tconst badges = options.badges\n\tif (!badges || !(key in badges)) return undefined\n\treturn normalizeBadgeSpec(badges[key], key)\n}\n\n/**\n * Resolve a stored choice value to a badge descriptor using field options.\n * @public\n */\nexport function lookupBadge(options: FieldOptions | undefined, key: string | undefined): BadgeDescriptor | undefined {\n\tif (options === undefined || key === undefined || key === '') return undefined\n\tif (Array.isArray(options)) return undefined\n\tif (!isOptionsRecord(options)) return undefined\n\tif (isStructuredSelectOptions(options)) return lookupFromStructured(options, key)\n\tif (isSelectChoiceMap(options)) return lookupFromBareMap(options, key)\n\treturn undefined\n}\n\n/**\n * Whether field options carry any badge mapping.\n * @public\n */\nexport function hasBadgeOptions(options: FieldOptions | undefined): boolean {\n\tif (options === undefined || Array.isArray(options)) return false\n\tif (!isOptionsRecord(options)) return false\n\tif (isStructuredSelectOptions(options)) return options.badges !== undefined && Object.keys(options.badges).length > 0\n\treturn isSelectChoiceMap(options)\n}\n"],"names":["COMPONENT_CATEGORY","componentCategory","component","COMPONENT_LINK_EXPANSION","componentLinkExpansion","resolveLinkRenderMode","link","fieldComponent","CANONICAL_COMPONENTS","BADGE_VARIANTS","BADGE_SPEC_OBJECT_KEYS","isOptionsRecord","options","isBadgeVariant","value","isBadgeSpecObject","key","obj","isBadgeSpec","isStructuredSelectOptions","isBadgeDescriptor","isSelectChoiceMap","badges","entries","isSelectOptions","selectChoices","normalizeBadgeSpec","spec","lookupFromBareMap","map","lookupFromStructured","lookupBadge","hasBadgeOptions"],"mappings":"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"}
package/dist/schema.d.ts CHANGED
@@ -32,6 +32,44 @@ export declare type ActionDefinition = z.infer<typeof ActionDefinition>;
32
32
  */
33
33
  export declare type AuthoredDoctype = Record<string, unknown>;
34
34
 
35
+ /**
36
+ * Resolved badge for rendering. Returned by `format` or built from an options map.
37
+ * @public
38
+ */
39
+ export declare interface BadgeDescriptor {
40
+ label: string;
41
+ variant?: BadgeVariant;
42
+ color?: string;
43
+ }
44
+
45
+ /**
46
+ * Where ABadge paints the same descriptor.
47
+ * @public
48
+ */
49
+ export declare type BadgePresentation = 'cell-fill' | 'input-accent';
50
+
51
+ /**
52
+ * Value in a choice→badge map: shorthand variant or object form.
53
+ * @public
54
+ */
55
+ export declare type BadgeSpec = BadgeVariant | BadgeSpecObject;
56
+
57
+ /**
58
+ * Per-choice badge configuration in a Select options map.
59
+ * @public
60
+ */
61
+ export declare interface BadgeSpecObject {
62
+ variant?: BadgeVariant;
63
+ color?: string;
64
+ label?: string;
65
+ }
66
+
67
+ /**
68
+ * Semantic badge variant. Maps to theme tokens `--sc-badge-{variant}-*`.
69
+ * @public
70
+ */
71
+ export declare type BadgeVariant = 'neutral' | 'success' | 'warning' | 'danger' | 'brand';
72
+
35
73
  /**
36
74
  * Build a merged scalar map from the built-in maps and user-provided custom scalars.
37
75
  * Precedence (highest to lowest): customScalars → GQL_SCALAR_MAP → WELL_KNOWN_SCALARS
@@ -247,10 +285,15 @@ export declare interface ColumnSchema {
247
285
  * Only applicable when `modalComponent` is set.
248
286
  */
249
287
  modalComponentExtraProps?: Record<string, any>;
288
+ /**
289
+ * Type-specific field options — Select choices, badge maps, quantity/currency config, etc.
290
+ * Spreads through `schemaToColumns` to `TableColumn`.
291
+ */
292
+ options?: FieldOptions;
250
293
  /**
251
294
  * Serialized function string used to format the cell value for display. Deserialized at
252
- * render time by the table store's `getFormattedValue`. `TableColumn.format` widens this
253
- * to also accept a live function directly.
295
+ * render time by the table store's `getFormattedValue`. May return a plain string, HTML, or a
296
+ * {@link BadgeDescriptor}. `TableColumn.format` widens this to also accept a live function.
254
297
  */
255
298
  format?: string;
256
299
  /**
@@ -632,6 +675,7 @@ export declare type FetchStrategy = z.infer<typeof FetchStrategy>;
632
675
  *
633
676
  * Usage:
634
677
  * - Select: array of choices (["Draft", "Submitted", "Cancelled"])
678
+ * - Select with badges: \{ choices: [...], badges: \{ Open: "warning", ... \} \} or bare map
635
679
  * - Decimal: config object (\{ precision: 10, scale: 2 \})
636
680
  * - Code: config object (\{ language: "python" \})
637
681
  *
@@ -1022,6 +1066,12 @@ export declare interface GraphQLConversionOptions {
1022
1066
  includeUnmappedMeta?: boolean;
1023
1067
  }
1024
1068
 
1069
+ /**
1070
+ * Whether field options carry any badge mapping.
1071
+ * @public
1072
+ */
1073
+ export declare function hasBadgeOptions(options: FieldOptions | undefined): boolean;
1074
+
1025
1075
  /**
1026
1076
  * Controls the level of user interaction for a field, container, or table.
1027
1077
  *
@@ -1089,6 +1139,25 @@ export declare function isActionAllowedInState(action: {
1089
1139
  allowedStates?: string[] | null;
1090
1140
  }, currentState: string): boolean;
1091
1141
 
1142
+ /**
1143
+ * True when `value` is a resolved badge descriptor for ACell / ADropdown.
1144
+ * @public
1145
+ */
1146
+ export declare function isBadgeDescriptor(value: unknown): value is BadgeDescriptor;
1147
+
1148
+ /**
1149
+ * True when `options` is a Select choice map (`{ Open: "warning", ... }` or
1150
+ * `{ choices: [...], badges: {...} }`), not a quantity/currency/code config bag.
1151
+ * @public
1152
+ */
1153
+ export declare function isSelectChoiceMap(options: FieldOptions | undefined): options is Record<string, BadgeSpec>;
1154
+
1155
+ /**
1156
+ * True when `options` uses the structured SelectOptions shape.
1157
+ * @public
1158
+ */
1159
+ export declare function isSelectOptions(options: FieldOptions | undefined): options is SelectOptions;
1160
+
1092
1161
  /**
1093
1162
  * Lazy fetch strategy - data is fetched on demand in a separate query.
1094
1163
  * @public
@@ -1182,6 +1251,12 @@ export declare type LinkExpansion = 'inline' | 'expand';
1182
1251
  */
1183
1252
  export declare type LinkRenderMode = 'inline' | 'record' | 'table';
1184
1253
 
1254
+ /**
1255
+ * Resolve a stored choice value to a badge descriptor using field options.
1256
+ * @public
1257
+ */
1258
+ export declare function lookupBadge(options: FieldOptions | undefined, key: string | undefined): BadgeDescriptor | undefined;
1259
+
1185
1260
  /**
1186
1261
  * Verify an authored doctype against freshly generated output and stamp provenance.
1187
1262
  *
@@ -1277,6 +1352,21 @@ export declare function resolveLinkRenderMode(link: {
1277
1352
  cardinality?: string;
1278
1353
  }, fieldComponent?: string): LinkRenderMode;
1279
1354
 
1355
+ /**
1356
+ * Dropdown / filter choice strings.
1357
+ * @public
1358
+ */
1359
+ export declare function selectChoices(options: FieldOptions | undefined): string[];
1360
+
1361
+ /**
1362
+ * Select field options when choices carry badge colors.
1363
+ * @public
1364
+ */
1365
+ export declare interface SelectOptions extends Record<string, unknown> {
1366
+ choices: string[];
1367
+ badges?: Record<string, BadgeSpec>;
1368
+ }
1369
+
1280
1370
  /**
1281
1371
  * Serialized function type - a function serialized to a string.
1282
1372
  * Used for custom fetch handlers.
@@ -1539,9 +1629,10 @@ export declare interface ValueField {
1539
1629
  edit?: boolean;
1540
1630
  /** Input mask pattern or serialized function */
1541
1631
  mask?: string;
1542
- /** Serialized `(value) => string` function for display formatting — distinct from `mask` (input).
1543
- * Spreads through `schemaToColumns` to `ColumnSchema.format`; deserialized at render time by
1544
- * ATable's `getFormattedValue`. */
1632
+ /** Serialized display formatter — distinct from `mask` (input). Spreads through
1633
+ * `schemaToColumns` to `ColumnSchema.format`; deserialized at render time by ATable's
1634
+ * `getFormattedValue`. Returns a plain string, HTML, or a {@link BadgeDescriptor} for badge
1635
+ * cells. When a descriptor is returned it wins over any badge map on `options`. */
1545
1636
  format?: string;
1546
1637
  /** Per-field interaction mode override */
1547
1638
  mode?: InteractionMode;
@@ -0,0 +1,74 @@
1
+ import type { FieldOptions } from './field';
2
+ /**
3
+ * Semantic badge variant. Maps to theme tokens `--sc-badge-{variant}-*`.
4
+ * @public
5
+ */
6
+ export type BadgeVariant = 'neutral' | 'success' | 'warning' | 'danger' | 'brand';
7
+ /**
8
+ * Where ABadge paints the same descriptor.
9
+ * @public
10
+ */
11
+ export type BadgePresentation = 'cell-fill' | 'input-accent';
12
+ /**
13
+ * Per-choice badge configuration in a Select options map.
14
+ * @public
15
+ */
16
+ export interface BadgeSpecObject {
17
+ variant?: BadgeVariant;
18
+ color?: string;
19
+ label?: string;
20
+ }
21
+ /**
22
+ * Value in a choice→badge map: shorthand variant or object form.
23
+ * @public
24
+ */
25
+ export type BadgeSpec = BadgeVariant | BadgeSpecObject;
26
+ /**
27
+ * Select field options when choices carry badge colors.
28
+ * @public
29
+ */
30
+ export interface SelectOptions extends Record<string, unknown> {
31
+ choices: string[];
32
+ badges?: Record<string, BadgeSpec>;
33
+ }
34
+ /**
35
+ * Resolved badge for rendering. Returned by `format` or built from an options map.
36
+ * @public
37
+ */
38
+ export interface BadgeDescriptor {
39
+ label: string;
40
+ variant?: BadgeVariant;
41
+ color?: string;
42
+ }
43
+ /**
44
+ * True when `value` is a resolved badge descriptor for ACell / ADropdown.
45
+ * @public
46
+ */
47
+ export declare function isBadgeDescriptor(value: unknown): value is BadgeDescriptor;
48
+ /**
49
+ * True when `options` is a Select choice map (`{ Open: "warning", ... }` or
50
+ * `{ choices: [...], badges: {...} }`), not a quantity/currency/code config bag.
51
+ * @public
52
+ */
53
+ export declare function isSelectChoiceMap(options: FieldOptions | undefined): options is Record<string, BadgeSpec>;
54
+ /**
55
+ * True when `options` uses the structured SelectOptions shape.
56
+ * @public
57
+ */
58
+ export declare function isSelectOptions(options: FieldOptions | undefined): options is SelectOptions;
59
+ /**
60
+ * Dropdown / filter choice strings.
61
+ * @public
62
+ */
63
+ export declare function selectChoices(options: FieldOptions | undefined): string[];
64
+ /**
65
+ * Resolve a stored choice value to a badge descriptor using field options.
66
+ * @public
67
+ */
68
+ export declare function lookupBadge(options: FieldOptions | undefined, key: string | undefined): BadgeDescriptor | undefined;
69
+ /**
70
+ * Whether field options carry any badge mapping.
71
+ * @public
72
+ */
73
+ export declare function hasBadgeOptions(options: FieldOptions | undefined): boolean;
74
+ //# sourceMappingURL=badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"badge.d.ts","sourceRoot":"","sources":["../../src/badge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEjF;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,cAAc,CAAA;AAE5D;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,eAAe,CAAA;AAEtD;;;GAGG;AACH,MAAM,WAAW,aAAc,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;CAClC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;CACd;AAsCD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAS1E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAUzG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,IAAI,aAAa,CAG3F;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,GAAG,MAAM,EAAE,CAOzE;AAgCD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,GAAG,SAAS,CAOnH;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,CAK1E"}
@@ -0,0 +1,158 @@
1
+ const BADGE_VARIANTS = new Set(['neutral', 'success', 'warning', 'danger', 'brand']);
2
+ const BADGE_SPEC_OBJECT_KEYS = new Set(['variant', 'color', 'label']);
3
+ function isOptionsRecord(options) {
4
+ return !Array.isArray(options);
5
+ }
6
+ function isBadgeVariant(value) {
7
+ return typeof value === 'string' && BADGE_VARIANTS.has(value);
8
+ }
9
+ function isBadgeSpecObject(value) {
10
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
11
+ return false;
12
+ for (const key of Object.keys(value)) {
13
+ if (!BADGE_SPEC_OBJECT_KEYS.has(key))
14
+ return false;
15
+ }
16
+ const obj = value;
17
+ if (obj.variant !== undefined && !isBadgeVariant(obj.variant))
18
+ return false;
19
+ if (obj.color !== undefined && typeof obj.color !== 'string')
20
+ return false;
21
+ if (obj.label !== undefined && typeof obj.label !== 'string')
22
+ return false;
23
+ return true;
24
+ }
25
+ function isBadgeSpec(value) {
26
+ return isBadgeVariant(value) || isBadgeSpecObject(value);
27
+ }
28
+ /**
29
+ * Narrows an options record to the structured `{ choices, badges }` form. A type predicate rather
30
+ * than a cast: `SelectOptions` requires `choices`, so asserting into it trips `no-unsafe-type-assertion`.
31
+ */
32
+ function isStructuredSelectOptions(options) {
33
+ return Array.isArray(options.choices);
34
+ }
35
+ /**
36
+ * True when `value` is a resolved badge descriptor for ACell / ADropdown.
37
+ * @public
38
+ */
39
+ export function isBadgeDescriptor(value) {
40
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
41
+ return false;
42
+ // Partial<> keeps this a widening assertion; asserting into BadgeDescriptor itself (required
43
+ // `label`) narrows, which `no-unsafe-type-assertion` rejects.
44
+ const obj = value;
45
+ if (typeof obj.label !== 'string')
46
+ return false;
47
+ if (obj.variant !== undefined && !isBadgeVariant(obj.variant))
48
+ return false;
49
+ if (obj.color !== undefined && typeof obj.color !== 'string')
50
+ return false;
51
+ return true;
52
+ }
53
+ /**
54
+ * True when `options` is a Select choice map (`{ Open: "warning", ... }` or
55
+ * `{ choices: [...], badges: {...} }`), not a quantity/currency/code config bag.
56
+ * @public
57
+ */
58
+ export function isSelectChoiceMap(options) {
59
+ if (options === undefined || Array.isArray(options) || !isOptionsRecord(options))
60
+ return false;
61
+ if (isStructuredSelectOptions(options)) {
62
+ const badges = options.badges;
63
+ if (badges === undefined)
64
+ return false;
65
+ return Object.values(badges).every(isBadgeSpec);
66
+ }
67
+ const entries = Object.entries(options);
68
+ if (entries.length === 0)
69
+ return false;
70
+ return entries.every(([, value]) => isBadgeSpec(value));
71
+ }
72
+ /**
73
+ * True when `options` uses the structured SelectOptions shape.
74
+ * @public
75
+ */
76
+ export function isSelectOptions(options) {
77
+ if (options === undefined || Array.isArray(options) || !isOptionsRecord(options))
78
+ return false;
79
+ return isStructuredSelectOptions(options);
80
+ }
81
+ /**
82
+ * Dropdown / filter choice strings.
83
+ * @public
84
+ */
85
+ export function selectChoices(options) {
86
+ if (options === undefined)
87
+ return [];
88
+ if (Array.isArray(options))
89
+ return options;
90
+ if (!isOptionsRecord(options))
91
+ return [];
92
+ if (isStructuredSelectOptions(options))
93
+ return options.choices;
94
+ if (isSelectChoiceMap(options))
95
+ return Object.keys(options);
96
+ return [];
97
+ }
98
+ function normalizeBadgeSpec(spec, key) {
99
+ if (isBadgeVariant(spec)) {
100
+ return { label: key, variant: spec };
101
+ }
102
+ // A malformed spec still names a real choice, so keep the label and drop only the styling.
103
+ // Reading `.variant`/`.color` off an unvalidated value is what let a misspelt variant reach the
104
+ // DOM as an unmatched class, and threw outright on null.
105
+ if (!isBadgeSpecObject(spec)) {
106
+ return { label: key };
107
+ }
108
+ return {
109
+ label: spec.label ?? key,
110
+ variant: spec.variant,
111
+ color: spec.color,
112
+ };
113
+ }
114
+ function lookupFromBareMap(map, key) {
115
+ if (key === undefined || key === '')
116
+ return undefined;
117
+ if (!(key in map))
118
+ return undefined;
119
+ return normalizeBadgeSpec(map[key], key);
120
+ }
121
+ function lookupFromStructured(options, key) {
122
+ if (key === undefined || key === '')
123
+ return undefined;
124
+ const badges = options.badges;
125
+ if (!badges || !(key in badges))
126
+ return undefined;
127
+ return normalizeBadgeSpec(badges[key], key);
128
+ }
129
+ /**
130
+ * Resolve a stored choice value to a badge descriptor using field options.
131
+ * @public
132
+ */
133
+ export function lookupBadge(options, key) {
134
+ if (options === undefined || key === undefined || key === '')
135
+ return undefined;
136
+ if (Array.isArray(options))
137
+ return undefined;
138
+ if (!isOptionsRecord(options))
139
+ return undefined;
140
+ if (isStructuredSelectOptions(options))
141
+ return lookupFromStructured(options, key);
142
+ if (isSelectChoiceMap(options))
143
+ return lookupFromBareMap(options, key);
144
+ return undefined;
145
+ }
146
+ /**
147
+ * Whether field options carry any badge mapping.
148
+ * @public
149
+ */
150
+ export function hasBadgeOptions(options) {
151
+ if (options === undefined || Array.isArray(options))
152
+ return false;
153
+ if (!isOptionsRecord(options))
154
+ return false;
155
+ if (isStructuredSelectOptions(options))
156
+ return options.badges !== undefined && Object.keys(options.badges).length > 0;
157
+ return isSelectChoiceMap(options);
158
+ }