@blotch/dolan 22.2.2 → 22.2.4

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 (259) hide show
  1. package/dist/commands/add.d.ts +6 -2
  2. package/dist/commands/add.js +41 -11
  3. package/dist/commands/add.js.map +1 -1
  4. package/dist/commands/add.test.js +162 -9
  5. package/dist/commands/add.test.js.map +1 -1
  6. package/dist/commands/diff.d.ts +2 -1
  7. package/dist/commands/diff.js +38 -25
  8. package/dist/commands/diff.js.map +1 -1
  9. package/dist/commands/diff.test.js +72 -11
  10. package/dist/commands/diff.test.js.map +1 -1
  11. package/dist/commands/doctor.js +228 -34
  12. package/dist/commands/doctor.js.map +1 -1
  13. package/dist/commands/doctor.test.js +367 -10
  14. package/dist/commands/doctor.test.js.map +1 -1
  15. package/dist/commands/eject.js +7 -0
  16. package/dist/commands/eject.js.map +1 -1
  17. package/dist/commands/eject.test.js +39 -0
  18. package/dist/commands/eject.test.js.map +1 -1
  19. package/dist/commands/info.js +3 -2
  20. package/dist/commands/info.js.map +1 -1
  21. package/dist/commands/init.d.ts +37 -1
  22. package/dist/commands/init.js +169 -40
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/init.test.js +392 -27
  25. package/dist/commands/init.test.js.map +1 -1
  26. package/dist/commands/install-packages.d.ts +6 -0
  27. package/dist/commands/install-packages.js +20 -0
  28. package/dist/commands/install-packages.js.map +1 -0
  29. package/dist/commands/install-packages.test.d.ts +1 -0
  30. package/dist/commands/install-packages.test.js +55 -0
  31. package/dist/commands/install-packages.test.js.map +1 -0
  32. package/dist/commands/list.js +21 -14
  33. package/dist/commands/list.js.map +1 -1
  34. package/dist/commands/list.test.js +62 -5
  35. package/dist/commands/list.test.js.map +1 -1
  36. package/dist/commands/outdated.js +11 -10
  37. package/dist/commands/outdated.js.map +1 -1
  38. package/dist/commands/outdated.test.js +20 -5
  39. package/dist/commands/outdated.test.js.map +1 -1
  40. package/dist/commands/remove.d.ts +4 -0
  41. package/dist/commands/remove.js +17 -4
  42. package/dist/commands/remove.js.map +1 -1
  43. package/dist/commands/remove.test.js +53 -2
  44. package/dist/commands/remove.test.js.map +1 -1
  45. package/dist/commands/theme.js +1 -1
  46. package/dist/commands/theme.js.map +1 -1
  47. package/dist/commands/update.d.ts +11 -0
  48. package/dist/commands/update.js +174 -58
  49. package/dist/commands/update.js.map +1 -1
  50. package/dist/commands/update.test.js +587 -33
  51. package/dist/commands/update.test.js.map +1 -1
  52. package/dist/index.js +7 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/lockfile.d.ts +7 -0
  55. package/dist/lockfile.js +36 -0
  56. package/dist/lockfile.js.map +1 -1
  57. package/dist/lockfile.test.d.ts +1 -0
  58. package/dist/lockfile.test.js +25 -0
  59. package/dist/lockfile.test.js.map +1 -0
  60. package/dist/registry.d.ts +14 -0
  61. package/dist/registry.js +78 -24
  62. package/dist/registry.js.map +1 -1
  63. package/dist/registry.test.js +119 -38
  64. package/dist/registry.test.js.map +1 -1
  65. package/dist/test-utils.d.ts +5 -0
  66. package/dist/test-utils.js +6 -1
  67. package/dist/test-utils.js.map +1 -1
  68. package/dist/testing/fire-lynx-event.d.ts +14 -0
  69. package/dist/testing/fire-lynx-event.js +19 -0
  70. package/dist/testing/fire-lynx-event.js.map +1 -0
  71. package/dist/testing/fire-lynx-event.test.d.ts +1 -0
  72. package/dist/testing/fire-lynx-event.test.js +44 -0
  73. package/dist/testing/fire-lynx-event.test.js.map +1 -0
  74. package/dist/testing/lynx-main-thread.d.ts +22 -0
  75. package/dist/testing/lynx-main-thread.js +11 -0
  76. package/dist/testing/lynx-main-thread.js.map +1 -0
  77. package/dist/testing/lynx-main-thread.test.d.ts +1 -0
  78. package/dist/testing/lynx-main-thread.test.js +24 -0
  79. package/dist/testing/lynx-main-thread.test.js.map +1 -0
  80. package/dist/testing/set-input-signal.d.ts +14 -0
  81. package/dist/testing/set-input-signal.js +21 -0
  82. package/dist/testing/set-input-signal.js.map +1 -0
  83. package/dist/testing/set-input-signal.test.d.ts +1 -0
  84. package/dist/testing/set-input-signal.test.js +20 -0
  85. package/dist/testing/set-input-signal.test.js.map +1 -0
  86. package/dist/ui/components/accordion/accordion.test.ts +158 -144
  87. package/dist/ui/components/accordion/accordion.ts +19 -14
  88. package/dist/ui/components/action-sheet/action-sheet.test.ts +283 -151
  89. package/dist/ui/components/action-sheet/action-sheet.ts +77 -43
  90. package/dist/ui/components/alert/alert.test.ts +67 -19
  91. package/dist/ui/components/alert/alert.ts +14 -16
  92. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +208 -94
  93. package/dist/ui/components/alert-dialog/alert-dialog.ts +75 -38
  94. package/dist/ui/components/area-chart/area-chart.test.ts +134 -119
  95. package/dist/ui/components/area-chart/area-chart.ts +100 -315
  96. package/dist/ui/components/area-chart/index.ts +2 -3
  97. package/dist/ui/components/avatar/avatar.test.ts +89 -45
  98. package/dist/ui/components/badge/badge.test.ts +57 -23
  99. package/dist/ui/components/badge/badge.ts +12 -14
  100. package/dist/ui/components/bar-chart/bar-chart.test.ts +172 -45
  101. package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
  102. package/dist/ui/components/bar-chart/index.ts +3 -3
  103. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +386 -128
  104. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +170 -53
  105. package/dist/ui/components/button/button.test.ts +611 -2
  106. package/dist/ui/components/button/button.ts +434 -49
  107. package/dist/ui/components/button/index.ts +2 -0
  108. package/dist/ui/components/button-group/button-group.test.ts +484 -2
  109. package/dist/ui/components/button-group/button-group.ts +380 -68
  110. package/dist/ui/components/button-group/index.ts +3 -0
  111. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +183 -58
  112. package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
  113. package/dist/ui/components/candlestick-chart/index.ts +3 -4
  114. package/dist/ui/components/card/card.test.ts +82 -62
  115. package/dist/ui/components/chart/chart-focus.test.ts +132 -0
  116. package/dist/ui/components/chart/chart-focus.ts +115 -0
  117. package/dist/ui/components/chart/chart-presets.test.ts +335 -0
  118. package/dist/ui/components/chart/chart-presets.ts +278 -0
  119. package/dist/ui/components/chart/chart-scene.test.ts +589 -0
  120. package/dist/ui/components/chart/chart-scene.ts +483 -0
  121. package/dist/ui/components/chart/chart-theme.test.ts +85 -0
  122. package/dist/ui/components/chart/chart-theme.ts +210 -0
  123. package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
  124. package/dist/ui/components/chart/chart-zoom.ts +162 -0
  125. package/dist/ui/components/chart/chart.test.ts +1017 -0
  126. package/dist/ui/components/chart/chart.ts +934 -0
  127. package/dist/ui/components/chart/index.ts +24 -0
  128. package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
  129. package/dist/ui/components/checkbox/checkbox.ts +23 -23
  130. package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
  131. package/dist/ui/components/collapsible/collapsible.ts +19 -14
  132. package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
  133. package/dist/ui/components/context-menu/context-menu.ts +110 -0
  134. package/dist/ui/components/context-menu/index.ts +1 -0
  135. package/dist/ui/components/dialog/dialog.test.ts +213 -108
  136. package/dist/ui/components/dialog/dialog.ts +72 -35
  137. package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
  138. package/dist/ui/components/dock/dock-scroll.ts +133 -0
  139. package/dist/ui/components/dock/dock.test.ts +749 -0
  140. package/dist/ui/components/dock/dock.ts +960 -0
  141. package/dist/ui/components/dock/index.ts +2 -0
  142. package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
  143. package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
  144. package/dist/ui/components/glass/concentric-radius.ts +17 -0
  145. package/dist/ui/components/glass/glass-design.test.ts +78 -0
  146. package/dist/ui/components/glass/glass-design.ts +119 -0
  147. package/dist/ui/components/glass/glass.test.ts +338 -0
  148. package/dist/ui/components/glass/glass.ts +360 -0
  149. package/dist/ui/components/glass/index.ts +18 -0
  150. package/dist/ui/components/icon/icon-theme.test.ts +169 -0
  151. package/dist/ui/components/icon/icon-theme.ts +227 -0
  152. package/dist/ui/components/icon/icon.test.ts +85 -25
  153. package/dist/ui/components/icon/icon.ts +38 -21
  154. package/dist/ui/components/input/input.test.ts +178 -101
  155. package/dist/ui/components/input/input.ts +15 -12
  156. package/dist/ui/components/label/label.test.ts +11 -24
  157. package/dist/ui/components/line-chart/index.ts +2 -3
  158. package/dist/ui/components/line-chart/line-chart.test.ts +149 -56
  159. package/dist/ui/components/line-chart/line-chart.ts +83 -241
  160. package/dist/ui/components/list/list.test.ts +119 -21
  161. package/dist/ui/components/list/list.ts +21 -15
  162. package/dist/ui/components/menu/index.ts +25 -0
  163. package/dist/ui/components/menu/menu-placement.test.ts +153 -0
  164. package/dist/ui/components/menu/menu-placement.ts +159 -0
  165. package/dist/ui/components/menu/menu-popup.test.ts +632 -0
  166. package/dist/ui/components/menu/menu-popup.ts +544 -0
  167. package/dist/ui/components/menu/menu.test.ts +429 -0
  168. package/dist/ui/components/menu/menu.ts +415 -0
  169. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +435 -191
  170. package/dist/ui/components/nav-drawer/nav-drawer.ts +127 -56
  171. package/dist/ui/components/pagination/pagination.test.ts +177 -65
  172. package/dist/ui/components/pagination/pagination.ts +55 -8
  173. package/dist/ui/components/progress/progress.test.ts +102 -46
  174. package/dist/ui/components/progress/progress.ts +52 -54
  175. package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
  176. package/dist/ui/components/radio-group/radio-group.ts +24 -24
  177. package/dist/ui/components/scatter-chart/index.ts +2 -3
  178. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +131 -26
  179. package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
  180. package/dist/ui/components/scroll-area/scroll-area.test.ts +95 -24
  181. package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
  182. package/dist/ui/components/select/select.test.ts +138 -78
  183. package/dist/ui/components/select/select.ts +17 -11
  184. package/dist/ui/components/separator/separator.test.ts +18 -17
  185. package/dist/ui/components/sheet/sheet.test.ts +64 -11
  186. package/dist/ui/components/sheet/sheet.ts +9 -1
  187. package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
  188. package/dist/ui/components/skeleton/skeleton.ts +10 -16
  189. package/dist/ui/components/spinner/spinner.test.ts +67 -22
  190. package/dist/ui/components/spinner/spinner.ts +48 -41
  191. package/dist/ui/components/switch/switch.test.ts +116 -46
  192. package/dist/ui/components/switch/switch.ts +8 -13
  193. package/dist/ui/components/tab-bar/index.ts +1 -0
  194. package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
  195. package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
  196. package/dist/ui/components/tabs/tabs.test.ts +215 -157
  197. package/dist/ui/components/tabs/tabs.ts +110 -118
  198. package/dist/ui/components/textarea/textarea.test.ts +206 -100
  199. package/dist/ui/components/textarea/textarea.ts +14 -12
  200. package/dist/ui/components/toast/toast.test.ts +436 -315
  201. package/dist/ui/components/toast/toast.ts +62 -55
  202. package/dist/ui/components/toggle/toggle.test.ts +247 -140
  203. package/dist/ui/theme/chart-colors.test.ts +35 -0
  204. package/dist/ui/theme/dark.css +46 -2
  205. package/dist/ui/theme/default.css +63 -7
  206. package/dist/ui/theme/icon-colors.test.ts +35 -0
  207. package/dist/ui/theme/rgb-channels.test.ts +84 -0
  208. package/dist/ui/theme/tailwind-build.test.ts +114 -0
  209. package/dist/ui/theme/tailwind-css.test.ts +83 -0
  210. package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
  211. package/dist/ui/theme/tailwind-plugin.ts +98 -25
  212. package/dist/ui/theme/tailwind.css +113 -0
  213. package/dist/ui/utils/animate.test.ts +13 -1
  214. package/dist/utils/analyze.d.ts +14 -0
  215. package/dist/utils/analyze.js +15 -0
  216. package/dist/utils/analyze.js.map +1 -1
  217. package/dist/utils/analyze.test.js +14 -1
  218. package/dist/utils/analyze.test.js.map +1 -1
  219. package/dist/utils/installed.d.ts +29 -0
  220. package/dist/utils/installed.js +43 -0
  221. package/dist/utils/installed.js.map +1 -0
  222. package/dist/utils/installed.test.d.ts +1 -0
  223. package/dist/utils/installed.test.js +57 -0
  224. package/dist/utils/installed.test.js.map +1 -0
  225. package/dist/utils/interactive.d.ts +9 -0
  226. package/dist/utils/interactive.js +10 -0
  227. package/dist/utils/interactive.js.map +1 -0
  228. package/dist/utils/interactive.test.d.ts +1 -0
  229. package/dist/utils/interactive.test.js +24 -0
  230. package/dist/utils/interactive.test.js.map +1 -0
  231. package/dist/utils/packages.d.ts +36 -0
  232. package/dist/utils/packages.js +70 -0
  233. package/dist/utils/packages.js.map +1 -0
  234. package/dist/utils/packages.test.d.ts +1 -0
  235. package/dist/utils/packages.test.js +89 -0
  236. package/dist/utils/packages.test.js.map +1 -0
  237. package/dist/utils/resolve-paths.d.ts +17 -1
  238. package/dist/utils/resolve-paths.js +21 -3
  239. package/dist/utils/resolve-paths.js.map +1 -1
  240. package/dist/utils/resolve-paths.test.js +38 -2
  241. package/dist/utils/resolve-paths.test.js.map +1 -1
  242. package/dist/utils/tailwind.d.ts +25 -0
  243. package/dist/utils/tailwind.js +51 -0
  244. package/dist/utils/tailwind.js.map +1 -0
  245. package/dist/utils/tailwind.test.d.ts +1 -0
  246. package/dist/utils/tailwind.test.js +71 -0
  247. package/dist/utils/tailwind.test.js.map +1 -0
  248. package/package.json +13 -1
  249. package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -121
  250. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -174
  251. package/dist/ui/components/button/button-render.test.ts +0 -113
  252. package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
  253. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -174
  254. package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
  255. package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
  256. package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
  257. package/dist/ui/components/cartesian-chart/index.ts +0 -18
  258. package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -121
  259. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -157
@@ -0,0 +1,35 @@
1
+ /// <reference types="node" />
2
+ // Not copied into apps (it checks the theme against a component), so it can
3
+ // read the theme files with Node.
4
+ import { readFileSync } from 'node:fs';
5
+ import { describe, expect, it } from 'vitest';
6
+ import {
7
+ CHART_DARK_COLORS,
8
+ CHART_LIGHT_COLORS,
9
+ } from '../components/chart/chart-theme';
10
+
11
+ /**
12
+ * `<ui-chart>` draws through native SVG, which can't read CSS variables, so the
13
+ * chart component keeps a TypeScript copy of the theme's colors. This keeps the
14
+ * copy in step with default.css and dark.css.
15
+ */
16
+ const tokens = (file: string): Record<string, string> => {
17
+ // Relative to the package, where `ng test` runs: the test runs from a build
18
+ // of it elsewhere, so `import.meta.url` doesn't point next to the CSS.
19
+ const css = readFileSync(`src/lib/theme/${file}`, 'utf8');
20
+ const values: Record<string, string> = {};
21
+ for (const match of css.matchAll(/--([\w-]+):\s*(rgba\([^)]*\))/g)) {
22
+ values[match[1]!] = match[2]!;
23
+ }
24
+ return values;
25
+ };
26
+
27
+ describe('chart color tables', () => {
28
+ it('match default.css', () => {
29
+ expect(CHART_LIGHT_COLORS).toEqual(tokens('default.css'));
30
+ });
31
+
32
+ it('match dark.css', () => {
33
+ expect(CHART_DARK_COLORS).toEqual(tokens('dark.css'));
34
+ });
35
+ });
@@ -1,8 +1,9 @@
1
1
  /*
2
2
  * @blotch/ui dark theme
3
3
  *
4
- * Complete rgba() color values referenced directly via var(--x) — see
5
- * default.css for why complete rgba values are used instead of HSL on Lynx.
4
+ * Complete rgba() color values referenced directly via var(--x), each with
5
+ * its `-rgb` channels for opacity modifiers — see default.css for why both,
6
+ * and why not HSL, on Lynx.
6
7
  *
7
8
  * Apply via a `.dark` class on the page element, or import as an
8
9
  * alternative to default.css.
@@ -10,32 +11,75 @@
10
11
 
11
12
  page.dark {
12
13
  --background: rgba(9, 9, 11, 1);
14
+ --background-rgb: 9, 9, 11;
13
15
  --foreground: rgba(250, 250, 250, 1);
16
+ --foreground-rgb: 250, 250, 250;
14
17
 
15
18
  --card: rgba(9, 9, 11, 1);
19
+ --card-rgb: 9, 9, 11;
16
20
  --card-foreground: rgba(250, 250, 250, 1);
21
+ --card-foreground-rgb: 250, 250, 250;
17
22
 
18
23
  --primary: rgba(250, 250, 250, 1);
24
+ --primary-rgb: 250, 250, 250;
19
25
  --primary-foreground: rgba(24, 24, 27, 1);
26
+ --primary-foreground-rgb: 24, 24, 27;
20
27
 
21
28
  --secondary: rgba(39, 39, 42, 1);
29
+ --secondary-rgb: 39, 39, 42;
22
30
  --secondary-foreground: rgba(250, 250, 250, 1);
31
+ --secondary-foreground-rgb: 250, 250, 250;
23
32
 
24
33
  --muted: rgba(39, 39, 42, 1);
34
+ --muted-rgb: 39, 39, 42;
25
35
  --muted-foreground: rgba(161, 161, 170, 1);
36
+ --muted-foreground-rgb: 161, 161, 170;
26
37
 
27
38
  --accent: rgba(39, 39, 42, 1);
39
+ --accent-rgb: 39, 39, 42;
28
40
  --accent-foreground: rgba(250, 250, 250, 1);
41
+ --accent-foreground-rgb: 250, 250, 250;
29
42
 
30
43
  --destructive: rgba(127, 29, 29, 1);
44
+ --destructive-rgb: 127, 29, 29;
31
45
  --destructive-foreground: rgba(250, 250, 250, 1);
46
+ --destructive-foreground-rgb: 250, 250, 250;
32
47
  --destructive-subtle: rgba(127, 29, 29, 0.1);
33
48
 
34
49
  --border: rgba(39, 39, 42, 1);
50
+ --border-rgb: 39, 39, 42;
35
51
  --input: rgba(39, 39, 42, 1);
52
+ --input-rgb: 39, 39, 42;
36
53
  --ring: rgba(212, 212, 216, 1);
54
+ --ring-rgb: 212, 212, 216;
37
55
  /* Translucent variant of --ring for a softer focus outline (see default.css). */
38
56
  --ring-subtle: rgba(212, 212, 216, 0.4);
39
57
 
58
+ /* Series palette for charts (bg-chart-1, and chart-theme.ts in the chart
59
+ component, which mirrors these values for SVG). */
60
+ --chart-1: rgba(38, 98, 217, 1);
61
+ --chart-1-rgb: 38, 98, 217;
62
+ --chart-2: rgba(46, 184, 138, 1);
63
+ --chart-2-rgb: 46, 184, 138;
64
+ --chart-3: rgba(232, 140, 48, 1);
65
+ --chart-3-rgb: 232, 140, 48;
66
+ --chart-4: rgba(175, 87, 219, 1);
67
+ --chart-4-rgb: 175, 87, 219;
68
+ --chart-5: rgba(226, 54, 112, 1);
69
+ --chart-5-rgb: 226, 54, 112;
70
+
71
+ /* Liquid Glass (the glass component): see default.css. */
72
+ --glass-tint: rgba(250, 250, 250, 1);
73
+ --glass-tint-rgb: 250, 250, 250;
74
+ --glass-tint-foreground: rgba(24, 24, 27, 1);
75
+ --glass-tint-foreground-rgb: 24, 24, 27;
76
+ --glass-foreground: rgba(250, 250, 250, 1);
77
+ --glass-foreground-rgb: 250, 250, 250;
78
+ --glass-muted-foreground: rgba(212, 212, 216, 1);
79
+ --glass-muted-foreground-rgb: 212, 212, 216;
80
+ --glass-clear-foreground: rgba(255, 255, 255, 1);
81
+ --glass-clear-foreground-rgb: 255, 255, 255;
82
+ --glass-dim: rgba(0, 0, 0, 0.35);
83
+
40
84
  --radius: 8px;
41
85
  }
@@ -13,44 +13,100 @@
13
13
  * rgba() values and referencing them directly sidesteps color-function parsing
14
14
  * entirely and renders correctly on Lynx.
15
15
  *
16
- * Tradeoff: Tailwind opacity modifiers on semantic colors (e.g. bg-primary/50)
17
- * do NOT work, because the color is an opaque value with no separable channels.
18
- * Where a translucent semantic color is genuinely needed, add a dedicated
19
- * rgba() variable (see --destructive-subtle) or use a solid token.
16
+ * Each opaque color also has its channels in a `-rgb` twin
17
+ * (`--primary-rgb: 24, 24, 27`) for Tailwind's opacity modifiers:
18
+ * `bg-primary/50` becomes `rgba(var(--primary-rgb), 0.5)`. Lynx substitutes
19
+ * var() as text before it parses the color, so the comma-separated list fills
20
+ * three arguments. Keep each pair the same color when you edit one: plain
21
+ * classes read `--x` and modifiers read `--x-rgb`.
20
22
  *
21
23
  * Apply to `page` (Lynx root element) or import globally.
22
24
  */
23
25
 
24
26
  page {
25
27
  --background: rgba(255, 255, 255, 1);
28
+ --background-rgb: 255, 255, 255;
26
29
  --foreground: rgba(9, 9, 11, 1);
30
+ --foreground-rgb: 9, 9, 11;
27
31
 
28
32
  --card: rgba(255, 255, 255, 1);
33
+ --card-rgb: 255, 255, 255;
29
34
  --card-foreground: rgba(9, 9, 11, 1);
35
+ --card-foreground-rgb: 9, 9, 11;
30
36
 
31
37
  --primary: rgba(24, 24, 27, 1);
38
+ --primary-rgb: 24, 24, 27;
32
39
  --primary-foreground: rgba(250, 250, 250, 1);
40
+ --primary-foreground-rgb: 250, 250, 250;
33
41
 
34
42
  --secondary: rgba(244, 244, 245, 1);
43
+ --secondary-rgb: 244, 244, 245;
35
44
  --secondary-foreground: rgba(24, 24, 27, 1);
45
+ --secondary-foreground-rgb: 24, 24, 27;
36
46
 
37
47
  --muted: rgba(244, 244, 245, 1);
48
+ --muted-rgb: 244, 244, 245;
38
49
  --muted-foreground: rgba(113, 113, 122, 1);
50
+ --muted-foreground-rgb: 113, 113, 122;
39
51
 
40
52
  --accent: rgba(244, 244, 245, 1);
53
+ --accent-rgb: 244, 244, 245;
41
54
  --accent-foreground: rgba(24, 24, 27, 1);
55
+ --accent-foreground-rgb: 24, 24, 27;
42
56
 
43
57
  --destructive: rgba(239, 68, 68, 1);
58
+ --destructive-rgb: 239, 68, 68;
44
59
  --destructive-foreground: rgba(250, 250, 250, 1);
60
+ --destructive-foreground-rgb: 250, 250, 250;
45
61
  --destructive-subtle: rgba(239, 68, 68, 0.1);
46
62
 
47
63
  --border: rgba(228, 228, 231, 1);
64
+ --border-rgb: 228, 228, 231;
48
65
  --input: rgba(228, 228, 231, 1);
66
+ --input-rgb: 228, 228, 231;
49
67
  --ring: rgba(24, 24, 27, 1);
50
- /* Translucent variant of --ring for a softer focus outline. A separate
51
- pre-composed value is needed because Tailwind opacity modifiers can't
52
- apply to the opaque --ring, and Lynx can't inject alpha into it either. */
68
+ --ring-rgb: 24, 24, 27;
69
+ /* Translucent variant of --ring for the soft outer focus ring. Input and
70
+ textarea set it in an inline box-shadow, where no Tailwind modifier
71
+ reaches, so it stays a complete color. */
53
72
  --ring-subtle: rgba(24, 24, 27, 0.4);
54
73
 
74
+ /* Series palette for charts (bg-chart-1, and chart-theme.ts in the chart
75
+ component, which mirrors these values for SVG). */
76
+ --chart-1: rgba(231, 110, 80, 1);
77
+ --chart-1-rgb: 231, 110, 80;
78
+ --chart-2: rgba(42, 157, 144, 1);
79
+ --chart-2-rgb: 42, 157, 144;
80
+ --chart-3: rgba(39, 71, 84, 1);
81
+ --chart-3-rgb: 39, 71, 84;
82
+ --chart-4: rgba(232, 196, 104, 1);
83
+ --chart-4-rgb: 232, 196, 104;
84
+ --chart-5: rgba(244, 164, 98, 1);
85
+ --chart-5-rgb: 244, 164, 98;
86
+
87
+ /* Liquid Glass (the glass component). Glass draws its own material, so
88
+ these color what sits on it, and tint it:
89
+ - glass-tint: the color tinted glass takes, such as a prominent glass
90
+ button's, and glass-tint-foreground what sits on it.
91
+ - glass-foreground and glass-muted-foreground: text and icons on regular
92
+ glass. Darker than muted-foreground, since what shows through the
93
+ glass varies.
94
+ - glass-clear-foreground: on clear glass, which sits over photos and
95
+ video, so it's white in both themes.
96
+ - glass-dim: the dimming layer under clear glass over bright content,
97
+ black at about a third, as Apple suggests. Already translucent, so it
98
+ has no channels. */
99
+ --glass-tint: rgba(24, 24, 27, 1);
100
+ --glass-tint-rgb: 24, 24, 27;
101
+ --glass-tint-foreground: rgba(250, 250, 250, 1);
102
+ --glass-tint-foreground-rgb: 250, 250, 250;
103
+ --glass-foreground: rgba(9, 9, 11, 1);
104
+ --glass-foreground-rgb: 9, 9, 11;
105
+ --glass-muted-foreground: rgba(63, 63, 70, 1);
106
+ --glass-muted-foreground-rgb: 63, 63, 70;
107
+ --glass-clear-foreground: rgba(255, 255, 255, 1);
108
+ --glass-clear-foreground-rgb: 255, 255, 255;
109
+ --glass-dim: rgba(0, 0, 0, 0.35);
110
+
55
111
  --radius: 8px;
56
112
  }
@@ -0,0 +1,35 @@
1
+ /// <reference types="node" />
2
+ // Not copied into apps (it checks the theme against a component), so it can
3
+ // read the theme files with Node.
4
+ import { readFileSync } from 'node:fs';
5
+ import { describe, expect, it } from 'vitest';
6
+ import {
7
+ ICON_DARK_COLORS,
8
+ ICON_LIGHT_COLORS,
9
+ } from '../components/icon/icon-theme';
10
+
11
+ /**
12
+ * `<ui-icon>`, `<ui-spinner>` and pagination draw through native SVG, which
13
+ * can't read CSS variables, so icon-theme.ts keeps a TypeScript copy of the
14
+ * theme's colors. This keeps the copy in step with default.css and dark.css.
15
+ */
16
+ const tokens = (file: string): Record<string, string> => {
17
+ // Relative to the package, where `ng test` runs: the test runs from a build
18
+ // of it elsewhere, so `import.meta.url` doesn't point next to the CSS.
19
+ const css = readFileSync(`src/lib/theme/${file}`, 'utf8');
20
+ const values: Record<string, string> = {};
21
+ for (const match of css.matchAll(/--([\w-]+):\s*(rgba\([^)]*\))/g)) {
22
+ values[match[1]!] = match[2]!;
23
+ }
24
+ return values;
25
+ };
26
+
27
+ describe('icon color tables', () => {
28
+ it('match default.css', () => {
29
+ expect(ICON_LIGHT_COLORS).toEqual(tokens('default.css'));
30
+ });
31
+
32
+ it('match dark.css', () => {
33
+ expect(ICON_DARK_COLORS).toEqual(tokens('dark.css'));
34
+ });
35
+ });
@@ -0,0 +1,84 @@
1
+ /// <reference types="node" />
2
+ // Not copied into apps (it reads the theme's CSS with Node), like the other
3
+ // theme checks next to it.
4
+ import { readFileSync } from 'node:fs';
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ import { blotchPlugin } from './tailwind-plugin';
8
+
9
+ /**
10
+ * Each opaque theme color is written twice: complete (`--primary:
11
+ * rgba(24, 24, 27, 1)`) for plain classes, and as channels (`--primary-rgb:
12
+ * 24, 24, 27`) for opacity modifiers. Nothing at runtime ties the two
13
+ * together, so a typo would make `bg-primary/50` a different color from
14
+ * `bg-primary`. These tests keep the pairs in step.
15
+ */
16
+ const variables = (file: string): Map<string, string> => {
17
+ // Comments mention variables too, so they go first.
18
+ // Relative to the package, where `ng test` runs: the test runs from a build
19
+ // of it elsewhere, so `import.meta.url` doesn't point next to the CSS.
20
+ const css = readFileSync(`src/lib/theme/${file}`, 'utf8').replace(
21
+ /\/\*[\s\S]*?\*\//g,
22
+ '',
23
+ );
24
+ return new Map(
25
+ [...css.matchAll(/(--[\w-]+):\s*([^;{}]+);/g)].map((match) => [
26
+ match[1]!,
27
+ match[2]!.trim(),
28
+ ]),
29
+ );
30
+ };
31
+
32
+ /**
33
+ * The `--x-rgb` variables the plugin's opacity modifiers read.
34
+ */
35
+ const channelVariables = (): string[] => {
36
+ const factory = blotchPlugin as unknown as {
37
+ config: { theme: { extend: { colors: Record<string, unknown> } } };
38
+ };
39
+ const names: string[] = [];
40
+ const walk = (value: unknown) => {
41
+ if (typeof value === 'function') {
42
+ const css = (value as (options: object) => string)({
43
+ opacityValue: '0.5',
44
+ });
45
+ names.push(
46
+ ...[...css.matchAll(/var\((--[\w-]+-rgb)\)/g)].map((m) => m[1]!),
47
+ );
48
+ } else if (typeof value === 'object' && value !== null) {
49
+ Object.values(value).forEach(walk);
50
+ }
51
+ };
52
+ walk(factory.config.theme.extend.colors);
53
+ return names;
54
+ };
55
+
56
+ describe.each(['./default.css', './dark.css'])('%s', (file) => {
57
+ const vars = variables(file);
58
+
59
+ it('has the channels of every color the plugin takes a modifier on', () => {
60
+ const names = channelVariables();
61
+ expect(names.length).toBeGreaterThan(0);
62
+ for (const name of names) {
63
+ expect(vars.get(name), name).toMatch(/^\d+, \d+, \d+$/);
64
+ }
65
+ });
66
+
67
+ it("gives each color's channels the same values as the color", () => {
68
+ for (const [name, value] of vars) {
69
+ if (!name.endsWith('-rgb')) continue;
70
+ const color = vars.get(name.slice(0, -'-rgb'.length));
71
+ expect(color, `${name} has no color`).toBe(`rgba(${value}, 1)`);
72
+ }
73
+ });
74
+
75
+ it('has channels for every opaque color', () => {
76
+ // A new token added without its channels would draw nothing with a
77
+ // modifier.
78
+ for (const [name, value] of vars) {
79
+ const channels = /^rgba\((\d+, \d+, \d+), 1\)$/.exec(value);
80
+ if (!channels) continue;
81
+ expect(vars.get(`${name}-rgb`), `${name}-rgb`).toBe(channels[1]);
82
+ }
83
+ });
84
+ });
@@ -0,0 +1,114 @@
1
+ /// <reference types="node" />
2
+ // Not copied into apps, unlike tailwind-plugin.test.ts, which covers the
3
+ // plugin there: this runs Tailwind's own build, through Node and postcss, which
4
+ // an app's `ng test` can't count on. It checks the classes the plugin's colors
5
+ // make, with the Lynx preset, as an app's build does.
6
+ import { createRequire } from 'node:module';
7
+ import preset from '@lynx-js/tailwind-preset';
8
+ import tailwindcss from 'tailwindcss';
9
+ import { describe, expect, it } from 'vitest';
10
+
11
+ import { blotchPlugin } from './tailwind-plugin';
12
+
13
+ /**
14
+ * The part of postcss these tests use.
15
+ */
16
+ type PostCss = (plugins: unknown[]) => {
17
+ process: (
18
+ css: string,
19
+ options: { from: undefined },
20
+ ) => Promise<{ css: string }>;
21
+ };
22
+
23
+ // postcss runs Tailwind. It's Tailwind's own dependency, so it's loaded from
24
+ // there rather than added to this package.
25
+ const postcss = createRequire(
26
+ createRequire(import.meta.url).resolve('tailwindcss'),
27
+ )('postcss') as PostCss;
28
+
29
+ /**
30
+ * Compiles `classes` with Tailwind, the Lynx preset and the plugin, as an
31
+ * app's build does, and returns the CSS.
32
+ */
33
+ const compile = async (classes: string): Promise<string> => {
34
+ const result = await postcss([
35
+ tailwindcss({
36
+ content: [{ raw: classes, extension: 'html' }],
37
+ presets: [preset],
38
+ plugins: [blotchPlugin],
39
+ }),
40
+ ]).process('@tailwind utilities;', { from: undefined });
41
+ return result.css;
42
+ };
43
+
44
+ describe('blotchPlugin colors in a Tailwind build', () => {
45
+ it('keeps plain color classes on the complete color', async () => {
46
+ const css = await compile('bg-primary text-foreground border-border');
47
+
48
+ expect(css).toContain('background-color: var(--primary)');
49
+ expect(css).toContain('color: var(--foreground)');
50
+ expect(css).toContain('border-color: var(--border)');
51
+ // No class reads the channels, so a theme without them still draws.
52
+ expect(css).not.toContain('-rgb');
53
+ });
54
+
55
+ it('turns opacity modifiers into rgba() over the channels', async () => {
56
+ const css = await compile(
57
+ 'bg-primary/50 text-foreground/70 border-border/60 bg-destructive/10 bg-chart-1/[.35]',
58
+ );
59
+
60
+ expect(css).toContain('background-color: rgba(var(--primary-rgb), 0.5)');
61
+ expect(css).toContain('color: rgba(var(--foreground-rgb), 0.7)');
62
+ expect(css).toContain('border-color: rgba(var(--border-rgb), 0.6)');
63
+ expect(css).toContain(
64
+ 'background-color: rgba(var(--destructive-rgb), 0.1)',
65
+ );
66
+ expect(css).toContain('background-color: rgba(var(--chart-1-rgb), .35)');
67
+ });
68
+
69
+ it('gives every mapped color a modifier, except the translucent destructive-subtle and glass-dim', async () => {
70
+ const names = [
71
+ 'background',
72
+ 'foreground',
73
+ 'card',
74
+ 'card-foreground',
75
+ 'primary',
76
+ 'primary-foreground',
77
+ 'secondary',
78
+ 'secondary-foreground',
79
+ 'muted',
80
+ 'muted-foreground',
81
+ 'accent',
82
+ 'accent-foreground',
83
+ 'destructive',
84
+ 'destructive-foreground',
85
+ 'border',
86
+ 'input',
87
+ 'ring',
88
+ 'chart-1',
89
+ 'chart-2',
90
+ 'chart-3',
91
+ 'chart-4',
92
+ 'chart-5',
93
+ 'glass-tint',
94
+ 'glass-tint-foreground',
95
+ 'glass-foreground',
96
+ 'glass-muted-foreground',
97
+ 'glass-clear-foreground',
98
+ ];
99
+ const css = await compile(
100
+ [
101
+ ...names.map((name) => `bg-${name}/50`),
102
+ 'bg-destructive-subtle/50',
103
+ 'bg-glass-dim/50',
104
+ ].join(' '),
105
+ );
106
+
107
+ for (const name of names) {
108
+ expect(css).toContain(`rgba(var(--${name}-rgb), 0.5)`);
109
+ }
110
+ // Tailwind can't add alpha to a complete color, so it makes no class.
111
+ expect(css).not.toContain('destructive-subtle');
112
+ expect(css).not.toContain('glass-dim');
113
+ });
114
+ });
@@ -0,0 +1,83 @@
1
+ /// <reference types="node" />
2
+ // Not copied into apps (it reads the theme's CSS with Node), like the other
3
+ // theme checks next to it.
4
+ import { readFileSync } from 'node:fs';
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ import { blotchPlugin } from './tailwind-plugin';
8
+
9
+ /**
10
+ * tailwind.css is tailwind-plugin.ts for Tailwind 4. Nothing ties the two
11
+ * together, so these tests keep them mapping the same colors and radii, and
12
+ * keep every color it maps backed by the theme files.
13
+ */
14
+ const read = (file: string): string =>
15
+ // Relative to the package, where `ng test` runs: the test runs from a build
16
+ // of it elsewhere, so `import.meta.url` doesn't point next to the CSS.
17
+ readFileSync(`src/lib/theme/${file}`, 'utf8');
18
+
19
+ const tailwindCss = (): string =>
20
+ read('./tailwind.css').replace(/\/\*[\s\S]*?\*\//g, '');
21
+
22
+ /**
23
+ * `--color-x` → the theme variable it reads, from the `@theme` block.
24
+ */
25
+ const mappedColors = (): Map<string, string> =>
26
+ new Map(
27
+ [...tailwindCss().matchAll(/--color-([\w-]+):\s*var\((--[\w-]+)\);/g)].map(
28
+ (match) => [match[1]!, match[2]!],
29
+ ),
30
+ );
31
+
32
+ /**
33
+ * The plugin's color classes, flattened as Tailwind names them.
34
+ */
35
+ const pluginColors = (): Map<string, string> => {
36
+ const factory = blotchPlugin as unknown as {
37
+ config: { theme: { extend: { colors: Record<string, unknown> } } };
38
+ };
39
+ const colors = new Map<string, string>();
40
+ const walk = (value: unknown, path: string[]) => {
41
+ if (typeof value === 'object' && value !== null) {
42
+ for (const [key, child] of Object.entries(value)) {
43
+ walk(child, key === 'DEFAULT' ? path : [...path, key]);
44
+ }
45
+ return;
46
+ }
47
+ const css =
48
+ typeof value === 'function'
49
+ ? (value as (options: object) => string)({})
50
+ : String(value);
51
+ colors.set(path.join('-'), /var\((--[\w-]+)\)/.exec(css)![1]!);
52
+ };
53
+ walk(factory.config.theme.extend.colors, []);
54
+ return colors;
55
+ };
56
+
57
+ describe('tailwind.css', () => {
58
+ it('maps the same colors to the same variables as the plugin', () => {
59
+ expect(mappedColors()).toEqual(pluginColors());
60
+ });
61
+
62
+ it('maps the radii as the plugin does', () => {
63
+ const css = tailwindCss();
64
+ expect(css).toContain('--radius-lg: var(--radius);');
65
+ expect(css).toContain('--radius-md: calc(var(--radius) - 2px);');
66
+ expect(css).toContain('--radius-sm: calc(var(--radius) - 4px);');
67
+ });
68
+
69
+ it.each(['./default.css', './dark.css'])(
70
+ 'reads only variables %s sets, each opaque one with its channels',
71
+ (file) => {
72
+ const css = read(file);
73
+ for (const variable of mappedColors().values()) {
74
+ const value = new RegExp(`${variable}:\\s*([^;]+);`).exec(css)?.[1];
75
+ expect(value, variable).toBeDefined();
76
+ if (value!.endsWith(', 1)')) {
77
+ // The build writes bg-primary/50 as rgba(var(--primary-rgb), 0.5).
78
+ expect(css, `${variable}-rgb`).toContain(`${variable}-rgb:`);
79
+ }
80
+ }
81
+ },
82
+ );
83
+ });
@@ -1,6 +1,10 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
2
 
3
- import { addSafeAreaUtilities, blotchPlugin } from './tailwind-plugin';
3
+ import {
4
+ addSafeAreaUtilities,
5
+ blotchPlugin,
6
+ themeColor,
7
+ } from './tailwind-plugin';
4
8
 
5
9
  /**
6
10
  * These tests exercise the utility generator in isolation with mock Tailwind
@@ -119,6 +123,42 @@ describe('blotchPlugin', () => {
119
123
  };
120
124
  const colors = factory.config?.theme?.extend?.colors;
121
125
  expect(colors).toBeDefined();
122
- expect(colors!['background']).toBe('var(--background)');
126
+ const background = colors!['background'] as (options?: object) => string;
127
+ expect(background()).toBe('var(--background)');
128
+ });
129
+ });
130
+
131
+ describe('themeColor', () => {
132
+ const primary = themeColor('primary');
133
+
134
+ it('reads the complete color when no alpha is asked for', () => {
135
+ // caret-primary, shadow-primary: Tailwind passes no options.
136
+ expect(primary()).toBe('var(--primary)');
137
+ expect(primary({})).toBe('var(--primary)');
138
+ });
139
+
140
+ it("reads the complete color for a plain class with Tailwind's opacity variable", () => {
141
+ // bg-primary: the opacity variable is always 1 unless a deprecated
142
+ // bg-opacity-* utility changes it, and that never worked on these colors.
143
+ expect(
144
+ primary({
145
+ opacityVariable: '--tw-bg-opacity',
146
+ opacityValue: 'var(--tw-bg-opacity, 1)',
147
+ }),
148
+ ).toBe('var(--primary)');
149
+ });
150
+
151
+ it('reads the complete color for an alpha of 1', () => {
152
+ // bg-primary/100 passes '1'; theme('colors.primary') passes the number 1.
153
+ expect(primary({ opacityValue: '1' })).toBe('var(--primary)');
154
+ expect(primary({ opacityValue: 1 })).toBe('var(--primary)');
155
+ });
156
+
157
+ it('reads the channels for an opacity modifier', () => {
158
+ expect(primary({ opacityValue: '0.5' })).toBe(
159
+ 'rgba(var(--primary-rgb), 0.5)',
160
+ );
161
+ // A gradient's transparent end stop asks for 0.
162
+ expect(primary({ opacityValue: 0 })).toBe('rgba(var(--primary-rgb), 0)');
123
163
  });
124
164
  });