@blotch/dolan 22.2.3 → 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.js +9 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +36 -3
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/doctor.js +187 -7
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +280 -7
- 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 +107 -1
- 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 +5 -0
- package/dist/commands/update.js +126 -45
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +314 -2
- package/dist/commands/update.test.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 +6 -0
- package/dist/registry.js +48 -2
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +45 -3
- 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 +5 -4
- package/dist/testing/set-input-signal.js +5 -4
- package/dist/testing/set-input-signal.js.map +1 -1
- 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 +272 -187
- package/dist/ui/components/action-sheet/action-sheet.ts +40 -34
- 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 +206 -139
- package/dist/ui/components/alert-dialog/alert-dialog.ts +32 -26
- package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
- 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 +82 -1
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +373 -297
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +34 -33
- package/dist/ui/components/button/button.test.ts +611 -2
- package/dist/ui/components/button/button.ts +431 -62
- 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 +82 -1
- package/dist/ui/components/card/card.test.ts +82 -62
- package/dist/ui/components/chart/chart-presets.test.ts +197 -1
- package/dist/ui/components/chart/chart-presets.ts +8 -1
- package/dist/ui/components/chart/chart-theme.ts +12 -0
- package/dist/ui/components/chart/chart.test.ts +1017 -0
- package/dist/ui/components/chart/chart.ts +56 -43
- 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 +210 -152
- package/dist/ui/components/dialog/dialog.ts +29 -23
- 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 +36 -0
- package/dist/ui/components/icon/icon-theme.ts +36 -13
- 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 +15 -12
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
- package/dist/ui/components/list/list.test.ts +80 -49
- 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 +425 -260
- package/dist/ui/components/nav-drawer/nav-drawer.ts +72 -56
- package/dist/ui/components/pagination/pagination.test.ts +136 -86
- 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/scatter-chart.test.ts +74 -1
- package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
- 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 +64 -46
- package/dist/ui/components/spinner/spinner.ts +16 -19
- 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 +434 -350
- package/dist/ui/components/toast/toast.ts +42 -51
- package/dist/ui/components/toggle/toggle.test.ts +247 -140
- 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 +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 +12 -0
- package/dist/utils/installed.js +16 -0
- package/dist/utils/installed.js.map +1 -1
- package/dist/utils/installed.test.js +17 -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 +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 +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,345 +1,421 @@
|
|
|
1
1
|
import { Component, signal } from '@angular/core';
|
|
2
|
-
import {
|
|
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
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
type MockInstance,
|
|
10
|
+
afterEach,
|
|
11
|
+
beforeEach,
|
|
12
|
+
describe,
|
|
13
|
+
expect,
|
|
14
|
+
it,
|
|
15
|
+
vi,
|
|
16
|
+
} from 'vitest';
|
|
5
17
|
import { DURATION } from '../../utils/animate';
|
|
6
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
type LynxMainThread,
|
|
20
|
+
lynxMainThread,
|
|
21
|
+
} from '../../../test-utils/lynx-main-thread';
|
|
7
22
|
import { UiBottomSheet } from './bottom-sheet';
|
|
8
23
|
|
|
9
24
|
@Component({
|
|
10
25
|
imports: [UiBottomSheet, LYNX_ELEMENTS],
|
|
11
|
-
template:
|
|
12
|
-
|
|
13
|
-
|
|
26
|
+
template: `
|
|
27
|
+
<ui-bottom-sheet [(open)]="open" class="max-h-96" (closed)="closed()">
|
|
28
|
+
<text>Panel</text>
|
|
29
|
+
</ui-bottom-sheet>
|
|
30
|
+
`,
|
|
14
31
|
})
|
|
15
32
|
class BottomSheetHost {
|
|
16
|
-
open =
|
|
33
|
+
readonly open = signal(false);
|
|
34
|
+
readonly closed = vi.fn();
|
|
17
35
|
}
|
|
18
36
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
(inst as unknown as { backdropRef: unknown }).backdropRef = signal({
|
|
50
|
-
nativeElement: panel,
|
|
51
|
-
});
|
|
52
|
-
(inst as unknown as { panelRef: unknown }).panelRef = signal({
|
|
53
|
-
nativeElement: panel,
|
|
54
|
-
});
|
|
55
|
-
inst.open.set(true);
|
|
56
|
-
for (let i = 0; i < 5; i++) await waitForUpdate();
|
|
57
|
-
expect(invoke).toHaveBeenCalledWith('requestAccessibilityFocus');
|
|
58
|
-
expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('true');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const patchRefs = (inst: UiBottomSheet, view: Element) => {
|
|
62
|
-
(inst as unknown as { backdropRef: unknown }).backdropRef = signal({
|
|
63
|
-
nativeElement: view,
|
|
64
|
-
});
|
|
65
|
-
(inst as unknown as { panelRef: unknown }).panelRef = signal({
|
|
66
|
-
nativeElement: view,
|
|
67
|
-
});
|
|
37
|
+
/**
|
|
38
|
+
* The Lynx function an element's `invoke()` calls to run one of its UI
|
|
39
|
+
* methods, such as moving a screen reader's focus. The testing library
|
|
40
|
+
* defines it on the global object.
|
|
41
|
+
*/
|
|
42
|
+
type InvokeUIMethod = {
|
|
43
|
+
__InvokeUIMethod: (
|
|
44
|
+
element: Element,
|
|
45
|
+
method: string,
|
|
46
|
+
params: Record<string, unknown>,
|
|
47
|
+
callback: (result: { code: number }) => void,
|
|
48
|
+
) => void;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** `__ElementAnimate`'s first argument for starting an animation. */
|
|
52
|
+
const START = 0;
|
|
53
|
+
/** `__ElementAnimate`'s first argument for canceling an animation. */
|
|
54
|
+
const CANCEL = 3;
|
|
55
|
+
|
|
56
|
+
const renderSheet = async () => {
|
|
57
|
+
const result = await render(BottomSheetHost);
|
|
58
|
+
const { container } = result;
|
|
59
|
+
return {
|
|
60
|
+
...result,
|
|
61
|
+
host: result.componentRef.instance as BottomSheetHost,
|
|
62
|
+
overlay: container.querySelector('overlay')!,
|
|
63
|
+
backdrop: container.querySelector('overlay > view')!,
|
|
64
|
+
panel: container.querySelector('.rounded-t-2xl')!,
|
|
65
|
+
// The strip around the grabber pill, which takes the drag.
|
|
66
|
+
handle: container.querySelector('.bg-muted')!.parentElement!,
|
|
68
67
|
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Opens the sheet and waits out its open sequence: the overlay shows on one
|
|
72
|
+
* tick, and the panel slides in on the next.
|
|
73
|
+
*/
|
|
74
|
+
const openSheet = async (host: BottomSheetHost) => {
|
|
75
|
+
host.open.set(true);
|
|
76
|
+
for (let i = 0; i < 3; i++) await waitForUpdate();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Waits for the close sequence to finish: the panel slides out, then the
|
|
81
|
+
* overlay hides and `closed` fires. Polled rather than timed, since the
|
|
82
|
+
* sequence only starts on the update after `open` changes.
|
|
83
|
+
*/
|
|
84
|
+
const waitForClose = async (host: BottomSheetHost) => {
|
|
85
|
+
await vi.waitFor(() => expect(host.closed).toHaveBeenCalled());
|
|
86
|
+
await waitForUpdate();
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The panel's live drag offset, from its inline transform.
|
|
91
|
+
*/
|
|
92
|
+
const transformOf = (panel: Element) =>
|
|
93
|
+
(panel as HTMLElement).style.getPropertyValue('transform');
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The touch fields a handle's touch event carries.
|
|
97
|
+
*/
|
|
98
|
+
const touchAt = (clientY: number) => ({
|
|
99
|
+
eventType: 'catchEvent',
|
|
100
|
+
touches: [{ clientY }],
|
|
101
|
+
});
|
|
69
102
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
describe('UiBottomSheet', () => {
|
|
104
|
+
// Watched per test: the testing library resets the main thread after each.
|
|
105
|
+
let animate: MockInstance<LynxMainThread['__ElementAnimate']>;
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
animate = vi.spyOn(lynxMainThread(), '__ElementAnimate');
|
|
73
108
|
});
|
|
109
|
+
afterEach(() => animate.mockRestore());
|
|
74
110
|
|
|
75
|
-
it('
|
|
76
|
-
const {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
await waitForUpdate();
|
|
80
|
-
(inst as unknown as { onBackdropTap: () => void }).onBackdropTap();
|
|
81
|
-
expect(inst.open()).toBe(false);
|
|
82
|
-
});
|
|
111
|
+
it('stays hidden until opened, then shows the panel and its content', async () => {
|
|
112
|
+
const { host, overlay, getByText } = await renderSheet();
|
|
113
|
+
expect(overlay.getAttribute('style')).toContain('display: none');
|
|
114
|
+
expect(overlay.getAttribute('visible')).toBe('false');
|
|
83
115
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
expect(()
|
|
88
|
-
(inst as unknown as { onPanelTap: () => void }).onPanelTap(),
|
|
89
|
-
).not.toThrow();
|
|
116
|
+
await openSheet(host);
|
|
117
|
+
expect(overlay.getAttribute('style')).not.toContain('display: none');
|
|
118
|
+
expect(overlay.getAttribute('visible')).toBe('true');
|
|
119
|
+
expect(getByText('Panel')).toBeTruthy();
|
|
90
120
|
});
|
|
91
121
|
|
|
92
|
-
it(
|
|
93
|
-
const {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
patchRefs(inst, view);
|
|
97
|
-
|
|
98
|
-
inst.open.set(true);
|
|
99
|
-
await waitForUpdate();
|
|
100
|
-
await waitForUpdate();
|
|
101
|
-
await waitForUpdate();
|
|
102
|
-
|
|
103
|
-
inst.open.set(false);
|
|
104
|
-
await new Promise((r) => setTimeout(r, 320));
|
|
105
|
-
await waitForUpdate();
|
|
106
|
-
expect(inst.open()).toBe(false);
|
|
122
|
+
it("adds the class to the panel's own", async () => {
|
|
123
|
+
const { panel } = await renderSheet();
|
|
124
|
+
expect(panel.classList).toContain('max-h-96');
|
|
125
|
+
expect(panel.classList).toContain('bg-background');
|
|
107
126
|
});
|
|
108
127
|
|
|
109
|
-
it('
|
|
110
|
-
const {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
it('fades the backdrop in and slides the panel up when opened', async () => {
|
|
129
|
+
const { host, backdrop, panel } = await renderSheet();
|
|
130
|
+
await openSheet(host);
|
|
131
|
+
expect(animate).toHaveBeenCalledWith(backdrop, [
|
|
132
|
+
START,
|
|
133
|
+
expect.any(String),
|
|
134
|
+
[{ opacity: 0 }, { opacity: 1 }],
|
|
135
|
+
expect.objectContaining({ duration: DURATION.normal }),
|
|
136
|
+
]);
|
|
137
|
+
expect(animate).toHaveBeenCalledWith(panel, [
|
|
138
|
+
START,
|
|
139
|
+
expect.any(String),
|
|
140
|
+
[
|
|
141
|
+
{ transform: 'translateY(100%)', opacity: 0.8 },
|
|
142
|
+
{ transform: 'translateY(0%)', opacity: 1 },
|
|
143
|
+
],
|
|
144
|
+
expect.objectContaining({ duration: DURATION.slow }),
|
|
145
|
+
]);
|
|
124
146
|
});
|
|
125
147
|
|
|
126
|
-
it('
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
patchRefs(inst, view);
|
|
131
|
-
const animate = vi.spyOn(
|
|
132
|
-
view as Element & { animate: () => unknown },
|
|
133
|
-
'animate',
|
|
148
|
+
it('moves a screen reader into the panel and keeps it there while open', async () => {
|
|
149
|
+
const invoke = vi.spyOn(
|
|
150
|
+
globalThis as unknown as InvokeUIMethod,
|
|
151
|
+
'__InvokeUIMethod',
|
|
134
152
|
);
|
|
135
|
-
|
|
136
|
-
|
|
153
|
+
const { host, panel } = await renderSheet();
|
|
154
|
+
expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('false');
|
|
137
155
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
expect(inst.open()).toBe(false);
|
|
148
|
-
// The exit slide starts the next tick, from where the finger let go
|
|
149
|
-
// rather than from the docked position.
|
|
150
|
-
await new Promise((r) => setTimeout(r, 0));
|
|
151
|
-
expect(
|
|
152
|
-
animate.mock.calls.some(
|
|
153
|
-
([keyframes]) =>
|
|
154
|
-
(keyframes as Keyframe[])[0]?.transform === 'translateY(200px)',
|
|
155
|
-
),
|
|
156
|
-
).toBe(true);
|
|
156
|
+
await openSheet(host);
|
|
157
|
+
expect(invoke).toHaveBeenCalledWith(
|
|
158
|
+
panel,
|
|
159
|
+
'requestAccessibilityFocus',
|
|
160
|
+
expect.anything(),
|
|
161
|
+
expect.any(Function),
|
|
162
|
+
);
|
|
163
|
+
expect(panel.getAttribute('accessibility-exclusive-focus')).toBe('true');
|
|
164
|
+
invoke.mockRestore();
|
|
157
165
|
});
|
|
158
166
|
|
|
159
|
-
it('
|
|
160
|
-
const {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
await
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
167
|
+
it('closes when the backdrop is tapped, sliding the panel down', async () => {
|
|
168
|
+
const { host, overlay, backdrop, panel } = await renderSheet();
|
|
169
|
+
await openSheet(host);
|
|
170
|
+
|
|
171
|
+
fireEvent.tap(backdrop);
|
|
172
|
+
expect(host.open()).toBe(false);
|
|
173
|
+
await waitForClose(host);
|
|
174
|
+
expect(animate).toHaveBeenCalledWith(backdrop, [
|
|
175
|
+
START,
|
|
176
|
+
expect.any(String),
|
|
177
|
+
[{ opacity: 1 }, { opacity: 0 }],
|
|
178
|
+
expect.objectContaining({ duration: DURATION.normal }),
|
|
179
|
+
]);
|
|
180
|
+
expect(animate).toHaveBeenCalledWith(panel, [
|
|
181
|
+
START,
|
|
182
|
+
expect.any(String),
|
|
183
|
+
[
|
|
184
|
+
{ transform: 'translateY(0%)', opacity: 1 },
|
|
185
|
+
{ transform: 'translateY(100%)', opacity: 0.8 },
|
|
186
|
+
],
|
|
187
|
+
expect.objectContaining({ duration: DURATION.normal }),
|
|
188
|
+
]);
|
|
189
|
+
// Once the slide is over, the overlay hides and `closed` fires, only for
|
|
190
|
+
// this close: a sheet that starts closed hasn't closed before.
|
|
191
|
+
expect(overlay.getAttribute('style')).toContain('display: none');
|
|
192
|
+
expect(host.closed).toHaveBeenCalledOnce();
|
|
177
193
|
});
|
|
178
194
|
|
|
179
|
-
it('
|
|
180
|
-
const {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
onDragEnd: () => void;
|
|
186
|
-
};
|
|
187
|
-
// touchmove without prior touchstart is a no-op (guards `#isDragging`).
|
|
188
|
-
cast.onHandleTouchMove({ touches: [{ clientY: 200 }] });
|
|
189
|
-
// touchstart / touchmove without touches entries is a no-op.
|
|
190
|
-
cast.onHandleTouchStart({ touches: [] });
|
|
191
|
-
cast.onHandleTouchStart({ touches: [{ clientY: 100 }] });
|
|
192
|
-
cast.onHandleTouchMove({ touches: [] });
|
|
193
|
-
// touchend without an active drag is also a no-op.
|
|
194
|
-
cast.onDragEnd();
|
|
195
|
-
cast.onDragEnd();
|
|
195
|
+
it('stays open when the panel is tapped', async () => {
|
|
196
|
+
const { host, panel } = await renderSheet();
|
|
197
|
+
await openSheet(host);
|
|
198
|
+
// The panel catches its taps, so they never reach the backdrop.
|
|
199
|
+
fireEvent.tap(panel, { eventType: 'catchEvent' });
|
|
200
|
+
expect(host.open()).toBe(true);
|
|
196
201
|
});
|
|
197
202
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
onBackdropTap: () => void;
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
it('mouse drag beyond the dismiss threshold closes the sheet', async () => {
|
|
207
|
-
const { componentRef, container } = await render(UiBottomSheet);
|
|
208
|
-
const inst = componentRef.instance as UiBottomSheet;
|
|
209
|
-
patchRefs(inst, container.querySelector('view') as Element);
|
|
210
|
-
inst.open.set(true);
|
|
211
|
-
await waitForUpdate();
|
|
212
|
-
|
|
213
|
-
const cast = inst as unknown as MouseDrag;
|
|
214
|
-
cast.onHandleMouseDown({ y: 100 });
|
|
215
|
-
cast.onMouseDragMove({ y: 300, buttons: 1 });
|
|
216
|
-
cast.onDragEnd();
|
|
203
|
+
it('stops its close timers when it is destroyed while closing', async () => {
|
|
204
|
+
const warn = vi.spyOn(console, 'warn');
|
|
205
|
+
const { host, unmount } = await renderSheet();
|
|
206
|
+
await openSheet(host);
|
|
207
|
+
host.open.set(false);
|
|
217
208
|
await waitForUpdate();
|
|
218
|
-
|
|
209
|
+
// Destroyed during the close animation, as an `@if` around the sheet
|
|
210
|
+
// would do. Its close timer (DURATION.normal + 20) used to fire anyway
|
|
211
|
+
// and emit `closed` from the destroyed output.
|
|
212
|
+
unmount();
|
|
213
|
+
await new Promise((resolve) => setTimeout(resolve, DURATION.normal + 50));
|
|
214
|
+
const warnings = warn.mock.calls.map(([message]) => String(message));
|
|
215
|
+
warn.mockRestore();
|
|
216
|
+
expect(warnings.filter((message) => message.includes('NG0953'))).toEqual(
|
|
217
|
+
[],
|
|
218
|
+
);
|
|
219
|
+
expect(host.closed).not.toHaveBeenCalled();
|
|
219
220
|
});
|
|
220
221
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
222
|
+
describe('dragged by its handle', () => {
|
|
223
|
+
it('follows the finger down, lightening the backdrop as it goes', async () => {
|
|
224
|
+
const { host, backdrop, panel, handle } = await renderSheet();
|
|
225
|
+
await openSheet(host);
|
|
226
|
+
animate.mockClear();
|
|
227
|
+
|
|
228
|
+
fireEvent.touchstart(handle, touchAt(100));
|
|
229
|
+
// The open slide holds the panel in place until it's canceled.
|
|
230
|
+
expect(animate).toHaveBeenCalledWith(panel, [CANCEL, expect.any(String)]);
|
|
231
|
+
fireEvent.touchmove(handle, touchAt(200));
|
|
232
|
+
await waitForUpdate();
|
|
233
|
+
expect(transformOf(panel)).toBe('translateY(100px)');
|
|
234
|
+
// 100px of the 500px over which the backdrop clears: 4/5 of its dim.
|
|
235
|
+
expect(backdrop.getAttribute('style')).toContain('rgba(0, 0, 0, 0.4)');
|
|
236
|
+
});
|
|
227
237
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
it('closes when released past the threshold, sliding on from the finger', async () => {
|
|
239
|
+
const { host, panel, handle } = await renderSheet();
|
|
240
|
+
await openSheet(host);
|
|
241
|
+
|
|
242
|
+
fireEvent.touchstart(handle, touchAt(100));
|
|
243
|
+
fireEvent.touchmove(handle, touchAt(300));
|
|
244
|
+
fireEvent.touchend(handle, { eventType: 'catchEvent' });
|
|
245
|
+
expect(host.open()).toBe(false);
|
|
246
|
+
await waitForClose(host);
|
|
247
|
+
expect(animate).toHaveBeenCalledWith(panel, [
|
|
248
|
+
START,
|
|
249
|
+
expect.any(String),
|
|
250
|
+
[
|
|
251
|
+
{ transform: 'translateY(200px)', opacity: 1 },
|
|
252
|
+
{ transform: 'translateY(600px)', opacity: 0.8 },
|
|
253
|
+
],
|
|
254
|
+
expect.objectContaining({ duration: DURATION.normal }),
|
|
255
|
+
]);
|
|
256
|
+
expect(host.closed).toHaveBeenCalledOnce();
|
|
257
|
+
// Hidden, it's back in place for the next open.
|
|
258
|
+
expect(transformOf(panel)).toBe('translateY(0px)');
|
|
259
|
+
});
|
|
235
260
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
261
|
+
it('springs back when released or canceled short of the threshold', async () => {
|
|
262
|
+
const { host, panel, handle } = await renderSheet();
|
|
263
|
+
await openSheet(host);
|
|
264
|
+
|
|
265
|
+
fireEvent.touchstart(handle, touchAt(100));
|
|
266
|
+
fireEvent.touchmove(handle, touchAt(130));
|
|
267
|
+
fireEvent.touchend(handle, { eventType: 'catchEvent' });
|
|
268
|
+
await waitForUpdate();
|
|
269
|
+
expect(host.open()).toBe(true);
|
|
270
|
+
expect(animate).toHaveBeenCalledWith(panel, [
|
|
271
|
+
START,
|
|
272
|
+
expect.any(String),
|
|
273
|
+
[{ transform: 'translateY(30px)' }, { transform: 'translateY(0px)' }],
|
|
274
|
+
expect.objectContaining({ duration: DURATION.fast }),
|
|
275
|
+
]);
|
|
276
|
+
expect(transformOf(panel)).toBe('translateY(0px)');
|
|
277
|
+
|
|
278
|
+
// A scroll that takes the touch over cancels it the same way.
|
|
279
|
+
fireEvent.touchstart(handle, touchAt(100));
|
|
280
|
+
fireEvent.touchmove(handle, touchAt(150));
|
|
281
|
+
fireEvent.touchcancel(handle, { eventType: 'catchEvent' });
|
|
282
|
+
await waitForUpdate();
|
|
283
|
+
expect(host.open()).toBe(true);
|
|
284
|
+
expect(transformOf(panel)).toBe('translateY(0px)');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("doesn't move up past where it's docked", async () => {
|
|
288
|
+
const { host, panel, handle } = await renderSheet();
|
|
289
|
+
await openSheet(host);
|
|
290
|
+
fireEvent.touchstart(handle, touchAt(100));
|
|
291
|
+
fireEvent.touchmove(handle, touchAt(40));
|
|
292
|
+
await waitForUpdate();
|
|
293
|
+
expect(transformOf(panel)).toBe('translateY(0px)');
|
|
294
|
+
});
|
|
241
295
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
296
|
+
it('ignores touches without a position, and moves outside a drag', async () => {
|
|
297
|
+
const { host, panel, handle } = await renderSheet();
|
|
298
|
+
await openSheet(host);
|
|
299
|
+
|
|
300
|
+
// A move or release with no drag under way does nothing.
|
|
301
|
+
fireEvent.touchmove(handle, touchAt(300));
|
|
302
|
+
fireEvent.touchend(handle, { eventType: 'catchEvent' });
|
|
303
|
+
// Nor does a touch whose position is missing.
|
|
304
|
+
fireEvent.touchstart(handle, { eventType: 'catchEvent' });
|
|
305
|
+
fireEvent.touchmove(handle, touchAt(300));
|
|
306
|
+
fireEvent.touchstart(handle, { eventType: 'catchEvent', touches: [] });
|
|
307
|
+
fireEvent.touchmove(handle, touchAt(300));
|
|
308
|
+
await waitForUpdate();
|
|
309
|
+
expect(transformOf(panel)).toBe('translateY(0px)');
|
|
310
|
+
|
|
311
|
+
fireEvent.touchstart(handle, touchAt(100));
|
|
312
|
+
fireEvent.touchmove(handle, { eventType: 'catchEvent', touches: [] });
|
|
313
|
+
await waitForUpdate();
|
|
314
|
+
expect(transformOf(panel)).toBe('translateY(0px)');
|
|
315
|
+
expect(host.open()).toBe(true);
|
|
316
|
+
});
|
|
254
317
|
});
|
|
255
318
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
319
|
+
describe('dragged by mouse', () => {
|
|
320
|
+
/**
|
|
321
|
+
* Presses the handle, which catches its own mousedown.
|
|
322
|
+
*/
|
|
323
|
+
const press = (handle: Element, y: number) =>
|
|
324
|
+
fireEvent.mousedown(handle, { eventType: 'catchEvent', y });
|
|
325
|
+
/**
|
|
326
|
+
* Moves the mouse, which the backdrop sees anywhere in the sheet.
|
|
327
|
+
*/
|
|
328
|
+
const move = (backdrop: Element, y: number, buttons = 1) =>
|
|
329
|
+
fireEvent.mousemove(backdrop, { y, buttons });
|
|
330
|
+
|
|
331
|
+
it('closes when released past the threshold', async () => {
|
|
332
|
+
const { host, backdrop, panel, handle } = await renderSheet();
|
|
333
|
+
await openSheet(host);
|
|
334
|
+
|
|
335
|
+
press(handle, 100);
|
|
336
|
+
move(backdrop, 300);
|
|
337
|
+
await waitForUpdate();
|
|
338
|
+
expect(transformOf(panel)).toBe('translateY(200px)');
|
|
339
|
+
fireEvent.mouseup(backdrop);
|
|
340
|
+
expect(host.open()).toBe(false);
|
|
341
|
+
});
|
|
261
342
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
cast.onMouseDragMove({ y: 120, buttons: 1 });
|
|
266
|
-
cast.onDragEnd();
|
|
267
|
-
// A real backdrop click starts with a mousedown on the backdrop.
|
|
268
|
-
cast.onBackdropMouseDown();
|
|
269
|
-
cast.onBackdropTap();
|
|
270
|
-
expect(inst.open()).toBe(false);
|
|
271
|
-
});
|
|
343
|
+
it('springs back when released short of the threshold', async () => {
|
|
344
|
+
const { host, backdrop, panel, handle } = await renderSheet();
|
|
345
|
+
await openSheet(host);
|
|
272
346
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
347
|
+
press(handle, 100);
|
|
348
|
+
move(backdrop, 130);
|
|
349
|
+
fireEvent.mouseup(backdrop);
|
|
350
|
+
await waitForUpdate();
|
|
351
|
+
expect(host.open()).toBe(true);
|
|
352
|
+
expect(transformOf(panel)).toBe('translateY(0px)');
|
|
353
|
+
});
|
|
278
354
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
355
|
+
it('ignores the backdrop click that follows a moved drag', async () => {
|
|
356
|
+
const { host, backdrop, handle } = await renderSheet();
|
|
357
|
+
await openSheet(host);
|
|
358
|
+
|
|
359
|
+
// A drag released above the panel: the browser's click lands on the
|
|
360
|
+
// backdrop, and must not close the sheet that just snapped back.
|
|
361
|
+
press(handle, 100);
|
|
362
|
+
move(backdrop, 40);
|
|
363
|
+
fireEvent.mouseup(backdrop);
|
|
364
|
+
fireEvent.tap(backdrop);
|
|
365
|
+
expect(host.open()).toBe(true);
|
|
366
|
+
|
|
367
|
+
// Only that one click is ignored: the next backdrop tap closes it.
|
|
368
|
+
fireEvent.tap(backdrop);
|
|
369
|
+
expect(host.open()).toBe(false);
|
|
370
|
+
});
|
|
292
371
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
372
|
+
it('a new press clears an ignore no click consumed', async () => {
|
|
373
|
+
const { host, backdrop, handle } = await renderSheet();
|
|
374
|
+
await openSheet(host);
|
|
375
|
+
|
|
376
|
+
// Released over the panel, so its catchtap ate the click.
|
|
377
|
+
press(handle, 100);
|
|
378
|
+
move(backdrop, 120);
|
|
379
|
+
fireEvent.mouseup(backdrop);
|
|
380
|
+
// A real backdrop click starts with a mousedown on the backdrop.
|
|
381
|
+
fireEvent.mousedown(backdrop);
|
|
382
|
+
fireEvent.tap(backdrop);
|
|
383
|
+
expect(host.open()).toBe(false);
|
|
384
|
+
});
|
|
299
385
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
386
|
+
it('a press without a move, or a touch drag, leaves the backdrop tap alone', async () => {
|
|
387
|
+
const { host, backdrop, handle } = await renderSheet();
|
|
388
|
+
await openSheet(host);
|
|
389
|
+
|
|
390
|
+
press(handle, 100);
|
|
391
|
+
fireEvent.mouseup(backdrop);
|
|
392
|
+
// A moved touch is never followed by a tap, so nothing is ignored.
|
|
393
|
+
fireEvent.touchstart(handle, touchAt(100));
|
|
394
|
+
fireEvent.touchmove(handle, touchAt(120));
|
|
395
|
+
fireEvent.touchend(handle, { eventType: 'catchEvent' });
|
|
396
|
+
fireEvent.tap(backdrop);
|
|
397
|
+
expect(host.open()).toBe(false);
|
|
398
|
+
});
|
|
309
399
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
// The cursor moving over an open sheet with no button pressed.
|
|
314
|
-
(inst as unknown as MouseDrag).onMouseDragMove({ y: 300, buttons: 1 });
|
|
315
|
-
expect((inst as unknown as { dragOffset: () => number }).dragOffset()).toBe(
|
|
316
|
-
0,
|
|
317
|
-
);
|
|
318
|
-
});
|
|
400
|
+
it('ends a drag whose release it never saw', async () => {
|
|
401
|
+
const { host, backdrop, handle } = await renderSheet();
|
|
402
|
+
await openSheet(host);
|
|
319
403
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
el.getAttribute('class')?.includes('custom-panel'),
|
|
328
|
-
),
|
|
329
|
-
).toBe(true);
|
|
330
|
-
});
|
|
404
|
+
press(handle, 100);
|
|
405
|
+
move(backdrop, 300);
|
|
406
|
+
// The button came up outside the window; the cursor is back without it.
|
|
407
|
+
move(backdrop, 120, 0);
|
|
408
|
+
// Released 200px down, past the threshold, so it closes.
|
|
409
|
+
expect(host.open()).toBe(false);
|
|
410
|
+
});
|
|
331
411
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
inst.open.set(true);
|
|
341
|
-
for (let i = 0; i < 10; i++) await waitForUpdate();
|
|
342
|
-
overlay = container.querySelector('overlay');
|
|
343
|
-
expect(overlay?.getAttribute('style')).not.toContain('display: none');
|
|
412
|
+
it('a move outside a drag leaves the sheet in place', async () => {
|
|
413
|
+
const { host, backdrop, panel } = await renderSheet();
|
|
414
|
+
await openSheet(host);
|
|
415
|
+
// The cursor passing over an open sheet with a button held elsewhere.
|
|
416
|
+
move(backdrop, 300);
|
|
417
|
+
await waitForUpdate();
|
|
418
|
+
expect(transformOf(panel)).toBe('translateY(0px)');
|
|
419
|
+
});
|
|
344
420
|
});
|
|
345
421
|
});
|