@cueplusplus/ui 0.8.0 → 0.10.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 (352) hide show
  1. package/CHANGELOG.md +457 -0
  2. package/README.md +131 -0
  3. package/dist/chat/message-list.js +2 -1
  4. package/dist/configurator/_export.d.ts +1 -1
  5. package/dist/configurator/_export.js +53 -13
  6. package/dist/configurator/_overrides.d.ts +25 -6
  7. package/dist/configurator/_overrides.js +30 -17
  8. package/dist/configurator/configurator.js +8 -3
  9. package/dist/configurator/panel-sections.js +43 -13
  10. package/dist/elements/command-palette.js +1 -1
  11. package/dist/elements/flow-graph.js +2 -2
  12. package/dist/elements/markdown.js +1 -1
  13. package/dist/elements/surfaces.js +4 -3
  14. package/dist/index.d.ts +7 -3
  15. package/dist/index.js +5 -2
  16. package/dist/instruments/meter.d.ts +5 -1
  17. package/dist/layout/bento.d.ts +144 -0
  18. package/dist/layout/bento.js +266 -0
  19. package/dist/layout/index.d.ts +3 -2
  20. package/dist/layout/index.js +3 -2
  21. package/dist/midi/piano-keyboard.js +5 -1
  22. package/dist/primitives/button.d.ts +12 -0
  23. package/dist/styles.css +23 -1
  24. package/dist/system/density.d.ts +17 -8
  25. package/dist/system/density.js +39 -16
  26. package/dist/system/index.d.ts +5 -2
  27. package/dist/system/index.js +3 -1
  28. package/dist/system/overrides.d.ts +80 -0
  29. package/dist/system/overrides.js +277 -0
  30. package/dist/system/portal.d.ts +4 -2
  31. package/dist/system/portal.js +34 -3
  32. package/dist/system/prepaint.d.ts +58 -7
  33. package/dist/system/prepaint.js +72 -20
  34. package/dist/system/theme-provider.d.ts +133 -8
  35. package/dist/system/theme-provider.js +203 -72
  36. package/dist/system/theme-registry.d.ts +53 -0
  37. package/dist/system/theme-registry.js +66 -0
  38. package/dist/system/use-density.d.ts +13 -5
  39. package/dist/system/use-density.js +142 -13
  40. package/dist/system/use-theme.d.ts +5 -3
  41. package/dist/system/use-theme.js +5 -3
  42. package/dist/system/vocabulary.d.ts +15 -0
  43. package/dist/system/vocabulary.js +111 -0
  44. package/dist/theming/contrast.d.ts +2 -122
  45. package/dist/theming/contrast.js +2 -194
  46. package/dist/theming/create-theme.d.ts +37 -11
  47. package/dist/theming/create-theme.js +54 -17
  48. package/dist/theming/index.d.ts +3 -4
  49. package/dist/theming/index.js +3 -4
  50. package/dist/theming/serialize.d.ts +24 -11
  51. package/dist/theming/serialize.js +16 -18
  52. package/manifest/components/accordion.json +24 -6
  53. package/manifest/components/activity-graph.json +20 -5
  54. package/manifest/components/agent-card.json +37 -9
  55. package/manifest/components/agent-handoff.json +21 -5
  56. package/manifest/components/agent-mode-badge.json +22 -4
  57. package/manifest/components/agent-pile.json +38 -8
  58. package/manifest/components/agent-plan.json +9 -2
  59. package/manifest/components/agent-status.json +17 -3
  60. package/manifest/components/agent-surface.json +26 -11
  61. package/manifest/components/alert-dialog.json +29 -5
  62. package/manifest/components/animated-number.json +14 -6
  63. package/manifest/components/app-bar.json +66 -13
  64. package/manifest/components/app-shell.json +133 -31
  65. package/manifest/components/app-window-frame.json +28 -6
  66. package/manifest/components/approval-card.json +37 -7
  67. package/manifest/components/artifact-card.json +16 -4
  68. package/manifest/components/ask-box.json +99 -21
  69. package/manifest/components/audience-icon.json +1 -7
  70. package/manifest/components/autocomplete.json +80 -18
  71. package/manifest/components/avatar-group.json +13 -2
  72. package/manifest/components/avatar.json +22 -4
  73. package/manifest/components/background-inbox.json +9 -2
  74. package/manifest/components/bento.json +306 -0
  75. package/manifest/components/branch-picker.json +29 -7
  76. package/manifest/components/breadcrumb.json +13 -3
  77. package/manifest/components/button-group.json +21 -4
  78. package/manifest/components/button.json +41 -4
  79. package/manifest/components/canvas-split-body.json +4 -1
  80. package/manifest/components/canvas-split-header.json +17 -4
  81. package/manifest/components/canvas-split-line.json +4 -1
  82. package/manifest/components/canvas-split-message.json +8 -1
  83. package/manifest/components/card.json +14 -3
  84. package/manifest/components/carousel.json +54 -12
  85. package/manifest/components/catalogue-icon.json +1 -7
  86. package/manifest/components/channel-beta-icon.json +1 -7
  87. package/manifest/components/channel-matrix.json +80 -15
  88. package/manifest/components/channel-released-icon.json +1 -7
  89. package/manifest/components/chart-container.json +26 -10
  90. package/manifest/components/chart-ramp.json +13 -3
  91. package/manifest/components/chart-swatch.json +9 -2
  92. package/manifest/components/chart-tooltip-content.json +31 -7
  93. package/manifest/components/chart.json +30 -6
  94. package/manifest/components/chat-panel-composer.json +9 -2
  95. package/manifest/components/checkbox-group.json +4 -2
  96. package/manifest/components/checkbox.json +17 -4
  97. package/manifest/components/checkpoint-history.json +13 -3
  98. package/manifest/components/chip.json +35 -5
  99. package/manifest/components/clamp.json +30 -7
  100. package/manifest/components/cli-tool-icon.json +1 -7
  101. package/manifest/components/code-diff.json +20 -5
  102. package/manifest/components/code-runner.json +32 -6
  103. package/manifest/components/collapsible.json +16 -4
  104. package/manifest/components/color-area.json +4 -1
  105. package/manifest/components/color-field.json +13 -3
  106. package/manifest/components/color-picker.json +75 -17
  107. package/manifest/components/color-slider.json +12 -3
  108. package/manifest/components/color-swatch.json +13 -2
  109. package/manifest/components/colors-section.json +10 -7
  110. package/manifest/components/combobox.json +81 -18
  111. package/manifest/components/command-palette.json +57 -13
  112. package/manifest/components/compaction-row.json +35 -7
  113. package/manifest/components/comparison-card.json +17 -4
  114. package/manifest/components/composer-attachment-chip.json +9 -2
  115. package/manifest/components/composer-bar.json +4 -1
  116. package/manifest/components/composer-command-item.json +8 -2
  117. package/manifest/components/composer-context.json +4 -1
  118. package/manifest/components/composer-input.json +5 -1
  119. package/manifest/components/composer-menu-item.json +4 -1
  120. package/manifest/components/composer-menu.json +12 -2
  121. package/manifest/components/composer-model-item.json +8 -2
  122. package/manifest/components/composer-model-trigger.json +8 -2
  123. package/manifest/components/composer-person-item.json +8 -2
  124. package/manifest/components/composer-send.json +8 -2
  125. package/manifest/components/composer-voice-button.json +4 -1
  126. package/manifest/components/composer-voice.json +8 -2
  127. package/manifest/components/composer.json +76 -17
  128. package/manifest/components/computer-use.json +17 -4
  129. package/manifest/components/confidence-marker.json +13 -3
  130. package/manifest/components/connection-state.json +23 -4
  131. package/manifest/components/container.json +14 -2
  132. package/manifest/components/context-breakdown.json +8 -2
  133. package/manifest/components/context-menu.json +16 -4
  134. package/manifest/components/context-usage.json +16 -4
  135. package/manifest/components/conversation-search.json +22 -5
  136. package/manifest/components/copy-button.json +32 -7
  137. package/manifest/components/cost-meter.json +12 -3
  138. package/manifest/components/cue-logotype.json +17 -3
  139. package/manifest/components/cue-mark.json +17 -3
  140. package/manifest/components/cue-portal-frame.json +14 -4
  141. package/manifest/components/data-row.json +21 -7
  142. package/manifest/components/data-table-pagination.json +18 -4
  143. package/manifest/components/data-table-toolbar.json +30 -7
  144. package/manifest/components/data-tree.json +42 -9
  145. package/manifest/components/date-field.json +13 -2
  146. package/manifest/components/date-picker.json +81 -19
  147. package/manifest/components/date-range-picker.json +81 -19
  148. package/manifest/components/day-separator.json +4 -1
  149. package/manifest/components/delegation-card.json +64 -12
  150. package/manifest/components/density.json +25 -5
  151. package/manifest/components/description-list.json +8 -1
  152. package/manifest/components/diagram.json +33 -7
  153. package/manifest/components/dialog.json +29 -6
  154. package/manifest/components/disclosure.json +31 -7
  155. package/manifest/components/dmx-bar.json +16 -4
  156. package/manifest/components/dmx-strip.json +20 -5
  157. package/manifest/components/document-reference.json +21 -5
  158. package/manifest/components/draft-restore.json +18 -4
  159. package/manifest/components/drawer.json +30 -6
  160. package/manifest/components/dropdown-menu.json +55 -10
  161. package/manifest/components/edit-message.json +32 -7
  162. package/manifest/components/elements-command-palette.json +27 -6
  163. package/manifest/components/elements-data-table.json +9 -2
  164. package/manifest/components/elements-timeline.json +8 -2
  165. package/manifest/components/elicitation-form.json +31 -6
  166. package/manifest/components/empty-state-composer.json +9 -2
  167. package/manifest/components/empty-state-suggestion.json +4 -1
  168. package/manifest/components/empty-state.json +25 -5
  169. package/manifest/components/end-of-turn-summary.json +16 -4
  170. package/manifest/components/env-var-input.json +54 -12
  171. package/manifest/components/error-state.json +17 -4
  172. package/manifest/components/export-dialog.json +27 -11
  173. package/manifest/components/eyebrow.json +4 -1
  174. package/manifest/components/feedback-dialog.json +33 -7
  175. package/manifest/components/field-description.json +4 -1
  176. package/manifest/components/field-error.json +13 -3
  177. package/manifest/components/field-label.json +4 -1
  178. package/manifest/components/field.json +4 -1
  179. package/manifest/components/file-tree.json +16 -4
  180. package/manifest/components/file-upload.json +58 -13
  181. package/manifest/components/flow-graph.json +12 -3
  182. package/manifest/components/folder-icon.json +1 -7
  183. package/manifest/components/footer.json +24 -4
  184. package/manifest/components/frac.json +10 -2
  185. package/manifest/components/generation-loader.json +17 -3
  186. package/manifest/components/generative-ui.json +39 -7
  187. package/manifest/components/grid.json +56 -5
  188. package/manifest/components/group-bar.json +25 -6
  189. package/manifest/components/guardrail-notice.json +22 -5
  190. package/manifest/components/hover-card.json +45 -8
  191. package/manifest/components/icon-button.json +22 -4
  192. package/manifest/components/image-generation.json +8 -2
  193. package/manifest/components/info-tip.json +42 -7
  194. package/manifest/components/inline-citation.json +14 -3
  195. package/manifest/components/input-group.json +24 -5
  196. package/manifest/components/input.json +13 -3
  197. package/manifest/components/item.json +42 -8
  198. package/manifest/components/job-progress.json +25 -6
  199. package/manifest/components/launcher-bubble.json +32 -7
  200. package/manifest/components/ledger.json +87 -19
  201. package/manifest/components/link.json +16 -3
  202. package/manifest/components/live-region-announcer.json +21 -5
  203. package/manifest/components/log-viewer.json +42 -10
  204. package/manifest/components/map-answer.json +17 -4
  205. package/manifest/components/markdown-text.json +36 -5
  206. package/manifest/components/math-block.json +12 -3
  207. package/manifest/components/mcp-server-icon.json +1 -7
  208. package/manifest/components/mcp-server-panel.json +18 -4
  209. package/manifest/components/memory-chips.json +9 -2
  210. package/manifest/components/menubar.json +33 -7
  211. package/manifest/components/message-actions.json +32 -7
  212. package/manifest/components/message-attachments.json +9 -2
  213. package/manifest/components/message-branches.json +14 -3
  214. package/manifest/components/message-list.json +27 -6
  215. package/manifest/components/message-pair.json +25 -5
  216. package/manifest/components/message-queue.json +13 -3
  217. package/manifest/components/message-timing.json +8 -2
  218. package/manifest/components/message.json +41 -9
  219. package/manifest/components/meter.json +43 -7
  220. package/manifest/components/mobile-composer.json +47 -10
  221. package/manifest/components/model-picker.json +13 -3
  222. package/manifest/components/multi-select.json +78 -17
  223. package/manifest/components/musical-time-input.json +34 -8
  224. package/manifest/components/navigation-menu.json +54 -12
  225. package/manifest/components/node-card.json +42 -7
  226. package/manifest/components/node-handle.json +17 -2
  227. package/manifest/components/number-field.json +28 -6
  228. package/manifest/components/number-ticker.json +8 -2
  229. package/manifest/components/onboarding.json +18 -4
  230. package/manifest/components/otp-field.json +43 -10
  231. package/manifest/components/page-shell.json +35 -6
  232. package/manifest/components/pagination.json +21 -5
  233. package/manifest/components/panel-header.json +15 -3
  234. package/manifest/components/password-input.json +38 -8
  235. package/manifest/components/permission-grant.json +28 -5
  236. package/manifest/components/permission-scopes.json +57 -12
  237. package/manifest/components/piano-keyboard.json +38 -9
  238. package/manifest/components/plussie.json +34 -5
  239. package/manifest/components/popover.json +45 -8
  240. package/manifest/components/preset-section.json +14 -8
  241. package/manifest/components/progress.json +25 -6
  242. package/manifest/components/prompt-library.json +27 -6
  243. package/manifest/components/queue-dock.json +36 -8
  244. package/manifest/components/quota-banner.json +25 -6
  245. package/manifest/components/quote-reply.json +29 -7
  246. package/manifest/components/radio-group.json +12 -2
  247. package/manifest/components/radio.json +17 -3
  248. package/manifest/components/rating.json +60 -13
  249. package/manifest/components/read-aloud.json +35 -8
  250. package/manifest/components/reasoning-effort.json +17 -4
  251. package/manifest/components/reasoning-panel.json +33 -8
  252. package/manifest/components/recommendation-card.json +35 -10
  253. package/manifest/components/regenerate-menu.json +22 -5
  254. package/manifest/components/replay-player.json +29 -7
  255. package/manifest/components/research-report.json +12 -3
  256. package/manifest/components/resizable.json +4 -1
  257. package/manifest/components/retrieval-chunks.json +16 -4
  258. package/manifest/components/revert-dock.json +61 -14
  259. package/manifest/components/reviewable-diff.json +23 -5
  260. package/manifest/components/risk-badge.json +18 -3
  261. package/manifest/components/row.json +17 -4
  262. package/manifest/components/schedule-card.json +25 -6
  263. package/manifest/components/score-breakdown.json +20 -5
  264. package/manifest/components/scroll-anchor.json +13 -3
  265. package/manifest/components/scroll-area.json +31 -8
  266. package/manifest/components/scrollable-tabs-list.json +12 -4
  267. package/manifest/components/scrub-input.json +84 -18
  268. package/manifest/components/seam-cell.json +19 -3
  269. package/manifest/components/seam-grid.json +12 -3
  270. package/manifest/components/search-input.json +49 -10
  271. package/manifest/components/section-header.json +36 -8
  272. package/manifest/components/segmented-control.json +50 -11
  273. package/manifest/components/select.json +74 -16
  274. package/manifest/components/separator.json +4 -1
  275. package/manifest/components/settings-panel.json +41 -9
  276. package/manifest/components/shape-section.json +10 -7
  277. package/manifest/components/shared-conversation.json +21 -5
  278. package/manifest/components/sheet.json +29 -6
  279. package/manifest/components/shimmer-label.json +4 -2
  280. package/manifest/components/sidebar.json +60 -13
  281. package/manifest/components/skill-icon.json +1 -7
  282. package/manifest/components/slider.json +22 -5
  283. package/manifest/components/sources.json +17 -4
  284. package/manifest/components/sparkline.json +17 -4
  285. package/manifest/components/speaker-identity.json +4 -1
  286. package/manifest/components/spec-sheet.json +16 -4
  287. package/manifest/components/spectrum-visualizer.json +41 -10
  288. package/manifest/components/spinner.json +12 -2
  289. package/manifest/components/stack-icon.json +1 -7
  290. package/manifest/components/stack.json +54 -7
  291. package/manifest/components/stacks-matrix-icon.json +1 -7
  292. package/manifest/components/stat.json +28 -4
  293. package/manifest/components/status-bar.json +50 -9
  294. package/manifest/components/status-dot.json +23 -4
  295. package/manifest/components/stepper.json +22 -5
  296. package/manifest/components/stopped-run.json +19 -4
  297. package/manifest/components/streaming-text.json +12 -3
  298. package/manifest/components/subagent-list.json +21 -5
  299. package/manifest/components/suggestions.json +27 -5
  300. package/manifest/components/swap-label.json +18 -4
  301. package/manifest/components/switch.json +18 -3
  302. package/manifest/components/table-scroll-region.json +4 -1
  303. package/manifest/components/table.json +54 -11
  304. package/manifest/components/tabs.json +16 -5
  305. package/manifest/components/tags-input.json +79 -17
  306. package/manifest/components/tail-status.json +37 -8
  307. package/manifest/components/terminal-block.json +25 -5
  308. package/manifest/components/terminal-frame.json +34 -8
  309. package/manifest/components/textarea.json +8 -3
  310. package/manifest/components/theme-configurator.json +47 -14
  311. package/manifest/components/theme-provider.json +97 -18
  312. package/manifest/components/thinking-indicator.json +8 -2
  313. package/manifest/components/thread-list.json +13 -3
  314. package/manifest/components/thread-search.json +22 -5
  315. package/manifest/components/threshold-rail.json +77 -14
  316. package/manifest/components/time-boundary.json +8 -2
  317. package/manifest/components/time-field.json +13 -2
  318. package/manifest/components/timeline-ruler.json +51 -12
  319. package/manifest/components/timeline.json +19 -3
  320. package/manifest/components/title-bar.json +19 -4
  321. package/manifest/components/toast.json +12 -3
  322. package/manifest/components/todo-list.json +8 -2
  323. package/manifest/components/toggle-group.json +33 -7
  324. package/manifest/components/toggle.json +23 -4
  325. package/manifest/components/token-editor.json +26 -10
  326. package/manifest/components/tool-call-card.json +83 -16
  327. package/manifest/components/tool-call.json +37 -9
  328. package/manifest/components/tool-error.json +34 -8
  329. package/manifest/components/tool-group.json +17 -4
  330. package/manifest/components/tool-timeline.json +37 -9
  331. package/manifest/components/toolbar.json +37 -8
  332. package/manifest/components/tooltip.json +41 -7
  333. package/manifest/components/trace-waterfall.json +12 -3
  334. package/manifest/components/tree-visibility-toggle.json +18 -3
  335. package/manifest/components/tree.json +52 -11
  336. package/manifest/components/turn-footer.json +36 -8
  337. package/manifest/components/two-step-button.json +46 -9
  338. package/manifest/components/typing-indicator.json +8 -1
  339. package/manifest/components/universe-grid.json +37 -10
  340. package/manifest/components/unread-divider.json +4 -1
  341. package/manifest/components/usage-chart.json +46 -9
  342. package/manifest/components/verdict-row.json +20 -5
  343. package/manifest/components/voice-conversation.json +37 -7
  344. package/manifest/components/web-preview.json +23 -5
  345. package/manifest/components/web-search.json +20 -5
  346. package/manifest/components/work-collapse.json +30 -7
  347. package/manifest/fixtures.json +106 -0
  348. package/manifest/manifest.json +449 -310
  349. package/manifest/tokens.json +122 -12
  350. package/package.json +16 -6
  351. package/dist/theming/_presets.d.ts +0 -11
  352. package/dist/theming/_presets.js +0 -678
@@ -1,9 +1,20 @@
1
- import { DEFAULT_DENSITY, DEFAULT_FONT, DENSITIES, FONTS, MODES, THEMES } from "@cueplusplus/tokens";
1
+ import { vocabulary } from "./vocabulary.js";
2
+ import { DEFAULT_DENSITY, DEFAULT_FONT } from "@cueplusplus/tokens";
3
+ import { MODES } from "@cueplusplus/theme-base";
2
4
  //#region src/system/prepaint.ts
3
5
  /**
4
6
  * Pre-paint stamping. Server-safe by design: no `"use client"`, no React, no
5
7
  * DOM access at module scope — `app/layout.tsx` calls `prepaintScript()` on the
6
8
  * server and inlines the result before hydration.
9
+ *
10
+ * That is also why the vocabulary comes from `./vocabulary` and never from
11
+ * `./theme-registry`: the registry's React half carries a `"use client"`
12
+ * directive, and a module reached through a client boundary arrives in a server
13
+ * graph as a client *reference* rather than a function — calling one throws at
14
+ * prerender, in the app's root layout, where nothing has rendered yet.
15
+ * `test/system/server-safe.test.ts` pins the exact set of files this one
16
+ * reaches, so the next import that would break the rule fails a test instead of
17
+ * a deployment.
7
18
  */
8
19
  /** localStorage key the ThemeProvider and the pre-paint script share by default. */
9
20
  const DEFAULT_STORAGE_KEY = "cue-ui";
@@ -22,39 +33,80 @@ function prepaintScript(storageKeyOrOptions = DEFAULT_STORAGE_KEY, defaultTheme
22
33
  defaults: {
23
34
  theme: defaultTheme,
24
35
  density: defaultDensity,
25
- mode: "dark"
36
+ mode: "dark",
37
+ font: defaultFont
26
38
  },
27
39
  readStoredPreferences: true
28
40
  } : storageKeyOrOptions;
29
- if (options === null || typeof options !== "object" || options.defaults === void 0) throw new TypeError("prepaintScript options require a complete default triple");
41
+ if (options === null || typeof options !== "object" || options.defaults === void 0) throw new TypeError("prepaintScript options require a defaults object with theme and mode");
30
42
  const defaults = options.defaults;
31
- if (defaults.theme === void 0 || defaults.density === void 0 || defaults.mode === void 0) throw new TypeError("prepaintScript options require a complete default triple");
32
- if (!THEMES.includes(defaults.theme)) throw new TypeError("prepaintScript defaults contain an invalid theme");
33
- if (!DENSITIES.includes(defaults.density)) throw new TypeError("prepaintScript defaults contain an invalid density");
34
- if (!MODES.includes(defaults.mode)) throw new TypeError("prepaintScript defaults contain an invalid mode");
35
- if (defaults.font !== void 0 && !FONTS.includes(defaults.font)) throw new TypeError("prepaintScript defaults contain an invalid font");
43
+ if (defaults.theme === void 0 || defaults.mode === void 0) throw new TypeError("prepaintScript options require defaults.theme and defaults.mode");
44
+ const vocab = vocabulary(options.themes ?? []);
45
+ if (!vocab.acceptsTheme(defaults.theme)) throw new TypeError(vocab.themes === null ? `prepaintScript defaults.theme ${JSON.stringify(defaults.theme)} is not a valid theme name` : `prepaintScript defaults.theme ${JSON.stringify(defaults.theme)} is not a registered theme`);
46
+ if (defaults.density !== void 0 && !vocab.acceptsDensity(defaults.theme, defaults.density)) throw new TypeError(`prepaintScript defaults.density ${JSON.stringify(defaults.density)} is not offered by theme ${JSON.stringify(defaults.theme)}`);
47
+ if (!MODES.includes(defaults.mode)) throw new TypeError(`prepaintScript defaults.mode ${JSON.stringify(defaults.mode)} is not one of ${MODES.join(", ")}`);
48
+ if (defaults.font !== void 0 && !vocab.acceptsFont(defaults.theme, defaults.font)) throw new TypeError(`prepaintScript defaults.font ${JSON.stringify(defaults.font)} is not offered by theme ${JSON.stringify(defaults.theme)}`);
36
49
  if (options.readStoredPreferences !== void 0 && typeof options.readStoredPreferences !== "boolean") throw new TypeError("prepaintScript readStoredPreferences must be boolean");
37
50
  const storageKey = options.storageKey ?? "cue-ui";
38
51
  if (typeof storageKey !== "string") throw new TypeError("prepaintScript storageKey must be a string");
39
52
  const readStoredPreferences = options.readStoredPreferences ?? true;
40
53
  const key = jsStringLiteral(storageKey);
41
54
  const fallbackTheme = jsStringLiteral(defaults.theme);
42
- const fallbackDensity = jsStringLiteral(defaults.density);
55
+ const fallbackDensity = jsStringLiteral(defaults.density ?? vocab.preferredDensity(defaults.theme));
43
56
  const fallbackMode = jsStringLiteral(defaults.mode);
44
- const fallbackFont = jsStringLiteral(defaults.font ?? defaultFont);
45
- const themes = scriptSafe(JSON.stringify([...THEMES]));
46
- const densities = scriptSafe(JSON.stringify([...DENSITIES]));
47
- const modes = scriptSafe(JSON.stringify([...MODES]));
57
+ const fallbackFont = jsStringLiteral(defaults.font ?? vocab.preferredFont(defaults.theme));
58
+ const baseDensity = jsStringLiteral(DEFAULT_DENSITY);
59
+ const baseFont = jsStringLiteral(DEFAULT_FONT);
60
+ /**
61
+ * The registered names, or `null`.
62
+ *
63
+ * `null` tells the script "any well-formed name", which is the empty-registry
64
+ * rule (see `vocabulary`) and is what the positional overload — every app
65
+ * that has not migrated — gets. Anything else is the exact list the provider
66
+ * will validate against a moment later, inlined so the two cannot disagree.
67
+ */
68
+ const themes = scriptSafe(JSON.stringify(vocab.themes));
48
69
  /**
49
- * The pairings, from the package that authors them, for the same reason.
70
+ * The other two axes, `supportsLight`, and each theme's own preference, **per theme**.
50
71
  *
51
- * This is also the axis where an unvalidated read is worst: a stale or
52
- * hand-edited `font` would stamp an attribute the stylesheet has no block
53
- * for, `--cue-font-sans` would keep the base stack, and the page would render
54
- * in a family the picker says is not selected.
72
+ * Per theme, because a rung a theme adds is scoped by the cascade to that
73
+ * theme's `data-theme`; accepting it under another theme stamps an attribute
74
+ * no block will match, and the page renders at the rung it fell back from
75
+ * while the picker says otherwise. The keys are short (`d`, `f`, `l`, `p`,
76
+ * `q`) because this is inlined into every document this app serves, once per
77
+ * response, and with ten themes registered the long names would cost more
78
+ * bytes than the whole rest of the script.
79
+ *
80
+ * `p` and `q` are §5's `densities.default` / `fontPairings.default` — the
81
+ * rung and the pairing that theme prefers when nobody named one — read
82
+ * through `vocabulary()`, which is the same function `acceptedPreferences()`
83
+ * asks on the client. Two implementations of one rule, one of them written
84
+ * out as a string of JavaScript, is exactly the asymmetry the round-trip
85
+ * tests exist to catch; inlining the *answers* rather than re-deriving them
86
+ * is what makes the two sides agree by construction.
87
+ *
88
+ * They are omitted when they equal the base default, which is what every one
89
+ * of the ten shipped presets does — none of them declares either. The final
90
+ * clause already ends at the base literal, so an entry that repeats it is
91
+ * bytes in every response that change no answer. `a.p&&…` below is what
92
+ * reads an absent key as "no opinion".
93
+ *
94
+ * With nothing registered there is one entry under the empty key: the base
95
+ * ladder, the base pairings, and a light block, which the blank base has.
55
96
  */
56
- const pairings = scriptSafe(JSON.stringify([...FONTS]));
57
- 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}.indexOf(s.theme)>-1?s.theme:${fallbackTheme};var d=${densities}.indexOf(s.density)>-1?s.density:${fallbackDensity};var f=${pairings}.indexOf(s.font)>-1?s.font:${fallbackFont};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=m==="system"?"light dark":m}catch(_){}}()`;
97
+ const axes = scriptSafe(JSON.stringify(Object.fromEntries((vocab.themes ?? [null]).map((name) => {
98
+ const preferredDensity = vocab.preferredDensity(name);
99
+ const preferredFont = vocab.preferredFont(name);
100
+ return [name ?? "", {
101
+ d: vocab.densitiesFor(name),
102
+ f: vocab.fontsFor(name),
103
+ l: vocab.supportsLight(name),
104
+ ...preferredDensity === DEFAULT_DENSITY ? {} : { p: preferredDensity },
105
+ ...preferredFont === DEFAULT_FONT ? {} : { q: preferredFont }
106
+ }];
107
+ }))));
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(_){}}()`;
58
110
  }
59
111
  //#endregion
60
112
  export { DEFAULT_STORAGE_KEY, prepaintScript };
@@ -1,5 +1,6 @@
1
+ import { TokenOverrides } from "./overrides.js";
1
2
  import * as React from "react";
2
- import { Density, FontName, Mode, ThemeName } from "@cueplusplus/tokens";
3
+ import { Density, FontName, Mode, ThemeManifest, ThemeName } from "@cueplusplus/theme-base";
3
4
  //#region src/system/theme-provider.d.ts
4
5
  /** A mode that has been resolved to an actual palette — `"system"` never survives this far. */
5
6
  type ResolvedMode = "dark" | "light";
@@ -7,6 +8,12 @@ type ResolvedMode = "dark" | "light";
7
8
  interface ThemeContextValue {
8
9
  /** Active theme preset. */
9
10
  theme: ThemeName;
11
+ /**
12
+ * The active theme's manifest, or `null` when the active name is not
13
+ * registered — which is every name in an app that passes no `themes`, and the
14
+ * one a `setTheme` for an unknown preset leaves behind.
15
+ */
16
+ manifest: ThemeManifest | null;
10
17
  /** Density the *provider* holds — a nested `<Density>` island does not change it. */
11
18
  density: Density;
12
19
  /** Mode as chosen, including the literal `"system"`. */
@@ -32,15 +39,66 @@ interface FontFamilies {
32
39
  mono?: string;
33
40
  }
34
41
  interface ThemeProviderProps {
35
- /** Initial theme preset. Defaults to `"cue"`. Changing it after mount adopts the new value. */
42
+ /**
43
+ * The registry: an ordered array of theme manifests, the first of which is
44
+ * the default theme.
45
+ *
46
+ * A prop rather than a side effect, so the server render and the client
47
+ * render are handed the same array and nothing depends on which module an app
48
+ * happened to import first. Each entry is a `manifest.json` from a
49
+ * `@cueplusplus/theme-<name>` package; this library never discovers a theme by
50
+ * itself, because which themes a product ships is an application decision.
51
+ *
52
+ * Optional for one release, defaulting to `[]` with a development warning. An
53
+ * app that registers nothing gets the base axes and no registry: any
54
+ * well-formed name is accepted as a theme (so a visitor's stored preference
55
+ * still restores on first paint), density and font validate against the base
56
+ * ladder and pairings, and `useTheme().manifest` is `null`. What it *paints*
57
+ * is a separate question with a separate answer — whichever `[data-theme]`
58
+ * blocks its stylesheets declare, which is the blank base only when none of
59
+ * them declares the stamped name.
60
+ *
61
+ * A nested provider that passes none inherits the ambient registry.
62
+ *
63
+ * One disclosure to be aware of: {@link prepaintScript} inlines the
64
+ * registered **names** — and each theme's `supportsLight` flag, and the names
65
+ * of any rung or pairing it adds — into the blocking script in every page's
66
+ * HTML, because first paint has to judge a stored preference before any
67
+ * module loads. No colours, no geometry, no package names, and only what an
68
+ * app passes here. But an app that registers
69
+ * a per-customer theme is publishing that customer's name to every visitor
70
+ * who views source, so register per-customer themes per response rather than
71
+ * globally.
72
+ */
73
+ themes?: readonly ThemeManifest[];
74
+ /**
75
+ * Initial theme preset. Defaults to the first registered manifest's name, or
76
+ * `"cue"` with nothing registered. Changing it after mount adopts the new value.
77
+ */
36
78
  theme?: ThemeName;
37
- /** Initial density level. Defaults to `"compact"`. */
79
+ /**
80
+ * Initial density level.
81
+ *
82
+ * Defaults to the rung the active theme's manifest names in
83
+ * `densities.default`, and to `"compact"` when it names none — spec §5's
84
+ * rule, and the same one `resolve()` applies when no rung is wanted, so a
85
+ * theme opens on the rung it prefers whether it is read from JS or painted
86
+ * by its own stylesheet. Naming a rung here overrides that for every theme:
87
+ * a consumer who named one named it deliberately.
88
+ *
89
+ * Changing it after mount adopts the new value; dropping it does not — a
90
+ * parent that stops naming a rung is not asking to move back to the theme's.
91
+ */
38
92
  density?: Density;
39
93
  /** Initial mode. Defaults to `"dark"`; `"system"` tracks `prefers-color-scheme`. */
40
94
  mode?: Mode;
41
95
  /**
42
- * Initial font pairing. Defaults to `"system"` — the platform's own faces,
43
- * nothing downloaded, and the theme keeps whatever monospace it authored.
96
+ * Initial font pairing.
97
+ *
98
+ * Defaults to the pairing the active theme's manifest names in
99
+ * `fontPairings.default` — §5's rule, the density prop's rule one axis over
100
+ * — and to `"system"` when it names none: the platform's own faces, nothing
101
+ * downloaded, and the theme keeps whatever monospace it authored.
44
102
  *
45
103
  * A pairing that needs delivering is a set of *names*: this library ships no
46
104
  * font files, and a pairing nobody delivers falls through its stack to the
@@ -58,6 +116,66 @@ interface ThemeProviderProps {
58
116
  fontScale?: number;
59
117
  /** Optional app-owned font stacks, published as inline CUE font custom properties. */
60
118
  fontFamilies?: FontFamilies;
119
+ /**
120
+ * Typed token edits layered over the active theme: colours per mode, geometry
121
+ * per rung, and the three font stacks.
122
+ *
123
+ * For the band of edits that sit below "publish a theme package": one accent
124
+ * for a tenant, a stack the app already loads, a rung with two more pixels in
125
+ * a touch build. Anything larger belongs in a `@cueplusplus/theme-<name>`
126
+ * package, where a build measures it; anything smaller than this is a
127
+ * `!important` in a stray stylesheet, which outranks the token layer
128
+ * everywhere at once.
129
+ *
130
+ * **Colours and fonts are inline on this element**, so they inherit down the
131
+ * subtree, beat every stylesheet without `!important`, and are re-stamped
132
+ * onto portal containers — which mount on `<body>` and inherit nothing from
133
+ * here. They do not leak *out* of this provider: a nested provider's colour
134
+ * edit is scoped to its own subtree, and to portals opened from inside it.
135
+ *
136
+ * **Densities are one document-scoped `<style>` element**, because a rung is
137
+ * selected by attribute rather than inherited, and because
138
+ * `useControlHeight()`'s measuring probe hangs off `document.body`, where
139
+ * a subtree-scoped rule would not reach it — leaving the measured height and
140
+ * the painted control disagreeing by exactly the override. The consequence is
141
+ * worth knowing before you nest one: a nested provider's `densities` edit
142
+ * reaches the whole page, exactly as a theme's own rung rules do. Colours and
143
+ * fonts in the same object do not.
144
+ *
145
+ * And it cuts the other way as well, which is the half that surprises: two
146
+ * providers editing the same rung produce two document-scoped rules of equal
147
+ * specificity, so the **later** one in document order wins everywhere — and
148
+ * the later one is the outer provider's, because a nested provider renders
149
+ * inside it. A nested `densities` edit does not merely leak out; an ancestor
150
+ * that edits the same rung overrules it *inside the nested subtree too*. If
151
+ * an island needs geometry of its own, it needs a rung of its own — a name no
152
+ * ancestor is editing — not the same rung with different numbers.
153
+ *
154
+ * Its rules are `[data-density="x"][data-density="x"]` — (0,2,0), tying a
155
+ * theme's own `[data-theme="t"] [data-density="x"]` and winning on source
156
+ * order — so the precedence is: **`overrides`, then the configurator's
157
+ * persisted snapshot, then the theme, then base.**
158
+ *
159
+ * **A font override shadows the pairing for this subtree.** `data-font` is
160
+ * the document's axis and this element deliberately never restates it (see
161
+ * the note by `stamp` below), but `overrides.fonts` writes the resolved
162
+ * `--cue-font-*` properties directly, which is a stronger claim than the
163
+ * attribute and is the point: this is how an app says "this product's face,
164
+ * whatever pairing the visitor picked".
165
+ *
166
+ * In development the resulting palette is measured, and any required contrast
167
+ * pair the edit broke — or made worse — is named on the console.
168
+ *
169
+ * @example
170
+ * <ThemeProvider themes={[cue]} overrides={{
171
+ * colors: { dark: { accent: "#ff8800" }, light: { accent: "#b35900" } },
172
+ * densities: { compact: { "control-md": "1.75rem" } },
173
+ * fonts: { sans: '"Tenant Sans", ui-sans-serif, sans-serif' },
174
+ * }}>
175
+ * <App />
176
+ * </ThemeProvider>
177
+ */
178
+ overrides?: TokenOverrides;
61
179
  /**
62
180
  * localStorage key holding `{ theme, density, font, mode }`. Defaults to `"cue-ui"`.
63
181
  * Only the outermost provider reads or writes it — a nested provider is an
@@ -78,7 +196,8 @@ interface ThemeProviderProps {
78
196
  /**
79
197
  * Root of the theme system: stamps `data-theme`, `data-density` and `data-mode`
80
198
  * so the token layer resolves, puts `data-font` on `<html>`, publishes
81
- * `--cue-font-scale`, and owns the persisted user preference.
199
+ * `--cue-font-scale`, owns the persisted user preference, and publishes the
200
+ * registry the three axis hooks read.
82
201
  *
83
202
  * Rendering: `<div data-cue-root data-theme data-density data-mode style={{ colorScheme, --cue-font-scale }}>`
84
203
  * (or the single child when `asChild`). The `theme`/`density`/`font`/`mode` props are
@@ -100,10 +219,16 @@ interface ThemeProviderProps {
100
219
  * font pairing — `useTheme().font` and `setFont` inside one are the root's.
101
220
  *
102
221
  * @example
103
- * <ThemeProvider theme="terminal" density="ultra-compact" font="plex" mode="system">
222
+ * // `cue` and `terminal` are the `manifest.json` each theme package ships, read
223
+ * // from the `…/manifest.json` subpath of `@cueplusplus/theme-cue` and
224
+ * // `@cueplusplus/theme-terminal`. Written that way round on purpose: a literal
225
+ * // import statement in this comment reads, to every import-graph gate in this
226
+ * // repository, as `ui` depending on a theme package — which is the one thing
227
+ * // it may not do.
228
+ * <ThemeProvider themes={[cue, terminal]} theme="terminal" density="ultra-compact" font="plex" mode="system">
104
229
  * <App />
105
230
  * </ThemeProvider>
106
231
  */
107
- declare function ThemeProvider({ theme: themeProp, density: densityProp, mode: modeProp, font: fontProp, fontScale, fontFamilies, storageKey, persistPreferences, asChild, className, style: styleProp, children }: ThemeProviderProps): React.JSX.Element;
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;
108
233
  //#endregion
109
234
  export { FontFamilies, ResolvedMode, ThemeContextValue, ThemeProvider, ThemeProviderProps };