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

adobe / spectrum-web-components / 13782907169

11 Mar 2025 07:50AM UTC coverage: 97.977%. Remained the same
13782907169

Pull #5181

github

web-flow
Merge 60ed4f689 into 0dadda413
Pull Request #5181: fix(lint): fixed the cem import statement

5298 of 5604 branches covered (94.54%)

Branch coverage included in aggregate %.

33650 of 34148 relevant lines covered (98.54%)

645.78 hits per line

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

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

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

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

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

44✔
171
        protected async dialogApplyFocus(
44✔
172
            targetOpenState: boolean,
99✔
173
            focusEl: HTMLElement | null
99✔
174
        ): Promise<void> {
99✔
175
            /**
99✔
176
             * Focus should be handled natively in `<dialog>` elements when leveraging `.showModal()`, but it's NOT.
99✔
177
             * - webkit bug: https://bugs.webkit.org/show_bug.cgi?id=255507
99✔
178
             * - firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1828398
99✔
179
             **/
99✔
180
            this.applyFocus(targetOpenState, focusEl);
99✔
181
        }
99✔
182
    }
44✔
183
    return OverlayWithDialog;
44✔
184
}
44✔
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