@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,442 @@
1
+ import { formatColorString } from "../color/_convert.js";
2
+ import { BASE_FONT_STACKS, DEFAULT_DENSITY_LEVEL, DENSITY_RADIUS_SCALE, THEME_COLOR_TOKENS, THEME_PRESETS } from "./_presets.js";
3
+ import { WCAG_AA_TEXT, contrastRatio, contrastReport } from "./contrast.js";
4
+ import { assertCssValue, assertThemeName, derivedTokens, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
5
+ import { clampChroma, converter, formatHex, inGamut, parse } from "culori";
6
+ //#region src/theming/create-theme.ts
7
+ /**
8
+ * `createTheme()` — the typed generator behind the spec's second theming tier.
9
+ *
10
+ * Three things it is not, because each of them is a trap this deliberately
11
+ * avoids:
12
+ *
13
+ * - **Not a runtime engine.** It is a pure function of its arguments, called at
14
+ * build time (or once, in the configurator's export dialog), and what comes
15
+ * out is a plain `[data-theme]` stylesheet — tier 1, the guaranteed contract.
16
+ * Nothing about a shipped app depends on this module existing.
17
+ * - **Not a palette generator.** Handed a preset and nothing else it returns the
18
+ * preset, byte for byte. Derivation is what happens to the tokens hanging off
19
+ * an anchor you actually moved, and only those: swap the accent and the hover,
20
+ * the ink on it, the focus ring and the selection wash follow; the ground, the
21
+ * ink and the five status tones do not. That rule is the whole reason
22
+ * "start from `terminal`, make it orange" is a two-line call rather than a
23
+ * fork of a palette.
24
+ * - **Not a colour opinion.** Every derivation happens in oklch, where a
25
+ * lightness step is a step the eye agrees with, and every result is measured
26
+ * against WCAG before you are told it is fine. A failing pair comes back in
27
+ * the report rather than being silently repaired — with one exception, noted
28
+ * below, where the value was this module's invention in the first place.
29
+ */
30
+ /** The preset a theme starts from when the caller names none. */
31
+ const DEFAULT_THEME_BASE = "cue";
32
+ /**
33
+ * How far the sunken ground sits below the page, in oklch lightness.
34
+ *
35
+ * Small on purpose: sunken is a well, not a second theme. Across the eight
36
+ * shipped presets the authored gap runs −0.011 to −0.031.
37
+ */
38
+ const SUNKEN_LIGHTNESS_STEP = -.02;
39
+ /**
40
+ * The three raised surfaces, as lightness above the ground.
41
+ *
42
+ * Even spacing, because the ladder encodes *elevation* and a reader counts
43
+ * rungs. The shipped presets cluster around these three values.
44
+ */
45
+ const SURFACE_LIGHTNESS_STEPS = [
46
+ .03,
47
+ .05,
48
+ .07
49
+ ];
50
+ /**
51
+ * The hairline and its stronger sibling, as lightness above the ground.
52
+ *
53
+ * Above the top surface, so a border still reads as a line when it is drawn on
54
+ * `surface-3`. `border-overlay` reuses the strong value: an overlay floats over
55
+ * everything, so its rim has to hold against the brightest thing under it.
56
+ */
57
+ const BORDER_LIGHTNESS_STEPS = [.08, .14];
58
+ /** How far secondary ink recedes from primary ink. */
59
+ const FG_MUTED_LIGHTNESS_STEP = -.25;
60
+ /** How far tertiary ink recedes from primary ink. */
61
+ const FG_SUBTLE_LIGHTNESS_STEP = -.45;
62
+ /**
63
+ * Chroma each rung of the ink ramp keeps from the rung above.
64
+ *
65
+ * Ink greys as it recedes rather than staying saturated: a muted label that held
66
+ * full chroma would read as a *coloured* label — a status, a link — instead of a
67
+ * quiet one. The hue is preserved throughout, so the ramp still belongs to the
68
+ * theme. `fg-subtle` takes the square, being two rungs down.
69
+ */
70
+ const INK_RAMP_CHROMA_SCALE = .6;
71
+ /** How far the accent darkens on hover. */
72
+ const ACCENT_HOVER_LIGHTNESS_STEP = -.05;
73
+ /** How much accent a derived selection wash carries. */
74
+ const SELECTION_ACCENT_PERCENT = 22;
75
+ /** What a label on a solid accent has to reach before this module accepts it. */
76
+ const ACCENT_FG_MINIMUM = WCAG_AA_TEXT;
77
+ /**
78
+ * How much of the dark ground's lightness survives the flip to paper.
79
+ *
80
+ * A ground's job is to be the extreme, so inverting it means going to the *other*
81
+ * extreme rather than to the arithmetic mirror: `1 − L` of a 0.16 near-black is a
82
+ * 0.84 mid-grey, which is not a page. Compressing instead maps the ladder of
83
+ * dark grounds onto a narrow paper band — a deeper dark theme gets a cleaner
84
+ * white page — and lands within 0.01 of all eight authored light presets.
85
+ */
86
+ const LIGHT_GROUND_COMPRESSION = .1;
87
+ /** Lightness a derived light colour steps down by while hunting for contrast. */
88
+ const LIGHT_DARKEN_STEP = .02;
89
+ /** How many steps that hunt may take before it gives up and returns its darkest. */
90
+ const LIGHT_DARKEN_LIMIT = 40;
91
+ const toOklch = converter("oklch");
92
+ const inSrgb = inGamut("rgb");
93
+ /** Read an anchor into oklch, or say which anchor could not be read. */
94
+ function readColor(value, label) {
95
+ const color = toOklch(parse(value));
96
+ if (color === void 0) throw new TypeError(`${label} is not a colour: ${JSON.stringify(value)}`);
97
+ return {
98
+ mode: "oklch",
99
+ l: color.l,
100
+ c: color.c,
101
+ h: color.h ?? 0
102
+ };
103
+ }
104
+ /**
105
+ * Print a derived colour.
106
+ *
107
+ * Chroma is **clamped** into sRGB rather than clipped — clipping a too-saturated
108
+ * step turns it into a different hue, whereas clamping only gives up the
109
+ * saturation no display was going to show. Then it goes out through the same
110
+ * formatter the colour picker writes with, so a generated token and a
111
+ * hand-picked one are spelled the same way.
112
+ */
113
+ function emit(color) {
114
+ const hex = formatHex(inSrgb(color) ? color : clampChroma(color, "oklch", "rgb"));
115
+ return formatColorString(hex, "oklch") ?? hex;
116
+ }
117
+ const clampLightness = (lightness) => Math.min(1, Math.max(0, lightness));
118
+ /** The same hue, moved along the lightness axis and optionally greyed. */
119
+ function shift(color, delta, chromaScale = 1) {
120
+ return emit({
121
+ mode: "oklch",
122
+ l: clampLightness(color.l + delta),
123
+ c: color.c * chromaScale,
124
+ h: color.h
125
+ });
126
+ }
127
+ /**
128
+ * The candidate that can be read on `ground`, preferring the theme's own ink.
129
+ *
130
+ * Tried in order: the colours the theme already owns, then pure black and white
131
+ * as a last resort. A theme's own ground or ink keeps the palette coherent; the
132
+ * fallback exists because an accent at mid lightness contrasts with neither.
133
+ */
134
+ function pickInk(ground, candidates) {
135
+ let best = candidates[0] ?? "#000000";
136
+ let bestRatio = -1;
137
+ for (const candidate of candidates) {
138
+ const ratio = contrastRatio(candidate, ground);
139
+ if (ratio > bestRatio) [best, bestRatio] = [candidate, ratio];
140
+ }
141
+ if (bestRatio >= ACCENT_FG_MINIMUM) return best;
142
+ for (const candidate of ["#000000", "#ffffff"]) {
143
+ const ratio = contrastRatio(candidate, ground);
144
+ if (ratio > bestRatio) [best, bestRatio] = [candidate, ratio];
145
+ }
146
+ return best;
147
+ }
148
+ /**
149
+ * Darken a colour until it can be read on `ground`.
150
+ *
151
+ * Only ever applied to values this module invented — the light variant's accent
152
+ * and status tones, which have to survive being moved from a near-black ground
153
+ * to paper. An authored colour is never quietly adjusted; it is reported.
154
+ */
155
+ function darkenToContrast(value, ground, minimum, label) {
156
+ const start = readColor(value, label);
157
+ for (let step = 0; step <= LIGHT_DARKEN_LIMIT; step += 1) {
158
+ const candidate = emit({
159
+ ...start,
160
+ l: clampLightness(start.l - step * LIGHT_DARKEN_STEP)
161
+ });
162
+ if (contrastRatio(candidate, ground) >= minimum) return candidate;
163
+ }
164
+ return emit({
165
+ ...start,
166
+ l: clampLightness(start.l - LIGHT_DARKEN_LIMIT * LIGHT_DARKEN_STEP)
167
+ });
168
+ }
169
+ /** Own-property lookup that admits a token may be absent. */
170
+ function lookup(map, token) {
171
+ return map != null && Object.hasOwn(map, token) ? map[token] : void 0;
172
+ }
173
+ /**
174
+ * A derived selection wash, as a live expression rather than a resolved colour.
175
+ *
176
+ * `var(--cue-accent)` is a self-reference — it resolves inside whichever block
177
+ * declares it — so one string is correct in the dark block and the light one,
178
+ * and stays correct if a consumer overrides the accent in tier 1 later.
179
+ */
180
+ const SELECTION_WASH = `color-mix(in oklch, var(--cue-accent) 22%, transparent)`;
181
+ /** The five status tones, in the order every theme block declares them. */
182
+ const STATUSES = [
183
+ "ok",
184
+ "busy",
185
+ "warn",
186
+ "danger",
187
+ "info"
188
+ ];
189
+ /** The three raised surfaces, in ladder order. */
190
+ const SURFACES = [
191
+ "surface-1",
192
+ "surface-2",
193
+ "surface-3"
194
+ ];
195
+ /**
196
+ * Resolve the dark block.
197
+ *
198
+ * The shape of every branch is the same: an explicit override wins; otherwise
199
+ * the preset's value stands *unless the anchor this token hangs off has moved*;
200
+ * only then is anything derived.
201
+ */
202
+ function resolveDark(anchors, preset) {
203
+ const colors = {};
204
+ const authored = /* @__PURE__ */ new Set();
205
+ const surfaces = anchors.surfaces ?? {};
206
+ const status = anchors.status ?? {};
207
+ /**
208
+ * One token. `derive` is omitted for the anchors themselves — a preset that
209
+ * declares no ground is not something to paper over with an invented one.
210
+ */
211
+ const define = (token, explicit, moved, derive) => {
212
+ if (explicit !== void 0) {
213
+ readColor(explicit, token);
214
+ colors[token] = explicit;
215
+ return;
216
+ }
217
+ const inherited = lookup(preset, token);
218
+ if (!moved && inherited !== void 0) {
219
+ colors[token] = inherited;
220
+ authored.add(token);
221
+ return;
222
+ }
223
+ if (derive === void 0) throw new TypeError(`the base preset declares no ${token}`);
224
+ colors[token] = derive();
225
+ };
226
+ const groundMoved = anchors.bg !== void 0;
227
+ const inkMoved = anchors.fg !== void 0;
228
+ const accentMoved = anchors.accent !== void 0;
229
+ define("bg", anchors.bg, false);
230
+ const ground = readColor(colors["bg"], "bg");
231
+ define("sunken", surfaces.sunken, groundMoved, () => shift(ground, SUNKEN_LIGHTNESS_STEP));
232
+ SURFACES.forEach((token, index) => {
233
+ define(token, surfaces[token], groundMoved, () => shift(ground, SURFACE_LIGHTNESS_STEPS[index]));
234
+ });
235
+ define("fg", anchors.fg, false);
236
+ const ink = readColor(colors["fg"], "fg");
237
+ define("fg-muted", void 0, inkMoved, () => shift(ink, FG_MUTED_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE));
238
+ define("fg-subtle", void 0, inkMoved, () => shift(ink, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE ** 2));
239
+ define("border", void 0, groundMoved, () => shift(ground, BORDER_LIGHTNESS_STEPS[0]));
240
+ define("border-strong", void 0, groundMoved, () => shift(ground, BORDER_LIGHTNESS_STEPS[1]));
241
+ define("border-overlay", void 0, groundMoved, () => colors["border-strong"]);
242
+ define("accent", anchors.accent, false);
243
+ const accent = colors["accent"];
244
+ define("accent-hover", void 0, accentMoved, () => shift(readColor(accent, "accent"), ACCENT_HOVER_LIGHTNESS_STEP));
245
+ define("accent-fg", void 0, accentMoved, () => pickInk(accent, [colors["bg"], colors["fg"]]));
246
+ for (const tone of STATUSES) define(tone, status[tone], false);
247
+ define("selection", void 0, accentMoved, () => SELECTION_WASH);
248
+ define("focus", void 0, accentMoved, () => accent);
249
+ define("data-ground", void 0, false);
250
+ define("scrim", void 0, false);
251
+ return {
252
+ colors,
253
+ authored
254
+ };
255
+ }
256
+ /**
257
+ * Resolve the light block.
258
+ *
259
+ * Per token: if the dark value came from the preset untouched and the preset
260
+ * ships a light block, the authored light value stands. Everything else is
261
+ * inverted — which is guesswork, and is why any derivation here marks the report
262
+ * provisional.
263
+ *
264
+ * The inversion is not `1 − L` across the board. The ground goes to paper (see
265
+ * {@link LIGHT_GROUND_COMPRESSION}) and the ladder then steps *down* from it,
266
+ * because paper leaves no headroom above: a "raised" surface on a white page is
267
+ * the slightly grey one, and the sunken well — the one rung that cannot keep its
268
+ * sign — folds to the far end, which is still the deepest thing on the page.
269
+ */
270
+ function resolveLight(dark, preset) {
271
+ const colors = {};
272
+ let provisional = false;
273
+ const define = (token, derive) => {
274
+ const inherited = dark.authored.has(token) ? lookup(preset, token) : void 0;
275
+ if (inherited !== void 0) {
276
+ colors[token] = inherited;
277
+ return;
278
+ }
279
+ colors[token] = derive();
280
+ provisional = true;
281
+ };
282
+ const darkGround = readColor(dark.colors["bg"], "bg");
283
+ define("bg", () => emit({
284
+ mode: "oklch",
285
+ l: clampLightness(1 - LIGHT_GROUND_COMPRESSION * darkGround.l),
286
+ c: darkGround.c,
287
+ h: darkGround.h
288
+ }));
289
+ const paper = readColor(colors["bg"], "bg");
290
+ /** Deepest rung: past the top surface by the sunken step. */
291
+ const sunkenDepth = (SURFACE_LIGHTNESS_STEPS.at(-1) ?? 0) + Math.abs(SUNKEN_LIGHTNESS_STEP);
292
+ define("sunken", () => shift(paper, -sunkenDepth));
293
+ SURFACES.forEach((token, index) => {
294
+ define(token, () => shift(paper, -SURFACE_LIGHTNESS_STEPS[index]));
295
+ });
296
+ const darkInk = readColor(dark.colors["fg"], "fg");
297
+ define("fg", () => emit({
298
+ mode: "oklch",
299
+ l: clampLightness(1 - darkInk.l),
300
+ c: darkInk.c,
301
+ h: darkInk.h
302
+ }));
303
+ const ink = readColor(colors["fg"], "fg");
304
+ define("fg-muted", () => shift(ink, .25, INK_RAMP_CHROMA_SCALE));
305
+ define("fg-subtle", () => shift(ink, .45, INK_RAMP_CHROMA_SCALE ** 2));
306
+ define("border", () => shift(paper, -BORDER_LIGHTNESS_STEPS[0]));
307
+ define("border-strong", () => shift(paper, -BORDER_LIGHTNESS_STEPS[1]));
308
+ define("border-overlay", () => colors["border-strong"]);
309
+ const paperGround = colors["bg"];
310
+ define("accent", () => darkenToContrast(dark.colors["accent"], paperGround, WCAG_AA_TEXT, "accent"));
311
+ const accent = colors["accent"];
312
+ define("accent-hover", () => shift(readColor(accent, "accent"), ACCENT_HOVER_LIGHTNESS_STEP));
313
+ define("accent-fg", () => pickInk(accent, [colors["bg"], colors["fg"]]));
314
+ for (const tone of STATUSES) define(tone, () => darkenToContrast(dark.colors[tone], paperGround, 3, tone));
315
+ define("selection", () => SELECTION_WASH);
316
+ define("focus", () => accent);
317
+ define("data-ground", () => dark.colors["data-ground"]);
318
+ define("scrim", () => dark.colors["scrim"]);
319
+ return {
320
+ colors,
321
+ provisional
322
+ };
323
+ }
324
+ /** The font declarations a theme block carries. */
325
+ function resolveFonts(anchors, preset) {
326
+ const fonts = anchors.fonts ?? {};
327
+ const declared = [];
328
+ if (fonts.sans !== void 0) declared.push(["font-sans", fonts.sans]);
329
+ declared.push(["font-mono", fonts.mono ?? lookup(preset, "font-mono") ?? BASE_FONT_STACKS["font-mono"]]);
330
+ if (fonts.display !== void 0) declared.push(["font-display", fonts.display]);
331
+ return declared.map(([name, value]) => [tokenProperty(name), assertCssValue(name, value)]);
332
+ }
333
+ /** Round a product of two scales to something a stylesheet can print. */
334
+ const scaled = (a, b) => String(Number((a * b).toFixed(4)));
335
+ /**
336
+ * The radius rules for a theme that asked for one.
337
+ *
338
+ * Geometry belongs to the density axis, so the multiplier is **crossed** with it
339
+ * rather than declared flat: an uncrossed `--cue-radius-scale` in a theme block
340
+ * outranks the density block that set it and would freeze `ultra-compact`'s 0.75
341
+ * at 1. The base block carries the product for the default density — that is the
342
+ * value an unstamped document falls back to — and every density whose own scale
343
+ * differs gets one crossed rule.
344
+ */
345
+ function radiusBlocks(name, radiusScale) {
346
+ const property = tokenProperty("radius-scale");
347
+ const fallback = DENSITY_RADIUS_SCALE[DEFAULT_DENSITY_LEVEL];
348
+ const blocks = [];
349
+ for (const [level, scale] of Object.entries(DENSITY_RADIUS_SCALE)) {
350
+ if (scale === fallback) continue;
351
+ blocks.push({
352
+ selector: themeSelector(name, { density: level }),
353
+ sections: [{ declarations: [[property, scaled(radiusScale, scale)]] }]
354
+ });
355
+ }
356
+ return {
357
+ base: [[property, scaled(radiusScale, fallback)]],
358
+ blocks
359
+ };
360
+ }
361
+ /** `[--cue-token, value]` pairs, in the order the contract lists them. */
362
+ function colorDeclarations(colors) {
363
+ return THEME_COLOR_TOKENS.map((token) => [tokenProperty(token), colors[token]]);
364
+ }
365
+ const DERIVED_COMMENT = "derived — kept inside the block so color-mix() reads this theme's values";
366
+ const FONT_COMMENT = "font — the theme's override, or the base stack restated";
367
+ const SHAPE_COMMENT = "shape — crossed with the density ladder below, never flat";
368
+ /**
369
+ * Generate a theme: a stylesheet, its token map, and a contrast report.
370
+ *
371
+ * Pure and synchronous. Call it in a build script, in a test, or in the
372
+ * configurator's export dialog — never in a render path, because there is
373
+ * nothing here a stylesheet is not already doing faster.
374
+ *
375
+ * @param anchors - The name, the preset to start from, and whatever you moved.
376
+ * @returns The stylesheet, the resolved tokens for both modes, and the report.
377
+ * @throws TypeError if the name is not a CSS identifier, an anchor is not a
378
+ * colour, a font stack carries CSS punctuation, or the base preset is unknown.
379
+ * @example
380
+ * const { css, report } = createTheme({ name: "acme", base: "terminal", accent: "#ff8800" });
381
+ * if (!report.passes) console.warn(report.failures);
382
+ */
383
+ function createTheme(anchors) {
384
+ const name = assertThemeName(anchors.name);
385
+ const baseName = anchors.base ?? "cue";
386
+ const preset = THEME_PRESETS[baseName];
387
+ if (preset === void 0) throw new TypeError(`unknown base theme: ${JSON.stringify(baseName)}`);
388
+ if (anchors.radiusScale !== void 0 && !(anchors.radiusScale > 0)) throw new TypeError(`radiusScale must be a positive number, got ${anchors.radiusScale}`);
389
+ const dark = resolveDark(anchors, preset.dark);
390
+ const light = anchors.supportsLight ?? preset.light !== null ? resolveLight(dark, preset.light) : null;
391
+ const derived = Object.entries(derivedTokens());
392
+ const fonts = resolveFonts(anchors, preset.fonts);
393
+ const radius = anchors.radiusScale === void 0 ? null : radiusBlocks(name, anchors.radiusScale);
394
+ const darkSections = [
395
+ { declarations: colorDeclarations(dark.colors) },
396
+ {
397
+ comment: DERIVED_COMMENT,
398
+ declarations: derived
399
+ },
400
+ {
401
+ comment: FONT_COMMENT,
402
+ declarations: fonts
403
+ },
404
+ ...radius === null ? [] : [{
405
+ comment: SHAPE_COMMENT,
406
+ declarations: radius.base
407
+ }]
408
+ ];
409
+ const blocks = [
410
+ {
411
+ selector: themeSelector(name),
412
+ colorScheme: "dark",
413
+ sections: darkSections
414
+ },
415
+ ...light === null ? [] : [{
416
+ selector: themeSelector(name, { mode: "light" }),
417
+ colorScheme: "light",
418
+ sections: [{ declarations: colorDeclarations(light.colors) }, {
419
+ comment: DERIVED_COMMENT,
420
+ declarations: derived
421
+ }]
422
+ }],
423
+ ...radius?.blocks ?? []
424
+ ];
425
+ const inputs = [{
426
+ mode: "dark",
427
+ tokens: dark.colors
428
+ }, ...light === null ? [] : [{
429
+ mode: "light",
430
+ tokens: light.colors,
431
+ provisional: light.provisional
432
+ }]];
433
+ return {
434
+ name,
435
+ css: serializeThemeCss(blocks, { banner: `@cueplusplus/ui — theme "${name}"` }),
436
+ tokens: Object.fromEntries(darkSections.flatMap((section) => section.declarations)),
437
+ light: light === null ? null : Object.fromEntries([...colorDeclarations(light.colors), ...derived]),
438
+ report: contrastReport(inputs)
439
+ };
440
+ }
441
+ //#endregion
442
+ export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, DEFAULT_THEME_BASE, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, createTheme };
@@ -0,0 +1,5 @@
1
+ import { CONTRAST_REQUIREMENTS, ContrastCheck, ContrastInput, ContrastReport, ContrastRequirement, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport, relativeLuminance } from "./contrast.js";
2
+ import { THEME_COLOR_TOKENS, ThemeColorToken } from "./_presets.js";
3
+ import { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CreatedTheme, DEFAULT_THEME_BASE, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, ThemeAnchors, createTheme } from "./create-theme.js";
4
+ import { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, SerializeThemeCssOptions, THEME_NAME_PATTERN, ThemeBlock, ThemeBlockSection, ThemeSelectorOptions, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
5
+ export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CONTRAST_REQUIREMENTS, CUE_TOKEN_PREFIX, type ContrastCheck, type ContrastInput, type ContrastReport, type ContrastRequirement, type CreatedTheme, DEFAULT_THEME_BASE, DERIVED_TOKEN_TEMPLATES, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, type SerializeThemeCssOptions, THEME_COLOR_TOKENS, THEME_NAME_PATTERN, type ThemeAnchors, type ThemeBlock, type ThemeBlockSection, type ThemeColorToken, type ThemeSelectorOptions, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, assertCssValue, assertThemeName, contrastRatio, contrastReport, createTheme, derivedTokens, relativeLuminance, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
@@ -0,0 +1,5 @@
1
+ import { THEME_COLOR_TOKENS } from "./_presets.js";
2
+ import { CONTRAST_REQUIREMENTS, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, contrastRatio, contrastReport, relativeLuminance } from "./contrast.js";
3
+ import { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, THEME_NAME_PATTERN, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty } from "./serialize.js";
4
+ import { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, DEFAULT_THEME_BASE, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, createTheme } from "./create-theme.js";
5
+ export { ACCENT_FG_MINIMUM, ACCENT_HOVER_LIGHTNESS_STEP, BORDER_LIGHTNESS_STEPS, CONTRAST_REQUIREMENTS, CUE_TOKEN_PREFIX, DEFAULT_THEME_BASE, DERIVED_TOKEN_TEMPLATES, FG_MUTED_LIGHTNESS_STEP, FG_SUBTLE_LIGHTNESS_STEP, INK_RAMP_CHROMA_SCALE, LIGHT_GROUND_COMPRESSION, SELECTION_ACCENT_PERCENT, SUNKEN_LIGHTNESS_STEP, SURFACE_LIGHTNESS_STEPS, THEME_COLOR_TOKENS, THEME_NAME_PATTERN, WCAG_AAA_TEXT, WCAG_AA_NON_TEXT, WCAG_AA_TEXT, assertCssValue, assertThemeName, contrastRatio, contrastReport, createTheme, derivedTokens, relativeLuminance, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };
@@ -0,0 +1,145 @@
1
+ import { Density } from "@cueplusplus/tokens";
2
+ //#region src/theming/serialize.d.ts
3
+ /**
4
+ * Turning a resolved token map back into the stylesheet the tokens build would
5
+ * have written.
6
+ *
7
+ * This module exists so there is exactly **one** spelling of the output format.
8
+ * The spec's whole third-party theming story is "tier 2 always compiles down to
9
+ * tier 1" — a generated theme has to be indistinguishable from a hand-authored
10
+ * `[data-theme]` block, or a consumer ends up with two vocabularies and the
11
+ * generator becomes a toy. So the shapes below mirror `build.mjs` deliberately,
12
+ * comments included: same selector form, same declaration order, same section
13
+ * breaks. `test/theming/presets.test.ts` diffs the two.
14
+ *
15
+ * It is also the only place that touches raw strings, which makes it the place
16
+ * to stop injection. A theme name lands inside an attribute selector and a font
17
+ * stack lands inside a declaration; both are checked here rather than trusted.
18
+ */
19
+ /** The one custom-property prefix this design system emits. */
20
+ declare const CUE_TOKEN_PREFIX = "--cue-";
21
+ /**
22
+ * What a theme may be called.
23
+ *
24
+ * The name is interpolated into `[data-theme="…"]`, so the pattern is a
25
+ * whitelist rather than an escape: a CSS identifier, which is also what a
26
+ * `data-theme` attribute is expected to hold and what a file called
27
+ * `<name>.css` can be named.
28
+ */
29
+ declare const THEME_NAME_PATTERN: RegExp;
30
+ /**
31
+ * Check a theme name and hand it back.
32
+ *
33
+ * @param name - The proposed theme name.
34
+ * @returns The same name, once it is known to be safe to interpolate.
35
+ * @throws TypeError if the name is not a CSS identifier.
36
+ * @example
37
+ * assertThemeName("acme-dark"); // → "acme-dark"
38
+ */
39
+ declare function assertThemeName(name: string): string;
40
+ /**
41
+ * Check a free-text declaration value and hand it back.
42
+ *
43
+ * @param name - The token the value belongs to, for the error message.
44
+ * @param value - The proposed value.
45
+ * @returns The same value, once it is known to stay inside its declaration.
46
+ * @throws TypeError if the value could end the declaration or the block.
47
+ * @example
48
+ * assertCssValue("font-mono", '"Berkeley Mono", monospace');
49
+ */
50
+ declare function assertCssValue(name: string, value: string): string;
51
+ /**
52
+ * The custom property a token is emitted as.
53
+ *
54
+ * Idempotent, so code that already holds `--cue-bg` can pass it straight back
55
+ * through instead of stripping the prefix first.
56
+ *
57
+ * @param name - A bare token name, or an already-prefixed property.
58
+ * @returns The `--cue-*` custom property name.
59
+ * @example
60
+ * tokenProperty("surface-1"); // → "--cue-surface-1"
61
+ */
62
+ declare function tokenProperty(name: string): string;
63
+ interface ThemeSelectorOptions {
64
+ /** Cross with the mode axis. Only `"light"` needs a selector; dark is the base block. */
65
+ mode?: "dark" | "light";
66
+ /** Cross with the density axis. Used only for the radius multiplier. */
67
+ density?: Density;
68
+ }
69
+ /**
70
+ * The selector a theme's declarations live under.
71
+ *
72
+ * Attribute selectors, not classes, because the two axes have to compose: a
73
+ * `[data-theme]` on the root and another on a portal, a `[data-mode]` crossed
74
+ * over either, all at the same specificity so later blocks win predictably.
75
+ *
76
+ * @param name - The theme name. Not re-checked — {@link assertThemeName} owns that.
77
+ * @param options - Which axes to cross with.
78
+ * @returns The CSS selector.
79
+ * @example
80
+ * themeSelector("acme", { mode: "light" }); // → '[data-theme="acme"][data-mode="light"]'
81
+ */
82
+ declare function themeSelector(name: string, options?: ThemeSelectorOptions): string;
83
+ /**
84
+ * The derived colours every theme block carries, as `[token, value]` pairs.
85
+ *
86
+ * They are `color-mix()` expressions over `var(--cue-*)` rather than resolved
87
+ * literals, and they are emitted **inside** the theme block rather than once on
88
+ * `:root`, for the same reason the build does it: a custom property's `var()`
89
+ * references are substituted at the element that declares it, so a `:root`-level
90
+ * mix would freeze the default theme's accent into every nested theme.
91
+ */
92
+ declare const DERIVED_TOKEN_TEMPLATES: ReadonlyArray<readonly [string, string]>;
93
+ /**
94
+ * {@link DERIVED_TOKEN_TEMPLATES} as a property → value map.
95
+ *
96
+ * @returns The eleven derived declarations, keyed by custom property.
97
+ * @example
98
+ * derivedTokens()["--cue-accent-soft"];
99
+ */
100
+ declare function derivedTokens(): Record<string, string>;
101
+ /** One run of declarations, optionally introduced by a comment. */
102
+ interface ThemeBlockSection {
103
+ /** Comment printed above the run. Omit on the first section. */
104
+ comment?: string;
105
+ /** `[property, value]` pairs, in emission order. */
106
+ declarations: ReadonlyArray<readonly [string, string]>;
107
+ }
108
+ /** One CSS rule of a generated theme. */
109
+ interface ThemeBlock {
110
+ /** The selector the rule declares under. */
111
+ selector: string;
112
+ /** Emitted as `color-scheme`, ahead of the custom properties. Omit to skip it. */
113
+ colorScheme?: "dark" | "light";
114
+ /** The declarations, grouped into commented sections. */
115
+ sections: readonly ThemeBlockSection[];
116
+ }
117
+ /**
118
+ * One rule, printed.
119
+ *
120
+ * Empty sections vanish rather than leaving a comment with nothing under it —
121
+ * which is what makes "emit the fonts section only if there are fonts" a
122
+ * data decision instead of a branch at every call site.
123
+ *
124
+ * @param block - Selector, colour scheme and sections.
125
+ * @returns The rule, without a trailing newline.
126
+ * @example
127
+ * serializeThemeBlock({ selector: ":root", sections: [{ declarations: [["--cue-bg", "#000"]] }] });
128
+ */
129
+ declare function serializeThemeBlock(block: ThemeBlock): string;
130
+ interface SerializeThemeCssOptions {
131
+ /** Text of the leading `/*! … *\/` banner. Omit for a bare stylesheet. */
132
+ banner?: string;
133
+ }
134
+ /**
135
+ * A whole generated stylesheet: banner, then one rule per block.
136
+ *
137
+ * @param blocks - The rules, in cascade order — later blocks win ties.
138
+ * @param options - Banner text.
139
+ * @returns The stylesheet, newline-terminated.
140
+ * @example
141
+ * serializeThemeCss(blocks, { banner: "@cueplusplus/ui — createTheme()" });
142
+ */
143
+ declare function serializeThemeCss(blocks: readonly ThemeBlock[], options?: SerializeThemeCssOptions): string;
144
+ //#endregion
145
+ export { CUE_TOKEN_PREFIX, DERIVED_TOKEN_TEMPLATES, SerializeThemeCssOptions, THEME_NAME_PATTERN, ThemeBlock, ThemeBlockSection, ThemeSelectorOptions, assertCssValue, assertThemeName, derivedTokens, serializeThemeBlock, serializeThemeCss, themeSelector, tokenProperty };