• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

adobe / spectrum-web-components / 12282443963

11 Dec 2024 06:17PM UTC coverage: 97.436% (-0.8%) from 98.206%
12282443963

Pull #4989

github

web-flow
Merge 92e762395 into 5b674b468
Pull Request #4989: chore: testing something

4996 of 5296 branches covered (94.34%)

Branch coverage included in aggregate %.

7 of 19 new or added lines in 1 file covered. (36.84%)

257 existing lines in 8 files now uncovered.

32739 of 33432 relevant lines covered (97.93%)

380.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.05
/packages/overlay/src/OverlayDialog.ts
1
/*
38✔
2
Copyright 2020 Adobe. All rights reserved.
38✔
3
This file is licensed to you under the Apache License, Version 2.0 (the "License");
38✔
4
you may not use this file except in compliance with the License. You may obtain a copy
38✔
5
of the License at http://www.apache.org/licenses/LICENSE-2.0
38✔
6

38✔
7
Unless required by applicable law or agreed to in writing, software distributed under
38✔
8
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
38✔
9
OF ANY KIND, either express or implied. See the License for the specific language
38✔
10
governing permissions and limitations under the License.
38✔
11
*/
38✔
12
import type { SpectrumElement } from '@spectrum-web-components/base';
38✔
13
import {
38✔
14
    firstFocusableIn,
38✔
15
    firstFocusableSlottedIn,
38✔
16
} from '@spectrum-web-components/shared/src/first-focusable-in.js';
38✔
17
import { VirtualTrigger } from './VirtualTrigger.js';
38✔
18
import { Constructor, OpenableElement } from './overlay-types.js';
38✔
19
import { guaranteedAllTransitionend, nextFrame } from './AbstractOverlay.js';
38✔
20
import {
38✔
21
    BeforetoggleClosedEvent,
38✔
22
    BeforetoggleOpenEvent,
38✔
23
    OverlayStateEvent,
38✔
24
} from './events.js';
38✔
25
import type { AbstractOverlay } from './AbstractOverlay.js';
38✔
26
import { userFocusableSelector } from '@spectrum-web-components/shared';
38✔
27

38✔
28
export function OverlayDialog<T extends Constructor<AbstractOverlay>>(
38✔
29
    constructor: T
38✔
30
): T & Constructor<SpectrumElement> {
38✔
31
    class OverlayWithDialog extends constructor {
38✔
32
        protected override async manageDialogOpen(): Promise<void> {
38✔
33
            const targetOpenState = this.open;
40✔
34
            await nextFrame();
40✔
35
            await this.managePosition();
40✔
36
            if (this.open !== targetOpenState) {
40✔
37
                return;
×
38
            }
×
39
            const focusEl = await this.dialogMakeTransition(targetOpenState);
40✔
40
            if (this.open !== targetOpenState) {
40✔
41
                return;
×
42
            }
×
43
            await this.dialogApplyFocus(targetOpenState, focusEl);
40✔
44
        }
40✔
45

38✔
46
        protected async dialogMakeTransition(
38✔
47
            targetOpenState: boolean
40✔
48
        ): Promise<HTMLElement | null> {
40✔
49
            let focusEl = null as HTMLElement | null;
40✔
50
            const start =
40✔
51
                (el: OpenableElement, index: number) =>
40✔
52
                async (): Promise<void> => {
40✔
53
                    el.open = targetOpenState;
40✔
54
                    if (!targetOpenState) {
40✔
55
                        const close = (): void => {
20✔
UNCOV
56
                            el.removeEventListener('close', close);
×
UNCOV
57
                            finish(el, index);
×
UNCOV
58
                        };
×
59
                        el.addEventListener('close', close);
20✔
60
                    }
20✔
61
                    if (index > 0) {
40✔
62
                        // Announce workflow on the first element _only_.
×
63
                        return;
×
64
                    }
×
65
                    const event = targetOpenState
40✔
66
                        ? BeforetoggleOpenEvent
20✔
67
                        : BeforetoggleClosedEvent;
20✔
68
                    this.dispatchEvent(new event());
40✔
69
                    if (!targetOpenState) {
40✔
70
                        // Show/focus workflow when opening _only_.
20✔
71
                        return;
20✔
72
                    }
20✔
73
                    if (el.matches(userFocusableSelector)) {
40✔
UNCOV
74
                        focusEl = el;
×
UNCOV
75
                    }
✔
76
                    focusEl = focusEl || firstFocusableIn(el);
20✔
77
                    if (!focusEl) {
40✔
78
                        const childSlots = el.querySelectorAll('slot');
17✔
79
                        childSlots.forEach((slot) => {
17✔
80
                            if (!focusEl) {
×
81
                                focusEl = firstFocusableSlottedIn(slot);
×
82
                            }
×
83
                        });
17✔
84
                    }
17✔
85
                    if (!this.isConnected || this.dialogEl.open) {
40✔
86
                        // In both of these cases the browser will error.
×
87
                        // You can neither "reopen" a <dialog> or open one that is not on the DOM.
×
88
                        return;
×
89
                    }
✔
90
                    this.dialogEl.showModal();
20✔
91
                };
40✔
92
            const finish = (el: OpenableElement, index: number) => (): void => {
40✔
93
                if (this.open !== targetOpenState) {
40✔
94
                    return;
×
95
                }
×
96
                const eventName = targetOpenState ? 'sp-opened' : 'sp-closed';
40✔
97
                if (index > 0) {
40✔
98
                    el.dispatchEvent(
×
99
                        new OverlayStateEvent(eventName, this, {
×
100
                            interaction: this.type,
×
101
                            publish: false,
×
102
                        })
×
103
                    );
×
104
                    return;
×
105
                }
×
106
                if (!this.isConnected || targetOpenState !== this.open) {
40✔
107
                    // Don't lead into the `.close()` workflow if not connected to the DOM.
9✔
108
                    // The browser will error in this case.
9✔
109
                    return;
9✔
110
                }
9✔
111
                const reportChange = async (): Promise<void> => {
31✔
112
                    const hasVirtualTrigger =
31✔
113
                        this.triggerElement instanceof VirtualTrigger;
31✔
114
                    this.dispatchEvent(
31✔
115
                        new OverlayStateEvent(eventName, this, {
31✔
116
                            interaction: this.type,
31✔
117
                            publish: hasVirtualTrigger,
31✔
118
                        })
31✔
119
                    );
31✔
120
                    el.dispatchEvent(
31✔
121
                        new OverlayStateEvent(eventName, this, {
31✔
122
                            interaction: this.type,
31✔
123
                            publish: false,
31✔
124
                        })
31✔
125
                    );
31✔
126
                    if (this.triggerElement && !hasVirtualTrigger) {
31✔
127
                        (this.triggerElement as HTMLElement).dispatchEvent(
3✔
128
                            new OverlayStateEvent(eventName, this, {
3✔
129
                                interaction: this.type,
3✔
130
                                publish: true,
3✔
131
                            })
3✔
132
                        );
3✔
133
                    }
3✔
134
                    this.state = targetOpenState ? 'opened' : 'closed';
31✔
135
                    this.returnFocus();
31✔
136
                    // Ensure layout and paint are done and the Overlay is still closed before removing the slottable request.
31✔
137
                    await nextFrame();
31✔
138
                    await nextFrame();
31✔
139
                    if (
31✔
140
                        targetOpenState === this.open &&
31✔
141
                        targetOpenState === false
19✔
142
                    ) {
31✔
143
                        this.requestSlottable();
9✔
144
                    }
9✔
145
                };
31✔
146
                if (!targetOpenState && this.dialogEl.open) {
40✔
147
                    this.dialogEl.addEventListener(
11✔
148
                        'close',
11✔
149
                        () => {
11✔
150
                            reportChange();
11✔
151
                        },
11✔
152
                        { once: true }
11✔
153
                    );
11✔
154
                    this.dialogEl.close();
11✔
155
                } else {
40✔
156
                    reportChange();
20✔
157
                }
20✔
158
            };
40✔
159
            this.elements.forEach((el, index) => {
40✔
160
                guaranteedAllTransitionend(
40✔
161
                    el,
40✔
162
                    start(el, index),
40✔
163
                    finish(el, index)
40✔
164
                );
40✔
165
            });
40✔
166
            return focusEl;
40✔
167
        }
40✔
168

38✔
169
        protected async dialogApplyFocus(
38✔
170
            targetOpenState: boolean,
40✔
171
            focusEl: HTMLElement | null
40✔
172
        ): Promise<void> {
40✔
173
            /**
40✔
174
             * Focus should be handled natively in `<dialog>` elements when leveraging `.showModal()`, but it's NOT.
40✔
175
             * - webkit bug: https://bugs.webkit.org/show_bug.cgi?id=255507
40✔
176
             * - firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1828398
40✔
177
             **/
40✔
178
            this.applyFocus(targetOpenState, focusEl);
40✔
179
        }
40✔
180
    }
38✔
181
    return OverlayWithDialog;
38✔
182
}
38✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc