@blotch/dolan 22.2.3 → 22.2.5
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.js +9 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +38 -3
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/doctor.js +258 -21
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +425 -11
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +38 -18
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +105 -2
- package/dist/commands/init.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 +7 -0
- package/dist/commands/update.js +194 -50
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +570 -3
- package/dist/commands/update.test.js.map +1 -1
- package/dist/lockfile.d.ts +17 -0
- package/dist/lockfile.js +56 -1
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +65 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js +119 -11
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +61 -6
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +27 -0
- package/dist/test-utils.js +45 -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 +5 -4
- package/dist/testing/set-input-signal.js +5 -4
- package/dist/testing/set-input-signal.js.map +1 -1
- package/dist/ui/boolean-inputs.test.ts +76 -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 +637 -183
- package/dist/ui/components/action-sheet/action-sheet.ts +339 -55
- package/dist/ui/components/action-sheet/index.ts +1 -0
- 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 +740 -129
- package/dist/ui/components/alert-dialog/alert-dialog.ts +672 -58
- package/dist/ui/components/alert-dialog/index.ts +5 -0
- package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
- package/dist/ui/components/area-chart/area-chart.ts +3 -2
- 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 +14 -15
- package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -1
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +1314 -273
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1046 -119
- package/dist/ui/components/bottom-sheet/index.ts +7 -1
- package/dist/ui/components/button/button.test.ts +646 -2
- package/dist/ui/components/button/button.ts +443 -64
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +528 -2
- package/dist/ui/components/button-group/button-group.ts +428 -70
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +82 -1
- package/dist/ui/components/card/card.test.ts +97 -61
- package/dist/ui/components/card/card.ts +2 -1
- package/dist/ui/components/chart/chart-presets.test.ts +197 -1
- package/dist/ui/components/chart/chart-presets.ts +14 -7
- package/dist/ui/components/chart/chart-theme.ts +12 -0
- package/dist/ui/components/chart/chart.test.ts +1031 -0
- package/dist/ui/components/chart/chart.ts +66 -46
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +25 -24
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +21 -15
- 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 +210 -152
- package/dist/ui/components/dialog/dialog.ts +35 -29
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +142 -0
- package/dist/ui/components/dock/dock-search.test.ts +594 -0
- package/dist/ui/components/dock/dock.test.ts +752 -0
- package/dist/ui/components/dock/dock.ts +1438 -0
- package/dist/ui/components/dock/index.ts +2 -0
- package/dist/ui/components/dynamic-color/dynamic-color.test.ts +144 -0
- package/dist/ui/components/dynamic-color/dynamic-color.ts +146 -0
- package/dist/ui/components/dynamic-color/index.ts +5 -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 +376 -0
- package/dist/ui/components/glass/glass.ts +430 -0
- package/dist/ui/components/glass/index.ts +19 -0
- package/dist/ui/components/icon/icon-theme.test.ts +64 -0
- package/dist/ui/components/icon/icon-theme.ts +70 -16
- package/dist/ui/components/icon/icon.test.ts +52 -41
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +17 -13
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/label/label.ts +8 -2
- package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
- package/dist/ui/components/line-chart/line-chart.ts +3 -2
- package/dist/ui/components/list/list.test.ts +80 -49
- package/dist/ui/components/list/list.ts +5 -4
- 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 +636 -0
- package/dist/ui/components/menu/menu-popup.ts +565 -0
- package/dist/ui/components/menu/menu.test.ts +523 -0
- package/dist/ui/components/menu/menu.ts +455 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +425 -260
- package/dist/ui/components/nav-drawer/nav-drawer.ts +75 -58
- package/dist/ui/components/navigation-bar/index.ts +1 -0
- package/dist/ui/components/navigation-bar/navigation-bar.test.ts +425 -0
- package/dist/ui/components/navigation-bar/navigation-bar.ts +411 -0
- package/dist/ui/components/pagination/pagination.test.ts +136 -86
- package/dist/ui/components/popover/index.ts +9 -0
- package/dist/ui/components/popover/popover-panel.test.ts +735 -0
- package/dist/ui/components/popover/popover-panel.ts +603 -0
- package/dist/ui/components/popover/popover.test.ts +286 -0
- package/dist/ui/components/popover/popover.ts +207 -0
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +54 -55
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +26 -25
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +74 -1
- package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
- package/dist/ui/components/scroll-area/scroll-area.ts +4 -3
- package/dist/ui/components/search-field/index.ts +15 -0
- package/dist/ui/components/search-field/keyboard.test.ts +147 -0
- package/dist/ui/components/search-field/keyboard.ts +123 -0
- package/dist/ui/components/search-field/search-field.test.ts +549 -0
- package/dist/ui/components/search-field/search-field.ts +628 -0
- package/dist/ui/components/segmented-control/index.ts +8 -0
- package/dist/ui/components/segmented-control/segmented-control.test.ts +861 -0
- package/dist/ui/components/segmented-control/segmented-control.ts +884 -0
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +19 -12
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +151 -11
- package/dist/ui/components/sheet/sheet.ts +68 -5
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/slider/index.ts +1 -0
- package/dist/ui/components/slider/slider.test.ts +695 -0
- package/dist/ui/components/slider/slider.ts +901 -0
- package/dist/ui/components/spinner/spinner.test.ts +64 -46
- package/dist/ui/components/spinner/spinner.ts +16 -19
- package/dist/ui/components/switch/index.ts +1 -1
- package/dist/ui/components/switch/switch.test.ts +534 -48
- package/dist/ui/components/switch/switch.ts +613 -48
- 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/index.ts +7 -1
- package/dist/ui/components/tabs/tabs.test.ts +484 -150
- package/dist/ui/components/tabs/tabs.ts +344 -149
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +16 -13
- package/dist/ui/components/toast/toast.test.ts +434 -350
- package/dist/ui/components/toast/toast.ts +42 -51
- package/dist/ui/components/toggle/index.ts +1 -0
- package/dist/ui/components/toggle/toggle.test.ts +559 -146
- package/dist/ui/components/toggle/toggle.ts +241 -26
- package/dist/ui/components/toolbar/bar-scroll.test.ts +310 -0
- package/dist/ui/components/toolbar/bar-scroll.ts +372 -0
- package/dist/ui/components/toolbar/index.ts +12 -0
- package/dist/ui/components/toolbar/toolbar.test.ts +438 -0
- package/dist/ui/components/toolbar/toolbar.ts +535 -0
- package/dist/ui/theme/chart-colors.test.ts +8 -3
- package/dist/ui/theme/dark.css +38 -2
- package/dist/ui/theme/default.css +55 -7
- package/dist/ui/theme/icon-colors.test.ts +8 -3
- 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 +93 -29
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +19 -0
- package/dist/utils/analyze.js +16 -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 +25 -1
- package/dist/utils/installed.js +33 -0
- package/dist/utils/installed.js.map +1 -1
- package/dist/utils/installed.test.js +55 -1
- package/dist/utils/installed.test.js.map +1 -1
- package/dist/utils/resolve-paths.d.ts +4 -4
- package/dist/utils/resolve-paths.js +5 -7
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +9 -5
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +45 -0
- package/dist/utils/tailwind.js +90 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +134 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +9 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -63
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -58
- package/dist/ui/components/button/button-render.test.ts +0 -132
- 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 -59
- package/dist/ui/components/chart/chart-render.test.ts +0 -730
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -83
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -64
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Component, computed, signal } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
DYNAMIC_COLOR_ROLES,
|
|
4
|
+
LYNX_ELEMENTS,
|
|
5
|
+
LynxTheme,
|
|
6
|
+
type DynamicColors,
|
|
7
|
+
type DynamicColorScheme,
|
|
8
|
+
} from '@blotch/angular-lynx';
|
|
9
|
+
import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
import {
|
|
12
|
+
ICON_LIGHT_COLORS,
|
|
13
|
+
injectIconColor,
|
|
14
|
+
provideIconTheme,
|
|
15
|
+
} from '../icon/icon-theme';
|
|
16
|
+
import {
|
|
17
|
+
DYNAMIC_THEME_ROLES,
|
|
18
|
+
UiDynamicColor,
|
|
19
|
+
dynamicThemeColors,
|
|
20
|
+
} from './dynamic-color';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A scheme in one color, with a primary of its own.
|
|
24
|
+
*/
|
|
25
|
+
const scheme = (color: string, primary: string): DynamicColorScheme =>
|
|
26
|
+
({
|
|
27
|
+
...Object.fromEntries(DYNAMIC_COLOR_ROLES.map((role) => [role, color])),
|
|
28
|
+
primary,
|
|
29
|
+
}) as DynamicColorScheme;
|
|
30
|
+
|
|
31
|
+
const WALLPAPER: DynamicColors = {
|
|
32
|
+
light: { ...scheme('#fef7ff', '#6750a4'), onSurface: '#1d1b20' },
|
|
33
|
+
dark: { ...scheme('#141218', '#d0bcff'), onSurface: '#e6e0e9' },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* What an icon inside the element draws `var(--primary)` with.
|
|
38
|
+
*/
|
|
39
|
+
@Component({
|
|
40
|
+
selector: 'test-swatch',
|
|
41
|
+
imports: [LYNX_ELEMENTS],
|
|
42
|
+
template: `<text class="swatch">{{ primary() }}</text>`,
|
|
43
|
+
})
|
|
44
|
+
class Swatch {
|
|
45
|
+
readonly #resolve = injectIconColor();
|
|
46
|
+
protected readonly primary = computed(() => this.#resolve('var(--primary)'));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@Component({
|
|
50
|
+
imports: [LYNX_ELEMENTS, UiDynamicColor, Swatch],
|
|
51
|
+
template: `
|
|
52
|
+
<view class="root" style="height: 100%" uiDynamicColor>
|
|
53
|
+
<test-swatch />
|
|
54
|
+
</view>
|
|
55
|
+
`,
|
|
56
|
+
})
|
|
57
|
+
class Host {}
|
|
58
|
+
|
|
59
|
+
const setup = async (colors: DynamicColors | null) => {
|
|
60
|
+
const dynamicColors = signal(colors);
|
|
61
|
+
const colorScheme = signal<'light' | 'dark'>('light');
|
|
62
|
+
const result = await render(Host, {
|
|
63
|
+
providers: [
|
|
64
|
+
{ provide: LynxTheme, useValue: { dynamicColors } },
|
|
65
|
+
provideIconTheme({ colorScheme: () => colorScheme() }),
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
const root = result.container.querySelector('.root') as HTMLElement;
|
|
69
|
+
const swatch = () => result.container.querySelector('.swatch')?.textContent;
|
|
70
|
+
return { dynamicColors, colorScheme, root, swatch };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
describe('dynamicThemeColors', () => {
|
|
74
|
+
it('gives each theme color its role’s color, and the ring’s subtle form at 40%', () => {
|
|
75
|
+
const colors = dynamicThemeColors(WALLPAPER.light);
|
|
76
|
+
expect(colors['primary']).toBe('rgba(103, 80, 164, 1)');
|
|
77
|
+
expect(colors['ring']).toBe('rgba(103, 80, 164, 1)');
|
|
78
|
+
expect(colors['ring-subtle']).toBe('rgba(103, 80, 164, 0.4)');
|
|
79
|
+
expect(colors['foreground']).toBe('rgba(29, 27, 32, 1)');
|
|
80
|
+
expect(colors['background']).toBe('rgba(254, 247, 255, 1)');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('leaves the destructive and chart colors to the theme', () => {
|
|
84
|
+
const colors = dynamicThemeColors(WALLPAPER.light);
|
|
85
|
+
expect(Object.keys(colors)).not.toContain('destructive');
|
|
86
|
+
expect(Object.keys(colors)).not.toContain('chart-1');
|
|
87
|
+
expect(Object.keys(colors)).not.toContain('glass-dim');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('only maps theme colors that exist', () => {
|
|
91
|
+
for (const token of Object.keys(DYNAMIC_THEME_ROLES)) {
|
|
92
|
+
expect(ICON_LIGHT_COLORS[token]).toBeDefined();
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('UiDynamicColor', () => {
|
|
98
|
+
it('sets the theme’s variables and their -rgb twins to the wallpaper’s colors', async () => {
|
|
99
|
+
const { root } = await setup(WALLPAPER);
|
|
100
|
+
expect(root.style.getPropertyValue('--primary')).toBe(
|
|
101
|
+
'rgba(103, 80, 164, 1)',
|
|
102
|
+
);
|
|
103
|
+
expect(root.style.getPropertyValue('--primary-rgb')).toBe('103, 80, 164');
|
|
104
|
+
expect(root.style.getPropertyValue('--foreground-rgb')).toBe('29, 27, 32');
|
|
105
|
+
expect(root.style.getPropertyValue('--ring-subtle')).toBe(
|
|
106
|
+
'rgba(103, 80, 164, 0.4)',
|
|
107
|
+
);
|
|
108
|
+
expect(root.style.getPropertyValue('--ring-subtle-rgb')).toBe('');
|
|
109
|
+
// The element's own style stays.
|
|
110
|
+
expect(root.style.getPropertyValue('height')).toBe('100%');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('sets the scheme the app is in', async () => {
|
|
114
|
+
const { root, colorScheme } = await setup(WALLPAPER);
|
|
115
|
+
colorScheme.set('dark');
|
|
116
|
+
await waitForUpdate();
|
|
117
|
+
expect(root.style.getPropertyValue('--primary')).toBe(
|
|
118
|
+
'rgba(208, 188, 255, 1)',
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('gives icons inside it the same colors', async () => {
|
|
123
|
+
const { swatch, colorScheme } = await setup(WALLPAPER);
|
|
124
|
+
expect(swatch()).toBe('rgba(103, 80, 164, 1)');
|
|
125
|
+
colorScheme.set('dark');
|
|
126
|
+
await waitForUpdate();
|
|
127
|
+
expect(swatch()).toBe('rgba(208, 188, 255, 1)');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('leaves the theme as it is where the host sends no colors, and when they go', async () => {
|
|
131
|
+
const { root, swatch, dynamicColors } = await setup(null);
|
|
132
|
+
expect(root.style.getPropertyValue('--primary')).toBe('');
|
|
133
|
+
expect(swatch()).toBe(ICON_LIGHT_COLORS['primary']);
|
|
134
|
+
|
|
135
|
+
dynamicColors.set(WALLPAPER);
|
|
136
|
+
await waitForUpdate();
|
|
137
|
+
expect(swatch()).toBe('rgba(103, 80, 164, 1)');
|
|
138
|
+
|
|
139
|
+
dynamicColors.set(null);
|
|
140
|
+
await waitForUpdate();
|
|
141
|
+
expect(root.style.getPropertyValue('--primary')).toBe('');
|
|
142
|
+
expect(swatch()).toBe(ICON_LIGHT_COLORS['primary']);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { computed, Directive, inject } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
LynxTheme,
|
|
4
|
+
type DynamicColorRole,
|
|
5
|
+
type DynamicColorScheme,
|
|
6
|
+
} from '@blotch/angular-lynx';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
injectColorScheme,
|
|
10
|
+
UI_THEME_COLORS,
|
|
11
|
+
type IconColorTokens,
|
|
12
|
+
type ThemeColors,
|
|
13
|
+
} from '../icon/icon-theme';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The Material 3 role each theme color takes from the wallpaper, so that
|
|
17
|
+
* everything drawn with the theme, the Material designs and the app's own
|
|
18
|
+
* classes alike, follows it with no colors of its own to set.
|
|
19
|
+
*
|
|
20
|
+
* - The app and its cards stand on the surface, and the Material designs'
|
|
21
|
+
* tonal surfaces (`bg-secondary`: bars, sheets, menus, the switch's track)
|
|
22
|
+
* on a surface container.
|
|
23
|
+
* - Selected items (`bg-accent`) take the secondary container, as Material's
|
|
24
|
+
* selected chips and navigation items do.
|
|
25
|
+
* - The destructive and chart colors aren't the wallpaper's: Material keeps
|
|
26
|
+
* errors red, and a chart's series tell data apart. So they stay the
|
|
27
|
+
* theme's, as do the clear glass's white and the dimming layer under it.
|
|
28
|
+
*/
|
|
29
|
+
export const DYNAMIC_THEME_ROLES: Readonly<Record<string, DynamicColorRole>> = {
|
|
30
|
+
background: 'surface',
|
|
31
|
+
foreground: 'onSurface',
|
|
32
|
+
card: 'surface',
|
|
33
|
+
'card-foreground': 'onSurface',
|
|
34
|
+
primary: 'primary',
|
|
35
|
+
'primary-foreground': 'onPrimary',
|
|
36
|
+
secondary: 'surfaceContainerHigh',
|
|
37
|
+
'secondary-foreground': 'onSurface',
|
|
38
|
+
muted: 'surfaceContainerHighest',
|
|
39
|
+
'muted-foreground': 'onSurfaceVariant',
|
|
40
|
+
accent: 'secondaryContainer',
|
|
41
|
+
'accent-foreground': 'onSecondaryContainer',
|
|
42
|
+
border: 'outlineVariant',
|
|
43
|
+
input: 'outlineVariant',
|
|
44
|
+
ring: 'primary',
|
|
45
|
+
'glass-tint': 'primary',
|
|
46
|
+
'glass-tint-foreground': 'onPrimary',
|
|
47
|
+
'glass-foreground': 'onSurface',
|
|
48
|
+
'glass-muted-foreground': 'onSurfaceVariant',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `#6750a4` as `103, 80, 164`, the form of the theme's `-rgb` twins.
|
|
53
|
+
*/
|
|
54
|
+
const channels = (hex: string): string =>
|
|
55
|
+
[1, 3, 5]
|
|
56
|
+
.map((start) => parseInt(hex.slice(start, start + 2), 16))
|
|
57
|
+
.join(', ');
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The theme's colors in a dynamic color scheme, by token name without `--`,
|
|
61
|
+
* each a complete `rgba()`, as the theme and its TypeScript copies write
|
|
62
|
+
* them. `ring-subtle` is the ring at 40%, as in the theme.
|
|
63
|
+
*/
|
|
64
|
+
export const dynamicThemeColors = (
|
|
65
|
+
scheme: DynamicColorScheme,
|
|
66
|
+
): IconColorTokens => {
|
|
67
|
+
const colors: Record<string, string> = {};
|
|
68
|
+
for (const [token, role] of Object.entries(DYNAMIC_THEME_ROLES)) {
|
|
69
|
+
colors[token] = `rgba(${channels(scheme[role])}, 1)`;
|
|
70
|
+
}
|
|
71
|
+
colors['ring-subtle'] = `rgba(${channels(scheme.primary)}, 0.4)`;
|
|
72
|
+
return colors;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The CSS variables that give a theme color its value in a scheme: the color,
|
|
77
|
+
* and for an opaque one its `-rgb` twin, which opacity modifiers such as
|
|
78
|
+
* `bg-primary/50` read.
|
|
79
|
+
*/
|
|
80
|
+
const themeVariables = (scheme: DynamicColorScheme): Record<string, string> => {
|
|
81
|
+
const variables: Record<string, string> = {};
|
|
82
|
+
for (const [token, color] of Object.entries(dynamicThemeColors(scheme))) {
|
|
83
|
+
variables[`--${token}`] = color;
|
|
84
|
+
}
|
|
85
|
+
for (const [token, role] of Object.entries(DYNAMIC_THEME_ROLES)) {
|
|
86
|
+
variables[`--${token}-rgb`] = channels(scheme[role]);
|
|
87
|
+
}
|
|
88
|
+
return variables;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const providedColors = () => inject(UiDynamicColor).colors;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Colors the theme from the user's wallpaper on Android 12 and later
|
|
95
|
+
* (Material You's dynamic color), for everything inside the element it's on.
|
|
96
|
+
* Put it on the app's root view, the one that switches the `dark` class:
|
|
97
|
+
*
|
|
98
|
+
* ```html
|
|
99
|
+
* <view class="h-full bg-background" [class.dark]="theme.isDarkMode()" uiDynamicColor>
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* It sets the theme's own CSS variables (`--primary`, `--secondary` and the
|
|
103
|
+
* others, with their `-rgb` twins) to the wallpaper's Material 3 roles (see
|
|
104
|
+
* {@link DYNAMIC_THEME_ROLES}), inline, where they win over the theme's
|
|
105
|
+
* `page` and `.dark` values. So the Material designs, and the app's own
|
|
106
|
+
* `bg-primary` and `text-muted-foreground`, follow the wallpaper without a
|
|
107
|
+
* second set of colors. Icons, glass tints and charts inside it, which draw
|
|
108
|
+
* with a TypeScript copy of the theme, get the same colors.
|
|
109
|
+
*
|
|
110
|
+
* Which scheme it sets, light or dark, follows `provideIconTheme()`'s
|
|
111
|
+
* `colorScheme`, as icons do: make it match the `dark` class.
|
|
112
|
+
*
|
|
113
|
+
* Where the host app sends no wallpaper colors (iOS, the web preview, Lynx
|
|
114
|
+
* Explorer, Android before 12), it sets nothing and the theme stays as it is.
|
|
115
|
+
* The colors come from `LynxTheme.dynamicColors`, which the Android app
|
|
116
|
+
* `ng build --platform android` makes sends.
|
|
117
|
+
*/
|
|
118
|
+
@Directive({
|
|
119
|
+
selector: '[uiDynamicColor]',
|
|
120
|
+
host: { '[style]': 'style()' },
|
|
121
|
+
providers: [{ provide: UI_THEME_COLORS, useFactory: providedColors }],
|
|
122
|
+
})
|
|
123
|
+
export class UiDynamicColor {
|
|
124
|
+
readonly #dynamicColors = inject(LynxTheme).dynamicColors;
|
|
125
|
+
readonly #scheme = injectColorScheme();
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The theme's colors in each scheme from the wallpaper, or none, for what
|
|
129
|
+
* draws inside the element and can't read its CSS variables (see
|
|
130
|
+
* UI_THEME_COLORS).
|
|
131
|
+
*/
|
|
132
|
+
readonly colors = computed<ThemeColors>(() => {
|
|
133
|
+
const colors = this.#dynamicColors();
|
|
134
|
+
return colors
|
|
135
|
+
? {
|
|
136
|
+
light: dynamicThemeColors(colors.light),
|
|
137
|
+
dark: dynamicThemeColors(colors.dark),
|
|
138
|
+
}
|
|
139
|
+
: {};
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
protected readonly style = computed(() => {
|
|
143
|
+
const colors = this.#dynamicColors();
|
|
144
|
+
return colors ? themeVariables(colors[this.#scheme()]) : null;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
@@ -1,60 +1,49 @@
|
|
|
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
3
|
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
4
4
|
import { describe, expect, it } from 'vitest';
|
|
5
|
-
import {
|
|
5
|
+
import type { IconName } from '../icon/icons';
|
|
6
6
|
import { UiEmptyState } from './empty-state';
|
|
7
7
|
|
|
8
8
|
@Component({
|
|
9
|
-
standalone: true,
|
|
10
9
|
imports: [UiEmptyState, LYNX_ELEMENTS],
|
|
11
10
|
template: `
|
|
12
11
|
<ui-empty-state
|
|
13
|
-
icon="
|
|
12
|
+
[icon]="icon()"
|
|
14
13
|
title="No results"
|
|
15
|
-
description="
|
|
14
|
+
[description]="description()"
|
|
15
|
+
class="py-6"
|
|
16
16
|
>
|
|
17
|
-
<text>
|
|
17
|
+
<text>Clear the filters</text>
|
|
18
18
|
</ui-empty-state>
|
|
19
19
|
`,
|
|
20
20
|
})
|
|
21
|
-
class EmptyStateHost {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
imports: [UiEmptyState, LYNX_ELEMENTS],
|
|
26
|
-
template: `<ui-empty-state title="Empty"></ui-empty-state>`,
|
|
27
|
-
})
|
|
28
|
-
class MinimalEmptyStateHost {}
|
|
21
|
+
class EmptyStateHost {
|
|
22
|
+
readonly icon = signal<IconName | undefined>(undefined);
|
|
23
|
+
readonly description = signal('');
|
|
24
|
+
}
|
|
29
25
|
|
|
30
26
|
describe('UiEmptyState', () => {
|
|
31
|
-
it('
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
expect(container).toBeTruthy();
|
|
27
|
+
it('shows only the title and projected content by default', async () => {
|
|
28
|
+
const { container, getByText, queryByText } = await render(EmptyStateHost);
|
|
29
|
+
expect(getByText('No results').classList).toContain('font-semibold');
|
|
30
|
+
expect(getByText('Clear the filters')).toBeTruthy();
|
|
31
|
+
expect(queryByText('Try adjusting your search')).toBeNull();
|
|
32
|
+
expect(container.querySelector('svg')).toBeNull();
|
|
33
|
+
expect(container.querySelector('.py-6')).toBeTruthy();
|
|
39
34
|
});
|
|
40
35
|
|
|
41
|
-
it('
|
|
42
|
-
const { container } = await render(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
it('renders the description class when a description is programmatically set', async () => {
|
|
47
|
-
// JIT template bindings don't propagate through `input()`; drive the input
|
|
48
|
-
// via setInputSignal so the @if(description()) branch actually renders and
|
|
49
|
-
// its computed class runs.
|
|
50
|
-
const { componentRef, container } = await render(UiEmptyState);
|
|
51
|
-
const inst = componentRef.instance as UiEmptyState;
|
|
52
|
-
setInputSignal(inst.title, 'Title');
|
|
53
|
-
setInputSignal(inst.description, 'A description');
|
|
36
|
+
it('adds the icon and description when they are set', async () => {
|
|
37
|
+
const { componentRef, container, getByText } = await render(EmptyStateHost);
|
|
38
|
+
const host = componentRef.instance as EmptyStateHost;
|
|
39
|
+
host.icon.set('search');
|
|
40
|
+
host.description.set('Try adjusting your search');
|
|
54
41
|
await waitForUpdate();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
expect(
|
|
42
|
+
expect(getByText('Try adjusting your search').classList).toContain(
|
|
43
|
+
'text-muted-foreground',
|
|
44
|
+
);
|
|
45
|
+
expect(container.querySelector('svg')?.getAttribute('content')).toContain(
|
|
46
|
+
'<svg',
|
|
47
|
+
);
|
|
59
48
|
});
|
|
60
49
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { concentricRadius } from './concentric-radius';
|
|
3
|
+
|
|
4
|
+
describe('concentricRadius', () => {
|
|
5
|
+
it('takes the inset off the outer radius, so the corners share a center', () => {
|
|
6
|
+
expect(concentricRadius(24, 8)).toBe(16);
|
|
7
|
+
expect(concentricRadius(20, 0)).toBe(20);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('gives square corners rather than a negative radius', () => {
|
|
11
|
+
expect(concentricRadius(8, 12)).toBe(0);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The corner radius for a shape nested `inset` px inside a shape whose corner
|
|
3
|
+
* radius is `outerRadius`, so that the two corners share a center and the gap
|
|
4
|
+
* between them stays the same width all the way round. Apple calls this
|
|
5
|
+
* concentricity, and Liquid Glass relies on it: a button inset 8px in a glass
|
|
6
|
+
* panel with 24px corners gets 16px corners.
|
|
7
|
+
*
|
|
8
|
+
* It never goes below 0: a shape inset further than the outer radius has
|
|
9
|
+
* square corners. Capsules and circles need no helper, because a capsule
|
|
10
|
+
* inset evenly inside a capsule is a capsule (`shape="capsule"`).
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* readonly innerRadius = computed(() => concentricRadius(this.radius(), 8));
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const concentricRadius = (outerRadius: number, inset: number): number =>
|
|
17
|
+
Math.max(0, outerRadius - inset);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Injector, runInInjectionContext } from '@angular/core';
|
|
2
|
+
import { LynxSystemInfo } from '@blotch/angular-lynx';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
glassDesignFor,
|
|
6
|
+
injectGlassDesign,
|
|
7
|
+
injectSystemDrawsGlass,
|
|
8
|
+
provideGlass,
|
|
9
|
+
UI_GLASS_OPTIONS,
|
|
10
|
+
} from './glass-design';
|
|
11
|
+
|
|
12
|
+
describe('glassDesignFor', () => {
|
|
13
|
+
it('gives iOS 26 and later Liquid Glass, and earlier iOS the frosted materials', () => {
|
|
14
|
+
expect(glassDesignFor('iOS', '26.0', {})).toBe('glass');
|
|
15
|
+
expect(glassDesignFor('iOS', '27.1', {})).toBe('glass');
|
|
16
|
+
expect(glassDesignFor('iOS', '25.4', {})).toBe('frosted');
|
|
17
|
+
expect(glassDesignFor('iOS', '15.0', {})).toBe('frosted');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('counts an iOS version not known yet as current', () => {
|
|
21
|
+
expect(glassDesignFor('iOS', '', {})).toBe('glass');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('gives Android Material unless the app opts into glass', () => {
|
|
25
|
+
expect(glassDesignFor('Android', '15', {})).toBe('material');
|
|
26
|
+
expect(glassDesignFor('Android', '15', { android: 'glass' })).toBe('glass');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('draws glass in the web preview, and Material where there is none', () => {
|
|
30
|
+
expect(glassDesignFor('web', '', {})).toBe('glass');
|
|
31
|
+
expect(glassDesignFor('Harmony', '5.0', { android: 'glass' })).toBe(
|
|
32
|
+
'material',
|
|
33
|
+
);
|
|
34
|
+
expect(glassDesignFor(undefined, '', {})).toBe('material');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('provideGlass', () => {
|
|
39
|
+
it('provides the options', () => {
|
|
40
|
+
expect(provideGlass({ android: 'glass' })).toEqual({
|
|
41
|
+
provide: UI_GLASS_OPTIONS,
|
|
42
|
+
useValue: { android: 'glass' },
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('injectGlassDesign', () => {
|
|
48
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Runs `fn` in an injector. A plain injector has no root scope, so it gets
|
|
52
|
+
* LynxSystemInfo, which the app's root injector would make, explicitly.
|
|
53
|
+
*/
|
|
54
|
+
const inject = <T>(fn: () => T, options = {}) =>
|
|
55
|
+
runInInjectionContext(
|
|
56
|
+
Injector.create({
|
|
57
|
+
providers: [
|
|
58
|
+
LynxSystemInfo,
|
|
59
|
+
{ provide: UI_GLASS_OPTIONS, useValue: options },
|
|
60
|
+
],
|
|
61
|
+
}),
|
|
62
|
+
fn,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
it("reads the platform and version from SystemInfo, with the app's options", () => {
|
|
66
|
+
vi.stubGlobal('SystemInfo', { platform: 'Android', osVersion: '14' });
|
|
67
|
+
|
|
68
|
+
expect(inject(injectGlassDesign)()).toBe('material');
|
|
69
|
+
expect(inject(injectGlassDesign, { android: 'glass' })()).toBe('glass');
|
|
70
|
+
expect(inject(injectSystemDrawsGlass)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('knows that iOS draws its own glass', () => {
|
|
74
|
+
vi.stubGlobal('SystemInfo', { platform: 'iOS', osVersion: '26.0' });
|
|
75
|
+
|
|
76
|
+
expect(inject(injectSystemDrawsGlass)).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
inject,
|
|
4
|
+
InjectionToken,
|
|
5
|
+
Injector,
|
|
6
|
+
type Provider,
|
|
7
|
+
type Signal,
|
|
8
|
+
} from '@angular/core';
|
|
9
|
+
import { LynxSystemInfo } from '@blotch/angular-lynx';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The look the glass components take on this platform. One API covers all
|
|
13
|
+
* three, and each component builds its own form of each: the dock is a
|
|
14
|
+
* floating glass capsule with `'glass'`, a tab bar on a blur material with
|
|
15
|
+
* `'frosted'`, and a navigation bar with `'material'`.
|
|
16
|
+
*
|
|
17
|
+
* - `'glass'`: Liquid Glass. iOS 26 and later draw it with UIKit's glass,
|
|
18
|
+
* the web preview draws it with WebGL, and Android, when the app opts in
|
|
19
|
+
* with {@link provideGlass}, approximates it with a blur, a tint and a rim.
|
|
20
|
+
* - `'frosted'`: Apple's design before Liquid Glass, for iOS 15 to 25, whose
|
|
21
|
+
* `blur-view` has no glass: frosted blur materials.
|
|
22
|
+
* - `'material'`: Material 3 Expressive, Android's default, which uses
|
|
23
|
+
* opaque tonal surfaces instead of glass.
|
|
24
|
+
*/
|
|
25
|
+
export type GlassDesign = 'glass' | 'frosted' | 'material';
|
|
26
|
+
|
|
27
|
+
export type GlassOptions = {
|
|
28
|
+
/**
|
|
29
|
+
* What Android gets: `'material'` (the default), or `'glass'` for the same
|
|
30
|
+
* look as iOS. Android's glass is a blur with a tint and a rim over it,
|
|
31
|
+
* without the refraction. Blurring costs more below Android 12, where Lynx
|
|
32
|
+
* can't use `RenderEffect`.
|
|
33
|
+
*/
|
|
34
|
+
readonly android?: 'material' | 'glass';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** The app's glass options; see {@link provideGlass}. */
|
|
38
|
+
export const UI_GLASS_OPTIONS = new InjectionToken<GlassOptions>(
|
|
39
|
+
'UI_GLASS_OPTIONS',
|
|
40
|
+
{ providedIn: 'root', factory: () => ({}) },
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets the glass options for the whole app, in `app.config.ts`:
|
|
45
|
+
*
|
|
46
|
+
* ```ts
|
|
47
|
+
* // Liquid Glass on Android too, instead of Material.
|
|
48
|
+
* providers: [provideGlass({ android: 'glass' })]
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export const provideGlass = (options: GlassOptions): Provider => ({
|
|
52
|
+
provide: UI_GLASS_OPTIONS,
|
|
53
|
+
useValue: options,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/** The first iOS whose `blur-view` draws Liquid Glass. */
|
|
57
|
+
export const FIRST_GLASS_IOS = 26;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The design for a platform, as `SystemInfo` names it, and its OS version.
|
|
61
|
+
*
|
|
62
|
+
* Before iOS 26, Lynx's `blur-view` doesn't know `blur-effect="glass"`: it
|
|
63
|
+
* logs an error and draws a `light` blur that ignores dark mode. So older
|
|
64
|
+
* iOS gets `'frosted'`. An iOS version that isn't known yet (empty) counts as
|
|
65
|
+
* current: on the native main thread it arrives from the background thread
|
|
66
|
+
* after the first frame, unless the host app sends it (see
|
|
67
|
+
* `LynxSystemInfo.osVersion`), and iOS 26 and later is where most phones are.
|
|
68
|
+
* Platforms with no glass, such as HarmonyOS, get `'material'`.
|
|
69
|
+
*/
|
|
70
|
+
export const glassDesignFor = (
|
|
71
|
+
platform: string | undefined,
|
|
72
|
+
osVersion: string,
|
|
73
|
+
options: GlassOptions,
|
|
74
|
+
): GlassDesign => {
|
|
75
|
+
switch (platform) {
|
|
76
|
+
case 'iOS':
|
|
77
|
+
return parseInt(osVersion, 10) < FIRST_GLASS_IOS ? 'frosted' : 'glass';
|
|
78
|
+
case 'Android':
|
|
79
|
+
return options.android ?? 'material';
|
|
80
|
+
case 'web':
|
|
81
|
+
return 'glass';
|
|
82
|
+
default:
|
|
83
|
+
return 'material';
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* LynxSystemInfo, or undefined where there's no SystemInfo to read, such as
|
|
89
|
+
* in unit tests, where LynxSystemInfo throws.
|
|
90
|
+
*/
|
|
91
|
+
const systemInfo = (injector: Injector): LynxSystemInfo | undefined => {
|
|
92
|
+
try {
|
|
93
|
+
return injector.get(LynxSystemInfo);
|
|
94
|
+
} catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The glass design on this platform, for a component's constructor or field
|
|
101
|
+
* initializers. A signal: on the native main thread, iOS's version can arrive
|
|
102
|
+
* after the first frame.
|
|
103
|
+
*/
|
|
104
|
+
export const injectGlassDesign = (): Signal<GlassDesign> => {
|
|
105
|
+
const options = inject(UI_GLASS_OPTIONS);
|
|
106
|
+
const info = systemInfo(inject(Injector));
|
|
107
|
+
return computed(() =>
|
|
108
|
+
glassDesignFor(info?.platform, info?.osVersion ?? '', options),
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Whether the system draws the glass and its materials itself: iOS. UIKit
|
|
114
|
+
* adapts them to Reduce Transparency, Increase Contrast and iOS 27's Liquid
|
|
115
|
+
* Glass setting on its own. Elsewhere, Android's approximation and the web
|
|
116
|
+
* preview's drawing can't, so the components adapt what they draw.
|
|
117
|
+
*/
|
|
118
|
+
export const injectSystemDrawsGlass = (): boolean =>
|
|
119
|
+
systemInfo(inject(Injector))?.platform === 'iOS';
|