@colixsystems/widget-sdk 0.195.0 → 0.196.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 CHANGED
@@ -77,7 +77,20 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
77
77
 
78
78
  ## Status
79
79
 
80
- `v0.195.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
80
+ `v0.196.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
81
+
82
+ ### What's new in 0.196.0 (contract 1.157.0)
83
+
84
+ **A gradient can carry more than two colours.** A component gradient was exactly `{ from, to, angle }`. It now takes an optional `via` list of up to three middle colours, painted in order and evenly spaced between `from` and `to`:
85
+
86
+ ```js
87
+ { from: "#0f172a", via: ["#7c3aed", "#db2777"], to: "#f59e0b", angle: 135 }
88
+ ```
89
+
90
+ - **`normaliseComponentGradient`** returns `via` only when the value carries some, so a two-colour gradient normalises exactly as before. A malformed `via` (not a list, more than `CONTRACT.themeComponentGradient.maxViaStops` entries, or an entry that is not a hex colour) is dropped and the two ends still paint, so a stored value never loses its gradient over it. Pass `{ strict: true }` to get `null` for a malformed `via` instead, which is what a write boundary wants.
91
+ - **`componentGradientColors(gradient)`** gives a normalised gradient's colours in paint order, ready for `<Gradient colors={…}>`. **`componentGradientMidpoint(gradient)`** gives the colour halfway along it, the basis for choosing readable text.
92
+ - **`<Card>` and `<Button>` paint the middle colours** when their gradient carries them, and a layout container's wash and photo scrim do the same on the web Player and in the exported app. A middle colour of a container wash may bind to the theme (`"theme:colors.primary"`) like its two ends.
93
+ - **Fully additive.** Every stored two-colour gradient renders byte-identically.
81
94
 
82
95
  ### What's new in 0.195.0 (contract 1.156.0)
83
96
 
@@ -5,7 +5,13 @@
5
5
  // Every resolver emits ONLY the fields that are set, so a widget's own base
6
6
  // style keeps whatever the author left alone.
7
7
 
8
- import { CONTRACT, isHexColor, mixHex, normaliseComponentGradient } from "./contract.js";
8
+ import {
9
+ CONTRACT,
10
+ isHexColor,
11
+ normaliseComponentGradient,
12
+ componentGradientColors,
13
+ componentGradientMidpoint,
14
+ } from "./contract.js";
9
15
  import { normaliseCornerRadius, cornerRadiusStyle } from "./corner-radius.js";
10
16
  import { normaliseSpacing, spacingStyle, isZeroSpacing } from "./spacing.js";
11
17
  import { flattenStyle } from "./flatten-style.js";
@@ -117,7 +123,7 @@ export function cardLayoutStyle(style) {
117
123
  export function gradientProps(value) {
118
124
  const gradient = normaliseComponentGradient(value);
119
125
  return gradient
120
- ? { colors: [gradient.from, gradient.to], angle: gradient.angle }
126
+ ? { colors: componentGradientColors(gradient), angle: gradient.angle }
121
127
  : null;
122
128
  }
123
129
 
@@ -131,8 +137,13 @@ export function gradientProps(value) {
131
137
  */
132
138
  export function cardBackdrop(styles, gradient) {
133
139
  if (gradient) {
134
- const [from, to] = gradient.colors;
135
- return isOpaqueHex(from) && isOpaqueHex(to) ? mixHex(from, to, 0.5) : null;
140
+ const { colors } = gradient;
141
+ if (!colors.every(isOpaqueHex)) return null;
142
+ return componentGradientMidpoint({
143
+ from: colors[0],
144
+ via: colors.slice(1, -1),
145
+ to: colors[colors.length - 1],
146
+ });
136
147
  }
137
148
  const fill = flattenStyle(styles).backgroundColor;
138
149
  return isOpaqueHex(fill) ? fill : null;
package/dist/contract.cjs CHANGED
@@ -332,6 +332,8 @@ const THEME_COMPONENT_GRADIENT = Object.freeze({
332
332
  angleMin: 0,
333
333
  angleMax: 359,
334
334
  defaultAngle: 180,
335
+ // sc-10685: middle colours allowed between the two ends (`via`).
336
+ maxViaStops: 3,
335
337
  });
336
338
 
337
339
  // REQ-THEME-LOOK: the app-wide SPACING MULTIPLIER. Layout spacing lives on the
@@ -5099,7 +5101,14 @@ const CONTRACT = deepFreeze({
5099
5101
  // `update(userId, { name?, email? })`, gated on `users.write:*`; the
5100
5102
  // linter requires that scope for a `.update(` call beside `useUsers(`.
5101
5103
  // `DirectoryError` gains `reason`, the host's specific refusal code.
5102
- version: "1.156.0",
5104
+ // 1.157.0: additive (sc-10685) — a component gradient takes an optional
5105
+ // `via` list of up to `themeComponentGradient.maxViaStops` middle colours
5106
+ // between `from` and `to`, evenly spaced. `normaliseComponentGradient`
5107
+ // returns it only when present, so a two-colour value normalises exactly
5108
+ // as before; a malformed `via` is dropped, or voids the gradient under the
5109
+ // new `{ strict: true }` option. New `componentGradientColors` / `componentGradientMidpoint`
5110
+ // read the paint list and the ink basis off a normalised gradient.
5111
+ version: "1.157.0",
5103
5112
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5104
5113
  hooks: HOOKS,
5105
5114
  primitives: PRIMITIVES,
@@ -5418,6 +5427,10 @@ const GRADIENT_HEX_RE = HEX_RE;
5418
5427
 
5419
5428
  /**
5420
5429
  * sc-3727 — normalise a component `gradient` to `{ from, to, angle }`, or `null`.
5430
+ * sc-10685: plus `via`, the middle colours, only when the value carries some.
5431
+ * A malformed `via` is dropped and the two ends still paint, so a stored value
5432
+ * never loses its wash over it; `{ strict: true }` voids the gradient instead,
5433
+ * for the boundaries that must refuse the write.
5421
5434
  *
5422
5435
  * ONE validator for a value arriving by two routes — the theme token and the
5423
5436
  * author's never-coerced per-instance `props.style` — so `set_theme` and the
@@ -5426,7 +5439,7 @@ const GRADIENT_HEX_RE = HEX_RE;
5426
5439
  * hex grammar is also the injection guard — these stops are interpolated into
5427
5440
  * CSS and into generated export source.
5428
5441
  */
5429
- function normaliseComponentGradient(raw) {
5442
+ function normaliseComponentGradient(raw, options) {
5430
5443
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
5431
5444
  const from = typeof raw.from === "string" ? raw.from.trim() : "";
5432
5445
  const to = typeof raw.to === "string" ? raw.to.trim() : "";
@@ -5436,7 +5449,44 @@ function normaliseComponentGradient(raw) {
5436
5449
  // land NaN in a style.
5437
5450
  const angle = raw.angle;
5438
5451
  const deg = Number.isFinite(angle) ? Math.round(angle) : defaultAngle;
5439
- return { from, to, angle: ((deg % 360) + 360) % 360 };
5452
+ const via = normaliseGradientVia(raw.via);
5453
+ if (!via && options && options.strict) return null;
5454
+ const gradient = { from, to, angle: ((deg % 360) + 360) % 360 };
5455
+ if (via && via.length > 0) gradient.via = via;
5456
+ return gradient;
5457
+ }
5458
+
5459
+ // All or nothing: one bad middle colour and none of them paint. `null` = malformed.
5460
+ function normaliseGradientVia(raw) {
5461
+ if (raw === undefined || raw === null) return [];
5462
+ if (!Array.isArray(raw) || raw.length > THEME_COMPONENT_GRADIENT.maxViaStops) {
5463
+ return null;
5464
+ }
5465
+ const via = [];
5466
+ for (const item of raw) {
5467
+ const stop = typeof item === "string" ? item.trim() : "";
5468
+ if (!GRADIENT_HEX_RE.test(stop)) return null;
5469
+ via.push(stop);
5470
+ }
5471
+ return via;
5472
+ }
5473
+
5474
+ /**
5475
+ * sc-10685 — a NORMALISED gradient's colours in paint order, evenly spaced:
5476
+ * `[from, ...via, to]`. The one place both hosts read the list from.
5477
+ */
5478
+ function componentGradientColors(gradient) {
5479
+ return [gradient.from, ...(gradient.via || []), gradient.to];
5480
+ }
5481
+
5482
+ /**
5483
+ * sc-10685 — the colour halfway along a NORMALISED gradient, which is what
5484
+ * text laid across it is contrasted against. Two colours: their mix, as before.
5485
+ */
5486
+ function componentGradientMidpoint(gradient) {
5487
+ const colors = componentGradientColors(gradient);
5488
+ const middle = (colors.length - 1) / 2;
5489
+ return mixHex(colors[Math.floor(middle)], colors[Math.ceil(middle)], 0.5);
5440
5490
  }
5441
5491
 
5442
5492
  // REQ-THEME-LOOK: coerce a theme's spacing scale into the usable band. Shared
@@ -5479,6 +5529,8 @@ module.exports = {
5479
5529
  INK_CONTRAST,
5480
5530
  gradientAngleToVector,
5481
5531
  normaliseComponentGradient,
5532
+ componentGradientColors,
5533
+ componentGradientMidpoint,
5482
5534
  isThemeColorTokenRef,
5483
5535
  parseThemeColorTokenRef,
5484
5536
  formatThemeColorTokenRef,
package/dist/contract.js CHANGED
@@ -332,6 +332,8 @@ const THEME_COMPONENT_GRADIENT = Object.freeze({
332
332
  angleMin: 0,
333
333
  angleMax: 359,
334
334
  defaultAngle: 180,
335
+ // sc-10685: middle colours allowed between the two ends (`via`).
336
+ maxViaStops: 3,
335
337
  });
336
338
 
337
339
  // REQ-THEME-LOOK: the app-wide SPACING MULTIPLIER. Layout spacing lives on the
@@ -5095,7 +5097,14 @@ const CONTRACT = deepFreeze({
5095
5097
  // `update(userId, { name?, email? })`, gated on `users.write:*`; the
5096
5098
  // linter requires that scope for a `.update(` call beside `useUsers(`.
5097
5099
  // `DirectoryError` gains `reason`, the host's specific refusal code.
5098
- version: "1.156.0",
5100
+ // 1.157.0: additive (sc-10685) — a component gradient takes an optional
5101
+ // `via` list of up to `themeComponentGradient.maxViaStops` middle colours
5102
+ // between `from` and `to`, evenly spaced. `normaliseComponentGradient`
5103
+ // returns it only when present, so a two-colour value normalises exactly
5104
+ // as before; a malformed `via` is dropped, or voids the gradient under the
5105
+ // new `{ strict: true }` option. New `componentGradientColors` / `componentGradientMidpoint`
5106
+ // read the paint list and the ink basis off a normalised gradient.
5107
+ version: "1.157.0",
5099
5108
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5100
5109
  hooks: HOOKS,
5101
5110
  primitives: PRIMITIVES,
@@ -5414,6 +5423,10 @@ const GRADIENT_HEX_RE = HEX_RE;
5414
5423
 
5415
5424
  /**
5416
5425
  * sc-3727 — normalise a component `gradient` to `{ from, to, angle }`, or `null`.
5426
+ * sc-10685: plus `via`, the middle colours, only when the value carries some.
5427
+ * A malformed `via` is dropped and the two ends still paint, so a stored value
5428
+ * never loses its wash over it; `{ strict: true }` voids the gradient instead,
5429
+ * for the boundaries that must refuse the write.
5417
5430
  *
5418
5431
  * ONE validator for a value arriving by two routes — the theme token and the
5419
5432
  * author's never-coerced per-instance `props.style` — so `set_theme` and the
@@ -5422,7 +5435,7 @@ const GRADIENT_HEX_RE = HEX_RE;
5422
5435
  * hex grammar is also the injection guard — these stops are interpolated into
5423
5436
  * CSS and into generated export source.
5424
5437
  */
5425
- function normaliseComponentGradient(raw) {
5438
+ function normaliseComponentGradient(raw, options) {
5426
5439
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
5427
5440
  const from = typeof raw.from === "string" ? raw.from.trim() : "";
5428
5441
  const to = typeof raw.to === "string" ? raw.to.trim() : "";
@@ -5432,7 +5445,44 @@ function normaliseComponentGradient(raw) {
5432
5445
  // land NaN in a style.
5433
5446
  const angle = raw.angle;
5434
5447
  const deg = Number.isFinite(angle) ? Math.round(angle) : defaultAngle;
5435
- return { from, to, angle: ((deg % 360) + 360) % 360 };
5448
+ const via = normaliseGradientVia(raw.via);
5449
+ if (!via && options && options.strict) return null;
5450
+ const gradient = { from, to, angle: ((deg % 360) + 360) % 360 };
5451
+ if (via && via.length > 0) gradient.via = via;
5452
+ return gradient;
5453
+ }
5454
+
5455
+ // All or nothing: one bad middle colour and none of them paint. `null` = malformed.
5456
+ function normaliseGradientVia(raw) {
5457
+ if (raw === undefined || raw === null) return [];
5458
+ if (!Array.isArray(raw) || raw.length > THEME_COMPONENT_GRADIENT.maxViaStops) {
5459
+ return null;
5460
+ }
5461
+ const via = [];
5462
+ for (const item of raw) {
5463
+ const stop = typeof item === "string" ? item.trim() : "";
5464
+ if (!GRADIENT_HEX_RE.test(stop)) return null;
5465
+ via.push(stop);
5466
+ }
5467
+ return via;
5468
+ }
5469
+
5470
+ /**
5471
+ * sc-10685 — a NORMALISED gradient's colours in paint order, evenly spaced:
5472
+ * `[from, ...via, to]`. The one place both hosts read the list from.
5473
+ */
5474
+ function componentGradientColors(gradient) {
5475
+ return [gradient.from, ...(gradient.via || []), gradient.to];
5476
+ }
5477
+
5478
+ /**
5479
+ * sc-10685 — the colour halfway along a NORMALISED gradient, which is what
5480
+ * text laid across it is contrasted against. Two colours: their mix, as before.
5481
+ */
5482
+ function componentGradientMidpoint(gradient) {
5483
+ const colors = componentGradientColors(gradient);
5484
+ const middle = (colors.length - 1) / 2;
5485
+ return mixHex(colors[Math.floor(middle)], colors[Math.ceil(middle)], 0.5);
5436
5486
  }
5437
5487
 
5438
5488
  // REQ-THEME-LOOK: coerce a theme's spacing scale into the usable band. Shared
@@ -5475,6 +5525,8 @@ export {
5475
5525
  INK_CONTRAST,
5476
5526
  gradientAngleToVector,
5477
5527
  normaliseComponentGradient,
5528
+ componentGradientColors,
5529
+ componentGradientMidpoint,
5478
5530
  isThemeColorTokenRef,
5479
5531
  parseThemeColorTokenRef,
5480
5532
  formatThemeColorTokenRef,
package/dist/index.d.ts CHANGED
@@ -3449,18 +3449,30 @@ export interface ComponentGradient {
3449
3449
  from: string;
3450
3450
  to: string;
3451
3451
  angle: number;
3452
+ /** Middle colours between `from` and `to`. Present only when there are some. */
3453
+ via?: string[];
3452
3454
  }
3453
3455
 
3454
3456
  /**
3455
3457
  * Normalise a component `gradient` style value to `{ from, to, angle }`, or null
3456
3458
  * when it is unusable. Both stops are required; the angle wraps into 0-359.
3459
+ * An optional `via` list (at most `CONTRACT.themeComponentGradient.maxViaStops`
3460
+ * hex colours) is kept as given. A malformed one is dropped and the two ends
3461
+ * still paint; pass `{ strict: true }` to get null for it instead.
3457
3462
  * Shared by the theme-token coercion and the widget render path, so the same
3458
3463
  * value cannot resolve two ways.
3459
3464
  */
3460
3465
  export function normaliseComponentGradient(
3461
3466
  raw: unknown,
3467
+ options?: { strict?: boolean },
3462
3468
  ): ComponentGradient | null;
3463
3469
 
3470
+ /** A normalised gradient's colours in paint order: `[from, ...via, to]`. */
3471
+ export function componentGradientColors(gradient: ComponentGradient): string[];
3472
+
3473
+ /** The colour halfway along a normalised gradient: the basis for text ink. */
3474
+ export function componentGradientMidpoint(gradient: ComponentGradient): string;
3475
+
3464
3476
  /** sc-8326 — the `theme:<path>` binding prefix, shared with the hosts' resolver. */
3465
3477
  export const THEME_TOKEN_REF_PREFIX: string;
3466
3478
 
package/dist/index.js CHANGED
@@ -178,6 +178,8 @@ export {
178
178
  deriveAccentTints,
179
179
  gradientAngleToVector,
180
180
  normaliseComponentGradient,
181
+ componentGradientColors,
182
+ componentGradientMidpoint,
181
183
  isThemeColorTokenRef,
182
184
  parseThemeColorTokenRef,
183
185
  formatThemeColorTokenRef,
@@ -171,6 +171,8 @@ export {
171
171
  deriveAccentTints,
172
172
  gradientAngleToVector,
173
173
  normaliseComponentGradient,
174
+ componentGradientColors,
175
+ componentGradientMidpoint,
174
176
  isThemeColorTokenRef,
175
177
  parseThemeColorTokenRef,
176
178
  formatThemeColorTokenRef,
@@ -148,7 +148,9 @@ function resolveAgainstSchema(schema, value, theme) {
148
148
  // VALUES — colours, sizes, enums — so an allowlisted `theme:` string in it can
149
149
  // only ever have been a binding. The depth cap keeps the walk bounded for the
150
150
  // structured style values (a gradient's stops, per-corner radii).
151
- const MAX_STYLE_DEPTH = 3;
151
+ // sc-10685: 4, so a widget gradient's `via` list (style > gradient > via >
152
+ // item) resolves like its two ends.
153
+ const MAX_STYLE_DEPTH = 4;
152
154
 
153
155
  function resolveStyleBag(value, theme, depth) {
154
156
  if (typeof value === "string") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.195.0",
3
+ "version": "0.196.0",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "homepage": "https://github.com/Colix-AB/AppStudio",
6
6
  "type": "module",
package/src/contract.js CHANGED
@@ -332,6 +332,8 @@ const THEME_COMPONENT_GRADIENT = Object.freeze({
332
332
  angleMin: 0,
333
333
  angleMax: 359,
334
334
  defaultAngle: 180,
335
+ // sc-10685: middle colours allowed between the two ends (`via`).
336
+ maxViaStops: 3,
335
337
  });
336
338
 
337
339
  // REQ-THEME-LOOK: the app-wide SPACING MULTIPLIER. Layout spacing lives on the
@@ -5095,7 +5097,14 @@ const CONTRACT = deepFreeze({
5095
5097
  // `update(userId, { name?, email? })`, gated on `users.write:*`; the
5096
5098
  // linter requires that scope for a `.update(` call beside `useUsers(`.
5097
5099
  // `DirectoryError` gains `reason`, the host's specific refusal code.
5098
- version: "1.156.0",
5100
+ // 1.157.0: additive (sc-10685) — a component gradient takes an optional
5101
+ // `via` list of up to `themeComponentGradient.maxViaStops` middle colours
5102
+ // between `from` and `to`, evenly spaced. `normaliseComponentGradient`
5103
+ // returns it only when present, so a two-colour value normalises exactly
5104
+ // as before; a malformed `via` is dropped, or voids the gradient under the
5105
+ // new `{ strict: true }` option. New `componentGradientColors` / `componentGradientMidpoint`
5106
+ // read the paint list and the ink basis off a normalised gradient.
5107
+ version: "1.157.0",
5099
5108
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5100
5109
  hooks: HOOKS,
5101
5110
  primitives: PRIMITIVES,
@@ -5414,6 +5423,10 @@ const GRADIENT_HEX_RE = HEX_RE;
5414
5423
 
5415
5424
  /**
5416
5425
  * sc-3727 — normalise a component `gradient` to `{ from, to, angle }`, or `null`.
5426
+ * sc-10685: plus `via`, the middle colours, only when the value carries some.
5427
+ * A malformed `via` is dropped and the two ends still paint, so a stored value
5428
+ * never loses its wash over it; `{ strict: true }` voids the gradient instead,
5429
+ * for the boundaries that must refuse the write.
5417
5430
  *
5418
5431
  * ONE validator for a value arriving by two routes — the theme token and the
5419
5432
  * author's never-coerced per-instance `props.style` — so `set_theme` and the
@@ -5422,7 +5435,7 @@ const GRADIENT_HEX_RE = HEX_RE;
5422
5435
  * hex grammar is also the injection guard — these stops are interpolated into
5423
5436
  * CSS and into generated export source.
5424
5437
  */
5425
- function normaliseComponentGradient(raw) {
5438
+ function normaliseComponentGradient(raw, options) {
5426
5439
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
5427
5440
  const from = typeof raw.from === "string" ? raw.from.trim() : "";
5428
5441
  const to = typeof raw.to === "string" ? raw.to.trim() : "";
@@ -5432,7 +5445,44 @@ function normaliseComponentGradient(raw) {
5432
5445
  // land NaN in a style.
5433
5446
  const angle = raw.angle;
5434
5447
  const deg = Number.isFinite(angle) ? Math.round(angle) : defaultAngle;
5435
- return { from, to, angle: ((deg % 360) + 360) % 360 };
5448
+ const via = normaliseGradientVia(raw.via);
5449
+ if (!via && options && options.strict) return null;
5450
+ const gradient = { from, to, angle: ((deg % 360) + 360) % 360 };
5451
+ if (via && via.length > 0) gradient.via = via;
5452
+ return gradient;
5453
+ }
5454
+
5455
+ // All or nothing: one bad middle colour and none of them paint. `null` = malformed.
5456
+ function normaliseGradientVia(raw) {
5457
+ if (raw === undefined || raw === null) return [];
5458
+ if (!Array.isArray(raw) || raw.length > THEME_COMPONENT_GRADIENT.maxViaStops) {
5459
+ return null;
5460
+ }
5461
+ const via = [];
5462
+ for (const item of raw) {
5463
+ const stop = typeof item === "string" ? item.trim() : "";
5464
+ if (!GRADIENT_HEX_RE.test(stop)) return null;
5465
+ via.push(stop);
5466
+ }
5467
+ return via;
5468
+ }
5469
+
5470
+ /**
5471
+ * sc-10685 — a NORMALISED gradient's colours in paint order, evenly spaced:
5472
+ * `[from, ...via, to]`. The one place both hosts read the list from.
5473
+ */
5474
+ function componentGradientColors(gradient) {
5475
+ return [gradient.from, ...(gradient.via || []), gradient.to];
5476
+ }
5477
+
5478
+ /**
5479
+ * sc-10685 — the colour halfway along a NORMALISED gradient, which is what
5480
+ * text laid across it is contrasted against. Two colours: their mix, as before.
5481
+ */
5482
+ function componentGradientMidpoint(gradient) {
5483
+ const colors = componentGradientColors(gradient);
5484
+ const middle = (colors.length - 1) / 2;
5485
+ return mixHex(colors[Math.floor(middle)], colors[Math.ceil(middle)], 0.5);
5436
5486
  }
5437
5487
 
5438
5488
  // REQ-THEME-LOOK: coerce a theme's spacing scale into the usable band. Shared
@@ -5475,6 +5525,8 @@ export {
5475
5525
  INK_CONTRAST,
5476
5526
  gradientAngleToVector,
5477
5527
  normaliseComponentGradient,
5528
+ componentGradientColors,
5529
+ componentGradientMidpoint,
5478
5530
  isThemeColorTokenRef,
5479
5531
  parseThemeColorTokenRef,
5480
5532
  formatThemeColorTokenRef,