@blotch/dolan 22.2.2 → 22.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.d.ts +6 -2
- package/dist/commands/add.js +41 -11
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +162 -9
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/diff.d.ts +2 -1
- package/dist/commands/diff.js +38 -25
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/diff.test.js +72 -11
- package/dist/commands/diff.test.js.map +1 -1
- package/dist/commands/doctor.js +228 -34
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +367 -10
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/eject.js +7 -0
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/eject.test.js +39 -0
- package/dist/commands/eject.test.js.map +1 -1
- package/dist/commands/info.js +3 -2
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/init.d.ts +37 -1
- package/dist/commands/init.js +169 -40
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +392 -27
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/install-packages.d.ts +6 -0
- package/dist/commands/install-packages.js +20 -0
- package/dist/commands/install-packages.js.map +1 -0
- package/dist/commands/install-packages.test.d.ts +1 -0
- package/dist/commands/install-packages.test.js +55 -0
- package/dist/commands/install-packages.test.js.map +1 -0
- package/dist/commands/list.js +21 -14
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/list.test.js +62 -5
- package/dist/commands/list.test.js.map +1 -1
- package/dist/commands/outdated.js +11 -10
- package/dist/commands/outdated.js.map +1 -1
- package/dist/commands/outdated.test.js +20 -5
- package/dist/commands/outdated.test.js.map +1 -1
- package/dist/commands/remove.d.ts +4 -0
- package/dist/commands/remove.js +17 -4
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/remove.test.js +53 -2
- package/dist/commands/remove.test.js.map +1 -1
- package/dist/commands/theme.js +1 -1
- package/dist/commands/theme.js.map +1 -1
- package/dist/commands/update.d.ts +11 -0
- package/dist/commands/update.js +174 -58
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +587 -33
- package/dist/commands/update.test.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/lockfile.d.ts +7 -0
- package/dist/lockfile.js +36 -0
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +25 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +78 -24
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +119 -38
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +5 -0
- package/dist/test-utils.js +6 -1
- package/dist/test-utils.js.map +1 -1
- package/dist/testing/fire-lynx-event.d.ts +14 -0
- package/dist/testing/fire-lynx-event.js +19 -0
- package/dist/testing/fire-lynx-event.js.map +1 -0
- package/dist/testing/fire-lynx-event.test.d.ts +1 -0
- package/dist/testing/fire-lynx-event.test.js +44 -0
- package/dist/testing/fire-lynx-event.test.js.map +1 -0
- package/dist/testing/lynx-main-thread.d.ts +22 -0
- package/dist/testing/lynx-main-thread.js +11 -0
- package/dist/testing/lynx-main-thread.js.map +1 -0
- package/dist/testing/lynx-main-thread.test.d.ts +1 -0
- package/dist/testing/lynx-main-thread.test.js +24 -0
- package/dist/testing/lynx-main-thread.test.js.map +1 -0
- package/dist/testing/set-input-signal.d.ts +14 -0
- package/dist/testing/set-input-signal.js +21 -0
- package/dist/testing/set-input-signal.js.map +1 -0
- package/dist/testing/set-input-signal.test.d.ts +1 -0
- package/dist/testing/set-input-signal.test.js +20 -0
- package/dist/testing/set-input-signal.test.js.map +1 -0
- package/dist/ui/components/accordion/accordion.test.ts +158 -144
- package/dist/ui/components/accordion/accordion.ts +19 -14
- package/dist/ui/components/action-sheet/action-sheet.test.ts +283 -151
- package/dist/ui/components/action-sheet/action-sheet.ts +77 -43
- package/dist/ui/components/alert/alert.test.ts +67 -19
- package/dist/ui/components/alert/alert.ts +14 -16
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +208 -94
- package/dist/ui/components/alert-dialog/alert-dialog.ts +75 -38
- package/dist/ui/components/area-chart/area-chart.test.ts +134 -119
- package/dist/ui/components/area-chart/area-chart.ts +100 -315
- package/dist/ui/components/area-chart/index.ts +2 -3
- package/dist/ui/components/avatar/avatar.test.ts +89 -45
- package/dist/ui/components/badge/badge.test.ts +57 -23
- package/dist/ui/components/badge/badge.ts +12 -14
- package/dist/ui/components/bar-chart/bar-chart.test.ts +172 -45
- package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
- package/dist/ui/components/bar-chart/index.ts +3 -3
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +386 -128
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +170 -53
- package/dist/ui/components/button/button.test.ts +611 -2
- package/dist/ui/components/button/button.ts +434 -49
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +484 -2
- package/dist/ui/components/button-group/button-group.ts +380 -68
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +183 -58
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
- package/dist/ui/components/candlestick-chart/index.ts +3 -4
- package/dist/ui/components/card/card.test.ts +82 -62
- package/dist/ui/components/chart/chart-focus.test.ts +132 -0
- package/dist/ui/components/chart/chart-focus.ts +115 -0
- package/dist/ui/components/chart/chart-presets.test.ts +335 -0
- package/dist/ui/components/chart/chart-presets.ts +278 -0
- package/dist/ui/components/chart/chart-scene.test.ts +589 -0
- package/dist/ui/components/chart/chart-scene.ts +483 -0
- package/dist/ui/components/chart/chart-theme.test.ts +85 -0
- package/dist/ui/components/chart/chart-theme.ts +210 -0
- package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
- package/dist/ui/components/chart/chart-zoom.ts +162 -0
- package/dist/ui/components/chart/chart.test.ts +1017 -0
- package/dist/ui/components/chart/chart.ts +934 -0
- package/dist/ui/components/chart/index.ts +24 -0
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +23 -23
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +19 -14
- package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
- package/dist/ui/components/context-menu/context-menu.ts +110 -0
- package/dist/ui/components/context-menu/index.ts +1 -0
- package/dist/ui/components/dialog/dialog.test.ts +213 -108
- package/dist/ui/components/dialog/dialog.ts +72 -35
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +133 -0
- package/dist/ui/components/dock/dock.test.ts +749 -0
- package/dist/ui/components/dock/dock.ts +960 -0
- package/dist/ui/components/dock/index.ts +2 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
- package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
- package/dist/ui/components/glass/concentric-radius.ts +17 -0
- package/dist/ui/components/glass/glass-design.test.ts +78 -0
- package/dist/ui/components/glass/glass-design.ts +119 -0
- package/dist/ui/components/glass/glass.test.ts +338 -0
- package/dist/ui/components/glass/glass.ts +360 -0
- package/dist/ui/components/glass/index.ts +18 -0
- package/dist/ui/components/icon/icon-theme.test.ts +169 -0
- package/dist/ui/components/icon/icon-theme.ts +227 -0
- package/dist/ui/components/icon/icon.test.ts +85 -25
- package/dist/ui/components/icon/icon.ts +38 -21
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +15 -12
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/line-chart/index.ts +2 -3
- package/dist/ui/components/line-chart/line-chart.test.ts +149 -56
- package/dist/ui/components/line-chart/line-chart.ts +83 -241
- package/dist/ui/components/list/list.test.ts +119 -21
- package/dist/ui/components/list/list.ts +21 -15
- package/dist/ui/components/menu/index.ts +25 -0
- package/dist/ui/components/menu/menu-placement.test.ts +153 -0
- package/dist/ui/components/menu/menu-placement.ts +159 -0
- package/dist/ui/components/menu/menu-popup.test.ts +632 -0
- package/dist/ui/components/menu/menu-popup.ts +544 -0
- package/dist/ui/components/menu/menu.test.ts +429 -0
- package/dist/ui/components/menu/menu.ts +415 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +435 -191
- package/dist/ui/components/nav-drawer/nav-drawer.ts +127 -56
- package/dist/ui/components/pagination/pagination.test.ts +177 -65
- package/dist/ui/components/pagination/pagination.ts +55 -8
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +52 -54
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +24 -24
- package/dist/ui/components/scatter-chart/index.ts +2 -3
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +131 -26
- package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
- package/dist/ui/components/scroll-area/scroll-area.test.ts +95 -24
- package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +17 -11
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +64 -11
- package/dist/ui/components/sheet/sheet.ts +9 -1
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/spinner/spinner.test.ts +67 -22
- package/dist/ui/components/spinner/spinner.ts +48 -41
- package/dist/ui/components/switch/switch.test.ts +116 -46
- package/dist/ui/components/switch/switch.ts +8 -13
- package/dist/ui/components/tab-bar/index.ts +1 -0
- package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
- package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
- package/dist/ui/components/tabs/tabs.test.ts +215 -157
- package/dist/ui/components/tabs/tabs.ts +110 -118
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +14 -12
- package/dist/ui/components/toast/toast.test.ts +436 -315
- package/dist/ui/components/toast/toast.ts +62 -55
- package/dist/ui/components/toggle/toggle.test.ts +247 -140
- package/dist/ui/theme/chart-colors.test.ts +35 -0
- package/dist/ui/theme/dark.css +46 -2
- package/dist/ui/theme/default.css +63 -7
- package/dist/ui/theme/icon-colors.test.ts +35 -0
- package/dist/ui/theme/rgb-channels.test.ts +84 -0
- package/dist/ui/theme/tailwind-build.test.ts +114 -0
- package/dist/ui/theme/tailwind-css.test.ts +83 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
- package/dist/ui/theme/tailwind-plugin.ts +98 -25
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +14 -0
- package/dist/utils/analyze.js +15 -0
- package/dist/utils/analyze.js.map +1 -1
- package/dist/utils/analyze.test.js +14 -1
- package/dist/utils/analyze.test.js.map +1 -1
- package/dist/utils/installed.d.ts +29 -0
- package/dist/utils/installed.js +43 -0
- package/dist/utils/installed.js.map +1 -0
- package/dist/utils/installed.test.d.ts +1 -0
- package/dist/utils/installed.test.js +57 -0
- package/dist/utils/installed.test.js.map +1 -0
- package/dist/utils/interactive.d.ts +9 -0
- package/dist/utils/interactive.js +10 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/interactive.test.d.ts +1 -0
- package/dist/utils/interactive.test.js +24 -0
- package/dist/utils/interactive.test.js.map +1 -0
- package/dist/utils/packages.d.ts +36 -0
- package/dist/utils/packages.js +70 -0
- package/dist/utils/packages.js.map +1 -0
- package/dist/utils/packages.test.d.ts +1 -0
- package/dist/utils/packages.test.js +89 -0
- package/dist/utils/packages.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +17 -1
- package/dist/utils/resolve-paths.js +21 -3
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +38 -2
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +25 -0
- package/dist/utils/tailwind.js +51 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +71 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +13 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -121
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -174
- package/dist/ui/components/button/button-render.test.ts +0 -113
- package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -174
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
- package/dist/ui/components/cartesian-chart/index.ts +0 -18
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -121
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -157
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type OnDestroy,
|
|
2
3
|
type OnInit,
|
|
3
4
|
Component,
|
|
4
5
|
ElementRef,
|
|
@@ -14,7 +15,12 @@ import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
|
14
15
|
|
|
15
16
|
import { type AnimationHandle, DURATION, EASING } from '../../utils/animate';
|
|
16
17
|
import { cn } from '../../utils/cn';
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
type ToastAction,
|
|
20
|
+
type ToastData,
|
|
21
|
+
dismissToast,
|
|
22
|
+
toasts,
|
|
23
|
+
} from './toast-state';
|
|
18
24
|
|
|
19
25
|
// --- Drag-to-dismiss tuning (front toast only) ---
|
|
20
26
|
// Minimum downward travel (px) before releasing dismisses the toast instead of
|
|
@@ -53,7 +59,6 @@ const stackTransform = (depth: number): string =>
|
|
|
53
59
|
*/
|
|
54
60
|
@Component({
|
|
55
61
|
selector: 'ui-toast-item',
|
|
56
|
-
standalone: true,
|
|
57
62
|
imports: [LYNX_ELEMENTS],
|
|
58
63
|
encapsulation: ViewEncapsulation.None,
|
|
59
64
|
// Styling + interaction live on the HOST element (a plain `view`, since the
|
|
@@ -80,13 +85,13 @@ const stackTransform = (depth: number): string =>
|
|
|
80
85
|
}
|
|
81
86
|
</view>
|
|
82
87
|
@if (data().action; as action) {
|
|
83
|
-
<view [class]="actionClass()" (catchtap)="onAction()">
|
|
88
|
+
<view [class]="actionClass()" (catchtap)="onAction(action)">
|
|
84
89
|
<text [class]="actionTextClass()">{{ action.label }}</text>
|
|
85
90
|
</view>
|
|
86
91
|
}
|
|
87
92
|
`,
|
|
88
93
|
})
|
|
89
|
-
export class UiToastItem implements OnInit {
|
|
94
|
+
export class UiToastItem implements OnInit, OnDestroy {
|
|
90
95
|
readonly data = input.required<ToastData>();
|
|
91
96
|
// 0 = front (newest, interactive); higher = further back in the stack.
|
|
92
97
|
readonly depth = input.required<number>();
|
|
@@ -96,6 +101,8 @@ export class UiToastItem implements OnInit {
|
|
|
96
101
|
readonly #host = inject(ElementRef);
|
|
97
102
|
#anim?: AnimationHandle;
|
|
98
103
|
#dismissTimer: ReturnType<typeof setTimeout> | null = null;
|
|
104
|
+
// The one that emits `dismissed` once the exit animation has played.
|
|
105
|
+
#exitTimer: ReturnType<typeof setTimeout> | null = null;
|
|
99
106
|
|
|
100
107
|
// Hidden until the entrance animation runs, so the toast doesn't flash at its
|
|
101
108
|
// resting position for a frame before sliding/fading in.
|
|
@@ -109,28 +116,25 @@ export class UiToastItem implements OnInit {
|
|
|
109
116
|
// unspecified order, and the timer can race both.
|
|
110
117
|
#isDismissing = false;
|
|
111
118
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
132
|
-
/* v8 ignore stop */
|
|
133
|
-
}
|
|
119
|
+
// The depth the restack effect last saw, so it knows where to animate from.
|
|
120
|
+
#stackedDepth: number | null = null;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Restack: when a toast in front is dismissed (or a new one is pushed) this
|
|
124
|
+
* toast's depth changes — animate it to its new slot. A field rather than a
|
|
125
|
+
* constructor: Angular's test build keeps a function for a constructor that
|
|
126
|
+
* nothing calls, which an app's coverage report would show as uncovered.
|
|
127
|
+
*/
|
|
128
|
+
protected readonly restacking = effect(() => {
|
|
129
|
+
const depth = this.depth();
|
|
130
|
+
const from = this.#stackedDepth;
|
|
131
|
+
this.#stackedDepth = depth;
|
|
132
|
+
// `effect` runs after inputs are set, so the first run just records the
|
|
133
|
+
// starting depth. After that it only re-runs once the depth changes; the
|
|
134
|
+
// second check is a safeguard.
|
|
135
|
+
if (from === null || from === depth) return;
|
|
136
|
+
this.#animateRestack(from, depth);
|
|
137
|
+
});
|
|
134
138
|
|
|
135
139
|
ngOnInit(): void {
|
|
136
140
|
// Start the auto-dismiss countdown when the toast mounts — i.e. when it is
|
|
@@ -228,8 +232,12 @@ export class UiToastItem implements OnInit {
|
|
|
228
232
|
this.dismiss();
|
|
229
233
|
}
|
|
230
234
|
|
|
231
|
-
|
|
232
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Runs the toast's action, then dismisses it. The action button only
|
|
237
|
+
* renders for a toast that has an action, which it passes in.
|
|
238
|
+
*/
|
|
239
|
+
protected onAction(action: ToastAction): void {
|
|
240
|
+
action.onAction();
|
|
233
241
|
this.dismiss();
|
|
234
242
|
}
|
|
235
243
|
|
|
@@ -295,13 +303,26 @@ export class UiToastItem implements OnInit {
|
|
|
295
303
|
this.#dismissTimer = null;
|
|
296
304
|
}
|
|
297
305
|
this.#animateOut();
|
|
298
|
-
|
|
306
|
+
this.#exitTimer = setTimeout(
|
|
307
|
+
() => this.dismissed.emit(),
|
|
308
|
+
DURATION.fast + 20,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* A toast can be destroyed before its timers fire: `dismissToast(id)` drops
|
|
314
|
+
* it from the queue straight away, and the toaster itself can be removed.
|
|
315
|
+
* Without this, the auto-dismiss timer later ran `dismiss()` on the destroyed
|
|
316
|
+
* component: it animated a detached element and emitted `dismissed` from a
|
|
317
|
+
* destroyed output, which Angular warns about in dev builds (NG0953).
|
|
318
|
+
*/
|
|
319
|
+
ngOnDestroy(): void {
|
|
320
|
+
if (this.#dismissTimer) clearTimeout(this.#dismissTimer);
|
|
321
|
+
if (this.#exitTimer) clearTimeout(this.#exitTimer);
|
|
299
322
|
}
|
|
300
323
|
|
|
301
324
|
#animateIn(): void {
|
|
302
325
|
const el = this.#host.nativeElement;
|
|
303
|
-
/* v8 ignore next 1 -- host is always mounted when animateIn runs in tests */
|
|
304
|
-
if (!el) return;
|
|
305
326
|
this.#anim?.cancel();
|
|
306
327
|
// Reveal now that the animation owns opacity (0 → 1 below); afterwards the
|
|
307
328
|
// host style keeps it at 1. Without this the resting-position frame before
|
|
@@ -341,32 +362,24 @@ export class UiToastItem implements OnInit {
|
|
|
341
362
|
}
|
|
342
363
|
|
|
343
364
|
/**
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
* v8 ignore start -- only invoked from the restack effect, which never
|
|
348
|
-
* re-runs in JIT (see the effect body above); exercised on device.
|
|
365
|
+
* Move to a new stack slot when depth changes (a toast in front popped, or a
|
|
366
|
+
* new one pushed this one back). This is what makes the stack slide forward
|
|
367
|
+
* when the front toast is dismissed.
|
|
349
368
|
*/
|
|
350
369
|
#animateRestack(from: number, to: number): void {
|
|
351
370
|
// A toast pushed while this one is animating out would shift its depth and
|
|
352
371
|
// restack it back into view — let the exit win instead.
|
|
353
372
|
if (this.#isDismissing) return;
|
|
354
373
|
const el = this.#host.nativeElement;
|
|
355
|
-
if (!el) return;
|
|
356
374
|
this.#anim?.cancel();
|
|
357
375
|
this.#anim = el.animate(
|
|
358
376
|
[{ transform: stackTransform(from) }, { transform: stackTransform(to) }],
|
|
359
377
|
{ duration: DURATION.normal, easing: EASING.standard, fill: 'forwards' },
|
|
360
378
|
);
|
|
361
379
|
}
|
|
362
|
-
/**
|
|
363
|
-
* v8 ignore stop
|
|
364
|
-
*/
|
|
365
380
|
|
|
366
381
|
#animateOut(): void {
|
|
367
382
|
const el = this.#host.nativeElement;
|
|
368
|
-
/* v8 ignore next 1 -- host is always mounted when animateOut runs in tests */
|
|
369
|
-
if (!el) return;
|
|
370
383
|
this.#anim?.cancel();
|
|
371
384
|
|
|
372
385
|
const d = this.depth();
|
|
@@ -414,18 +427,13 @@ export class UiToastItem implements OnInit {
|
|
|
414
427
|
#snapBack(fromOffset: number): void {
|
|
415
428
|
const el = this.#host.nativeElement;
|
|
416
429
|
this.#anim?.cancel();
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
],
|
|
425
|
-
{ duration: DURATION.fast, easing: EASING.spring, fill: 'forwards' },
|
|
426
|
-
);
|
|
427
|
-
}
|
|
428
|
-
/* v8 ignore stop */
|
|
430
|
+
this.#anim = el.animate(
|
|
431
|
+
[
|
|
432
|
+
{ transform: `translateY(${fromOffset}px) scale(1)` },
|
|
433
|
+
{ transform: 'translateY(0px) scale(1)' },
|
|
434
|
+
],
|
|
435
|
+
{ duration: DURATION.fast, easing: EASING.spring, fill: 'forwards' },
|
|
436
|
+
);
|
|
429
437
|
// Match the inline transform to the animation's resting state so the two
|
|
430
438
|
// agree once fill:'forwards' hands control back to the style binding.
|
|
431
439
|
this.dragOffset.set(0);
|
|
@@ -447,7 +455,6 @@ export class UiToastItem implements OnInit {
|
|
|
447
455
|
*/
|
|
448
456
|
@Component({
|
|
449
457
|
selector: 'ui-toaster',
|
|
450
|
-
standalone: true,
|
|
451
458
|
imports: [LYNX_ELEMENTS, UiToastItem],
|
|
452
459
|
encapsulation: ViewEncapsulation.None,
|
|
453
460
|
template: `
|
|
@@ -1,186 +1,293 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import {
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
fireEvent,
|
|
4
|
+
render,
|
|
5
|
+
waitForUpdate,
|
|
6
|
+
} from '@blotch/angular-lynx-testing-library';
|
|
3
7
|
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
4
8
|
import { describe, expect, it } from 'vitest';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
9
|
+
import { fireLynxEvent } from '../../../test-utils/fire-lynx-event';
|
|
10
|
+
import {
|
|
11
|
+
type ToggleSize,
|
|
12
|
+
type ToggleVariant,
|
|
13
|
+
UiToggle,
|
|
14
|
+
UiToggleGroup,
|
|
15
|
+
UiToggleGroupItem,
|
|
16
|
+
} from './toggle';
|
|
7
17
|
|
|
8
18
|
@Component({
|
|
9
|
-
standalone: true,
|
|
10
19
|
imports: [UiToggle, LYNX_ELEMENTS],
|
|
11
|
-
template:
|
|
20
|
+
template: `
|
|
21
|
+
<ui-toggle
|
|
22
|
+
[(pressed)]="bold"
|
|
23
|
+
[variant]="variant()"
|
|
24
|
+
[size]="size()"
|
|
25
|
+
[disabled]="disabled()"
|
|
26
|
+
class="w-20"
|
|
27
|
+
><text>Bold</text></ui-toggle
|
|
28
|
+
>
|
|
29
|
+
`,
|
|
12
30
|
})
|
|
13
|
-
class ToggleHost {
|
|
31
|
+
class ToggleHost {
|
|
32
|
+
readonly bold = signal(false);
|
|
33
|
+
readonly variant = signal<ToggleVariant>('default');
|
|
34
|
+
readonly size = signal<ToggleSize>('default');
|
|
35
|
+
readonly disabled = signal(false);
|
|
36
|
+
}
|
|
14
37
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
38
|
+
@Component({
|
|
39
|
+
imports: [UiToggleGroup, UiToggleGroupItem, LYNX_ELEMENTS],
|
|
40
|
+
template: `
|
|
41
|
+
<ui-toggle-group
|
|
42
|
+
[type]="type()"
|
|
43
|
+
[(value)]="align"
|
|
44
|
+
[variant]="variant()"
|
|
45
|
+
[size]="size()"
|
|
46
|
+
[disabled]="disabled()"
|
|
47
|
+
class="w-full"
|
|
48
|
+
>
|
|
49
|
+
<ui-toggle-group-item value="left" class="flex-1"
|
|
50
|
+
><text>Left</text></ui-toggle-group-item
|
|
51
|
+
>
|
|
52
|
+
<ui-toggle-group-item value="center"
|
|
53
|
+
><text>Center</text></ui-toggle-group-item
|
|
54
|
+
>
|
|
55
|
+
<ui-toggle-group-item value="right"
|
|
56
|
+
><text>Right</text></ui-toggle-group-item
|
|
57
|
+
>
|
|
58
|
+
</ui-toggle-group>
|
|
59
|
+
`,
|
|
60
|
+
})
|
|
61
|
+
class ToggleGroupHost {
|
|
62
|
+
readonly type = signal<'single' | 'multiple'>('single');
|
|
63
|
+
readonly align = signal<string[]>([]);
|
|
64
|
+
readonly variant = signal<ToggleVariant>('default');
|
|
65
|
+
readonly size = signal<ToggleSize>('default');
|
|
66
|
+
readonly disabled = signal(false);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The view (the one that takes presses) a toggle or toggle group item
|
|
71
|
+
* projects its label into.
|
|
72
|
+
*/
|
|
73
|
+
const viewOf = (label: Element) => label.parentElement!;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The scale a view's inline style holds, if any.
|
|
77
|
+
*/
|
|
78
|
+
const scaleOf = (view: Element) =>
|
|
79
|
+
(view as HTMLElement).style.getPropertyValue('transform');
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Presses and releases a view by touch, by mouse, and by a touch a scroll
|
|
83
|
+
* takes over, checking it scales down while pressed and back after each.
|
|
84
|
+
*/
|
|
85
|
+
const expectPressFeedback = (view: Element) => {
|
|
86
|
+
fireEvent.touchstart(view);
|
|
87
|
+
expect(scaleOf(view)).toBe('scale(0.97)');
|
|
88
|
+
fireEvent.touchend(view);
|
|
89
|
+
expect(scaleOf(view)).toBe('scale(1)');
|
|
90
|
+
|
|
91
|
+
fireEvent.touchstart(view);
|
|
92
|
+
fireEvent.touchcancel(view);
|
|
93
|
+
expect(scaleOf(view)).toBe('scale(1)');
|
|
94
|
+
|
|
95
|
+
// Lynx on the web fires mouse events for a cursor, never touch ones.
|
|
96
|
+
fireEvent.mousedown(view);
|
|
97
|
+
expect(scaleOf(view)).toBe('scale(0.97)');
|
|
98
|
+
fireEvent.mouseup(view);
|
|
99
|
+
expect(scaleOf(view)).toBe('scale(1)');
|
|
100
|
+
|
|
101
|
+
fireEvent.mousedown(view);
|
|
102
|
+
fireLynxEvent(view, 'mouseleave');
|
|
103
|
+
expect(scaleOf(view)).toBe('scale(1)');
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Presses a disabled view by touch and by mouse: it shouldn't scale.
|
|
108
|
+
*/
|
|
109
|
+
const expectNoPressFeedback = (view: Element) => {
|
|
110
|
+
fireEvent.touchstart(view);
|
|
111
|
+
fireEvent.mousedown(view);
|
|
112
|
+
expect(scaleOf(view)).toBe('');
|
|
113
|
+
fireEvent.touchend(view);
|
|
114
|
+
fireEvent.mouseup(view);
|
|
115
|
+
expect(scaleOf(view)).toBe('');
|
|
116
|
+
};
|
|
20
117
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
118
|
+
describe('UiToggle', () => {
|
|
119
|
+
const renderToggle = async () => {
|
|
120
|
+
const result = await render(ToggleHost);
|
|
121
|
+
return {
|
|
122
|
+
...result,
|
|
123
|
+
host: result.componentRef.instance as ToggleHost,
|
|
124
|
+
toggle: () => viewOf(result.getByText('Bold')),
|
|
25
125
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
it('renders its content unpressed, with the given class', async () => {
|
|
129
|
+
const { toggle } = await renderToggle();
|
|
130
|
+
expect(toggle().classList).toContain('bg-transparent');
|
|
131
|
+
expect(toggle().classList).toContain('h-10');
|
|
132
|
+
expect(toggle().classList).toContain('w-20');
|
|
133
|
+
expect(toggle().classList).not.toContain('opacity-50');
|
|
30
134
|
});
|
|
31
135
|
|
|
32
|
-
it('
|
|
33
|
-
const {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
136
|
+
it('flips `pressed` and its highlight on each tap', async () => {
|
|
137
|
+
const { host, toggle } = await renderToggle();
|
|
138
|
+
fireEvent.tap(toggle());
|
|
139
|
+
await waitForUpdate();
|
|
140
|
+
expect(host.bold()).toBe(true);
|
|
141
|
+
expect(toggle().classList).toContain('bg-accent');
|
|
142
|
+
|
|
143
|
+
fireEvent.tap(toggle());
|
|
38
144
|
await waitForUpdate();
|
|
39
|
-
|
|
40
|
-
expect(
|
|
145
|
+
expect(host.bold()).toBe(false);
|
|
146
|
+
expect(toggle().classList).toContain('bg-transparent');
|
|
41
147
|
});
|
|
42
148
|
|
|
43
|
-
it('
|
|
44
|
-
const {
|
|
45
|
-
|
|
46
|
-
onPressStart: () => void;
|
|
47
|
-
onPressEnd: () => void;
|
|
48
|
-
onPressCancel: () => void;
|
|
49
|
-
};
|
|
50
|
-
expect(() => inst.onPressStart()).not.toThrow();
|
|
51
|
-
expect(() => inst.onPressEnd()).not.toThrow();
|
|
52
|
-
expect(() => inst.onPressCancel()).not.toThrow();
|
|
53
|
-
setInputSignal((inst as unknown as UiToggle).disabled, true);
|
|
149
|
+
it('highlights when the app presses it', async () => {
|
|
150
|
+
const { host, toggle } = await renderToggle();
|
|
151
|
+
host.bold.set(true);
|
|
54
152
|
await waitForUpdate();
|
|
55
|
-
expect(()
|
|
56
|
-
expect(() => inst.onPressEnd()).not.toThrow();
|
|
57
|
-
expect(() => inst.onPressCancel()).not.toThrow();
|
|
153
|
+
expect(toggle().classList).toContain('bg-accent');
|
|
58
154
|
});
|
|
59
155
|
|
|
60
|
-
it(
|
|
61
|
-
const {
|
|
62
|
-
|
|
63
|
-
inst.pressed.set(true);
|
|
156
|
+
it("dims and ignores taps while it's disabled", async () => {
|
|
157
|
+
const { host, toggle } = await renderToggle();
|
|
158
|
+
host.disabled.set(true);
|
|
64
159
|
await waitForUpdate();
|
|
65
|
-
|
|
66
|
-
|
|
160
|
+
expect(toggle().classList).toContain('opacity-50');
|
|
161
|
+
fireEvent.tap(toggle());
|
|
162
|
+
expect(host.bold()).toBe(false);
|
|
67
163
|
});
|
|
68
164
|
|
|
69
|
-
it('applies
|
|
70
|
-
const {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
setInputSignal(inst.size, 'sm');
|
|
165
|
+
it('applies the classes for its variant and size', async () => {
|
|
166
|
+
const { host, toggle } = await renderToggle();
|
|
167
|
+
host.variant.set('outline');
|
|
168
|
+
host.size.set('sm');
|
|
74
169
|
await waitForUpdate();
|
|
75
|
-
|
|
76
|
-
expect(
|
|
77
|
-
|
|
170
|
+
expect(toggle().classList).toContain('border-border');
|
|
171
|
+
expect(toggle().classList).toContain('h-9');
|
|
172
|
+
|
|
173
|
+
host.size.set('lg');
|
|
174
|
+
await waitForUpdate();
|
|
175
|
+
expect(toggle().classList).toContain('h-11');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('scales down while pressed, and back on release or cancel', async () => {
|
|
179
|
+
const { toggle } = await renderToggle();
|
|
180
|
+
expectPressFeedback(toggle());
|
|
78
181
|
});
|
|
79
182
|
|
|
80
|
-
it('
|
|
81
|
-
const {
|
|
82
|
-
|
|
83
|
-
setInputSignal(inst.size, 'lg');
|
|
183
|
+
it("doesn't react to presses while it's disabled", async () => {
|
|
184
|
+
const { host, toggle } = await renderToggle();
|
|
185
|
+
host.disabled.set(true);
|
|
84
186
|
await waitForUpdate();
|
|
85
|
-
|
|
86
|
-
'h-11',
|
|
87
|
-
);
|
|
187
|
+
expectNoPressFeedback(toggle());
|
|
88
188
|
});
|
|
89
189
|
});
|
|
90
190
|
|
|
91
191
|
describe('UiToggleGroup', () => {
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
expect(inst.value()).toEqual([]);
|
|
102
|
-
});
|
|
192
|
+
const renderGroup = async () => {
|
|
193
|
+
const result = await render(ToggleGroupHost);
|
|
194
|
+
return {
|
|
195
|
+
...result,
|
|
196
|
+
host: result.componentRef.instance as ToggleGroupHost,
|
|
197
|
+
group: () => result.container.querySelector('.gap-1')!,
|
|
198
|
+
item: (label: string) => viewOf(result.getByText(label)),
|
|
199
|
+
};
|
|
200
|
+
};
|
|
103
201
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
202
|
+
/**
|
|
203
|
+
* Taps an item, then lets the selection reach its highlight.
|
|
204
|
+
*/
|
|
205
|
+
const tap = async (item: Element) => {
|
|
206
|
+
fireEvent.tap(item);
|
|
108
207
|
await waitForUpdate();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
expect(
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
it('lays its items out in a row, with the given class', async () => {
|
|
211
|
+
const { group } = await renderGroup();
|
|
212
|
+
expect(group().classList).toContain('flex-row');
|
|
213
|
+
expect(group().classList).toContain('w-full');
|
|
114
214
|
});
|
|
115
215
|
|
|
116
|
-
it('
|
|
117
|
-
const {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
216
|
+
it('selects one item at a time, and deselects it on a second tap', async () => {
|
|
217
|
+
const { host, item } = await renderGroup();
|
|
218
|
+
await tap(item('Left'));
|
|
219
|
+
expect(host.align()).toEqual(['left']);
|
|
220
|
+
expect(item('Left').classList).toContain('bg-accent');
|
|
221
|
+
|
|
222
|
+
await tap(item('Center'));
|
|
223
|
+
expect(host.align()).toEqual(['center']);
|
|
224
|
+
expect(item('Left').classList).toContain('bg-transparent');
|
|
225
|
+
expect(item('Center').classList).toContain('bg-accent');
|
|
226
|
+
|
|
227
|
+
// Unlike a radio group, a single toggle group can have nothing selected.
|
|
228
|
+
await tap(item('Center'));
|
|
229
|
+
expect(host.align()).toEqual([]);
|
|
230
|
+
expect(item('Center').classList).toContain('bg-transparent');
|
|
123
231
|
});
|
|
124
232
|
|
|
125
|
-
it('
|
|
126
|
-
const {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
233
|
+
it('toggles each item on its own when it allows multiple', async () => {
|
|
234
|
+
const { host, item } = await renderGroup();
|
|
235
|
+
host.type.set('multiple');
|
|
236
|
+
await waitForUpdate();
|
|
237
|
+
|
|
238
|
+
await tap(item('Left'));
|
|
239
|
+
await tap(item('Right'));
|
|
240
|
+
expect(host.align()).toEqual(['left', 'right']);
|
|
241
|
+
|
|
242
|
+
await tap(item('Left'));
|
|
243
|
+
expect(host.align()).toEqual(['right']);
|
|
244
|
+
expect(item('Left').classList).toContain('bg-transparent');
|
|
245
|
+
expect(item('Right').classList).toContain('bg-accent');
|
|
131
246
|
});
|
|
132
|
-
});
|
|
133
247
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
parentDisabled = false,
|
|
138
|
-
) => {
|
|
139
|
-
const parentResult = await render(UiToggleGroup);
|
|
140
|
-
const parent = parentResult.componentRef.instance as UiToggleGroup;
|
|
141
|
-
if (parentValue.length) parent.value.set(parentValue);
|
|
142
|
-
if (parentDisabled) setInputSignal(parent.disabled, true);
|
|
248
|
+
it('highlights the items the app selects', async () => {
|
|
249
|
+
const { host, item } = await renderGroup();
|
|
250
|
+
host.align.set(['right']);
|
|
143
251
|
await waitForUpdate();
|
|
252
|
+
expect(item('Right').classList).toContain('bg-accent');
|
|
253
|
+
expect(item('Left').classList).toContain('bg-transparent');
|
|
254
|
+
});
|
|
144
255
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const item = itemResult.componentRef.instance as UiToggleGroupItem;
|
|
149
|
-
setInputSignal(item.itemValue, 'a');
|
|
256
|
+
it('dims every item and ignores taps while disabled', async () => {
|
|
257
|
+
const { host, item } = await renderGroup();
|
|
258
|
+
host.disabled.set(true);
|
|
150
259
|
await waitForUpdate();
|
|
151
|
-
|
|
152
|
-
|
|
260
|
+
for (const label of ['Left', 'Center', 'Right']) {
|
|
261
|
+
expect(item(label).classList).toContain('opacity-50');
|
|
262
|
+
await tap(item(label));
|
|
263
|
+
}
|
|
264
|
+
expect(host.align()).toEqual([]);
|
|
265
|
+
});
|
|
153
266
|
|
|
154
|
-
it(
|
|
155
|
-
const {
|
|
156
|
-
(item
|
|
157
|
-
expect(
|
|
267
|
+
it("styles every item with the group's variant and size, and its own class", async () => {
|
|
268
|
+
const { host, item } = await renderGroup();
|
|
269
|
+
expect(item('Left').classList).toContain('h-10');
|
|
270
|
+
expect(item('Left').classList).toContain('flex-1');
|
|
271
|
+
expect(item('Left').classList).not.toContain('opacity-50');
|
|
272
|
+
|
|
273
|
+
host.variant.set('outline');
|
|
274
|
+
host.size.set('sm');
|
|
275
|
+
await waitForUpdate();
|
|
276
|
+
for (const label of ['Left', 'Center', 'Right']) {
|
|
277
|
+
expect(item(label).classList).toContain('border-border');
|
|
278
|
+
expect(item(label).classList).toContain('h-9');
|
|
279
|
+
}
|
|
158
280
|
});
|
|
159
281
|
|
|
160
|
-
it('
|
|
161
|
-
const {
|
|
162
|
-
|
|
163
|
-
expect(view?.getAttribute('class')).toContain('bg-accent');
|
|
282
|
+
it('scales an item down while pressed, and back on release or cancel', async () => {
|
|
283
|
+
const { item } = await renderGroup();
|
|
284
|
+
expectPressFeedback(item('Center'));
|
|
164
285
|
});
|
|
165
286
|
|
|
166
|
-
it('
|
|
167
|
-
const { item } = await
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
onPressCancel: () => void;
|
|
172
|
-
};
|
|
173
|
-
expect(() => cast.onPressStart()).not.toThrow();
|
|
174
|
-
expect(() => cast.onPressEnd()).not.toThrow();
|
|
175
|
-
|
|
176
|
-
const disabled = await renderItem([], true);
|
|
177
|
-
const cast2 = disabled.item as unknown as {
|
|
178
|
-
onPressStart: () => void;
|
|
179
|
-
onPressEnd: () => void;
|
|
180
|
-
onPressCancel: () => void;
|
|
181
|
-
};
|
|
182
|
-
expect(() => cast2.onPressStart()).not.toThrow();
|
|
183
|
-
expect(() => cast2.onPressEnd()).not.toThrow();
|
|
184
|
-
expect(() => cast2.onPressCancel()).not.toThrow();
|
|
287
|
+
it("doesn't react to presses while the group is disabled", async () => {
|
|
288
|
+
const { host, item } = await renderGroup();
|
|
289
|
+
host.disabled.set(true);
|
|
290
|
+
await waitForUpdate();
|
|
291
|
+
expectNoPressFeedback(item('Center'));
|
|
185
292
|
});
|
|
186
293
|
});
|