@cueplusplus/ui 0.14.1 → 0.15.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 (105) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/dist/agent-runtime/tool-call-card.js +1 -1
  3. package/dist/chat/message.js +1 -1
  4. package/dist/chrome/band.js +10 -4
  5. package/dist/chrome/eyebrow.js +1 -1
  6. package/dist/chrome/footer.js +1 -1
  7. package/dist/chrome/section-header.js +1 -1
  8. package/dist/configurator/configurator.js +1 -1
  9. package/dist/configurator/export-dialog.js +1 -1
  10. package/dist/configurator/panel-sections.js +1 -1
  11. package/dist/configurator/token-editor.js +1 -1
  12. package/dist/date/calendar.js +2 -2
  13. package/dist/elements/context-usage.js +3 -3
  14. package/dist/elements/transcript-rules.js +1 -1
  15. package/dist/forms/field.js +1 -1
  16. package/dist/forms/slider.js +1 -1
  17. package/dist/instruments/code-block.js +1 -1
  18. package/dist/instruments/ledger.js +1 -1
  19. package/dist/instruments/progress.js +1 -1
  20. package/dist/instruments/stat.js +1 -1
  21. package/dist/instruments/table.js +2 -2
  22. package/dist/instruments/terminal-frame.js +1 -1
  23. package/dist/layout/_ground.js +1 -1
  24. package/dist/layout/collapsible.js +1 -1
  25. package/dist/layout/description-list.js +1 -1
  26. package/dist/layout/preview.d.ts +13 -1
  27. package/dist/layout/preview.js +16 -9
  28. package/dist/layout/sidebar.js +1 -1
  29. package/dist/layout/stepper.js +1 -1
  30. package/dist/overlays/_surface.d.ts +1 -1
  31. package/dist/overlays/_surface.js +1 -1
  32. package/dist/overlays/command-palette.js +1 -1
  33. package/dist/overlays/toast.js +1 -1
  34. package/dist/primitives/chip.js +2 -2
  35. package/dist/system/density.d.ts +11 -2
  36. package/dist/system/density.js +29 -9
  37. package/dist/system/portal.js +8 -2
  38. package/dist/system/prepaint.d.ts +18 -1
  39. package/dist/system/prepaint.js +8 -2
  40. package/dist/system/theme-provider.d.ts +17 -2
  41. package/dist/system/theme-provider.js +57 -17
  42. package/dist/system/vocabulary.d.ts +1 -1
  43. package/dist/system/vocabulary.js +14 -2
  44. package/manifest/components/alert-dialog.json +2 -1
  45. package/manifest/components/autocomplete.json +2 -1
  46. package/manifest/components/band.json +1 -1
  47. package/manifest/components/calendar.json +3 -1
  48. package/manifest/components/channel-matrix.json +2 -1
  49. package/manifest/components/chart-tooltip-content.json +2 -1
  50. package/manifest/components/chip.json +1 -0
  51. package/manifest/components/code-block.json +2 -1
  52. package/manifest/components/collapsible.json +2 -1
  53. package/manifest/components/colors-section.json +2 -1
  54. package/manifest/components/combobox.json +2 -1
  55. package/manifest/components/command-palette.json +2 -1
  56. package/manifest/components/context-menu.json +2 -1
  57. package/manifest/components/context-usage.json +1 -0
  58. package/manifest/components/density.json +13 -0
  59. package/manifest/components/description-list.json +2 -1
  60. package/manifest/components/dialog.json +2 -1
  61. package/manifest/components/disclosure.json +2 -1
  62. package/manifest/components/dmx-bar.json +2 -1
  63. package/manifest/components/dmx-strip.json +2 -1
  64. package/manifest/components/drawer.json +2 -1
  65. package/manifest/components/dropdown-menu.json +2 -1
  66. package/manifest/components/export-dialog.json +2 -1
  67. package/manifest/components/eyebrow.json +2 -1
  68. package/manifest/components/field-description.json +2 -1
  69. package/manifest/components/field-error.json +2 -1
  70. package/manifest/components/field-label.json +2 -1
  71. package/manifest/components/field.json +2 -1
  72. package/manifest/components/footer.json +1 -0
  73. package/manifest/components/frames.json +2 -1
  74. package/manifest/components/hover-card.json +2 -1
  75. package/manifest/components/ledger.json +2 -1
  76. package/manifest/components/menubar.json +2 -1
  77. package/manifest/components/message.json +2 -1
  78. package/manifest/components/multi-select.json +2 -1
  79. package/manifest/components/navigation-menu.json +2 -1
  80. package/manifest/components/popover.json +2 -1
  81. package/manifest/components/preset-section.json +2 -1
  82. package/manifest/components/preview.json +15 -1
  83. package/manifest/components/progress.json +2 -1
  84. package/manifest/components/section-header.json +2 -1
  85. package/manifest/components/shape-section.json +2 -1
  86. package/manifest/components/sheet.json +2 -1
  87. package/manifest/components/sidebar.json +2 -1
  88. package/manifest/components/slider.json +2 -1
  89. package/manifest/components/stat.json +1 -0
  90. package/manifest/components/stepper.json +2 -1
  91. package/manifest/components/table-scroll-region.json +2 -1
  92. package/manifest/components/table.json +2 -1
  93. package/manifest/components/tags-input.json +2 -1
  94. package/manifest/components/terminal-frame.json +1 -0
  95. package/manifest/components/theme-configurator.json +2 -1
  96. package/manifest/components/theme-provider.json +13 -0
  97. package/manifest/components/time-boundary.json +2 -1
  98. package/manifest/components/toast.json +2 -0
  99. package/manifest/components/token-editor.json +2 -1
  100. package/manifest/components/tool-call-card.json +2 -1
  101. package/manifest/components/tooltip.json +2 -1
  102. package/manifest/components/unread-divider.json +2 -1
  103. package/manifest/manifest.json +63 -63
  104. package/manifest/tokens.json +29 -2
  105. package/package.json +4 -4
@@ -44,8 +44,8 @@ const chipVariants = cva("inline-flex h-chip select-none items-center justify-ce
44
44
  info: "border-info/35 text-info"
45
45
  },
46
46
  variant: {
47
- tinted: "px-(--cue-space-3) text-(length:--cue-text-label) tracking-[0.15em]",
48
- outline: "bg-transparent px-(--cue-space-3) text-(length:--cue-text-label) tracking-[0.15em]",
47
+ tinted: "px-(--cue-space-3) text-(length:--cue-text-label) tracking-(--cue-tracking-caps)",
48
+ outline: "bg-transparent px-(--cue-space-3) text-(length:--cue-text-label) tracking-(--cue-tracking-caps)",
49
49
  tag: "bg-transparent px-(--cue-space-2) text-(length:--cue-text-micro) tracking-[0.06em]"
50
50
  },
51
51
  interactive: {
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { StyleSet } from "@cueplusplus/tokens";
2
+ import { Paint, StyleSet } from "@cueplusplus/tokens";
3
3
  import { Density as Density$1 } from "@cueplusplus/theme-base";
4
4
  //#region src/system/density.d.ts
5
5
  interface DensityProps {
@@ -8,6 +8,15 @@ interface DensityProps {
8
8
  * `large` or `ultra-large`.
9
9
  */
10
10
  density: Density$1;
11
+ /**
12
+ * A look pack for this subtree.
13
+ *
14
+ * Optional, and **absent means inherit**, exactly as `styleSet` is: an island
15
+ * that names none stamps no `data-paint` and passes the ambient pack straight
16
+ * through. `none` is a *value*, not an absence — it restates the `:root`
17
+ * defaults, so a subtree stamps it to opt out of a pack its parent set.
18
+ */
19
+ paint?: Paint;
11
20
  /**
12
21
  * A style set for this subtree: `console`, `editorial` or `marketing`.
13
22
  *
@@ -68,6 +77,6 @@ interface DensityProps {
68
77
  * <Toolbar />
69
78
  * </Density>
70
79
  */
71
- declare function Density$2({ density, styleSet, children, className, style }: DensityProps): React.JSX.Element;
80
+ declare function Density$2({ density, styleSet, paint, children, className, style }: DensityProps): React.JSX.Element;
72
81
  //#endregion
73
82
  export { Density$2 as Density, DensityProps };
@@ -57,6 +57,21 @@ const AmbientThemeContext = React.createContext(null);
57
57
  */
58
58
  const StyleSetContext = React.createContext(null);
59
59
  /**
60
+ * The look pack stamped around this subtree, or `null` when none is.
61
+ *
62
+ * The fifth axis's counterpart to {@link StyleSetContext}, and internal for the
63
+ * same reasons: a provider publishes this and its `ThemeContext.paint` from one
64
+ * value, a `<Density paint>` island re-scopes it without a `ThemeContext` of its
65
+ * own, and `Preview`'s stage and the portal stamp both leave the DOM they were
66
+ * written in and have to carry the answer with them.
67
+ *
68
+ * `null` is not `"none"`. Absent means *inherit*; `none` is a pack that restates
69
+ * the `:root` defaults, which is what a subtree stamps to opt OUT of an ambient
70
+ * pack. Collapsing the two would make every unpainted subtree override its
71
+ * parent instead of inheriting from it.
72
+ */
73
+ const PaintContext = React.createContext(null);
74
+ /**
60
75
  * Re-scope any subtree to a different density level.
61
76
  *
62
77
  * Renders a plain `<div data-density="…" data-cue-theme="…">`: the geometry
@@ -89,23 +104,28 @@ const StyleSetContext = React.createContext(null);
89
104
  * <Toolbar />
90
105
  * </Density>
91
106
  */
92
- function Density({ density, styleSet, children, className, style }) {
107
+ function Density({ density, styleSet, paint, children, className, style }) {
93
108
  const theme = React.useContext(AmbientThemeContext);
94
109
  const ambientStyleSet = React.useContext(StyleSetContext);
110
+ const ambientPaint = React.useContext(PaintContext);
95
111
  return /* @__PURE__ */ jsx(DensityContext.Provider, {
96
112
  value: density,
97
113
  children: /* @__PURE__ */ jsx(StyleSetContext.Provider, {
98
114
  value: styleSet ?? ambientStyleSet,
99
- children: /* @__PURE__ */ jsx("div", {
100
- "data-density": density,
101
- "data-cue-theme": theme ?? void 0,
102
- "data-style": styleSet ?? void 0,
103
- className,
104
- style,
105
- children
115
+ children: /* @__PURE__ */ jsx(PaintContext.Provider, {
116
+ value: paint ?? ambientPaint,
117
+ children: /* @__PURE__ */ jsx("div", {
118
+ "data-density": density,
119
+ "data-cue-theme": theme ?? void 0,
120
+ "data-style": styleSet ?? void 0,
121
+ "data-paint": paint ?? void 0,
122
+ className,
123
+ style,
124
+ children
125
+ })
106
126
  })
107
127
  })
108
128
  });
109
129
  }
110
130
  //#endregion
111
- export { AmbientThemeContext, DEFAULT_DENSITY, Density, DensityContext, StyleSetContext };
131
+ export { AmbientThemeContext, DEFAULT_DENSITY, Density, DensityContext, PaintContext, StyleSetContext };
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.js";
3
3
  import { AgentSkinContext } from "./agent-skin.js";
4
- import { DensityContext, StyleSetContext } from "./density.js";
4
+ import { DensityContext, PaintContext, StyleSetContext } from "./density.js";
5
5
  import { OverridesContext } from "./overrides.js";
6
6
  import { FontFamiliesContext, FontScaleContext, ThemeContext } from "./theme-provider.js";
7
7
  import * as React from "react";
@@ -31,6 +31,7 @@ function usePortalStamp() {
31
31
  const overrides = React.useContext(OverridesContext);
32
32
  const skin = React.useContext(AgentSkinContext);
33
33
  const styleSet = React.useContext(StyleSetContext);
34
+ const paint = React.useContext(PaintContext);
34
35
  return {
35
36
  theme: theme?.theme ?? DEFAULT_THEME,
36
37
  density: island ?? theme?.density ?? "compact",
@@ -41,7 +42,8 @@ function usePortalStamp() {
41
42
  overrides,
42
43
  skin: skin?.skin ?? null,
43
44
  fidelity: skin?.fidelity ?? null,
44
- styleSet
45
+ styleSet,
46
+ paint
45
47
  };
46
48
  }
47
49
  /**
@@ -97,6 +99,7 @@ function useCuePortalProps() {
97
99
  stamp.density,
98
100
  stamp.font,
99
101
  stamp.styleSet,
102
+ stamp.paint,
100
103
  stamp.mode,
101
104
  stamp.fontScale,
102
105
  stamp.fontFamilies?.sans,
@@ -132,6 +135,8 @@ function applyStamp(element, stamp) {
132
135
  element.setAttribute("data-mode", stamp.mode);
133
136
  if (stamp.styleSet === null) element.removeAttribute("data-style");
134
137
  else element.setAttribute("data-style", stamp.styleSet);
138
+ if (stamp.paint === null) element.removeAttribute("data-paint");
139
+ else element.setAttribute("data-paint", stamp.paint);
135
140
  if (stamp.skin === null) element.removeAttribute("data-cue-skin");
136
141
  else element.setAttribute("data-cue-skin", stamp.skin);
137
142
  if (stamp.fidelity === null) element.removeAttribute("data-cue-fidelity");
@@ -182,6 +187,7 @@ function CuePortalFrame({ children, className, style }) {
182
187
  "data-font": stamp.font,
183
188
  "data-mode": stamp.mode,
184
189
  "data-style": stamp.styleSet ?? void 0,
190
+ "data-paint": stamp.paint ?? void 0,
185
191
  "data-cue-skin": stamp.skin ?? void 0,
186
192
  "data-cue-fidelity": stamp.fidelity ?? void 0,
187
193
  className,
@@ -1,4 +1,4 @@
1
- import { StyleSet } from "@cueplusplus/tokens";
1
+ import { Paint, StyleSet } from "@cueplusplus/tokens";
2
2
  import { Density as Density$1, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
3
3
  //#region src/system/prepaint.d.ts
4
4
  /** localStorage key the ThemeProvider and the pre-paint script share by default. */
@@ -21,6 +21,13 @@ interface PersistedPreferences {
21
21
  * value to hold an opinion about.
22
22
  */
23
23
  style?: string;
24
+ /**
25
+ * Last look pack the user picked, or absent — which is what "no pack" means,
26
+ * for exactly the reason `style` above gives. An application that never
27
+ * touches this axis keeps storing the object it always stored, and the
28
+ * pre-paint script has no sixth value to hold an opinion about.
29
+ */
30
+ paint?: string;
24
31
  }
25
32
  /**
26
33
  * The server-authoritative fallback stamped when storage is absent or ignored.
@@ -52,6 +59,16 @@ interface PrepaintDefaults {
52
59
  * (stored, then this) rather than four.
53
60
  */
54
61
  styleSet?: StyleSet;
62
+ /**
63
+ * The look pack an unstamped page opens on, or omitted for none.
64
+ *
65
+ * The second axis whose absence is a value, and it behaves exactly as
66
+ * `styleSet` does: omitted means the script REMOVES `data-paint` rather than
67
+ * stamping a default, because a page in no pack has made no claim about
68
+ * finish and `[data-paint]` blocks key their whole value-set on the attribute
69
+ * being there.
70
+ */
71
+ paint?: Paint;
55
72
  }
56
73
  /** Options form for apps whose server projection, not localStorage, owns first paint. */
57
74
  interface PrepaintOptions {
@@ -1,5 +1,5 @@
1
1
  import { vocabulary } from "./vocabulary.js";
2
- import { DEFAULT_DENSITY, DEFAULT_FONT, STYLE_SETS } from "@cueplusplus/tokens";
2
+ import { DEFAULT_DENSITY, DEFAULT_FONT, PAINTS, STYLE_SETS } from "@cueplusplus/tokens";
3
3
  import { MODES } from "@cueplusplus/theme-base";
4
4
  //#region src/system/prepaint.ts
5
5
  /**
@@ -114,7 +114,13 @@ function prepaintScript(storageKeyOrOptions = DEFAULT_STORAGE_KEY, defaultTheme
114
114
  */
115
115
  const styleSets = scriptSafe(JSON.stringify([...STYLE_SETS]));
116
116
  const fallbackStyleSet = defaults.styleSet === void 0 ? "null" : jsStringLiteral(defaults.styleSet);
117
- return `!function(){try{var k=${key},e=document.documentElement,s=null;` + (readStoredPreferences ? "try{var r=localStorage.getItem(k);s=r?JSON.parse(r):null}catch(_){}" : "") + `s=s&&typeof s=="object"?s:{};var T=${themes},A=${axes},P=/^[a-z][a-z0-9-]*$/i;var t=T?(T.indexOf(s.theme)>-1?s.theme:${fallbackTheme}):(typeof s.theme=="string"&&P.test(s.theme)?s.theme:${fallbackTheme});var a=A[T?t:""]||A[""]||{d:[],f:[]};var d=a.d.indexOf(s.density)>-1?s.density:(a.d.indexOf(${fallbackDensity})>-1?${fallbackDensity}:(a.p&&a.d.indexOf(a.p)>-1?a.p:${baseDensity}));var f=a.f.indexOf(s.font)>-1?s.font:(a.f.indexOf(${fallbackFont})>-1?${fallbackFont}:(a.q&&a.f.indexOf(a.q)>-1?a.q:${baseFont}));var m=${modes}.indexOf(s.mode)>-1?s.mode:${fallbackMode};var S=${styleSets},y=S.indexOf(s.style)>-1?s.style:${fallbackStyleSet};e.setAttribute("data-theme",t);e.setAttribute("data-density",d);e.setAttribute("data-font",f);e.setAttribute("data-mode",m);y?e.setAttribute("data-style",y):e.removeAttribute("data-style");e.style.colorScheme=a.l===false?"dark":(m==="system"?"light dark":m)}catch(_){}}()`;
117
+ /**
118
+ * The packs, inlined for the same reason as everything above it, and the
119
+ * second axis whose absence is a legal value.
120
+ */
121
+ const paints = scriptSafe(JSON.stringify([...PAINTS]));
122
+ const fallbackPaint = defaults.paint === void 0 ? "null" : jsStringLiteral(defaults.paint);
123
+ return `!function(){try{var k=${key},e=document.documentElement,s=null;` + (readStoredPreferences ? "try{var r=localStorage.getItem(k);s=r?JSON.parse(r):null}catch(_){}" : "") + `s=s&&typeof s=="object"?s:{};var T=${themes},A=${axes},P=/^[a-z][a-z0-9-]*$/i;var t=T?(T.indexOf(s.theme)>-1?s.theme:${fallbackTheme}):(typeof s.theme=="string"&&P.test(s.theme)?s.theme:${fallbackTheme});var a=A[T?t:""]||A[""]||{d:[],f:[]};var d=a.d.indexOf(s.density)>-1?s.density:(a.d.indexOf(${fallbackDensity})>-1?${fallbackDensity}:(a.p&&a.d.indexOf(a.p)>-1?a.p:${baseDensity}));var f=a.f.indexOf(s.font)>-1?s.font:(a.f.indexOf(${fallbackFont})>-1?${fallbackFont}:(a.q&&a.f.indexOf(a.q)>-1?a.q:${baseFont}));var m=${modes}.indexOf(s.mode)>-1?s.mode:${fallbackMode};var S=${styleSets},y=S.indexOf(s.style)>-1?s.style:${fallbackStyleSet};var G=${paints},g=G.indexOf(s.paint)>-1?s.paint:${fallbackPaint};e.setAttribute("data-theme",t);e.setAttribute("data-density",d);e.setAttribute("data-font",f);e.setAttribute("data-mode",m);y?e.setAttribute("data-style",y):e.removeAttribute("data-style");g?e.setAttribute("data-paint",g):e.removeAttribute("data-paint");e.style.colorScheme=a.l===false?"dark":(m==="system"?"light dark":m)}catch(_){}}()`;
118
124
  }
119
125
  //#endregion
120
126
  export { DEFAULT_STORAGE_KEY, prepaintScript };
@@ -1,6 +1,6 @@
1
1
  import { TokenOverrides } from "./overrides.js";
2
2
  import * as React from "react";
3
- import { StyleSet } from "@cueplusplus/tokens";
3
+ import { Paint, StyleSet } from "@cueplusplus/tokens";
4
4
  import { Density as Density$1, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
5
5
  //#region src/system/theme-provider.d.ts
6
6
  /** A mode that has been resolved to an actual palette — `"system"` never survives this far. */
@@ -33,6 +33,14 @@ interface ThemeContextValue {
33
33
  * `StyleSetContext` the portal and stage stamps read are one value.
34
34
  */
35
35
  styleSet: StyleSet | null;
36
+ /**
37
+ * The look pack in force on this subtree, or `null`.
38
+ *
39
+ * *In force*, not *named here*, exactly as `styleSet` is: a provider that
40
+ * names no pack reports the one stamped around it, so this and the
41
+ * `PaintContext` the portal and stage stamps read are one value.
42
+ */
43
+ paint: Paint | null;
36
44
  /** Mode after resolving `"system"` against `prefers-color-scheme`. */
37
45
  resolvedMode: ResolvedMode;
38
46
  /** Switch theme and persist the new preference triple. */
@@ -58,6 +66,8 @@ interface ThemeContextValue {
58
66
  * out-specify an ancestor's in a cascade.
59
67
  */
60
68
  setStyleSet: (next: StyleSet | null) => void;
69
+ /** Switch the look pack and persist it. `null` clears the attribute. */
70
+ setPaint: (next: Paint | null) => void;
61
71
  }
62
72
  /** Provider-owned font-family overrides that detached portals must republish. */
63
73
  interface FontFamilies {
@@ -164,6 +174,11 @@ interface ThemeProviderProps {
164
174
  * holds its own rather than handing the axis up to the root.
165
175
  */
166
176
  styleSet?: StyleSet;
177
+ /**
178
+ * A look pack for this subtree. Opt-in: a provider that names none stamps no
179
+ * `data-paint`, and a nested one inherits the pack stamped around it.
180
+ */
181
+ paint?: Paint;
167
182
  /** Multiplier on every text token, published as `--cue-font-scale`. Defaults to `1`. */
168
183
  fontScale?: number;
169
184
  /** Optional app-owned font stacks, published as inline CUE font custom properties. */
@@ -330,6 +345,6 @@ interface ThemeProviderProps {
330
345
  * <App />
331
346
  * </ThemeProvider>
332
347
  */
333
- declare function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityProp, mode: modeProp, font: fontProp, styleSet: styleSetProp, fontScale, fontFamilies, overrides, storageKey, persistPreferences, nonce, asChild, className, style: styleProp, children }: ThemeProviderProps): React.JSX.Element;
348
+ declare function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityProp, mode: modeProp, font: fontProp, styleSet: styleSetProp, paint: paintProp, fontScale, fontFamilies, overrides, storageKey, persistPreferences, nonce, asChild, className, style: styleProp, children }: ThemeProviderProps): React.JSX.Element;
334
349
  //#endregion
335
350
  export { FontFamilies, ResolvedMode, ThemeContextValue, ThemeProvider, ThemeProviderProps };
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { useIsomorphicInsertionEffect, useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect.js";
4
- import { AmbientThemeContext, DensityContext, StyleSetContext } from "./density.js";
5
- import { buildRegistry, isStyleSet, vocabulary, warnOnce } from "./vocabulary.js";
4
+ import { AmbientThemeContext, DensityContext, PaintContext, StyleSetContext } from "./density.js";
5
+ import { buildRegistry, isPaint, isStyleSet, vocabulary, warnOnce } from "./vocabulary.js";
6
6
  import { OverridesContext, inlineOverrides, overridesCss, reportOverrides } from "./overrides.js";
7
7
  import { DEFAULT_STORAGE_KEY } from "./prepaint.js";
8
8
  import { ThemeRegistryContext } from "./theme-registry.js";
@@ -117,16 +117,21 @@ function acceptedPreferences(vocab, stored, fallback) {
117
117
  density: vocab.acceptsDensity(theme, stored.density) ? stored.density : vocab.acceptsDensity(theme, fallback.density) ? fallback.density : vocab.preferredDensity(theme),
118
118
  font: vocab.acceptsFont(theme, stored.font) ? stored.font : vocab.acceptsFont(theme, fallback.font) ? fallback.font : vocab.preferredFont(theme),
119
119
  mode: isOneOf(MODES, stored.mode) ? stored.mode : fallback.mode,
120
- styleSet: isStyleSet(stored.style) ? stored.style : fallback.styleSet
120
+ styleSet: isStyleSet(stored.style) ? stored.style : fallback.styleSet,
121
+ paint: isPaint(stored.paint) ? stored.paint : fallback.paint
121
122
  };
122
123
  }
123
124
  function writePreferences(storageKey, preferences) {
124
125
  if (typeof localStorage === "undefined") return;
125
- const { styleSet, ...axes } = preferences;
126
- const payload = styleSet === null ? axes : {
126
+ const { styleSet, paint, ...axes } = preferences;
127
+ const withStyle = styleSet === null ? axes : {
127
128
  ...axes,
128
129
  style: styleSet
129
130
  };
131
+ const payload = paint === null ? withStyle : {
132
+ ...withStyle,
133
+ paint
134
+ };
130
135
  try {
131
136
  localStorage.setItem(storageKey, JSON.stringify(payload));
132
137
  } catch {}
@@ -171,10 +176,11 @@ function writePreferences(storageKey, preferences) {
171
176
  * <App />
172
177
  * </ThemeProvider>
173
178
  */
174
- function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityProp, mode: modeProp = DEFAULT_MODE, font: fontProp, styleSet: styleSetProp, fontScale = 1, fontFamilies, overrides, storageKey = DEFAULT_STORAGE_KEY, persistPreferences = true, nonce, asChild = false, className, style: styleProp, children }) {
179
+ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityProp, mode: modeProp = DEFAULT_MODE, font: fontProp, styleSet: styleSetProp, paint: paintProp, fontScale = 1, fontFamilies, overrides, storageKey = DEFAULT_STORAGE_KEY, persistPreferences = true, nonce, asChild = false, className, style: styleProp, children }) {
175
180
  const ambient = React.useContext(ThemeContext);
176
181
  const isRoot = ambient === null;
177
182
  const outerStyleSet = React.useContext(StyleSetContext);
183
+ const outerPaint = React.useContext(PaintContext);
178
184
  const ambientRegistry = React.useContext(ThemeRegistryContext);
179
185
  const themes = themesProp ?? ambientRegistry?.themes ?? EMPTY;
180
186
  const vocab = React.useMemo(() => vocabulary(themes), [themes]);
@@ -200,10 +206,12 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
200
206
  const initialDensity = densityProp ?? vocab.preferredDensity(initialTheme);
201
207
  const initialFont = fontProp ?? vocab.preferredFont(initialTheme);
202
208
  const initialStyleSet = styleSetProp ?? (isRoot ? null : outerStyleSet);
209
+ const initialPaint = paintProp ?? (isRoot ? null : outerPaint);
203
210
  const [theme, setThemeState] = React.useState(initialTheme);
204
211
  const [densityState, setDensityState] = React.useState(initialDensity);
205
212
  const [mode, setModeState] = React.useState(modeProp);
206
213
  const [styleSet, setStyleSetState] = React.useState(initialStyleSet);
214
+ const [paint, setPaintState] = React.useState(initialPaint);
207
215
  const [storedSeed] = React.useState(() => {
208
216
  if (!isRoot || !persistPreferences) return {
209
217
  theme: initialTheme,
@@ -245,14 +253,16 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
245
253
  density,
246
254
  font,
247
255
  mode,
248
- styleSet
256
+ styleSet,
257
+ paint
249
258
  });
250
259
  setThemeState(next.theme);
251
260
  setDensityState(next.density);
252
261
  setRootFontState(next.font);
253
262
  setModeState(next.mode);
254
263
  setStyleSetState(next.styleSet);
255
- if (stored.theme !== void 0 && stored.theme !== next.theme || stored.density !== void 0 && stored.density !== next.density || stored.font !== void 0 && stored.font !== next.font || stored.mode !== void 0 && stored.mode !== next.mode || stored.style !== void 0 && !isStyleSet(stored.style)) writePreferences(storageKey, next);
264
+ setPaintState(next.paint);
265
+ if (stored.theme !== void 0 && stored.theme !== next.theme || stored.density !== void 0 && stored.density !== next.density || stored.font !== void 0 && stored.font !== next.font || stored.mode !== void 0 && stored.mode !== next.mode || stored.style !== void 0 && !isStyleSet(stored.style) || stored.paint !== void 0 && !isPaint(stored.paint)) writePreferences(storageKey, next);
256
266
  }, [
257
267
  isRoot,
258
268
  persistPreferences,
@@ -263,7 +273,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
263
273
  density: densityProp,
264
274
  mode: modeProp,
265
275
  font: fontProp,
266
- styleSet: styleSetProp
276
+ styleSet: styleSetProp,
277
+ paint: paintProp
267
278
  });
268
279
  React.useEffect(() => {
269
280
  const previous = lastProps.current;
@@ -272,19 +283,22 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
272
283
  density: densityProp,
273
284
  mode: modeProp,
274
285
  font: fontProp,
275
- styleSet: styleSetProp
286
+ styleSet: styleSetProp,
287
+ paint: paintProp
276
288
  };
277
289
  if (previous.theme !== themeProp && themeProp !== void 0) setThemeState(themeProp);
278
290
  if (previous.density !== densityProp && densityProp !== void 0) setDensityState(densityProp);
279
291
  if (previous.mode !== modeProp) setModeState(modeProp);
280
292
  if (previous.font !== fontProp && fontProp !== void 0) setRootFontState(fontProp);
281
293
  if (previous.styleSet !== styleSetProp && styleSetProp !== void 0) setStyleSetState(styleSetProp);
294
+ if (previous.paint !== paintProp && paintProp !== void 0) setPaintState(paintProp);
282
295
  }, [
283
296
  themeProp,
284
297
  densityProp,
285
298
  modeProp,
286
299
  fontProp,
287
- styleSetProp
300
+ styleSetProp,
301
+ paintProp
288
302
  ]);
289
303
  useIsomorphicLayoutEffect(() => {
290
304
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
@@ -299,7 +313,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
299
313
  density: densityState,
300
314
  mode,
301
315
  font: rawFont,
302
- styleSet
316
+ styleSet,
317
+ paint
303
318
  });
304
319
  useIsomorphicLayoutEffect(() => {
305
320
  stateRef.current = {
@@ -307,7 +322,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
307
322
  density: densityState,
308
323
  mode,
309
324
  font: rawFont,
310
- styleSet
325
+ styleSet,
326
+ paint
311
327
  };
312
328
  });
313
329
  const persist = React.useCallback((patch) => {
@@ -345,6 +361,10 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
345
361
  setStyleSetState(next);
346
362
  persist({ styleSet: next });
347
363
  }, [persist]);
364
+ const setPaint = React.useCallback((next) => {
365
+ setPaintState(next);
366
+ persist({ paint: next });
367
+ }, [persist]);
348
368
  const originalStamp = React.useRef(null);
349
369
  useIsomorphicInsertionEffect(() => {
350
370
  if (!isRoot || typeof document === "undefined") return;
@@ -354,7 +374,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
354
374
  density,
355
375
  font,
356
376
  mode,
357
- styleSet
377
+ styleSet,
378
+ paint
358
379
  };
359
380
  const next = restored || !persistPreferences ? current : acceptedPreferences(vocab, readPreferences(storageKey), current);
360
381
  originalStamp.current ??= {
@@ -363,6 +384,7 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
363
384
  font: root.getAttribute("data-font"),
364
385
  mode: root.getAttribute("data-mode"),
365
386
  style: root.getAttribute("data-style"),
387
+ paint: root.getAttribute("data-paint"),
366
388
  colorScheme: root.style.colorScheme
367
389
  };
368
390
  const write = (name, value) => {
@@ -375,6 +397,9 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
375
397
  if (next.styleSet === null) {
376
398
  if (root.hasAttribute("data-style")) root.removeAttribute("data-style");
377
399
  } else write("data-style", next.styleSet);
400
+ if (next.paint === null) {
401
+ if (root.hasAttribute("data-paint")) root.removeAttribute("data-paint");
402
+ } else write("data-paint", next.paint);
378
403
  const scheme = (themes.find((m) => m.name === next.theme) ?? null)?.supportsLight === false ? "dark" : next.mode === "system" ? "light dark" : next.mode;
379
404
  if (root.style.colorScheme !== scheme) root.style.colorScheme = scheme;
380
405
  }, [
@@ -404,9 +429,19 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
404
429
  restore("data-font", original.font);
405
430
  restore("data-mode", original.mode);
406
431
  restore("data-style", original.style);
432
+ restore("data-paint", original.paint);
407
433
  root.style.colorScheme = original.colorScheme;
408
434
  }, []);
409
435
  const effectiveStyleSet = styleSet ?? outerStyleSet;
436
+ /**
437
+ * The pack in force, for the same reason and with the same caveat: it is what
438
+ * `useTheme().paint` reports, and it does NOT feed the `data-paint` stamp.
439
+ * That stays on this provider's own `paint` state, so a root that named none
440
+ * stamps nothing on itself and nothing on `<html>` however much is stamped
441
+ * around it — which keeps a `<Density paint>` island out of the document-wide
442
+ * attribute and out of the storage the pre-paint script reads back.
443
+ */
444
+ const effectivePaint = paint ?? outerPaint;
410
445
  const contextValue = React.useMemo(() => ({
411
446
  theme,
412
447
  manifest,
@@ -414,12 +449,14 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
414
449
  mode,
415
450
  font,
416
451
  styleSet: effectiveStyleSet,
452
+ paint: effectivePaint,
417
453
  resolvedMode,
418
454
  setTheme,
419
455
  setDensity,
420
456
  setMode,
421
457
  setFont,
422
- setStyleSet
458
+ setStyleSet,
459
+ setPaint
423
460
  }), [
424
461
  theme,
425
462
  manifest,
@@ -427,12 +464,14 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
427
464
  mode,
428
465
  font,
429
466
  effectiveStyleSet,
467
+ effectivePaint,
430
468
  resolvedMode,
431
469
  setTheme,
432
470
  setDensity,
433
471
  setMode,
434
472
  setFont,
435
- setStyleSet
473
+ setStyleSet,
474
+ setPaint
436
475
  ]);
437
476
  /** The axes on offer under the theme that is actually active. */
438
477
  const registry = React.useMemo(() => buildRegistry(themes, theme), [themes, theme]);
@@ -491,7 +530,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
491
530
  "data-theme": theme,
492
531
  "data-density": density,
493
532
  "data-mode": mode,
494
- ...styleSet === null ? null : { "data-style": styleSet }
533
+ ...styleSet === null ? null : { "data-style": styleSet },
534
+ ...paint === null ? null : { "data-paint": paint }
495
535
  };
496
536
  let root;
497
537
  if (asChild) {
@@ -1,4 +1,4 @@
1
- import { StyleSet } from "@cueplusplus/tokens";
1
+ import { Paint, StyleSet } from "@cueplusplus/tokens";
2
2
  import { Density as Density$1, DensityEntry, FontEntry, FontName, ThemeManifest } from "@cueplusplus/theme-base";
3
3
  //#region src/system/vocabulary.d.ts
4
4
  /** What the three registry hooks read: the manifests, and the axes the ACTIVE theme offers. */
@@ -1,4 +1,4 @@
1
- import { DEFAULT_DENSITY, DEFAULT_FONT, STYLE_SETS } from "@cueplusplus/tokens";
1
+ import { DEFAULT_DENSITY, DEFAULT_FONT, PAINTS, STYLE_SETS } from "@cueplusplus/tokens";
2
2
  import { DENSITIES, FONTS, THEME_NAME_PATTERN, resolveDensities, resolveFonts } from "@cueplusplus/theme-base";
3
3
  //#region src/system/vocabulary.ts
4
4
  /**
@@ -82,6 +82,18 @@ function vocabulary(themes) {
82
82
  function isStyleSet(value) {
83
83
  return typeof value === "string" && STYLE_SETS.includes(value);
84
84
  }
85
+ /**
86
+ * Whether a value is one of the look packs.
87
+ *
88
+ * Standalone for exactly the reason {@link isStyleSet} is, and the reason reads
89
+ * the same one axis over: no theme can add a pack. The vocabulary is emitted by
90
+ * `@cueplusplus/tokens`, no manifest field describes it, so there is nothing
91
+ * per-theme to ask and a per-theme signature would be parameters pretending to
92
+ * be a rule.
93
+ */
94
+ function isPaint(value) {
95
+ return typeof value === "string" && PAINTS.includes(value);
96
+ }
85
97
  /** Derive the context value. Pure; the provider memoises it on `themes` and the active name. */
86
98
  function buildRegistry(themes, active) {
87
99
  return {
@@ -122,4 +134,4 @@ function noteOnce(message) {
122
134
  console.info(`[cue] ${message}`);
123
135
  }
124
136
  //#endregion
125
- export { buildRegistry, isStyleSet, noteOnce, vocabulary, warnOnce };
137
+ export { buildRegistry, isPaint, isStyleSet, noteOnce, vocabulary, warnOnce };
@@ -185,7 +185,8 @@
185
185
  "--cue-surface-2",
186
186
  "--cue-text-emphasis",
187
187
  "--cue-text-label",
188
- "--cue-text-ui"
188
+ "--cue-text-ui",
189
+ "--cue-tracking-caps"
189
190
  ],
190
191
  "summary": "A modal that asks one question with two answers, for anything irreversible.",
191
192
  "examples": [
@@ -232,7 +232,8 @@
232
232
  "--cue-text-body",
233
233
  "--cue-text-emphasis",
234
234
  "--cue-text-label",
235
- "--cue-text-ui"
235
+ "--cue-text-ui",
236
+ "--cue-tracking-caps"
236
237
  ],
237
238
  "summary": "A text field that suggests as you type — and accepts what you typed anyway.",
238
239
  "examples": [
@@ -72,8 +72,8 @@
72
72
  "tokensUsed": [
73
73
  "--cue-band-ground",
74
74
  "--cue-band-rule",
75
+ "--cue-band-rule-color",
75
76
  "--cue-band-y",
76
- "--cue-border",
77
77
  "--cue-measure-prose"
78
78
  ],
79
79
  "summary": "A full-bleed horizontal section: a rule to the window's edge, a column at the measure.",
@@ -35,7 +35,9 @@
35
35
  "--cue-surface-3",
36
36
  "--cue-text-label",
37
37
  "--cue-text-micro",
38
- "--cue-text-ui"
38
+ "--cue-text-ui",
39
+ "--cue-tracking-caps",
40
+ "--cue-tracking-caps-tight"
39
41
  ],
40
42
  "summary": "A month grid, on react-day-picker.",
41
43
  "examples": [
@@ -209,7 +209,8 @@
209
209
  "--cue-text-emphasis",
210
210
  "--cue-text-label",
211
211
  "--cue-text-micro",
212
- "--cue-text-ui"
212
+ "--cue-text-ui",
213
+ "--cue-tracking-caps"
213
214
  ],
214
215
  "summary": "The channel-by-channel view: 512 addressed cells, repainted imperatively.",
215
216
  "examples": [
@@ -110,7 +110,8 @@
110
110
  "--cue-surface-2",
111
111
  "--cue-text-emphasis",
112
112
  "--cue-text-label",
113
- "--cue-text-ui"
113
+ "--cue-text-ui",
114
+ "--cue-tracking-caps"
114
115
  ],
115
116
  "summary": "The chart tooltip surface: an overlay, styled like every other overlay.",
116
117
  "examples": [
@@ -123,6 +123,7 @@
123
123
  "--cue-surface-2",
124
124
  "--cue-text-label",
125
125
  "--cue-text-micro",
126
+ "--cue-tracking-caps",
126
127
  "--cue-warn"
127
128
  ],
128
129
  "summary": "The status pill, at three volumes: tinted fill, bare outline, or a hairline tag.",
@@ -57,7 +57,8 @@
57
57
  "--cue-space-4",
58
58
  "--cue-sunken",
59
59
  "--cue-text-micro",
60
- "--cue-text-ui"
60
+ "--cue-text-ui",
61
+ "--cue-tracking-caps"
61
62
  ],
62
63
  "summary": "A block of code with a copy button and no highlighter palette; `language=\"jsx\"` adds emphasis in three inks the theme already has.",
63
64
  "examples": [
@@ -103,7 +103,8 @@
103
103
  "--cue-space-4",
104
104
  "--cue-text-label",
105
105
  "--cue-text-micro",
106
- "--cue-text-ui"
106
+ "--cue-text-ui",
107
+ "--cue-tracking-caps"
107
108
  ],
108
109
  "summary": "One section that opens and closes — an accordion of exactly one.",
109
110
  "examples": [
@@ -52,7 +52,8 @@
52
52
  "--cue-space-4",
53
53
  "--cue-text-label",
54
54
  "--cue-text-micro",
55
- "--cue-text-ui"
55
+ "--cue-text-ui",
56
+ "--cue-tracking-caps"
56
57
  ],
57
58
  "summary": "Section 2 of the configurator — every colour the active theme declares, one row each.",
58
59
  "examples": [
@@ -235,7 +235,8 @@
235
235
  "--cue-text-body",
236
236
  "--cue-text-emphasis",
237
237
  "--cue-text-label",
238
- "--cue-text-ui"
238
+ "--cue-text-ui",
239
+ "--cue-tracking-caps"
239
240
  ],
240
241
  "summary": "A `Select` you can type into: one value out of a known, filterable set.",
241
242
  "examples": [