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

adobe / spectrum-web-components / 12660154690

07 Jan 2025 10:10PM UTC coverage: 97.416% (-0.8%) from 98.209%
12660154690

Pull #4690

github

web-flow
Merge 168797a23 into 5bf31e817
Pull Request #4690: fix(OverlayTrigger): conditionally attach slotchange listener

4992 of 5291 branches covered (94.35%)

Branch coverage included in aggregate %.

20 of 20 new or added lines in 1 file covered. (100.0%)

278 existing lines in 8 files now uncovered.

32743 of 33445 relevant lines covered (97.9%)

373.37 hits per line

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

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

14✔
7
Unless required by applicable law or agreed to in writing, software distributed under
14✔
8
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
14✔
9
OF ANY KIND, either express or implied. See the License for the specific language
14✔
10
governing permissions and limitations under the License.
14✔
11
*/
14✔
12

14✔
13
import {
14✔
14
    CSSResultArray,
14✔
15
    html,
14✔
16
    nothing,
14✔
17
    PropertyValues,
14✔
18
    SpectrumElement,
14✔
19
    TemplateResult,
14✔
20
} from '@spectrum-web-components/base';
14✔
21
import { property } from '@spectrum-web-components/base/src/decorators.js';
14✔
22

14✔
23
import '@spectrum-web-components/underlay/sp-underlay.js';
14✔
24
import '@spectrum-web-components/button/sp-button.js';
14✔
25

14✔
26
// Leveraged in build systems that use aliasing to prevent multiple registrations: https://github.com/adobe/spectrum-web-components/pull/3225
14✔
27
import '@spectrum-web-components/dialog/sp-dialog.js';
14✔
28
import modalWrapperStyles from '@spectrum-web-components/modal/src/modal-wrapper.css.js';
14✔
29
import modalStyles from '@spectrum-web-components/modal/src/modal.css.js';
14✔
30
import { Dialog } from './Dialog.js';
14✔
31
import { FocusVisiblePolyfillMixin } from '@spectrum-web-components/shared';
14✔
32
import { firstFocusableIn } from '@spectrum-web-components/shared/src/first-focusable-in.js';
14✔
33

14✔
34
/**
14✔
35
 * @element sp-dialog-base
14✔
36
 *
14✔
37
 * @slot - A Dialog element to display.
14✔
38
 * @fires close - Announces that the dialog has been closed.
14✔
39
 */
14✔
40
export class DialogBase extends FocusVisiblePolyfillMixin(SpectrumElement) {
14✔
41
    public static override get styles(): CSSResultArray {
30✔
42
        return [modalWrapperStyles, modalStyles];
30✔
43
    }
30✔
44

30✔
45
    @property({ type: Boolean, reflect: true })
30✔
46
    public dismissable = false;
30✔
47

30✔
48
    @property({ type: Boolean, reflect: true })
30✔
49
    public open = false;
30✔
50

30✔
51
    @property({ type: String, reflect: true })
30✔
52
    public mode?: 'fullscreen' | 'fullscreenTakeover';
30✔
53

30✔
54
    /**
30✔
55
     * When set to true, fills screens smaller than 350px high and 400px wide with the full dialog.
30✔
56
     */
30✔
57
    @property({ type: Boolean })
30✔
58
    public responsive = false;
30✔
59

30✔
60
    private transitionPromise = Promise.resolve();
30✔
61

30✔
62
    private resolveTransitionPromise = (): void => {
30✔
63
        return;
×
64
    };
×
65

30✔
66
    @property({ type: Boolean })
30✔
67
    public underlay = false;
30✔
68

30✔
69
    protected get dialog(): Dialog {
30✔
UNCOV
70
        const dialog = (
×
UNCOV
71
            this.shadowRoot.querySelector('slot') as HTMLSlotElement
×
UNCOV
72
        ).assignedElements()[0] as Dialog;
×
UNCOV
73
        if (window.__swc.DEBUG) {
×
UNCOV
74
            if (!dialog) {
×
75
                window.__swc.warn(
×
76
                    this,
×
77
                    `<${this.localName}> expects to be provided dialog content via its default slot.`,
×
78
                    'https://opensource.adobe.com/spectrum-web-components/components/dialog-base/#dialog'
×
79
                );
×
80
            }
×
UNCOV
81
        }
×
UNCOV
82
        return dialog || this;
×
UNCOV
83
    }
×
84

30✔
85
    public override async focus(): Promise<void> {
30✔
86
        if (this.shadowRoot) {
1✔
87
            const firstFocusable = firstFocusableIn(this.dialog);
1✔
88
            if (firstFocusable) {
1✔
89
                if ((firstFocusable as SpectrumElement).updateComplete) {
1✔
90
                    await firstFocusable.updateComplete;
1✔
91
                }
1✔
92
                firstFocusable.focus();
1✔
93
            } else {
1✔
94
                this.dialog.focus();
×
95
            }
×
96
            /* c8 ignore next 3 */
14✔
97
        } else {
14✔
98
            super.focus();
14✔
99
        }
14✔
100
    }
1✔
101

30✔
102
    private animating = false;
30✔
103

14✔
104
    public overlayWillCloseCallback(): boolean {
14✔
105
        if (!this.open) return this.animating;
×
106
        this.close();
×
107
        return true;
×
108
    }
×
109

14✔
110
    private dismiss(): void {
14✔
111
        if (!this.dismissable) {
2✔
112
            return;
1✔
113
        }
1✔
114
        this.close();
1✔
115
    }
2✔
116

14✔
117
    protected handleClose(event: Event): void {
14✔
118
        event.stopPropagation();
1✔
119
        this.close();
1✔
120
    }
1✔
121

14✔
122
    public close(): void {
14✔
123
        this.open = false;
2✔
124
    }
2✔
125

14✔
126
    private dispatchClosed(): void {
14✔
127
        this.dispatchEvent(
4✔
128
            new Event('close', {
4✔
129
                bubbles: true,
4✔
130
            })
4✔
131
        );
4✔
132
    }
4✔
133

14✔
134
    private handleTransitionEvent(event: TransitionEvent): void {
14✔
135
        this.dispatchEvent(
16✔
136
            new TransitionEvent(event.type, {
16✔
137
                bubbles: true,
16✔
138
                composed: true,
16✔
139
                propertyName: event.propertyName,
16✔
140
            })
16✔
141
        );
16✔
142
    }
16✔
143

14✔
144
    protected handleUnderlayTransitionend(event: TransitionEvent): void {
14✔
145
        if (!this.open && event.propertyName === 'visibility') {
2✔
146
            this.resolveTransitionPromise();
1✔
147
        }
1✔
148
        this.handleTransitionEvent(event);
2✔
149
    }
2✔
150

14✔
151
    protected handleModalTransitionend(event: TransitionEvent): void {
14✔
152
        if (this.open || !this.underlay) {
6✔
153
            this.resolveTransitionPromise();
3✔
154
        }
3✔
155
        this.handleTransitionEvent(event);
6✔
156
    }
6✔
157

14✔
158
    private get hasTransitionDuration(): boolean {
14✔
159
        const modal = this.shadowRoot.querySelector('.modal') as HTMLElement;
2✔
160

2✔
161
        const modalTransitionDurations =
2✔
162
            window.getComputedStyle(modal).transitionDuration;
2✔
163
        for (const duration of modalTransitionDurations.split(','))
2✔
164
            if (parseFloat(duration) > 0) return true;
2!
UNCOV
165

×
UNCOV
166
        const underlay = this.shadowRoot.querySelector(
×
UNCOV
167
            'sp-underlay'
×
UNCOV
168
        ) as HTMLElement;
×
UNCOV
169

×
UNCOV
170
        if (underlay) {
×
171
            const underlayTransitionDurations =
×
172
                window.getComputedStyle(underlay).transitionDuration;
×
173
            for (const duration of underlayTransitionDurations.split(','))
×
174
                if (parseFloat(duration) > 0) return true;
×
175
        }
×
UNCOV
176

×
UNCOV
177
        return false;
×
178
    }
2✔
179

14✔
180
    protected override update(changes: PropertyValues<this>): void {
14✔
181
        if (changes.has('open') && changes.get('open') !== undefined) {
32✔
182
            const hasTransitionDuration = this.hasTransitionDuration;
2✔
183
            this.animating = true;
2✔
184
            this.transitionPromise = new Promise((res) => {
2✔
185
                this.resolveTransitionPromise = () => {
2✔
186
                    this.animating = false;
4✔
187
                    if (!this.open && hasTransitionDuration)
4✔
188
                        this.dispatchClosed();
4✔
189
                    res();
4✔
190
                };
4✔
191
            });
2✔
192
            if (!this.open && !hasTransitionDuration) this.dispatchClosed();
2!
193
        }
2✔
194
        super.update(changes);
32✔
195
    }
32✔
196

14✔
197
    protected renderDialog(): TemplateResult {
14✔
198
        return html`
6✔
199
            <slot></slot>
6✔
200
        `;
6✔
201
    }
6✔
202

14✔
203
    protected override render(): TemplateResult {
14✔
204
        return html`
32✔
205
            ${this.underlay
32✔
206
                ? html`
15✔
207
                      <sp-underlay
15✔
208
                          ?open=${this.open}
15✔
209
                          @close=${this.dismiss}
15✔
210
                          @transitionrun=${this.handleTransitionEvent}
15✔
211
                          @transitionend=${this.handleUnderlayTransitionend}
15✔
212
                          @transitioncancel=${this.handleTransitionEvent}
15✔
213
                      ></sp-underlay>
17✔
214
                  `
17✔
215
                : nothing}
32✔
216
            <div
32✔
217
                class="modal ${this.mode}"
32✔
218
                @transitionrun=${this.handleTransitionEvent}
32✔
219
                @transitionend=${this.handleModalTransitionend}
32✔
220
                @transitioncancel=${this.handleTransitionEvent}
32✔
221
                @close=${this.handleClose}
32✔
222
            >
32✔
223
                ${this.renderDialog()}
32✔
224
            </div>
32✔
225
        `;
32✔
226
    }
32✔
227

14✔
228
    protected override updated(changes: PropertyValues<this>): void {
14✔
229
        if (changes.has('open')) {
32✔
230
            if (this.open) {
32✔
231
                if (
12✔
232
                    'updateComplete' in this.dialog &&
12✔
233
                    'shouldManageTabOrderForScrolling' in this.dialog
12✔
234
                ) {
12✔
235
                    this.dialog.updateComplete.then(() => {
12✔
236
                        this.dialog.shouldManageTabOrderForScrolling();
12✔
237
                    });
12✔
238
                }
12✔
239
            }
12✔
240
        }
32✔
241
    }
32✔
242

14✔
243
    /**
14✔
244
     * Bind the open/close transition into the update complete lifecycle so
14✔
245
     * that the overlay system can wait for it to be "visibly ready" before
14✔
246
     * attempting to throw focus into the content contained herein. Not
14✔
247
     * waiting for this can cause small amounts of page scroll to happen
14✔
248
     * while opening the Tray when focusable content is included: e.g. Menu
14✔
249
     * elements whose selected Menu Item is not the first Menu Item.
14✔
250
     */
14✔
251
    protected override async getUpdateComplete(): Promise<boolean> {
14✔
252
        const complete = (await super.getUpdateComplete()) as boolean;
31✔
253
        await this.transitionPromise;
31✔
254
        return complete;
31✔
255
    }
31✔
256
}
14✔
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