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

adobe / spectrum-web-components / 18327331653

07 Oct 2025 09:59PM UTC coverage: 97.929% (-0.06%) from 97.984%
18327331653

Pull #5792

github

web-flow
Merge 8914ce201 into 7d23140c2
Pull Request #5792: remove animations from some components

5338 of 5625 branches covered (94.9%)

Branch coverage included in aggregate %.

34050 of 34596 relevant lines covered (98.42%)

649.89 hits per line

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

92.91
/packages/dialog/src/DialogBase.ts
1
/**
14✔
2
 * Copyright 2025 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 {
25✔
42
        return [modalWrapperStyles, modalStyles];
25✔
43
    }
25✔
44

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

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

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

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

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

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

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

25✔
69
    protected get dialog(): Dialog {
25✔
70
        const dialog = (
8✔
71
            this.shadowRoot.querySelector('slot') as HTMLSlotElement
8✔
72
        ).assignedElements()[0] as Dialog;
8✔
73
        if (window.__swc.DEBUG) {
8✔
74
            if (!dialog) {
8✔
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
            }
×
81
        }
8✔
82
        return dialog || this;
8✔
83
    }
8✔
84

25✔
85
    public override async focus(): Promise<void> {
25✔
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

25✔
102
    private animating = false;
25✔
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) {
4✔
112
            return;
1✔
113
        }
1✔
114
        this.close();
3✔
115
    }
4✔
116

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

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

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

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

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

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

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

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

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

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

22✔
177
        return false;
22✔
178
    }
22✔
179

14✔
180
    protected override update(changes: PropertyValues<this>): void {
14✔
181
        if (changes.has('open') && changes.get('open') !== undefined) {
50✔
182
            const hasTransitionDuration = this.hasTransitionDuration;
22✔
183
            this.animating = true;
22✔
184
            this.transitionPromise = new Promise((res) => {
22✔
185
                this.resolveTransitionPromise = () => {
22✔
186
                    this.animating = false;
39✔
187
                    if (!this.open && hasTransitionDuration)
39✔
188
                        this.dispatchClosed();
39✔
189
                    res();
39✔
190
                };
39✔
191
            });
22✔
192
            if (!this.open && !hasTransitionDuration) this.dispatchClosed();
22✔
193
        }
22✔
194
        super.update(changes);
50✔
195
    }
50✔
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`
50✔
205
            ${this.underlay
50✔
206
                ? html`
19✔
207
                      <sp-underlay
19✔
208
                          ?open=${this.open}
19✔
209
                          @close=${this.dismiss}
19✔
210
                          @transitionrun=${this.handleTransitionEvent}
19✔
211
                          @transitionend=${this.handleUnderlayTransitionend}
19✔
212
                          @transitioncancel=${this.handleTransitionEvent}
19✔
213
                      ></sp-underlay>
31✔
214
                  `
31✔
215
                : nothing}
50✔
216
            <div
50✔
217
                class="modal ${this.mode}"
50✔
218
                @transitionrun=${this.handleTransitionEvent}
50✔
219
                @transitionend=${this.handleModalTransitionend}
50✔
220
                @transitioncancel=${this.handleTransitionEvent}
50✔
221
                @close=${this.handleClose}
50✔
222
            >
50✔
223
                ${this.renderDialog()}
50✔
224
            </div>
50✔
225
        `;
50✔
226
    }
50✔
227

14✔
228
    protected override updated(changes: PropertyValues<this>): void {
14✔
229
        if (changes.has('open')) {
50✔
230
            if (this.open) {
47✔
231
                if (
24✔
232
                    'updateComplete' in this.dialog &&
24✔
233
                    'shouldManageTabOrderForScrolling' in this.dialog
24✔
234
                ) {
24✔
235
                    this.dialog.updateComplete.then(() => {
24✔
236
                        this.dialog.shouldManageTabOrderForScrolling();
24✔
237
                    });
24✔
238
                }
24✔
239
            }
24✔
240
        }
47✔
241
    }
50✔
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;
37✔
253
        await this.transitionPromise;
37✔
254
        return complete;
28✔
255
    }
37✔
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