@cueplusplus/ui 0.1.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 (372) hide show
  1. package/dist/charts/_token.js +39 -0
  2. package/dist/charts/container.d.ts +58 -0
  3. package/dist/charts/container.js +77 -0
  4. package/dist/charts/index.d.ts +5 -0
  5. package/dist/charts/index.js +5 -0
  6. package/dist/charts/ramps.d.ts +140 -0
  7. package/dist/charts/ramps.js +615 -0
  8. package/dist/charts/theme.d.ts +112 -0
  9. package/dist/charts/theme.js +165 -0
  10. package/dist/charts/tones.d.ts +111 -0
  11. package/dist/charts/tones.js +127 -0
  12. package/dist/chat/_glyphs.js +55 -0
  13. package/dist/chat/agent-color.d.ts +84 -0
  14. package/dist/chat/agent-color.js +101 -0
  15. package/dist/chat/agent-pile.d.ts +55 -0
  16. package/dist/chat/agent-pile.js +121 -0
  17. package/dist/chat/ask-box.d.ts +88 -0
  18. package/dist/chat/ask-box.js +341 -0
  19. package/dist/chat/composer.d.ts +66 -0
  20. package/dist/chat/composer.js +144 -0
  21. package/dist/chat/delegation-card.d.ts +60 -0
  22. package/dist/chat/delegation-card.js +129 -0
  23. package/dist/chat/index.d.ts +8 -0
  24. package/dist/chat/index.js +8 -0
  25. package/dist/chat/message-list.d.ts +39 -0
  26. package/dist/chat/message-list.js +105 -0
  27. package/dist/chat/message.d.ts +47 -0
  28. package/dist/chat/message.js +108 -0
  29. package/dist/chrome/_glyphs.js +101 -0
  30. package/dist/chrome/_tabs-scroll.d.ts +62 -0
  31. package/dist/chrome/_tabs-scroll.js +68 -0
  32. package/dist/chrome/_tree.d.ts +75 -0
  33. package/dist/chrome/_tree.js +49 -0
  34. package/dist/chrome/empty-state.d.ts +37 -0
  35. package/dist/chrome/empty-state.js +57 -0
  36. package/dist/chrome/eyebrow.d.ts +29 -0
  37. package/dist/chrome/eyebrow.js +36 -0
  38. package/dist/chrome/index.d.ts +17 -0
  39. package/dist/chrome/index.js +17 -0
  40. package/dist/chrome/navigation-menu.d.ts +80 -0
  41. package/dist/chrome/navigation-menu.js +121 -0
  42. package/dist/chrome/page-shell.d.ts +38 -0
  43. package/dist/chrome/page-shell.js +66 -0
  44. package/dist/chrome/panel.d.ts +55 -0
  45. package/dist/chrome/panel.js +67 -0
  46. package/dist/chrome/row.d.ts +82 -0
  47. package/dist/chrome/row.js +106 -0
  48. package/dist/chrome/scroll-area.d.ts +39 -0
  49. package/dist/chrome/scroll-area.js +70 -0
  50. package/dist/chrome/section-header.d.ts +42 -0
  51. package/dist/chrome/section-header.js +79 -0
  52. package/dist/chrome/segmented-control.d.ts +58 -0
  53. package/dist/chrome/segmented-control.js +76 -0
  54. package/dist/chrome/status-bar.d.ts +46 -0
  55. package/dist/chrome/status-bar.js +56 -0
  56. package/dist/chrome/tabs.d.ts +80 -0
  57. package/dist/chrome/tabs.js +176 -0
  58. package/dist/chrome/title-bar.d.ts +37 -0
  59. package/dist/chrome/title-bar.js +45 -0
  60. package/dist/chrome/toolbar.d.ts +46 -0
  61. package/dist/chrome/toolbar.js +62 -0
  62. package/dist/chrome/tree.d.ts +98 -0
  63. package/dist/chrome/tree.js +248 -0
  64. package/dist/color/_checker.js +14 -0
  65. package/dist/color/_convert.d.ts +52 -0
  66. package/dist/color/_convert.js +66 -0
  67. package/dist/color/_glyphs.js +28 -0
  68. package/dist/color/_thumb.js +15 -0
  69. package/dist/color/_value.js +28 -0
  70. package/dist/color/color-area.d.ts +27 -0
  71. package/dist/color/color-area.js +40 -0
  72. package/dist/color/color-field.d.ts +32 -0
  73. package/dist/color/color-field.js +43 -0
  74. package/dist/color/color-picker.d.ts +71 -0
  75. package/dist/color/color-picker.js +178 -0
  76. package/dist/color/color-slider.d.ts +31 -0
  77. package/dist/color/color-slider.js +51 -0
  78. package/dist/color/color-swatch.d.ts +32 -0
  79. package/dist/color/color-swatch.js +44 -0
  80. package/dist/color/index.d.ts +7 -0
  81. package/dist/color/index.js +7 -0
  82. package/dist/configurator/_export.d.ts +78 -0
  83. package/dist/configurator/_export.js +261 -0
  84. package/dist/configurator/_glyphs.js +92 -0
  85. package/dist/configurator/_overrides.d.ts +247 -0
  86. package/dist/configurator/_overrides.js +514 -0
  87. package/dist/configurator/configurator.d.ts +71 -0
  88. package/dist/configurator/configurator.js +279 -0
  89. package/dist/configurator/export-dialog.d.ts +30 -0
  90. package/dist/configurator/export-dialog.js +121 -0
  91. package/dist/configurator/index.d.ts +8 -0
  92. package/dist/configurator/index.js +8 -0
  93. package/dist/configurator/panel-sections.d.ts +86 -0
  94. package/dist/configurator/panel-sections.js +316 -0
  95. package/dist/configurator/token-editor.d.ts +74 -0
  96. package/dist/configurator/token-editor.js +149 -0
  97. package/dist/configurator/use-zoom.d.ts +87 -0
  98. package/dist/configurator/use-zoom.js +113 -0
  99. package/dist/date/_glyphs.js +34 -0
  100. package/dist/date/_segments.js +55 -0
  101. package/dist/date/_trigger.js +31 -0
  102. package/dist/date/calendar.d.ts +25 -0
  103. package/dist/date/calendar.js +125 -0
  104. package/dist/date/date-field.d.ts +34 -0
  105. package/dist/date/date-field.js +41 -0
  106. package/dist/date/date-picker.d.ts +69 -0
  107. package/dist/date/date-picker.js +89 -0
  108. package/dist/date/date-range-picker.d.ts +65 -0
  109. package/dist/date/date-range-picker.js +92 -0
  110. package/dist/date/index.d.ts +6 -0
  111. package/dist/date/index.js +6 -0
  112. package/dist/date/time-field.d.ts +29 -0
  113. package/dist/date/time-field.js +36 -0
  114. package/dist/dmx/_ramp.d.ts +77 -0
  115. package/dist/dmx/_ramp.js +169 -0
  116. package/dist/dmx/_readout.js +53 -0
  117. package/dist/dmx/_universe.d.ts +105 -0
  118. package/dist/dmx/_universe.js +112 -0
  119. package/dist/dmx/channel-matrix.d.ts +81 -0
  120. package/dist/dmx/channel-matrix.js +285 -0
  121. package/dist/dmx/dmx-bar.d.ts +50 -0
  122. package/dist/dmx/dmx-bar.js +107 -0
  123. package/dist/dmx/dmx-strip.d.ts +41 -0
  124. package/dist/dmx/dmx-strip.js +196 -0
  125. package/dist/dmx/index.d.ts +7 -0
  126. package/dist/dmx/index.js +7 -0
  127. package/dist/dmx/universe-grid.d.ts +61 -0
  128. package/dist/dmx/universe-grid.js +182 -0
  129. package/dist/flow/_frame.d.ts +52 -0
  130. package/dist/flow/_frame.js +38 -0
  131. package/dist/flow/controls.d.ts +41 -0
  132. package/dist/flow/controls.js +53 -0
  133. package/dist/flow/flow.css +121 -0
  134. package/dist/flow/handles.d.ts +40 -0
  135. package/dist/flow/handles.js +61 -0
  136. package/dist/flow/index.d.ts +7 -0
  137. package/dist/flow/index.js +7 -0
  138. package/dist/flow/node-card.d.ts +71 -0
  139. package/dist/flow/node-card.js +102 -0
  140. package/dist/flow/signal-edge.d.ts +60 -0
  141. package/dist/flow/signal-edge.js +108 -0
  142. package/dist/flow/use-flow-theme.d.ts +69 -0
  143. package/dist/flow/use-flow-theme.js +109 -0
  144. package/dist/forms/_chassis.d.ts +64 -0
  145. package/dist/forms/_chassis.js +59 -0
  146. package/dist/forms/_glyphs.js +143 -0
  147. package/dist/forms/_listbox.js +55 -0
  148. package/dist/forms/autocomplete.d.ts +61 -0
  149. package/dist/forms/autocomplete.js +88 -0
  150. package/dist/forms/button-group.d.ts +47 -0
  151. package/dist/forms/button-group.js +52 -0
  152. package/dist/forms/checkbox.d.ts +51 -0
  153. package/dist/forms/checkbox.js +98 -0
  154. package/dist/forms/combobox.d.ts +73 -0
  155. package/dist/forms/combobox.js +108 -0
  156. package/dist/forms/env-var-input.d.ts +74 -0
  157. package/dist/forms/env-var-input.js +116 -0
  158. package/dist/forms/field.d.ts +89 -0
  159. package/dist/forms/field.js +102 -0
  160. package/dist/forms/file-upload.d.ts +113 -0
  161. package/dist/forms/file-upload.js +232 -0
  162. package/dist/forms/index.d.ts +26 -0
  163. package/dist/forms/index.js +26 -0
  164. package/dist/forms/input-group.d.ts +31 -0
  165. package/dist/forms/input-group.js +54 -0
  166. package/dist/forms/input.d.ts +33 -0
  167. package/dist/forms/input.js +36 -0
  168. package/dist/forms/multi-select.d.ts +62 -0
  169. package/dist/forms/multi-select.js +107 -0
  170. package/dist/forms/number-field.d.ts +38 -0
  171. package/dist/forms/number-field.js +64 -0
  172. package/dist/forms/otp-field.d.ts +54 -0
  173. package/dist/forms/otp-field.js +59 -0
  174. package/dist/forms/password-input.d.ts +46 -0
  175. package/dist/forms/password-input.js +62 -0
  176. package/dist/forms/radio-group.d.ts +50 -0
  177. package/dist/forms/radio-group.js +64 -0
  178. package/dist/forms/rating.d.ts +69 -0
  179. package/dist/forms/rating.js +103 -0
  180. package/dist/forms/scrub-input.d.ts +81 -0
  181. package/dist/forms/scrub-input.js +249 -0
  182. package/dist/forms/search-input.d.ts +54 -0
  183. package/dist/forms/search-input.js +112 -0
  184. package/dist/forms/select.d.ts +75 -0
  185. package/dist/forms/select.js +91 -0
  186. package/dist/forms/slider.d.ts +44 -0
  187. package/dist/forms/slider.js +65 -0
  188. package/dist/forms/switch.d.ts +36 -0
  189. package/dist/forms/switch.js +71 -0
  190. package/dist/forms/tags-input.d.ts +69 -0
  191. package/dist/forms/tags-input.js +148 -0
  192. package/dist/forms/textarea.d.ts +27 -0
  193. package/dist/forms/textarea.js +37 -0
  194. package/dist/forms/toggle-group.d.ts +41 -0
  195. package/dist/forms/toggle-group.js +56 -0
  196. package/dist/forms/toggle.d.ts +41 -0
  197. package/dist/forms/toggle.js +100 -0
  198. package/dist/index.d.ts +112 -0
  199. package/dist/index.js +104 -0
  200. package/dist/instruments/_ansi.js +116 -0
  201. package/dist/instruments/_glyphs.js +90 -0
  202. package/dist/instruments/_motion.js +56 -0
  203. package/dist/instruments/_rem.js +21 -0
  204. package/dist/instruments/app-window-frame.d.ts +48 -0
  205. package/dist/instruments/app-window-frame.js +76 -0
  206. package/dist/instruments/copy-button.d.ts +41 -0
  207. package/dist/instruments/copy-button.js +112 -0
  208. package/dist/instruments/data-table.d.ts +168 -0
  209. package/dist/instruments/data-table.js +212 -0
  210. package/dist/instruments/group-bar.d.ts +63 -0
  211. package/dist/instruments/group-bar.js +122 -0
  212. package/dist/instruments/index.d.ts +13 -0
  213. package/dist/instruments/index.js +13 -0
  214. package/dist/instruments/log-viewer.d.ts +74 -0
  215. package/dist/instruments/log-viewer.js +215 -0
  216. package/dist/instruments/meter.d.ts +77 -0
  217. package/dist/instruments/meter.js +96 -0
  218. package/dist/instruments/progress.d.ts +45 -0
  219. package/dist/instruments/progress.js +61 -0
  220. package/dist/instruments/seam-grid.d.ts +66 -0
  221. package/dist/instruments/seam-grid.js +78 -0
  222. package/dist/instruments/sparkline.d.ts +39 -0
  223. package/dist/instruments/sparkline.js +85 -0
  224. package/dist/instruments/stat.d.ts +35 -0
  225. package/dist/instruments/stat.js +59 -0
  226. package/dist/instruments/table.d.ts +51 -0
  227. package/dist/instruments/table.js +92 -0
  228. package/dist/instruments/terminal-frame.d.ts +89 -0
  229. package/dist/instruments/terminal-frame.js +256 -0
  230. package/dist/instruments/two-step-button.d.ts +47 -0
  231. package/dist/instruments/two-step-button.js +147 -0
  232. package/dist/instruments/usage-chart.d.ts +55 -0
  233. package/dist/instruments/usage-chart.js +235 -0
  234. package/dist/layout/_collapse.js +46 -0
  235. package/dist/layout/_glyphs.js +45 -0
  236. package/dist/layout/accordion.d.ts +52 -0
  237. package/dist/layout/accordion.js +81 -0
  238. package/dist/layout/breadcrumb.d.ts +35 -0
  239. package/dist/layout/breadcrumb.js +67 -0
  240. package/dist/layout/card.d.ts +51 -0
  241. package/dist/layout/card.js +76 -0
  242. package/dist/layout/carousel.d.ts +96 -0
  243. package/dist/layout/carousel.js +211 -0
  244. package/dist/layout/collapsible.d.ts +74 -0
  245. package/dist/layout/collapsible.js +126 -0
  246. package/dist/layout/container.d.ts +27 -0
  247. package/dist/layout/container.js +40 -0
  248. package/dist/layout/description-list.d.ts +24 -0
  249. package/dist/layout/description-list.js +37 -0
  250. package/dist/layout/grid.d.ts +40 -0
  251. package/dist/layout/grid.js +48 -0
  252. package/dist/layout/index.d.ts +15 -0
  253. package/dist/layout/index.js +15 -0
  254. package/dist/layout/item.d.ts +65 -0
  255. package/dist/layout/item.js +100 -0
  256. package/dist/layout/link.d.ts +48 -0
  257. package/dist/layout/link.js +63 -0
  258. package/dist/layout/pagination.d.ts +54 -0
  259. package/dist/layout/pagination.js +121 -0
  260. package/dist/layout/resizable.d.ts +38 -0
  261. package/dist/layout/resizable.js +68 -0
  262. package/dist/layout/sidebar.d.ts +67 -0
  263. package/dist/layout/sidebar.js +129 -0
  264. package/dist/layout/stack.d.ts +46 -0
  265. package/dist/layout/stack.js +62 -0
  266. package/dist/layout/stepper.d.ts +49 -0
  267. package/dist/layout/stepper.js +81 -0
  268. package/dist/layout/timeline.d.ts +43 -0
  269. package/dist/layout/timeline.js +63 -0
  270. package/dist/lib/aria-class.js +24 -0
  271. package/dist/lib/cn.js +18 -0
  272. package/dist/lib/compose.js +38 -0
  273. package/dist/lib/cva.d.ts +18 -0
  274. package/dist/lib/cva.js +65 -0
  275. package/dist/midi/_notes.d.ts +101 -0
  276. package/dist/midi/_notes.js +183 -0
  277. package/dist/midi/_ruler.d.ts +100 -0
  278. package/dist/midi/_ruler.js +105 -0
  279. package/dist/midi/_time.d.ts +107 -0
  280. package/dist/midi/_time.js +130 -0
  281. package/dist/midi/index.d.ts +9 -0
  282. package/dist/midi/index.js +9 -0
  283. package/dist/midi/musical-time-input.d.ts +48 -0
  284. package/dist/midi/musical-time-input.js +154 -0
  285. package/dist/midi/piano-keyboard.d.ts +50 -0
  286. package/dist/midi/piano-keyboard.js +193 -0
  287. package/dist/midi/spectrum-visualizer.d.ts +65 -0
  288. package/dist/midi/spectrum-visualizer.js +213 -0
  289. package/dist/midi/threshold-rail.d.ts +72 -0
  290. package/dist/midi/threshold-rail.js +214 -0
  291. package/dist/midi/timeline-ruler.d.ts +83 -0
  292. package/dist/midi/timeline-ruler.js +152 -0
  293. package/dist/overlays/_debounce.js +34 -0
  294. package/dist/overlays/_glyphs.js +59 -0
  295. package/dist/overlays/_surface.d.ts +70 -0
  296. package/dist/overlays/_surface.js +70 -0
  297. package/dist/overlays/_wheel.js +42 -0
  298. package/dist/overlays/alert-dialog.d.ts +67 -0
  299. package/dist/overlays/alert-dialog.js +117 -0
  300. package/dist/overlays/command-palette.d.ts +93 -0
  301. package/dist/overlays/command-palette.js +193 -0
  302. package/dist/overlays/context-menu.d.ts +64 -0
  303. package/dist/overlays/context-menu.js +78 -0
  304. package/dist/overlays/dialog.d.ts +72 -0
  305. package/dist/overlays/dialog.js +124 -0
  306. package/dist/overlays/drawer.d.ts +75 -0
  307. package/dist/overlays/drawer.js +151 -0
  308. package/dist/overlays/dropdown-menu.d.ts +79 -0
  309. package/dist/overlays/dropdown-menu.js +156 -0
  310. package/dist/overlays/hover-card.d.ts +58 -0
  311. package/dist/overlays/hover-card.js +91 -0
  312. package/dist/overlays/index.d.ts +15 -0
  313. package/dist/overlays/index.js +15 -0
  314. package/dist/overlays/info-tip.d.ts +41 -0
  315. package/dist/overlays/info-tip.js +45 -0
  316. package/dist/overlays/menubar.d.ts +72 -0
  317. package/dist/overlays/menubar.js +100 -0
  318. package/dist/overlays/popover.d.ts +66 -0
  319. package/dist/overlays/popover.js +114 -0
  320. package/dist/overlays/sheet.d.ts +78 -0
  321. package/dist/overlays/sheet.js +131 -0
  322. package/dist/overlays/toast.d.ts +106 -0
  323. package/dist/overlays/toast.js +146 -0
  324. package/dist/overlays/tooltip.d.ts +57 -0
  325. package/dist/overlays/tooltip.js +95 -0
  326. package/dist/primitives/avatar.d.ts +51 -0
  327. package/dist/primitives/avatar.js +93 -0
  328. package/dist/primitives/button.d.ts +46 -0
  329. package/dist/primitives/button.js +94 -0
  330. package/dist/primitives/chip.d.ts +47 -0
  331. package/dist/primitives/chip.js +152 -0
  332. package/dist/primitives/icon-button.d.ts +41 -0
  333. package/dist/primitives/icon-button.js +59 -0
  334. package/dist/primitives/index.d.ts +10 -0
  335. package/dist/primitives/index.js +10 -0
  336. package/dist/primitives/kbd.d.ts +17 -0
  337. package/dist/primitives/kbd.js +24 -0
  338. package/dist/primitives/separator.d.ts +25 -0
  339. package/dist/primitives/separator.js +30 -0
  340. package/dist/primitives/skeleton.d.ts +20 -0
  341. package/dist/primitives/skeleton.js +27 -0
  342. package/dist/primitives/spinner.d.ts +26 -0
  343. package/dist/primitives/spinner.js +31 -0
  344. package/dist/primitives/status-dot.d.ts +44 -0
  345. package/dist/primitives/status-dot.js +50 -0
  346. package/dist/styles.css +19 -0
  347. package/dist/system/density.d.ts +33 -0
  348. package/dist/system/density.js +49 -0
  349. package/dist/system/index.d.ts +8 -0
  350. package/dist/system/index.js +7 -0
  351. package/dist/system/portal.d.ts +51 -0
  352. package/dist/system/portal.js +122 -0
  353. package/dist/system/prepaint.d.ts +38 -0
  354. package/dist/system/prepaint.js +37 -0
  355. package/dist/system/theme-provider.d.ts +72 -0
  356. package/dist/system/theme-provider.js +252 -0
  357. package/dist/system/use-density.d.ts +33 -0
  358. package/dist/system/use-density.js +84 -0
  359. package/dist/system/use-isomorphic-layout-effect.js +15 -0
  360. package/dist/system/use-theme.d.ts +19 -0
  361. package/dist/system/use-theme.js +25 -0
  362. package/dist/theming/_presets.d.ts +11 -0
  363. package/dist/theming/_presets.js +487 -0
  364. package/dist/theming/contrast.d.ts +110 -0
  365. package/dist/theming/contrast.js +167 -0
  366. package/dist/theming/create-theme.d.ts +165 -0
  367. package/dist/theming/create-theme.js +442 -0
  368. package/dist/theming/index.d.ts +5 -0
  369. package/dist/theming/index.js +5 -0
  370. package/dist/theming/serialize.d.ts +145 -0
  371. package/dist/theming/serialize.js +165 -0
  372. package/package.json +216 -0
@@ -0,0 +1,514 @@
1
+ import { BASE_FONT_STACKS, DEFAULT_DENSITY_LEVEL, DENSITY_RADIUS_SCALE, THEME_COLOR_TOKENS, THEME_PRESETS } from "../theming/_presets.js";
2
+ import { THEME_NAME_PATTERN, assertCssValue, serializeThemeBlock, tokenProperty } from "../theming/serialize.js";
3
+ //#region src/configurator/_overrides.ts
4
+ /**
5
+ * The override model behind the theme configurator: what a user may move, where
6
+ * the declaration has to land for it to win, and how the whole thing survives a
7
+ * reload.
8
+ *
9
+ * Everything here is a pure function of an immutable `ThemeOverrides` value,
10
+ * apart from the three functions at the bottom that talk to the document and to
11
+ * `localStorage`. That split is deliberate — the panel is a view over this
12
+ * model, the export dialog is a second view over the same model, and neither is
13
+ * allowed a private opinion about what the tokens are.
14
+ *
15
+ * ## Why the scoping is this fussy
16
+ *
17
+ * The token layer declares its values under *attribute* selectors on two
18
+ * disjoint axes (`[data-theme]` for colour, `[data-density]` for geometry), and
19
+ * both axes are re-declared at every element that carries the attribute — the
20
+ * `<html>` element, the provider root, a `<Density>` island, a portal container.
21
+ * An override is therefore not "set the variable on `:root`": that loses in
22
+ * three separate ways.
23
+ *
24
+ * - A colour written under a bare `[data-theme="x"]` (0,1,0) is outranked by the
25
+ * shipped `[data-theme="x"][data-mode="light"]` block (0,2,0), so the moment
26
+ * the user switches to light the override vanishes. Colours are therefore
27
+ * written **crossed with the mode**, which is also honest: a theme's two modes
28
+ * are two palettes, and editing one should not silently edit the other.
29
+ * - A geometry multiplier written flat outranks the density block that set it,
30
+ * freezing `ultra-compact`'s 0.75 radius scale at 1 — the same trap
31
+ * `createTheme()` documents. Multipliers are therefore **crossed with the
32
+ * density ladder**, one rule per level, with the level's own value folded in.
33
+ * - `--cue-font-scale` is published by `<ThemeProvider>` as an *inline* custom
34
+ * property, and no stylesheet outranks an inline declaration. It is the one
35
+ * token here that needs `!important`, and it gets its own rule so that the
36
+ * flag never spreads to anything that does not need it.
37
+ *
38
+ * @internal Not part of the public API; `./configurator` re-exports what a host
39
+ * app needs.
40
+ */
41
+ /** The one style element the configurator writes into. */
42
+ const OVERRIDE_STYLE_ID = "cue-configurator-overrides";
43
+ /** Default `localStorage` key for the persisted snapshot. */
44
+ const DEFAULT_CONFIGURATOR_STORAGE_KEY = "cue-configurator";
45
+ /**
46
+ * Schema version of the persisted snapshot.
47
+ *
48
+ * A blob from another version is discarded rather than migrated: the payload is
49
+ * a design preference a user can rebuild in a minute, and a wrong migration
50
+ * would paint a page with values nobody chose.
51
+ */
52
+ const CONFIGURATOR_VERSION = 1;
53
+ /** Banner printed at the top of the applied — and exported — stylesheet. */
54
+ const OVERRIDE_BANNER = "@cueplusplus/ui — theme configurator overrides";
55
+ /**
56
+ * The unitless density multiplier each level publishes.
57
+ *
58
+ * A copy of `packages/tokens/src/density/*.tokens.json`, for the same reason
59
+ * `theming/_presets.ts` copies the palettes: the tokens package emits CSS, and a
60
+ * value this module has to *multiply* cannot be read out of a stylesheet.
61
+ * `test/configurator/overrides.test.ts` re-reads the DTCG sources and fails on
62
+ * the first number that has drifted.
63
+ */
64
+ const DENSITY_FACTOR = {
65
+ "ultra-compact": .85,
66
+ "normal": 1,
67
+ "large": 1.3
68
+ };
69
+ /** The authored label tracking, and the value the "reset" arrow returns to. */
70
+ const BASE_TRACKING_LABEL = "0.12em";
71
+ /** The colour tokens the panel edits — the same list a theme block declares. */
72
+ const COLOR_TOKENS = THEME_COLOR_TOKENS;
73
+ /** The type stacks the panel edits. Mode-independent: a typeface is not a palette. */
74
+ const FONT_TOKENS = [
75
+ "font-sans",
76
+ "font-mono",
77
+ "font-display"
78
+ ];
79
+ /**
80
+ * The multipliers that have to be crossed with the density ladder, and the
81
+ * per-level values they are crossed with.
82
+ */
83
+ const SCALED_TOKENS = {
84
+ "radius-scale": DENSITY_RADIUS_SCALE,
85
+ "density": DENSITY_FACTOR
86
+ };
87
+ /** Axis-free tokens: one declaration covers every level. */
88
+ const FLAT_TOKENS = ["tracking-label"];
89
+ /** Tokens the provider publishes inline, which a stylesheet can only reach with `!important`. */
90
+ const INLINE_TOKENS = ["font-scale"];
91
+ /** Every token the configurator is allowed to write, as `--cue-*` properties, in contract order. */
92
+ const MANAGED_TOKENS = [
93
+ ...COLOR_TOKENS,
94
+ ...FONT_TOKENS,
95
+ ...Object.keys(SCALED_TOKENS),
96
+ ...FLAT_TOKENS,
97
+ ...INLINE_TOKENS
98
+ ].map(tokenProperty);
99
+ const KINDS = Object.fromEntries([
100
+ ...COLOR_TOKENS.map((token) => [tokenProperty(token), "color"]),
101
+ ...FONT_TOKENS.map((token) => [tokenProperty(token), "font"]),
102
+ ...Object.keys(SCALED_TOKENS).map((token) => [tokenProperty(token), "scaled"]),
103
+ ...FLAT_TOKENS.map((token) => [tokenProperty(token), "flat"]),
104
+ ...INLINE_TOKENS.map((token) => [tokenProperty(token), "inline"])
105
+ ]);
106
+ /** The density levels, default first — the order the crossed rules are emitted in. */
107
+ const DENSITY_LEVELS = [DEFAULT_DENSITY_LEVEL, ...Object.keys(DENSITY_FACTOR).filter((level) => level !== DEFAULT_DENSITY_LEVEL)];
108
+ /** The nothing-has-moved state. Frozen: it is shared by every caller. */
109
+ const EMPTY_OVERRIDES = Object.freeze({
110
+ themes: Object.freeze({}),
111
+ global: Object.freeze({})
112
+ });
113
+ /** Which of the five buckets a token belongs to, or `null` if it is not ours to write. */
114
+ function tokenKind(token) {
115
+ return KINDS[tokenProperty(token)] ?? null;
116
+ }
117
+ /** The key a theme+mode pair is stored under. */
118
+ function scopeKey(scope) {
119
+ return `${scope.theme}/${scope.mode}`;
120
+ }
121
+ /** The inverse of {@link scopeKey}, rejecting anything that would not be safe in a selector. */
122
+ function parseScopeKey(key) {
123
+ const slash = key.lastIndexOf("/");
124
+ if (slash === -1) return null;
125
+ const theme = key.slice(0, slash);
126
+ const mode = key.slice(slash + 1);
127
+ if (!THEME_NAME_PATTERN.test(theme)) return null;
128
+ if (mode !== "dark" && mode !== "light") return null;
129
+ return {
130
+ theme,
131
+ mode
132
+ };
133
+ }
134
+ /**
135
+ * Whether a value can be printed into a declaration without escaping it.
136
+ *
137
+ * Colours arrive from the picker already re-formatted, but a font stack is free
138
+ * text a user typed, and it ends up inside a stylesheet. This is the same check
139
+ * `theming/serialize.ts` makes — asked as a question instead of thrown as an
140
+ * error, so a field can mark itself invalid while the user is still typing.
141
+ *
142
+ * @param value - The proposed declaration value.
143
+ * @returns `true` when it stays inside its declaration.
144
+ * @example
145
+ * isSafeCssValue("Iosevka, monospace"); // → true
146
+ */
147
+ function isSafeCssValue(value) {
148
+ try {
149
+ assertCssValue("value", value);
150
+ return true;
151
+ } catch {
152
+ return false;
153
+ }
154
+ }
155
+ /**
156
+ * The value a token has before the user touches anything.
157
+ *
158
+ * Colours come from the preset for *this mode*; fonts from the preset's own
159
+ * override, else the base stack; the multipliers are 1 by definition, because
160
+ * what the panel edits is the multiplier and not the ladder underneath it.
161
+ *
162
+ * @param scope - Theme and mode being edited.
163
+ * @param token - A bare token name or a `--cue-*` property.
164
+ * @returns The shipped value, as a CSS string.
165
+ * @example
166
+ * baseValue({ theme: "terminal", mode: "dark" }, "accent"); // → "hsl(180 100% 50%)"
167
+ */
168
+ function baseValue(scope, token) {
169
+ const property = tokenProperty(token);
170
+ const name = property.slice(6);
171
+ const preset = THEME_PRESETS[scope.theme];
172
+ switch (tokenKind(property)) {
173
+ case "color": return ((scope.mode === "light" ? preset?.light : preset?.dark) ?? preset?.dark)?.[name] ?? "";
174
+ case "font": return preset?.fonts[name] ?? BASE_FONT_STACKS[name] ?? "";
175
+ case "scaled":
176
+ case "inline": return "1";
177
+ case "flat": return BASE_TRACKING_LABEL;
178
+ default: return "";
179
+ }
180
+ }
181
+ /** The scope keys a token's declaration is stored under: both modes for a font, else one. */
182
+ function storageScopes(scope, kind) {
183
+ if (kind === "font") return [scopeKey({
184
+ ...scope,
185
+ mode: "dark"
186
+ }), scopeKey({
187
+ ...scope,
188
+ mode: "light"
189
+ })];
190
+ return [scopeKey(scope)];
191
+ }
192
+ /** A shallow clone deep enough to edit one scope without aliasing the rest. */
193
+ function cloneOverrides(overrides) {
194
+ const themes = {};
195
+ for (const [key, entries] of Object.entries(overrides.themes)) themes[key] = { ...entries };
196
+ return {
197
+ themes,
198
+ global: { ...overrides.global }
199
+ };
200
+ }
201
+ /** Drop scopes that no longer hold anything, so `serializeOverrideCss` emits no empty rules. */
202
+ function prune(overrides) {
203
+ for (const [key, entries] of Object.entries(overrides.themes)) if (Object.keys(entries).length === 0) delete overrides.themes[key];
204
+ return overrides;
205
+ }
206
+ /**
207
+ * Move a token, returning a new state.
208
+ *
209
+ * Setting a token back to the value the preset ships is a *reset*, not an
210
+ * override — otherwise "I put it back" would still export a declaration and the
211
+ * user would carry a no-op forever.
212
+ *
213
+ * @param overrides - Current state. Never mutated.
214
+ * @param scope - Theme and mode being edited.
215
+ * @param token - A bare token name or a `--cue-*` property.
216
+ * @param value - The new CSS value.
217
+ * @returns The new state.
218
+ * @throws TypeError if the token is not one the configurator manages, or the
219
+ * value could break out of its declaration.
220
+ * @example
221
+ * setOverride(overrides, { theme: "terminal", mode: "dark" }, "accent", "#ff8800");
222
+ */
223
+ function setOverride(overrides, scope, token, value) {
224
+ const property = tokenProperty(token);
225
+ const kind = tokenKind(property);
226
+ if (kind === null) throw new TypeError(`${property} is not a token the configurator manages`);
227
+ assertCssValue(property, value);
228
+ if (value === baseValue(scope, property)) return clearOverride(overrides, scope, property);
229
+ const next = cloneOverrides(overrides);
230
+ if (kind === "color" || kind === "font") for (const key of storageScopes(scope, kind)) next.themes[key] = {
231
+ ...next.themes[key],
232
+ [property]: value
233
+ };
234
+ else next.global[property] = value;
235
+ return prune(next);
236
+ }
237
+ /**
238
+ * Put a token back to its shipped value, returning a new state.
239
+ *
240
+ * @param overrides - Current state. Never mutated.
241
+ * @param scope - Theme and mode being edited.
242
+ * @param token - A bare token name or a `--cue-*` property.
243
+ * @returns The new state.
244
+ * @example
245
+ * clearOverride(overrides, scope, "--cue-accent");
246
+ */
247
+ function clearOverride(overrides, scope, token) {
248
+ const property = tokenProperty(token);
249
+ const kind = tokenKind(property);
250
+ if (kind === null) return overrides;
251
+ const next = cloneOverrides(overrides);
252
+ if (kind === "color" || kind === "font") for (const key of storageScopes(scope, kind)) delete next.themes[key]?.[property];
253
+ else delete next.global[property];
254
+ return prune(next);
255
+ }
256
+ /** Whether a token currently carries an override in this scope. */
257
+ function isOverridden(overrides, scope, token) {
258
+ const property = tokenProperty(token);
259
+ const kind = tokenKind(property);
260
+ if (kind === null) return false;
261
+ if (kind === "color" || kind === "font") return overrides.themes[scopeKey(scope)]?.[property] !== void 0;
262
+ return overrides.global[property] !== void 0;
263
+ }
264
+ /**
265
+ * How many declarations the user has made.
266
+ *
267
+ * Counted as declarations rather than as decisions: a font family is one choice
268
+ * that produces two declarations, one per mode, and the number in the panel is
269
+ * the number that will appear in the export.
270
+ */
271
+ function overrideCount(overrides) {
272
+ let total = Object.keys(overrides.global).length;
273
+ for (const entries of Object.values(overrides.themes)) total += Object.keys(entries).length;
274
+ return total;
275
+ }
276
+ /** The value a token has right now: the override if there is one, else the shipped value. */
277
+ function resolveValue(overrides, scope, token) {
278
+ const property = tokenProperty(token);
279
+ const kind = tokenKind(property);
280
+ if (kind === null) return "";
281
+ return (kind === "color" || kind === "font" ? overrides.themes[scopeKey(scope)]?.[property] : overrides.global[property]) ?? baseValue(scope, property);
282
+ }
283
+ /**
284
+ * Every token the configurator manages, resolved for one scope.
285
+ *
286
+ * This is what `onChange` hands a host app and what the registry export writes:
287
+ * a complete picture, not a diff, so a consumer never has to know which preset
288
+ * the values came from.
289
+ *
290
+ * @param overrides - Current state.
291
+ * @param scope - Theme and mode to resolve for.
292
+ * @returns `--cue-*` property → CSS value.
293
+ */
294
+ function resolvedTokens(overrides, scope) {
295
+ return Object.fromEntries(MANAGED_TOKENS.map((property) => [property, resolveValue(overrides, scope, property)]));
296
+ }
297
+ /** Declarations of one scope, ordered by the token contract rather than by edit order. */
298
+ function orderedDeclarations(entries) {
299
+ return MANAGED_TOKENS.filter((property) => entries[property] !== void 0).map((property) => [property, entries[property]]);
300
+ }
301
+ /**
302
+ * The selector a theme-scoped declaration lands under.
303
+ *
304
+ * Not `themeSelector()` from the theming group, which emits the *authoring*
305
+ * form: a bare `[data-theme="x"]` for dark, because that is the block a
306
+ * stylesheet writes first. An override has to beat that block **and** the light
307
+ * block above it, so it always names the mode.
308
+ */
309
+ function overrideSelector(theme, mode) {
310
+ return `[data-theme="${theme}"][data-mode="${mode}"]`;
311
+ }
312
+ /** Round a product of two scales to something a stylesheet can print. */
313
+ const scaled = (a, b) => String(Number((a * b).toFixed(4)));
314
+ /** A multiplier, crossed with one density level's own value. */
315
+ function crossed(property, value, level) {
316
+ const ladder = SCALED_TOKENS[property.slice(6)];
317
+ const factor = Number(value);
318
+ if (ladder === void 0 || !Number.isFinite(factor)) return value;
319
+ return scaled(factor, ladder[level]);
320
+ }
321
+ const THEME_COMMENT = "the mode is named so the override outranks the shipped light block";
322
+ const CROSSED_COMMENT = "crossed with the density ladder — flat would freeze ultra-compact's 0.75";
323
+ const INLINE_COMMENT = "the provider inlines this one; !important is the only lever left";
324
+ /**
325
+ * Print the overrides as a stylesheet.
326
+ *
327
+ * Returns `""` when nothing has moved — an empty stylesheet is not a stylesheet
328
+ * with an empty rule in it, and the runtime uses the empty string as its signal
329
+ * to remove the style element entirely.
330
+ *
331
+ * @param overrides - Current state.
332
+ * @param options - Banner text.
333
+ * @returns The stylesheet, newline-terminated, or `""`.
334
+ * @example
335
+ * serializeOverrideCss(overrides, { banner: OVERRIDE_BANNER });
336
+ */
337
+ function serializeOverrideCss(overrides, options = {}) {
338
+ const blocks = [];
339
+ for (const key of Object.keys(overrides.themes).toSorted()) {
340
+ const scope = parseScopeKey(key);
341
+ const declarations = orderedDeclarations(overrides.themes[key] ?? {});
342
+ if (scope === null || declarations.length === 0) continue;
343
+ blocks.push({
344
+ selector: overrideSelector(scope.theme, scope.mode),
345
+ sections: [{
346
+ comment: blocks.length === 0 ? THEME_COMMENT : void 0,
347
+ declarations
348
+ }]
349
+ });
350
+ }
351
+ const global = orderedDeclarations(overrides.global);
352
+ const scaledDeclarations = global.filter(([property]) => tokenKind(property) === "scaled");
353
+ const flatDeclarations = global.filter(([property]) => tokenKind(property) === "flat");
354
+ const inlineDeclarations = global.filter(([property]) => tokenKind(property) === "inline");
355
+ if (scaledDeclarations.length > 0) for (const level of DENSITY_LEVELS) {
356
+ const isDefault = level === DEFAULT_DENSITY_LEVEL;
357
+ blocks.push({
358
+ selector: isDefault ? `:root,\n[data-density="${level}"]` : `[data-density="${level}"]`,
359
+ sections: [{
360
+ comment: isDefault ? CROSSED_COMMENT : void 0,
361
+ declarations: scaledDeclarations.map(([property, value]) => [property, crossed(property, value, level)])
362
+ }]
363
+ });
364
+ }
365
+ if (flatDeclarations.length > 0) blocks.push({
366
+ selector: ":root,\n[data-density]",
367
+ sections: [{ declarations: flatDeclarations }]
368
+ });
369
+ if (inlineDeclarations.length > 0) blocks.push({
370
+ selector: ":root,\n[data-cue-root],\n[data-cue-portal]",
371
+ sections: [{
372
+ comment: INLINE_COMMENT,
373
+ declarations: inlineDeclarations.map(([property, value]) => [property, `${value} !important`])
374
+ }]
375
+ });
376
+ if (blocks.length === 0) return "";
377
+ return `${[...options.banner === void 0 ? [] : [`/*! ${options.banner}\n * Written by <ThemeConfigurator>. Paste it, do not edit it.\n */`], ...blocks.map(serializeThemeBlock)].join("\n\n")}\n`;
378
+ }
379
+ /**
380
+ * The stylesheet the panel applies — and, byte for byte, the one its CSS export
381
+ * hands you. One function so the two can never drift.
382
+ *
383
+ * @param overrides - Current state.
384
+ * @returns The banner-topped stylesheet, or `""` when nothing has moved.
385
+ */
386
+ function configuratorCss(overrides) {
387
+ return serializeOverrideCss(overrides, { banner: OVERRIDE_BANNER });
388
+ }
389
+ /**
390
+ * Put the stylesheet into the document, creating the style element if needed.
391
+ *
392
+ * One element, identified by {@link OVERRIDE_STYLE_ID}, appended to `<head>`
393
+ * after everything else — later in the cascade is half of how these overrides
394
+ * win. An empty stylesheet removes the element instead of leaving an empty one
395
+ * behind, so "reset all" leaves the document exactly as it found it.
396
+ *
397
+ * @param css - The stylesheet, from {@link configuratorCss}.
398
+ * @param doc - Document to write into. Defaults to the ambient one.
399
+ * @returns The style element, or `null` when there was nothing to apply.
400
+ */
401
+ function applyOverrideCss(css, doc = globalThis.document) {
402
+ if (doc === void 0) return null;
403
+ if (css === "") {
404
+ removeOverrideStyles(doc);
405
+ return null;
406
+ }
407
+ let element = doc.getElementById(OVERRIDE_STYLE_ID);
408
+ if (element === null) {
409
+ element = doc.createElement("style");
410
+ element.id = OVERRIDE_STYLE_ID;
411
+ doc.head.appendChild(element);
412
+ }
413
+ if (element.textContent !== css) element.textContent = css;
414
+ return element;
415
+ }
416
+ /** Take the override stylesheet back out of the document. */
417
+ function removeOverrideStyles(doc = globalThis.document) {
418
+ doc?.getElementById(OVERRIDE_STYLE_ID)?.remove();
419
+ }
420
+ /** Keep only the entries this module is willing to print into a stylesheet. */
421
+ function sanitizeEntries(input) {
422
+ const entries = {};
423
+ if (input === null || typeof input !== "object") return entries;
424
+ for (const [property, value] of Object.entries(input)) {
425
+ if (typeof value !== "string") continue;
426
+ if (!MANAGED_TOKENS.includes(property)) continue;
427
+ if (!isSafeCssValue(value)) continue;
428
+ entries[property] = value;
429
+ }
430
+ return entries;
431
+ }
432
+ /** The persisted blob, read defensively — it is user-editable text that becomes CSS. */
433
+ function sanitizeOverrides(input) {
434
+ const overrides = {
435
+ themes: {},
436
+ global: {}
437
+ };
438
+ if (input === null || typeof input !== "object") return overrides;
439
+ const source = input;
440
+ if (source.themes !== null && typeof source.themes === "object") for (const [key, entries] of Object.entries(source.themes)) {
441
+ if (parseScopeKey(key) === null) continue;
442
+ const clean = sanitizeEntries(entries);
443
+ if (Object.keys(clean).length > 0) overrides.themes[key] = clean;
444
+ }
445
+ overrides.global = sanitizeEntries(source.global);
446
+ return overrides;
447
+ }
448
+ /** The snapshot a first-time visitor gets. */
449
+ function defaultSnapshot() {
450
+ return {
451
+ version: 1,
452
+ overrides: EMPTY_OVERRIDES,
453
+ zoom: 1,
454
+ panel: {
455
+ position: "floating",
456
+ x: 24,
457
+ y: 24,
458
+ collapsed: false
459
+ }
460
+ };
461
+ }
462
+ /**
463
+ * Read the persisted snapshot.
464
+ *
465
+ * Every field is validated, and anything that fails validation is dropped
466
+ * rather than repaired — the blob is text a user can edit in devtools, and its
467
+ * values are printed straight into a stylesheet.
468
+ *
469
+ * @param storageKey - The `localStorage` key.
470
+ * @returns The snapshot, or `null` when there is nothing usable stored.
471
+ */
472
+ function readSnapshot(storageKey) {
473
+ if (typeof localStorage === "undefined") return null;
474
+ let parsed;
475
+ try {
476
+ const raw = localStorage.getItem(storageKey);
477
+ if (raw === null) return null;
478
+ parsed = JSON.parse(raw);
479
+ } catch {
480
+ return null;
481
+ }
482
+ if (parsed === null || typeof parsed !== "object") return null;
483
+ const stored = parsed;
484
+ if (stored.version !== 1) return null;
485
+ const fallback = defaultSnapshot();
486
+ const panel = stored.panel ?? {};
487
+ const number = (value, or) => typeof value === "number" && Number.isFinite(value) ? value : or;
488
+ return {
489
+ version: 1,
490
+ overrides: sanitizeOverrides(stored.overrides),
491
+ zoom: number(stored.zoom, fallback.zoom),
492
+ panel: {
493
+ position: panel["position"] === "docked-right" ? "docked-right" : "floating",
494
+ x: number(panel["x"], fallback.panel.x),
495
+ y: number(panel["y"], fallback.panel.y),
496
+ collapsed: panel["collapsed"] === true
497
+ }
498
+ };
499
+ }
500
+ /**
501
+ * Persist the snapshot. A full quota or a private-mode failure is swallowed —
502
+ * a preference is a nicety, never a hard failure.
503
+ *
504
+ * @param storageKey - The `localStorage` key.
505
+ * @param snapshot - What to store.
506
+ */
507
+ function writeSnapshot(storageKey, snapshot) {
508
+ if (typeof localStorage === "undefined") return;
509
+ try {
510
+ localStorage.setItem(storageKey, JSON.stringify(snapshot));
511
+ } catch {}
512
+ }
513
+ //#endregion
514
+ export { BASE_TRACKING_LABEL, COLOR_TOKENS, CONFIGURATOR_VERSION, DEFAULT_CONFIGURATOR_STORAGE_KEY, DENSITY_FACTOR, EMPTY_OVERRIDES, FLAT_TOKENS, FONT_TOKENS, INLINE_TOKENS, MANAGED_TOKENS, OVERRIDE_BANNER, OVERRIDE_STYLE_ID, SCALED_TOKENS, applyOverrideCss, baseValue, clearOverride, configuratorCss, defaultSnapshot, isOverridden, isSafeCssValue, overrideCount, readSnapshot, removeOverrideStyles, resolveValue, resolvedTokens, scopeKey, serializeOverrideCss, setOverride, tokenKind, writeSnapshot };
@@ -0,0 +1,71 @@
1
+ import { ConfiguratorPosition } from "./_overrides.js";
2
+ import * as React from "react";
3
+ //#region src/configurator/configurator.d.ts
4
+ interface ThemeConfiguratorProps {
5
+ /** Placement, controlled. Pair with `onPositionChange`. */
6
+ position?: ConfiguratorPosition;
7
+ /** Initial placement. A stored one wins. Defaults to `"floating"`. */
8
+ defaultPosition?: ConfiguratorPosition;
9
+ /** Called when the user docks or floats the panel. */
10
+ onPositionChange?: (position: ConfiguratorPosition) => void;
11
+ /** Start furled to the title bar. A stored value wins. Defaults to `false`. */
12
+ defaultCollapsed?: boolean;
13
+ /**
14
+ * `localStorage` key holding the overrides, the placement and the zoom.
15
+ * Defaults to `"cue-configurator"`.
16
+ */
17
+ storageKey?: string;
18
+ /**
19
+ * Called with the full resolved `--cue-*` map — never a diff — whenever the
20
+ * overrides, the theme or the mode change, and once on mount.
21
+ */
22
+ onChange?: (tokens: Record<string, string>) => void;
23
+ /** Bind ⌘/Ctrl with `+`, `-` and `0` to the zoom valve. Defaults to `true`. */
24
+ zoomShortcuts?: boolean;
25
+ /** Panel heading. Defaults to `"Theme"`. */
26
+ title?: React.ReactNode;
27
+ /** Extra classes for the panel. */
28
+ className?: string;
29
+ }
30
+ /**
31
+ * The floating theme configurator: the panel that edits the design system it is
32
+ * drawn with.
33
+ *
34
+ * It is a **component**, not a devtool bolted onto the kitchensink — mount it in
35
+ * any app behind a flag and a designer can retune that app's theme in place. The
36
+ * whole thing rests on one rule, and the rule is what keeps it from being a toy:
37
+ * *everything it does is a `--cue-*` declaration in one stylesheet.* No inline
38
+ * styles on the root, no parallel token vocabulary, no runtime theme engine. The
39
+ * CSS it exports is byte for byte the CSS the page is wearing, which is the only
40
+ * reason the export dialog can promise a round trip at all.
41
+ *
42
+ * What it owns:
43
+ *
44
+ * - **Preset, mode and density** are the provider's, driven through `useTheme()`
45
+ * so a switch restamps `<html>`, every portal, and the persisted preference.
46
+ * - **Colour, type and geometry overrides** go into
47
+ * `<style id="cue-configurator-overrides">`, scoped so they win where they
48
+ * have to and lose where they should — the cascade reasoning is in
49
+ * `_overrides.ts`, and it is the interesting part of this group.
50
+ * - **Text scale and root zoom**, the two relief valves the density ladder is
51
+ * paired with.
52
+ * - **Its own placement**, furled state and every override, in `localStorage`
53
+ * under one key.
54
+ *
55
+ * Renders in place as a `position: fixed` region, so mount it inside the
56
+ * provider — it needs the theme context — but outside any transformed ancestor,
57
+ * which would turn `fixed` into `absolute` and take the panel with the page.
58
+ *
59
+ * @throws If rendered outside a `<ThemeProvider>`: there is no theme to
60
+ * configure, which is a wiring bug rather than a fallback situation.
61
+ * @example
62
+ * <ThemeProvider>
63
+ * <App />
64
+ * {devMode ? <ThemeConfigurator /> : null}
65
+ * </ThemeProvider>
66
+ * @example
67
+ * <ThemeConfigurator position="docked-right" onChange={(tokens) => report(tokens)} />
68
+ */
69
+ declare function ThemeConfigurator({ position: positionProp, defaultPosition, onPositionChange, defaultCollapsed, storageKey, onChange, zoomShortcuts, title, className }: ThemeConfiguratorProps): React.JSX.Element;
70
+ //#endregion
71
+ export { ThemeConfigurator, ThemeConfiguratorProps };