@blotch/dolan 22.2.3 → 22.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.js +9 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +38 -3
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/doctor.js +258 -21
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +425 -11
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +38 -18
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +105 -2
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/theme.js +1 -1
- package/dist/commands/theme.js.map +1 -1
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +194 -50
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +570 -3
- package/dist/commands/update.test.js.map +1 -1
- package/dist/lockfile.d.ts +17 -0
- package/dist/lockfile.js +56 -1
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +65 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js +119 -11
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +61 -6
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +27 -0
- package/dist/test-utils.js +45 -1
- package/dist/test-utils.js.map +1 -1
- package/dist/testing/fire-lynx-event.d.ts +14 -0
- package/dist/testing/fire-lynx-event.js +19 -0
- package/dist/testing/fire-lynx-event.js.map +1 -0
- package/dist/testing/fire-lynx-event.test.d.ts +1 -0
- package/dist/testing/fire-lynx-event.test.js +44 -0
- package/dist/testing/fire-lynx-event.test.js.map +1 -0
- package/dist/testing/lynx-main-thread.d.ts +22 -0
- package/dist/testing/lynx-main-thread.js +11 -0
- package/dist/testing/lynx-main-thread.js.map +1 -0
- package/dist/testing/lynx-main-thread.test.d.ts +1 -0
- package/dist/testing/lynx-main-thread.test.js +24 -0
- package/dist/testing/lynx-main-thread.test.js.map +1 -0
- package/dist/testing/set-input-signal.d.ts +5 -4
- package/dist/testing/set-input-signal.js +5 -4
- package/dist/testing/set-input-signal.js.map +1 -1
- package/dist/ui/boolean-inputs.test.ts +76 -0
- package/dist/ui/components/accordion/accordion.test.ts +158 -144
- package/dist/ui/components/accordion/accordion.ts +19 -14
- package/dist/ui/components/action-sheet/action-sheet.test.ts +637 -183
- package/dist/ui/components/action-sheet/action-sheet.ts +339 -55
- package/dist/ui/components/action-sheet/index.ts +1 -0
- package/dist/ui/components/alert/alert.test.ts +67 -19
- package/dist/ui/components/alert/alert.ts +14 -16
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +740 -129
- package/dist/ui/components/alert-dialog/alert-dialog.ts +672 -58
- package/dist/ui/components/alert-dialog/index.ts +5 -0
- package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
- package/dist/ui/components/area-chart/area-chart.ts +3 -2
- package/dist/ui/components/avatar/avatar.test.ts +89 -45
- package/dist/ui/components/badge/badge.test.ts +57 -23
- package/dist/ui/components/badge/badge.ts +14 -15
- package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -1
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +1314 -273
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1046 -119
- package/dist/ui/components/bottom-sheet/index.ts +7 -1
- package/dist/ui/components/button/button.test.ts +646 -2
- package/dist/ui/components/button/button.ts +443 -64
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +528 -2
- package/dist/ui/components/button-group/button-group.ts +428 -70
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +82 -1
- package/dist/ui/components/card/card.test.ts +97 -61
- package/dist/ui/components/card/card.ts +2 -1
- package/dist/ui/components/chart/chart-presets.test.ts +197 -1
- package/dist/ui/components/chart/chart-presets.ts +14 -7
- package/dist/ui/components/chart/chart-theme.ts +12 -0
- package/dist/ui/components/chart/chart.test.ts +1031 -0
- package/dist/ui/components/chart/chart.ts +66 -46
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +25 -24
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +21 -15
- package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
- package/dist/ui/components/context-menu/context-menu.ts +110 -0
- package/dist/ui/components/context-menu/index.ts +1 -0
- package/dist/ui/components/dialog/dialog.test.ts +210 -152
- package/dist/ui/components/dialog/dialog.ts +35 -29
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +142 -0
- package/dist/ui/components/dock/dock-search.test.ts +594 -0
- package/dist/ui/components/dock/dock.test.ts +752 -0
- package/dist/ui/components/dock/dock.ts +1438 -0
- package/dist/ui/components/dock/index.ts +2 -0
- package/dist/ui/components/dynamic-color/dynamic-color.test.ts +144 -0
- package/dist/ui/components/dynamic-color/dynamic-color.ts +146 -0
- package/dist/ui/components/dynamic-color/index.ts +5 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
- package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
- package/dist/ui/components/glass/concentric-radius.ts +17 -0
- package/dist/ui/components/glass/glass-design.test.ts +78 -0
- package/dist/ui/components/glass/glass-design.ts +119 -0
- package/dist/ui/components/glass/glass.test.ts +376 -0
- package/dist/ui/components/glass/glass.ts +430 -0
- package/dist/ui/components/glass/index.ts +19 -0
- package/dist/ui/components/icon/icon-theme.test.ts +64 -0
- package/dist/ui/components/icon/icon-theme.ts +70 -16
- package/dist/ui/components/icon/icon.test.ts +52 -41
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +17 -13
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/label/label.ts +8 -2
- package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
- package/dist/ui/components/line-chart/line-chart.ts +3 -2
- package/dist/ui/components/list/list.test.ts +80 -49
- package/dist/ui/components/list/list.ts +5 -4
- package/dist/ui/components/menu/index.ts +25 -0
- package/dist/ui/components/menu/menu-placement.test.ts +153 -0
- package/dist/ui/components/menu/menu-placement.ts +159 -0
- package/dist/ui/components/menu/menu-popup.test.ts +636 -0
- package/dist/ui/components/menu/menu-popup.ts +565 -0
- package/dist/ui/components/menu/menu.test.ts +523 -0
- package/dist/ui/components/menu/menu.ts +455 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +425 -260
- package/dist/ui/components/nav-drawer/nav-drawer.ts +75 -58
- package/dist/ui/components/navigation-bar/index.ts +1 -0
- package/dist/ui/components/navigation-bar/navigation-bar.test.ts +425 -0
- package/dist/ui/components/navigation-bar/navigation-bar.ts +411 -0
- package/dist/ui/components/pagination/pagination.test.ts +136 -86
- package/dist/ui/components/popover/index.ts +9 -0
- package/dist/ui/components/popover/popover-panel.test.ts +735 -0
- package/dist/ui/components/popover/popover-panel.ts +603 -0
- package/dist/ui/components/popover/popover.test.ts +286 -0
- package/dist/ui/components/popover/popover.ts +207 -0
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +54 -55
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +26 -25
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +74 -1
- package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
- package/dist/ui/components/scroll-area/scroll-area.ts +4 -3
- package/dist/ui/components/search-field/index.ts +15 -0
- package/dist/ui/components/search-field/keyboard.test.ts +147 -0
- package/dist/ui/components/search-field/keyboard.ts +123 -0
- package/dist/ui/components/search-field/search-field.test.ts +549 -0
- package/dist/ui/components/search-field/search-field.ts +628 -0
- package/dist/ui/components/segmented-control/index.ts +8 -0
- package/dist/ui/components/segmented-control/segmented-control.test.ts +861 -0
- package/dist/ui/components/segmented-control/segmented-control.ts +884 -0
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +19 -12
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +151 -11
- package/dist/ui/components/sheet/sheet.ts +68 -5
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/slider/index.ts +1 -0
- package/dist/ui/components/slider/slider.test.ts +695 -0
- package/dist/ui/components/slider/slider.ts +901 -0
- package/dist/ui/components/spinner/spinner.test.ts +64 -46
- package/dist/ui/components/spinner/spinner.ts +16 -19
- package/dist/ui/components/switch/index.ts +1 -1
- package/dist/ui/components/switch/switch.test.ts +534 -48
- package/dist/ui/components/switch/switch.ts +613 -48
- package/dist/ui/components/tab-bar/index.ts +1 -0
- package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
- package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
- package/dist/ui/components/tabs/index.ts +7 -1
- package/dist/ui/components/tabs/tabs.test.ts +484 -150
- package/dist/ui/components/tabs/tabs.ts +344 -149
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +16 -13
- package/dist/ui/components/toast/toast.test.ts +434 -350
- package/dist/ui/components/toast/toast.ts +42 -51
- package/dist/ui/components/toggle/index.ts +1 -0
- package/dist/ui/components/toggle/toggle.test.ts +559 -146
- package/dist/ui/components/toggle/toggle.ts +241 -26
- package/dist/ui/components/toolbar/bar-scroll.test.ts +310 -0
- package/dist/ui/components/toolbar/bar-scroll.ts +372 -0
- package/dist/ui/components/toolbar/index.ts +12 -0
- package/dist/ui/components/toolbar/toolbar.test.ts +438 -0
- package/dist/ui/components/toolbar/toolbar.ts +535 -0
- package/dist/ui/theme/chart-colors.test.ts +8 -3
- package/dist/ui/theme/dark.css +38 -2
- package/dist/ui/theme/default.css +55 -7
- package/dist/ui/theme/icon-colors.test.ts +8 -3
- package/dist/ui/theme/rgb-channels.test.ts +84 -0
- package/dist/ui/theme/tailwind-build.test.ts +114 -0
- package/dist/ui/theme/tailwind-css.test.ts +83 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
- package/dist/ui/theme/tailwind-plugin.ts +93 -29
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +19 -0
- package/dist/utils/analyze.js +16 -0
- package/dist/utils/analyze.js.map +1 -1
- package/dist/utils/analyze.test.js +14 -1
- package/dist/utils/analyze.test.js.map +1 -1
- package/dist/utils/installed.d.ts +25 -1
- package/dist/utils/installed.js +33 -0
- package/dist/utils/installed.js.map +1 -1
- package/dist/utils/installed.test.js +55 -1
- package/dist/utils/installed.test.js.map +1 -1
- package/dist/utils/resolve-paths.d.ts +4 -4
- package/dist/utils/resolve-paths.js +5 -7
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +9 -5
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +45 -0
- package/dist/utils/tailwind.js +90 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +134 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +9 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -63
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -58
- package/dist/ui/components/button/button-render.test.ts +0 -132
- package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -59
- package/dist/ui/components/chart/chart-render.test.ts +0 -730
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -83
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -64
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type OnDestroy,
|
|
4
4
|
Component,
|
|
5
5
|
ViewEncapsulation,
|
|
6
|
+
booleanAttribute,
|
|
6
7
|
computed,
|
|
7
8
|
contentChildren,
|
|
8
9
|
effect,
|
|
@@ -40,10 +41,10 @@ import { UI_ICON_COLOR } from '../icon/icon-theme';
|
|
|
40
41
|
<!-- bg-black/50 dims the content behind the drawer. The fadeIn/fadeOut in
|
|
41
42
|
#animateIn/#animateOut already tween this element's opacity over
|
|
42
43
|
DURATION.normal, so the dim animates in lockstep with the panel slide —
|
|
43
|
-
matching the dialog/action-sheet/bottom-sheet backdrop.
|
|
44
|
-
|
|
45
|
-
rgb(0 0 0 / 0.5), which Lynx
|
|
46
|
-
|
|
44
|
+
matching the dialog/action-sheet/bottom-sheet backdrop. It's a named
|
|
45
|
+
color, not a theme one, because the dim is black in light and dark
|
|
46
|
+
mode alike; bg-black/50 compiles to rgb(0 0 0 / 0.5), which Lynx
|
|
47
|
+
accepts. -->
|
|
47
48
|
<view
|
|
48
49
|
#backdrop
|
|
49
50
|
class="h-full w-full bg-black/50"
|
|
@@ -82,8 +83,11 @@ export class UiNavDrawer implements OnDestroy {
|
|
|
82
83
|
: 'position: fixed; overflow: visible; width: 100vw; height: 100vh; top: 0; left: 0; z-index: 999; display: none;',
|
|
83
84
|
);
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
// Required: both are always in the template (the overlay hides with
|
|
87
|
+
// display:none rather than leaving the tree), and the sequences only read
|
|
88
|
+
// them from timers that start after the first render.
|
|
89
|
+
readonly backdropRef = viewChild.required<ElementRef>('backdrop');
|
|
90
|
+
readonly panelRef = viewChild.required<ElementRef>('panel');
|
|
87
91
|
#backdropAnim?: AnimationHandle;
|
|
88
92
|
#panelAnim?: AnimationHandle;
|
|
89
93
|
#hasBeenOpen = false;
|
|
@@ -91,21 +95,26 @@ export class UiNavDrawer implements OnDestroy {
|
|
|
91
95
|
// them.
|
|
92
96
|
readonly #timers = new Set<ReturnType<typeof setTimeout>>();
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Plays the open or close sequence whenever `open` changes. A field rather
|
|
100
|
+
* than a constructor: Angular's test build keeps a function for a
|
|
101
|
+
* constructor that nothing calls, which an app's coverage report would show
|
|
102
|
+
* as uncovered.
|
|
103
|
+
*
|
|
104
|
+
* `#hasBeenOpen` prevents the close animation from running on the initial
|
|
105
|
+
* effect evaluation when `open` starts as false. Without this guard, the
|
|
106
|
+
* first run would call #doClose() and emit `closed` before the drawer
|
|
107
|
+
* has ever been opened.
|
|
108
|
+
*/
|
|
109
|
+
protected readonly openAndClose = effect(() => {
|
|
110
|
+
const isOpen = this.open();
|
|
111
|
+
if (isOpen) {
|
|
112
|
+
this.#hasBeenOpen = true;
|
|
113
|
+
this.#doOpen();
|
|
114
|
+
} else if (this.#hasBeenOpen) {
|
|
115
|
+
this.#doClose();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
109
118
|
|
|
110
119
|
protected readonly panelClass = computed(() =>
|
|
111
120
|
cn(
|
|
@@ -192,9 +201,8 @@ export class UiNavDrawer implements OnDestroy {
|
|
|
192
201
|
}
|
|
193
202
|
|
|
194
203
|
#animateIn(): void {
|
|
195
|
-
const backdrop = this.backdropRef()
|
|
196
|
-
const panel = this.panelRef()
|
|
197
|
-
if (!backdrop || !panel) return;
|
|
204
|
+
const backdrop = this.backdropRef().nativeElement;
|
|
205
|
+
const panel = this.panelRef().nativeElement;
|
|
198
206
|
|
|
199
207
|
this.#backdropAnim?.cancel();
|
|
200
208
|
this.#panelAnim?.cancel();
|
|
@@ -213,9 +221,8 @@ export class UiNavDrawer implements OnDestroy {
|
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
#animateOut(): void {
|
|
216
|
-
const backdrop = this.backdropRef()
|
|
217
|
-
const panel = this.panelRef()
|
|
218
|
-
if (!backdrop || !panel) return;
|
|
224
|
+
const backdrop = this.backdropRef().nativeElement;
|
|
225
|
+
const panel = this.panelRef().nativeElement;
|
|
219
226
|
|
|
220
227
|
this.#backdropAnim?.cancel();
|
|
221
228
|
this.#panelAnim?.cancel();
|
|
@@ -258,6 +265,13 @@ export class UiNavDrawerHeader {
|
|
|
258
265
|
);
|
|
259
266
|
}
|
|
260
267
|
|
|
268
|
+
/**
|
|
269
|
+
* `UiNavDrawerItem`, for `contentChildren()`. A call rather than the class
|
|
270
|
+
* itself: for a class, Angular's test build keeps an arrow function that
|
|
271
|
+
* nothing calls, which an app's coverage report would show as uncovered.
|
|
272
|
+
*/
|
|
273
|
+
const navDrawerItem = () => UiNavDrawerItem;
|
|
274
|
+
|
|
261
275
|
@Component({
|
|
262
276
|
selector: 'ui-nav-drawer-content',
|
|
263
277
|
imports: [LYNX_ELEMENTS],
|
|
@@ -314,27 +328,29 @@ export class UiNavDrawerContent {
|
|
|
314
328
|
// Every nav item projected into this section (Home + each group item). Used to
|
|
315
329
|
// find the active one when the drawer opens. `descendants: true` reaches items
|
|
316
330
|
// nested inside the @for group wrappers, not just the direct content children.
|
|
317
|
-
readonly items = contentChildren(
|
|
331
|
+
readonly items = contentChildren(navDrawerItem(), { descendants: true });
|
|
318
332
|
|
|
319
333
|
protected readonly contentClass = computed(() =>
|
|
320
334
|
cn('h-full w-full', this.userClass()),
|
|
321
335
|
);
|
|
322
336
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
337
|
+
/**
|
|
338
|
+
* When the drawer opens, land the user on their current route instead of the
|
|
339
|
+
* top of a potentially long nav list.
|
|
340
|
+
*
|
|
341
|
+
* The <overlay> is display:none until open, so the scroll-view has no
|
|
342
|
+
* laid-out child frames until it's shown — defer a tick so the just-shown
|
|
343
|
+
* content is measured before we read it.
|
|
344
|
+
*
|
|
345
|
+
* This open-driven path is the fallback: it covers the web preview (where
|
|
346
|
+
* contentsizechanged isn't emitted) and re-opens that don't trigger a
|
|
347
|
+
* relayout. On device, onContentLayout() below is the precise trigger. Both
|
|
348
|
+
* call the same instant scroll, so firing twice is a harmless no-op.
|
|
349
|
+
*/
|
|
350
|
+
protected readonly revealActiveOnOpen = effect(() => {
|
|
351
|
+
if (!this.#drawer.open()) return;
|
|
352
|
+
setTimeout(() => this.#scrollActiveIntoView(), 0);
|
|
353
|
+
});
|
|
338
354
|
|
|
339
355
|
/**
|
|
340
356
|
* Fired by the scroll-view once its content finishes layout. Gated on the
|
|
@@ -347,30 +363,31 @@ export class UiNavDrawerContent {
|
|
|
347
363
|
|
|
348
364
|
#scrollActiveIntoView(): void {
|
|
349
365
|
this.items()
|
|
350
|
-
/* v8 ignore start -- .find predicate unreachable when no content-projected items */
|
|
351
366
|
.find((item) => item.active())
|
|
352
|
-
/* v8 ignore stop */
|
|
353
367
|
?.scrollIntoView();
|
|
354
368
|
}
|
|
355
369
|
}
|
|
356
370
|
|
|
371
|
+
/**
|
|
372
|
+
* The color icons inside an item draw with: the destructive color in a
|
|
373
|
+
* destructive item. A function of its own rather than written inline in
|
|
374
|
+
* `providers`: Angular's test build keeps a second copy of an inline factory
|
|
375
|
+
* that nothing calls, which an app's coverage report would show as uncovered.
|
|
376
|
+
*/
|
|
377
|
+
const itemIconColor = () => {
|
|
378
|
+
const item = inject(UiNavDrawerItem);
|
|
379
|
+
return computed(() =>
|
|
380
|
+
item.variant() === 'destructive' ? 'var(--destructive)' : undefined,
|
|
381
|
+
);
|
|
382
|
+
};
|
|
383
|
+
|
|
357
384
|
@Component({
|
|
358
385
|
selector: 'ui-nav-drawer-item',
|
|
359
386
|
imports: [LYNX_ELEMENTS],
|
|
360
387
|
encapsulation: ViewEncapsulation.None,
|
|
361
388
|
// Icons projected into the item (see the variant below) inject this through
|
|
362
389
|
// the item's element injector, which content projection keeps them under.
|
|
363
|
-
providers: [
|
|
364
|
-
{
|
|
365
|
-
provide: UI_ICON_COLOR,
|
|
366
|
-
useFactory: () => {
|
|
367
|
-
const item = inject(UiNavDrawerItem);
|
|
368
|
-
return computed(() =>
|
|
369
|
-
item.variant() === 'destructive' ? 'var(--destructive)' : undefined,
|
|
370
|
-
);
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
],
|
|
390
|
+
providers: [{ provide: UI_ICON_COLOR, useFactory: itemIconColor }],
|
|
374
391
|
template: `
|
|
375
392
|
<view
|
|
376
393
|
#container
|
|
@@ -391,8 +408,8 @@ export class UiNavDrawerItem {
|
|
|
391
408
|
readonly #drawer = inject(UiNavDrawer);
|
|
392
409
|
|
|
393
410
|
readonly variant = input<'default' | 'destructive'>('default');
|
|
394
|
-
readonly active = input(false);
|
|
395
|
-
readonly autoClose = input(true);
|
|
411
|
+
readonly active = input(false, { transform: booleanAttribute });
|
|
412
|
+
readonly autoClose = input(true, { transform: booleanAttribute });
|
|
396
413
|
readonly userClass = input<string>('', { alias: 'class' });
|
|
397
414
|
|
|
398
415
|
readonly pressed = output<void>();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UiNavigationBar } from './navigation-bar';
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { Location, LocationStrategy } from '@angular/common';
|
|
2
|
+
import { MockLocationStrategy } from '@angular/common/testing';
|
|
3
|
+
import { Component, type Provider, signal, viewChild } from '@angular/core';
|
|
4
|
+
import { Router, RouterOutlet, type Routes } from '@angular/router';
|
|
5
|
+
import {
|
|
6
|
+
fireEvent,
|
|
7
|
+
render,
|
|
8
|
+
waitForUpdate,
|
|
9
|
+
} from '@blotch/angular-lynx-testing-library';
|
|
10
|
+
import {
|
|
11
|
+
LYNX_ELEMENTS,
|
|
12
|
+
LynxAccessibilitySettings,
|
|
13
|
+
LynxSystemInfo,
|
|
14
|
+
provideRouter,
|
|
15
|
+
} from '@blotch/angular-lynx';
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
17
|
+
import { provideGlass } from '../glass/glass-design';
|
|
18
|
+
import { UiBarScroll, UiBarState } from '../toolbar/bar-scroll';
|
|
19
|
+
import { UiToolbarItem } from '../toolbar/toolbar';
|
|
20
|
+
import { UiNavigationBar } from './navigation-bar';
|
|
21
|
+
|
|
22
|
+
@Component({
|
|
23
|
+
selector: 'notes-page',
|
|
24
|
+
imports: [LYNX_ELEMENTS],
|
|
25
|
+
template: `<text>Notes page</text>`,
|
|
26
|
+
})
|
|
27
|
+
class NotesPage {}
|
|
28
|
+
|
|
29
|
+
@Component({
|
|
30
|
+
selector: 'note-page',
|
|
31
|
+
imports: [LYNX_ELEMENTS],
|
|
32
|
+
template: `<text>Note page</text>`,
|
|
33
|
+
})
|
|
34
|
+
class NotePage {}
|
|
35
|
+
|
|
36
|
+
const routes: Routes = [
|
|
37
|
+
{ path: '', component: NotesPage },
|
|
38
|
+
{ path: 'note', component: NotePage },
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
@Component({
|
|
42
|
+
imports: [
|
|
43
|
+
LYNX_ELEMENTS,
|
|
44
|
+
RouterOutlet,
|
|
45
|
+
UiBarScroll,
|
|
46
|
+
UiNavigationBar,
|
|
47
|
+
UiToolbarItem,
|
|
48
|
+
],
|
|
49
|
+
template: `
|
|
50
|
+
@if (scroll()) {
|
|
51
|
+
<scroll-view ui-bar-scroll id="scroll" scroll-orientation="vertical">
|
|
52
|
+
<view style="height: 2000px"></view>
|
|
53
|
+
</scroll-view>
|
|
54
|
+
}
|
|
55
|
+
<ui-navigation-bar
|
|
56
|
+
id="bar"
|
|
57
|
+
[title]="title()"
|
|
58
|
+
[largeTitle]="largeTitle()"
|
|
59
|
+
[titleCentered]="titleCentered()"
|
|
60
|
+
[backLabel]="backLabel()"
|
|
61
|
+
[backRoute]="backRoute()"
|
|
62
|
+
[backButtonHidden]="backButtonHidden()"
|
|
63
|
+
[class]="barClass()"
|
|
64
|
+
>
|
|
65
|
+
<ui-toolbar-item leading label="Edit" />
|
|
66
|
+
<ui-toolbar-item icon="plus" label="New note" />
|
|
67
|
+
</ui-navigation-bar>
|
|
68
|
+
<router-outlet />
|
|
69
|
+
`,
|
|
70
|
+
})
|
|
71
|
+
class BarHost {
|
|
72
|
+
readonly title = signal('Notes');
|
|
73
|
+
readonly largeTitle = signal(false);
|
|
74
|
+
readonly titleCentered = signal(false);
|
|
75
|
+
readonly backLabel = signal('Back');
|
|
76
|
+
readonly backRoute = signal<string | undefined>(undefined);
|
|
77
|
+
readonly backButtonHidden = signal(false);
|
|
78
|
+
readonly barClass = signal('');
|
|
79
|
+
readonly scroll = signal(false);
|
|
80
|
+
readonly bar = viewChild.required(UiNavigationBar);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Accessibility settings a test can change, as the host app would. */
|
|
84
|
+
const settings = {
|
|
85
|
+
reduceMotion: signal(false),
|
|
86
|
+
reduceTransparency: signal(false),
|
|
87
|
+
increaseContrast: signal(false),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const IOS_26 = { platform: 'iOS', osVersion: '26.0' };
|
|
91
|
+
const IOS_18 = { platform: 'iOS', osVersion: '18.5' };
|
|
92
|
+
const ANDROID = { platform: 'Android', osVersion: '15' };
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Runs change detection until `done` holds: a navigation takes a few ticks.
|
|
96
|
+
*/
|
|
97
|
+
const until = async (done: () => boolean): Promise<void> => {
|
|
98
|
+
for (let i = 0; i < 20 && !done(); i++) {
|
|
99
|
+
await waitForUpdate();
|
|
100
|
+
}
|
|
101
|
+
expect(done()).toBe(true);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Renders the bar on a platform, with the router unless `router` is false.
|
|
106
|
+
* The platform is provided rather than stubbed as a global: the router's
|
|
107
|
+
* setup switches the testing library's threads, which puts back its own
|
|
108
|
+
* SystemInfo before the bar reads it.
|
|
109
|
+
*/
|
|
110
|
+
const renderBar = async (
|
|
111
|
+
platform: { platform: string; osVersion: string },
|
|
112
|
+
providers: Provider[] = [],
|
|
113
|
+
) => {
|
|
114
|
+
const result = await render(BarHost, {
|
|
115
|
+
providers: [
|
|
116
|
+
provideRouter(routes),
|
|
117
|
+
{ provide: LynxSystemInfo, useValue: platform },
|
|
118
|
+
{ provide: LynxAccessibilitySettings, useValue: settings },
|
|
119
|
+
...providers,
|
|
120
|
+
],
|
|
121
|
+
});
|
|
122
|
+
const host = result.componentRef.instance as BarHost;
|
|
123
|
+
const injector = result.componentRef.injector;
|
|
124
|
+
const state = injector.get(UiBarState);
|
|
125
|
+
const { container } = result;
|
|
126
|
+
const bar = () => container.querySelector<HTMLElement>('#bar')!;
|
|
127
|
+
const row = () =>
|
|
128
|
+
Array.from(bar().children).find((child) =>
|
|
129
|
+
child.className.includes('justify-between'),
|
|
130
|
+
) as HTMLElement;
|
|
131
|
+
/**
|
|
132
|
+
* The bar's two ends: the views holding Edit and New note.
|
|
133
|
+
*/
|
|
134
|
+
const leading = () =>
|
|
135
|
+
result.getByRole('button', { name: 'Edit' }).parentElement!;
|
|
136
|
+
const trailing = () =>
|
|
137
|
+
result.getByRole('button', { name: 'New note' }).parentElement!;
|
|
138
|
+
const title = () => result.queryByRole('heading', { name: host.title() });
|
|
139
|
+
/**
|
|
140
|
+
* The view holding the bar's title, not the copy that measures it.
|
|
141
|
+
*/
|
|
142
|
+
const titleBox = () =>
|
|
143
|
+
Array.from(row().children).find(
|
|
144
|
+
(child) =>
|
|
145
|
+
child.className.includes('flex-1') ||
|
|
146
|
+
child.className.includes('bottom-0'),
|
|
147
|
+
) as HTMLElement;
|
|
148
|
+
const back = () => result.queryByRole('button', { name: host.backLabel() });
|
|
149
|
+
const page = () =>
|
|
150
|
+
Array.from(container.querySelectorAll('text'))
|
|
151
|
+
.map((text) => text.textContent ?? '')
|
|
152
|
+
.find((text) => text.endsWith(' page'));
|
|
153
|
+
const update = async (change: () => void) => {
|
|
154
|
+
change();
|
|
155
|
+
await waitForUpdate();
|
|
156
|
+
};
|
|
157
|
+
await until(() => page() === 'Notes page');
|
|
158
|
+
return {
|
|
159
|
+
...result,
|
|
160
|
+
host,
|
|
161
|
+
injector,
|
|
162
|
+
state,
|
|
163
|
+
bar,
|
|
164
|
+
row,
|
|
165
|
+
leading,
|
|
166
|
+
trailing,
|
|
167
|
+
title,
|
|
168
|
+
titleBox,
|
|
169
|
+
back,
|
|
170
|
+
page,
|
|
171
|
+
update,
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const style = (element: HTMLElement, name: string) =>
|
|
176
|
+
element.style.getPropertyValue(name);
|
|
177
|
+
|
|
178
|
+
// The components' selectors aren't Lynx elements, so the renderer falls back
|
|
179
|
+
// to a <view> for each and warns.
|
|
180
|
+
beforeEach(() => {
|
|
181
|
+
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
afterEach(() => {
|
|
185
|
+
vi.restoreAllMocks();
|
|
186
|
+
settings.reduceMotion.set(false);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('UiNavigationBar on iOS 26 and later', () => {
|
|
190
|
+
it('floats over the top of the content below the status bar, with no background', async () => {
|
|
191
|
+
const { bar, state, row } = await renderBar(IOS_26);
|
|
192
|
+
expect(bar().classList).toContain('absolute');
|
|
193
|
+
expect(bar().classList).toContain('top-0');
|
|
194
|
+
expect(bar().classList).toContain('pt-safe');
|
|
195
|
+
expect(bar().classList).not.toContain('bg-background');
|
|
196
|
+
expect(bar().querySelector('[shape="rounded"]')).toBeNull();
|
|
197
|
+
expect(row().className).toContain('h-[44px]');
|
|
198
|
+
expect(style(row(), 'padding-left')).toBe('16px');
|
|
199
|
+
// It tells the scroll view under it how much room it takes.
|
|
200
|
+
expect(state.topBar()?.height()).toBe(44);
|
|
201
|
+
expect(state.topBar()?.largeTitle()).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('shows the title as a heading, with the leading items after the Back button and the rest at the end', async () => {
|
|
205
|
+
const { title, leading, trailing, row, host, update } =
|
|
206
|
+
await renderBar(IOS_26);
|
|
207
|
+
expect(title()?.className).toContain('font-semibold');
|
|
208
|
+
expect(title()?.className).toContain('text-center');
|
|
209
|
+
const ends = Array.from(row().children);
|
|
210
|
+
expect(ends.indexOf(leading())).toBeLessThan(ends.indexOf(trailing()));
|
|
211
|
+
|
|
212
|
+
await update(() => host.title.set('Inbox'));
|
|
213
|
+
expect(title()?.textContent).toBe('Inbox');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('centers the title on the screen when it fits, or between the ends when it doesn’t', async () => {
|
|
217
|
+
const { row, leading, trailing, titleBox, container } =
|
|
218
|
+
await renderBar(IOS_26);
|
|
219
|
+
const copy = () =>
|
|
220
|
+
container.querySelector<HTMLElement>(
|
|
221
|
+
'[accessibility-elements-hidden="true"] text',
|
|
222
|
+
)!;
|
|
223
|
+
const lay = async (element: Element, width: number) => {
|
|
224
|
+
fireEvent.layoutchange(element, { detail: { width, height: 44 } });
|
|
225
|
+
await waitForUpdate();
|
|
226
|
+
};
|
|
227
|
+
await lay(row(), 402);
|
|
228
|
+
await lay(leading(), 44);
|
|
229
|
+
await lay(trailing(), 100);
|
|
230
|
+
await lay(copy(), 80);
|
|
231
|
+
// Clear of the wider end on both sides: 16 + 100 + 8.
|
|
232
|
+
expect(style(titleBox(), 'left')).toBe('124px');
|
|
233
|
+
expect(style(titleBox(), 'right')).toBe('124px');
|
|
234
|
+
|
|
235
|
+
// Too wide for that: between the ends.
|
|
236
|
+
await lay(copy(), 200);
|
|
237
|
+
expect(style(titleBox(), 'left')).toBe('68px');
|
|
238
|
+
expect(style(titleBox(), 'right')).toBe('124px');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('shows a Back button while there’s a screen to go back to, and goes back through the router', async () => {
|
|
242
|
+
const { back, injector, page, host, update } = await renderBar(IOS_26);
|
|
243
|
+
expect(back()).toBeNull();
|
|
244
|
+
|
|
245
|
+
await injector.get(Router).navigateByUrl('/note');
|
|
246
|
+
await until(() => page() === 'Note page');
|
|
247
|
+
await update(() => host.backLabel.set('Notes'));
|
|
248
|
+
expect(back()).not.toBeNull();
|
|
249
|
+
expect(host.bar().canGoBack()).toBe(true);
|
|
250
|
+
|
|
251
|
+
fireEvent.tap(back()!);
|
|
252
|
+
await until(() => page() === 'Notes page');
|
|
253
|
+
expect(back()).toBeNull();
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('hides the Back button on a screen that asks', async () => {
|
|
257
|
+
const { back, injector, page, host, update } = await renderBar(IOS_26);
|
|
258
|
+
await injector.get(Router).navigateByUrl('/note');
|
|
259
|
+
await until(() => page() === 'Note page');
|
|
260
|
+
await update(() => host.backButtonHidden.set(true));
|
|
261
|
+
expect(back()).toBeNull();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('goes to backRoute in place of the screen when there’s no history', async () => {
|
|
265
|
+
const { back, injector, page, host, update } = await renderBar(IOS_26);
|
|
266
|
+
await injector.get(Router).navigateByUrl('/note', { replaceUrl: true });
|
|
267
|
+
await until(() => page() === 'Note page');
|
|
268
|
+
expect(back()).toBeNull();
|
|
269
|
+
|
|
270
|
+
await update(() => host.backRoute.set('/'));
|
|
271
|
+
fireEvent.tap(back()!);
|
|
272
|
+
await until(() => page() === 'Notes page');
|
|
273
|
+
// It replaced the note: there's still nothing to go back to.
|
|
274
|
+
expect(host.bar().canGoBack()).toBe(false);
|
|
275
|
+
expect(injector.get(Location).path()).toBe('');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('does nothing on goBack() with nowhere to go', async () => {
|
|
279
|
+
const { host, page } = await renderBar(IOS_26);
|
|
280
|
+
host.bar().goBack();
|
|
281
|
+
await waitForUpdate();
|
|
282
|
+
expect(page()).toBe('Notes page');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('starts the title large at the top of the content, and shows it once it has scrolled under', async () => {
|
|
286
|
+
const { host, state, update, title, titleBox, getAllByRole } =
|
|
287
|
+
await renderBar(IOS_26);
|
|
288
|
+
await update(() => host.largeTitle.set(true));
|
|
289
|
+
// Without a scroll view there's no large title, so the bar keeps it.
|
|
290
|
+
expect(title()).not.toBeNull();
|
|
291
|
+
expect(state.topBar()?.largeTitle()).toBe('Notes');
|
|
292
|
+
|
|
293
|
+
await update(() => host.scroll.set(true));
|
|
294
|
+
// The scroll view draws the large title; the bar's hides.
|
|
295
|
+
expect(getAllByRole('heading', { name: 'Notes' })).toHaveLength(1);
|
|
296
|
+
expect(style(titleBox(), 'opacity')).toBe('0');
|
|
297
|
+
expect(style(titleBox(), 'transition')).toContain('opacity');
|
|
298
|
+
|
|
299
|
+
await update(() => state.largeTitleUnder.set(true));
|
|
300
|
+
expect(getAllByRole('heading', { name: 'Notes' })).toHaveLength(2);
|
|
301
|
+
expect(style(titleBox(), 'opacity')).toBe('1');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('shows the title at once under Reduce Motion', async () => {
|
|
305
|
+
const { host, update, titleBox } = await renderBar(IOS_26);
|
|
306
|
+
await update(() => {
|
|
307
|
+
host.largeTitle.set(true);
|
|
308
|
+
host.scroll.set(true);
|
|
309
|
+
settings.reduceMotion.set(true);
|
|
310
|
+
});
|
|
311
|
+
expect(style(titleBox(), 'transition')).toBe('none');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('takes extra classes', async () => {
|
|
315
|
+
const { host, update, bar } = await renderBar(IOS_26);
|
|
316
|
+
await update(() => host.barClass.set('z-10'));
|
|
317
|
+
expect(bar().classList).toContain('z-10');
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe('UiNavigationBar before iOS 26', () => {
|
|
322
|
+
it("is clear at the top of the content, and on the bars' chrome material with a hairline once content scrolls under it", async () => {
|
|
323
|
+
const { bar, state, update, row } = await renderBar(IOS_18);
|
|
324
|
+
const material = () => bar().querySelector('[shape="rounded"]');
|
|
325
|
+
expect(material()).toBeNull();
|
|
326
|
+
expect(style(row(), 'padding-left')).toBe('8px');
|
|
327
|
+
|
|
328
|
+
await update(() => state.scrolledUnderTop.set(true));
|
|
329
|
+
expect(material()?.getAttribute('blur-material')).toBe('chrome');
|
|
330
|
+
expect(material()?.getAttribute('blur-effect')).toBe('extra-light');
|
|
331
|
+
expect(bar().querySelector('.h-\\[0\\.5px\\]')).not.toBeNull();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('shows the previous screen’s title beside the Back button’s chevron', async () => {
|
|
335
|
+
const { back, injector, page, host, update } = await renderBar(IOS_18);
|
|
336
|
+
await injector.get(Router).navigateByUrl('/note');
|
|
337
|
+
await until(() => page() === 'Note page');
|
|
338
|
+
await update(() => host.backLabel.set('Notes'));
|
|
339
|
+
expect(back()?.querySelector('text')?.textContent).toBe('Notes');
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe('UiNavigationBar on Android', () => {
|
|
344
|
+
it('is Material 3’s small top app bar, the title at the start', async () => {
|
|
345
|
+
const { bar, row, state, title, titleBox, leading, trailing, container } =
|
|
346
|
+
await renderBar(ANDROID);
|
|
347
|
+
expect(row().className).toContain('h-[64px]');
|
|
348
|
+
expect(state.topBar()?.height()).toBe(64);
|
|
349
|
+
expect(bar().classList).toContain('bg-background');
|
|
350
|
+
expect(title()?.className).toContain('text-[22px]');
|
|
351
|
+
expect(title()?.className).not.toContain('text-center');
|
|
352
|
+
expect(titleBox().className).toContain('flex-1');
|
|
353
|
+
expect(leading().className).toContain('min-w-[12px]');
|
|
354
|
+
expect(trailing().className).toContain('pr-[4px]');
|
|
355
|
+
// Laid out in the row, so it measures no copy.
|
|
356
|
+
expect(
|
|
357
|
+
container.querySelector('[accessibility-elements-hidden="true"]'),
|
|
358
|
+
).toBeNull();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('centers the title in the center-aligned app bar', async () => {
|
|
362
|
+
const { host, update, title, titleBox, leading } = await renderBar(ANDROID);
|
|
363
|
+
await update(() => host.titleCentered.set(true));
|
|
364
|
+
expect(title()?.className).toContain('text-center');
|
|
365
|
+
expect(titleBox().className).toContain('top-0');
|
|
366
|
+
expect(leading().className).not.toContain('min-w-[12px]');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('turns to the surface container color as content scrolls under it', async () => {
|
|
370
|
+
const { bar, state, update } = await renderBar(ANDROID);
|
|
371
|
+
expect(style(bar(), 'transition')).toContain('background-color');
|
|
372
|
+
await update(() => state.scrolledUnderTop.set(true));
|
|
373
|
+
expect(bar().classList).toContain('bg-secondary');
|
|
374
|
+
|
|
375
|
+
await update(() => settings.reduceMotion.set(true));
|
|
376
|
+
expect(style(bar(), 'transition')).toBe('');
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* A bar in an app without the router.
|
|
382
|
+
*/
|
|
383
|
+
@Component({
|
|
384
|
+
imports: [LYNX_ELEMENTS, UiNavigationBar],
|
|
385
|
+
template: `<ui-navigation-bar title="Settings" backRoute="/" />`,
|
|
386
|
+
})
|
|
387
|
+
class PlainBarHost {
|
|
388
|
+
readonly bar = viewChild.required(UiNavigationBar);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
describe('UiNavigationBar with glass on Android', () => {
|
|
392
|
+
it('fades the content beneath it itself, as Android’s scroll view takes no mask', async () => {
|
|
393
|
+
const { bar } = await renderBar(ANDROID, [
|
|
394
|
+
provideGlass({ android: 'glass' }),
|
|
395
|
+
]);
|
|
396
|
+
const fade = style(bar(), 'background-image');
|
|
397
|
+
expect(fade).toContain('linear-gradient(to bottom');
|
|
398
|
+
expect(fade).toContain('var(--background-rgb)');
|
|
399
|
+
// It isn't Material's bar.
|
|
400
|
+
expect(bar().classList).not.toContain('bg-background');
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe('UiNavigationBar without the Lynx router', () => {
|
|
405
|
+
it('has no history to go back through without the router', async () => {
|
|
406
|
+
const result = await render(PlainBarHost, {
|
|
407
|
+
providers: [
|
|
408
|
+
{ provide: LynxSystemInfo, useValue: IOS_26 },
|
|
409
|
+
{ provide: LynxAccessibilitySettings, useValue: settings },
|
|
410
|
+
],
|
|
411
|
+
});
|
|
412
|
+
const host = result.componentRef.instance as PlainBarHost;
|
|
413
|
+
expect(host.bar().canGoBack()).toBe(false);
|
|
414
|
+
// backRoute still shows the button, for an app with routes of its own.
|
|
415
|
+
expect(result.getByRole('button', { name: 'Back' })).toBeTruthy();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('has no Back button with a LocationStrategy of the app’s own', async () => {
|
|
419
|
+
const { back, host } = await renderBar(IOS_26, [
|
|
420
|
+
{ provide: LocationStrategy, useClass: MockLocationStrategy },
|
|
421
|
+
]);
|
|
422
|
+
expect(back()).toBeNull();
|
|
423
|
+
expect(host.bar().canGoBack()).toBe(false);
|
|
424
|
+
});
|
|
425
|
+
});
|