@cueplusplus/ui 0.12.0 → 0.14.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 (196) hide show
  1. package/CHANGELOG.md +305 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE.md +34 -0
  4. package/README.md +2 -19
  5. package/dist/agent-runtime/tool-call-card.js +2 -2
  6. package/dist/chat/ask-box.js +2 -2
  7. package/dist/chat/composer.js +1 -1
  8. package/dist/chat/message.js +1 -1
  9. package/dist/chrome/_band.js +54 -0
  10. package/dist/chrome/band.d.ts +106 -0
  11. package/dist/chrome/band.js +97 -0
  12. package/dist/chrome/empty-state.js +1 -1
  13. package/dist/chrome/footer.d.ts +13 -0
  14. package/dist/chrome/footer.js +17 -2
  15. package/dist/chrome/index.d.ts +2 -1
  16. package/dist/chrome/index.js +2 -1
  17. package/dist/chrome/navigation-menu.js +1 -1
  18. package/dist/chrome/page-shell.js +4 -4
  19. package/dist/configurator/export-dialog.js +1 -1
  20. package/dist/configurator/panel-sections.js +1 -1
  21. package/dist/elements/agent-card.js +1 -1
  22. package/dist/elements/agent-handoff.js +2 -2
  23. package/dist/elements/canvas-split.js +2 -2
  24. package/dist/elements/chat-panel.js +1 -1
  25. package/dist/elements/code-diff.js +1 -1
  26. package/dist/elements/code-runner.js +2 -2
  27. package/dist/elements/comparison-card.js +1 -1
  28. package/dist/elements/confidence-marker.js +1 -1
  29. package/dist/elements/conversation-search.js +1 -1
  30. package/dist/elements/day-separator.js +1 -1
  31. package/dist/elements/document-reference.js +1 -1
  32. package/dist/elements/edit-message.js +1 -1
  33. package/dist/elements/elicitation-form.js +1 -1
  34. package/dist/elements/guardrail-notice.js +1 -1
  35. package/dist/elements/image-generation.js +1 -1
  36. package/dist/elements/inline-citation.js +2 -2
  37. package/dist/elements/markdown.js +1 -1
  38. package/dist/elements/math-block.js +1 -1
  39. package/dist/elements/message-branches.js +1 -1
  40. package/dist/elements/message-pair.js +1 -1
  41. package/dist/elements/onboarding.js +2 -2
  42. package/dist/elements/prompt-library.js +1 -1
  43. package/dist/elements/quote-reply.js +2 -2
  44. package/dist/elements/read-aloud.js +1 -1
  45. package/dist/elements/reasoning-panel.js +1 -1
  46. package/dist/elements/recommendation-card.js +1 -1
  47. package/dist/elements/research-report.js +1 -1
  48. package/dist/elements/retrieval-chunks.js +1 -1
  49. package/dist/elements/reviewable-diff.js +1 -1
  50. package/dist/elements/score-breakdown.js +1 -1
  51. package/dist/elements/scroll-anchor.js +1 -1
  52. package/dist/elements/settings-panel.js +1 -1
  53. package/dist/elements/shared-conversation.js +1 -1
  54. package/dist/elements/speaker-identity.js +1 -1
  55. package/dist/elements/stopped-run.js +1 -1
  56. package/dist/elements/streaming-text.js +1 -1
  57. package/dist/elements/timeline.js +1 -1
  58. package/dist/elements/tool-error.js +1 -1
  59. package/dist/elements/voice-conversation.js +1 -1
  60. package/dist/forms/textarea.js +1 -1
  61. package/dist/index.d.ts +5 -4
  62. package/dist/index.js +4 -2
  63. package/dist/instruments/code-block.js +1 -1
  64. package/dist/instruments/progress.d.ts +19 -3
  65. package/dist/instruments/progress.js +19 -1
  66. package/dist/instruments/terminal-frame.js +1 -1
  67. package/dist/layout/_collapse.js +1 -1
  68. package/dist/layout/card.js +1 -1
  69. package/dist/layout/container.js +3 -3
  70. package/dist/layout/frames.d.ts +19 -1
  71. package/dist/layout/frames.js +21 -7
  72. package/dist/layout/index.d.ts +2 -2
  73. package/dist/layout/preview.d.ts +29 -9
  74. package/dist/layout/preview.js +38 -17
  75. package/dist/overlays/_surface.d.ts +1 -1
  76. package/dist/overlays/_surface.js +1 -1
  77. package/dist/overlays/toast.js +1 -1
  78. package/dist/overlays/tooltip.js +1 -1
  79. package/dist/primitives/chip.d.ts +1 -1
  80. package/dist/system/density.d.ts +34 -6
  81. package/dist/system/density.js +41 -10
  82. package/dist/system/index.d.ts +2 -1
  83. package/dist/system/index.js +2 -1
  84. package/dist/system/portal.d.ts +8 -7
  85. package/dist/system/portal.js +16 -9
  86. package/dist/system/prepaint.d.ts +25 -5
  87. package/dist/system/prepaint.js +10 -2
  88. package/dist/system/theme-provider.d.ts +86 -7
  89. package/dist/system/theme-provider.js +62 -20
  90. package/dist/system/use-theme.d.ts +9 -2
  91. package/dist/system/use-theme.js +9 -2
  92. package/dist/system/vocabulary.d.ts +2 -1
  93. package/dist/system/vocabulary.js +16 -2
  94. package/manifest/components/accordion.json +1 -0
  95. package/manifest/components/agent-card.json +1 -0
  96. package/manifest/components/agent-handoff.json +1 -0
  97. package/manifest/components/alert-dialog.json +1 -0
  98. package/manifest/components/ask-box.json +1 -0
  99. package/manifest/components/autocomplete.json +1 -0
  100. package/manifest/components/band.json +104 -0
  101. package/manifest/components/canvas-split-body.json +1 -0
  102. package/manifest/components/canvas-split-document.json +1 -0
  103. package/manifest/components/canvas-split-header.json +1 -0
  104. package/manifest/components/canvas-split-line.json +1 -0
  105. package/manifest/components/canvas-split-message.json +1 -0
  106. package/manifest/components/canvas-split-thread.json +1 -0
  107. package/manifest/components/canvas-split.json +1 -0
  108. package/manifest/components/card.json +1 -0
  109. package/manifest/components/channel-matrix.json +1 -0
  110. package/manifest/components/chart-tooltip-content.json +1 -0
  111. package/manifest/components/chat-panel-assistant-message.json +1 -0
  112. package/manifest/components/chat-panel-composer.json +1 -0
  113. package/manifest/components/chat-panel-messages.json +1 -0
  114. package/manifest/components/chat-panel-typing.json +1 -0
  115. package/manifest/components/chat-panel-user-message.json +1 -0
  116. package/manifest/components/chat-panel.json +1 -0
  117. package/manifest/components/code-block.json +1 -0
  118. package/manifest/components/code-diff.json +1 -0
  119. package/manifest/components/code-runner.json +1 -0
  120. package/manifest/components/collapsible.json +1 -0
  121. package/manifest/components/colors-section.json +1 -0
  122. package/manifest/components/combobox.json +1 -0
  123. package/manifest/components/command-palette.json +1 -0
  124. package/manifest/components/comparison-card.json +1 -0
  125. package/manifest/components/composer.json +1 -0
  126. package/manifest/components/confidence-marker.json +1 -0
  127. package/manifest/components/container.json +3 -0
  128. package/manifest/components/context-menu.json +1 -0
  129. package/manifest/components/conversation-search.json +1 -0
  130. package/manifest/components/cue-portal-frame.json +1 -1
  131. package/manifest/components/day-separator.json +1 -0
  132. package/manifest/components/density.json +16 -1
  133. package/manifest/components/dialog.json +1 -0
  134. package/manifest/components/disclosure.json +1 -0
  135. package/manifest/components/dmx-bar.json +1 -0
  136. package/manifest/components/dmx-strip.json +1 -0
  137. package/manifest/components/document-reference.json +1 -0
  138. package/manifest/components/drawer.json +1 -0
  139. package/manifest/components/dropdown-menu.json +1 -0
  140. package/manifest/components/edit-message.json +1 -0
  141. package/manifest/components/elements-timeline.json +1 -0
  142. package/manifest/components/elicitation-form.json +1 -0
  143. package/manifest/components/empty-state.json +1 -0
  144. package/manifest/components/export-dialog.json +1 -0
  145. package/manifest/components/footer.json +3 -2
  146. package/manifest/components/frac.json +1 -0
  147. package/manifest/components/frames.json +14 -0
  148. package/manifest/components/guardrail-notice.json +1 -0
  149. package/manifest/components/hover-card.json +1 -0
  150. package/manifest/components/inline-citation.json +1 -0
  151. package/manifest/components/markdown-text.json +1 -0
  152. package/manifest/components/math-block.json +1 -0
  153. package/manifest/components/menubar.json +1 -0
  154. package/manifest/components/message-branches.json +1 -0
  155. package/manifest/components/message-pair.json +1 -0
  156. package/manifest/components/message.json +1 -0
  157. package/manifest/components/multi-select.json +1 -0
  158. package/manifest/components/navigation-menu.json +1 -0
  159. package/manifest/components/onboarding.json +1 -0
  160. package/manifest/components/page-shell.json +4 -0
  161. package/manifest/components/popover.json +1 -0
  162. package/manifest/components/preset-section.json +1 -0
  163. package/manifest/components/preview.json +17 -2
  164. package/manifest/components/progress.json +2 -2
  165. package/manifest/components/prompt-library.json +1 -0
  166. package/manifest/components/quote-reply.json +1 -0
  167. package/manifest/components/read-aloud.json +1 -0
  168. package/manifest/components/reasoning-panel.json +1 -0
  169. package/manifest/components/recommendation-card.json +1 -0
  170. package/manifest/components/research-report.json +1 -0
  171. package/manifest/components/retrieval-chunks.json +1 -0
  172. package/manifest/components/reviewable-diff.json +1 -0
  173. package/manifest/components/score-breakdown.json +1 -0
  174. package/manifest/components/scroll-anchor.json +1 -0
  175. package/manifest/components/settings-panel.json +1 -0
  176. package/manifest/components/shape-section.json +1 -0
  177. package/manifest/components/shared-conversation.json +1 -0
  178. package/manifest/components/sheet.json +1 -0
  179. package/manifest/components/speaker-identity.json +1 -0
  180. package/manifest/components/stopped-run.json +1 -0
  181. package/manifest/components/streaming-text.json +1 -0
  182. package/manifest/components/sub.json +1 -0
  183. package/manifest/components/sup.json +1 -0
  184. package/manifest/components/tags-input.json +1 -0
  185. package/manifest/components/terminal-frame.json +1 -0
  186. package/manifest/components/textarea.json +1 -0
  187. package/manifest/components/theme-configurator.json +1 -0
  188. package/manifest/components/theme-provider.json +26 -1
  189. package/manifest/components/toast.json +1 -0
  190. package/manifest/components/tool-call-card.json +1 -0
  191. package/manifest/components/tool-error.json +1 -0
  192. package/manifest/components/tooltip.json +1 -0
  193. package/manifest/components/voice-conversation.json +1 -0
  194. package/manifest/manifest.json +133 -105
  195. package/manifest/tokens.json +20 -1
  196. package/package.json +11 -9
@@ -1,6 +1,7 @@
1
1
  import { TokenOverrides } from "./overrides.js";
2
2
  import * as React from "react";
3
- import { Density, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
3
+ import { StyleSet } from "@cueplusplus/tokens";
4
+ import { Density as Density$1, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
4
5
  //#region src/system/theme-provider.d.ts
5
6
  /** A mode that has been resolved to an actual palette — `"system"` never survives this far. */
6
7
  type ResolvedMode = "dark" | "light";
@@ -15,21 +16,48 @@ interface ThemeContextValue {
15
16
  */
16
17
  manifest: ThemeManifest | null;
17
18
  /** Density the *provider* holds — a nested `<Density>` island does not change it. */
18
- density: Density;
19
+ density: Density$1;
19
20
  /** Mode as chosen, including the literal `"system"`. */
20
21
  mode: Mode;
21
22
  /** Active font pairing. */
22
23
  font: FontName;
24
+ /**
25
+ * The style set in force, or `null` when nothing has chosen one — which is
26
+ * every application that has not asked for page composition. `null` rather
27
+ * than the default set's name, because the two are different facts: no
28
+ * attribute is stamped, and `console` is the set a subtree stamps to say it is
29
+ * the ordinary thing inside a page that is not.
30
+ *
31
+ * *In force*, not *named here*: a provider that names no set reports the one
32
+ * stamped around it, exactly as the DOM resolves it, so this and the
33
+ * `StyleSetContext` the portal and stage stamps read are one value.
34
+ */
35
+ styleSet: StyleSet | null;
23
36
  /** Mode after resolving `"system"` against `prefers-color-scheme`. */
24
37
  resolvedMode: ResolvedMode;
25
38
  /** Switch theme and persist the new preference triple. */
26
39
  setTheme: (theme: ThemeName) => void;
27
40
  /** Switch the provider-level density and persist the new preference triple. */
28
- setDensity: (density: Density) => void;
41
+ setDensity: (density: Density$1) => void;
29
42
  /** Switch mode (`"system"` included) and persist the new preference triple. */
30
43
  setMode: (mode: Mode) => void;
31
44
  /** Switch the font pairing and persist the new preference. */
32
45
  setFont: (font: FontName) => void;
46
+ /**
47
+ * Switch the style set this provider names, and persist the preference.
48
+ *
49
+ * `null` means *this provider names none* — not *no set applies*. The
50
+ * provider stops stamping `data-style`, so a nested one under an outer
51
+ * `editorial` page goes back to inheriting `editorial`, and `styleSet`
52
+ * above reports it, because that is what the DOM resolves to. At the root,
53
+ * where nothing is stamped around it, `null` and "no set" coincide.
54
+ *
55
+ * To positively leave an inherited set rather than stop naming one, pass
56
+ * {@link DEFAULT_STYLE_SET}: it stamps an attribute whose block restates the
57
+ * `:root` defaults token for token, which is the only thing that can
58
+ * out-specify an ancestor's in a cascade.
59
+ */
60
+ setStyleSet: (next: StyleSet | null) => void;
33
61
  }
34
62
  /** Provider-owned font-family overrides that detached portals must republish. */
35
63
  interface FontFamilies {
@@ -89,7 +117,7 @@ interface ThemeProviderProps {
89
117
  * Changing it after mount adopts the new value; dropping it does not — a
90
118
  * parent that stops naming a rung is not asking to move back to the theme's.
91
119
  */
92
- density?: Density;
120
+ density?: Density$1;
93
121
  /** Initial mode. Defaults to `"dark"`; `"system"` tracks `prefers-color-scheme`. */
94
122
  mode?: Mode;
95
123
  /**
@@ -112,6 +140,30 @@ interface ThemeProviderProps {
112
140
  * needs nothing from this library but `data-font` on a `<div>`.
113
141
  */
114
142
  font?: FontName;
143
+ /**
144
+ * Page composition for this subtree: `console`, `editorial` or `marketing`.
145
+ *
146
+ * **`styleSet`, not `style`.** This interface already has a
147
+ * `style?: React.CSSProperties` a few lines below, applied to the provider
148
+ * root, and a second meaning for that name would be the one collision every
149
+ * consumer of this component would have to think about. The DOM attribute is
150
+ * `data-style`, matching the four public axes — `data-cue-*` is this tree's
151
+ * prefix for *internal* attributes (`data-cue-theme`, `data-cue-fidelity`), and
152
+ * a public axis wearing the internal prefix would be the more confusing of the
153
+ * two mistakes.
154
+ *
155
+ * Unset, **no attribute is stamped at all**, and that is the promise: an
156
+ * application that has never heard of this axis renders the markup it rendered
157
+ * before it existed and stores the preference object it stored before it
158
+ * existed. A style set is opt-in page composition, not a fourth thing every
159
+ * page has.
160
+ *
161
+ * Like the other three axes this is an *initial* value the provider then holds
162
+ * as state, so `setStyleSet` can drive it; a changed prop is adopted after
163
+ * mount. Unlike the font pairing it has an island form, so a nested provider
164
+ * holds its own rather than handing the axis up to the root.
165
+ */
166
+ styleSet?: StyleSet;
115
167
  /** Multiplier on every text token, published as `--cue-font-scale`. Defaults to `1`. */
116
168
  fontScale?: number;
117
169
  /** Optional app-owned font stacks, published as inline CUE font custom properties. */
@@ -184,6 +236,29 @@ interface ThemeProviderProps {
184
236
  storageKey?: string;
185
237
  /** Restore and write root preferences in localStorage. Defaults to `true`. */
186
238
  persistPreferences?: boolean;
239
+ /**
240
+ * The Content-Security-Policy nonce for the density override layer.
241
+ *
242
+ * When `overrides.densities` is set this provider emits one `<style>`
243
+ * element (see {@link ThemeProviderProps.overrides}). Under a policy of
244
+ * `style-src-elem 'self' 'nonce-<per-request>'` the browser refuses that
245
+ * element on every page load, because an element React renders on the client
246
+ * cannot pick up a server nonce by itself. Pass the request's nonce here and
247
+ * it is set on the element.
248
+ *
249
+ * Nothing else in this library emits a `<style>` element, and
250
+ * `prepaintScript()` returns a *string* the application renders in a
251
+ * `<script>` of its own — so the application already controls that element's
252
+ * nonce, and this prop is the whole of what a strict policy needs from here.
253
+ *
254
+ * **A nonce cannot rescue a style ATTRIBUTE**, and this provider always sets
255
+ * one on its root: `color-scheme` and `--cue-font-scale`, plus the font
256
+ * families and overrides where they are given. CSP has no nonce form for
257
+ * `style-src-attr` — it is `'unsafe-inline'` or `'none'` — so an application
258
+ * serving `style-src-attr 'none'` loses those declarations, silently. That is
259
+ * a policy decision rather than something this prop can change.
260
+ */
261
+ nonce?: string;
187
262
  /** Stamp the single child element instead of rendering a wrapper `<div>`. */
188
263
  asChild?: boolean;
189
264
  /** Extra classes for the provider root. */
@@ -200,7 +275,8 @@ interface ThemeProviderProps {
200
275
  * registry the three axis hooks read.
201
276
  *
202
277
  * Rendering: `<div data-cue-root data-theme data-density data-mode style={{ colorScheme, --cue-font-scale }}>`
203
- * (or the single child when `asChild`). The `theme`/`density`/`font`/`mode` props are
278
+ * (or the single child when `asChild`), plus `data-style` when — and only when —
279
+ * a style set is in force. The `theme`/`density`/`font`/`mode`/`styleSet` props are
204
280
  * *initial* values — the provider holds the state so `setTheme` and friends can
205
281
  * drive it — but a changed prop is adopted after mount, so a controlling parent
206
282
  * still works.
@@ -211,7 +287,10 @@ interface ThemeProviderProps {
211
287
  * below.
212
288
  *
213
289
  * Persistence and the pre-paint contract: setters write
214
- * `{ theme, density, font, mode }` to `localStorage[storageKey]`, which is exactly what
290
+ * `{ theme, density, font, mode }` to `localStorage[storageKey]`, plus a `style`
291
+ * key holding the style set when there is one — omitted entirely when there is
292
+ * not, so an application that never touches that axis writes the same four-key
293
+ * object it always wrote. That is exactly what
215
294
  * {@link prepaintScript} reads to stamp `<html>` before the first paint. The
216
295
  * outermost provider keeps `<html>` in sync while the app runs (and restores the
217
296
  * previous stamp on unmount) so the page ground, UA scrollbars and form controls
@@ -229,6 +308,6 @@ interface ThemeProviderProps {
229
308
  * <App />
230
309
  * </ThemeProvider>
231
310
  */
232
- declare function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityProp, mode: modeProp, font: fontProp, fontScale, fontFamilies, overrides, storageKey, persistPreferences, asChild, className, style: styleProp, children }: ThemeProviderProps): React.JSX.Element;
311
+ 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;
233
312
  //#endregion
234
313
  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 } from "./density.js";
5
- import { buildRegistry, vocabulary, warnOnce } from "./vocabulary.js";
4
+ import { AmbientThemeContext, DensityContext, StyleSetContext } from "./density.js";
5
+ import { buildRegistry, 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";
@@ -115,13 +115,19 @@ function acceptedPreferences(vocab, stored, fallback) {
115
115
  theme,
116
116
  density: vocab.acceptsDensity(theme, stored.density) ? stored.density : vocab.acceptsDensity(theme, fallback.density) ? fallback.density : vocab.preferredDensity(theme),
117
117
  font: vocab.acceptsFont(theme, stored.font) ? stored.font : vocab.acceptsFont(theme, fallback.font) ? fallback.font : vocab.preferredFont(theme),
118
- mode: isOneOf(MODES, stored.mode) ? stored.mode : fallback.mode
118
+ mode: isOneOf(MODES, stored.mode) ? stored.mode : fallback.mode,
119
+ styleSet: isStyleSet(stored.style) ? stored.style : fallback.styleSet
119
120
  };
120
121
  }
121
122
  function writePreferences(storageKey, preferences) {
122
123
  if (typeof localStorage === "undefined") return;
124
+ const { styleSet, ...axes } = preferences;
125
+ const payload = styleSet === null ? axes : {
126
+ ...axes,
127
+ style: styleSet
128
+ };
123
129
  try {
124
- localStorage.setItem(storageKey, JSON.stringify(preferences));
130
+ localStorage.setItem(storageKey, JSON.stringify(payload));
125
131
  } catch {}
126
132
  }
127
133
  /**
@@ -131,7 +137,8 @@ function writePreferences(storageKey, preferences) {
131
137
  * registry the three axis hooks read.
132
138
  *
133
139
  * Rendering: `<div data-cue-root data-theme data-density data-mode style={{ colorScheme, --cue-font-scale }}>`
134
- * (or the single child when `asChild`). The `theme`/`density`/`font`/`mode` props are
140
+ * (or the single child when `asChild`), plus `data-style` when — and only when —
141
+ * a style set is in force. The `theme`/`density`/`font`/`mode`/`styleSet` props are
135
142
  * *initial* values — the provider holds the state so `setTheme` and friends can
136
143
  * drive it — but a changed prop is adopted after mount, so a controlling parent
137
144
  * still works.
@@ -142,7 +149,10 @@ function writePreferences(storageKey, preferences) {
142
149
  * below.
143
150
  *
144
151
  * Persistence and the pre-paint contract: setters write
145
- * `{ theme, density, font, mode }` to `localStorage[storageKey]`, which is exactly what
152
+ * `{ theme, density, font, mode }` to `localStorage[storageKey]`, plus a `style`
153
+ * key holding the style set when there is one — omitted entirely when there is
154
+ * not, so an application that never touches that axis writes the same four-key
155
+ * object it always wrote. That is exactly what
146
156
  * {@link prepaintScript} reads to stamp `<html>` before the first paint. The
147
157
  * outermost provider keeps `<html>` in sync while the app runs (and restores the
148
158
  * previous stamp on unmount) so the page ground, UA scrollbars and form controls
@@ -160,9 +170,10 @@ function writePreferences(storageKey, preferences) {
160
170
  * <App />
161
171
  * </ThemeProvider>
162
172
  */
163
- function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityProp, mode: modeProp = DEFAULT_MODE, font: fontProp, fontScale = 1, fontFamilies, overrides, storageKey = DEFAULT_STORAGE_KEY, persistPreferences = true, asChild = false, className, style: styleProp, children }) {
173
+ 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 }) {
164
174
  const ambient = React.useContext(ThemeContext);
165
175
  const isRoot = ambient === null;
176
+ const outerStyleSet = React.useContext(StyleSetContext);
166
177
  const ambientRegistry = React.useContext(ThemeRegistryContext);
167
178
  const themes = themesProp ?? ambientRegistry?.themes ?? EMPTY;
168
179
  const vocab = React.useMemo(() => vocabulary(themes), [themes]);
@@ -187,9 +198,11 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
187
198
  const initialTheme = themeProp ?? (isRoot ? themes[0]?.name : ambient?.theme) ?? DEFAULT_THEME;
188
199
  const initialDensity = densityProp ?? vocab.preferredDensity(initialTheme);
189
200
  const initialFont = fontProp ?? vocab.preferredFont(initialTheme);
201
+ const initialStyleSet = styleSetProp ?? (isRoot ? null : outerStyleSet);
190
202
  const [theme, setThemeState] = React.useState(initialTheme);
191
203
  const [densityState, setDensityState] = React.useState(initialDensity);
192
204
  const [mode, setModeState] = React.useState(modeProp);
205
+ const [styleSet, setStyleSetState] = React.useState(initialStyleSet);
193
206
  const [storedSeed] = React.useState(() => {
194
207
  if (!isRoot || !persistPreferences) return {
195
208
  theme: initialTheme,
@@ -230,13 +243,15 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
230
243
  theme,
231
244
  density,
232
245
  font,
233
- mode
246
+ mode,
247
+ styleSet
234
248
  });
235
249
  setThemeState(next.theme);
236
250
  setDensityState(next.density);
237
251
  setRootFontState(next.font);
238
252
  setModeState(next.mode);
239
- 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) writePreferences(storageKey, next);
253
+ setStyleSetState(next.styleSet);
254
+ 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);
240
255
  }, [
241
256
  isRoot,
242
257
  persistPreferences,
@@ -246,7 +261,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
246
261
  theme: themeProp,
247
262
  density: densityProp,
248
263
  mode: modeProp,
249
- font: fontProp
264
+ font: fontProp,
265
+ styleSet: styleSetProp
250
266
  });
251
267
  React.useEffect(() => {
252
268
  const previous = lastProps.current;
@@ -254,17 +270,20 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
254
270
  theme: themeProp,
255
271
  density: densityProp,
256
272
  mode: modeProp,
257
- font: fontProp
273
+ font: fontProp,
274
+ styleSet: styleSetProp
258
275
  };
259
276
  if (previous.theme !== themeProp && themeProp !== void 0) setThemeState(themeProp);
260
277
  if (previous.density !== densityProp && densityProp !== void 0) setDensityState(densityProp);
261
278
  if (previous.mode !== modeProp) setModeState(modeProp);
262
279
  if (previous.font !== fontProp && fontProp !== void 0) setRootFontState(fontProp);
280
+ if (previous.styleSet !== styleSetProp && styleSetProp !== void 0) setStyleSetState(styleSetProp);
263
281
  }, [
264
282
  themeProp,
265
283
  densityProp,
266
284
  modeProp,
267
- fontProp
285
+ fontProp,
286
+ styleSetProp
268
287
  ]);
269
288
  useIsomorphicLayoutEffect(() => {
270
289
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
@@ -278,14 +297,16 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
278
297
  theme,
279
298
  density: densityState,
280
299
  mode,
281
- font: rawFont
300
+ font: rawFont,
301
+ styleSet
282
302
  });
283
303
  useIsomorphicLayoutEffect(() => {
284
304
  stateRef.current = {
285
305
  theme,
286
306
  density: densityState,
287
307
  mode,
288
- font: rawFont
308
+ font: rawFont,
309
+ styleSet
289
310
  };
290
311
  });
291
312
  const persist = React.useCallback((patch) => {
@@ -319,6 +340,10 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
319
340
  }, [persist]);
320
341
  /** Nested providers hand the axis straight up: one document, one pairing. */
321
342
  const setFont = isRoot ? rootSetFont : ambient.setFont;
343
+ const setStyleSet = React.useCallback((next) => {
344
+ setStyleSetState(next);
345
+ persist({ styleSet: next });
346
+ }, [persist]);
322
347
  const originalStamp = React.useRef(null);
323
348
  useIsomorphicInsertionEffect(() => {
324
349
  if (!isRoot || typeof document === "undefined") return;
@@ -327,7 +352,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
327
352
  theme,
328
353
  density,
329
354
  font,
330
- mode
355
+ mode,
356
+ styleSet
331
357
  };
332
358
  const next = restored || !persistPreferences ? current : acceptedPreferences(vocab, readPreferences(storageKey), current);
333
359
  originalStamp.current ??= {
@@ -335,6 +361,7 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
335
361
  density: root.getAttribute("data-density"),
336
362
  font: root.getAttribute("data-font"),
337
363
  mode: root.getAttribute("data-mode"),
364
+ style: root.getAttribute("data-style"),
338
365
  colorScheme: root.style.colorScheme
339
366
  };
340
367
  const write = (name, value) => {
@@ -344,6 +371,9 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
344
371
  write("data-density", next.density);
345
372
  write("data-font", next.font);
346
373
  write("data-mode", next.mode);
374
+ if (next.styleSet === null) {
375
+ if (root.hasAttribute("data-style")) root.removeAttribute("data-style");
376
+ } else write("data-style", next.styleSet);
347
377
  const scheme = (themes.find((m) => m.name === next.theme) ?? null)?.supportsLight === false ? "dark" : next.mode === "system" ? "light dark" : next.mode;
348
378
  if (root.style.colorScheme !== scheme) root.style.colorScheme = scheme;
349
379
  }, [
@@ -355,6 +385,7 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
355
385
  density,
356
386
  font,
357
387
  mode,
388
+ styleSet,
358
389
  themes,
359
390
  vocab
360
391
  ]);
@@ -371,30 +402,36 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
371
402
  restore("data-density", original.density);
372
403
  restore("data-font", original.font);
373
404
  restore("data-mode", original.mode);
405
+ restore("data-style", original.style);
374
406
  root.style.colorScheme = original.colorScheme;
375
407
  }, []);
408
+ const effectiveStyleSet = styleSet ?? outerStyleSet;
376
409
  const contextValue = React.useMemo(() => ({
377
410
  theme,
378
411
  manifest,
379
412
  density,
380
413
  mode,
381
414
  font,
415
+ styleSet: effectiveStyleSet,
382
416
  resolvedMode,
383
417
  setTheme,
384
418
  setDensity,
385
419
  setMode,
386
- setFont
420
+ setFont,
421
+ setStyleSet
387
422
  }), [
388
423
  theme,
389
424
  manifest,
390
425
  density,
391
426
  mode,
392
427
  font,
428
+ effectiveStyleSet,
393
429
  resolvedMode,
394
430
  setTheme,
395
431
  setDensity,
396
432
  setMode,
397
- setFont
433
+ setFont,
434
+ setStyleSet
398
435
  ]);
399
436
  /** The axes on offer under the theme that is actually active. */
400
437
  const registry = React.useMemo(() => buildRegistry(themes, theme), [themes, theme]);
@@ -452,7 +489,8 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
452
489
  "data-cue-root": "",
453
490
  "data-theme": theme,
454
491
  "data-density": density,
455
- "data-mode": mode
492
+ "data-mode": mode,
493
+ ...styleSet === null ? null : { "data-style": styleSet }
456
494
  };
457
495
  let root;
458
496
  if (asChild) {
@@ -472,6 +510,7 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
472
510
  children
473
511
  });
474
512
  const densityLayer = overridesStyle === "" ? null : /* @__PURE__ */ jsx("style", {
513
+ ...nonce === void 0 ? null : { nonce },
475
514
  "data-cue-overrides": "",
476
515
  children: overridesStyle
477
516
  });
@@ -487,9 +526,12 @@ function ThemeProvider({ themes: themesProp, theme: themeProp, density: densityP
487
526
  value: effectiveFontFamilies,
488
527
  children: /* @__PURE__ */ jsx(OverridesContext.Provider, {
489
528
  value: effectiveOverrides,
490
- children: /* @__PURE__ */ jsxs(DensityContext.Provider, {
529
+ children: /* @__PURE__ */ jsx(DensityContext.Provider, {
491
530
  value: density,
492
- children: [root, densityLayer]
531
+ children: /* @__PURE__ */ jsxs(StyleSetContext.Provider, {
532
+ value: effectiveStyleSet,
533
+ children: [root, densityLayer]
534
+ })
493
535
  })
494
536
  })
495
537
  })
@@ -3,8 +3,8 @@ import { ThemeContextValue } from "./theme-provider.js";
3
3
  /**
4
4
  * Read and drive the ambient theme state.
5
5
  *
6
- * Returns `{ theme, manifest, density, mode, font, resolvedMode, setTheme,
7
- * setDensity, setMode, setFont }`.
6
+ * Returns `{ theme, manifest, density, mode, font, styleSet, resolvedMode,
7
+ * setTheme, setDensity, setMode, setFont, setStyleSet }`.
8
8
  * `density` here is the *provider's* level — a nested `<Density>` island does not
9
9
  * change it; use `useDensity()` when you want the level that actually applies
10
10
  * where you are. `font` is the pairing the provider holds; it has no island, so
@@ -12,6 +12,13 @@ import { ThemeContextValue } from "./theme-provider.js";
12
12
  * when that name is not in the provider's `themes` — which is every name in an
13
13
  * app that has registered none; `useThemes()` is what was registered.
14
14
  *
15
+ * `styleSet` is the page-composition set in force, or `null` when nothing has
16
+ * chosen one — which is every application that has not asked for it. *In
17
+ * force*, not *named here*: a provider that names no set of its own reports the
18
+ * one stamped around it — a `<Density styleSet>` island between it and an outer
19
+ * provider included — exactly as the DOM resolves it, so this and the set a
20
+ * portal or a `<Preview>` stage carries are one value.
21
+ *
15
22
  * @throws If called outside a `<ThemeProvider>`. Theme state is never guessed:
16
23
  * a missing provider is a wiring bug, not a fallback situation.
17
24
  * @example
@@ -5,8 +5,8 @@ import * as React from "react";
5
5
  /**
6
6
  * Read and drive the ambient theme state.
7
7
  *
8
- * Returns `{ theme, manifest, density, mode, font, resolvedMode, setTheme,
9
- * setDensity, setMode, setFont }`.
8
+ * Returns `{ theme, manifest, density, mode, font, styleSet, resolvedMode,
9
+ * setTheme, setDensity, setMode, setFont, setStyleSet }`.
10
10
  * `density` here is the *provider's* level — a nested `<Density>` island does not
11
11
  * change it; use `useDensity()` when you want the level that actually applies
12
12
  * where you are. `font` is the pairing the provider holds; it has no island, so
@@ -14,6 +14,13 @@ import * as React from "react";
14
14
  * when that name is not in the provider's `themes` — which is every name in an
15
15
  * app that has registered none; `useThemes()` is what was registered.
16
16
  *
17
+ * `styleSet` is the page-composition set in force, or `null` when nothing has
18
+ * chosen one — which is every application that has not asked for it. *In
19
+ * force*, not *named here*: a provider that names no set of its own reports the
20
+ * one stamped around it — a `<Density styleSet>` island between it and an outer
21
+ * provider included — exactly as the DOM resolves it, so this and the set a
22
+ * portal or a `<Preview>` stage carries are one value.
23
+ *
17
24
  * @throws If called outside a `<ThemeProvider>`. Theme state is never guessed:
18
25
  * a missing provider is a wiring bug, not a fallback situation.
19
26
  * @example
@@ -1,4 +1,5 @@
1
- import { Density, DensityEntry, FontEntry, FontName, ThemeManifest } from "@cueplusplus/theme-base";
1
+ import { StyleSet } from "@cueplusplus/tokens";
2
+ import { Density as Density$1, DensityEntry, FontEntry, FontName, ThemeManifest } from "@cueplusplus/theme-base";
2
3
  //#region src/system/vocabulary.d.ts
3
4
  /** What the three registry hooks read: the manifests, and the axes the ACTIVE theme offers. */
4
5
  interface ThemeRegistry {
@@ -1,4 +1,4 @@
1
- import { DEFAULT_DENSITY, DEFAULT_FONT } from "@cueplusplus/tokens";
1
+ import { DEFAULT_DENSITY, DEFAULT_FONT, 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
  /**
@@ -68,6 +68,20 @@ function vocabulary(themes) {
68
68
  supportsLight: (theme) => theme === null ? true : byName.get(theme)?.supportsLight ?? true
69
69
  };
70
70
  }
71
+ /**
72
+ * Whether a value is one of the three style sets.
73
+ *
74
+ * A standalone function rather than a member of {@link Vocabulary}, and the
75
+ * asymmetry is the point: every other axis is asked *under a theme*, because a
76
+ * theme may add a rung or a pairing and a name accepted under the wrong theme
77
+ * stamps an attribute no block matches. No theme can add a style set — the axis
78
+ * is emitted by `@cueplusplus/tokens` and no manifest field describes it — so
79
+ * there is nothing per-theme to ask and a per-theme signature would be three
80
+ * parameters pretending to be a rule.
81
+ */
82
+ function isStyleSet(value) {
83
+ return typeof value === "string" && STYLE_SETS.includes(value);
84
+ }
71
85
  /** Derive the context value. Pure; the provider memoises it on `themes` and the active name. */
72
86
  function buildRegistry(themes, active) {
73
87
  return {
@@ -108,4 +122,4 @@ function noteOnce(message) {
108
122
  console.info(`[cue] ${message}`);
109
123
  }
110
124
  //#endregion
111
- export { buildRegistry, noteOnce, vocabulary, warnOnce };
125
+ export { buildRegistry, isStyleSet, noteOnce, vocabulary, warnOnce };
@@ -138,6 +138,7 @@
138
138
  "--cue-fg-subtle",
139
139
  "--cue-font-mono",
140
140
  "--cue-icon-sm",
141
+ "--cue-leading-ui",
141
142
  "--cue-pad-row-x",
142
143
  "--cue-space-1",
143
144
  "--cue-space-3",
@@ -113,6 +113,7 @@
113
113
  "--cue-border",
114
114
  "--cue-fg-muted",
115
115
  "--cue-fg-subtle",
116
+ "--cue-leading-ui",
116
117
  "--cue-ok",
117
118
  "--cue-radius-control",
118
119
  "--cue-radius-overlay",
@@ -72,6 +72,7 @@
72
72
  "--cue-border",
73
73
  "--cue-fg-muted",
74
74
  "--cue-fg-subtle",
75
+ "--cue-leading-ui",
75
76
  "--cue-stream",
76
77
  "--cue-text-ui"
77
78
  ],
@@ -173,6 +173,7 @@
173
173
  "--cue-fg-muted",
174
174
  "--cue-fg-subtle",
175
175
  "--cue-font-mono",
176
+ "--cue-leading-ui",
176
177
  "--cue-radius-control",
177
178
  "--cue-radius-overlay",
178
179
  "--cue-scrim",
@@ -248,6 +248,7 @@
248
248
  "--cue-fg-subtle",
249
249
  "--cue-font-mono",
250
250
  "--cue-icon-sm",
251
+ "--cue-leading-ui",
251
252
  "--cue-ok",
252
253
  "--cue-radius-surface",
253
254
  "--cue-space-2",
@@ -218,6 +218,7 @@
218
218
  "--cue-focus",
219
219
  "--cue-font-mono",
220
220
  "--cue-icon-sm",
221
+ "--cue-leading-ui",
221
222
  "--cue-radius-control",
222
223
  "--cue-radius-overlay",
223
224
  "--cue-scrim",