@cueplusplus/ui 0.9.0 → 0.11.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 (330) hide show
  1. package/CHANGELOG.md +337 -0
  2. package/README.md +197 -9
  3. package/dist/chrome/index.d.ts +2 -1
  4. package/dist/chrome/index.js +3 -2
  5. package/dist/chrome/inspector.d.ts +61 -0
  6. package/dist/chrome/inspector.js +121 -0
  7. package/dist/forms/input.d.ts +12 -0
  8. package/dist/forms/textarea.d.ts +12 -0
  9. package/dist/forms/toggle.d.ts +6 -0
  10. package/dist/index.d.ts +7 -3
  11. package/dist/index.js +8 -3
  12. package/dist/instruments/code-block.d.ts +35 -0
  13. package/dist/instruments/code-block.js +173 -0
  14. package/dist/instruments/index.d.ts +2 -1
  15. package/dist/instruments/index.js +2 -1
  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 +4 -2
  20. package/dist/layout/index.js +4 -2
  21. package/dist/layout/preview.d.ts +110 -0
  22. package/dist/layout/preview.js +459 -0
  23. package/dist/primitives/button.d.ts +12 -0
  24. package/dist/primitives/chip.d.ts +1 -1
  25. package/dist/system/index.d.ts +2 -2
  26. package/dist/system/index.js +2 -1
  27. package/dist/system/overrides.d.ts +38 -1
  28. package/dist/system/overrides.js +40 -1
  29. package/manifest/components/accordion.json +24 -6
  30. package/manifest/components/activity-graph.json +20 -5
  31. package/manifest/components/agent-card.json +37 -9
  32. package/manifest/components/agent-handoff.json +21 -5
  33. package/manifest/components/agent-mode-badge.json +22 -4
  34. package/manifest/components/agent-pile.json +38 -8
  35. package/manifest/components/agent-plan.json +9 -2
  36. package/manifest/components/agent-status.json +17 -3
  37. package/manifest/components/agent-surface.json +26 -11
  38. package/manifest/components/alert-dialog.json +29 -5
  39. package/manifest/components/animated-number.json +14 -6
  40. package/manifest/components/app-bar.json +66 -13
  41. package/manifest/components/app-shell.json +133 -31
  42. package/manifest/components/app-window-frame.json +28 -6
  43. package/manifest/components/approval-card.json +37 -7
  44. package/manifest/components/artifact-card.json +16 -4
  45. package/manifest/components/ask-box.json +99 -21
  46. package/manifest/components/audience-icon.json +1 -7
  47. package/manifest/components/autocomplete.json +80 -18
  48. package/manifest/components/avatar-group.json +13 -2
  49. package/manifest/components/avatar.json +22 -4
  50. package/manifest/components/background-inbox.json +9 -2
  51. package/manifest/components/bento.json +306 -0
  52. package/manifest/components/branch-picker.json +29 -7
  53. package/manifest/components/breadcrumb.json +13 -3
  54. package/manifest/components/button-group.json +21 -4
  55. package/manifest/components/button.json +41 -4
  56. package/manifest/components/canvas-split-body.json +4 -1
  57. package/manifest/components/canvas-split-header.json +17 -4
  58. package/manifest/components/canvas-split-line.json +4 -1
  59. package/manifest/components/canvas-split-message.json +8 -1
  60. package/manifest/components/card.json +14 -3
  61. package/manifest/components/carousel.json +54 -12
  62. package/manifest/components/catalogue-icon.json +1 -7
  63. package/manifest/components/channel-beta-icon.json +1 -7
  64. package/manifest/components/channel-matrix.json +80 -15
  65. package/manifest/components/channel-released-icon.json +1 -7
  66. package/manifest/components/chart-container.json +26 -10
  67. package/manifest/components/chart-ramp.json +13 -3
  68. package/manifest/components/chart-swatch.json +9 -2
  69. package/manifest/components/chart-tooltip-content.json +31 -7
  70. package/manifest/components/chart.json +30 -6
  71. package/manifest/components/chat-panel-composer.json +9 -2
  72. package/manifest/components/checkbox-group.json +4 -2
  73. package/manifest/components/checkbox.json +17 -4
  74. package/manifest/components/checkpoint-history.json +13 -3
  75. package/manifest/components/chip.json +35 -5
  76. package/manifest/components/clamp.json +30 -7
  77. package/manifest/components/cli-tool-icon.json +1 -7
  78. package/manifest/components/code-block.json +106 -0
  79. package/manifest/components/code-diff.json +20 -5
  80. package/manifest/components/code-runner.json +32 -6
  81. package/manifest/components/collapsible.json +16 -4
  82. package/manifest/components/color-area.json +4 -1
  83. package/manifest/components/color-field.json +13 -3
  84. package/manifest/components/color-picker.json +75 -17
  85. package/manifest/components/color-slider.json +12 -3
  86. package/manifest/components/color-swatch.json +13 -2
  87. package/manifest/components/colors-section.json +8 -4
  88. package/manifest/components/combobox.json +81 -18
  89. package/manifest/components/command-palette.json +57 -13
  90. package/manifest/components/compaction-row.json +35 -7
  91. package/manifest/components/comparison-card.json +17 -4
  92. package/manifest/components/composer-attachment-chip.json +9 -2
  93. package/manifest/components/composer-bar.json +4 -1
  94. package/manifest/components/composer-command-item.json +8 -2
  95. package/manifest/components/composer-context.json +4 -1
  96. package/manifest/components/composer-input.json +5 -1
  97. package/manifest/components/composer-menu-item.json +4 -1
  98. package/manifest/components/composer-menu.json +12 -2
  99. package/manifest/components/composer-model-item.json +8 -2
  100. package/manifest/components/composer-model-trigger.json +8 -2
  101. package/manifest/components/composer-person-item.json +8 -2
  102. package/manifest/components/composer-send.json +8 -2
  103. package/manifest/components/composer-voice-button.json +4 -1
  104. package/manifest/components/composer-voice.json +8 -2
  105. package/manifest/components/composer.json +76 -17
  106. package/manifest/components/computer-use.json +17 -4
  107. package/manifest/components/confidence-marker.json +13 -3
  108. package/manifest/components/connection-state.json +23 -4
  109. package/manifest/components/container.json +14 -2
  110. package/manifest/components/context-breakdown.json +8 -2
  111. package/manifest/components/context-menu.json +16 -4
  112. package/manifest/components/context-usage.json +16 -4
  113. package/manifest/components/conversation-search.json +22 -5
  114. package/manifest/components/copy-button.json +32 -7
  115. package/manifest/components/cost-meter.json +12 -3
  116. package/manifest/components/cue-logotype.json +17 -3
  117. package/manifest/components/cue-mark.json +17 -3
  118. package/manifest/components/cue-portal-frame.json +13 -3
  119. package/manifest/components/data-row.json +21 -7
  120. package/manifest/components/data-table-pagination.json +18 -4
  121. package/manifest/components/data-table-toolbar.json +30 -7
  122. package/manifest/components/data-tree.json +42 -9
  123. package/manifest/components/date-field.json +13 -2
  124. package/manifest/components/date-picker.json +81 -19
  125. package/manifest/components/date-range-picker.json +81 -19
  126. package/manifest/components/day-separator.json +4 -1
  127. package/manifest/components/delegation-card.json +64 -12
  128. package/manifest/components/density.json +24 -4
  129. package/manifest/components/description-list.json +8 -1
  130. package/manifest/components/diagram.json +33 -7
  131. package/manifest/components/dialog.json +29 -6
  132. package/manifest/components/disclosure.json +31 -7
  133. package/manifest/components/dmx-bar.json +16 -4
  134. package/manifest/components/dmx-strip.json +20 -5
  135. package/manifest/components/document-reference.json +21 -5
  136. package/manifest/components/draft-restore.json +18 -4
  137. package/manifest/components/drawer.json +30 -6
  138. package/manifest/components/dropdown-menu.json +55 -10
  139. package/manifest/components/edit-message.json +32 -7
  140. package/manifest/components/elements-command-palette.json +27 -6
  141. package/manifest/components/elements-data-table.json +9 -2
  142. package/manifest/components/elements-timeline.json +8 -2
  143. package/manifest/components/elicitation-form.json +31 -6
  144. package/manifest/components/empty-state-composer.json +9 -2
  145. package/manifest/components/empty-state-suggestion.json +4 -1
  146. package/manifest/components/empty-state.json +25 -5
  147. package/manifest/components/end-of-turn-summary.json +16 -4
  148. package/manifest/components/env-var-input.json +54 -12
  149. package/manifest/components/error-state.json +17 -4
  150. package/manifest/components/export-dialog.json +27 -8
  151. package/manifest/components/eyebrow.json +4 -1
  152. package/manifest/components/feedback-dialog.json +33 -7
  153. package/manifest/components/field-description.json +4 -1
  154. package/manifest/components/field-error.json +13 -3
  155. package/manifest/components/field-label.json +4 -1
  156. package/manifest/components/field.json +4 -1
  157. package/manifest/components/file-tree.json +16 -4
  158. package/manifest/components/file-upload.json +58 -13
  159. package/manifest/components/flow-graph.json +12 -3
  160. package/manifest/components/folder-icon.json +1 -7
  161. package/manifest/components/footer.json +24 -4
  162. package/manifest/components/frac.json +10 -2
  163. package/manifest/components/generation-loader.json +17 -3
  164. package/manifest/components/generative-ui.json +39 -7
  165. package/manifest/components/grid.json +56 -5
  166. package/manifest/components/group-bar.json +25 -6
  167. package/manifest/components/guardrail-notice.json +22 -5
  168. package/manifest/components/hover-card.json +45 -8
  169. package/manifest/components/icon-button.json +22 -4
  170. package/manifest/components/image-generation.json +8 -2
  171. package/manifest/components/info-tip.json +42 -7
  172. package/manifest/components/inline-citation.json +14 -3
  173. package/manifest/components/input-group.json +24 -5
  174. package/manifest/components/input.json +33 -3
  175. package/manifest/components/inspector.json +199 -0
  176. package/manifest/components/item.json +42 -8
  177. package/manifest/components/job-progress.json +25 -6
  178. package/manifest/components/launcher-bubble.json +32 -7
  179. package/manifest/components/ledger.json +87 -19
  180. package/manifest/components/link.json +16 -3
  181. package/manifest/components/live-region-announcer.json +21 -5
  182. package/manifest/components/log-viewer.json +42 -10
  183. package/manifest/components/map-answer.json +17 -4
  184. package/manifest/components/markdown-text.json +36 -5
  185. package/manifest/components/math-block.json +12 -3
  186. package/manifest/components/mcp-server-icon.json +1 -7
  187. package/manifest/components/mcp-server-panel.json +18 -4
  188. package/manifest/components/memory-chips.json +9 -2
  189. package/manifest/components/menubar.json +33 -7
  190. package/manifest/components/message-actions.json +32 -7
  191. package/manifest/components/message-attachments.json +9 -2
  192. package/manifest/components/message-branches.json +14 -3
  193. package/manifest/components/message-list.json +27 -6
  194. package/manifest/components/message-pair.json +25 -5
  195. package/manifest/components/message-queue.json +13 -3
  196. package/manifest/components/message-timing.json +8 -2
  197. package/manifest/components/message.json +41 -9
  198. package/manifest/components/meter.json +43 -7
  199. package/manifest/components/mobile-composer.json +47 -10
  200. package/manifest/components/model-picker.json +13 -3
  201. package/manifest/components/multi-select.json +78 -17
  202. package/manifest/components/musical-time-input.json +34 -8
  203. package/manifest/components/navigation-menu.json +54 -12
  204. package/manifest/components/node-card.json +42 -7
  205. package/manifest/components/node-handle.json +17 -2
  206. package/manifest/components/number-field.json +28 -6
  207. package/manifest/components/number-ticker.json +8 -2
  208. package/manifest/components/onboarding.json +18 -4
  209. package/manifest/components/otp-field.json +43 -10
  210. package/manifest/components/page-shell.json +35 -6
  211. package/manifest/components/pagination.json +21 -5
  212. package/manifest/components/panel-header.json +15 -3
  213. package/manifest/components/password-input.json +58 -8
  214. package/manifest/components/permission-grant.json +28 -5
  215. package/manifest/components/permission-scopes.json +57 -12
  216. package/manifest/components/piano-keyboard.json +38 -9
  217. package/manifest/components/plussie.json +34 -5
  218. package/manifest/components/popover.json +45 -8
  219. package/manifest/components/preset-section.json +12 -5
  220. package/manifest/components/preview.json +202 -0
  221. package/manifest/components/progress.json +25 -6
  222. package/manifest/components/prompt-library.json +27 -6
  223. package/manifest/components/queue-dock.json +36 -8
  224. package/manifest/components/quota-banner.json +25 -6
  225. package/manifest/components/quote-reply.json +29 -7
  226. package/manifest/components/radio-group.json +12 -2
  227. package/manifest/components/radio.json +17 -3
  228. package/manifest/components/rating.json +60 -13
  229. package/manifest/components/read-aloud.json +35 -8
  230. package/manifest/components/reasoning-effort.json +17 -4
  231. package/manifest/components/reasoning-panel.json +33 -8
  232. package/manifest/components/recommendation-card.json +35 -10
  233. package/manifest/components/regenerate-menu.json +22 -5
  234. package/manifest/components/replay-player.json +29 -7
  235. package/manifest/components/research-report.json +12 -3
  236. package/manifest/components/resizable.json +4 -1
  237. package/manifest/components/retrieval-chunks.json +16 -4
  238. package/manifest/components/revert-dock.json +61 -14
  239. package/manifest/components/reviewable-diff.json +23 -5
  240. package/manifest/components/risk-badge.json +18 -3
  241. package/manifest/components/row.json +17 -4
  242. package/manifest/components/schedule-card.json +25 -6
  243. package/manifest/components/score-breakdown.json +20 -5
  244. package/manifest/components/scroll-anchor.json +13 -3
  245. package/manifest/components/scroll-area.json +31 -8
  246. package/manifest/components/scrollable-tabs-list.json +12 -4
  247. package/manifest/components/scrub-input.json +84 -18
  248. package/manifest/components/seam-cell.json +19 -3
  249. package/manifest/components/seam-grid.json +12 -3
  250. package/manifest/components/search-input.json +69 -10
  251. package/manifest/components/section-header.json +36 -8
  252. package/manifest/components/segmented-control.json +50 -11
  253. package/manifest/components/select.json +74 -16
  254. package/manifest/components/separator.json +4 -1
  255. package/manifest/components/settings-panel.json +41 -9
  256. package/manifest/components/shape-section.json +8 -4
  257. package/manifest/components/shared-conversation.json +21 -5
  258. package/manifest/components/sheet.json +29 -6
  259. package/manifest/components/shimmer-label.json +4 -2
  260. package/manifest/components/sidebar.json +60 -13
  261. package/manifest/components/skill-icon.json +1 -7
  262. package/manifest/components/slider.json +22 -5
  263. package/manifest/components/sources.json +17 -4
  264. package/manifest/components/sparkline.json +17 -4
  265. package/manifest/components/speaker-identity.json +4 -1
  266. package/manifest/components/spec-sheet.json +16 -4
  267. package/manifest/components/spectrum-visualizer.json +41 -10
  268. package/manifest/components/spinner.json +12 -2
  269. package/manifest/components/stack-icon.json +1 -7
  270. package/manifest/components/stack.json +54 -7
  271. package/manifest/components/stacks-matrix-icon.json +1 -7
  272. package/manifest/components/stat.json +28 -4
  273. package/manifest/components/status-bar.json +50 -9
  274. package/manifest/components/status-dot.json +23 -4
  275. package/manifest/components/stepper.json +22 -5
  276. package/manifest/components/stopped-run.json +19 -4
  277. package/manifest/components/streaming-text.json +12 -3
  278. package/manifest/components/subagent-list.json +21 -5
  279. package/manifest/components/suggestions.json +27 -5
  280. package/manifest/components/swap-label.json +18 -4
  281. package/manifest/components/switch.json +18 -3
  282. package/manifest/components/table-scroll-region.json +4 -1
  283. package/manifest/components/table.json +54 -11
  284. package/manifest/components/tabs.json +16 -5
  285. package/manifest/components/tags-input.json +79 -17
  286. package/manifest/components/tail-status.json +37 -8
  287. package/manifest/components/terminal-block.json +25 -5
  288. package/manifest/components/terminal-frame.json +34 -8
  289. package/manifest/components/textarea.json +28 -3
  290. package/manifest/components/theme-configurator.json +47 -11
  291. package/manifest/components/theme-provider.json +79 -15
  292. package/manifest/components/thinking-indicator.json +8 -2
  293. package/manifest/components/thread-list.json +13 -3
  294. package/manifest/components/thread-search.json +22 -5
  295. package/manifest/components/threshold-rail.json +77 -14
  296. package/manifest/components/time-boundary.json +8 -2
  297. package/manifest/components/time-field.json +13 -2
  298. package/manifest/components/timeline-ruler.json +51 -12
  299. package/manifest/components/timeline.json +19 -3
  300. package/manifest/components/title-bar.json +19 -4
  301. package/manifest/components/toast.json +12 -3
  302. package/manifest/components/todo-list.json +8 -2
  303. package/manifest/components/toggle-group.json +33 -7
  304. package/manifest/components/toggle.json +33 -4
  305. package/manifest/components/token-editor.json +26 -7
  306. package/manifest/components/tool-call-card.json +83 -16
  307. package/manifest/components/tool-call.json +37 -9
  308. package/manifest/components/tool-error.json +34 -8
  309. package/manifest/components/tool-group.json +17 -4
  310. package/manifest/components/tool-timeline.json +37 -9
  311. package/manifest/components/toolbar.json +37 -8
  312. package/manifest/components/tooltip.json +41 -7
  313. package/manifest/components/trace-waterfall.json +12 -3
  314. package/manifest/components/tree-visibility-toggle.json +18 -3
  315. package/manifest/components/tree.json +52 -11
  316. package/manifest/components/turn-footer.json +36 -8
  317. package/manifest/components/two-step-button.json +46 -9
  318. package/manifest/components/typing-indicator.json +8 -1
  319. package/manifest/components/universe-grid.json +37 -10
  320. package/manifest/components/unread-divider.json +4 -1
  321. package/manifest/components/usage-chart.json +46 -9
  322. package/manifest/components/verdict-row.json +20 -5
  323. package/manifest/components/voice-conversation.json +37 -7
  324. package/manifest/components/web-preview.json +23 -5
  325. package/manifest/components/web-search.json +20 -5
  326. package/manifest/components/work-collapse.json +30 -7
  327. package/manifest/fixtures.json +111 -0
  328. package/manifest/manifest.json +354 -297
  329. package/manifest/tokens.json +2 -2
  330. package/package.json +8 -7
@@ -0,0 +1,459 @@
1
+ "use client";
2
+ import { cn } from "../lib/cn.js";
3
+ import { useIsomorphicLayoutEffect } from "../system/use-isomorphic-layout-effect.js";
4
+ import { AmbientThemeContext, DensityContext } from "../system/density.js";
5
+ import { OverridesContext } from "../system/overrides.js";
6
+ import { ThemeContext } from "../system/theme-provider.js";
7
+ import * as React from "react";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ //#region src/layout/preview.tsx
10
+ const RESIZE_DEFAULTS = {
11
+ axis: "both",
12
+ minWidth: 160,
13
+ maxWidth: 1280,
14
+ minHeight: 96,
15
+ maxHeight: 960
16
+ };
17
+ /** One arrow press, in px, and one Shift+arrow press. */
18
+ const STEP = 8;
19
+ const BIG_STEP = 64;
20
+ const ROOT_CLASS = "relative flex min-w-0 flex-col rounded-(--radius-surface) border border-border bg-surface-1 text-fg";
21
+ const BENCH_CLASS = "relative flex min-w-0 flex-1 overflow-auto rounded-t-(--radius-surface)";
22
+ /**
23
+ * The stage: centred, dotted in the stage's own `--cue-hair-strong`, and at
24
+ * least three top rungs of the space ladder tall so a preview reserves its room
25
+ * before its specimen arrives. Static literals, because Tailwind scans text.
26
+ *
27
+ * Centred **safely** (`place-items: safe center`): a specimen wider than the
28
+ * stage starts at the stage's edge and overflows at its end, where the bench
29
+ * scrolls to it. Plain `center` split the overflow across both sides, and the
30
+ * left half sat before the scroll origin, where nothing reaches it.
31
+ */
32
+ const STAGE_CLASS = "grid h-full w-full min-h-[calc(var(--cue-space-8)*3)] place-items-center-safe bg-surface-1 bg-[radial-gradient(circle,var(--cue-hair-strong)_1px,transparent_1.3px)] bg-[length:14px_14px] p-(--cue-space-6) text-fg";
33
+ const TOOLS_CLASS = "absolute top-(--cue-space-2) right-(--cue-space-2) z-10 flex items-center gap-px rounded-(--radius-control) border border-border bg-surface-2 p-px";
34
+ /**
35
+ * The room the tools take, kept clear on the stage — only when there are tools.
36
+ *
37
+ * The tools sit over the stage's top right, so a specimen that reached that
38
+ * corner sat under them, and its own controls there with it: the index's
39
+ * Calendar paged by an arrow the wrench was drawn over. So a Preview with tools
40
+ * pads its stage's top by the tools' whole depth — their offset, their height
41
+ * (an `sm` control, plus the one-pixel border and padding on each side), and one
42
+ * rung of space below them.
43
+ *
44
+ * **Declared on the bench and read on the stage**, because the two sit in
45
+ * different densities: the tools are drawn in the page's, and the stage may set
46
+ * its own. A custom property's `var()`s are resolved where it is declared, so
47
+ * the stage inherits the depth measured at the page's rung — a compact stage
48
+ * still clears tools drawn at the page's size. And **only with tools**: a bare
49
+ * Preview carries none of it, which is the one the docs' front page draws
50
+ * seventeen times under a byte ceiling.
51
+ */
52
+ const TOOLS_RESERVE_BENCH = "[--preview-tools-clear:calc(var(--cue-space-2)*2+var(--cue-control-sm)+4px)]";
53
+ const TOOLS_RESERVE_STAGE = "pt-(--preview-tools-clear)";
54
+ const FOOTER_CLASS = "flex min-w-0 items-center gap-(--cue-space-2) border-t border-border px-(--cue-space-4) py-(--cue-space-2)";
55
+ const GRIP_FOCUS = "touch-none outline-none hover:bg-(--cue-border-strong) focus-visible:bg-accent";
56
+ const GRIP_X_CLASS = `absolute top-0 right-0 z-10 h-full w-(--cue-space-2) cursor-col-resize ${GRIP_FOCUS}`;
57
+ const GRIP_Y_CLASS = `absolute bottom-0 left-0 z-10 h-(--cue-space-2) w-full cursor-row-resize ${GRIP_FOCUS}`;
58
+ const GRIP_CORNER_CLASS = "absolute right-0 bottom-0 z-20 size-(--cue-space-4) cursor-nwse-resize touch-none";
59
+ const READOUT_CLASS = "pointer-events-none absolute right-(--cue-space-5) bottom-(--cue-space-5) z-20 rounded-(--radius-control) bg-surface-3 px-(--cue-space-2) py-px font-mono text-(length:--cue-text-label) text-fg-muted";
60
+ function clamp(value, min, max) {
61
+ return Math.min(max, Math.max(min, value));
62
+ }
63
+ /** The bounds a `resizable` prop means, or `null` for a fixed stage. */
64
+ function resolveResize(resizable) {
65
+ if (resizable === void 0 || resizable === false) return null;
66
+ const given = resizable === true ? {} : resizable;
67
+ return {
68
+ axis: given.axis ?? RESIZE_DEFAULTS.axis,
69
+ minWidth: given.minWidth ?? RESIZE_DEFAULTS.minWidth,
70
+ maxWidth: given.maxWidth ?? RESIZE_DEFAULTS.maxWidth,
71
+ minHeight: given.minHeight ?? RESIZE_DEFAULTS.minHeight,
72
+ maxHeight: given.maxHeight ?? RESIZE_DEFAULTS.maxHeight
73
+ };
74
+ }
75
+ /**
76
+ * The room the stage has: the bench's inline size in whole px, measured at
77
+ * mount and again whenever a `ResizeObserver` says the bench changed — the
78
+ * viewport resizing, a grid track reflowing around it.
79
+ *
80
+ * `null` means "no bound from the room". That covers a fixed Preview, which
81
+ * measures and observes nothing, so a frame without `resizable` costs no
82
+ * observer. It covers a bench that measures 0, which is not laid out
83
+ * (`display: none`, detached, or jsdom). And it covers the first render, which
84
+ * is also the server's, so the markup a server writes and the first client
85
+ * render agree. A host with no `ResizeObserver` gets the measurement at mount
86
+ * and nothing after, as `elements/clamp.tsx` does.
87
+ */
88
+ function useAvailableWidth(bench, enabled) {
89
+ const [available, setAvailable] = React.useState(null);
90
+ useIsomorphicLayoutEffect(() => {
91
+ const node = bench.current;
92
+ if (!enabled || node === null) return;
93
+ const measure = () => {
94
+ const width = Math.floor(node.getBoundingClientRect().width);
95
+ setAvailable(width > 0 ? width : null);
96
+ };
97
+ measure();
98
+ if (typeof ResizeObserver === "undefined") return;
99
+ const observer = new ResizeObserver(measure);
100
+ observer.observe(node);
101
+ return () => observer.disconnect();
102
+ }, [bench, enabled]);
103
+ return available;
104
+ }
105
+ /**
106
+ * The bounds in force: the given ones, with the widest narrowed to the room
107
+ * the bench has, so a stage never outgrows the frame — or the grid track — it
108
+ * sits in. A room narrower than `minWidth` brings the floor down with it,
109
+ * because a floor wider than the room is a stage that overhangs its tile.
110
+ * Height takes no bound from the room: a taller stage grows its frame.
111
+ */
112
+ function effectiveBounds(options, available) {
113
+ if (available === null) return options;
114
+ const maxWidth = Math.min(options.maxWidth, available);
115
+ return {
116
+ ...options,
117
+ maxWidth,
118
+ minWidth: Math.min(options.minWidth, maxWidth)
119
+ };
120
+ }
121
+ /**
122
+ * The attributes the stage carries — and, when nothing is asked, none at all.
123
+ *
124
+ * Inheriting is the *absence* of attributes, which is why every branch below
125
+ * is conditional. Three branches add a second attribute, each for a reason in
126
+ * the stylesheets rather than in taste.
127
+ *
128
+ * A theme's light block is the compound selector
129
+ * `[data-theme="t"][data-mode="light"]`, so a mode with no theme restates the
130
+ * page's theme on the same element. Read the other way, a theme with no mode
131
+ * repeats the page's mode. Otherwise the stage would match the theme's dark
132
+ * block under a light page, while every popup opened on it came up light:
133
+ * popups are stamped from the context `StageScope` publishes, which carries
134
+ * the page's mode.
135
+ *
136
+ * A theme's density overrides are addressed through `data-cue-theme`, the
137
+ * stamp `<Density>` writes, so a density names the nearest theme the same way.
138
+ */
139
+ function stageAttributes(asked, page) {
140
+ const attributes = {};
141
+ const scoped = asked.theme ?? (asked.mode === void 0 ? null : page?.theme ?? null);
142
+ if (scoped !== null) attributes["data-theme"] = scoped;
143
+ const moded = asked.mode ?? (asked.theme === void 0 ? void 0 : page?.mode);
144
+ if (moded !== void 0) attributes["data-mode"] = moded;
145
+ if (asked.density !== void 0) {
146
+ attributes["data-density"] = asked.density;
147
+ const nearest = asked.theme ?? page?.theme ?? null;
148
+ if (nearest !== null) attributes["data-cue-theme"] = nearest;
149
+ }
150
+ if (asked.font !== void 0) attributes["data-font"] = asked.font;
151
+ return attributes;
152
+ }
153
+ /**
154
+ * The contexts the stage republishes, so an overlay opened on it follows it.
155
+ *
156
+ * `useCuePortalProps()` stamps a portal container from `ThemeContext` and
157
+ * `DensityContext`; a popup mounts on `<body>` and inherits nothing from the
158
+ * stage's DOM. So the stage's subtree reads a theme context with the stage's
159
+ * theme and mode, and a density context with the stage's rung. The tools and
160
+ * footer are outside this subtree, so what they open follows the page. A theme
161
+ * the provider did not register leaves `manifest` null, which is what
162
+ * `useTheme()` already reports for a name nobody registered.
163
+ */
164
+ function StageScope({ theme, mode, density, children }) {
165
+ const outer = React.useContext(ThemeContext);
166
+ const outerOverrides = React.useContext(OverridesContext);
167
+ const outerDensity = React.useContext(DensityContext);
168
+ const outerAmbient = React.useContext(AmbientThemeContext);
169
+ const value = React.useMemo(() => {
170
+ if (outer === null || theme === void 0 && mode === void 0) return outer;
171
+ const scoped = theme ?? outer.theme;
172
+ return {
173
+ ...outer,
174
+ theme: scoped,
175
+ manifest: scoped === outer.theme ? outer.manifest : null,
176
+ mode: mode ?? outer.mode,
177
+ resolvedMode: mode === "dark" || mode === "light" ? mode : outer.resolvedMode
178
+ };
179
+ }, [
180
+ outer,
181
+ theme,
182
+ mode
183
+ ]);
184
+ const stamped = theme !== void 0 || mode !== void 0;
185
+ return /* @__PURE__ */ jsx(AmbientThemeContext.Provider, {
186
+ value: theme ?? outerAmbient,
187
+ children: /* @__PURE__ */ jsx(DensityContext.Provider, {
188
+ value: density ?? outerDensity,
189
+ children: /* @__PURE__ */ jsx(OverridesContext.Provider, {
190
+ value: stamped ? null : outerOverrides,
191
+ children: /* @__PURE__ */ jsx(ThemeContext.Provider, {
192
+ value,
193
+ children
194
+ })
195
+ })
196
+ })
197
+ });
198
+ }
199
+ /**
200
+ * The stage's size: explicit per axis once a reader has moved it, and the size
201
+ * the layout gives it before that — `null` on an axis, which is also what a
202
+ * double-click returns an axis to.
203
+ *
204
+ * An axis nobody set is measured live, not once. Its value is what the grip
205
+ * announces on focus, before any key, and where the first key or drag starts
206
+ * from, so it has to follow the sizer at rest: the track widening under it, the
207
+ * specimen growing after mount (every listing specimen loads behind a
208
+ * skeleton), a Preview mounted hidden and shown later. Observing the bench is
209
+ * not enough for that. The bench reports a width, and a specimen that grows
210
+ * moves only the height, so the sizer is observed as well, for as long as any
211
+ * axis is unset.
212
+ */
213
+ function useStageSize(options) {
214
+ const sizer = React.useRef(null);
215
+ const [width, setWidth] = React.useState(null);
216
+ const [height, setHeight] = React.useState(null);
217
+ const [natural, setNatural] = React.useState({
218
+ width: 0,
219
+ height: 0
220
+ });
221
+ const [drag, setDrag] = React.useState(null);
222
+ const enabled = options !== null;
223
+ const widthSet = width !== null;
224
+ const heightSet = height !== null;
225
+ useIsomorphicLayoutEffect(() => {
226
+ const node = sizer.current;
227
+ if (!enabled || node === null || widthSet && heightSet) return;
228
+ const measure = () => {
229
+ const rect = node.getBoundingClientRect();
230
+ setNatural((previous) => {
231
+ const next = {
232
+ width: widthSet ? previous.width : Math.round(rect.width),
233
+ height: heightSet ? previous.height : Math.round(rect.height)
234
+ };
235
+ return next.width === previous.width && next.height === previous.height ? previous : next;
236
+ });
237
+ };
238
+ measure();
239
+ if (typeof ResizeObserver === "undefined") return;
240
+ const observer = new ResizeObserver(measure);
241
+ observer.observe(node);
242
+ return () => observer.disconnect();
243
+ }, [
244
+ enabled,
245
+ widthSet,
246
+ heightSet
247
+ ]);
248
+ return {
249
+ sizer,
250
+ width,
251
+ height,
252
+ setWidth,
253
+ setHeight,
254
+ shownWidth: options === null ? 0 : clamp(width ?? natural.width, options.minWidth, options.maxWidth),
255
+ shownHeight: options === null ? 0 : clamp(height ?? natural.height, options.minHeight, options.maxHeight),
256
+ drag,
257
+ setDrag
258
+ };
259
+ }
260
+ /** The two keyboard-operable edges, the corner, and the readout. Chrome, never stage. */
261
+ function Grips({ label, options, size }) {
262
+ const { shownWidth, shownHeight, drag, setDrag, setWidth, setHeight } = size;
263
+ const stepWidth = (event) => {
264
+ const step = event.shiftKey ? BIG_STEP : STEP;
265
+ let next = null;
266
+ if (event.key === "ArrowRight") next = shownWidth + step;
267
+ else if (event.key === "ArrowLeft") next = shownWidth - step;
268
+ else if (event.key === "Home") next = options.minWidth;
269
+ else if (event.key === "End") next = options.maxWidth;
270
+ if (next === null) return;
271
+ event.preventDefault();
272
+ setWidth(clamp(next, options.minWidth, options.maxWidth));
273
+ };
274
+ const stepHeight = (event) => {
275
+ const step = event.shiftKey ? BIG_STEP : STEP;
276
+ let next = null;
277
+ if (event.key === "ArrowDown") next = shownHeight + step;
278
+ else if (event.key === "ArrowUp") next = shownHeight - step;
279
+ else if (event.key === "Home") next = options.minHeight;
280
+ else if (event.key === "End") next = options.maxHeight;
281
+ if (next === null) return;
282
+ event.preventDefault();
283
+ setHeight(clamp(next, options.minHeight, options.maxHeight));
284
+ };
285
+ const pointer = (axis) => ({
286
+ onPointerDown: (event) => {
287
+ if (event.button !== 0) return;
288
+ event.preventDefault();
289
+ event.currentTarget.setPointerCapture?.(event.pointerId);
290
+ setDrag({
291
+ axis,
292
+ x: event.clientX,
293
+ y: event.clientY,
294
+ width: shownWidth,
295
+ height: shownHeight
296
+ });
297
+ },
298
+ onPointerMove: (event) => {
299
+ if (drag === null) return;
300
+ if (drag.axis !== "y") setWidth(clamp(drag.width + event.clientX - drag.x, options.minWidth, options.maxWidth));
301
+ if (drag.axis !== "x") setHeight(clamp(drag.height + event.clientY - drag.y, options.minHeight, options.maxHeight));
302
+ },
303
+ onPointerUp: (event) => {
304
+ if (drag === null) return;
305
+ event.currentTarget.releasePointerCapture?.(event.pointerId);
306
+ setDrag(null);
307
+ },
308
+ onPointerCancel: () => setDrag(null)
309
+ });
310
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
311
+ options.axis === "y" ? null : /* @__PURE__ */ jsx("div", {
312
+ role: "separator",
313
+ tabIndex: 0,
314
+ "aria-orientation": "vertical",
315
+ "aria-label": `Resize ${label} width`,
316
+ "aria-valuenow": shownWidth,
317
+ "aria-valuemin": options.minWidth,
318
+ "aria-valuemax": options.maxWidth,
319
+ "aria-valuetext": `${shownWidth} pixels wide`,
320
+ "data-slot": "preview-grip-x",
321
+ className: GRIP_X_CLASS,
322
+ onKeyDown: stepWidth,
323
+ onDoubleClick: () => setWidth(null),
324
+ ...pointer("x")
325
+ }),
326
+ options.axis === "x" ? null : /* @__PURE__ */ jsx("div", {
327
+ role: "separator",
328
+ tabIndex: 0,
329
+ "aria-orientation": "horizontal",
330
+ "aria-label": `Resize ${label} height`,
331
+ "aria-valuenow": shownHeight,
332
+ "aria-valuemin": options.minHeight,
333
+ "aria-valuemax": options.maxHeight,
334
+ "aria-valuetext": `${shownHeight} pixels tall`,
335
+ "data-slot": "preview-grip-y",
336
+ className: GRIP_Y_CLASS,
337
+ onKeyDown: stepHeight,
338
+ onDoubleClick: () => setHeight(null),
339
+ ...pointer("y")
340
+ }),
341
+ options.axis === "both" ? /* @__PURE__ */ jsx("div", {
342
+ "aria-hidden": "true",
343
+ "data-slot": "preview-grip-corner",
344
+ className: GRIP_CORNER_CLASS,
345
+ onDoubleClick: () => {
346
+ setWidth(null);
347
+ setHeight(null);
348
+ },
349
+ ...pointer("both")
350
+ }) : null,
351
+ drag === null ? null : /* @__PURE__ */ jsx("span", {
352
+ "aria-hidden": "true",
353
+ "data-slot": "preview-readout",
354
+ className: READOUT_CLASS,
355
+ children: `${shownWidth} × ${shownHeight}`
356
+ })
357
+ ] });
358
+ }
359
+ /**
360
+ * A frame for one component: a stage that is its own theme island, a slot for
361
+ * tools at the top right, and a footer.
362
+ *
363
+ * **The stage is the island; the chrome is not.** `theme`, `mode`, `density`
364
+ * and `font` land on the stage element only, and the stage republishes the
365
+ * theme and density contexts so an overlay opened on it follows it. The tools,
366
+ * the footer, the grips and anything they open stay in the scope around the
367
+ * Preview — a `terminal` preview's own controls do not turn cyan. With none of
368
+ * the four set, the stage carries no attribute and costs what a `<div>` costs.
369
+ * A stage `theme` with no `mode` repeats the page's mode beside it.
370
+ *
371
+ * **`resizable` is handles of its own**, both axes by default: an edge grip per
372
+ * axis that is a keyboard-operable `separator` valued in pixels, and a corner
373
+ * grip for the pointer. Double-click resets an axis. The width never outgrows
374
+ * the frame: its bound in force is the frame's own width, observed live, when
375
+ * that is under `maxWidth`. Not `Resizable`, which is split panes on an
376
+ * optional peer.
377
+ *
378
+ * Props, not a compound: one stage and two slots, each saying what it is.
379
+ *
380
+ * @example
381
+ * <Preview label="Button preview" tools={<IconButton aria-label="Configure Button" icon={Wrench} />}>
382
+ * <Button variant="primary">Take cue</Button>
383
+ * </Preview>
384
+ * @example
385
+ * <Preview label="Chip preview" theme="terminal" density="compact" resizable>
386
+ * <Chip tone="accent">Standby</Chip>
387
+ * </Preview>
388
+ */
389
+ const Preview = React.forwardRef(function Preview({ children, label, theme, mode, density, font, resizable, tools, footer, className, ...elementProps }, ref) {
390
+ const outer = React.useContext(ThemeContext);
391
+ const bench = React.useRef(null);
392
+ const given = resolveResize(resizable);
393
+ const available = useAvailableWidth(bench, given !== null);
394
+ const options = given === null ? null : effectiveBounds(given, available);
395
+ const size = useStageSize(options);
396
+ const attributes = stageAttributes({
397
+ theme,
398
+ mode,
399
+ density,
400
+ font
401
+ }, outer);
402
+ const stage = /* @__PURE__ */ jsx("div", {
403
+ "data-slot": "preview-stage",
404
+ ...attributes,
405
+ className: cn(STAGE_CLASS, options !== null && "flex-1", tools !== void 0 && TOOLS_RESERVE_STAGE),
406
+ style: size.height === null ? void 0 : { minHeight: 0 },
407
+ children: /* @__PURE__ */ jsx(StageScope, {
408
+ theme,
409
+ mode,
410
+ density,
411
+ children
412
+ })
413
+ });
414
+ return /* @__PURE__ */ jsxs("div", {
415
+ ...elementProps,
416
+ ref,
417
+ role: "group",
418
+ "aria-label": label,
419
+ "data-slot": "preview",
420
+ className: cn(ROOT_CLASS, options !== null && "w-full", className),
421
+ children: [
422
+ /* @__PURE__ */ jsx("div", {
423
+ ref: bench,
424
+ "data-slot": "preview-bench",
425
+ className: cn(BENCH_CLASS, footer === void 0 && "rounded-b-(--radius-surface)", tools !== void 0 && TOOLS_RESERVE_BENCH),
426
+ children: options === null ? stage : /* @__PURE__ */ jsxs("div", {
427
+ ref: size.sizer,
428
+ "data-slot": "preview-sizer",
429
+ className: cn("relative flex flex-col", size.width === null && "w-full"),
430
+ style: {
431
+ width: size.width === null ? void 0 : `${size.shownWidth}px`,
432
+ height: size.height === null ? void 0 : `${size.shownHeight}px`,
433
+ minWidth: `${options.minWidth}px`,
434
+ maxWidth: `${options.maxWidth}px`,
435
+ minHeight: `${options.minHeight}px`,
436
+ maxHeight: `${options.maxHeight}px`
437
+ },
438
+ children: [stage, /* @__PURE__ */ jsx(Grips, {
439
+ label,
440
+ options,
441
+ size
442
+ })]
443
+ })
444
+ }),
445
+ tools === void 0 ? null : /* @__PURE__ */ jsx("div", {
446
+ "data-slot": "preview-tools",
447
+ className: TOOLS_CLASS,
448
+ children: tools
449
+ }),
450
+ footer === void 0 ? null : /* @__PURE__ */ jsx("div", {
451
+ "data-slot": "preview-footer",
452
+ className: FOOTER_CLASS,
453
+ children: footer
454
+ })
455
+ ]
456
+ });
457
+ });
458
+ //#endregion
459
+ export { Preview };
@@ -26,6 +26,18 @@ interface ButtonProps extends React.ComponentPropsWithoutRef<"button">, VariantP
26
26
  * level without the caller passing a pixel size.
27
27
  */
28
28
  icon?: React.ElementType;
29
+ /**
30
+ * The label. A button with no children and no other accessible name is a
31
+ * control a screen reader announces as nothing.
32
+ *
33
+ * Declared explicitly, although `React.ComponentPropsWithoutRef<"button">`
34
+ * already carries it, for the reason `Card.Header` and `Bento.Tile` declare
35
+ * theirs: docgen documents a prop only where the library *declares* it, so
36
+ * without this line the manifest files no `children` for the system's most
37
+ * used control — the props table omits it, and a propSet cannot name it,
38
+ * which is how `/play/button` came to preview an unlabelled `<button>`.
39
+ */
40
+ children?: React.ReactNode;
29
41
  }
30
42
  /**
31
43
  * The system's primary action control.
@@ -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?: "info" | "warn" | "danger" | "accent" | "ok" | "busy" | "neutral" | null | undefined;
33
+ tone?: "danger" | "info" | "warn" | "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,4 +1,4 @@
1
- import { TokenOverrides } from "./overrides.js";
1
+ import { TokenOverrides, isSafeTokenValue } from "./overrides.js";
2
2
  import { FontFamilies, ResolvedMode, ThemeContextValue, ThemeProvider, ThemeProviderProps } from "./theme-provider.js";
3
3
  import { Density, DensityProps } from "./density.js";
4
4
  import { CuePortalFrame, CuePortalFrameProps, useCuePortalProps } from "./portal.js";
@@ -8,4 +8,4 @@ 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
10
  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, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
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 };
@@ -1,8 +1,9 @@
1
1
  import { Density } from "./density.js";
2
+ import { isSafeTokenValue } from "./overrides.js";
2
3
  import { DEFAULT_STORAGE_KEY, prepaintScript } from "./prepaint.js";
3
4
  import { useDensities, useFonts, useThemes } from "./theme-registry.js";
4
5
  import { ThemeProvider } from "./theme-provider.js";
5
6
  import { CuePortalFrame, useCuePortalProps } from "./portal.js";
6
7
  import { useControlHeight, useDensity } from "./use-density.js";
7
8
  import { useTheme } from "./use-theme.js";
8
- export { CuePortalFrame, DEFAULT_STORAGE_KEY, Density, ThemeProvider, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
9
+ export { CuePortalFrame, DEFAULT_STORAGE_KEY, Density, ThemeProvider, isSafeTokenValue, prepaintScript, useControlHeight, useCuePortalProps, useDensities, useDensity, useFonts, useTheme, useThemes };
@@ -39,5 +39,42 @@ interface TokenOverrides {
39
39
  display: string;
40
40
  }>;
41
41
  }
42
+ /**
43
+ * Whether a string may be written into the density stylesheet this module
44
+ * builds.
45
+ *
46
+ * The same rule {@link overridesCss} applies at `SAFE_VALUE`, exported because a
47
+ * caller that builds a {@link TokenOverrides} wants to refuse a value at the
48
+ * *input* rather than watch it be dropped, silently and one warning later, at
49
+ * the sheet. A panel that cannot ask the question has two bad answers open to
50
+ * it: keep a second copy of this regex, which stops being this regex the first
51
+ * time either moves, or write the value anyway and let a reader wonder why the
52
+ * picture did not change.
53
+ *
54
+ * `@cueplusplus/ui/configurator` exports `isSafeCssValue` (`configurator/_overrides.ts:239`)
55
+ * for the configurator's own snapshot, and this is deliberately **not** that
56
+ * function: same rule, asked from the module that owns it, without dragging in
57
+ * a subpath that also carries `ThemeConfigurator`, `ExportDialog` and an
58
+ * optional `react-aria-components` peer. Neither imports the other, and the
59
+ * test beside this file is what holds them to one answer.
60
+ *
61
+ * **The one thing the rule does not cover, said out loud: an unterminated
62
+ * `/*`.** It carries none of the five refused characters, so it is accepted and
63
+ * written, and everything after it in that stylesheet — the closing brace and
64
+ * any later declaration — is inside a comment. That is a value of the caller's
65
+ * own that stops their *other* overrides applying, not an injection: with `;`,
66
+ * `{`, `}`, `<` and `\` all refused, no second value can close the comment and
67
+ * open a rule. A caller that wants to refuse it as well can say so on top of
68
+ * this; tightening `SAFE_VALUE` itself would change what `overridesCss` writes,
69
+ * which is a different release note from this one.
70
+ *
71
+ * @param value - The proposed declaration value.
72
+ * @returns `true` when it cannot close its declaration, its block or the
73
+ * `<style>` element — see the comment case above for what that does not mean.
74
+ * @example
75
+ * isSafeTokenValue("Iosevka, monospace"); // → true
76
+ * isSafeTokenValue("red;}"); // → false
77
+ */
78
+ declare function isSafeTokenValue(value: string): boolean;
42
79
  //#endregion
43
- export { TokenOverrides };
80
+ export { TokenOverrides, isSafeTokenValue };
@@ -102,6 +102,45 @@ function inlineOverrides(overrides, mode) {
102
102
  */
103
103
  const SAFE_VALUE = /^[^;{}<>\\]+$/;
104
104
  /**
105
+ * Whether a string may be written into the density stylesheet this module
106
+ * builds.
107
+ *
108
+ * The same rule {@link overridesCss} applies at `SAFE_VALUE`, exported because a
109
+ * caller that builds a {@link TokenOverrides} wants to refuse a value at the
110
+ * *input* rather than watch it be dropped, silently and one warning later, at
111
+ * the sheet. A panel that cannot ask the question has two bad answers open to
112
+ * it: keep a second copy of this regex, which stops being this regex the first
113
+ * time either moves, or write the value anyway and let a reader wonder why the
114
+ * picture did not change.
115
+ *
116
+ * `@cueplusplus/ui/configurator` exports `isSafeCssValue` (`configurator/_overrides.ts:239`)
117
+ * for the configurator's own snapshot, and this is deliberately **not** that
118
+ * function: same rule, asked from the module that owns it, without dragging in
119
+ * a subpath that also carries `ThemeConfigurator`, `ExportDialog` and an
120
+ * optional `react-aria-components` peer. Neither imports the other, and the
121
+ * test beside this file is what holds them to one answer.
122
+ *
123
+ * **The one thing the rule does not cover, said out loud: an unterminated
124
+ * `/*`.** It carries none of the five refused characters, so it is accepted and
125
+ * written, and everything after it in that stylesheet — the closing brace and
126
+ * any later declaration — is inside a comment. That is a value of the caller's
127
+ * own that stops their *other* overrides applying, not an injection: with `;`,
128
+ * `{`, `}`, `<` and `\` all refused, no second value can close the comment and
129
+ * open a rule. A caller that wants to refuse it as well can say so on top of
130
+ * this; tightening `SAFE_VALUE` itself would change what `overridesCss` writes,
131
+ * which is a different release note from this one.
132
+ *
133
+ * @param value - The proposed declaration value.
134
+ * @returns `true` when it cannot close its declaration, its block or the
135
+ * `<style>` element — see the comment case above for what that does not mean.
136
+ * @example
137
+ * isSafeTokenValue("Iosevka, monospace"); // → true
138
+ * isSafeTokenValue("red;}"); // → false
139
+ */
140
+ function isSafeTokenValue(value) {
141
+ return SAFE_VALUE.test(value);
142
+ }
143
+ /**
105
144
  * The document-scoped stylesheet text for the density layer.
106
145
  *
107
146
  * **Why the selector says the attribute twice.** A rung a theme retunes ships
@@ -235,4 +274,4 @@ function reportOverrides(manifest, mode, overrides, theme) {
235
274
  for (const check of after.advisories) if (caused(check)) noteOnce(`overrides: ${check.id} at ${check.ratio.toFixed(2)}:1 misses its ${check.minimum} advisory floor under theme "${theme}"`);
236
275
  }
237
276
  //#endregion
238
- export { OverridesContext, inlineOverrides, overridesCss, reportOverrides };
277
+ export { OverridesContext, inlineOverrides, isSafeTokenValue, overridesCss, reportOverrides };
@@ -26,7 +26,10 @@
26
26
  "type": "string",
27
27
  "required": false,
28
28
  "defaultValue": null,
29
- "description": "Extra classes for the accordion frame."
29
+ "description": "Extra classes for the accordion frame.",
30
+ "control": {
31
+ "kind": "string"
32
+ }
30
33
  }
31
34
  ],
32
35
  "typeReferences": [
@@ -43,7 +46,10 @@
43
46
  "type": "string",
44
47
  "required": false,
45
48
  "defaultValue": null,
46
- "description": "Extra classes for the section."
49
+ "description": "Extra classes for the section.",
50
+ "control": {
51
+ "kind": "string"
52
+ }
47
53
  }
48
54
  ],
49
55
  "typeReferences": [
@@ -60,7 +66,10 @@
60
66
  "type": "string",
61
67
  "required": false,
62
68
  "defaultValue": null,
63
- "description": "Extra classes for the heading."
69
+ "description": "Extra classes for the heading.",
70
+ "control": {
71
+ "kind": "string"
72
+ }
64
73
  }
65
74
  ],
66
75
  "typeReferences": [
@@ -77,7 +86,10 @@
77
86
  "type": "string",
78
87
  "required": false,
79
88
  "defaultValue": null,
80
- "description": "Extra classes for the summary row."
89
+ "description": "Extra classes for the summary row.",
90
+ "control": {
91
+ "kind": "string"
92
+ }
81
93
  }
82
94
  ],
83
95
  "typeReferences": [
@@ -94,14 +106,20 @@
94
106
  "type": "string",
95
107
  "required": false,
96
108
  "defaultValue": null,
97
- "description": "Extra classes for the animating box. Padding belongs on `bodyClassName`."
109
+ "description": "Extra classes for the animating box. Padding belongs on `bodyClassName`.",
110
+ "control": {
111
+ "kind": "string"
112
+ }
98
113
  },
99
114
  {
100
115
  "name": "bodyClassName",
101
116
  "type": "string",
102
117
  "required": false,
103
118
  "defaultValue": null,
104
- "description": "Extra classes for the padded body inside it."
119
+ "description": "Extra classes for the padded body inside it.",
120
+ "control": {
121
+ "kind": "string"
122
+ }
105
123
  }
106
124
  ],
107
125
  "typeReferences": [