@blotch/dolan 22.2.3 → 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 (185) hide show
  1. package/dist/commands/add.js +9 -1
  2. package/dist/commands/add.js.map +1 -1
  3. package/dist/commands/add.test.js +36 -3
  4. package/dist/commands/add.test.js.map +1 -1
  5. package/dist/commands/doctor.js +187 -7
  6. package/dist/commands/doctor.js.map +1 -1
  7. package/dist/commands/doctor.test.js +280 -7
  8. package/dist/commands/doctor.test.js.map +1 -1
  9. package/dist/commands/init.d.ts +2 -2
  10. package/dist/commands/init.js +38 -18
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/init.test.js +107 -1
  13. package/dist/commands/init.test.js.map +1 -1
  14. package/dist/commands/theme.js +1 -1
  15. package/dist/commands/theme.js.map +1 -1
  16. package/dist/commands/update.d.ts +5 -0
  17. package/dist/commands/update.js +126 -45
  18. package/dist/commands/update.js.map +1 -1
  19. package/dist/commands/update.test.js +314 -2
  20. package/dist/commands/update.test.js.map +1 -1
  21. package/dist/lockfile.d.ts +7 -0
  22. package/dist/lockfile.js +36 -0
  23. package/dist/lockfile.js.map +1 -1
  24. package/dist/lockfile.test.d.ts +1 -0
  25. package/dist/lockfile.test.js +25 -0
  26. package/dist/lockfile.test.js.map +1 -0
  27. package/dist/registry.d.ts +6 -0
  28. package/dist/registry.js +48 -2
  29. package/dist/registry.js.map +1 -1
  30. package/dist/registry.test.js +45 -3
  31. package/dist/registry.test.js.map +1 -1
  32. package/dist/test-utils.d.ts +5 -0
  33. package/dist/test-utils.js +6 -1
  34. package/dist/test-utils.js.map +1 -1
  35. package/dist/testing/fire-lynx-event.d.ts +14 -0
  36. package/dist/testing/fire-lynx-event.js +19 -0
  37. package/dist/testing/fire-lynx-event.js.map +1 -0
  38. package/dist/testing/fire-lynx-event.test.d.ts +1 -0
  39. package/dist/testing/fire-lynx-event.test.js +44 -0
  40. package/dist/testing/fire-lynx-event.test.js.map +1 -0
  41. package/dist/testing/lynx-main-thread.d.ts +22 -0
  42. package/dist/testing/lynx-main-thread.js +11 -0
  43. package/dist/testing/lynx-main-thread.js.map +1 -0
  44. package/dist/testing/lynx-main-thread.test.d.ts +1 -0
  45. package/dist/testing/lynx-main-thread.test.js +24 -0
  46. package/dist/testing/lynx-main-thread.test.js.map +1 -0
  47. package/dist/testing/set-input-signal.d.ts +5 -4
  48. package/dist/testing/set-input-signal.js +5 -4
  49. package/dist/testing/set-input-signal.js.map +1 -1
  50. package/dist/ui/components/accordion/accordion.test.ts +158 -144
  51. package/dist/ui/components/accordion/accordion.ts +19 -14
  52. package/dist/ui/components/action-sheet/action-sheet.test.ts +272 -187
  53. package/dist/ui/components/action-sheet/action-sheet.ts +40 -34
  54. package/dist/ui/components/alert/alert.test.ts +67 -19
  55. package/dist/ui/components/alert/alert.ts +14 -16
  56. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +206 -139
  57. package/dist/ui/components/alert-dialog/alert-dialog.ts +32 -26
  58. package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
  59. package/dist/ui/components/avatar/avatar.test.ts +89 -45
  60. package/dist/ui/components/badge/badge.test.ts +57 -23
  61. package/dist/ui/components/badge/badge.ts +12 -14
  62. package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -1
  63. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +373 -297
  64. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +34 -33
  65. package/dist/ui/components/button/button.test.ts +611 -2
  66. package/dist/ui/components/button/button.ts +431 -62
  67. package/dist/ui/components/button/index.ts +2 -0
  68. package/dist/ui/components/button-group/button-group.test.ts +484 -2
  69. package/dist/ui/components/button-group/button-group.ts +380 -68
  70. package/dist/ui/components/button-group/index.ts +3 -0
  71. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +82 -1
  72. package/dist/ui/components/card/card.test.ts +82 -62
  73. package/dist/ui/components/chart/chart-presets.test.ts +197 -1
  74. package/dist/ui/components/chart/chart-presets.ts +8 -1
  75. package/dist/ui/components/chart/chart-theme.ts +12 -0
  76. package/dist/ui/components/chart/chart.test.ts +1017 -0
  77. package/dist/ui/components/chart/chart.ts +56 -43
  78. package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
  79. package/dist/ui/components/checkbox/checkbox.ts +23 -23
  80. package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
  81. package/dist/ui/components/collapsible/collapsible.ts +19 -14
  82. package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
  83. package/dist/ui/components/context-menu/context-menu.ts +110 -0
  84. package/dist/ui/components/context-menu/index.ts +1 -0
  85. package/dist/ui/components/dialog/dialog.test.ts +210 -152
  86. package/dist/ui/components/dialog/dialog.ts +29 -23
  87. package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
  88. package/dist/ui/components/dock/dock-scroll.ts +133 -0
  89. package/dist/ui/components/dock/dock.test.ts +749 -0
  90. package/dist/ui/components/dock/dock.ts +960 -0
  91. package/dist/ui/components/dock/index.ts +2 -0
  92. package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
  93. package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
  94. package/dist/ui/components/glass/concentric-radius.ts +17 -0
  95. package/dist/ui/components/glass/glass-design.test.ts +78 -0
  96. package/dist/ui/components/glass/glass-design.ts +119 -0
  97. package/dist/ui/components/glass/glass.test.ts +338 -0
  98. package/dist/ui/components/glass/glass.ts +360 -0
  99. package/dist/ui/components/glass/index.ts +18 -0
  100. package/dist/ui/components/icon/icon-theme.test.ts +36 -0
  101. package/dist/ui/components/icon/icon-theme.ts +36 -13
  102. package/dist/ui/components/icon/icon.test.ts +52 -41
  103. package/dist/ui/components/input/input.test.ts +178 -101
  104. package/dist/ui/components/input/input.ts +15 -12
  105. package/dist/ui/components/label/label.test.ts +11 -24
  106. package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
  107. package/dist/ui/components/list/list.test.ts +80 -49
  108. package/dist/ui/components/menu/index.ts +25 -0
  109. package/dist/ui/components/menu/menu-placement.test.ts +153 -0
  110. package/dist/ui/components/menu/menu-placement.ts +159 -0
  111. package/dist/ui/components/menu/menu-popup.test.ts +632 -0
  112. package/dist/ui/components/menu/menu-popup.ts +544 -0
  113. package/dist/ui/components/menu/menu.test.ts +429 -0
  114. package/dist/ui/components/menu/menu.ts +415 -0
  115. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +425 -260
  116. package/dist/ui/components/nav-drawer/nav-drawer.ts +72 -56
  117. package/dist/ui/components/pagination/pagination.test.ts +136 -86
  118. package/dist/ui/components/progress/progress.test.ts +102 -46
  119. package/dist/ui/components/progress/progress.ts +52 -54
  120. package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
  121. package/dist/ui/components/radio-group/radio-group.ts +24 -24
  122. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +74 -1
  123. package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
  124. package/dist/ui/components/select/select.test.ts +138 -78
  125. package/dist/ui/components/select/select.ts +17 -11
  126. package/dist/ui/components/separator/separator.test.ts +18 -17
  127. package/dist/ui/components/sheet/sheet.test.ts +64 -11
  128. package/dist/ui/components/sheet/sheet.ts +9 -1
  129. package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
  130. package/dist/ui/components/skeleton/skeleton.ts +10 -16
  131. package/dist/ui/components/spinner/spinner.test.ts +64 -46
  132. package/dist/ui/components/spinner/spinner.ts +16 -19
  133. package/dist/ui/components/switch/switch.test.ts +116 -46
  134. package/dist/ui/components/switch/switch.ts +8 -13
  135. package/dist/ui/components/tab-bar/index.ts +1 -0
  136. package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
  137. package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
  138. package/dist/ui/components/tabs/tabs.test.ts +215 -157
  139. package/dist/ui/components/tabs/tabs.ts +110 -118
  140. package/dist/ui/components/textarea/textarea.test.ts +206 -100
  141. package/dist/ui/components/textarea/textarea.ts +14 -12
  142. package/dist/ui/components/toast/toast.test.ts +434 -350
  143. package/dist/ui/components/toast/toast.ts +42 -51
  144. package/dist/ui/components/toggle/toggle.test.ts +247 -140
  145. package/dist/ui/theme/chart-colors.test.ts +8 -3
  146. package/dist/ui/theme/dark.css +38 -2
  147. package/dist/ui/theme/default.css +55 -7
  148. package/dist/ui/theme/icon-colors.test.ts +8 -3
  149. package/dist/ui/theme/rgb-channels.test.ts +84 -0
  150. package/dist/ui/theme/tailwind-build.test.ts +114 -0
  151. package/dist/ui/theme/tailwind-css.test.ts +83 -0
  152. package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
  153. package/dist/ui/theme/tailwind-plugin.ts +93 -29
  154. package/dist/ui/theme/tailwind.css +113 -0
  155. package/dist/ui/utils/animate.test.ts +13 -1
  156. package/dist/utils/analyze.d.ts +14 -0
  157. package/dist/utils/analyze.js +15 -0
  158. package/dist/utils/analyze.js.map +1 -1
  159. package/dist/utils/analyze.test.js +14 -1
  160. package/dist/utils/analyze.test.js.map +1 -1
  161. package/dist/utils/installed.d.ts +12 -0
  162. package/dist/utils/installed.js +16 -0
  163. package/dist/utils/installed.js.map +1 -1
  164. package/dist/utils/installed.test.js +17 -1
  165. package/dist/utils/installed.test.js.map +1 -1
  166. package/dist/utils/resolve-paths.d.ts +4 -4
  167. package/dist/utils/resolve-paths.js +5 -7
  168. package/dist/utils/resolve-paths.js.map +1 -1
  169. package/dist/utils/resolve-paths.test.js +9 -5
  170. package/dist/utils/resolve-paths.test.js.map +1 -1
  171. package/dist/utils/tailwind.d.ts +25 -0
  172. package/dist/utils/tailwind.js +51 -0
  173. package/dist/utils/tailwind.js.map +1 -0
  174. package/dist/utils/tailwind.test.d.ts +1 -0
  175. package/dist/utils/tailwind.test.js +71 -0
  176. package/dist/utils/tailwind.test.js.map +1 -0
  177. package/package.json +9 -1
  178. package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -63
  179. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -58
  180. package/dist/ui/components/button/button-render.test.ts +0 -132
  181. package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
  182. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -59
  183. package/dist/ui/components/chart/chart-render.test.ts +0 -730
  184. package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -83
  185. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -64
@@ -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,40 +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
 
40
58
  /* Series palette for charts (bg-chart-1, and chart-theme.ts in the chart
41
59
  component, which mirrors these values for SVG). */
42
60
  --chart-1: rgba(38, 98, 217, 1);
61
+ --chart-1-rgb: 38, 98, 217;
43
62
  --chart-2: rgba(46, 184, 138, 1);
63
+ --chart-2-rgb: 46, 184, 138;
44
64
  --chart-3: rgba(232, 140, 48, 1);
65
+ --chart-3-rgb: 232, 140, 48;
45
66
  --chart-4: rgba(175, 87, 219, 1);
67
+ --chart-4-rgb: 175, 87, 219;
46
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);
47
83
 
48
84
  --radius: 8px;
49
85
  }
@@ -13,52 +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
 
55
74
  /* Series palette for charts (bg-chart-1, and chart-theme.ts in the chart
56
75
  component, which mirrors these values for SVG). */
57
76
  --chart-1: rgba(231, 110, 80, 1);
77
+ --chart-1-rgb: 231, 110, 80;
58
78
  --chart-2: rgba(42, 157, 144, 1);
79
+ --chart-2-rgb: 42, 157, 144;
59
80
  --chart-3: rgba(39, 71, 84, 1);
81
+ --chart-3-rgb: 39, 71, 84;
60
82
  --chart-4: rgba(232, 196, 104, 1);
83
+ --chart-4-rgb: 232, 196, 104;
61
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);
62
110
 
63
111
  --radius: 8px;
64
112
  }
@@ -1,3 +1,6 @@
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.
1
4
  import { readFileSync } from 'node:fs';
2
5
  import { describe, expect, it } from 'vitest';
3
6
  import {
@@ -11,7 +14,9 @@ import {
11
14
  * theme's colors. This keeps the copy in step with default.css and dark.css.
12
15
  */
13
16
  const tokens = (file: string): Record<string, string> => {
14
- const css = readFileSync(new URL(file, import.meta.url), 'utf8');
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');
15
20
  const values: Record<string, string> = {};
16
21
  for (const match of css.matchAll(/--([\w-]+):\s*(rgba\([^)]*\))/g)) {
17
22
  values[match[1]!] = match[2]!;
@@ -21,10 +26,10 @@ const tokens = (file: string): Record<string, string> => {
21
26
 
22
27
  describe('icon color tables', () => {
23
28
  it('match default.css', () => {
24
- expect(ICON_LIGHT_COLORS).toEqual(tokens('./default.css'));
29
+ expect(ICON_LIGHT_COLORS).toEqual(tokens('default.css'));
25
30
  });
26
31
 
27
32
  it('match dark.css', () => {
28
- expect(ICON_DARK_COLORS).toEqual(tokens('./dark.css'));
33
+ expect(ICON_DARK_COLORS).toEqual(tokens('dark.css'));
29
34
  });
30
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
  });