@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.
- package/dist/commands/add.d.ts +6 -2
- package/dist/commands/add.js +41 -11
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +162 -9
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/diff.d.ts +2 -1
- package/dist/commands/diff.js +38 -25
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/diff.test.js +72 -11
- package/dist/commands/diff.test.js.map +1 -1
- package/dist/commands/doctor.js +228 -34
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +367 -10
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/eject.js +7 -0
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/eject.test.js +39 -0
- package/dist/commands/eject.test.js.map +1 -1
- package/dist/commands/info.js +3 -2
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/init.d.ts +37 -1
- package/dist/commands/init.js +169 -40
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +392 -27
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/install-packages.d.ts +6 -0
- package/dist/commands/install-packages.js +20 -0
- package/dist/commands/install-packages.js.map +1 -0
- package/dist/commands/install-packages.test.d.ts +1 -0
- package/dist/commands/install-packages.test.js +55 -0
- package/dist/commands/install-packages.test.js.map +1 -0
- package/dist/commands/list.js +21 -14
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/list.test.js +62 -5
- package/dist/commands/list.test.js.map +1 -1
- package/dist/commands/outdated.js +11 -10
- package/dist/commands/outdated.js.map +1 -1
- package/dist/commands/outdated.test.js +20 -5
- package/dist/commands/outdated.test.js.map +1 -1
- package/dist/commands/remove.d.ts +4 -0
- package/dist/commands/remove.js +17 -4
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/remove.test.js +53 -2
- package/dist/commands/remove.test.js.map +1 -1
- package/dist/commands/theme.js +1 -1
- package/dist/commands/theme.js.map +1 -1
- package/dist/commands/update.d.ts +11 -0
- package/dist/commands/update.js +174 -58
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +587 -33
- package/dist/commands/update.test.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/lockfile.d.ts +7 -0
- package/dist/lockfile.js +36 -0
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +25 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +78 -24
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +119 -38
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +5 -0
- package/dist/test-utils.js +6 -1
- package/dist/test-utils.js.map +1 -1
- package/dist/testing/fire-lynx-event.d.ts +14 -0
- package/dist/testing/fire-lynx-event.js +19 -0
- package/dist/testing/fire-lynx-event.js.map +1 -0
- package/dist/testing/fire-lynx-event.test.d.ts +1 -0
- package/dist/testing/fire-lynx-event.test.js +44 -0
- package/dist/testing/fire-lynx-event.test.js.map +1 -0
- package/dist/testing/lynx-main-thread.d.ts +22 -0
- package/dist/testing/lynx-main-thread.js +11 -0
- package/dist/testing/lynx-main-thread.js.map +1 -0
- package/dist/testing/lynx-main-thread.test.d.ts +1 -0
- package/dist/testing/lynx-main-thread.test.js +24 -0
- package/dist/testing/lynx-main-thread.test.js.map +1 -0
- package/dist/testing/set-input-signal.d.ts +14 -0
- package/dist/testing/set-input-signal.js +21 -0
- package/dist/testing/set-input-signal.js.map +1 -0
- package/dist/testing/set-input-signal.test.d.ts +1 -0
- package/dist/testing/set-input-signal.test.js +20 -0
- package/dist/testing/set-input-signal.test.js.map +1 -0
- package/dist/ui/components/accordion/accordion.test.ts +158 -144
- package/dist/ui/components/accordion/accordion.ts +19 -14
- package/dist/ui/components/action-sheet/action-sheet.test.ts +283 -151
- package/dist/ui/components/action-sheet/action-sheet.ts +77 -43
- package/dist/ui/components/alert/alert.test.ts +67 -19
- package/dist/ui/components/alert/alert.ts +14 -16
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +208 -94
- package/dist/ui/components/alert-dialog/alert-dialog.ts +75 -38
- package/dist/ui/components/area-chart/area-chart.test.ts +134 -119
- package/dist/ui/components/area-chart/area-chart.ts +100 -315
- package/dist/ui/components/area-chart/index.ts +2 -3
- package/dist/ui/components/avatar/avatar.test.ts +89 -45
- package/dist/ui/components/badge/badge.test.ts +57 -23
- package/dist/ui/components/badge/badge.ts +12 -14
- package/dist/ui/components/bar-chart/bar-chart.test.ts +172 -45
- package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
- package/dist/ui/components/bar-chart/index.ts +3 -3
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +386 -128
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +170 -53
- package/dist/ui/components/button/button.test.ts +611 -2
- package/dist/ui/components/button/button.ts +434 -49
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +484 -2
- package/dist/ui/components/button-group/button-group.ts +380 -68
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +183 -58
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
- package/dist/ui/components/candlestick-chart/index.ts +3 -4
- package/dist/ui/components/card/card.test.ts +82 -62
- package/dist/ui/components/chart/chart-focus.test.ts +132 -0
- package/dist/ui/components/chart/chart-focus.ts +115 -0
- package/dist/ui/components/chart/chart-presets.test.ts +335 -0
- package/dist/ui/components/chart/chart-presets.ts +278 -0
- package/dist/ui/components/chart/chart-scene.test.ts +589 -0
- package/dist/ui/components/chart/chart-scene.ts +483 -0
- package/dist/ui/components/chart/chart-theme.test.ts +85 -0
- package/dist/ui/components/chart/chart-theme.ts +210 -0
- package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
- package/dist/ui/components/chart/chart-zoom.ts +162 -0
- package/dist/ui/components/chart/chart.test.ts +1017 -0
- package/dist/ui/components/chart/chart.ts +934 -0
- package/dist/ui/components/chart/index.ts +24 -0
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +23 -23
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +19 -14
- package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
- package/dist/ui/components/context-menu/context-menu.ts +110 -0
- package/dist/ui/components/context-menu/index.ts +1 -0
- package/dist/ui/components/dialog/dialog.test.ts +213 -108
- package/dist/ui/components/dialog/dialog.ts +72 -35
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +133 -0
- package/dist/ui/components/dock/dock.test.ts +749 -0
- package/dist/ui/components/dock/dock.ts +960 -0
- package/dist/ui/components/dock/index.ts +2 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
- package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
- package/dist/ui/components/glass/concentric-radius.ts +17 -0
- package/dist/ui/components/glass/glass-design.test.ts +78 -0
- package/dist/ui/components/glass/glass-design.ts +119 -0
- package/dist/ui/components/glass/glass.test.ts +338 -0
- package/dist/ui/components/glass/glass.ts +360 -0
- package/dist/ui/components/glass/index.ts +18 -0
- package/dist/ui/components/icon/icon-theme.test.ts +169 -0
- package/dist/ui/components/icon/icon-theme.ts +227 -0
- package/dist/ui/components/icon/icon.test.ts +85 -25
- package/dist/ui/components/icon/icon.ts +38 -21
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +15 -12
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/line-chart/index.ts +2 -3
- package/dist/ui/components/line-chart/line-chart.test.ts +149 -56
- package/dist/ui/components/line-chart/line-chart.ts +83 -241
- package/dist/ui/components/list/list.test.ts +119 -21
- package/dist/ui/components/list/list.ts +21 -15
- package/dist/ui/components/menu/index.ts +25 -0
- package/dist/ui/components/menu/menu-placement.test.ts +153 -0
- package/dist/ui/components/menu/menu-placement.ts +159 -0
- package/dist/ui/components/menu/menu-popup.test.ts +632 -0
- package/dist/ui/components/menu/menu-popup.ts +544 -0
- package/dist/ui/components/menu/menu.test.ts +429 -0
- package/dist/ui/components/menu/menu.ts +415 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +435 -191
- package/dist/ui/components/nav-drawer/nav-drawer.ts +127 -56
- package/dist/ui/components/pagination/pagination.test.ts +177 -65
- package/dist/ui/components/pagination/pagination.ts +55 -8
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +52 -54
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +24 -24
- package/dist/ui/components/scatter-chart/index.ts +2 -3
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +131 -26
- package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
- package/dist/ui/components/scroll-area/scroll-area.test.ts +95 -24
- package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +17 -11
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +64 -11
- package/dist/ui/components/sheet/sheet.ts +9 -1
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/spinner/spinner.test.ts +67 -22
- package/dist/ui/components/spinner/spinner.ts +48 -41
- package/dist/ui/components/switch/switch.test.ts +116 -46
- package/dist/ui/components/switch/switch.ts +8 -13
- package/dist/ui/components/tab-bar/index.ts +1 -0
- package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
- package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
- package/dist/ui/components/tabs/tabs.test.ts +215 -157
- package/dist/ui/components/tabs/tabs.ts +110 -118
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +14 -12
- package/dist/ui/components/toast/toast.test.ts +436 -315
- package/dist/ui/components/toast/toast.ts +62 -55
- package/dist/ui/components/toggle/toggle.test.ts +247 -140
- package/dist/ui/theme/chart-colors.test.ts +35 -0
- package/dist/ui/theme/dark.css +46 -2
- package/dist/ui/theme/default.css +63 -7
- package/dist/ui/theme/icon-colors.test.ts +35 -0
- package/dist/ui/theme/rgb-channels.test.ts +84 -0
- package/dist/ui/theme/tailwind-build.test.ts +114 -0
- package/dist/ui/theme/tailwind-css.test.ts +83 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
- package/dist/ui/theme/tailwind-plugin.ts +98 -25
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +14 -0
- package/dist/utils/analyze.js +15 -0
- package/dist/utils/analyze.js.map +1 -1
- package/dist/utils/analyze.test.js +14 -1
- package/dist/utils/analyze.test.js.map +1 -1
- package/dist/utils/installed.d.ts +29 -0
- package/dist/utils/installed.js +43 -0
- package/dist/utils/installed.js.map +1 -0
- package/dist/utils/installed.test.d.ts +1 -0
- package/dist/utils/installed.test.js +57 -0
- package/dist/utils/installed.test.js.map +1 -0
- package/dist/utils/interactive.d.ts +9 -0
- package/dist/utils/interactive.js +10 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/interactive.test.d.ts +1 -0
- package/dist/utils/interactive.test.js +24 -0
- package/dist/utils/interactive.test.js.map +1 -0
- package/dist/utils/packages.d.ts +36 -0
- package/dist/utils/packages.js +70 -0
- package/dist/utils/packages.js.map +1 -0
- package/dist/utils/packages.test.d.ts +1 -0
- package/dist/utils/packages.test.js +89 -0
- package/dist/utils/packages.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +17 -1
- package/dist/utils/resolve-paths.js +21 -3
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +38 -2
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +25 -0
- package/dist/utils/tailwind.js +51 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +71 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +13 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -121
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -174
- package/dist/ui/components/button/button-render.test.ts +0 -113
- package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -174
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
- package/dist/ui/components/cartesian-chart/index.ts +0 -18
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -121
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -157
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
inject,
|
|
4
|
+
InjectionToken,
|
|
5
|
+
type Provider,
|
|
6
|
+
type Signal,
|
|
7
|
+
} from '@angular/core';
|
|
8
|
+
import { LynxTheme } from '@blotch/angular-lynx';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Colors for the components that draw with Lynx's `<svg>`: `<ui-icon>`,
|
|
12
|
+
* `<ui-spinner>`, and the parts built on them (pagination's chevrons, a loading
|
|
13
|
+
* button's spinner).
|
|
14
|
+
*
|
|
15
|
+
* ## Why colors live in TypeScript as well as CSS
|
|
16
|
+
* `<svg>` parses its markup by itself. It never sees the page's CSS: on the web
|
|
17
|
+
* preview the markup becomes an `<img>`, and the native elements in Lynx 4.1
|
|
18
|
+
* read only a literal `current-color`. So `var(--foreground)` and text color
|
|
19
|
+
* classes can't reach a stroke, and Lynx has no computed-style API to read a
|
|
20
|
+
* variable's value from JS. Instead these components take `var(--token)` colors
|
|
21
|
+
* and swap each for its value in the active table below before they write the
|
|
22
|
+
* markup. This mirrors how `<ui-chart>` colors its marks (`chart-theme.ts`).
|
|
23
|
+
*
|
|
24
|
+
* The tables mirror `default.css` and `dark.css` (a test in the theme folder
|
|
25
|
+
* keeps them in step). If you change a color there, change it here too, or pass
|
|
26
|
+
* your own with {@link provideIconTheme}.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** Theme token name (without `--`) to a complete CSS color. */
|
|
30
|
+
export type IconColorTokens = Readonly<Record<string, string>>;
|
|
31
|
+
|
|
32
|
+
export type IconColorScheme = 'light' | 'dark';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Which table the colors come from: a fixed scheme, `'system'` to follow the
|
|
36
|
+
* device appearance (`LynxTheme`), or a function reading the app's own theme
|
|
37
|
+
* state.
|
|
38
|
+
*
|
|
39
|
+
* It has to match how the app switches its CSS theme, since the text next to an
|
|
40
|
+
* icon reads the CSS variables. The ui theme's dark mode is opt-in (the app
|
|
41
|
+
* toggles the `.dark` class), so the default is `'light'`: an icon in an app
|
|
42
|
+
* without dark mode stays visible when the device is dark.
|
|
43
|
+
*/
|
|
44
|
+
export type IconColorSchemeOption =
|
|
45
|
+
| IconColorScheme
|
|
46
|
+
| 'system'
|
|
47
|
+
| (() => IconColorScheme);
|
|
48
|
+
|
|
49
|
+
export type UiIconTheme = {
|
|
50
|
+
/** Token values for light mode; mirrors `default.css`. */
|
|
51
|
+
readonly light: IconColorTokens;
|
|
52
|
+
/** Token values for dark mode; mirrors `dark.css`. */
|
|
53
|
+
readonly dark: IconColorTokens;
|
|
54
|
+
/** See {@link IconColorSchemeOption}; `'light'` when unset. */
|
|
55
|
+
readonly colorScheme?: IconColorSchemeOption;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const ICON_LIGHT_COLORS: IconColorTokens = {
|
|
59
|
+
background: 'rgba(255, 255, 255, 1)',
|
|
60
|
+
foreground: 'rgba(9, 9, 11, 1)',
|
|
61
|
+
card: 'rgba(255, 255, 255, 1)',
|
|
62
|
+
'card-foreground': 'rgba(9, 9, 11, 1)',
|
|
63
|
+
primary: 'rgba(24, 24, 27, 1)',
|
|
64
|
+
'primary-foreground': 'rgba(250, 250, 250, 1)',
|
|
65
|
+
secondary: 'rgba(244, 244, 245, 1)',
|
|
66
|
+
'secondary-foreground': 'rgba(24, 24, 27, 1)',
|
|
67
|
+
muted: 'rgba(244, 244, 245, 1)',
|
|
68
|
+
'muted-foreground': 'rgba(113, 113, 122, 1)',
|
|
69
|
+
accent: 'rgba(244, 244, 245, 1)',
|
|
70
|
+
'accent-foreground': 'rgba(24, 24, 27, 1)',
|
|
71
|
+
destructive: 'rgba(239, 68, 68, 1)',
|
|
72
|
+
'destructive-foreground': 'rgba(250, 250, 250, 1)',
|
|
73
|
+
'destructive-subtle': 'rgba(239, 68, 68, 0.1)',
|
|
74
|
+
border: 'rgba(228, 228, 231, 1)',
|
|
75
|
+
input: 'rgba(228, 228, 231, 1)',
|
|
76
|
+
ring: 'rgba(24, 24, 27, 1)',
|
|
77
|
+
'ring-subtle': 'rgba(24, 24, 27, 0.4)',
|
|
78
|
+
'chart-1': 'rgba(231, 110, 80, 1)',
|
|
79
|
+
'chart-2': 'rgba(42, 157, 144, 1)',
|
|
80
|
+
'chart-3': 'rgba(39, 71, 84, 1)',
|
|
81
|
+
'chart-4': 'rgba(232, 196, 104, 1)',
|
|
82
|
+
'chart-5': 'rgba(244, 164, 98, 1)',
|
|
83
|
+
'glass-tint': 'rgba(24, 24, 27, 1)',
|
|
84
|
+
'glass-tint-foreground': 'rgba(250, 250, 250, 1)',
|
|
85
|
+
'glass-foreground': 'rgba(9, 9, 11, 1)',
|
|
86
|
+
'glass-muted-foreground': 'rgba(63, 63, 70, 1)',
|
|
87
|
+
'glass-clear-foreground': 'rgba(255, 255, 255, 1)',
|
|
88
|
+
'glass-dim': 'rgba(0, 0, 0, 0.35)',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const ICON_DARK_COLORS: IconColorTokens = {
|
|
92
|
+
background: 'rgba(9, 9, 11, 1)',
|
|
93
|
+
foreground: 'rgba(250, 250, 250, 1)',
|
|
94
|
+
card: 'rgba(9, 9, 11, 1)',
|
|
95
|
+
'card-foreground': 'rgba(250, 250, 250, 1)',
|
|
96
|
+
primary: 'rgba(250, 250, 250, 1)',
|
|
97
|
+
'primary-foreground': 'rgba(24, 24, 27, 1)',
|
|
98
|
+
secondary: 'rgba(39, 39, 42, 1)',
|
|
99
|
+
'secondary-foreground': 'rgba(250, 250, 250, 1)',
|
|
100
|
+
muted: 'rgba(39, 39, 42, 1)',
|
|
101
|
+
'muted-foreground': 'rgba(161, 161, 170, 1)',
|
|
102
|
+
accent: 'rgba(39, 39, 42, 1)',
|
|
103
|
+
'accent-foreground': 'rgba(250, 250, 250, 1)',
|
|
104
|
+
destructive: 'rgba(127, 29, 29, 1)',
|
|
105
|
+
'destructive-foreground': 'rgba(250, 250, 250, 1)',
|
|
106
|
+
'destructive-subtle': 'rgba(127, 29, 29, 0.1)',
|
|
107
|
+
border: 'rgba(39, 39, 42, 1)',
|
|
108
|
+
input: 'rgba(39, 39, 42, 1)',
|
|
109
|
+
ring: 'rgba(212, 212, 216, 1)',
|
|
110
|
+
'ring-subtle': 'rgba(212, 212, 216, 0.4)',
|
|
111
|
+
'chart-1': 'rgba(38, 98, 217, 1)',
|
|
112
|
+
'chart-2': 'rgba(46, 184, 138, 1)',
|
|
113
|
+
'chart-3': 'rgba(232, 140, 48, 1)',
|
|
114
|
+
'chart-4': 'rgba(175, 87, 219, 1)',
|
|
115
|
+
'chart-5': 'rgba(226, 54, 112, 1)',
|
|
116
|
+
'glass-tint': 'rgba(250, 250, 250, 1)',
|
|
117
|
+
'glass-tint-foreground': 'rgba(24, 24, 27, 1)',
|
|
118
|
+
'glass-foreground': 'rgba(250, 250, 250, 1)',
|
|
119
|
+
'glass-muted-foreground': 'rgba(212, 212, 216, 1)',
|
|
120
|
+
'glass-clear-foreground': 'rgba(255, 255, 255, 1)',
|
|
121
|
+
'glass-dim': 'rgba(0, 0, 0, 0.35)',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const UI_ICON_THEME = new InjectionToken<UiIconTheme>('UI_ICON_THEME', {
|
|
125
|
+
providedIn: 'root',
|
|
126
|
+
factory: () => ({ light: ICON_LIGHT_COLORS, dark: ICON_DARK_COLORS }),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Overrides icon colors, merged over the defaults: pass only the tokens you
|
|
131
|
+
* changed in your CSS, or just a color scheme.
|
|
132
|
+
*
|
|
133
|
+
* ```ts
|
|
134
|
+
* // An app that switches .dark with the device appearance:
|
|
135
|
+
* providers: [provideIconTheme({ colorScheme: 'system' })]
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export const provideIconTheme = (theme: {
|
|
139
|
+
light?: IconColorTokens;
|
|
140
|
+
dark?: IconColorTokens;
|
|
141
|
+
colorScheme?: IconColorSchemeOption;
|
|
142
|
+
}): Provider => ({
|
|
143
|
+
provide: UI_ICON_THEME,
|
|
144
|
+
useValue: {
|
|
145
|
+
light: { ...ICON_LIGHT_COLORS, ...theme.light },
|
|
146
|
+
dark: { ...ICON_DARK_COLORS, ...theme.dark },
|
|
147
|
+
colorScheme: theme.colorScheme,
|
|
148
|
+
} satisfies UiIconTheme,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The color icons inside an element use when they're given none. A container
|
|
153
|
+
* whose variant recolors its content (a destructive nav drawer item) provides
|
|
154
|
+
* it, and every `<ui-icon>` projected into it picks it up through DI, the way
|
|
155
|
+
* text would inherit `color` on the web.
|
|
156
|
+
*/
|
|
157
|
+
export const UI_ICON_COLOR = new InjectionToken<Signal<string | undefined>>(
|
|
158
|
+
'UI_ICON_COLOR',
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
/** What an icon draws with when nothing sets its color. */
|
|
162
|
+
export const DEFAULT_ICON_COLOR = 'var(--foreground)';
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Turns a color into one an SVG parser can draw: `var(--token)` (with or
|
|
166
|
+
* without a fallback) becomes the token's value. Anything else (`#2563eb`,
|
|
167
|
+
* `rgba(…)`) is already concrete and passes through.
|
|
168
|
+
*/
|
|
169
|
+
export const resolveIconColor = (
|
|
170
|
+
color: string,
|
|
171
|
+
tokens: IconColorTokens,
|
|
172
|
+
): string => {
|
|
173
|
+
const value = color.trim();
|
|
174
|
+
if (!value.startsWith('var(') || !value.endsWith(')')) return value;
|
|
175
|
+
|
|
176
|
+
const inner = value.slice(4, -1);
|
|
177
|
+
// A custom property name can't contain a comma, so the first comma always
|
|
178
|
+
// separates the name from the fallback.
|
|
179
|
+
const comma = inner.indexOf(',');
|
|
180
|
+
const name = (comma < 0 ? inner : inner.slice(0, comma)).trim();
|
|
181
|
+
const token = tokens[name.replace(/^--/, '')];
|
|
182
|
+
if (token !== undefined) return token;
|
|
183
|
+
// An unknown token falls back the way CSS does: to the var()'s fallback when
|
|
184
|
+
// it has one, and otherwise to the foreground so the icon stays visible.
|
|
185
|
+
return comma < 0
|
|
186
|
+
? (tokens['foreground'] ?? value)
|
|
187
|
+
: resolveIconColor(inner.slice(comma + 1), tokens);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The color scheme the app's theme is in, as {@link provideIconTheme} sets it
|
|
192
|
+
* (`'light'` unless it says otherwise). For what has to match the app's CSS
|
|
193
|
+
* theme but can't read it: icons pick their color table with it, and glass
|
|
194
|
+
* picks a light or dark material.
|
|
195
|
+
*/
|
|
196
|
+
export const injectColorScheme = (): Signal<IconColorScheme> => {
|
|
197
|
+
const option = inject(UI_ICON_THEME).colorScheme ?? 'light';
|
|
198
|
+
const appearance = inject(LynxTheme);
|
|
199
|
+
return computed(() =>
|
|
200
|
+
option === 'system'
|
|
201
|
+
? appearance.isDarkMode()
|
|
202
|
+
? 'dark'
|
|
203
|
+
: 'light'
|
|
204
|
+
: typeof option === 'function'
|
|
205
|
+
? option()
|
|
206
|
+
: option,
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* For an SVG component's constructor: a function that resolves its `color`
|
|
212
|
+
* input (or, without one, the surrounding {@link UI_ICON_COLOR}, then the
|
|
213
|
+
* foreground) against the active color table. It reads signals, so a
|
|
214
|
+
* `computed()` calling it follows scheme and theme changes.
|
|
215
|
+
*/
|
|
216
|
+
export const injectIconColor = (): ((color: string | undefined) => string) => {
|
|
217
|
+
const theme = inject(UI_ICON_THEME);
|
|
218
|
+
const scheme = injectColorScheme();
|
|
219
|
+
const inherited = inject(UI_ICON_COLOR, { optional: true });
|
|
220
|
+
|
|
221
|
+
const tokens = computed(() =>
|
|
222
|
+
scheme() === 'dark' ? theme.dark : theme.light,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
return (color) =>
|
|
226
|
+
resolveIconColor(color ?? inherited?.() ?? DEFAULT_ICON_COLOR, tokens());
|
|
227
|
+
};
|
|
@@ -1,47 +1,107 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
2
|
import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
|
|
3
|
-
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
4
3
|
import { describe, expect, it } from 'vitest';
|
|
5
|
-
import { setInputSignal } from '../../../test-utils/set-input-signal';
|
|
6
4
|
import { UiIcon } from './icon';
|
|
5
|
+
import {
|
|
6
|
+
ICON_DARK_COLORS,
|
|
7
|
+
ICON_LIGHT_COLORS,
|
|
8
|
+
UI_ICON_COLOR,
|
|
9
|
+
provideIconTheme,
|
|
10
|
+
} from './icon-theme';
|
|
7
11
|
import { ICONS, type IconName } from './icons';
|
|
8
12
|
|
|
9
13
|
@Component({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
imports: [UiIcon],
|
|
15
|
+
template: `
|
|
16
|
+
<ui-icon
|
|
17
|
+
[name]="name()"
|
|
18
|
+
[size]="size()"
|
|
19
|
+
[color]="color()"
|
|
20
|
+
[class]="className()"
|
|
21
|
+
/>
|
|
22
|
+
`,
|
|
13
23
|
})
|
|
14
24
|
class IconHost {
|
|
15
|
-
name
|
|
16
|
-
size
|
|
17
|
-
color
|
|
25
|
+
readonly name = signal<IconName>('check');
|
|
26
|
+
readonly size = signal<'xs' | 'sm' | 'md' | 'lg'>('md');
|
|
27
|
+
readonly color = signal<string | undefined>(undefined);
|
|
28
|
+
readonly className = signal('');
|
|
18
29
|
}
|
|
19
30
|
|
|
31
|
+
const renderIcon = async (providers: Parameters<typeof render>[1] = {}) => {
|
|
32
|
+
const result = await render(IconHost, providers);
|
|
33
|
+
return {
|
|
34
|
+
...result,
|
|
35
|
+
host: result.componentRef.instance as IconHost,
|
|
36
|
+
svg: () => result.container.querySelector('svg')!,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
20
40
|
describe('UiIcon', () => {
|
|
21
|
-
it('
|
|
22
|
-
const {
|
|
23
|
-
expect(
|
|
41
|
+
it('draws the named icon at the default size', async () => {
|
|
42
|
+
const { svg } = await renderIcon();
|
|
43
|
+
expect(svg().getAttribute('style')).toContain('24px');
|
|
44
|
+
expect(svg().getAttribute('content')).toContain('<svg');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('draws another icon when the name changes', async () => {
|
|
48
|
+
const { host, svg } = await renderIcon();
|
|
49
|
+
const check = svg().getAttribute('content');
|
|
50
|
+
host.name.set('x');
|
|
51
|
+
await waitForUpdate();
|
|
52
|
+
expect(svg().getAttribute('content')).not.toBe(check);
|
|
24
53
|
});
|
|
25
54
|
|
|
26
55
|
it('applies the size input to the width/height style', async () => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const { componentRef, container } = await render(UiIcon);
|
|
30
|
-
setInputSignal((componentRef.instance as UiIcon).name, 'check' as IconName);
|
|
31
|
-
setInputSignal((componentRef.instance as UiIcon).size, 'xs');
|
|
56
|
+
const { host, svg } = await renderIcon();
|
|
57
|
+
host.size.set('xs');
|
|
32
58
|
await waitForUpdate();
|
|
33
|
-
|
|
34
|
-
expect(svg?.getAttribute('style')).toContain('16px');
|
|
59
|
+
expect(svg().getAttribute('style')).toContain('16px');
|
|
35
60
|
});
|
|
36
61
|
|
|
37
62
|
it('substitutes currentColor with the color input value', async () => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
63
|
+
// Lynx's SVG parser reads neither `currentColor` nor CSS variables.
|
|
64
|
+
const { host, svg } = await renderIcon();
|
|
65
|
+
host.color.set('rgb(255, 0, 0)');
|
|
66
|
+
await waitForUpdate();
|
|
67
|
+
expect(svg().getAttribute('content')).toContain('rgb(255, 0, 0)');
|
|
68
|
+
expect(svg().getAttribute('content')).not.toContain('currentColor');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("draws in the light theme's foreground when no color is set", async () => {
|
|
72
|
+
const { svg } = await renderIcon();
|
|
73
|
+
const content = svg().getAttribute('content');
|
|
74
|
+
expect(content).toContain(ICON_LIGHT_COLORS['foreground']);
|
|
75
|
+
expect(content).not.toContain('currentColor');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('resolves a theme token against the active color scheme', async () => {
|
|
79
|
+
const { host, svg } = await renderIcon({
|
|
80
|
+
providers: [provideIconTheme({ colorScheme: 'dark' })],
|
|
81
|
+
});
|
|
82
|
+
host.color.set('var(--primary)');
|
|
83
|
+
await waitForUpdate();
|
|
84
|
+
expect(svg().getAttribute('content')).toContain(
|
|
85
|
+
ICON_DARK_COLORS['primary'],
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("takes its container's color when it sets none", async () => {
|
|
90
|
+
const { svg } = await renderIcon({
|
|
91
|
+
providers: [
|
|
92
|
+
{ provide: UI_ICON_COLOR, useValue: signal('var(--destructive)') },
|
|
93
|
+
],
|
|
94
|
+
});
|
|
95
|
+
expect(svg().getAttribute('content')).toContain(
|
|
96
|
+
ICON_LIGHT_COLORS['destructive'],
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('applies the class input to the svg', async () => {
|
|
101
|
+
const { host, svg } = await renderIcon();
|
|
102
|
+
host.className.set('mr-2');
|
|
41
103
|
await waitForUpdate();
|
|
42
|
-
|
|
43
|
-
expect(svg?.getAttribute('content')).toContain('rgb(255, 0, 0)');
|
|
44
|
-
expect(svg?.getAttribute('content')).not.toContain('currentColor');
|
|
104
|
+
expect(svg().getAttribute('class')).toBe('mr-2');
|
|
45
105
|
});
|
|
46
106
|
});
|
|
47
107
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Component, ViewEncapsulation, input } from '@angular/core';
|
|
2
2
|
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
3
3
|
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
import { injectIconColor } from './icon-theme';
|
|
4
6
|
import { type IconName, ICONS } from './icons';
|
|
5
7
|
|
|
6
8
|
const SIZE_MAP = { xs: 16, sm: 20, md: 24, lg: 32 } as const;
|
|
@@ -16,39 +18,54 @@ const SIZE_MAP = { xs: 16, sm: 20, md: 24, lg: 32 } as const;
|
|
|
16
18
|
imports: [LYNX_ELEMENTS],
|
|
17
19
|
encapsulation: ViewEncapsulation.None,
|
|
18
20
|
template: `
|
|
19
|
-
<svg
|
|
21
|
+
<svg
|
|
22
|
+
[attr.content]="svgContent()"
|
|
23
|
+
[attr.style]="sizeStyle()"
|
|
24
|
+
[class]="svgClass()"
|
|
25
|
+
/>
|
|
20
26
|
`,
|
|
21
27
|
})
|
|
22
28
|
export class UiIcon {
|
|
23
29
|
readonly name = input.required<IconName>();
|
|
24
30
|
readonly size = input<'xs' | 'sm' | 'md' | 'lg'>('md');
|
|
31
|
+
/**
|
|
32
|
+
* A literal color or a theme token (`var(--primary)`). Without one, the icon
|
|
33
|
+
* takes the color its container sets (see `UI_ICON_COLOR`), then the theme's
|
|
34
|
+
* foreground.
|
|
35
|
+
*/
|
|
25
36
|
readonly color = input<string | undefined>(undefined);
|
|
37
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
38
|
+
|
|
39
|
+
readonly #resolveColor = injectIconColor();
|
|
26
40
|
|
|
27
41
|
protected svgContent(): string {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return svg;
|
|
42
|
+
// Lynx SVG elements don't propagate the CSS `color` property into SVG
|
|
43
|
+
// stroke attributes, so we substitute the literal `currentColor` in the
|
|
44
|
+
// markup string rather than relying on `currentColor` CSS propagation.
|
|
45
|
+
// The color is resolved first: the SVG parser can't read `var(--x)` either
|
|
46
|
+
// (see icon-theme.ts).
|
|
47
|
+
//
|
|
48
|
+
// Uses split().join() instead of String.prototype.replaceAll(). replaceAll
|
|
49
|
+
// is ES2021, but Lynx's main thread runs on PrimJS at an ES2019 target;
|
|
50
|
+
// because it's a runtime method (not syntax) the build's down-leveling does
|
|
51
|
+
// not polyfill it, so it simply doesn't exist there. Calling it throws
|
|
52
|
+
// "main-thread.js exception: not a function" from inside Angular change
|
|
53
|
+
// detection, which aborts the whole tick before Lynx flushes the element
|
|
54
|
+
// tree — so NO icon paints (not just a colored one), and the error count
|
|
55
|
+
// climbs on every render/interaction as each tick re-runs and re-throws.
|
|
56
|
+
// split/join is ES2019-safe and is preferred over `replace(/.../g)` here
|
|
57
|
+
// because the search term is a plain literal and needs no regex escaping.
|
|
58
|
+
return ICONS[this.name()]
|
|
59
|
+
.split('currentColor')
|
|
60
|
+
.join(this.#resolveColor(this.color()));
|
|
48
61
|
}
|
|
49
62
|
|
|
50
63
|
protected sizeStyle(): string {
|
|
51
64
|
const px = SIZE_MAP[this.size()];
|
|
52
65
|
return `width: ${px}px; height: ${px}px;`;
|
|
53
66
|
}
|
|
67
|
+
|
|
68
|
+
protected svgClass(): string {
|
|
69
|
+
return cn(this.userClass());
|
|
70
|
+
}
|
|
54
71
|
}
|