@blotch/dolan 0.0.0-stage → 22.2.1
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 +22 -0
- package/dist/commands/add.js +132 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/add.test.d.ts +1 -0
- package/dist/commands/add.test.js +209 -0
- package/dist/commands/add.test.js.map +1 -0
- package/dist/commands/diff.d.ts +13 -0
- package/dist/commands/diff.js +155 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/diff.test.d.ts +1 -0
- package/dist/commands/diff.test.js +327 -0
- package/dist/commands/diff.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +15 -0
- package/dist/commands/doctor.js +248 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +1 -0
- package/dist/commands/doctor.test.js +301 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/eject.d.ts +11 -0
- package/dist/commands/eject.js +55 -0
- package/dist/commands/eject.js.map +1 -0
- package/dist/commands/eject.test.d.ts +1 -0
- package/dist/commands/eject.test.js +151 -0
- package/dist/commands/eject.test.js.map +1 -0
- package/dist/commands/info.d.ts +1 -0
- package/dist/commands/info.js +73 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/info.test.d.ts +1 -0
- package/dist/commands/info.test.js +143 -0
- package/dist/commands/info.test.js.map +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +98 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +1 -0
- package/dist/commands/init.test.js +154 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/list.d.ts +16 -0
- package/dist/commands/list.js +124 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/list.test.d.ts +1 -0
- package/dist/commands/list.test.js +285 -0
- package/dist/commands/list.test.js.map +1 -0
- package/dist/commands/outdated.d.ts +13 -0
- package/dist/commands/outdated.js +116 -0
- package/dist/commands/outdated.js.map +1 -0
- package/dist/commands/outdated.test.d.ts +1 -0
- package/dist/commands/outdated.test.js +208 -0
- package/dist/commands/outdated.test.js.map +1 -0
- package/dist/commands/remove.d.ts +18 -0
- package/dist/commands/remove.js +161 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/remove.test.d.ts +1 -0
- package/dist/commands/remove.test.js +361 -0
- package/dist/commands/remove.test.js.map +1 -0
- package/dist/commands/theme.d.ts +1 -0
- package/dist/commands/theme.js +66 -0
- package/dist/commands/theme.js.map +1 -0
- package/dist/commands/theme.test.d.ts +1 -0
- package/dist/commands/theme.test.js +133 -0
- package/dist/commands/theme.test.js.map +1 -0
- package/dist/commands/update.d.ts +31 -0
- package/dist/commands/update.js +601 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/update.test.d.ts +1 -0
- package/dist/commands/update.test.js +1233 -0
- package/dist/commands/update.test.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +19 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +48 -0
- package/dist/config.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/lockfile.d.ts +21 -0
- package/dist/lockfile.js +39 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +85 -0
- package/dist/registry.js.map +1 -0
- package/dist/registry.test.d.ts +1 -0
- package/dist/registry.test.js +95 -0
- package/dist/registry.test.js.map +1 -0
- package/dist/test-utils.d.ts +16 -0
- package/dist/test-utils.js +65 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/ui/components/accordion/accordion.test.ts +194 -0
- package/dist/ui/components/accordion/accordion.ts +173 -0
- package/dist/ui/components/accordion/index.ts +6 -0
- package/dist/ui/components/action-sheet/action-sheet.test.ts +211 -0
- package/dist/ui/components/action-sheet/action-sheet.ts +353 -0
- package/dist/ui/components/action-sheet/index.ts +6 -0
- package/dist/ui/components/alert/alert.test.ts +48 -0
- package/dist/ui/components/alert/alert.ts +104 -0
- package/dist/ui/components/alert/index.ts +1 -0
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +141 -0
- package/dist/ui/components/alert-dialog/alert-dialog.ts +243 -0
- package/dist/ui/components/alert-dialog/index.ts +7 -0
- package/dist/ui/components/area-chart/area-chart-render.test.ts +121 -0
- package/dist/ui/components/area-chart/area-chart.test.ts +158 -0
- package/dist/ui/components/area-chart/area-chart.ts +349 -0
- package/dist/ui/components/area-chart/index.ts +6 -0
- package/dist/ui/components/aspect-ratio/aspect-ratio.test.ts +10 -0
- package/dist/ui/components/aspect-ratio/aspect-ratio.ts +28 -0
- package/dist/ui/components/aspect-ratio/index.ts +1 -0
- package/dist/ui/components/avatar/avatar.test.ts +69 -0
- package/dist/ui/components/avatar/avatar.ts +131 -0
- package/dist/ui/components/avatar/index.ts +1 -0
- package/dist/ui/components/badge/badge.test.ts +49 -0
- package/dist/ui/components/badge/badge.ts +97 -0
- package/dist/ui/components/badge/index.ts +1 -0
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +174 -0
- package/dist/ui/components/bar-chart/bar-chart.test.ts +73 -0
- package/dist/ui/components/bar-chart/bar-chart.ts +327 -0
- package/dist/ui/components/bar-chart/index.ts +6 -0
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +163 -0
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +339 -0
- package/dist/ui/components/bottom-sheet/index.ts +1 -0
- package/dist/ui/components/button/button-render.test.ts +113 -0
- package/dist/ui/components/button/button.test.ts +70 -0
- package/dist/ui/components/button/button.ts +170 -0
- package/dist/ui/components/button/index.ts +6 -0
- package/dist/ui/components/button-group/button-group-render.test.ts +146 -0
- package/dist/ui/components/button-group/button-group.test.ts +54 -0
- package/dist/ui/components/button-group/button-group.ts +209 -0
- package/dist/ui/components/button-group/index.ts +8 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +174 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +96 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +355 -0
- package/dist/ui/components/candlestick-chart/index.ts +7 -0
- package/dist/ui/components/card/card.test.ts +110 -0
- package/dist/ui/components/card/card.ts +162 -0
- package/dist/ui/components/card/index.ts +8 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +188 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +549 -0
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +1140 -0
- package/dist/ui/components/cartesian-chart/index.ts +18 -0
- package/dist/ui/components/checkbox/checkbox.test.ts +59 -0
- package/dist/ui/components/checkbox/checkbox.ts +96 -0
- package/dist/ui/components/checkbox/index.ts +1 -0
- package/dist/ui/components/collapsible/collapsible.test.ts +112 -0
- package/dist/ui/components/collapsible/collapsible.ts +122 -0
- package/dist/ui/components/collapsible/index.ts +5 -0
- package/dist/ui/components/dialog/dialog.test.ts +157 -0
- package/dist/ui/components/dialog/dialog.ts +248 -0
- package/dist/ui/components/dialog/index.ts +7 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +60 -0
- package/dist/ui/components/empty-state/empty-state.ts +46 -0
- package/dist/ui/components/empty-state/index.ts +1 -0
- package/dist/ui/components/icon/icon.test.ts +52 -0
- package/dist/ui/components/icon/icon.ts +54 -0
- package/dist/ui/components/icon/icons.ts +569 -0
- package/dist/ui/components/icon/index.ts +2 -0
- package/dist/ui/components/input/index.ts +1 -0
- package/dist/ui/components/input/input.test.ts +134 -0
- package/dist/ui/components/input/input.ts +189 -0
- package/dist/ui/components/label/index.ts +1 -0
- package/dist/ui/components/label/label.test.ts +38 -0
- package/dist/ui/components/label/label.ts +24 -0
- package/dist/ui/components/line-chart/index.ts +6 -0
- package/dist/ui/components/line-chart/line-chart-render.test.ts +121 -0
- package/dist/ui/components/line-chart/line-chart.test.ts +81 -0
- package/dist/ui/components/line-chart/line-chart.ts +273 -0
- package/dist/ui/components/list/index.ts +1 -0
- package/dist/ui/components/list/list.test.ts +35 -0
- package/dist/ui/components/list/list.ts +71 -0
- package/dist/ui/components/nav-drawer/index.ts +8 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +267 -0
- package/dist/ui/components/nav-drawer/nav-drawer.ts +504 -0
- package/dist/ui/components/pagination/index.ts +1 -0
- package/dist/ui/components/pagination/pagination.test.ts +96 -0
- package/dist/ui/components/pagination/pagination.ts +147 -0
- package/dist/ui/components/progress/index.ts +1 -0
- package/dist/ui/components/progress/progress.test.ts +71 -0
- package/dist/ui/components/progress/progress.ts +118 -0
- package/dist/ui/components/radio-group/index.ts +1 -0
- package/dist/ui/components/radio-group/radio-group.test.ts +115 -0
- package/dist/ui/components/radio-group/radio-group.ts +129 -0
- package/dist/ui/components/scatter-chart/index.ts +6 -0
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +157 -0
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -0
- package/dist/ui/components/scatter-chart/scatter-chart.ts +277 -0
- package/dist/ui/components/scroll-area/index.ts +1 -0
- package/dist/ui/components/scroll-area/scroll-area.test.ts +40 -0
- package/dist/ui/components/scroll-area/scroll-area.ts +73 -0
- package/dist/ui/components/select/index.ts +1 -0
- package/dist/ui/components/select/select.test.ts +107 -0
- package/dist/ui/components/select/select.ts +146 -0
- package/dist/ui/components/separator/index.ts +1 -0
- package/dist/ui/components/separator/separator.test.ts +26 -0
- package/dist/ui/components/separator/separator.ts +45 -0
- package/dist/ui/components/sheet/index.ts +7 -0
- package/dist/ui/components/sheet/sheet.test.ts +44 -0
- package/dist/ui/components/sheet/sheet.ts +114 -0
- package/dist/ui/components/skeleton/index.ts +1 -0
- package/dist/ui/components/skeleton/skeleton.test.ts +13 -0
- package/dist/ui/components/skeleton/skeleton.ts +45 -0
- package/dist/ui/components/spinner/index.ts +1 -0
- package/dist/ui/components/spinner/spinner.test.ts +39 -0
- package/dist/ui/components/spinner/spinner.ts +83 -0
- package/dist/ui/components/switch/index.ts +1 -0
- package/dist/ui/components/switch/switch.test.ts +63 -0
- package/dist/ui/components/switch/switch.ts +98 -0
- package/dist/ui/components/tabs/index.ts +1 -0
- package/dist/ui/components/tabs/tabs.test.ts +199 -0
- package/dist/ui/components/tabs/tabs.ts +317 -0
- package/dist/ui/components/textarea/index.ts +1 -0
- package/dist/ui/components/textarea/textarea.test.ts +138 -0
- package/dist/ui/components/textarea/textarea.ts +221 -0
- package/dist/ui/components/toast/index.ts +9 -0
- package/dist/ui/components/toast/toast-state.test.ts +54 -0
- package/dist/ui/components/toast/toast-state.ts +46 -0
- package/dist/ui/components/toast/toast.test.ts +342 -0
- package/dist/ui/components/toast/toast.ts +527 -0
- package/dist/ui/components/toggle/index.ts +7 -0
- package/dist/ui/components/toggle/toggle.test.ts +186 -0
- package/dist/ui/components/toggle/toggle.ts +218 -0
- package/dist/ui/components/typography/index.ts +5 -0
- package/dist/ui/components/typography/typography.test.ts +55 -0
- package/dist/ui/components/typography/typography.ts +44 -0
- package/dist/ui/theme/dark.css +41 -0
- package/dist/ui/theme/default.css +56 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +124 -0
- package/dist/ui/theme/tailwind-plugin.ts +151 -0
- package/dist/ui/utils/animate.test.ts +324 -0
- package/dist/ui/utils/animate.ts +544 -0
- package/dist/ui/utils/cn.test.ts +27 -0
- package/dist/ui/utils/cn.ts +6 -0
- package/dist/utils/analyze.d.ts +32 -0
- package/dist/utils/analyze.js +109 -0
- package/dist/utils/analyze.js.map +1 -0
- package/dist/utils/analyze.test.d.ts +1 -0
- package/dist/utils/analyze.test.js +146 -0
- package/dist/utils/analyze.test.js.map +1 -0
- package/dist/utils/animate.d.ts +210 -0
- package/dist/utils/animate.js +423 -0
- package/dist/utils/animate.js.map +1 -0
- package/dist/utils/animate.test.d.ts +1 -0
- package/dist/utils/animate.test.js +365 -0
- package/dist/utils/animate.test.js.map +1 -0
- package/dist/utils/banner.d.ts +1 -0
- package/dist/utils/banner.js +15 -0
- package/dist/utils/banner.js.map +1 -0
- package/dist/utils/banner.test.d.ts +1 -0
- package/dist/utils/banner.test.js +29 -0
- package/dist/utils/banner.test.js.map +1 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/utils/cn.js +6 -0
- package/dist/utils/cn.js.map +1 -0
- package/dist/utils/cn.test.d.ts +1 -0
- package/dist/utils/cn.test.js +21 -0
- package/dist/utils/cn.test.js.map +1 -0
- package/dist/utils/format.d.ts +31 -0
- package/dist/utils/format.js +95 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/format.test.d.ts +1 -0
- package/dist/utils/format.test.js +71 -0
- package/dist/utils/format.test.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +16 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/logger.test.d.ts +1 -0
- package/dist/utils/logger.test.js +36 -0
- package/dist/utils/logger.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +12 -0
- package/dist/utils/resolve-paths.js +29 -0
- package/dist/utils/resolve-paths.js.map +1 -0
- package/dist/utils/resolve-paths.test.d.ts +1 -0
- package/dist/utils/resolve-paths.test.js +52 -0
- package/dist/utils/resolve-paths.test.js.map +1 -0
- package/package.json +56 -4
- package/README.md +0 -3
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ElementRef,
|
|
3
|
+
Component,
|
|
4
|
+
ViewEncapsulation,
|
|
5
|
+
computed,
|
|
6
|
+
contentChildren,
|
|
7
|
+
effect,
|
|
8
|
+
inject,
|
|
9
|
+
input,
|
|
10
|
+
model,
|
|
11
|
+
output,
|
|
12
|
+
signal,
|
|
13
|
+
viewChild,
|
|
14
|
+
} from '@angular/core';
|
|
15
|
+
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
type AnimationHandle,
|
|
19
|
+
DURATION,
|
|
20
|
+
EASING,
|
|
21
|
+
SCALE,
|
|
22
|
+
fadeIn,
|
|
23
|
+
fadeOut,
|
|
24
|
+
pressDown,
|
|
25
|
+
pressRelease,
|
|
26
|
+
slideIn,
|
|
27
|
+
slideOut,
|
|
28
|
+
} from '../../utils/animate';
|
|
29
|
+
import { cn } from '../../utils/cn';
|
|
30
|
+
import { UiIcon } from '../icon';
|
|
31
|
+
|
|
32
|
+
@Component({
|
|
33
|
+
selector: 'ui-nav-drawer',
|
|
34
|
+
standalone: true,
|
|
35
|
+
imports: [LYNX_ELEMENTS],
|
|
36
|
+
encapsulation: ViewEncapsulation.None,
|
|
37
|
+
template: `
|
|
38
|
+
<overlay [attr.visible]="overlayVisible()" [style]="overlayStyle()">
|
|
39
|
+
<!-- bg-black/50 dims the content behind the drawer. The fadeIn/fadeOut in
|
|
40
|
+
#animateIn/#animateOut already tween this element's opacity over
|
|
41
|
+
DURATION.normal, so the dim animates in lockstep with the panel slide —
|
|
42
|
+
matching the dialog/action-sheet/bottom-sheet backdrop. A named color
|
|
43
|
+
with an opacity modifier is required here: bg-black/50 compiles to
|
|
44
|
+
rgb(0 0 0 / 0.5), which Lynx accepts, whereas opacity modifiers on
|
|
45
|
+
semantic tokens don't. -->
|
|
46
|
+
<view
|
|
47
|
+
#backdrop
|
|
48
|
+
class="h-full w-full bg-black/50"
|
|
49
|
+
(bindtap)="onBackdropTap()"
|
|
50
|
+
>
|
|
51
|
+
<view
|
|
52
|
+
#panel
|
|
53
|
+
[class]="panelClass()"
|
|
54
|
+
[style]="panelPositionStyle()"
|
|
55
|
+
(catchtap)="onPanelTap()"
|
|
56
|
+
>
|
|
57
|
+
<ng-content />
|
|
58
|
+
</view>
|
|
59
|
+
</view>
|
|
60
|
+
</overlay>
|
|
61
|
+
`,
|
|
62
|
+
})
|
|
63
|
+
export class UiNavDrawer {
|
|
64
|
+
readonly open = model(false);
|
|
65
|
+
readonly side = input<'left' | 'right'>('left');
|
|
66
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
67
|
+
|
|
68
|
+
readonly closed = output<void>();
|
|
69
|
+
|
|
70
|
+
protected readonly overlayVisible = signal(false);
|
|
71
|
+
/**
|
|
72
|
+
* `width`/`height`/`top`/`left` are web-only necessities: on web
|
|
73
|
+
* `<overlay>` has no intrinsic full-screen sizing (unlike native), so
|
|
74
|
+
* without them it collapses to 0x0 and nothing renders. `z-index` keeps it
|
|
75
|
+
* above sibling content that would otherwise occasionally paint on top.
|
|
76
|
+
*/
|
|
77
|
+
protected readonly overlayStyle = computed(() =>
|
|
78
|
+
this.overlayVisible()
|
|
79
|
+
? 'position: fixed; overflow: visible; width: 100vw; height: 100vh; top: 0; left: 0; z-index: 999;'
|
|
80
|
+
: 'position: fixed; overflow: visible; width: 100vw; height: 100vh; top: 0; left: 0; z-index: 999; display: none;',
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
readonly backdropRef = viewChild<ElementRef>('backdrop');
|
|
84
|
+
readonly panelRef = viewChild<ElementRef>('panel');
|
|
85
|
+
#backdropAnim?: AnimationHandle;
|
|
86
|
+
#panelAnim?: AnimationHandle;
|
|
87
|
+
#hasBeenOpen = false;
|
|
88
|
+
|
|
89
|
+
constructor() {
|
|
90
|
+
// `#hasBeenOpen` prevents the close animation from running on the initial
|
|
91
|
+
// effect evaluation when `open` starts as false. Without this guard, the
|
|
92
|
+
// first run would call #doClose() and emit `closed` before the drawer
|
|
93
|
+
// has ever been opened.
|
|
94
|
+
effect(() => {
|
|
95
|
+
const isOpen = this.open();
|
|
96
|
+
if (isOpen) {
|
|
97
|
+
this.#hasBeenOpen = true;
|
|
98
|
+
this.#doOpen();
|
|
99
|
+
} else if (this.#hasBeenOpen) {
|
|
100
|
+
this.#doClose();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected readonly panelClass = computed(() =>
|
|
106
|
+
cn(
|
|
107
|
+
'h-full flex-col bg-background flex',
|
|
108
|
+
this.side() === 'left'
|
|
109
|
+
? 'border-r border-border'
|
|
110
|
+
: 'border-l border-border',
|
|
111
|
+
this.userClass(),
|
|
112
|
+
),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
protected readonly panelPositionStyle = computed(() => {
|
|
116
|
+
const sideValue = this.side();
|
|
117
|
+
return sideValue === 'left'
|
|
118
|
+
? 'position: absolute; top: 0; bottom: 0; left: 0; width: 80%;'
|
|
119
|
+
: 'position: absolute; top: 0; bottom: 0; right: 0; width: 80%;';
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
protected onBackdropTap(): void {
|
|
123
|
+
this.open.set(false);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* No-op tap handler for the panel. `catchtap` (vs `bindtap`) already stops
|
|
128
|
+
* the tap from bubbling to the backdrop — Lynx controls propagation via the
|
|
129
|
+
* event prefix, not at runtime. (The renderer shims `event.stopPropagation()`
|
|
130
|
+
* as a no-op so DOM-style handlers don't crash, but it has no effect here.)
|
|
131
|
+
*/
|
|
132
|
+
protected onPanelTap(): void {}
|
|
133
|
+
|
|
134
|
+
close(): void {
|
|
135
|
+
this.open.set(false);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Two-phase open: first make the overlay visible (so native elements exist),
|
|
140
|
+
* then animate in on the next frame. Without the nested setTimeout, the
|
|
141
|
+
* animate() call would target elements that haven't been flushed to native
|
|
142
|
+
* yet — Lynx element.animate() requires the element to be in the tree.
|
|
143
|
+
*/
|
|
144
|
+
#doOpen(): void {
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
this.overlayVisible.set(true);
|
|
147
|
+
setTimeout(() => this.#animateIn(), 0);
|
|
148
|
+
}, 0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Reverse of open: animate out first, then hide the overlay after the
|
|
153
|
+
* animation completes. The +20ms buffer accounts for timing imprecision
|
|
154
|
+
* in Lynx's timer system — removing the overlay mid-animation would
|
|
155
|
+
* cause a visual glitch (elements disappear before fade completes).
|
|
156
|
+
*/
|
|
157
|
+
#doClose(): void {
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
this.#animateOut();
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
this.overlayVisible.set(false);
|
|
162
|
+
this.closed.emit();
|
|
163
|
+
}, DURATION.normal + 20);
|
|
164
|
+
}, 0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#animateIn(): void {
|
|
168
|
+
const backdrop = this.backdropRef()?.nativeElement;
|
|
169
|
+
const panel = this.panelRef()?.nativeElement;
|
|
170
|
+
if (!backdrop || !panel) return;
|
|
171
|
+
|
|
172
|
+
this.#backdropAnim?.cancel();
|
|
173
|
+
this.#panelAnim?.cancel();
|
|
174
|
+
|
|
175
|
+
this.#backdropAnim = fadeIn(backdrop, { duration: DURATION.normal });
|
|
176
|
+
const direction = this.side() === 'left' ? 'left' : 'right';
|
|
177
|
+
this.#panelAnim = slideIn(panel, direction, {
|
|
178
|
+
duration: DURATION.slow,
|
|
179
|
+
easing: EASING.sheet,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#animateOut(): void {
|
|
184
|
+
const backdrop = this.backdropRef()?.nativeElement;
|
|
185
|
+
const panel = this.panelRef()?.nativeElement;
|
|
186
|
+
if (!backdrop || !panel) return;
|
|
187
|
+
|
|
188
|
+
this.#backdropAnim?.cancel();
|
|
189
|
+
this.#panelAnim?.cancel();
|
|
190
|
+
|
|
191
|
+
this.#backdropAnim = fadeOut(backdrop, { duration: DURATION.normal });
|
|
192
|
+
const direction = this.side() === 'left' ? 'left' : 'right';
|
|
193
|
+
this.#panelAnim = slideOut(panel, direction, {
|
|
194
|
+
duration: DURATION.normal,
|
|
195
|
+
easing: EASING.accelerate,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@Component({
|
|
201
|
+
selector: 'ui-nav-drawer-header',
|
|
202
|
+
standalone: true,
|
|
203
|
+
imports: [LYNX_ELEMENTS],
|
|
204
|
+
encapsulation: ViewEncapsulation.None,
|
|
205
|
+
template: `
|
|
206
|
+
<view [class]="containerClass()">
|
|
207
|
+
<ng-content />
|
|
208
|
+
</view>
|
|
209
|
+
`,
|
|
210
|
+
})
|
|
211
|
+
export class UiNavDrawerHeader {
|
|
212
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
213
|
+
|
|
214
|
+
// Fixes: the header title/subtitle were drawn behind the device status bar /
|
|
215
|
+
// notch / dynamic island. The drawer panel is positioned top: 0; bottom: 0
|
|
216
|
+
// (see UiNavDrawer.panelPositionStyle), so it spans the full screen height and
|
|
217
|
+
// the header — its first child — starts at y=0, under the system inset.
|
|
218
|
+
// Fix: split the padding — keep `px-4 pb-4` (1rem) and swap the top for
|
|
219
|
+
// `pt-safe-4` = calc(1rem + env(safe-area-inset-top)), preserving the original
|
|
220
|
+
// p-4 top padding while clearing the inset. Degrades to plain 1rem where the
|
|
221
|
+
// inset is 0 (e.g. the web preview), so no platform fork is needed.
|
|
222
|
+
protected readonly containerClass = computed(() =>
|
|
223
|
+
cn(
|
|
224
|
+
'flex-col gap-1.5 border-b border-border px-4 pb-4 flex pt-safe-4',
|
|
225
|
+
this.userClass(),
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@Component({
|
|
231
|
+
selector: 'ui-nav-drawer-content',
|
|
232
|
+
standalone: true,
|
|
233
|
+
imports: [LYNX_ELEMENTS],
|
|
234
|
+
encapsulation: ViewEncapsulation.None,
|
|
235
|
+
// Fixes: a long item list scrolled nowhere and spilled over the header and
|
|
236
|
+
// footer instead of scrolling within its own section.
|
|
237
|
+
//
|
|
238
|
+
// Root cause: Lynx forces `scroll-view` into linear layout, where it EXPANDS
|
|
239
|
+
// to fit its content rather than accepting a flex-computed height (see
|
|
240
|
+
// investigations/lynx-vs-web-differences.md, "scroll-view requires an explicit
|
|
241
|
+
// height constraint to scroll"). It therefore has no bound to scroll within
|
|
242
|
+
// and grows past its slot in the panel's flex-col.
|
|
243
|
+
//
|
|
244
|
+
// Two approaches were tried and failed, hence this shape:
|
|
245
|
+
// 1. `flex-1` directly on the scroll-view — ignored, because the scroll-view
|
|
246
|
+
// sizes to content, not to the flex track. Still overflowed.
|
|
247
|
+
// 2. wrapping the scroll-view in an inner `flex-1` view — the inner view is
|
|
248
|
+
// NOT the flex child of the panel; the host element is. The host still
|
|
249
|
+
// sized to content and overflowed, and the wrapper being `flex` (a ROW)
|
|
250
|
+
// also shrank the scroll-view to its content WIDTH.
|
|
251
|
+
//
|
|
252
|
+
// Working fix: put the flex sizing on the HOST element (the real flex child of
|
|
253
|
+
// the panel's flex-col — same lesson as ui-tabs-content / ui-separator).
|
|
254
|
+
// `flex-1` claims the space left between the header and footer; `flex flex-col`
|
|
255
|
+
// makes the host lay the scroll-view out vertically to fill that height. The
|
|
256
|
+
// panel's flex-col stretches the host to full width by default (Lynx
|
|
257
|
+
// align-items: stretch), so no width class is needed on the host.
|
|
258
|
+
host: {
|
|
259
|
+
class: 'flex-1 flex-col flex',
|
|
260
|
+
},
|
|
261
|
+
// With the host now a definite height, the scroll-view fills it with `h-full`
|
|
262
|
+
// (the explicit height Lynx requires to actually scroll) and `w-full` to span
|
|
263
|
+
// the host width. This is the bound the scroll-view was missing above.
|
|
264
|
+
//
|
|
265
|
+
// (bindcontentsizechanged) fires once the scroll-view finishes laying out its
|
|
266
|
+
// content (iOS/Android). That's the exact moment child frames exist, so it's
|
|
267
|
+
// the precise trigger for scrolling the active item into view when the drawer
|
|
268
|
+
// is first shown — see onContentLayout().
|
|
269
|
+
template: `
|
|
270
|
+
<scroll-view
|
|
271
|
+
scroll-orientation="vertical"
|
|
272
|
+
[class]="contentClass()"
|
|
273
|
+
(bindcontentsizechanged)="onContentLayout()"
|
|
274
|
+
>
|
|
275
|
+
<ng-content />
|
|
276
|
+
</scroll-view>
|
|
277
|
+
`,
|
|
278
|
+
})
|
|
279
|
+
export class UiNavDrawerContent {
|
|
280
|
+
readonly #drawer = inject(UiNavDrawer);
|
|
281
|
+
|
|
282
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
283
|
+
|
|
284
|
+
// Every nav item projected into this section (Home + each group item). Used to
|
|
285
|
+
// find the active one when the drawer opens. `descendants: true` reaches items
|
|
286
|
+
// nested inside the @for group wrappers, not just the direct content children.
|
|
287
|
+
readonly items = contentChildren(UiNavDrawerItem, { descendants: true });
|
|
288
|
+
|
|
289
|
+
protected readonly contentClass = computed(() =>
|
|
290
|
+
cn('h-full w-full', this.userClass()),
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
constructor() {
|
|
294
|
+
// When the drawer opens, land the user on their current route instead of the
|
|
295
|
+
// top of a potentially long nav list. The <overlay> is display:none until
|
|
296
|
+
// open, so the scroll-view has no laid-out child frames until it's shown —
|
|
297
|
+
// defer a tick so the just-shown content is measured before we read it.
|
|
298
|
+
//
|
|
299
|
+
// This open-driven path is the fallback: it covers the web preview (where
|
|
300
|
+
// contentsizechanged isn't emitted) and re-opens that don't trigger a
|
|
301
|
+
// relayout. On device, onContentLayout() below is the precise trigger. Both
|
|
302
|
+
// call the same instant scroll, so firing twice is a harmless no-op.
|
|
303
|
+
effect(() => {
|
|
304
|
+
if (!this.#drawer.open()) return;
|
|
305
|
+
setTimeout(() => this.#scrollActiveIntoView(), 0);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Fired by the scroll-view once its content finishes layout. Gated on the
|
|
311
|
+
* drawer being open so a close-time relayout (content collapsing as the overlay
|
|
312
|
+
* hides) doesn't scroll an already-dismissed panel.
|
|
313
|
+
*/
|
|
314
|
+
protected onContentLayout(): void {
|
|
315
|
+
if (this.#drawer.open()) this.#scrollActiveIntoView();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#scrollActiveIntoView(): void {
|
|
319
|
+
this.items()
|
|
320
|
+
/* v8 ignore start -- .find predicate unreachable when no content-projected items */
|
|
321
|
+
.find((item) => item.active())
|
|
322
|
+
/* v8 ignore stop */
|
|
323
|
+
?.scrollIntoView();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
@Component({
|
|
328
|
+
selector: 'ui-nav-drawer-item',
|
|
329
|
+
standalone: true,
|
|
330
|
+
imports: [LYNX_ELEMENTS],
|
|
331
|
+
encapsulation: ViewEncapsulation.None,
|
|
332
|
+
template: `
|
|
333
|
+
<view
|
|
334
|
+
#container
|
|
335
|
+
[class]="containerClass()"
|
|
336
|
+
(bindtouchstart)="onPressStart()"
|
|
337
|
+
(bindtouchend)="onPressEnd()"
|
|
338
|
+
(bindtouchcancel)="onPressCancel()"
|
|
339
|
+
(bindmousedown)="onPressStart()"
|
|
340
|
+
(bindmouseup)="onPressEnd()"
|
|
341
|
+
(bindmouseleave)="onPressCancel()"
|
|
342
|
+
(bindtap)="onTap()"
|
|
343
|
+
>
|
|
344
|
+
<ng-content />
|
|
345
|
+
</view>
|
|
346
|
+
`,
|
|
347
|
+
})
|
|
348
|
+
export class UiNavDrawerItem {
|
|
349
|
+
readonly #drawer = inject(UiNavDrawer);
|
|
350
|
+
|
|
351
|
+
readonly variant = input<'default' | 'destructive'>('default');
|
|
352
|
+
readonly active = input(false);
|
|
353
|
+
readonly autoClose = input(true);
|
|
354
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
355
|
+
|
|
356
|
+
readonly pressed = output<void>();
|
|
357
|
+
|
|
358
|
+
readonly containerRef = viewChild<ElementRef>('container');
|
|
359
|
+
#pressAnim?: AnimationHandle;
|
|
360
|
+
|
|
361
|
+
protected readonly containerClass = computed(() =>
|
|
362
|
+
cn(
|
|
363
|
+
'flex-row items-center gap-3 px-4 py-3 flex',
|
|
364
|
+
this.active() ? 'bg-accent' : 'bg-transparent',
|
|
365
|
+
this.userClass(),
|
|
366
|
+
),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
protected onPressStart(): void {
|
|
370
|
+
this.#pressAnim?.cancel();
|
|
371
|
+
this.#pressAnim = pressDown(
|
|
372
|
+
this.containerRef()?.nativeElement,
|
|
373
|
+
SCALE.pressDownLight,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
protected onPressEnd(): void {
|
|
378
|
+
this.#pressAnim?.cancel();
|
|
379
|
+
this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
protected onPressCancel(): void {
|
|
383
|
+
this.#pressAnim?.cancel();
|
|
384
|
+
this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
protected onTap(): void {
|
|
388
|
+
this.pressed.emit();
|
|
389
|
+
if (this.autoClose()) {
|
|
390
|
+
this.#drawer.close();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Scrolls the enclosing scroll-view so this item is visible. `scrollIntoView`
|
|
396
|
+
* is a base LynxUI UIMethod bound to ANY (direct or indirect) child of a
|
|
397
|
+
* scroll container: it walks up to the nearest <scroll-view> and scrolls that
|
|
398
|
+
* container itself. Because it targets the element — not a child index/offset —
|
|
399
|
+
* it's immune to the display:none control-flow anchor <view>s Angular
|
|
400
|
+
* interleaves as scroll-view children for @if/@for (which would corrupt any
|
|
401
|
+
* index-based scroll). UiNavDrawerContent calls this to reveal the active route
|
|
402
|
+
* when the drawer opens.
|
|
403
|
+
*
|
|
404
|
+
* block:'center' centers the item in the viewport; behavior:'auto' positions
|
|
405
|
+
* instantly (not animated) so the drawer appears already-scrolled as it slides
|
|
406
|
+
* in, instead of visibly jumping after it settles.
|
|
407
|
+
*/
|
|
408
|
+
scrollIntoView(): void {
|
|
409
|
+
// The param shape differs by platform, so send both — each side reads its
|
|
410
|
+
// own keys and ignores the rest:
|
|
411
|
+
// - Native Lynx (iOS/Android) reads params.scrollIntoViewOptions.
|
|
412
|
+
// - Web maps invoke() onto the DOM element.scrollIntoView(params), which
|
|
413
|
+
// reads top-level ScrollIntoViewOptions (block/behavior).
|
|
414
|
+
this.containerRef()?.nativeElement.invoke('scrollIntoView', {
|
|
415
|
+
scrollIntoViewOptions: { block: 'center', behavior: 'auto' },
|
|
416
|
+
block: 'center',
|
|
417
|
+
behavior: 'auto',
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
@Component({
|
|
423
|
+
selector: 'ui-nav-drawer-footer',
|
|
424
|
+
standalone: true,
|
|
425
|
+
imports: [LYNX_ELEMENTS],
|
|
426
|
+
encapsulation: ViewEncapsulation.None,
|
|
427
|
+
template: `
|
|
428
|
+
<view [class]="containerClass()">
|
|
429
|
+
<ng-content />
|
|
430
|
+
</view>
|
|
431
|
+
`,
|
|
432
|
+
})
|
|
433
|
+
export class UiNavDrawerFooter {
|
|
434
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
435
|
+
|
|
436
|
+
// Fixes (mirror of the header): the footer content was drawn behind the home
|
|
437
|
+
// indicator. The footer is the last child of the top: 0; bottom: 0 panel, so
|
|
438
|
+
// its lower edge reaches the very bottom of the screen, under the inset.
|
|
439
|
+
// Fix: keep `px-4 pt-4` (1rem) and swap the bottom for `pb-safe-4` =
|
|
440
|
+
// calc(1rem + env(safe-area-inset-bottom)), preserving the original p-4 bottom
|
|
441
|
+
// padding while lifting the content clear of the inset.
|
|
442
|
+
protected readonly containerClass = computed(() =>
|
|
443
|
+
cn(
|
|
444
|
+
'flex-col gap-2 border-t border-border px-4 pt-4 flex pb-safe-4',
|
|
445
|
+
this.userClass(),
|
|
446
|
+
),
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
@Component({
|
|
451
|
+
selector: 'ui-nav-drawer-trigger',
|
|
452
|
+
standalone: true,
|
|
453
|
+
imports: [LYNX_ELEMENTS, UiIcon],
|
|
454
|
+
encapsulation: ViewEncapsulation.None,
|
|
455
|
+
template: `
|
|
456
|
+
<view
|
|
457
|
+
#container
|
|
458
|
+
[class]="containerClass()"
|
|
459
|
+
(bindtouchstart)="onPressStart()"
|
|
460
|
+
(bindtouchend)="onPressEnd()"
|
|
461
|
+
(bindtouchcancel)="onPressCancel()"
|
|
462
|
+
(bindmousedown)="onPressStart()"
|
|
463
|
+
(bindmouseup)="onPressEnd()"
|
|
464
|
+
(bindmouseleave)="onPressCancel()"
|
|
465
|
+
(bindtap)="onTap()"
|
|
466
|
+
>
|
|
467
|
+
<ui-icon name="menu" size="md" />
|
|
468
|
+
</view>
|
|
469
|
+
`,
|
|
470
|
+
})
|
|
471
|
+
export class UiNavDrawerTrigger {
|
|
472
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
473
|
+
|
|
474
|
+
readonly pressed = output<void>();
|
|
475
|
+
|
|
476
|
+
readonly containerRef = viewChild<ElementRef>('container');
|
|
477
|
+
#pressAnim?: AnimationHandle;
|
|
478
|
+
|
|
479
|
+
protected readonly containerClass = computed(() =>
|
|
480
|
+
cn(
|
|
481
|
+
'h-10 w-10 items-center rounded-md flex justify-center',
|
|
482
|
+
this.userClass(),
|
|
483
|
+
),
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
protected onPressStart(): void {
|
|
487
|
+
this.#pressAnim?.cancel();
|
|
488
|
+
this.#pressAnim = pressDown(this.containerRef()?.nativeElement);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
protected onPressEnd(): void {
|
|
492
|
+
this.#pressAnim?.cancel();
|
|
493
|
+
this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
protected onPressCancel(): void {
|
|
497
|
+
this.#pressAnim?.cancel();
|
|
498
|
+
this.#pressAnim = pressRelease(this.containerRef()?.nativeElement);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
protected onTap(): void {
|
|
502
|
+
this.pressed.emit();
|
|
503
|
+
}
|
|
504
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiPagination } from './pagination';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { setInputSignal } from '../../../test-utils/set-input-signal';
|
|
4
|
+
import { UiPagination } from './pagination';
|
|
5
|
+
|
|
6
|
+
describe('UiPagination', () => {
|
|
7
|
+
const setup = async (totalPages: number, page = 1, siblingCount = 1) => {
|
|
8
|
+
const { componentRef, container } = await render(UiPagination);
|
|
9
|
+
const inst = componentRef.instance as UiPagination;
|
|
10
|
+
setInputSignal(inst.totalPages, totalPages);
|
|
11
|
+
setInputSignal(inst.siblingCount, siblingCount);
|
|
12
|
+
inst.page.set(page);
|
|
13
|
+
await waitForUpdate();
|
|
14
|
+
return { inst, container };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
it('renders the pagination controls', async () => {
|
|
18
|
+
const { container } = await setup(5, 2);
|
|
19
|
+
expect(container).toBeTruthy();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('lists every page when total fits the un-truncated window', async () => {
|
|
23
|
+
// With siblings=1 the threshold is 5+2 = 7, so a total of 5 stays inline.
|
|
24
|
+
const { container } = await setup(5, 3);
|
|
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('...');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('inserts a trailing ellipsis when the window is near the start', async () => {
|
|
33
|
+
// total=10, page=2, siblings=1 → rangeStart=2, rangeEnd=3; gap on right only.
|
|
34
|
+
const { container } = await setup(10, 2);
|
|
35
|
+
expect(container.textContent ?? '').toContain('...');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('inserts a leading ellipsis when the window is near the end', async () => {
|
|
39
|
+
// total=10, page=9, siblings=1 → rangeStart=8, rangeEnd=9; gap on left only.
|
|
40
|
+
const { container } = await setup(10, 9);
|
|
41
|
+
expect(container.textContent ?? '').toContain('...');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('inserts leading and trailing ellipses when the window sits in the middle', async () => {
|
|
45
|
+
// total=10, page=5, siblings=1 → rangeStart=4, rangeEnd=6; gaps on both sides.
|
|
46
|
+
const { container } = await setup(10, 5);
|
|
47
|
+
// Two ellipses in the DOM as separate <text> nodes.
|
|
48
|
+
expect(
|
|
49
|
+
container.textContent?.match(/\.\.\./g)?.length,
|
|
50
|
+
).toBeGreaterThanOrEqual(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('prev() decrements page when > 1', async () => {
|
|
54
|
+
const { inst } = await setup(5, 3);
|
|
55
|
+
(inst as unknown as { prev: () => void }).prev();
|
|
56
|
+
await waitForUpdate();
|
|
57
|
+
expect(inst.page()).toBe(2);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('prev() is a no-op at page 1', async () => {
|
|
61
|
+
const { inst } = await setup(5, 1);
|
|
62
|
+
(inst as unknown as { prev: () => void }).prev();
|
|
63
|
+
await waitForUpdate();
|
|
64
|
+
expect(inst.page()).toBe(1);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('next() increments page when < totalPages', async () => {
|
|
68
|
+
const { inst } = await setup(5, 3);
|
|
69
|
+
(inst as unknown as { next: () => void }).next();
|
|
70
|
+
await waitForUpdate();
|
|
71
|
+
expect(inst.page()).toBe(4);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('next() is a no-op at the last page', async () => {
|
|
75
|
+
const { inst } = await setup(5, 5);
|
|
76
|
+
(inst as unknown as { next: () => void }).next();
|
|
77
|
+
await waitForUpdate();
|
|
78
|
+
expect(inst.page()).toBe(5);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('goTo() jumps the page directly', async () => {
|
|
82
|
+
const { inst } = await setup(5, 1);
|
|
83
|
+
(inst as unknown as { goTo: (n: number) => void }).goTo(4);
|
|
84
|
+
await waitForUpdate();
|
|
85
|
+
expect(inst.page()).toBe(4);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('active page carries the primary variant class', async () => {
|
|
89
|
+
const { container } = await setup(3, 2);
|
|
90
|
+
// The active page's tile background is bg-primary; the others are transparent.
|
|
91
|
+
const primary = Array.from(container.querySelectorAll('view')).find((el) =>
|
|
92
|
+
el.getAttribute('class')?.includes('bg-primary'),
|
|
93
|
+
);
|
|
94
|
+
expect(primary).toBeTruthy();
|
|
95
|
+
});
|
|
96
|
+
});
|