@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,7 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { useIsomorphicLayoutEffect } from "../system/use-isomorphic-layout-effect.js";
4
- import { AmbientThemeContext, DensityContext } from "../system/density.js";
4
+ import { AmbientThemeContext, DensityContext, StyleSetContext } from "../system/density.js";
5
5
  import { OverridesContext } from "../system/overrides.js";
6
6
  import { ThemeContext } from "../system/theme-provider.js";
7
7
  import { GROUND_CLASSES } from "./_ground.js";
@@ -34,7 +34,7 @@ const BENCH_CLASS = "relative flex min-w-0 flex-1 overflow-auto rounded-t-(--rad
34
34
  * goes.** `cn` preserves the order it is given, so a ground appended after the
35
35
  * padding would emit the dot grid in a different place from the one every
36
36
  * caller's markup has today — and `/`'s seventeen bare Previews and the group
37
- * pages' 325 tooled ones are both measured to the byte. Head, ground, tail, in
37
+ * pages' 326 tooled ones are both measured to the byte. Head, ground, tail, in
38
38
  * that order, is today's string.
39
39
  */
40
40
  const STAGE_HEAD = "grid h-full w-full min-h-[calc(var(--cue-space-8)*3)] place-items-center-safe";
@@ -222,6 +222,11 @@ function effectiveBounds(options, available) {
222
222
  *
223
223
  * A theme's density overrides are addressed through `data-cue-theme`, the
224
224
  * stamp `<Density>` writes, so a density names the nearest theme the same way.
225
+ *
226
+ * The style set adds no second attribute, and that is the difference worth
227
+ * writing down beside the three that do: a `[data-style]` block is a single
228
+ * attribute selector naming no other axis, so a set stamped alone matches on
229
+ * its own.
225
230
  */
226
231
  function stageAttributes(asked, page) {
227
232
  const attributes = {};
@@ -235,6 +240,7 @@ function stageAttributes(asked, page) {
235
240
  if (nearest !== null) attributes["data-cue-theme"] = nearest;
236
241
  }
237
242
  if (asked.font !== void 0) attributes["data-font"] = asked.font;
243
+ if (asked.styleSet !== void 0) attributes["data-style"] = asked.styleSet;
238
244
  return attributes;
239
245
  }
240
246
  /**
@@ -247,12 +253,17 @@ function stageAttributes(asked, page) {
247
253
  * footer are outside this subtree, so what they open follows the page. A theme
248
254
  * the provider did not register leaves `manifest` null, which is what
249
255
  * `useTheme()` already reports for a name nobody registered.
256
+ *
257
+ * The style set travels with the density for the same reason and by the same
258
+ * route: `useCuePortalProps()` stamps a portal container from `StyleSetContext`,
259
+ * and a popup opened on the stage inherits nothing from the stage's DOM.
250
260
  */
251
- function StageScope({ theme, mode, density, children }) {
261
+ function StageScope({ theme, mode, density, styleSet, children }) {
252
262
  const outer = React.useContext(ThemeContext);
253
263
  const outerOverrides = React.useContext(OverridesContext);
254
264
  const outerDensity = React.useContext(DensityContext);
255
265
  const outerAmbient = React.useContext(AmbientThemeContext);
266
+ const outerStyleSet = React.useContext(StyleSetContext);
256
267
  const value = React.useMemo(() => {
257
268
  if (outer === null || theme === void 0 && mode === void 0) return outer;
258
269
  const scoped = theme ?? outer.theme;
@@ -273,11 +284,14 @@ function StageScope({ theme, mode, density, children }) {
273
284
  value: theme ?? outerAmbient,
274
285
  children: /* @__PURE__ */ jsx(DensityContext.Provider, {
275
286
  value: density ?? outerDensity,
276
- children: /* @__PURE__ */ jsx(OverridesContext.Provider, {
277
- value: stamped ? null : outerOverrides,
278
- children: /* @__PURE__ */ jsx(ThemeContext.Provider, {
279
- value,
280
- children
287
+ children: /* @__PURE__ */ jsx(StyleSetContext.Provider, {
288
+ value: styleSet ?? outerStyleSet,
289
+ children: /* @__PURE__ */ jsx(OverridesContext.Provider, {
290
+ value: stamped ? null : outerOverrides,
291
+ children: /* @__PURE__ */ jsx(ThemeContext.Provider, {
292
+ value,
293
+ children
294
+ })
281
295
  })
282
296
  })
283
297
  })
@@ -456,13 +470,14 @@ function Grips({ label, options, size }) {
456
470
  * whatever canvas the frame sits on shows through. Everything below holds in
457
471
  * both.
458
472
  *
459
- * **The stage is the island; the chrome is not.** `theme`, `mode`, `density`
460
- * and `font` land on the stage element only, and the stage republishes the
461
- * theme and density contexts so an overlay opened on it follows it. The tools,
462
- * the footer, the grips and anything they open stay in the scope around the
463
- * Preview — a `terminal` preview's own controls do not turn cyan. With none of
464
- * the four set, the stage carries no attribute and costs what a `<div>` costs.
465
- * A stage `theme` with no `mode` repeats the page's mode beside it.
473
+ * **The stage is the island; the chrome is not.** `theme`, `mode`, `density`,
474
+ * `font` and `styleSet` land on the stage element only, and the stage
475
+ * republishes the theme, density and style-set contexts so an overlay opened on
476
+ * it follows it. The tools, the footer, the grips and anything they open stay
477
+ * in the scope around the Preview — a `terminal` preview's own controls do not
478
+ * turn cyan. With none of the five set, the stage carries no attribute and
479
+ * costs what a `<div>` costs. A stage `theme` with no `mode` repeats the page's
480
+ * mode beside it.
466
481
  *
467
482
  * **`resizable` is handles of its own**, both axes by default: an edge grip per
468
483
  * axis that is a keyboard-operable `separator` valued in pixels, and a corner
@@ -485,8 +500,12 @@ function Grips({ label, options, size }) {
485
500
  * <Preview label="Button" presentation="frame" count={3}>
486
501
  * <Button variant="primary">Take cue</Button>
487
502
  * </Preview>
503
+ * @example
504
+ * <Preview label="Editorial band" styleSet="editorial" presentation="frame">
505
+ * <Container className="max-w-(--cue-measure-prose)">…</Container>
506
+ * </Preview>
488
507
  */
489
- const Preview = React.forwardRef(function Preview({ children, label, theme, mode, density, font, resizable, tools, footer, presentation = "panel", name, count, ground, captions = true, className, ...elementProps }, ref) {
508
+ const Preview = React.forwardRef(function Preview({ children, label, theme, mode, density, font, styleSet, resizable, tools, footer, presentation = "panel", name, count, ground, captions = true, className, ...elementProps }, ref) {
490
509
  const outer = React.useContext(ThemeContext);
491
510
  const bench = React.useRef(null);
492
511
  const given = resolveResize(resizable);
@@ -497,7 +516,8 @@ const Preview = React.forwardRef(function Preview({ children, label, theme, mode
497
516
  theme,
498
517
  mode,
499
518
  density,
500
- font
519
+ font,
520
+ styleSet
501
521
  }, outer);
502
522
  const framed = presentation === "frame";
503
523
  const stage = /* @__PURE__ */ jsx("div", {
@@ -509,6 +529,7 @@ const Preview = React.forwardRef(function Preview({ children, label, theme, mode
509
529
  theme,
510
530
  mode,
511
531
  density,
532
+ styleSet,
512
533
  children
513
534
  })
514
535
  });
@@ -49,7 +49,7 @@ declare const overlayTextClasses = "font-mono text-(length:--cue-text-ui) text-f
49
49
  /** The heading line of a dialog, an alert dialog or a popover. */
50
50
  declare const overlayTitleClasses = "font-mono text-(length:--cue-text-emphasis) font-medium text-fg";
51
51
  /** The supporting line under an overlay title. */
52
- declare const overlayDescriptionClasses = "font-mono text-(length:--cue-text-ui) leading-relaxed text-fg-muted";
52
+ declare const overlayDescriptionClasses = "font-mono text-(length:--cue-text-ui) leading-(--cue-leading-ui) text-fg-muted";
53
53
  /** The action strip at the foot of a modal surface. */
54
54
  declare const overlayFooterClasses = "flex items-center justify-end gap-(--cue-space-3) pt-(--cue-space-2)";
55
55
  /**
@@ -49,7 +49,7 @@ const overlayTextClasses = "font-mono text-(length:--cue-text-ui) text-fg";
49
49
  /** The heading line of a dialog, an alert dialog or a popover. */
50
50
  const overlayTitleClasses = "font-mono text-(length:--cue-text-emphasis) font-medium text-fg";
51
51
  /** The supporting line under an overlay title. */
52
- const overlayDescriptionClasses = "font-mono text-(length:--cue-text-ui) leading-relaxed text-fg-muted";
52
+ const overlayDescriptionClasses = "font-mono text-(length:--cue-text-ui) leading-(--cue-leading-ui) text-fg-muted";
53
53
  /** The action strip at the foot of a modal surface. */
54
54
  const overlayFooterClasses = "flex items-center justify-end gap-(--cue-space-3) pt-(--cue-space-2)";
55
55
  /**
@@ -83,7 +83,7 @@ function ToastItem({ toast, className, closeLabel }) {
83
83
  }),
84
84
  /* @__PURE__ */ jsx(Toast.Description, {
85
85
  "data-slot": "toast-description",
86
- className: "font-mono text-(length:--cue-text-label) leading-relaxed text-fg-muted"
86
+ className: "font-mono text-(length:--cue-text-label) leading-(--cue-leading-ui) text-fg-muted"
87
87
  }),
88
88
  /* @__PURE__ */ jsx(Toast.Action, {
89
89
  "data-slot": "toast-action",
@@ -83,7 +83,7 @@ const Tooltip$1 = {
83
83
  id,
84
84
  role: "tooltip",
85
85
  "data-slot": "tooltip-popup",
86
- className: cn(overlaySurfaceClasses, overlayPopupTransitionClasses, "max-w-[16rem] px-(--cue-space-3) py-(--cue-space-2) font-mono text-(length:--cue-text-label) leading-relaxed text-fg", className),
86
+ className: cn(overlaySurfaceClasses, overlayPopupTransitionClasses, "max-w-[16rem] px-(--cue-space-3) py-(--cue-space-2) font-mono text-(length:--cue-text-label) leading-(--cue-leading-ui) text-fg", className),
87
87
  ...popupProps,
88
88
  children
89
89
  })
@@ -30,7 +30,7 @@ import * as React from "react";
30
30
  * <span className={chipVariants({ variant: "tag" })}>mcp</span>
31
31
  */
32
32
  declare const chipVariants: (props?: ({
33
- tone?: "danger" | "info" | "warn" | "accent" | "ok" | "busy" | "neutral" | null | undefined;
33
+ tone?: "info" | "warn" | "danger" | "accent" | "ok" | "busy" | "neutral" | null | undefined;
34
34
  variant?: "outline" | "tinted" | "tag" | null | undefined;
35
35
  interactive?: boolean | null | undefined;
36
36
  } & ClassProp) | undefined) => string;
@@ -1,12 +1,33 @@
1
1
  import * as React from "react";
2
- import { Density } from "@cueplusplus/theme-base";
2
+ import { StyleSet } from "@cueplusplus/tokens";
3
+ import { Density as Density$1 } from "@cueplusplus/theme-base";
3
4
  //#region src/system/density.d.ts
4
5
  interface DensityProps {
5
6
  /**
6
7
  * Level to apply to this subtree: `ultra-compact`, `compact`, `normal`,
7
8
  * `large` or `ultra-large`.
8
9
  */
9
- density: Density;
10
+ density: Density$1;
11
+ /**
12
+ * A style set for this subtree: `console`, `editorial` or `marketing`.
13
+ *
14
+ * Optional, and **absent means inherit** — an island that names none stamps no
15
+ * `data-style` and passes the ambient set straight through, exactly as a bare
16
+ * `<Density>` does today. Naming one is what makes this the library's island
17
+ * form of the fourth axis, and there are two reasons to reach for it:
18
+ *
19
+ * - `console` inside a page that is not console. That is the whole job of the
20
+ * default set: it restates the `:root` defaults, so a subtree stamped with
21
+ * it renders the ordinary look inside an editorial or marketing page.
22
+ * - Moving the band rhythm with the rung. `--cue-band-y` is
23
+ * `calc(var(--cue-space-7) * 2)` declared on the `[data-style]` element, and
24
+ * a custom property's `var()` references are substituted where they are
25
+ * declared — so a nested density island moves `--cue-space-*` but inherits
26
+ * the rhythm already resolved above it. Restating the set here re-resolves
27
+ * it against this island's rung, which is how a denser island gets a
28
+ * proportionally tighter band instead of the outer one's.
29
+ */
30
+ styleSet?: StyleSet;
10
31
  /** Subtree that should render at `density`. */
11
32
  children: React.ReactNode;
12
33
  /** Extra classes for the island element. It is an ordinary `<div>` — style it like one. */
@@ -26,8 +47,9 @@ interface DensityProps {
26
47
  *
27
48
  * `data-cue-theme` carries the nearest `<ThemeProvider>`'s name, or is
28
49
  * omitted where there is none. It is read by nothing this island does —
29
- * theme and mode are deliberately *not* re-scoped here, density is the only
30
- * axis an island may override (spec §6) — it exists so a theme's own
50
+ * theme and mode are deliberately *not* re-scoped here; density was for a long
51
+ * time the only axis an island could override (spec §6), and a named
52
+ * `styleSet` is the second and last — it exists so a theme's own
31
53
  * generated CSS can address exactly this island regardless of how many
32
54
  * *other* themed providers sit between it and the one it belongs to. A CSS
33
55
  * ancestor selector can only ask "does some ancestor carry theme `x`", never
@@ -35,11 +57,17 @@ interface DensityProps {
35
57
  * `useContext` already does, at render time, and hands the answer to CSS as
36
58
  * an attribute rather than leaving it unanswerable there.
37
59
  *
60
+ * `data-style` joins them when — and only when — a `styleSet` is named. Absence
61
+ * is how the axis inherits: custom properties inherit down the DOM, so an island
62
+ * that stamped the ambient set would be restating an answer it already had, and
63
+ * an island that stamped a *default* set would silently re-point the rhythm of
64
+ * every page that nested one.
65
+ *
38
66
  * @example
39
67
  * <Density density="ultra-compact">
40
68
  * <Toolbar />
41
69
  * </Density>
42
70
  */
43
- declare function Density$1({ density, children, className, style }: DensityProps): React.JSX.Element;
71
+ declare function Density$2({ density, styleSet, children, className, style }: DensityProps): React.JSX.Element;
44
72
  //#endregion
45
- export { Density$1 as Density, DensityProps };
73
+ export { Density$2 as Density, DensityProps };
@@ -38,6 +38,25 @@ const DensityContext = React.createContext(null);
38
38
  */
39
39
  const AmbientThemeContext = React.createContext(null);
40
40
  /**
41
+ * Nearest ambient style set. `null` means "nothing has stamped a set", which
42
+ * every reader reports as "no style set" rather than as the default one.
43
+ *
44
+ * Defined here, not in `theme-provider.tsx`, for the reason `AmbientThemeContext`
45
+ * above is: `theme-provider.tsx` already imports `DensityContext` from this
46
+ * module, and a context this small living over there and imported back here
47
+ * would cycle the two modules. Its readers are the provider, `Density` below,
48
+ * `Preview`'s `StageScope` and the portal stamp.
49
+ *
50
+ * Internal, and read directly rather than through `useTheme().styleSet`. A
51
+ * provider publishes this and its `ThemeContext.styleSet` from one value, so
52
+ * inside a provider the two agree; a `<Density styleSet>` island re-scopes this
53
+ * context without a `ThemeContext` of its own, so only this one answers "what
54
+ * set is stamped around me" everywhere. That is the question `Preview`'s stage
55
+ * and the portal stamp have to ask, because both leave the DOM they were
56
+ * written in and have to carry the answer with them.
57
+ */
58
+ const StyleSetContext = React.createContext(null);
59
+ /**
41
60
  * Re-scope any subtree to a different density level.
42
61
  *
43
62
  * Renders a plain `<div data-density="…" data-cue-theme="…">`: the geometry
@@ -49,8 +68,9 @@ const AmbientThemeContext = React.createContext(null);
49
68
  *
50
69
  * `data-cue-theme` carries the nearest `<ThemeProvider>`'s name, or is
51
70
  * omitted where there is none. It is read by nothing this island does —
52
- * theme and mode are deliberately *not* re-scoped here, density is the only
53
- * axis an island may override (spec §6) — it exists so a theme's own
71
+ * theme and mode are deliberately *not* re-scoped here; density was for a long
72
+ * time the only axis an island could override (spec §6), and a named
73
+ * `styleSet` is the second and last — it exists so a theme's own
54
74
  * generated CSS can address exactly this island regardless of how many
55
75
  * *other* themed providers sit between it and the one it belongs to. A CSS
56
76
  * ancestor selector can only ask "does some ancestor carry theme `x`", never
@@ -58,23 +78,34 @@ const AmbientThemeContext = React.createContext(null);
58
78
  * `useContext` already does, at render time, and hands the answer to CSS as
59
79
  * an attribute rather than leaving it unanswerable there.
60
80
  *
81
+ * `data-style` joins them when — and only when — a `styleSet` is named. Absence
82
+ * is how the axis inherits: custom properties inherit down the DOM, so an island
83
+ * that stamped the ambient set would be restating an answer it already had, and
84
+ * an island that stamped a *default* set would silently re-point the rhythm of
85
+ * every page that nested one.
86
+ *
61
87
  * @example
62
88
  * <Density density="ultra-compact">
63
89
  * <Toolbar />
64
90
  * </Density>
65
91
  */
66
- function Density({ density, children, className, style }) {
92
+ function Density({ density, styleSet, children, className, style }) {
67
93
  const theme = React.useContext(AmbientThemeContext);
94
+ const ambientStyleSet = React.useContext(StyleSetContext);
68
95
  return /* @__PURE__ */ jsx(DensityContext.Provider, {
69
96
  value: density,
70
- children: /* @__PURE__ */ jsx("div", {
71
- "data-density": density,
72
- "data-cue-theme": theme ?? void 0,
73
- className,
74
- style,
75
- children
97
+ children: /* @__PURE__ */ jsx(StyleSetContext.Provider, {
98
+ 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
106
+ })
76
107
  })
77
108
  });
78
109
  }
79
110
  //#endregion
80
- export { AmbientThemeContext, DEFAULT_DENSITY, Density, DensityContext };
111
+ export { AmbientThemeContext, DEFAULT_DENSITY, Density, DensityContext, StyleSetContext };
@@ -7,5 +7,6 @@ import { ThemeRegistry } from "./vocabulary.js";
7
7
  import { useDensities, useFonts, useThemes } from "./theme-registry.js";
8
8
  import { ControlSize, useControlHeight, useDensity } from "./use-density.js";
9
9
  import { useTheme } from "./use-theme.js";
10
+ import { DEFAULT_STYLE_SET, STYLE_SETS, StyleSet } from "@cueplusplus/tokens";
10
11
  import { Density as DensityLevel, DensityEntry, FontEntry, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
11
- export { type ControlSize, CuePortalFrame, type CuePortalFrameProps, DEFAULT_STORAGE_KEY, Density, type DensityEntry, type DensityLevel, type DensityProps, type FontEntry, type FontFamilies, type FontName, type Mode, type PersistedPreferences, type PrepaintDefaults, type PrepaintOptions, type ResolvedMode, type ThemeContextValue, type ThemeManifest, type ThemeName, ThemeProvider, type ThemeProviderProps, type ThemeRegistry, type TokenOverrides, isSafeTokenValue, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
12
+ export { type ControlSize, CuePortalFrame, type CuePortalFrameProps, DEFAULT_STORAGE_KEY, DEFAULT_STYLE_SET, Density, type DensityEntry, type DensityLevel, type DensityProps, type FontEntry, type FontFamilies, type FontName, type Mode, type PersistedPreferences, type PrepaintDefaults, type PrepaintOptions, type ResolvedMode, STYLE_SETS, type StyleSet, type ThemeContextValue, type ThemeManifest, type ThemeName, ThemeProvider, type ThemeProviderProps, type ThemeRegistry, type TokenOverrides, isSafeTokenValue, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
@@ -6,4 +6,5 @@ import { ThemeProvider } from "./theme-provider.js";
6
6
  import { CuePortalFrame, useCuePortalProps } from "./portal.js";
7
7
  import { useControlHeight, useDensity } from "./use-density.js";
8
8
  import { useTheme } from "./use-theme.js";
9
- export { CuePortalFrame, DEFAULT_STORAGE_KEY, Density, ThemeProvider, isSafeTokenValue, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
9
+ import { DEFAULT_STYLE_SET, STYLE_SETS } from "@cueplusplus/tokens";
10
+ export { CuePortalFrame, DEFAULT_STORAGE_KEY, DEFAULT_STYLE_SET, Density, STYLE_SETS, ThemeProvider, isSafeTokenValue, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
@@ -7,8 +7,9 @@ import * as React from "react";
7
7
  * Returns a `container` element appended to `document.body` and stamped with
8
8
  * `data-theme` / `data-density` / `data-font` / `data-mode` (plus
9
9
  * `--cue-font-scale` and any `ThemeProvider` `overrides` colour or font
10
- * property) copied from the nearest provider and density island, and
11
- * with `data-cue-skin` / `data-cue-fidelity` when the caller sits inside an
10
+ * property) copied from the nearest provider and density island, with
11
+ * `data-style` when the caller is inside a style set, and with
12
+ * `data-cue-skin` / `data-cue-fidelity` when the caller sits inside an
12
13
  * `AgentSurface` island. The container is
13
14
  * `display: contents`, so it changes no layout and creates no containing block —
14
15
  * fixed-position overlay content behaves exactly as if it were a direct child of
@@ -41,11 +42,11 @@ interface CuePortalFrameProps {
41
42
  * chosen (a third-party overlay, or content already portaled by something else).
42
43
  *
43
44
  * Wraps `children` in a `display: contents` element carrying the same
44
- * `data-theme` / `data-density` / `data-font` / `data-mode` / `data-cue-skin` /
45
- * `data-cue-fidelity` stamp, `--cue-font-scale` and `overrides` properties as
46
- * {@link useCuePortalProps}. Prefer the hook — a stamped container costs one
47
- * element per overlay instead of one per render tree — and reach for this only
48
- * when the container prop is not available.
45
+ * `data-theme` / `data-density` / `data-font` / `data-mode` / `data-style` /
46
+ * `data-cue-skin` / `data-cue-fidelity` stamp, `--cue-font-scale` and
47
+ * `overrides` properties as {@link useCuePortalProps}. Prefer the hook — a
48
+ * stamped container costs one element per overlay instead of one per render
49
+ * tree — and reach for this only when the container prop is not available.
49
50
  *
50
51
  * @example
51
52
  * <SomeThirdPartyPortal><CuePortalFrame>{content}</CuePortalFrame></SomeThirdPartyPortal>
@@ -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 } from "./density.js";
4
+ import { DensityContext, 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";
@@ -30,6 +30,7 @@ function usePortalStamp() {
30
30
  const fontFamilies = React.useContext(FontFamiliesContext);
31
31
  const overrides = React.useContext(OverridesContext);
32
32
  const skin = React.useContext(AgentSkinContext);
33
+ const styleSet = React.useContext(StyleSetContext);
33
34
  return {
34
35
  theme: theme?.theme ?? DEFAULT_THEME,
35
36
  density: island ?? theme?.density ?? "compact",
@@ -39,7 +40,8 @@ function usePortalStamp() {
39
40
  fontFamilies,
40
41
  overrides,
41
42
  skin: skin?.skin ?? null,
42
- fidelity: skin?.fidelity ?? null
43
+ fidelity: skin?.fidelity ?? null,
44
+ styleSet
43
45
  };
44
46
  }
45
47
  /**
@@ -49,8 +51,9 @@ function usePortalStamp() {
49
51
  * Returns a `container` element appended to `document.body` and stamped with
50
52
  * `data-theme` / `data-density` / `data-font` / `data-mode` (plus
51
53
  * `--cue-font-scale` and any `ThemeProvider` `overrides` colour or font
52
- * property) copied from the nearest provider and density island, and
53
- * with `data-cue-skin` / `data-cue-fidelity` when the caller sits inside an
54
+ * property) copied from the nearest provider and density island, with
55
+ * `data-style` when the caller is inside a style set, and with
56
+ * `data-cue-skin` / `data-cue-fidelity` when the caller sits inside an
54
57
  * `AgentSurface` island. The container is
55
58
  * `display: contents`, so it changes no layout and creates no containing block —
56
59
  * fixed-position overlay content behaves exactly as if it were a direct child of
@@ -93,6 +96,7 @@ function useCuePortalProps() {
93
96
  stamp.theme,
94
97
  stamp.density,
95
98
  stamp.font,
99
+ stamp.styleSet,
96
100
  stamp.mode,
97
101
  stamp.fontScale,
98
102
  stamp.fontFamilies?.sans,
@@ -126,6 +130,8 @@ function applyStamp(element, stamp) {
126
130
  element.setAttribute("data-density", stamp.density);
127
131
  element.setAttribute("data-font", stamp.font);
128
132
  element.setAttribute("data-mode", stamp.mode);
133
+ if (stamp.styleSet === null) element.removeAttribute("data-style");
134
+ else element.setAttribute("data-style", stamp.styleSet);
129
135
  if (stamp.skin === null) element.removeAttribute("data-cue-skin");
130
136
  else element.setAttribute("data-cue-skin", stamp.skin);
131
137
  if (stamp.fidelity === null) element.removeAttribute("data-cue-fidelity");
@@ -150,11 +156,11 @@ function setOptionalProperty(element, property, value) {
150
156
  * chosen (a third-party overlay, or content already portaled by something else).
151
157
  *
152
158
  * Wraps `children` in a `display: contents` element carrying the same
153
- * `data-theme` / `data-density` / `data-font` / `data-mode` / `data-cue-skin` /
154
- * `data-cue-fidelity` stamp, `--cue-font-scale` and `overrides` properties as
155
- * {@link useCuePortalProps}. Prefer the hook — a stamped container costs one
156
- * element per overlay instead of one per render tree — and reach for this only
157
- * when the container prop is not available.
159
+ * `data-theme` / `data-density` / `data-font` / `data-mode` / `data-style` /
160
+ * `data-cue-skin` / `data-cue-fidelity` stamp, `--cue-font-scale` and
161
+ * `overrides` properties as {@link useCuePortalProps}. Prefer the hook — a
162
+ * stamped container costs one element per overlay instead of one per render
163
+ * tree — and reach for this only when the container prop is not available.
158
164
  *
159
165
  * @example
160
166
  * <SomeThirdPartyPortal><CuePortalFrame>{content}</CuePortalFrame></SomeThirdPartyPortal>
@@ -175,6 +181,7 @@ function CuePortalFrame({ children, className, style }) {
175
181
  "data-density": stamp.density,
176
182
  "data-font": stamp.font,
177
183
  "data-mode": stamp.mode,
184
+ "data-style": stamp.styleSet ?? void 0,
178
185
  "data-cue-skin": stamp.skin ?? void 0,
179
186
  "data-cue-fidelity": stamp.fidelity ?? void 0,
180
187
  className,
@@ -1,4 +1,5 @@
1
- import { Density, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
1
+ import { StyleSet } from "@cueplusplus/tokens";
2
+ import { Density as Density$1, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
2
3
  //#region src/system/prepaint.d.ts
3
4
  /** localStorage key the ThemeProvider and the pre-paint script share by default. */
4
5
  declare const DEFAULT_STORAGE_KEY = "cue-ui";
@@ -12,6 +13,14 @@ interface PersistedPreferences {
12
13
  mode?: string;
13
14
  /** Last font pairing the user picked. */
14
15
  font?: string;
16
+ /**
17
+ * Last style set the user picked, or absent — which is what "no style set"
18
+ * looks like in storage. Absent rather than `null`, because the key is written
19
+ * only when there is a set: an application that never touches the axis stores
20
+ * the four-key object it always stored, and the pre-paint script has no fifth
21
+ * value to hold an opinion about.
22
+ */
23
+ style?: string;
15
24
  }
16
25
  /**
17
26
  * The server-authoritative fallback stamped when storage is absent or ignored.
@@ -29,10 +38,20 @@ interface PersistedPreferences {
29
38
  interface PrepaintDefaults {
30
39
  theme: ThemeName;
31
40
  /** Initial density rung. Omitted, the theme's own `densities.default` decides. */
32
- density?: Density;
41
+ density?: Density$1;
33
42
  mode: Mode;
34
43
  /** Initial document-wide font pairing. Omitted, the theme's own `fontPairings.default` decides. */
35
44
  font?: FontName;
45
+ /**
46
+ * Initial style set. Omitted — and it is omitted by every app that has not
47
+ * asked for page composition — the script stamps no `data-style` at all, which
48
+ * is exactly what `<ThemeProvider>` does with no `styleSet` prop.
49
+ *
50
+ * Unlike `density` and `font` there is no per-theme preference behind it: no
51
+ * manifest can declare a style set, so the fall-through is two tiers deep
52
+ * (stored, then this) rather than four.
53
+ */
54
+ styleSet?: StyleSet;
36
55
  }
37
56
  /** Options form for apps whose server projection, not localStorage, owns first paint. */
38
57
  interface PrepaintOptions {
@@ -59,8 +78,9 @@ interface PrepaintOptions {
59
78
  }
60
79
  /**
61
80
  * Build the blocking inline script that stamps `data-theme`, `data-density`,
62
- * `data-font`, `data-mode` and `color-scheme` on `<html>` from localStorage
63
- * before the first paint.
81
+ * `data-font`, `data-mode`, `data-style` and `color-scheme` on `<html>` from
82
+ * localStorage before the first paint, and removes `data-style` when nothing
83
+ * chose one.
64
84
  *
65
85
  * Render it as `<script dangerouslySetInnerHTML={{ __html: prepaintScript() }} />`
66
86
  * in `<head>`, above everything else. The output never contains `</script>` and
@@ -108,7 +128,7 @@ interface PrepaintOptions {
108
128
  * // An app whose provider is <ThemeProvider theme="terminal" density="normal">:
109
129
  * prepaintScript(DEFAULT_STORAGE_KEY, "terminal", "normal");
110
130
  */
111
- declare function prepaintScript(storageKey?: string, defaultTheme?: ThemeName, defaultDensity?: Density, defaultFont?: FontName): string;
131
+ declare function prepaintScript(storageKey?: string, defaultTheme?: ThemeName, defaultDensity?: Density$1, defaultFont?: FontName): string;
112
132
  declare function prepaintScript(options: PrepaintOptions): string;
113
133
  //#endregion
114
134
  export { DEFAULT_STORAGE_KEY, PersistedPreferences, PrepaintDefaults, PrepaintOptions, prepaintScript };
@@ -1,5 +1,5 @@
1
1
  import { vocabulary } from "./vocabulary.js";
2
- import { DEFAULT_DENSITY, DEFAULT_FONT } from "@cueplusplus/tokens";
2
+ import { DEFAULT_DENSITY, DEFAULT_FONT, STYLE_SETS } from "@cueplusplus/tokens";
3
3
  import { MODES } from "@cueplusplus/theme-base";
4
4
  //#region src/system/prepaint.ts
5
5
  /**
@@ -106,7 +106,15 @@ function prepaintScript(storageKeyOrOptions = DEFAULT_STORAGE_KEY, defaultTheme
106
106
  }];
107
107
  }))));
108
108
  const modes = scriptSafe(JSON.stringify([...MODES]));
109
- 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};e.setAttribute("data-theme",t);e.setAttribute("data-density",d);e.setAttribute("data-font",f);e.setAttribute("data-mode",m);e.style.colorScheme=a.l===false?"dark":(m==="system"?"light dark":m)}catch(_){}}()`;
109
+ /**
110
+ * The three sets, and the app's own, inlined for the same reason the rungs and
111
+ * pairings are: this script runs before any module does and cannot import a
112
+ * list. `null` is a legal fallback here and means "no set" — the one axis
113
+ * whose absence is a value.
114
+ */
115
+ const styleSets = scriptSafe(JSON.stringify([...STYLE_SETS]));
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(_){}}()`;
110
118
  }
111
119
  //#endregion
112
120
  export { DEFAULT_STORAGE_KEY, prepaintScript };