@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
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
1
2
|
import {
|
|
2
3
|
type ElementRef,
|
|
3
4
|
Component,
|
|
4
5
|
ViewEncapsulation,
|
|
5
6
|
computed,
|
|
7
|
+
contentChild,
|
|
6
8
|
inject,
|
|
7
9
|
input,
|
|
8
10
|
output,
|
|
11
|
+
signal,
|
|
9
12
|
viewChild,
|
|
10
13
|
} from '@angular/core';
|
|
11
|
-
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
14
|
+
import { LYNX_ELEMENTS, LynxAccessibilitySettings } from '@blotch/angular-lynx';
|
|
12
15
|
import { cva, type VariantProps } from 'class-variance-authority';
|
|
13
16
|
|
|
14
17
|
import {
|
|
@@ -17,14 +20,23 @@ import {
|
|
|
17
20
|
pressRelease,
|
|
18
21
|
} from '../../utils/animate';
|
|
19
22
|
import { cn } from '../../utils/cn';
|
|
23
|
+
import { concentricRadius } from '../glass/concentric-radius';
|
|
24
|
+
import { UiGlass } from '../glass/glass';
|
|
25
|
+
import {
|
|
26
|
+
injectGlassDesign,
|
|
27
|
+
injectSystemDrawsGlass,
|
|
28
|
+
} from '../glass/glass-design';
|
|
29
|
+
import { UiIcon } from '../icon/icon';
|
|
30
|
+
import { UI_ICON_COLOR } from '../icon/icon-theme';
|
|
20
31
|
|
|
21
32
|
/**
|
|
22
|
-
* Per-item styling. Corners are intentionally left
|
|
23
|
-
* container clips the whole bar with `overflow-hidden`
|
|
24
|
-
* the outer edges round off while every internal seam
|
|
25
|
-
* individual items by position would require
|
|
26
|
-
* selectors, which Lynx's CSS engine does not
|
|
27
|
-
* library relies on positional selectors — see
|
|
33
|
+
* Per-item styling of the solid variants. Corners are intentionally left
|
|
34
|
+
* square here: the group container clips the whole bar with `overflow-hidden`
|
|
35
|
+
* + `rounded-md`, so only the outer edges round off while every internal seam
|
|
36
|
+
* stays crisp. Rounding individual items by position would require
|
|
37
|
+
* `:first-child`/`:last-child` selectors, which Lynx's CSS engine does not
|
|
38
|
+
* support (no component in the library relies on positional selectors — see
|
|
39
|
+
* CLAUDE.md).
|
|
28
40
|
*/
|
|
29
41
|
export const buttonGroupItemVariants = cva('items-center flex justify-center', {
|
|
30
42
|
variants: {
|
|
@@ -38,6 +50,7 @@ export const buttonGroupItemVariants = cva('items-center flex justify-center', {
|
|
|
38
50
|
default: 'h-10 px-4',
|
|
39
51
|
sm: 'h-9 px-3',
|
|
40
52
|
lg: 'h-11 px-6',
|
|
53
|
+
icon: 'h-10 w-10',
|
|
41
54
|
},
|
|
42
55
|
},
|
|
43
56
|
defaultVariants: {
|
|
@@ -46,32 +59,91 @@ export const buttonGroupItemVariants = cva('items-center flex justify-center', {
|
|
|
46
59
|
},
|
|
47
60
|
});
|
|
48
61
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
variant: {
|
|
52
|
-
default: 'text-secondary-foreground',
|
|
53
|
-
outline: 'text-foreground',
|
|
54
|
-
},
|
|
55
|
-
size: {
|
|
56
|
-
default: 'text-sm',
|
|
57
|
-
sm: 'text-xs',
|
|
58
|
-
lg: 'text-base',
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
defaultVariants: {
|
|
62
|
-
variant: 'default',
|
|
63
|
-
size: 'default',
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
export type ButtonGroupVariant = NonNullable<
|
|
62
|
+
/** A variant drawn with solid colors, on every platform. */
|
|
63
|
+
export type SolidButtonGroupVariant = NonNullable<
|
|
68
64
|
VariantProps<typeof buttonGroupItemVariants>['variant']
|
|
69
65
|
>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The glass variants: the group is one glass shape with plain items in it,
|
|
69
|
+
* the way iOS groups toolbar items on one glass background. `glass-clear` is
|
|
70
|
+
* for a group over photos and video.
|
|
71
|
+
*/
|
|
72
|
+
export type GlassButtonGroupVariant = 'glass' | 'glass-clear';
|
|
73
|
+
|
|
74
|
+
export type ButtonGroupVariant =
|
|
75
|
+
| SolidButtonGroupVariant
|
|
76
|
+
| GlassButtonGroupVariant;
|
|
70
77
|
export type ButtonGroupSize = NonNullable<
|
|
71
78
|
VariantProps<typeof buttonGroupItemVariants>['size']
|
|
72
79
|
>;
|
|
73
80
|
export type ButtonGroupOrientation = 'horizontal' | 'vertical';
|
|
74
81
|
|
|
82
|
+
/**
|
|
83
|
+
* How a group looks on this platform: the solid variants everywhere, and the
|
|
84
|
+
* glass ones as one glass shape (`'glass'`) or, on Android by default, as
|
|
85
|
+
* Material 3 Expressive's connected button group (`'material'`).
|
|
86
|
+
*/
|
|
87
|
+
export type ButtonGroupLook = 'solid' | 'glass' | 'material';
|
|
88
|
+
|
|
89
|
+
/** Each size's item height, in px, for the corner radii. */
|
|
90
|
+
const ITEM_HEIGHTS: Readonly<Record<ButtonGroupSize, number>> = {
|
|
91
|
+
default: 40,
|
|
92
|
+
sm: 36,
|
|
93
|
+
lg: 44,
|
|
94
|
+
icon: 40,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** How far the items sit inside a glass group's edge, in px (`p-1`). */
|
|
98
|
+
const GLASS_INSET = 4;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The inner corners of Material's connected button group, in px: the
|
|
102
|
+
* group's ends are fully round, the corners between items small.
|
|
103
|
+
*/
|
|
104
|
+
const MATERIAL_INNER_RADIUS = 8;
|
|
105
|
+
|
|
106
|
+
const LABEL_SIZES: Readonly<Record<ButtonGroupSize, string>> = {
|
|
107
|
+
default: 'text-sm',
|
|
108
|
+
sm: 'text-xs',
|
|
109
|
+
lg: 'text-base',
|
|
110
|
+
icon: 'text-sm',
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const SOLID_LABEL_COLORS: Readonly<Record<SolidButtonGroupVariant, string>> = {
|
|
114
|
+
default: 'text-secondary-foreground',
|
|
115
|
+
outline: 'text-foreground',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/** The token of each solid variant's label color, for projected icons. */
|
|
119
|
+
const SOLID_FOREGROUNDS: Readonly<Record<SolidButtonGroupVariant, string>> = {
|
|
120
|
+
default: 'var(--secondary-foreground)',
|
|
121
|
+
outline: 'var(--foreground)',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** The size classes of an item in a glass or Material group. */
|
|
125
|
+
const ITEM_SIZES: Readonly<Record<ButtonGroupSize, string>> = {
|
|
126
|
+
default: 'h-10 px-4',
|
|
127
|
+
sm: 'h-9 px-3',
|
|
128
|
+
lg: 'h-11 px-6',
|
|
129
|
+
icon: 'h-10 w-10',
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* `UiIcon`, for `contentChild()`. A call rather than the class itself: for a
|
|
134
|
+
* class, Angular's test build keeps an arrow function that nothing calls,
|
|
135
|
+
* which an app's coverage report would show as uncovered.
|
|
136
|
+
*/
|
|
137
|
+
const iconType = () => UiIcon;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The color an icon projected into an item draws with when it's given none:
|
|
141
|
+
* the items' label color (see UI_ICON_COLOR). Named, not inline: the
|
|
142
|
+
* component's metadata keeps its own copy of an inline factory, which
|
|
143
|
+
* nothing calls.
|
|
144
|
+
*/
|
|
145
|
+
const groupIconColor = () => inject(UiButtonGroup).foreground;
|
|
146
|
+
|
|
75
147
|
/**
|
|
76
148
|
* A row (or column) of connected action buttons — a toolbar of related
|
|
77
149
|
* commands rendered as a single, seamless control.
|
|
@@ -81,36 +153,185 @@ export type ButtonGroupOrientation = 'horizontal' | 'vertical';
|
|
|
81
153
|
* the `UiToggleGroup`/`UiToggleGroupItem` pattern. Unlike a toggle group this
|
|
82
154
|
* carries no selection state: every item is an independent action that emits
|
|
83
155
|
* `pressed`.
|
|
156
|
+
*
|
|
157
|
+
* With a glass variant the group is one glass shape with plain items in it,
|
|
158
|
+
* as iOS 26 groups toolbar items on one background, rather than a glass
|
|
159
|
+
* shape per item merged in a `ui-glass-group`: merged circles join with
|
|
160
|
+
* pinched waists, not the straight edges of a toolbar's capsule, and glass
|
|
161
|
+
* isn't layered on glass. On Android it's Material's connected button group
|
|
162
|
+
* by default.
|
|
84
163
|
*/
|
|
85
164
|
@Component({
|
|
86
165
|
selector: 'ui-button-group',
|
|
87
|
-
|
|
88
|
-
imports: [LYNX_ELEMENTS],
|
|
166
|
+
imports: [LYNX_ELEMENTS, NgTemplateOutlet, UiGlass],
|
|
89
167
|
encapsulation: ViewEncapsulation.None,
|
|
168
|
+
// Icons projected into the items inject this: they're declared inside the
|
|
169
|
+
// group, so the group's element injector is one of theirs.
|
|
170
|
+
providers: [{ provide: UI_ICON_COLOR, useFactory: groupIconColor }],
|
|
90
171
|
template: `
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
172
|
+
@if (look() === 'glass') {
|
|
173
|
+
<!-- The frame is the group's box and, under clear glass, its dimming
|
|
174
|
+
layer, in the glass's shape. -->
|
|
175
|
+
<view [class]="frameClass()" [style.border-radius.px]="cornerRadius()">
|
|
176
|
+
<blur-view
|
|
177
|
+
ui-glass
|
|
178
|
+
[shape]="capsule() ? 'capsule' : 'rounded'"
|
|
179
|
+
[radius]="radius()"
|
|
180
|
+
[variant]="variant() === 'glass-clear' ? 'clear' : 'regular'"
|
|
181
|
+
[interactive]="interactive()"
|
|
182
|
+
[class]="glassClass()"
|
|
183
|
+
>
|
|
184
|
+
<ng-container [ngTemplateOutlet]="items" />
|
|
185
|
+
</blur-view>
|
|
186
|
+
</view>
|
|
187
|
+
} @else {
|
|
188
|
+
<view
|
|
189
|
+
[class]="containerClass()"
|
|
190
|
+
[style.border-radius.px]="materialRadius()"
|
|
191
|
+
>
|
|
192
|
+
<ng-container [ngTemplateOutlet]="items" />
|
|
193
|
+
</view>
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
<!-- One copy of the items, for whichever form the group takes: Angular
|
|
197
|
+
projects content once. -->
|
|
198
|
+
<ng-template #items><ng-content /></ng-template>
|
|
94
199
|
`,
|
|
95
200
|
})
|
|
96
201
|
export class UiButtonGroup {
|
|
97
202
|
readonly orientation = input<ButtonGroupOrientation>('horizontal');
|
|
203
|
+
/**
|
|
204
|
+
* `'default'` or `'outline'`, or `'glass'` / `'glass-clear'` for one glass
|
|
205
|
+
* background (see GlassButtonGroupVariant).
|
|
206
|
+
*/
|
|
98
207
|
readonly variant = input<ButtonGroupVariant>('default');
|
|
208
|
+
/** The items' height and padding; `'icon'` makes square icon-only items. */
|
|
99
209
|
readonly size = input<ButtonGroupSize>('default');
|
|
100
210
|
readonly disabled = input(false);
|
|
101
211
|
readonly userClass = input<string>('', { alias: 'class' });
|
|
102
212
|
|
|
213
|
+
readonly #settings = inject(LynxAccessibilitySettings);
|
|
214
|
+
readonly #design = injectGlassDesign();
|
|
215
|
+
readonly #systemDrawsGlass = injectSystemDrawsGlass();
|
|
216
|
+
|
|
217
|
+
/** The form the group takes on this platform, for its items. */
|
|
218
|
+
readonly look = computed<ButtonGroupLook>(() => {
|
|
219
|
+
const variant = this.variant();
|
|
220
|
+
if (variant !== 'glass' && variant !== 'glass-clear') return 'solid';
|
|
221
|
+
return this.#design() === 'material' ? 'material' : 'glass';
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The glass's corner radius, in px: half an item's height plus the inset,
|
|
226
|
+
* the ends a capsule of the items would have. A column of wider items
|
|
227
|
+
* takes it for its corners.
|
|
228
|
+
*/
|
|
229
|
+
protected readonly radius = computed(
|
|
230
|
+
() => ITEM_HEIGHTS[this.size()] / 2 + GLASS_INSET,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* The corner radius of an item's pressed highlight, in px. In glass it's
|
|
235
|
+
* concentric with the group's corners, so the gap between them stays even
|
|
236
|
+
* all the way round; in Material's connected group it's the small inner
|
|
237
|
+
* corner, and the group's round ends clip the outer ones.
|
|
238
|
+
*/
|
|
239
|
+
readonly itemRadius = computed(() =>
|
|
240
|
+
this.look() === 'material'
|
|
241
|
+
? MATERIAL_INNER_RADIUS
|
|
242
|
+
: concentricRadius(this.radius(), GLASS_INSET),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* A row, or a column of square icon items, is a capsule. A column of
|
|
247
|
+
* wider items has round corners instead. A capsule takes `rounded-full`
|
|
248
|
+
* rather than a radius of half its height, which iOS draws with
|
|
249
|
+
* squared-off ends.
|
|
250
|
+
*/
|
|
251
|
+
protected readonly capsule = computed(
|
|
252
|
+
() => this.orientation() === 'horizontal' || this.size() === 'icon',
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
/** The frame's corners, under glass that isn't a capsule. */
|
|
256
|
+
protected readonly cornerRadius = computed(() =>
|
|
257
|
+
this.capsule() ? null : this.radius(),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
/** A Material column of wider items: its ends are half an item round. */
|
|
261
|
+
protected readonly materialRadius = computed(() =>
|
|
262
|
+
this.look() === 'material' && !this.capsule()
|
|
263
|
+
? ITEM_HEIGHTS[this.size()] / 2
|
|
264
|
+
: null,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* As UiButton's: the glass reacts to touch, unless Reduce Motion is on
|
|
269
|
+
* where the runtime draws it.
|
|
270
|
+
*/
|
|
271
|
+
protected readonly interactive = computed(
|
|
272
|
+
() =>
|
|
273
|
+
!this.disabled() &&
|
|
274
|
+
(this.#systemDrawsGlass || !this.#settings.reduceMotion()),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Clear glass that's see-through (not the opaque surface Reduce
|
|
279
|
+
* Transparency makes of the runtime's glass) sits on the dimming layer,
|
|
280
|
+
* and its items turn white.
|
|
281
|
+
*/
|
|
282
|
+
readonly #dimmed = computed(
|
|
283
|
+
() =>
|
|
284
|
+
this.variant() === 'glass-clear' &&
|
|
285
|
+
this.look() === 'glass' &&
|
|
286
|
+
(this.#systemDrawsGlass || !this.#settings.reduceTransparency()),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
/** The items' label color, as a `var()`, for projected icons. */
|
|
290
|
+
readonly foreground = computed(() => {
|
|
291
|
+
const variant = this.variant();
|
|
292
|
+
if (variant !== 'glass' && variant !== 'glass-clear') {
|
|
293
|
+
return SOLID_FOREGROUNDS[variant];
|
|
294
|
+
}
|
|
295
|
+
if (this.look() === 'material') return 'var(--secondary-foreground)';
|
|
296
|
+
return this.#dimmed()
|
|
297
|
+
? 'var(--glass-clear-foreground)'
|
|
298
|
+
: 'var(--glass-foreground)';
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
readonly #direction = computed(() =>
|
|
302
|
+
this.orientation() === 'vertical' ? 'flex-col' : 'flex-row',
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
protected readonly frameClass = computed(() =>
|
|
306
|
+
cn(
|
|
307
|
+
// `self-start` stops the group stretching to fill a column parent so
|
|
308
|
+
// it hugs its buttons like a real toolbar. Pass `class="w-full"` to
|
|
309
|
+
// override.
|
|
310
|
+
'flex-col self-start flex',
|
|
311
|
+
this.capsule() && 'rounded-full',
|
|
312
|
+
this.#dimmed() && 'bg-glass-dim',
|
|
313
|
+
this.userClass(),
|
|
314
|
+
),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
protected readonly glassClass = computed(() =>
|
|
318
|
+
cn('p-1 flex', this.#direction()),
|
|
319
|
+
);
|
|
320
|
+
|
|
103
321
|
protected readonly containerClass = computed(() =>
|
|
104
322
|
cn(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
323
|
+
this.look() === 'material'
|
|
324
|
+
? // Material's connected group: the items, 2px apart, show the page
|
|
325
|
+
// between them, and the group's round ends clip the outer corners.
|
|
326
|
+
cn('gap-0.5 flex overflow-hidden', this.capsule() && 'rounded-full')
|
|
327
|
+
: // The dividers between items are the group's own `bg-border`
|
|
328
|
+
// showing through a 1px `gap-px` seam — no per-item borders needed,
|
|
329
|
+
// which keeps items position-agnostic. `overflow-hidden` +
|
|
330
|
+
// `rounded-md` then clip the square item corners so the bar reads
|
|
331
|
+
// as one rounded control.
|
|
332
|
+
'gap-px rounded-md border border-border bg-border flex overflow-hidden',
|
|
112
333
|
'self-start',
|
|
113
|
-
this
|
|
334
|
+
this.#direction(),
|
|
114
335
|
this.userClass(),
|
|
115
336
|
),
|
|
116
337
|
);
|
|
@@ -122,13 +343,15 @@ export class UiButtonGroup {
|
|
|
122
343
|
*/
|
|
123
344
|
@Component({
|
|
124
345
|
selector: 'ui-button-group-item',
|
|
125
|
-
standalone: true,
|
|
126
346
|
imports: [LYNX_ELEMENTS],
|
|
127
347
|
encapsulation: ViewEncapsulation.None,
|
|
128
348
|
template: `
|
|
129
349
|
<view
|
|
130
350
|
#container
|
|
131
351
|
[class]="containerClass()"
|
|
352
|
+
[attr.accessibility-element]="accessibilityLabel() ? 'true' : null"
|
|
353
|
+
[attr.accessibility-traits]="accessibilityLabel() ? 'button' : null"
|
|
354
|
+
[attr.accessibility-label]="accessibilityLabel() || null"
|
|
132
355
|
(bindtouchstart)="onPressStart()"
|
|
133
356
|
(bindtouchend)="onPressEnd()"
|
|
134
357
|
(bindtouchcancel)="onPressCancel()"
|
|
@@ -137,68 +360,143 @@ export class UiButtonGroup {
|
|
|
137
360
|
(bindmouseleave)="onPressCancel()"
|
|
138
361
|
(bindtap)="onTap()"
|
|
139
362
|
>
|
|
140
|
-
|
|
363
|
+
@if (highlightClass(); as highlight) {
|
|
364
|
+
<view [class]="highlight" [style.border-radius.px]="radius()"></view>
|
|
365
|
+
}
|
|
366
|
+
<ng-content select="ui-icon" />
|
|
367
|
+
@if (showLabel()) {
|
|
368
|
+
<text
|
|
369
|
+
[attr.accessibility-traits]="accessibilityLabel() ? null : 'button'"
|
|
370
|
+
[class]="labelClass()"
|
|
371
|
+
><ng-content
|
|
372
|
+
/></text>
|
|
373
|
+
}
|
|
141
374
|
</view>
|
|
142
375
|
`,
|
|
143
376
|
})
|
|
144
377
|
export class UiButtonGroupItem {
|
|
145
378
|
readonly #group = inject(UiButtonGroup);
|
|
379
|
+
readonly #settings = inject(LynxAccessibilitySettings);
|
|
146
380
|
|
|
147
381
|
readonly disabled = input(false);
|
|
382
|
+
/**
|
|
383
|
+
* What a screen reader says for the item. An icon-only item needs one; an
|
|
384
|
+
* item with a label reads its label without it.
|
|
385
|
+
*/
|
|
386
|
+
readonly accessibilityLabel = input<string | undefined>(undefined, {
|
|
387
|
+
alias: 'accessibility-label',
|
|
388
|
+
});
|
|
148
389
|
readonly userClass = input<string>('', { alias: 'class' });
|
|
149
390
|
|
|
150
391
|
readonly pressed = output<void>();
|
|
151
392
|
|
|
152
393
|
readonly containerRef = viewChild<ElementRef>('container');
|
|
394
|
+
/** A `<ui-icon>` projected into the item, which sits before the label. */
|
|
395
|
+
readonly projectedIcon = contentChild(iconType());
|
|
153
396
|
#pressAnim?: AnimationHandle;
|
|
154
397
|
|
|
398
|
+
/** A finger (or the mouse) is down on the item. */
|
|
399
|
+
protected readonly pressing = signal(false);
|
|
400
|
+
|
|
155
401
|
// Disabled when either the whole group or this specific item is disabled.
|
|
156
402
|
protected readonly isDisabled = computed(
|
|
157
403
|
() => this.#group.disabled() || this.disabled(),
|
|
158
404
|
);
|
|
159
405
|
|
|
160
|
-
protected readonly
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
406
|
+
protected readonly radius = computed(() => this.#group.itemRadius());
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Glass and Material items show a highlight while pressed, in place: the
|
|
410
|
+
* glass around them is what reacts on iOS, and Material marks a press with
|
|
411
|
+
* a state layer. Solid items shrink, or dim under Reduce Motion.
|
|
412
|
+
*/
|
|
413
|
+
readonly #highlights = computed(() => this.#group.look() !== 'solid');
|
|
414
|
+
|
|
415
|
+
protected readonly showLabel = computed(
|
|
416
|
+
() => !(this.projectedIcon() && this.#group.size() === 'icon'),
|
|
169
417
|
);
|
|
170
418
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
buttonGroupTextVariants({
|
|
174
|
-
variant: this.#group.variant(),
|
|
175
|
-
size: this.#group.size(),
|
|
176
|
-
}),
|
|
177
|
-
),
|
|
419
|
+
readonly #gap = computed(
|
|
420
|
+
() => !!this.projectedIcon() && this.showLabel() && 'gap-2',
|
|
178
421
|
);
|
|
179
422
|
|
|
423
|
+
protected readonly containerClass = computed(() => {
|
|
424
|
+
const variant = this.#group.variant();
|
|
425
|
+
const size = this.#group.size();
|
|
426
|
+
const look = this.#group.look();
|
|
427
|
+
const disabled = this.isDisabled() && 'opacity-50';
|
|
428
|
+
if (variant !== 'glass' && variant !== 'glass-clear') {
|
|
429
|
+
return cn(
|
|
430
|
+
buttonGroupItemVariants({ variant, size }),
|
|
431
|
+
this.#gap(),
|
|
432
|
+
this.pressing() && this.#settings.reduceMotion() && 'opacity-70',
|
|
433
|
+
disabled,
|
|
434
|
+
this.userClass(),
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
return cn(
|
|
438
|
+
'flex-row items-center flex relative justify-center',
|
|
439
|
+
ITEM_SIZES[size],
|
|
440
|
+
look === 'material' &&
|
|
441
|
+
cn(
|
|
442
|
+
'rounded-[8px] bg-secondary overflow-hidden',
|
|
443
|
+
this.#settings.increaseContrast() && 'border border-foreground',
|
|
444
|
+
),
|
|
445
|
+
this.#gap(),
|
|
446
|
+
disabled,
|
|
447
|
+
this.userClass(),
|
|
448
|
+
);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* The pressed highlight: the label's color at a low opacity, under the
|
|
453
|
+
* content, in the item's concentric corners. It comes and goes with the
|
|
454
|
+
* finger, with no motion.
|
|
455
|
+
*/
|
|
456
|
+
protected readonly highlightClass = computed(
|
|
457
|
+
() =>
|
|
458
|
+
this.pressing() &&
|
|
459
|
+
this.#highlights() &&
|
|
460
|
+
cn(
|
|
461
|
+
'h-full w-full absolute left-0 top-0',
|
|
462
|
+
this.#group.look() === 'material'
|
|
463
|
+
? 'bg-secondary-foreground opacity-10'
|
|
464
|
+
: 'bg-glass-foreground opacity-15',
|
|
465
|
+
),
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
protected readonly labelClass = computed(() => {
|
|
469
|
+
const variant = this.#group.variant();
|
|
470
|
+
let color: string;
|
|
471
|
+
if (variant !== 'glass' && variant !== 'glass-clear') {
|
|
472
|
+
color = SOLID_LABEL_COLORS[variant];
|
|
473
|
+
} else if (this.#group.look() === 'material') {
|
|
474
|
+
color = 'text-secondary-foreground';
|
|
475
|
+
} else {
|
|
476
|
+
// The glass points this at the clear foreground on clear glass.
|
|
477
|
+
color = 'text-glass-foreground';
|
|
478
|
+
}
|
|
479
|
+
return cn('font-medium', LABEL_SIZES[this.#group.size()], color);
|
|
480
|
+
});
|
|
481
|
+
|
|
180
482
|
protected onPressStart(): void {
|
|
181
483
|
if (this.isDisabled()) return;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
484
|
+
this.pressing.set(true);
|
|
485
|
+
if (!this.#scales()) return;
|
|
486
|
+
// See UiButton: press feedback is an el.setStyle()-driven CSS transition.
|
|
185
487
|
this.#pressAnim?.cancel();
|
|
186
488
|
this.#pressAnim = pressDown(this.containerRef()?.nativeElement);
|
|
187
489
|
}
|
|
188
490
|
|
|
189
491
|
protected onPressEnd(): void {
|
|
190
492
|
if (this.isDisabled()) return;
|
|
191
|
-
|
|
192
|
-
this.#pressAnim?.cancel();
|
|
193
|
-
this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
|
|
493
|
+
this.#release();
|
|
194
494
|
}
|
|
195
495
|
|
|
196
496
|
protected onPressCancel(): void {
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
this.#pressAnim?.cancel();
|
|
201
|
-
this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
|
|
497
|
+
// Release on cancel too — otherwise a touch stolen by a scroll gesture
|
|
498
|
+
// leaves the item stuck in its pressed-down state.
|
|
499
|
+
this.#release();
|
|
202
500
|
}
|
|
203
501
|
|
|
204
502
|
protected onTap(): void {
|
|
@@ -206,4 +504,18 @@ export class UiButtonGroupItem {
|
|
|
206
504
|
this.pressed.emit();
|
|
207
505
|
}
|
|
208
506
|
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Solid items shrink under the finger, unless Reduce Motion is on.
|
|
510
|
+
*/
|
|
511
|
+
#scales(): boolean {
|
|
512
|
+
return !this.#highlights() && !this.#settings.reduceMotion();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
#release(): void {
|
|
516
|
+
this.pressing.set(false);
|
|
517
|
+
if (!this.#scales()) return;
|
|
518
|
+
this.#pressAnim?.cancel();
|
|
519
|
+
this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
|
|
520
|
+
}
|
|
209
521
|
}
|
|
@@ -2,7 +2,10 @@ export {
|
|
|
2
2
|
UiButtonGroup,
|
|
3
3
|
UiButtonGroupItem,
|
|
4
4
|
buttonGroupItemVariants,
|
|
5
|
+
type ButtonGroupLook,
|
|
5
6
|
type ButtonGroupVariant,
|
|
6
7
|
type ButtonGroupSize,
|
|
7
8
|
type ButtonGroupOrientation,
|
|
9
|
+
type GlassButtonGroupVariant,
|
|
10
|
+
type SolidButtonGroupVariant,
|
|
8
11
|
} from './button-group';
|