@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
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
1
2
|
import {
|
|
2
|
-
type ElementRef,
|
|
3
3
|
type OnDestroy,
|
|
4
4
|
Component,
|
|
5
|
+
Directive,
|
|
6
|
+
ElementRef,
|
|
5
7
|
ViewEncapsulation,
|
|
6
8
|
computed,
|
|
7
9
|
effect,
|
|
@@ -27,19 +29,62 @@ import {
|
|
|
27
29
|
slideOut,
|
|
28
30
|
} from '../../utils/animate';
|
|
29
31
|
import { cn } from '../../utils/cn';
|
|
30
|
-
|
|
32
|
+
import { UiGlass } from '../glass/glass';
|
|
33
|
+
import { injectGlassDesign } from '../glass/glass-design';
|
|
34
|
+
import { type PopoverAnchor, UiPopoverPanel } from '../popover/popover-panel';
|
|
35
|
+
|
|
36
|
+
/** How many action sheets there have been, for their glass ids. */
|
|
37
|
+
let sheetCount = 0;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The width of an action sheet opened from a control: iOS's, and Material's
|
|
41
|
+
* widest menu.
|
|
42
|
+
*/
|
|
43
|
+
const ANCHORED_WIDTH = { glass: 270, material: 280 } as const;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* An action sheet: a short list of choices about an action the user took,
|
|
47
|
+
* such as saving or deleting a draft.
|
|
48
|
+
*
|
|
49
|
+
* ```html
|
|
50
|
+
* <ui-button (pressed)="open.set(true)">Share</ui-button>
|
|
51
|
+
* <ui-action-sheet [(open)]="open">
|
|
52
|
+
* <ui-action-sheet-title>Share this document</ui-action-sheet-title>
|
|
53
|
+
* <ui-action-sheet-item (pressed)="copyLink()">Copy link</ui-action-sheet-item>
|
|
54
|
+
* <ui-action-sheet-item variant="destructive" (pressed)="remove()">Delete</ui-action-sheet-item>
|
|
55
|
+
* <ui-action-sheet-cancel />
|
|
56
|
+
* </ui-action-sheet>
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* By default it slides up from the bottom of the screen over a dimmed
|
|
60
|
+
* backdrop, with Cancel in a tile of its own below the actions, and takes
|
|
61
|
+
* the screen until it closes.
|
|
62
|
+
*
|
|
63
|
+
* Mark the control that presents it with `[uiActionSheetAnchor]="sheet"`
|
|
64
|
+
* to open it from that control instead, as iOS 26 does: there it's Liquid
|
|
65
|
+
* Glass that grows out of the control, and on Android a Material 3 menu
|
|
66
|
+
* that unfolds beside it. Nothing dims, Cancel gives way to a touch outside,
|
|
67
|
+
* and that touch still reaches what it lands on. Before iOS 26, iPhone
|
|
68
|
+
* action sheets always rose from the bottom, so it does there too.
|
|
69
|
+
*/
|
|
31
70
|
@Component({
|
|
32
71
|
selector: 'ui-action-sheet',
|
|
33
|
-
imports: [LYNX_ELEMENTS],
|
|
72
|
+
imports: [LYNX_ELEMENTS, NgTemplateOutlet, UiPopoverPanel],
|
|
34
73
|
encapsulation: ViewEncapsulation.None,
|
|
35
74
|
template: `
|
|
36
|
-
|
|
75
|
+
<!-- Android's back button asks the overlay to close (requestclose),
|
|
76
|
+
which closes the sheet as Cancel does. -->
|
|
77
|
+
<overlay
|
|
78
|
+
[attr.visible]="overlayVisible()"
|
|
79
|
+
[style]="overlayStyle()"
|
|
80
|
+
(bindrequestclose)="close()"
|
|
81
|
+
>
|
|
37
82
|
<!-- bg-black/50 dims the content behind the sheet. The fadeIn/fadeOut in
|
|
38
83
|
#animateIn/#animateOut already tween this element's opacity over
|
|
39
84
|
DURATION.normal, so the dim animates in lockstep with the panel slide —
|
|
40
|
-
matching the dialog/bottom-sheet backdrop.
|
|
41
|
-
|
|
42
|
-
|
|
85
|
+
matching the dialog/bottom-sheet backdrop. It's a named color, not a
|
|
86
|
+
theme one, because the dim is black in light and dark mode alike;
|
|
87
|
+
bg-black/50 compiles to rgb(0 0 0 / 0.5), which Lynx accepts. -->
|
|
43
88
|
<view
|
|
44
89
|
#backdrop
|
|
45
90
|
class="h-full w-full bg-black/50"
|
|
@@ -62,7 +107,9 @@ import { cn } from '../../utils/cn';
|
|
|
62
107
|
class="flex-col rounded-lg border border-border bg-card flex overflow-hidden"
|
|
63
108
|
style="box-shadow: 0 2px 16px rgba(0, 0, 0, 0.15);"
|
|
64
109
|
>
|
|
65
|
-
|
|
110
|
+
@if (!anchored()) {
|
|
111
|
+
<ng-container [ngTemplateOutlet]="actions" />
|
|
112
|
+
}
|
|
66
113
|
</view>
|
|
67
114
|
<!-- Cancel button is slotted separately so it renders outside the
|
|
68
115
|
rounded card — matching the iOS action sheet visual convention
|
|
@@ -71,6 +118,28 @@ import { cn } from '../../utils/cn';
|
|
|
71
118
|
</view>
|
|
72
119
|
</view>
|
|
73
120
|
</overlay>
|
|
121
|
+
@if (anchorElement(); as anchor) {
|
|
122
|
+
<!-- Opened from its control: a popover's panel, which leaves the rest
|
|
123
|
+
of the screen as it was. A touch outside it is the cancel, as
|
|
124
|
+
UIKit drops an action sheet's Cancel when it shows it as a
|
|
125
|
+
popover, so the Cancel tile above stays hidden. -->
|
|
126
|
+
<ui-popover-panel
|
|
127
|
+
#anchoredPanel
|
|
128
|
+
[open]="open()"
|
|
129
|
+
[anchor]="anchor"
|
|
130
|
+
[width]="anchoredWidth()"
|
|
131
|
+
[glassId]="glassId"
|
|
132
|
+
(dismissed)="close()"
|
|
133
|
+
(closed)="closed.emit()"
|
|
134
|
+
>
|
|
135
|
+
<view [class]="anchoredClass()">
|
|
136
|
+
<ng-container [ngTemplateOutlet]="actions" />
|
|
137
|
+
</view>
|
|
138
|
+
</ui-popover-panel>
|
|
139
|
+
}
|
|
140
|
+
<!-- One copy of the title and items, for whichever form the sheet takes:
|
|
141
|
+
Angular projects content once, so it can't be in both. -->
|
|
142
|
+
<ng-template #actions><ng-content /></ng-template>
|
|
74
143
|
`,
|
|
75
144
|
})
|
|
76
145
|
export class UiActionSheet implements OnDestroy {
|
|
@@ -79,6 +148,57 @@ export class UiActionSheet implements OnDestroy {
|
|
|
79
148
|
|
|
80
149
|
readonly closed = output<void>();
|
|
81
150
|
|
|
151
|
+
readonly design = injectGlassDesign();
|
|
152
|
+
readonly #anchor = signal<PopoverAnchor | null>(null);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The id the anchored sheet's glass and a glass control share (see
|
|
156
|
+
* UiGlass's `glassId`), unique to the sheet, so it takes over the
|
|
157
|
+
* control's glass as it grows out of it.
|
|
158
|
+
*/
|
|
159
|
+
readonly glassId = `ui-action-sheet-${++sheetCount}`;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The control the sheet opens from, on a design that opens it there: iOS
|
|
163
|
+
* 26's glass and Android's Material. iPhone action sheets rose from the
|
|
164
|
+
* bottom edge before iOS 26 whatever presented them, so `frosted` keeps
|
|
165
|
+
* the bottom sheet.
|
|
166
|
+
*/
|
|
167
|
+
protected readonly anchorElement = computed(() =>
|
|
168
|
+
this.design() === 'frosted' ? null : this.#anchor(),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
/** Whether the sheet opens from its control rather than from the bottom. */
|
|
172
|
+
readonly anchored = computed(() => this.anchorElement() !== null);
|
|
173
|
+
|
|
174
|
+
// Optional: only there while the sheet is anchored.
|
|
175
|
+
readonly anchoredPanel = viewChild<UiPopoverPanel>('anchoredPanel');
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Whether the sheet covers its control now: on iOS 26 it grows out of the
|
|
179
|
+
* control and shrinks back into it, which hides meanwhile.
|
|
180
|
+
*/
|
|
181
|
+
readonly coversAnchor = computed(
|
|
182
|
+
() => this.anchoredPanel()?.coversAnchor() ?? false,
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* How the sheet's title and items draw: as today's bottom sheet, as
|
|
187
|
+
* iOS 26's glass popover, or as a Material 3 menu.
|
|
188
|
+
*/
|
|
189
|
+
readonly look = computed<'sheet' | 'glass' | 'material'>(() => {
|
|
190
|
+
if (!this.anchored()) return 'sheet';
|
|
191
|
+
return this.design() === 'material' ? 'material' : 'glass';
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
protected readonly anchoredWidth = computed(() =>
|
|
195
|
+
this.look() === 'material' ? ANCHORED_WIDTH.material : ANCHORED_WIDTH.glass,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
protected readonly anchoredClass = computed(() =>
|
|
199
|
+
cn('flex-col flex', this.look() === 'material' ? 'py-2' : 'py-1.5'),
|
|
200
|
+
);
|
|
201
|
+
|
|
82
202
|
protected readonly overlayVisible = signal(false);
|
|
83
203
|
/**
|
|
84
204
|
* `width`/`height`/`top`/`left` are web-only necessities: on web
|
|
@@ -92,30 +212,39 @@ export class UiActionSheet implements OnDestroy {
|
|
|
92
212
|
: 'position: fixed; overflow: visible; width: 100vw; height: 100vh; top: 0; left: 0; z-index: 999; display: none;',
|
|
93
213
|
);
|
|
94
214
|
|
|
95
|
-
|
|
96
|
-
|
|
215
|
+
// Required: both are always in the template (the overlay is hidden, never
|
|
216
|
+
// removed), and nothing reads them before the first render.
|
|
217
|
+
readonly backdropRef = viewChild.required<ElementRef>('backdrop');
|
|
218
|
+
readonly panelRef = viewChild.required<ElementRef>('panel');
|
|
97
219
|
#backdropAnim?: AnimationHandle;
|
|
98
220
|
#panelAnim?: AnimationHandle;
|
|
99
|
-
#
|
|
221
|
+
#sheetOpen = false;
|
|
100
222
|
// The open and close sequences' pending timers, so ngOnDestroy can stop
|
|
101
223
|
// them.
|
|
102
224
|
readonly #timers = new Set<ReturnType<typeof setTimeout>>();
|
|
103
225
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Opens or closes the action sheet as `open` changes. A field rather than a
|
|
228
|
+
* constructor: Angular's test build keeps a function for a constructor that
|
|
229
|
+
* nothing calls, which an app's coverage report would show as uncovered.
|
|
230
|
+
*
|
|
231
|
+
* `#sheetOpen` says the bottom sheet is open or opening, so only a sheet
|
|
232
|
+
* that opened closes. Without it, the first run, with `open` false, would
|
|
233
|
+
* call #doClose() and emit `closed` before the action sheet ever opened,
|
|
234
|
+
* and so would an anchor arriving or leaving while it's closed.
|
|
235
|
+
*/
|
|
236
|
+
protected readonly openOrClose = effect(() => {
|
|
237
|
+
// An anchored sheet opens in its popover panel instead, which follows
|
|
238
|
+
// `open` itself.
|
|
239
|
+
const isOpen = this.open() && !this.anchored();
|
|
240
|
+
if (isOpen) {
|
|
241
|
+
this.#sheetOpen = true;
|
|
242
|
+
this.#doOpen();
|
|
243
|
+
} else if (this.#sheetOpen) {
|
|
244
|
+
this.#sheetOpen = false;
|
|
245
|
+
this.#doClose();
|
|
246
|
+
}
|
|
247
|
+
});
|
|
119
248
|
|
|
120
249
|
protected readonly panelClass = computed(() =>
|
|
121
250
|
cn('w-full flex-col gap-2 px-4 pb-6 flex', this.userClass()),
|
|
@@ -133,6 +262,21 @@ export class UiActionSheet implements OnDestroy {
|
|
|
133
262
|
this.open.set(false);
|
|
134
263
|
}
|
|
135
264
|
|
|
265
|
+
/**
|
|
266
|
+
* @internal Called by the `uiActionSheetAnchor` directive.
|
|
267
|
+
*/
|
|
268
|
+
setAnchor(anchor: PopoverAnchor | null): void {
|
|
269
|
+
this.#anchor.set(anchor);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* @internal A touch began on the anchor, which opens the sheet rather than
|
|
274
|
+
* landing outside it (see UiPopoverPanel.touchedAnchor).
|
|
275
|
+
*/
|
|
276
|
+
touchedAnchor(): void {
|
|
277
|
+
this.anchoredPanel()?.touchedAnchor();
|
|
278
|
+
}
|
|
279
|
+
|
|
136
280
|
/**
|
|
137
281
|
* No-op tap handler for the panel. `catchtap` (vs `bindtap`) already stops
|
|
138
282
|
* the tap from bubbling to the backdrop — Lynx controls propagation via the
|
|
@@ -190,9 +334,8 @@ export class UiActionSheet implements OnDestroy {
|
|
|
190
334
|
}
|
|
191
335
|
|
|
192
336
|
#animateIn(): void {
|
|
193
|
-
const backdrop = this.backdropRef()
|
|
194
|
-
const panel = this.panelRef()
|
|
195
|
-
if (!backdrop || !panel) return;
|
|
337
|
+
const backdrop = this.backdropRef().nativeElement;
|
|
338
|
+
const panel = this.panelRef().nativeElement;
|
|
196
339
|
|
|
197
340
|
this.#backdropAnim?.cancel();
|
|
198
341
|
this.#panelAnim?.cancel();
|
|
@@ -210,9 +353,8 @@ export class UiActionSheet implements OnDestroy {
|
|
|
210
353
|
}
|
|
211
354
|
|
|
212
355
|
#animateOut(): void {
|
|
213
|
-
const backdrop = this.backdropRef()
|
|
214
|
-
const panel = this.panelRef()
|
|
215
|
-
if (!backdrop || !panel) return;
|
|
356
|
+
const backdrop = this.backdropRef().nativeElement;
|
|
357
|
+
const panel = this.panelRef().nativeElement;
|
|
216
358
|
|
|
217
359
|
this.#backdropAnim?.cancel();
|
|
218
360
|
this.#panelAnim?.cancel();
|
|
@@ -225,28 +367,73 @@ export class UiActionSheet implements OnDestroy {
|
|
|
225
367
|
}
|
|
226
368
|
}
|
|
227
369
|
|
|
370
|
+
/** How an action sheet's title and items draw; see UiActionSheet.look. */
|
|
371
|
+
type ActionSheetLook = 'sheet' | 'glass' | 'material';
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* The look of the sheet a title or item is in. Today's bottom sheet when
|
|
375
|
+
* it's in none.
|
|
376
|
+
*/
|
|
377
|
+
const lookOf = (sheet: UiActionSheet | null) =>
|
|
378
|
+
computed<ActionSheetLook>(() => sheet?.look() ?? 'sheet');
|
|
379
|
+
|
|
380
|
+
// The bottom sheet's tiles; iOS 26's popover, whose rows are 44pt tall with
|
|
381
|
+
// the text in the middle, as UIKit's action sheets keep it in every form;
|
|
382
|
+
// and Material 3's menu, with 48dp rows and the text at the start.
|
|
383
|
+
const TITLE_CONTAINER: Readonly<Record<ActionSheetLook, string>> = {
|
|
384
|
+
sheet: 'items-center px-4 py-3 flex justify-center',
|
|
385
|
+
glass: 'items-center px-4 pb-1 pt-2 flex justify-center',
|
|
386
|
+
material: 'items-center px-3 pb-1 pt-2 flex',
|
|
387
|
+
};
|
|
388
|
+
const TITLE_TEXT: Readonly<Record<ActionSheetLook, string>> = {
|
|
389
|
+
sheet: 'text-sm text-muted-foreground',
|
|
390
|
+
glass: 'text-center text-[13px] text-glass-muted-foreground',
|
|
391
|
+
material: 'text-[13px] text-glass-muted-foreground',
|
|
392
|
+
};
|
|
393
|
+
const ITEM_CONTAINER: Readonly<Record<ActionSheetLook, string>> = {
|
|
394
|
+
sheet: 'items-center border-t border-border px-4 py-3 flex justify-center',
|
|
395
|
+
glass:
|
|
396
|
+
'min-h-[44px] items-center px-4 py-[11px] flex justify-center relative',
|
|
397
|
+
material: 'min-h-[48px] items-center px-3 py-3 flex relative',
|
|
398
|
+
};
|
|
399
|
+
const ITEM_TEXT: Readonly<Record<ActionSheetLook, string>> = {
|
|
400
|
+
sheet: 'text-base',
|
|
401
|
+
glass: 'text-center text-[17px]',
|
|
402
|
+
material: 'text-[16px]',
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* The action sheet's title, muted, above its items. A screen reader reads
|
|
407
|
+
* it as a heading.
|
|
408
|
+
*/
|
|
228
409
|
@Component({
|
|
229
410
|
selector: 'ui-action-sheet-title',
|
|
230
411
|
imports: [LYNX_ELEMENTS],
|
|
231
412
|
encapsulation: ViewEncapsulation.None,
|
|
232
413
|
template: `
|
|
233
414
|
<view [class]="containerClass()">
|
|
234
|
-
<text [class]="textClass()"
|
|
415
|
+
<text [class]="textClass()" accessibility-traits="header"
|
|
416
|
+
><ng-content
|
|
417
|
+
/></text>
|
|
235
418
|
</view>
|
|
236
419
|
`,
|
|
237
420
|
})
|
|
238
421
|
export class UiActionSheetTitle {
|
|
422
|
+
readonly #look = lookOf(inject(UiActionSheet, { optional: true }));
|
|
423
|
+
|
|
239
424
|
readonly userClass = input<string>('', { alias: 'class' });
|
|
240
425
|
|
|
241
426
|
protected readonly containerClass = computed(() =>
|
|
242
|
-
cn(
|
|
427
|
+
cn(TITLE_CONTAINER[this.#look()], this.userClass()),
|
|
243
428
|
);
|
|
244
429
|
|
|
245
|
-
protected readonly textClass = computed(() =>
|
|
246
|
-
cn('text-sm text-muted-foreground'),
|
|
247
|
-
);
|
|
430
|
+
protected readonly textClass = computed(() => TITLE_TEXT[this.#look()]);
|
|
248
431
|
}
|
|
249
432
|
|
|
433
|
+
/**
|
|
434
|
+
* One of the action sheet's choices: a tap emits `pressed` and closes the
|
|
435
|
+
* sheet. A screen reader reads it as a button.
|
|
436
|
+
*/
|
|
250
437
|
@Component({
|
|
251
438
|
selector: 'ui-action-sheet-item',
|
|
252
439
|
imports: [LYNX_ELEMENTS],
|
|
@@ -263,51 +450,86 @@ export class UiActionSheetTitle {
|
|
|
263
450
|
(bindmouseleave)="onPressCancel()"
|
|
264
451
|
(bindtap)="onTap()"
|
|
265
452
|
>
|
|
266
|
-
|
|
453
|
+
@if (look() !== 'sheet') {
|
|
454
|
+
<!-- A menu row's pressed highlight (see UiMenuItem's highlightClass):
|
|
455
|
+
rows in a popover light up rather than shrink. -->
|
|
456
|
+
<view
|
|
457
|
+
[class]="highlightClass()"
|
|
458
|
+
[style.opacity]="held() ? highlightOpacity() : 0"
|
|
459
|
+
></view>
|
|
460
|
+
}
|
|
461
|
+
<text [class]="textClass()" accessibility-traits="button"
|
|
462
|
+
><ng-content
|
|
463
|
+
/></text>
|
|
267
464
|
</view>
|
|
268
465
|
`,
|
|
269
466
|
})
|
|
270
467
|
export class UiActionSheetItem {
|
|
271
468
|
readonly #sheet = inject(UiActionSheet);
|
|
469
|
+
protected readonly look = lookOf(this.#sheet);
|
|
272
470
|
|
|
273
471
|
readonly variant = input<'default' | 'destructive'>('default');
|
|
274
472
|
readonly userClass = input<string>('', { alias: 'class' });
|
|
275
473
|
|
|
276
474
|
readonly pressed = output<void>();
|
|
277
475
|
|
|
278
|
-
|
|
476
|
+
// Required: the container is always in the template.
|
|
477
|
+
readonly containerRef = viewChild.required<ElementRef>('container');
|
|
279
478
|
#pressAnim?: AnimationHandle;
|
|
280
479
|
|
|
281
|
-
|
|
480
|
+
/** Whether a finger (or the mouse) is down on a row in a popover. */
|
|
481
|
+
protected readonly held = signal(false);
|
|
482
|
+
|
|
483
|
+
// As UiMenuItem's: iOS 26's inset capsule, and Material's full-width
|
|
484
|
+
// state layer.
|
|
485
|
+
protected readonly highlightClass = computed(() =>
|
|
282
486
|
cn(
|
|
283
|
-
'
|
|
284
|
-
this.
|
|
487
|
+
'bg-glass-foreground absolute bottom-0 top-0',
|
|
488
|
+
this.look() === 'glass'
|
|
489
|
+
? 'rounded-full left-[10px] right-[10px]'
|
|
490
|
+
: 'left-0 right-0',
|
|
285
491
|
),
|
|
286
492
|
);
|
|
493
|
+
protected readonly highlightOpacity = computed(() =>
|
|
494
|
+
this.look() === 'glass' ? 0.08 : 0.1,
|
|
495
|
+
);
|
|
287
496
|
|
|
288
|
-
protected readonly
|
|
289
|
-
cn(
|
|
290
|
-
'text-base',
|
|
291
|
-
this.variant() === 'destructive' ? 'text-destructive' : 'text-primary',
|
|
292
|
-
),
|
|
497
|
+
protected readonly containerClass = computed(() =>
|
|
498
|
+
cn(ITEM_CONTAINER[this.look()], this.userClass()),
|
|
293
499
|
);
|
|
294
500
|
|
|
501
|
+
protected readonly textClass = computed(() => {
|
|
502
|
+
const destructive = this.variant() === 'destructive';
|
|
503
|
+
return cn(
|
|
504
|
+
ITEM_TEXT[this.look()],
|
|
505
|
+
destructive && 'text-destructive',
|
|
506
|
+
!destructive &&
|
|
507
|
+
(this.look() === 'sheet' ? 'text-primary' : 'text-glass-foreground'),
|
|
508
|
+
);
|
|
509
|
+
});
|
|
510
|
+
|
|
295
511
|
protected onPressStart(): void {
|
|
512
|
+
if (this.look() !== 'sheet') {
|
|
513
|
+
this.held.set(true);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
296
516
|
this.#pressAnim?.cancel();
|
|
297
517
|
this.#pressAnim = pressDown(
|
|
298
|
-
this.containerRef()
|
|
518
|
+
this.containerRef().nativeElement,
|
|
299
519
|
SCALE.pressDownLight,
|
|
300
520
|
);
|
|
301
521
|
}
|
|
302
522
|
|
|
303
523
|
protected onPressEnd(): void {
|
|
524
|
+
this.held.set(false);
|
|
304
525
|
this.#pressAnim?.cancel();
|
|
305
|
-
this.#pressAnim = pressRelease(this.containerRef()
|
|
526
|
+
this.#pressAnim = pressRelease(this.containerRef().nativeElement);
|
|
306
527
|
}
|
|
307
528
|
|
|
308
529
|
protected onPressCancel(): void {
|
|
530
|
+
this.held.set(false);
|
|
309
531
|
this.#pressAnim?.cancel();
|
|
310
|
-
this.#pressAnim = pressRelease(this.containerRef()
|
|
532
|
+
this.#pressAnim = pressRelease(this.containerRef().nativeElement);
|
|
311
533
|
}
|
|
312
534
|
|
|
313
535
|
protected onTap(): void {
|
|
@@ -316,6 +538,11 @@ export class UiActionSheetItem {
|
|
|
316
538
|
}
|
|
317
539
|
}
|
|
318
540
|
|
|
541
|
+
/**
|
|
542
|
+
* The action sheet's Cancel, in a tile of its own below the actions: a tap
|
|
543
|
+
* closes the sheet. An action sheet opened from its control has none: a
|
|
544
|
+
* touch outside it cancels.
|
|
545
|
+
*/
|
|
319
546
|
@Component({
|
|
320
547
|
selector: 'ui-action-sheet-cancel',
|
|
321
548
|
imports: [LYNX_ELEMENTS],
|
|
@@ -333,7 +560,9 @@ export class UiActionSheetItem {
|
|
|
333
560
|
(bindmouseleave)="onPressCancel()"
|
|
334
561
|
(bindtap)="onTap()"
|
|
335
562
|
>
|
|
336
|
-
<text [class]="textClass()">{{
|
|
563
|
+
<text [class]="textClass()" accessibility-traits="button">{{
|
|
564
|
+
label()
|
|
565
|
+
}}</text>
|
|
337
566
|
</view>
|
|
338
567
|
`,
|
|
339
568
|
})
|
|
@@ -343,7 +572,8 @@ export class UiActionSheetCancel {
|
|
|
343
572
|
readonly label = input('Cancel');
|
|
344
573
|
readonly userClass = input<string>('', { alias: 'class' });
|
|
345
574
|
|
|
346
|
-
|
|
575
|
+
// Required: the container is always in the template.
|
|
576
|
+
readonly containerRef = viewChild.required<ElementRef>('container');
|
|
347
577
|
#pressAnim?: AnimationHandle;
|
|
348
578
|
|
|
349
579
|
protected readonly containerClass = computed(() =>
|
|
@@ -360,22 +590,76 @@ export class UiActionSheetCancel {
|
|
|
360
590
|
protected onPressStart(): void {
|
|
361
591
|
this.#pressAnim?.cancel();
|
|
362
592
|
this.#pressAnim = pressDown(
|
|
363
|
-
this.containerRef()
|
|
593
|
+
this.containerRef().nativeElement,
|
|
364
594
|
SCALE.pressDownLight,
|
|
365
595
|
);
|
|
366
596
|
}
|
|
367
597
|
|
|
368
598
|
protected onPressEnd(): void {
|
|
369
599
|
this.#pressAnim?.cancel();
|
|
370
|
-
this.#pressAnim = pressRelease(this.containerRef()
|
|
600
|
+
this.#pressAnim = pressRelease(this.containerRef().nativeElement);
|
|
371
601
|
}
|
|
372
602
|
|
|
373
603
|
protected onPressCancel(): void {
|
|
374
604
|
this.#pressAnim?.cancel();
|
|
375
|
-
this.#pressAnim = pressRelease(this.containerRef()
|
|
605
|
+
this.#pressAnim = pressRelease(this.containerRef().nativeElement);
|
|
376
606
|
}
|
|
377
607
|
|
|
378
608
|
protected onTap(): void {
|
|
379
609
|
this.#sheet.close();
|
|
380
610
|
}
|
|
381
611
|
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Marks the control that presents an action sheet, which then opens from it
|
|
615
|
+
* instead of from the bottom of the screen: Liquid Glass that grows out of
|
|
616
|
+
* it on iOS 26, and a Material 3 menu beside it on Android.
|
|
617
|
+
*
|
|
618
|
+
* ```html
|
|
619
|
+
* <ui-button [uiActionSheetAnchor]="sheet" (pressed)="open.set(true)">
|
|
620
|
+
* Share
|
|
621
|
+
* </ui-button>
|
|
622
|
+
* <ui-action-sheet #sheet [(open)]="open">…</ui-action-sheet>
|
|
623
|
+
* ```
|
|
624
|
+
*
|
|
625
|
+
* It only says where the sheet opens from: open it as before, from the
|
|
626
|
+
* control's own tap handler. A touch on the control doesn't count as one
|
|
627
|
+
* outside the sheet, so it doesn't close it.
|
|
628
|
+
*/
|
|
629
|
+
@Directive({
|
|
630
|
+
selector: '[uiActionSheetAnchor]',
|
|
631
|
+
host: {
|
|
632
|
+
// Bubbling, from what the touch landed on in the control: Lynx 4.1 ran
|
|
633
|
+
// no capture listener on iOS (see UiPopoverPanel).
|
|
634
|
+
'(bindtouchstart)': 'sheet().touchedAnchor()',
|
|
635
|
+
'(bindmousedown)': 'sheet().touchedAnchor()',
|
|
636
|
+
'[style.visibility]': 'sheet().coversAnchor() ? "hidden" : null',
|
|
637
|
+
},
|
|
638
|
+
})
|
|
639
|
+
export class UiActionSheetAnchor {
|
|
640
|
+
/** The action sheet this control presents. */
|
|
641
|
+
readonly sheet = input.required<UiActionSheet>({
|
|
642
|
+
alias: 'uiActionSheetAnchor',
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
readonly #element =
|
|
646
|
+
inject<ElementRef<PopoverAnchor>>(ElementRef).nativeElement;
|
|
647
|
+
readonly #glass = inject(UiGlass, { self: true, optional: true });
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Points the sheet at this control, and away from it when the directive
|
|
651
|
+
* is destroyed or points at another sheet. A control that's glass itself
|
|
652
|
+
* (`<blur-view ui-glass [uiActionSheetAnchor]>`) shares the sheet's glass
|
|
653
|
+
* id meanwhile, so the sheet takes its glass over (see UiPopoverPanel's
|
|
654
|
+
* coversAnchor).
|
|
655
|
+
*/
|
|
656
|
+
protected readonly register = effect((onCleanup) => {
|
|
657
|
+
const sheet = this.sheet();
|
|
658
|
+
sheet.setAnchor(this.#element);
|
|
659
|
+
this.#glass?.pair(sheet.glassId);
|
|
660
|
+
onCleanup(() => {
|
|
661
|
+
sheet.setAnchor(null);
|
|
662
|
+
this.#glass?.pair(null);
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
}
|