@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,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ElementRef,
|
|
3
|
+
type OnDestroy,
|
|
3
4
|
Component,
|
|
4
5
|
ViewEncapsulation,
|
|
5
6
|
computed,
|
|
@@ -28,10 +29,10 @@ import {
|
|
|
28
29
|
} from '../../utils/animate';
|
|
29
30
|
import { cn } from '../../utils/cn';
|
|
30
31
|
import { UiIcon } from '../icon';
|
|
32
|
+
import { UI_ICON_COLOR } from '../icon/icon-theme';
|
|
31
33
|
|
|
32
34
|
@Component({
|
|
33
35
|
selector: 'ui-nav-drawer',
|
|
34
|
-
standalone: true,
|
|
35
36
|
imports: [LYNX_ELEMENTS],
|
|
36
37
|
encapsulation: ViewEncapsulation.None,
|
|
37
38
|
template: `
|
|
@@ -39,10 +40,10 @@ import { UiIcon } from '../icon';
|
|
|
39
40
|
<!-- bg-black/50 dims the content behind the drawer. The fadeIn/fadeOut in
|
|
40
41
|
#animateIn/#animateOut already tween this element's opacity over
|
|
41
42
|
DURATION.normal, so the dim animates in lockstep with the panel slide —
|
|
42
|
-
matching the dialog/action-sheet/bottom-sheet backdrop.
|
|
43
|
-
|
|
44
|
-
rgb(0 0 0 / 0.5), which Lynx
|
|
45
|
-
|
|
43
|
+
matching the dialog/action-sheet/bottom-sheet backdrop. It's a named
|
|
44
|
+
color, not a theme one, because the dim is black in light and dark
|
|
45
|
+
mode alike; bg-black/50 compiles to rgb(0 0 0 / 0.5), which Lynx
|
|
46
|
+
accepts. -->
|
|
46
47
|
<view
|
|
47
48
|
#backdrop
|
|
48
49
|
class="h-full w-full bg-black/50"
|
|
@@ -52,6 +53,7 @@ import { UiIcon } from '../icon';
|
|
|
52
53
|
#panel
|
|
53
54
|
[class]="panelClass()"
|
|
54
55
|
[style]="panelPositionStyle()"
|
|
56
|
+
[accessibility-exclusive-focus]="overlayVisible()"
|
|
55
57
|
(catchtap)="onPanelTap()"
|
|
56
58
|
>
|
|
57
59
|
<ng-content />
|
|
@@ -60,7 +62,7 @@ import { UiIcon } from '../icon';
|
|
|
60
62
|
</overlay>
|
|
61
63
|
`,
|
|
62
64
|
})
|
|
63
|
-
export class UiNavDrawer {
|
|
65
|
+
export class UiNavDrawer implements OnDestroy {
|
|
64
66
|
readonly open = model(false);
|
|
65
67
|
readonly side = input<'left' | 'right'>('left');
|
|
66
68
|
readonly userClass = input<string>('', { alias: 'class' });
|
|
@@ -80,27 +82,38 @@ export class UiNavDrawer {
|
|
|
80
82
|
: 'position: fixed; overflow: visible; width: 100vw; height: 100vh; top: 0; left: 0; z-index: 999; display: none;',
|
|
81
83
|
);
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
// Required: both are always in the template (the overlay hides with
|
|
86
|
+
// display:none rather than leaving the tree), and the sequences only read
|
|
87
|
+
// them from timers that start after the first render.
|
|
88
|
+
readonly backdropRef = viewChild.required<ElementRef>('backdrop');
|
|
89
|
+
readonly panelRef = viewChild.required<ElementRef>('panel');
|
|
85
90
|
#backdropAnim?: AnimationHandle;
|
|
86
91
|
#panelAnim?: AnimationHandle;
|
|
87
92
|
#hasBeenOpen = false;
|
|
93
|
+
// The open and close sequences' pending timers, so ngOnDestroy can stop
|
|
94
|
+
// them.
|
|
95
|
+
readonly #timers = new Set<ReturnType<typeof setTimeout>>();
|
|
88
96
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Plays the open or close sequence whenever `open` changes. A field rather
|
|
99
|
+
* than a constructor: Angular's test build keeps a function for a
|
|
100
|
+
* constructor that nothing calls, which an app's coverage report would show
|
|
101
|
+
* as uncovered.
|
|
102
|
+
*
|
|
103
|
+
* `#hasBeenOpen` prevents the close animation from running on the initial
|
|
104
|
+
* effect evaluation when `open` starts as false. Without this guard, the
|
|
105
|
+
* first run would call #doClose() and emit `closed` before the drawer
|
|
106
|
+
* has ever been opened.
|
|
107
|
+
*/
|
|
108
|
+
protected readonly openAndClose = effect(() => {
|
|
109
|
+
const isOpen = this.open();
|
|
110
|
+
if (isOpen) {
|
|
111
|
+
this.#hasBeenOpen = true;
|
|
112
|
+
this.#doOpen();
|
|
113
|
+
} else if (this.#hasBeenOpen) {
|
|
114
|
+
this.#doClose();
|
|
115
|
+
}
|
|
116
|
+
});
|
|
104
117
|
|
|
105
118
|
protected readonly panelClass = computed(() =>
|
|
106
119
|
cn(
|
|
@@ -135,6 +148,28 @@ export class UiNavDrawer {
|
|
|
135
148
|
this.open.set(false);
|
|
136
149
|
}
|
|
137
150
|
|
|
151
|
+
/**
|
|
152
|
+
* The drawer can be destroyed while its open or close sequence is still
|
|
153
|
+
* running, for example by an `@if` around it or a route change right after
|
|
154
|
+
* closing it. The rest of the sequence then ran on the destroyed component:
|
|
155
|
+
* it animated detached elements and emitted `closed` from a destroyed
|
|
156
|
+
* output, which Angular warns about in dev builds (NG0953).
|
|
157
|
+
*/
|
|
158
|
+
ngOnDestroy(): void {
|
|
159
|
+
for (const timer of this.#timers) clearTimeout(timer);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* `setTimeout`, tracked so that ngOnDestroy can cancel it.
|
|
164
|
+
*/
|
|
165
|
+
#later(callback: () => void, ms: number): void {
|
|
166
|
+
const timer = setTimeout(() => {
|
|
167
|
+
this.#timers.delete(timer);
|
|
168
|
+
callback();
|
|
169
|
+
}, ms);
|
|
170
|
+
this.#timers.add(timer);
|
|
171
|
+
}
|
|
172
|
+
|
|
138
173
|
/**
|
|
139
174
|
* Two-phase open: first make the overlay visible (so native elements exist),
|
|
140
175
|
* then animate in on the next frame. Without the nested setTimeout, the
|
|
@@ -142,9 +177,9 @@ export class UiNavDrawer {
|
|
|
142
177
|
* yet — Lynx element.animate() requires the element to be in the tree.
|
|
143
178
|
*/
|
|
144
179
|
#doOpen(): void {
|
|
145
|
-
|
|
180
|
+
this.#later(() => {
|
|
146
181
|
this.overlayVisible.set(true);
|
|
147
|
-
|
|
182
|
+
this.#later(() => this.#animateIn(), 0);
|
|
148
183
|
}, 0);
|
|
149
184
|
}
|
|
150
185
|
|
|
@@ -155,9 +190,9 @@ export class UiNavDrawer {
|
|
|
155
190
|
* cause a visual glitch (elements disappear before fade completes).
|
|
156
191
|
*/
|
|
157
192
|
#doClose(): void {
|
|
158
|
-
|
|
193
|
+
this.#later(() => {
|
|
159
194
|
this.#animateOut();
|
|
160
|
-
|
|
195
|
+
this.#later(() => {
|
|
161
196
|
this.overlayVisible.set(false);
|
|
162
197
|
this.closed.emit();
|
|
163
198
|
}, DURATION.normal + 20);
|
|
@@ -165,9 +200,8 @@ export class UiNavDrawer {
|
|
|
165
200
|
}
|
|
166
201
|
|
|
167
202
|
#animateIn(): void {
|
|
168
|
-
const backdrop = this.backdropRef()
|
|
169
|
-
const panel = this.panelRef()
|
|
170
|
-
if (!backdrop || !panel) return;
|
|
203
|
+
const backdrop = this.backdropRef().nativeElement;
|
|
204
|
+
const panel = this.panelRef().nativeElement;
|
|
171
205
|
|
|
172
206
|
this.#backdropAnim?.cancel();
|
|
173
207
|
this.#panelAnim?.cancel();
|
|
@@ -178,12 +212,16 @@ export class UiNavDrawer {
|
|
|
178
212
|
duration: DURATION.slow,
|
|
179
213
|
easing: EASING.sheet,
|
|
180
214
|
});
|
|
215
|
+
// Move a screen reader into the panel. It would otherwise stay on whatever
|
|
216
|
+
// opened it, which is now behind the overlay. With the panel's
|
|
217
|
+
// accessibility-exclusive-focus (see the template), it then can't leave
|
|
218
|
+
// the panel until it closes.
|
|
219
|
+
panel.invoke?.('requestAccessibilityFocus');
|
|
181
220
|
}
|
|
182
221
|
|
|
183
222
|
#animateOut(): void {
|
|
184
|
-
const backdrop = this.backdropRef()
|
|
185
|
-
const panel = this.panelRef()
|
|
186
|
-
if (!backdrop || !panel) return;
|
|
223
|
+
const backdrop = this.backdropRef().nativeElement;
|
|
224
|
+
const panel = this.panelRef().nativeElement;
|
|
187
225
|
|
|
188
226
|
this.#backdropAnim?.cancel();
|
|
189
227
|
this.#panelAnim?.cancel();
|
|
@@ -199,7 +237,6 @@ export class UiNavDrawer {
|
|
|
199
237
|
|
|
200
238
|
@Component({
|
|
201
239
|
selector: 'ui-nav-drawer-header',
|
|
202
|
-
standalone: true,
|
|
203
240
|
imports: [LYNX_ELEMENTS],
|
|
204
241
|
encapsulation: ViewEncapsulation.None,
|
|
205
242
|
template: `
|
|
@@ -227,9 +264,15 @@ export class UiNavDrawerHeader {
|
|
|
227
264
|
);
|
|
228
265
|
}
|
|
229
266
|
|
|
267
|
+
/**
|
|
268
|
+
* `UiNavDrawerItem`, for `contentChildren()`. A call rather than the class
|
|
269
|
+
* itself: for a class, Angular's test build keeps an arrow function that
|
|
270
|
+
* nothing calls, which an app's coverage report would show as uncovered.
|
|
271
|
+
*/
|
|
272
|
+
const navDrawerItem = () => UiNavDrawerItem;
|
|
273
|
+
|
|
230
274
|
@Component({
|
|
231
275
|
selector: 'ui-nav-drawer-content',
|
|
232
|
-
standalone: true,
|
|
233
276
|
imports: [LYNX_ELEMENTS],
|
|
234
277
|
encapsulation: ViewEncapsulation.None,
|
|
235
278
|
// Fixes: a long item list scrolled nowhere and spilled over the header and
|
|
@@ -284,27 +327,29 @@ export class UiNavDrawerContent {
|
|
|
284
327
|
// Every nav item projected into this section (Home + each group item). Used to
|
|
285
328
|
// find the active one when the drawer opens. `descendants: true` reaches items
|
|
286
329
|
// nested inside the @for group wrappers, not just the direct content children.
|
|
287
|
-
readonly items = contentChildren(
|
|
330
|
+
readonly items = contentChildren(navDrawerItem(), { descendants: true });
|
|
288
331
|
|
|
289
332
|
protected readonly contentClass = computed(() =>
|
|
290
333
|
cn('h-full w-full', this.userClass()),
|
|
291
334
|
);
|
|
292
335
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
336
|
+
/**
|
|
337
|
+
* When the drawer opens, land the user on their current route instead of the
|
|
338
|
+
* top of a potentially long nav list.
|
|
339
|
+
*
|
|
340
|
+
* The <overlay> is display:none until open, so the scroll-view has no
|
|
341
|
+
* laid-out child frames until it's shown — defer a tick so the just-shown
|
|
342
|
+
* content is measured before we read it.
|
|
343
|
+
*
|
|
344
|
+
* This open-driven path is the fallback: it covers the web preview (where
|
|
345
|
+
* contentsizechanged isn't emitted) and re-opens that don't trigger a
|
|
346
|
+
* relayout. On device, onContentLayout() below is the precise trigger. Both
|
|
347
|
+
* call the same instant scroll, so firing twice is a harmless no-op.
|
|
348
|
+
*/
|
|
349
|
+
protected readonly revealActiveOnOpen = effect(() => {
|
|
350
|
+
if (!this.#drawer.open()) return;
|
|
351
|
+
setTimeout(() => this.#scrollActiveIntoView(), 0);
|
|
352
|
+
});
|
|
308
353
|
|
|
309
354
|
/**
|
|
310
355
|
* Fired by the scroll-view once its content finishes layout. Gated on the
|
|
@@ -317,18 +362,31 @@ export class UiNavDrawerContent {
|
|
|
317
362
|
|
|
318
363
|
#scrollActiveIntoView(): void {
|
|
319
364
|
this.items()
|
|
320
|
-
/* v8 ignore start -- .find predicate unreachable when no content-projected items */
|
|
321
365
|
.find((item) => item.active())
|
|
322
|
-
/* v8 ignore stop */
|
|
323
366
|
?.scrollIntoView();
|
|
324
367
|
}
|
|
325
368
|
}
|
|
326
369
|
|
|
370
|
+
/**
|
|
371
|
+
* The color icons inside an item draw with: the destructive color in a
|
|
372
|
+
* destructive item. A function of its own rather than written inline in
|
|
373
|
+
* `providers`: Angular's test build keeps a second copy of an inline factory
|
|
374
|
+
* that nothing calls, which an app's coverage report would show as uncovered.
|
|
375
|
+
*/
|
|
376
|
+
const itemIconColor = () => {
|
|
377
|
+
const item = inject(UiNavDrawerItem);
|
|
378
|
+
return computed(() =>
|
|
379
|
+
item.variant() === 'destructive' ? 'var(--destructive)' : undefined,
|
|
380
|
+
);
|
|
381
|
+
};
|
|
382
|
+
|
|
327
383
|
@Component({
|
|
328
384
|
selector: 'ui-nav-drawer-item',
|
|
329
|
-
standalone: true,
|
|
330
385
|
imports: [LYNX_ELEMENTS],
|
|
331
386
|
encapsulation: ViewEncapsulation.None,
|
|
387
|
+
// Icons projected into the item (see the variant below) inject this through
|
|
388
|
+
// the item's element injector, which content projection keeps them under.
|
|
389
|
+
providers: [{ provide: UI_ICON_COLOR, useFactory: itemIconColor }],
|
|
332
390
|
template: `
|
|
333
391
|
<view
|
|
334
392
|
#container
|
|
@@ -358,10 +416,18 @@ export class UiNavDrawerItem {
|
|
|
358
416
|
readonly containerRef = viewChild<ElementRef>('container');
|
|
359
417
|
#pressAnim?: AnimationHandle;
|
|
360
418
|
|
|
419
|
+
// `destructive` recolors the item's content, which the item doesn't render
|
|
420
|
+
// itself: the label and icon are projected. Lynx doesn't inherit `color`, so
|
|
421
|
+
// a text class here wouldn't reach the label. CSS variables do inherit, so
|
|
422
|
+
// the item points `--foreground` at `--destructive` for everything inside
|
|
423
|
+
// it: a label styled `text-foreground` (as items are) turns destructive.
|
|
424
|
+
// Icons draw with SVG, which can't read CSS, so they get the same color
|
|
425
|
+
// through UI_ICON_COLOR (see providers above).
|
|
361
426
|
protected readonly containerClass = computed(() =>
|
|
362
427
|
cn(
|
|
363
428
|
'flex-row items-center gap-3 px-4 py-3 flex',
|
|
364
429
|
this.active() ? 'bg-accent' : 'bg-transparent',
|
|
430
|
+
this.variant() === 'destructive' && '[--foreground:var(--destructive)]',
|
|
365
431
|
this.userClass(),
|
|
366
432
|
),
|
|
367
433
|
);
|
|
@@ -421,7 +487,6 @@ export class UiNavDrawerItem {
|
|
|
421
487
|
|
|
422
488
|
@Component({
|
|
423
489
|
selector: 'ui-nav-drawer-footer',
|
|
424
|
-
standalone: true,
|
|
425
490
|
imports: [LYNX_ELEMENTS],
|
|
426
491
|
encapsulation: ViewEncapsulation.None,
|
|
427
492
|
template: `
|
|
@@ -449,13 +514,17 @@ export class UiNavDrawerFooter {
|
|
|
449
514
|
|
|
450
515
|
@Component({
|
|
451
516
|
selector: 'ui-nav-drawer-trigger',
|
|
452
|
-
standalone: true,
|
|
453
517
|
imports: [LYNX_ELEMENTS, UiIcon],
|
|
454
518
|
encapsulation: ViewEncapsulation.None,
|
|
455
519
|
template: `
|
|
520
|
+
<!-- The trigger is only an icon, so it carries the words a screen reader
|
|
521
|
+
says for it. -->
|
|
456
522
|
<view
|
|
457
523
|
#container
|
|
458
524
|
[class]="containerClass()"
|
|
525
|
+
[accessibility-element]="true"
|
|
526
|
+
accessibility-traits="button"
|
|
527
|
+
[accessibility-label]="label()"
|
|
459
528
|
(bindtouchstart)="onPressStart()"
|
|
460
529
|
(bindtouchend)="onPressEnd()"
|
|
461
530
|
(bindtouchcancel)="onPressCancel()"
|
|
@@ -469,6 +538,8 @@ export class UiNavDrawerFooter {
|
|
|
469
538
|
`,
|
|
470
539
|
})
|
|
471
540
|
export class UiNavDrawerTrigger {
|
|
541
|
+
/** What a screen reader says for the trigger. */
|
|
542
|
+
readonly label = input('Open navigation menu');
|
|
472
543
|
readonly userClass = input<string>('', { alias: 'class' });
|
|
473
544
|
|
|
474
545
|
readonly pressed = output<void>();
|
|
@@ -1,96 +1,208 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
fireEvent,
|
|
4
|
+
render,
|
|
5
|
+
waitForUpdate,
|
|
6
|
+
within,
|
|
7
|
+
} from '@blotch/angular-lynx-testing-library';
|
|
2
8
|
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import {
|
|
9
|
+
import { ICON_LIGHT_COLORS } from '../icon/icon-theme';
|
|
4
10
|
import { UiPagination } from './pagination';
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
@Component({
|
|
13
|
+
imports: [UiPagination],
|
|
14
|
+
template: `
|
|
15
|
+
<ui-pagination
|
|
16
|
+
[(page)]="page"
|
|
17
|
+
[totalPages]="totalPages()"
|
|
18
|
+
[siblingCount]="siblingCount()"
|
|
19
|
+
class="mt-4"
|
|
20
|
+
/>
|
|
21
|
+
`,
|
|
22
|
+
})
|
|
23
|
+
class PaginationHost {
|
|
24
|
+
readonly page = signal(1);
|
|
25
|
+
readonly totalPages = signal(5);
|
|
26
|
+
readonly siblingCount = signal(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Component({
|
|
30
|
+
imports: [UiPagination],
|
|
31
|
+
template: `
|
|
32
|
+
<ui-pagination
|
|
33
|
+
[totalPages]="2"
|
|
34
|
+
previousLabel="Back"
|
|
35
|
+
nextLabel="Forward"
|
|
36
|
+
[pageLabel]="resultsLabel"
|
|
37
|
+
/>
|
|
38
|
+
`,
|
|
39
|
+
})
|
|
40
|
+
class LabeledPaginationHost {
|
|
41
|
+
readonly resultsLabel = (page: number, current: boolean): string =>
|
|
42
|
+
current ? `Results ${page}, showing` : `Results ${page}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Renders the row at a page of a total, with `siblingCount` pages on each
|
|
47
|
+
* side of the current one.
|
|
48
|
+
*/
|
|
49
|
+
const renderPagination = async (
|
|
50
|
+
totalPages: number,
|
|
51
|
+
page = 1,
|
|
52
|
+
siblingCount = 1,
|
|
53
|
+
) => {
|
|
54
|
+
const result = await render(PaginationHost);
|
|
55
|
+
const host = result.componentRef.instance as PaginationHost;
|
|
56
|
+
host.totalPages.set(totalPages);
|
|
57
|
+
host.siblingCount.set(siblingCount);
|
|
58
|
+
host.page.set(page);
|
|
59
|
+
await waitForUpdate();
|
|
60
|
+
return {
|
|
61
|
+
...result,
|
|
62
|
+
host,
|
|
63
|
+
/** The button a screen reader reads as `label`. */
|
|
64
|
+
button: (label: string) => result.getByRole('button', { name: label }),
|
|
15
65
|
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* What the row shows between its arrows, in order: page numbers and gaps.
|
|
70
|
+
*/
|
|
71
|
+
const shown = (container: Element): (string | null)[] =>
|
|
72
|
+
Array.from(container.querySelectorAll('text')).map(
|
|
73
|
+
(el) => el.textContent?.trim() ?? null,
|
|
74
|
+
);
|
|
16
75
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
76
|
+
/**
|
|
77
|
+
* The screen reader labels of the row's buttons, in order. getAllByRole()
|
|
78
|
+
* finds only what a screen reader reaches as a button, so this also checks
|
|
79
|
+
* each one's traits and accessibility-element.
|
|
80
|
+
*/
|
|
81
|
+
const labels = (container: Element): (string | null)[] =>
|
|
82
|
+
within(container as HTMLElement)
|
|
83
|
+
.getAllByRole('button')
|
|
84
|
+
.map((button) => button.getAttribute('accessibility-label'));
|
|
85
|
+
|
|
86
|
+
describe('UiPagination', () => {
|
|
87
|
+
it('lists every page when they all fit, with the given class', async () => {
|
|
88
|
+
// With one sibling, up to 5 + 1 * 2 = 7 pages fit without gaps.
|
|
89
|
+
const { container } = await renderPagination(7, 3);
|
|
90
|
+
expect(shown(container)).toEqual(['1', '2', '3', '4', '5', '6', '7']);
|
|
91
|
+
expect(container.querySelector('.gap-1')?.classList).toContain('mt-4');
|
|
20
92
|
});
|
|
21
93
|
|
|
22
|
-
it('
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Each page should render its numeric label.
|
|
26
|
-
const text = container.textContent ?? '';
|
|
27
|
-
expect(text).toContain('1');
|
|
28
|
-
expect(text).toContain('5');
|
|
29
|
-
expect(text).not.toContain('...');
|
|
94
|
+
it('leaves a gap before the last page near the start', async () => {
|
|
95
|
+
const { container } = await renderPagination(10, 2);
|
|
96
|
+
expect(shown(container)).toEqual(['1', '2', '3', '...', '10']);
|
|
30
97
|
});
|
|
31
98
|
|
|
32
|
-
it('
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
expect(container.textContent ?? '').toContain('...');
|
|
99
|
+
it('leaves a gap after the first page near the end', async () => {
|
|
100
|
+
const { container } = await renderPagination(10, 9);
|
|
101
|
+
expect(shown(container)).toEqual(['1', '...', '8', '9', '10']);
|
|
36
102
|
});
|
|
37
103
|
|
|
38
|
-
it('
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
expect(container.textContent ?? '').toContain('...');
|
|
104
|
+
it('leaves gaps on both sides in the middle', async () => {
|
|
105
|
+
const { container } = await renderPagination(10, 5);
|
|
106
|
+
expect(shown(container)).toEqual(['1', '...', '4', '5', '6', '...', '10']);
|
|
42
107
|
});
|
|
43
108
|
|
|
44
|
-
it('
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
109
|
+
it('widens the window around the current page with more siblings', async () => {
|
|
110
|
+
const { container } = await renderPagination(12, 6, 2);
|
|
111
|
+
expect(shown(container)).toEqual([
|
|
112
|
+
'1',
|
|
113
|
+
'...',
|
|
114
|
+
'4',
|
|
115
|
+
'5',
|
|
116
|
+
'6',
|
|
117
|
+
'7',
|
|
118
|
+
'8',
|
|
119
|
+
'...',
|
|
120
|
+
'12',
|
|
121
|
+
]);
|
|
51
122
|
});
|
|
52
123
|
|
|
53
|
-
it('
|
|
54
|
-
const {
|
|
55
|
-
(
|
|
56
|
-
|
|
57
|
-
expect(
|
|
124
|
+
it('highlights the current page', async () => {
|
|
125
|
+
const { button } = await renderPagination(3, 2);
|
|
126
|
+
expect(button('Page 2, current page').classList).toContain('bg-primary');
|
|
127
|
+
expect(button('Page 1').classList).toContain('bg-transparent');
|
|
128
|
+
expect(button('Page 2, current page').textContent?.trim()).toBe('2');
|
|
58
129
|
});
|
|
59
130
|
|
|
60
|
-
it('
|
|
61
|
-
const {
|
|
62
|
-
(
|
|
131
|
+
it('goes to a tapped page', async () => {
|
|
132
|
+
const { host, button } = await renderPagination(5, 1);
|
|
133
|
+
fireEvent.tap(button('Page 4'));
|
|
63
134
|
await waitForUpdate();
|
|
64
|
-
expect(
|
|
135
|
+
expect(host.page()).toBe(4);
|
|
136
|
+
expect(button('Page 4, current page').classList).toContain('bg-primary');
|
|
65
137
|
});
|
|
66
138
|
|
|
67
|
-
it('
|
|
68
|
-
const {
|
|
69
|
-
(
|
|
139
|
+
it('steps back and forward with the arrows', async () => {
|
|
140
|
+
const { host, button } = await renderPagination(5, 3);
|
|
141
|
+
fireEvent.tap(button('Previous page'));
|
|
70
142
|
await waitForUpdate();
|
|
71
|
-
expect(
|
|
72
|
-
});
|
|
143
|
+
expect(host.page()).toBe(2);
|
|
73
144
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
(inst as unknown as { next: () => void }).next();
|
|
145
|
+
fireEvent.tap(button('Next page'));
|
|
146
|
+
fireEvent.tap(button('Next page'));
|
|
77
147
|
await waitForUpdate();
|
|
78
|
-
expect(
|
|
148
|
+
expect(host.page()).toBe(4);
|
|
79
149
|
});
|
|
80
150
|
|
|
81
|
-
it(
|
|
82
|
-
const
|
|
83
|
-
(
|
|
151
|
+
it("dims the arrow that can't go further, and ignores its taps", async () => {
|
|
152
|
+
const first = await renderPagination(5, 1);
|
|
153
|
+
expect(first.button('Previous page').classList).toContain('opacity-50');
|
|
154
|
+
expect(first.button('Next page').classList).not.toContain('opacity-50');
|
|
155
|
+
fireEvent.tap(first.button('Previous page'));
|
|
84
156
|
await waitForUpdate();
|
|
85
|
-
expect(
|
|
157
|
+
expect(first.host.page()).toBe(1);
|
|
158
|
+
|
|
159
|
+
const last = await renderPagination(5, 5);
|
|
160
|
+
expect(last.button('Next page').classList).toContain('opacity-50');
|
|
161
|
+
fireEvent.tap(last.button('Next page'));
|
|
162
|
+
await waitForUpdate();
|
|
163
|
+
expect(last.host.page()).toBe(5);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('labels every button for screen readers', async () => {
|
|
167
|
+
const { container } = await renderPagination(3, 2);
|
|
168
|
+
expect(labels(container)).toEqual([
|
|
169
|
+
'Previous page',
|
|
170
|
+
'Page 1',
|
|
171
|
+
'Page 2, current page',
|
|
172
|
+
'Page 3',
|
|
173
|
+
'Next page',
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('takes its own labels', async () => {
|
|
178
|
+
const { container } = await render(LabeledPaginationHost);
|
|
179
|
+
expect(labels(container)).toEqual([
|
|
180
|
+
'Back',
|
|
181
|
+
'Results 1, showing',
|
|
182
|
+
'Results 2',
|
|
183
|
+
'Forward',
|
|
184
|
+
]);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('hides the gaps from screen readers', async () => {
|
|
188
|
+
const { container } = await renderPagination(10, 5);
|
|
189
|
+
const gaps = Array.from(container.querySelectorAll('text'))
|
|
190
|
+
.filter((el) => el.textContent === '...')
|
|
191
|
+
.map((el) =>
|
|
192
|
+
el.parentElement?.getAttribute('accessibility-elements-hidden'),
|
|
193
|
+
);
|
|
194
|
+
expect(gaps).toEqual(['true', 'true']);
|
|
86
195
|
});
|
|
87
196
|
|
|
88
|
-
it(
|
|
89
|
-
const { container } = await
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
el.getAttribute('class')?.includes('bg-primary'),
|
|
197
|
+
it("draws the arrows in the theme's foreground", async () => {
|
|
198
|
+
const { container } = await renderPagination(3, 2);
|
|
199
|
+
const arrows = Array.from(container.querySelectorAll('svg')).map((el) =>
|
|
200
|
+
el.getAttribute('content'),
|
|
93
201
|
);
|
|
94
|
-
expect(
|
|
202
|
+
expect(arrows).toHaveLength(2);
|
|
203
|
+
for (const arrow of arrows) {
|
|
204
|
+
expect(arrow).toContain(ICON_LIGHT_COLORS['foreground']);
|
|
205
|
+
expect(arrow).not.toContain('currentColor');
|
|
206
|
+
}
|
|
95
207
|
});
|
|
96
208
|
});
|