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

adobe / spectrum-web-components / 14094674923

26 Mar 2025 10:27PM UTC coverage: 86.218% (-11.8%) from 98.002%
14094674923

Pull #5221

github

web-flow
Merge 2a1ea92e7 into 3184c1e6a
Pull Request #5221: RFC | leverage css module imports in components

1737 of 2032 branches covered (85.48%)

Branch coverage included in aggregate %.

14184 of 16434 relevant lines covered (86.31%)

85.29 hits per line

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

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

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

42✔
13
import { LitElement, ReactiveElement } from 'lit';
42✔
14
import { version } from '@spectrum-web-components/base/src/version.js';
42✔
15
type ThemeRoot = HTMLElement & {
42✔
16
    startManagingContentDirection: (el: HTMLElement) => void;
42✔
17
    stopManagingContentDirection: (el: HTMLElement) => void;
42✔
18
};
42✔
19

42✔
20
type Constructor<T = Record<string, unknown>> = {
42✔
21
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
42✔
22
    new (...args: any[]): T;
42✔
23
    prototype: T;
42✔
24
};
42✔
25

42✔
26
export interface SpectrumInterface {
42✔
27
    shadowRoot: ShadowRoot;
42✔
28
    isLTR: boolean;
42✔
29
    hasVisibleFocusInTree(): boolean;
42✔
30
    dir: 'ltr' | 'rtl';
42✔
31
}
42✔
32

42✔
33
const observedForElements: Set<HTMLElement> = new Set();
42✔
34

42✔
35
const updateRTL = (): void => {
42✔
36
    const dir =
45✔
37
        document.documentElement.dir === 'rtl'
45✔
38
            ? document.documentElement.dir
1✔
39
            : 'ltr';
44✔
40
    observedForElements.forEach((el) => {
45✔
41
        el.setAttribute('dir', dir);
1✔
42
    });
45✔
43
};
45✔
44

42✔
45
const rtlObserver = new MutationObserver(updateRTL);
42✔
46

42✔
47
rtlObserver.observe(document.documentElement, {
42✔
48
    attributes: true,
42✔
49
    attributeFilter: ['dir'],
42✔
50
});
42✔
51

42✔
52
type ContentDirectionManager = HTMLElement & {
42✔
53
    startManagingContentDirection?(): void;
42✔
54
};
42✔
55

42✔
56
const canManageContentDirection = (el: ContentDirectionManager): boolean =>
42✔
57
    typeof el.startManagingContentDirection !== 'undefined' ||
4,601✔
58
    el.tagName === 'SP-THEME';
4,246✔
59

42✔
60
export function SpectrumMixin<T extends Constructor<ReactiveElement>>(
42✔
61
    constructor: T
42✔
62
): T & Constructor<SpectrumInterface> {
42✔
63
    class SpectrumMixinElement extends constructor {
42✔
64
        /**
42✔
65
         * @private
42✔
66
         */
42✔
67
        public override shadowRoot!: ShadowRoot;
42✔
68
        private _dirParent?: HTMLElement;
42✔
69

42✔
70
        /**
42✔
71
         * @private
42✔
72
         */
42✔
73
        public override dir!: 'ltr' | 'rtl';
42✔
74

42✔
75
        /**
42✔
76
         * @private
42✔
77
         */
42✔
78
        public get isLTR(): boolean {
42✔
79
            return this.dir === 'ltr';
117✔
80
        }
117✔
81

41✔
82
        public hasVisibleFocusInTree(): boolean {
42✔
83
            const getAncestors = (root: Document = document): HTMLElement[] => {
4✔
84
                // eslint-disable-next-line @spectrum-web-components/document-active-element
4✔
85
                let currentNode = root.activeElement as HTMLElement;
4✔
86
                while (
4✔
87
                    currentNode?.shadowRoot &&
4✔
88
                    currentNode.shadowRoot.activeElement
8✔
89
                ) {
4✔
90
                    currentNode = currentNode.shadowRoot
4✔
91
                        .activeElement as HTMLElement;
4✔
92
                }
4✔
93
                const ancestors: HTMLElement[] = currentNode
4✔
94
                    ? [currentNode]
4✔
95
                    : [];
×
96
                while (currentNode) {
4✔
97
                    const ancestor =
28✔
98
                        currentNode.assignedSlot ||
28✔
99
                        currentNode.parentElement ||
24✔
100
                        (currentNode.getRootNode() as ShadowRoot)?.host;
12✔
101
                    if (ancestor) {
28✔
102
                        ancestors.push(ancestor as HTMLElement);
24✔
103
                    }
24✔
104
                    currentNode = ancestor as HTMLElement;
28✔
105
                }
28✔
106
                return ancestors;
4✔
107
            };
4✔
108
            const activeElement = getAncestors(
4✔
109
                this.getRootNode() as Document
4✔
110
            )[0];
4✔
111
            if (!activeElement) {
4✔
112
                return false;
×
113
            }
×
114
            // Browsers without support for the `:focus-visible`
4✔
115
            // selector will throw on the following test (Safari, older things).
4✔
116
            // Some won't throw, but will be focusing item rather than the menu and
4✔
117
            // will rely on the polyfill to know whether focus is "visible" or not.
4✔
118
            try {
4✔
119
                return (
4✔
120
                    activeElement.matches(':focus-visible') ||
4✔
121
                    activeElement.matches('.focus-visible')
×
122
                );
4✔
123
                /* c8 ignore next 3 */
42✔
124
            } catch (error) {
42✔
125
                return activeElement.matches('.focus-visible');
42✔
126
            }
42✔
127
        }
4✔
128

41✔
129
        public override connectedCallback(): void {
42✔
130
            if (!this.hasAttribute('dir')) {
1,049✔
131
                let dirParent = ((this as HTMLElement).assignedSlot ||
1,049✔
132
                    this.parentNode) as HTMLElement;
991✔
133
                while (
1,049✔
134
                    dirParent !== document.documentElement &&
1,049✔
135
                    !canManageContentDirection(
4,601✔
136
                        dirParent as ContentDirectionManager
4,601✔
137
                    )
4,601✔
138
                ) {
1,049✔
139
                    dirParent = ((dirParent as HTMLElement).assignedSlot || // step into the shadow DOM of the parent of a slotted node
4,238✔
140
                        dirParent.parentNode || // DOM Element detected
3,793✔
141
                        (dirParent as unknown as ShadowRoot)
824✔
142
                            .host) as HTMLElement;
824✔
143
                }
4,246✔
144
                this.dir =
1,057✔
145
                    dirParent.dir === 'rtl' ? dirParent.dir : this.dir || 'ltr';
1,057✔
146
                if (dirParent === document.documentElement) {
1,049✔
147
                    observedForElements.add(this);
694✔
148
                } else {
1,041✔
149
                    const { localName } = dirParent;
355✔
150
                    if (
355✔
151
                        localName.search('-') > -1 &&
355✔
152
                        !customElements.get(localName)
355✔
153
                    ) {
355✔
154
                        /* c8 ignore next 5 */
42✔
155
                        customElements.whenDefined(localName).then(() => {
42✔
156
                            (
42✔
157
                                dirParent as ThemeRoot
42✔
158
                            ).startManagingContentDirection(this);
42✔
159
                        });
42✔
160
                    } else {
355✔
161
                        (dirParent as ThemeRoot).startManagingContentDirection(
355✔
162
                            this
355✔
163
                        );
355✔
164
                    }
355✔
165
                }
355✔
166
                this._dirParent = dirParent as HTMLElement;
1,049✔
167
            }
1,049✔
168
            super.connectedCallback();
1,049✔
169
        }
1,049✔
170

49✔
171
        public override disconnectedCallback(): void {
42✔
172
            super.disconnectedCallback();
1,047✔
173
            if (this._dirParent) {
1,047✔
174
                if (this._dirParent === document.documentElement) {
1,047✔
175
                    observedForElements.delete(this);
692✔
176
                } else {
1,047✔
177
                    (this._dirParent as ThemeRoot).stopManagingContentDirection(
363✔
178
                        this
355✔
179
                    );
355✔
180
                }
363✔
181
                this.removeAttribute('dir');
1,047✔
182
            }
1,047✔
183
        }
1,047✔
184
    }
49✔
185
    return SpectrumMixinElement;
42✔
186
}
42✔
187

42✔
188
export class SpectrumElement extends SpectrumMixin(LitElement) {
42✔
189
    static VERSION = version;
42✔
190
}
42✔
191

42✔
192
if (window.__swc.DEBUG) {
42✔
193
    const ignoreWarningTypes = {
42✔
194
        default: false,
42✔
195
        accessibility: false,
42✔
196
        api: false,
42✔
197
    };
42✔
198
    const ignoreWarningLevels = {
42✔
199
        default: false,
42✔
200
        low: false,
42✔
201
        medium: false,
42✔
202
        high: false,
42✔
203
        deprecation: false,
42✔
204
    };
42✔
205
    window.__swc = {
42✔
206
        ...window.__swc,
42✔
207
        ignoreWarningLocalNames: {
42✔
208
            /* c8 ignore next 1 */
42✔
209
            ...(window.__swc?.ignoreWarningLocalNames || {}),
42✔
210
        },
42✔
211
        ignoreWarningTypes: {
42✔
212
            ...ignoreWarningTypes,
42✔
213
            /* c8 ignore next 1 */
42✔
214
            ...(window.__swc?.ignoreWarningTypes || {}),
42✔
215
        },
42✔
216
        ignoreWarningLevels: {
42✔
217
            ...ignoreWarningLevels,
42✔
218
            /* c8 ignore next 1 */
42✔
219
            ...(window.__swc?.ignoreWarningLevels || {}),
42✔
220
        },
42✔
221
        issuedWarnings: new Set(),
42✔
222
        warn: (
42✔
223
            element,
146✔
224
            message,
146✔
225
            url,
146✔
226
            { type = 'api', level = 'default', issues } = {}
146✔
227
        ): void => {
146✔
228
            const { localName = 'base' } = element || {};
146✔
229
            const id = `${localName}:${type}:${level}` as BrandedSWCWarningID;
146✔
230
            if (!window.__swc.verbose && window.__swc.issuedWarnings.has(id))
146✔
231
                return;
146✔
232
            /* c8 ignore next 3 */
42✔
233
            if (window.__swc.ignoreWarningLocalNames[localName]) return;
42✔
234
            if (window.__swc.ignoreWarningTypes[type]) return;
42✔
235
            if (window.__swc.ignoreWarningLevels[level]) return;
42✔
236
            window.__swc.issuedWarnings.add(id);
90✔
237
            let listedIssues = '';
90✔
238
            if (issues && issues.length) {
146✔
239
                issues.unshift('');
8✔
240
                listedIssues = issues.join('\n    - ') + '\n';
8✔
241
            }
8✔
242
            const intro = level === 'deprecation' ? 'DEPRECATION NOTICE: ' : '';
146✔
243
            const inspectElement = element
146✔
244
                ? '\nInspect this issue in the follow element:'
21✔
245
                : '';
70✔
246
            const displayURL = (element ? '\n\n' : '\n') + url + '\n';
146✔
247
            const messages: unknown[] = [];
146✔
248
            messages.push(
146✔
249
                intro + message + '\n' + listedIssues + inspectElement
146✔
250
            );
146✔
251
            if (element) {
146✔
252
                messages.push(element);
21✔
253
            }
21✔
254
            messages.push(displayURL, {
90✔
255
                data: {
90✔
256
                    localName,
90✔
257
                    type,
90✔
258
                    level,
90✔
259
                },
90✔
260
            });
90✔
261
            console.warn(...messages);
90✔
262
        },
146✔
263
    };
42✔
264

42✔
265
    window.__swc.warn(
42✔
266
        undefined,
42✔
267
        'Spectrum Web Components is in dev mode. Not recommended for production!',
42✔
268
        'https://opensource.adobe.com/spectrum-web-components/dev-mode/',
42✔
269
        { type: 'default' }
42✔
270
    );
42✔
271
}
42✔
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