@blotch/dolan 22.2.4 → 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.test.js +2 -0
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/doctor.js +78 -21
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +167 -26
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/init.test.js +4 -7
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/update.d.ts +3 -1
- package/dist/commands/update.js +78 -15
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +257 -2
- package/dist/commands/update.test.js.map +1 -1
- package/dist/lockfile.d.ts +10 -0
- package/dist/lockfile.js +20 -1
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.js +43 -3
- package/dist/lockfile.test.js.map +1 -1
- package/dist/registry.js +73 -11
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +16 -3
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +24 -2
- package/dist/test-utils.js +41 -2
- package/dist/test-utils.js.map +1 -1
- package/dist/ui/boolean-inputs.test.ts +76 -0
- package/dist/ui/components/action-sheet/action-sheet.test.ts +370 -1
- package/dist/ui/components/action-sheet/action-sheet.ts +306 -28
- package/dist/ui/components/action-sheet/index.ts +1 -0
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +552 -8
- package/dist/ui/components/alert-dialog/alert-dialog.ts +644 -36
- package/dist/ui/components/alert-dialog/index.ts +5 -0
- package/dist/ui/components/area-chart/area-chart.ts +3 -2
- package/dist/ui/components/badge/badge.ts +2 -1
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +968 -3
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1021 -95
- package/dist/ui/components/bottom-sheet/index.ts +7 -1
- package/dist/ui/components/button/button.test.ts +36 -1
- package/dist/ui/components/button/button.ts +17 -7
- package/dist/ui/components/button-group/button-group.test.ts +53 -9
- package/dist/ui/components/button-group/button-group.ts +67 -21
- package/dist/ui/components/card/card.test.ts +16 -0
- package/dist/ui/components/card/card.ts +2 -1
- package/dist/ui/components/chart/chart-presets.ts +6 -6
- package/dist/ui/components/chart/chart.test.ts +14 -0
- package/dist/ui/components/chart/chart.ts +10 -3
- package/dist/ui/components/checkbox/checkbox.ts +2 -1
- package/dist/ui/components/collapsible/collapsible.ts +2 -1
- package/dist/ui/components/dialog/dialog.ts +6 -6
- package/dist/ui/components/dock/dock-scroll.ts +9 -0
- package/dist/ui/components/dock/dock-search.test.ts +594 -0
- package/dist/ui/components/dock/dock.test.ts +4 -1
- package/dist/ui/components/dock/dock.ts +529 -51
- 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/glass/glass.test.ts +39 -1
- package/dist/ui/components/glass/glass.ts +87 -17
- package/dist/ui/components/glass/index.ts +1 -0
- package/dist/ui/components/icon/icon-theme.test.ts +28 -0
- package/dist/ui/components/icon/icon-theme.ts +38 -7
- package/dist/ui/components/input/input.ts +2 -1
- package/dist/ui/components/label/label.ts +8 -2
- package/dist/ui/components/line-chart/line-chart.ts +3 -2
- package/dist/ui/components/list/list.ts +5 -4
- package/dist/ui/components/menu/menu-popup.test.ts +18 -14
- package/dist/ui/components/menu/menu-popup.ts +24 -3
- package/dist/ui/components/menu/menu.test.ts +99 -5
- package/dist/ui/components/menu/menu.ts +74 -34
- package/dist/ui/components/nav-drawer/nav-drawer.ts +3 -2
- 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/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.ts +2 -1
- package/dist/ui/components/radio-group/radio-group.ts +2 -1
- 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.ts +2 -1
- package/dist/ui/components/sheet/sheet.test.ts +90 -3
- package/dist/ui/components/sheet/sheet.ts +59 -4
- 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/switch/index.ts +1 -1
- package/dist/ui/components/switch/switch.test.ts +464 -48
- package/dist/ui/components/switch/switch.ts +613 -43
- package/dist/ui/components/tabs/index.ts +7 -1
- package/dist/ui/components/tabs/tabs.test.ts +278 -2
- package/dist/ui/components/tabs/tabs.ts +233 -30
- package/dist/ui/components/textarea/textarea.ts +2 -1
- package/dist/ui/components/toggle/index.ts +1 -0
- package/dist/ui/components/toggle/toggle.test.ts +310 -4
- 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/utils/analyze.d.ts +10 -5
- package/dist/utils/analyze.js +3 -2
- package/dist/utils/analyze.js.map +1 -1
- package/dist/utils/installed.d.ts +13 -1
- package/dist/utils/installed.js +17 -0
- package/dist/utils/installed.js.map +1 -1
- package/dist/utils/installed.test.js +39 -1
- package/dist/utils/installed.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +25 -5
- package/dist/utils/tailwind.js +53 -14
- package/dist/utils/tailwind.js.map +1 -1
- package/dist/utils/tailwind.test.js +85 -22
- package/dist/utils/tailwind.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { Location, LocationStrategy } from '@angular/common';
|
|
2
|
+
import {
|
|
3
|
+
Component,
|
|
4
|
+
DestroyRef,
|
|
5
|
+
Injector,
|
|
6
|
+
ViewEncapsulation,
|
|
7
|
+
booleanAttribute,
|
|
8
|
+
computed,
|
|
9
|
+
inject,
|
|
10
|
+
input,
|
|
11
|
+
signal,
|
|
12
|
+
} from '@angular/core';
|
|
13
|
+
import { ROUTES, Router } from '@angular/router';
|
|
14
|
+
import {
|
|
15
|
+
LYNX_ELEMENTS,
|
|
16
|
+
LynxAccessibilitySettings,
|
|
17
|
+
LynxLocationStrategy,
|
|
18
|
+
} from '@blotch/angular-lynx';
|
|
19
|
+
|
|
20
|
+
import { DURATION, EASING } from '../../utils/animate';
|
|
21
|
+
import { cn } from '../../utils/cn';
|
|
22
|
+
import { UiGlass } from '../glass/glass';
|
|
23
|
+
import { type GlassDesign, injectGlassDesign } from '../glass/glass-design';
|
|
24
|
+
import { edgeFadeOverlay, platformOf, UiBarState } from '../toolbar/bar-scroll';
|
|
25
|
+
import { UiToolbarItem } from '../toolbar/toolbar';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The bar's height below the status bar, in px: 44pt on iOS, with and without
|
|
29
|
+
* Liquid Glass, and Material 3's 64dp small and center-aligned top app bars.
|
|
30
|
+
*/
|
|
31
|
+
const BAR_HEIGHT: Readonly<Record<GlassDesign, number>> = {
|
|
32
|
+
glass: 44,
|
|
33
|
+
frosted: 44,
|
|
34
|
+
material: 64,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The row's padding at each end, in px: iOS 26's bars float their controls
|
|
39
|
+
* 16pt in, the bars before it 8pt, and Material's top app bar has its icon
|
|
40
|
+
* buttons' 4dp in each end's own padding.
|
|
41
|
+
*/
|
|
42
|
+
const ROW_PADDING: Readonly<Record<GlassDesign, number>> = {
|
|
43
|
+
glass: 16,
|
|
44
|
+
frosted: 8,
|
|
45
|
+
material: 0,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** The space between a centered title and the controls beside it, in px. */
|
|
49
|
+
const TITLE_GAP = 8;
|
|
50
|
+
|
|
51
|
+
/** What Lynx's `layoutchange` event carries, as far as the bar reads it. */
|
|
52
|
+
type LayoutChangeEvent = Event & { detail: { width: number } };
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* How the inline title comes in as the large title scrolls under the bar,
|
|
56
|
+
* and how Material's bar changes color as content scrolls under it.
|
|
57
|
+
*/
|
|
58
|
+
const TITLE_TRANSITION = `opacity ${DURATION.fast}ms ${EASING.standard}`;
|
|
59
|
+
const CONTAINER_TRANSITION = `background-color ${DURATION.normal}ms ${EASING.standard}`;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The router's history, when the app set the router up with `provideRouter()`
|
|
63
|
+
* from `@blotch/angular-lynx`, whose in-memory history says whether there's
|
|
64
|
+
* a screen to go back to. Without the router there's nothing to go back
|
|
65
|
+
* through, and its LocationStrategy is never created: on Lynx the browser's
|
|
66
|
+
* one it would fall back to can't be.
|
|
67
|
+
*/
|
|
68
|
+
const historyOf = (injector: Injector): LynxLocationStrategy | null => {
|
|
69
|
+
if (!injector.get(ROUTES, null)) return null;
|
|
70
|
+
const strategy = injector.get(LocationStrategy);
|
|
71
|
+
return strategy instanceof LynxLocationStrategy ? strategy : null;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The bar at the top of a screen: its title, a Back button while there's a
|
|
76
|
+
* screen to go back to, and the screen's actions, floating over the content,
|
|
77
|
+
* which scrolls beneath it.
|
|
78
|
+
*
|
|
79
|
+
* ```html
|
|
80
|
+
* <ui-navigation-bar title="Notes" largeTitle>
|
|
81
|
+
* <ui-toolbar-item leading label="Edit" (pressed)="edit()" />
|
|
82
|
+
* <ui-toolbar-group>
|
|
83
|
+
* <ui-toolbar-item icon="search" label="Search" (pressed)="search()" />
|
|
84
|
+
* <ui-toolbar-item icon="plus" label="New note" (pressed)="add()" />
|
|
85
|
+
* </ui-toolbar-group>
|
|
86
|
+
* </ui-navigation-bar>
|
|
87
|
+
* <scroll-view ui-bar-scroll scroll-orientation="vertical" class="h-full">
|
|
88
|
+
* …
|
|
89
|
+
* </scroll-view>
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* Items and groups go at the trailing end, or after the Back button with the
|
|
93
|
+
* `leading` attribute. Put `ui-bar-scroll` on the screen's scroll view: its
|
|
94
|
+
* content then starts below the bar and the status bar, the large title
|
|
95
|
+
* scrolls with it, and the bar follows it.
|
|
96
|
+
*
|
|
97
|
+
* Its form follows the platform (see GlassDesign):
|
|
98
|
+
*
|
|
99
|
+
* - `'glass'` (iOS 26 and later, the web, Android with the glass opt-in): no
|
|
100
|
+
* bar background. The items float in glass, grouped items share one
|
|
101
|
+
* capsule, the Back button is a glass circle with a chevron, and the title
|
|
102
|
+
* is plain text. Content fades out as it scrolls beneath them, iOS's
|
|
103
|
+
* scroll edge effect.
|
|
104
|
+
* - `'frosted'` (iOS 15 to 25): the navigation bar of that time, clear while
|
|
105
|
+
* the content is at its top and on the bars' chrome material with a
|
|
106
|
+
* hairline once content scrolls under it. Items and the Back button, a chevron with the
|
|
107
|
+
* previous screen's title, are in the tint color.
|
|
108
|
+
* - `'material'` (Android): Material 3's small top app bar, or center-aligned
|
|
109
|
+
* with `titleCentered`, whose container turns from the surface color to the
|
|
110
|
+
* surface container's as content scrolls under it. The Back button is its
|
|
111
|
+
* navigation icon, an arrow.
|
|
112
|
+
*
|
|
113
|
+
* With `largeTitle`, the title starts large at the top of the content, and
|
|
114
|
+
* the bar shows it once it has scrolled under: iOS's large title, and
|
|
115
|
+
* Material 3 Expressive's medium flexible app bar.
|
|
116
|
+
*/
|
|
117
|
+
@Component({
|
|
118
|
+
selector: 'ui-navigation-bar',
|
|
119
|
+
imports: [LYNX_ELEMENTS, UiGlass, UiToolbarItem],
|
|
120
|
+
encapsulation: ViewEncapsulation.None,
|
|
121
|
+
host: {
|
|
122
|
+
'[class]': 'hostClass()',
|
|
123
|
+
'[style.transition]': 'containerTransition()',
|
|
124
|
+
'[style.background-image]': 'edgeFade()',
|
|
125
|
+
},
|
|
126
|
+
// The frosted material, the bars' chrome, is added and removed rather
|
|
127
|
+
// than faded: a blur view under a fading opacity draws wrongly on iOS.
|
|
128
|
+
template: `
|
|
129
|
+
@if (frostedBackground()) {
|
|
130
|
+
<blur-view
|
|
131
|
+
ui-glass
|
|
132
|
+
shape="rounded"
|
|
133
|
+
[radius]="0"
|
|
134
|
+
material="chrome"
|
|
135
|
+
class="absolute bottom-0 left-0 right-0 top-0"
|
|
136
|
+
></blur-view>
|
|
137
|
+
<view class="h-[0.5px] bg-border absolute bottom-0 left-0 right-0"></view>
|
|
138
|
+
}
|
|
139
|
+
<view
|
|
140
|
+
[class]="rowClass()"
|
|
141
|
+
[style]="rowStyle()"
|
|
142
|
+
(bindlayoutchange)="measureRow($event)"
|
|
143
|
+
>
|
|
144
|
+
@if (!titleAtStart()) {
|
|
145
|
+
<!-- An unconstrained, invisible copy of the title, which measures
|
|
146
|
+
how wide it would be on one line. -->
|
|
147
|
+
<view
|
|
148
|
+
class="flex-row opacity-0 flex absolute left-0 top-0"
|
|
149
|
+
accessibility-elements-hidden="true"
|
|
150
|
+
>
|
|
151
|
+
<text
|
|
152
|
+
[class]="titleClass()"
|
|
153
|
+
(bindlayoutchange)="measureTitle($event)"
|
|
154
|
+
>{{ title() }}</text
|
|
155
|
+
>
|
|
156
|
+
</view>
|
|
157
|
+
}
|
|
158
|
+
<view
|
|
159
|
+
[class]="leadingClass()"
|
|
160
|
+
(bindlayoutchange)="measureLeading($event)"
|
|
161
|
+
>
|
|
162
|
+
@if (backShown()) {
|
|
163
|
+
<ui-toolbar-item back [label]="backLabel()" (pressed)="goBack()" />
|
|
164
|
+
}
|
|
165
|
+
<ng-content select="[leading]" />
|
|
166
|
+
</view>
|
|
167
|
+
<view
|
|
168
|
+
[class]="titleBoxClass()"
|
|
169
|
+
[style]="titleBoxStyle()"
|
|
170
|
+
[attr.accessibility-elements-hidden]="titleShown() ? null : true"
|
|
171
|
+
>
|
|
172
|
+
<text
|
|
173
|
+
[text-maxline]="1"
|
|
174
|
+
accessibility-traits="header"
|
|
175
|
+
[class]="titleClass()"
|
|
176
|
+
>{{ title() }}</text
|
|
177
|
+
>
|
|
178
|
+
</view>
|
|
179
|
+
<view
|
|
180
|
+
[class]="trailingClass()"
|
|
181
|
+
(bindlayoutchange)="measureTrailing($event)"
|
|
182
|
+
>
|
|
183
|
+
<ng-content />
|
|
184
|
+
</view>
|
|
185
|
+
</view>
|
|
186
|
+
`,
|
|
187
|
+
})
|
|
188
|
+
export class UiNavigationBar {
|
|
189
|
+
/** The screen's title: a word or a short phrase. */
|
|
190
|
+
readonly title = input('');
|
|
191
|
+
/**
|
|
192
|
+
* The title starts large at the top of the content, and moves into the bar
|
|
193
|
+
* as it scrolls under it. Needs `ui-bar-scroll` on the screen's scroll
|
|
194
|
+
* view, which draws it.
|
|
195
|
+
*/
|
|
196
|
+
readonly largeTitle = input(false, { transform: booleanAttribute });
|
|
197
|
+
/**
|
|
198
|
+
* Material's center-aligned top app bar, rather than the small one with
|
|
199
|
+
* the title at the start. iOS always centers the title in the bar.
|
|
200
|
+
*/
|
|
201
|
+
readonly titleCentered = input(false, { transform: booleanAttribute });
|
|
202
|
+
/**
|
|
203
|
+
* The previous screen's title, which the Back button shows beside its
|
|
204
|
+
* chevron before iOS 26, and which screen readers say for it everywhere.
|
|
205
|
+
*/
|
|
206
|
+
readonly backLabel = input('Back');
|
|
207
|
+
/**
|
|
208
|
+
* Where Back goes when there's no screen to go back to, such as a detail
|
|
209
|
+
* screen the app opened on: a URL for `Router.navigateByUrl()`, usually
|
|
210
|
+
* the screen above it. It replaces the current screen in the history.
|
|
211
|
+
* Without it, the Back button shows only while there's history.
|
|
212
|
+
*/
|
|
213
|
+
readonly backRoute = input<string | undefined>(undefined);
|
|
214
|
+
/**
|
|
215
|
+
* Hides the Back button, for a screen that's a starting point though
|
|
216
|
+
* there's history behind it, such as a tab's first screen.
|
|
217
|
+
*/
|
|
218
|
+
readonly backButtonHidden = input(false, { transform: booleanAttribute });
|
|
219
|
+
readonly userClass = input<string>('', { alias: 'class' });
|
|
220
|
+
|
|
221
|
+
readonly #state = inject(UiBarState);
|
|
222
|
+
readonly #settings = inject(LynxAccessibilitySettings);
|
|
223
|
+
readonly #injector = inject(Injector);
|
|
224
|
+
readonly #history = historyOf(this.#injector);
|
|
225
|
+
readonly #platform = platformOf(this.#injector);
|
|
226
|
+
|
|
227
|
+
/** The design on this platform (see GlassDesign), which the items follow. */
|
|
228
|
+
readonly design = injectGlassDesign();
|
|
229
|
+
|
|
230
|
+
readonly #material = computed(() => this.design() === 'material');
|
|
231
|
+
/** No fading or color changing under Reduce Motion. */
|
|
232
|
+
readonly #animate = computed(() => !this.#settings.reduceMotion());
|
|
233
|
+
|
|
234
|
+
/** The widths of the bar, its two ends and its title, from Lynx's layout. */
|
|
235
|
+
protected readonly rowWidth = signal(0);
|
|
236
|
+
protected readonly leadingWidth = signal(0);
|
|
237
|
+
protected readonly trailingWidth = signal(0);
|
|
238
|
+
protected readonly titleWidth = signal(0);
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Whether there's a screen to go back to in the router's history: there
|
|
242
|
+
* is after a navigation, and isn't on the first screen.
|
|
243
|
+
*/
|
|
244
|
+
readonly canGoBack = computed(() => this.#history?.canGoBack() ?? false);
|
|
245
|
+
|
|
246
|
+
protected readonly backShown = computed(
|
|
247
|
+
() => !this.backButtonHidden() && (this.canGoBack() || !!this.backRoute()),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// What the scroll view under the bar makes room for: the bar below the
|
|
251
|
+
// status bar, and the large title at the top of its content.
|
|
252
|
+
protected readonly attachment = this.#state.attach(
|
|
253
|
+
this.#state.topBar,
|
|
254
|
+
{
|
|
255
|
+
height: computed(() => BAR_HEIGHT[this.design()]),
|
|
256
|
+
largeTitle: computed(() => (this.largeTitle() ? this.title() : null)),
|
|
257
|
+
},
|
|
258
|
+
inject(DestroyRef),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The title shows in the bar unless the large title is showing at the top
|
|
263
|
+
* of the content: before it has scrolled under the bar. Without a scroll
|
|
264
|
+
* view there's no large title, so the bar shows the title.
|
|
265
|
+
*/
|
|
266
|
+
protected readonly titleShown = computed(
|
|
267
|
+
() =>
|
|
268
|
+
!this.largeTitle() ||
|
|
269
|
+
!this.#state.content() ||
|
|
270
|
+
this.#state.largeTitleUnder(),
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* On Android, glass bars fade the content beneath them themselves, since
|
|
275
|
+
* its scroll view takes no mask (see edgeFadeOverlay).
|
|
276
|
+
*/
|
|
277
|
+
protected readonly edgeFade = computed(() =>
|
|
278
|
+
this.design() === 'glass' && this.#platform === 'Android'
|
|
279
|
+
? edgeFadeOverlay('bottom')
|
|
280
|
+
: null,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
protected readonly frostedBackground = computed(
|
|
284
|
+
() => this.design() === 'frosted' && this.#state.scrolledUnderTop(),
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
/** Material's small top app bar puts the title at the start. */
|
|
288
|
+
protected readonly titleAtStart = computed(
|
|
289
|
+
() => this.#material() && !this.titleCentered(),
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
protected readonly hostClass = computed(() =>
|
|
293
|
+
cn(
|
|
294
|
+
'flex-col flex absolute left-0 right-0 top-0 pt-safe',
|
|
295
|
+
this.#material() &&
|
|
296
|
+
(this.#state.scrolledUnderTop() ? 'bg-secondary' : 'bg-background'),
|
|
297
|
+
this.userClass(),
|
|
298
|
+
),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
protected readonly containerTransition = computed(() =>
|
|
302
|
+
this.#material() && this.#animate() ? CONTAINER_TRANSITION : null,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
protected readonly rowClass = computed(() =>
|
|
306
|
+
cn(
|
|
307
|
+
'flex-row items-center flex relative justify-between',
|
|
308
|
+
this.#material() ? 'h-[64px]' : 'h-[44px]',
|
|
309
|
+
),
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
protected readonly rowStyle = computed(() => {
|
|
313
|
+
const padding = `${ROW_PADDING[this.design()]}px`;
|
|
314
|
+
return { 'padding-left': padding, 'padding-right': padding };
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* The ends keep their controls' width. Material's small bar puts its
|
|
319
|
+
* navigation icon 4dp in, and the title 16dp in without one (`min-w`), or
|
|
320
|
+
* right after it: Compose's TopAppBar layout.
|
|
321
|
+
*/
|
|
322
|
+
protected readonly leadingClass = computed(() =>
|
|
323
|
+
cn(
|
|
324
|
+
'shrink-0 flex-row items-center flex',
|
|
325
|
+
this.#material() ? 'pl-[4px]' : 'gap-[8px]',
|
|
326
|
+
this.titleAtStart() && 'min-w-[12px]',
|
|
327
|
+
),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
protected readonly trailingClass = computed(() =>
|
|
331
|
+
cn(
|
|
332
|
+
'shrink-0 flex-row items-center flex',
|
|
333
|
+
this.#material() ? 'pr-[4px]' : 'gap-[8px]',
|
|
334
|
+
),
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* A centered title is centered on the screen, clear of the wider end on
|
|
339
|
+
* both sides, when it fits there; otherwise it's centered in the room
|
|
340
|
+
* between the ends, and shortens with an ellipsis when even that's too
|
|
341
|
+
* little: UIKit's inline title. At the start, it takes the room between
|
|
342
|
+
* the ends.
|
|
343
|
+
*/
|
|
344
|
+
protected readonly titleBoxClass = computed(() =>
|
|
345
|
+
this.titleAtStart()
|
|
346
|
+
? 'flex-1 flex-row items-center flex px-[4px]'
|
|
347
|
+
: 'flex-row items-center flex absolute bottom-0 top-0 justify-center',
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
protected readonly titleBoxStyle = computed(() => {
|
|
351
|
+
const style: Record<string, string> = {
|
|
352
|
+
opacity: this.titleShown() ? '1' : '0',
|
|
353
|
+
transition: this.#animate() ? TITLE_TRANSITION : 'none',
|
|
354
|
+
};
|
|
355
|
+
if (!this.titleAtStart()) {
|
|
356
|
+
const padding = ROW_PADDING[this.design()];
|
|
357
|
+
const leading = padding + this.leadingWidth() + TITLE_GAP;
|
|
358
|
+
const trailing = padding + this.trailingWidth() + TITLE_GAP;
|
|
359
|
+
const centered = Math.max(leading, trailing);
|
|
360
|
+
const fits = this.titleWidth() <= this.rowWidth() - 2 * centered;
|
|
361
|
+
style['left'] = `${fits ? centered : leading}px`;
|
|
362
|
+
style['right'] = `${fits ? centered : trailing}px`;
|
|
363
|
+
}
|
|
364
|
+
return style;
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
protected readonly titleClass = computed(() =>
|
|
368
|
+
cn(
|
|
369
|
+
this.#material()
|
|
370
|
+
? 'text-[22px] leading-7'
|
|
371
|
+
: 'text-[17px] font-semibold leading-[22px]',
|
|
372
|
+
!this.titleAtStart() && 'text-center',
|
|
373
|
+
'text-foreground',
|
|
374
|
+
),
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
protected measureRow(event: Event): void {
|
|
378
|
+
this.rowWidth.set((event as LayoutChangeEvent).detail.width);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
protected measureTitle(event: Event): void {
|
|
382
|
+
this.titleWidth.set((event as LayoutChangeEvent).detail.width);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
protected measureLeading(event: Event): void {
|
|
386
|
+
this.leadingWidth.set((event as LayoutChangeEvent).detail.width);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
protected measureTrailing(event: Event): void {
|
|
390
|
+
this.trailingWidth.set((event as LayoutChangeEvent).detail.width);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Goes back, as the Back button does: through the router's history when
|
|
395
|
+
* there's a screen to go back to, which runs the screen's guards as the
|
|
396
|
+
* browser's back button does, or else to `backRoute`, in place of this
|
|
397
|
+
* screen. With neither, it does nothing.
|
|
398
|
+
*/
|
|
399
|
+
goBack(): void {
|
|
400
|
+
if (this.canGoBack()) {
|
|
401
|
+
this.#injector.get(Location).back();
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const route = this.backRoute();
|
|
405
|
+
if (route) {
|
|
406
|
+
void this.#injector
|
|
407
|
+
.get(Router)
|
|
408
|
+
.navigateByUrl(route, { replaceUrl: true });
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|