• 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

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

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

1✔
13
import type { ReactiveController, ReactiveElement } from 'lit';
1✔
14
export const elementResolverUpdatedSymbol = Symbol('element resolver updated');
1✔
15

1✔
16
export class ElementResolutionController implements ReactiveController {
1✔
17
    get element(): HTMLElement | null {
1✔
18
        return this._element;
1✔
19
    }
1✔
20

1✔
21
    set element(element: HTMLElement | null) {
1✔
22
        if (element === this.element) return;
7✔
23
        const previous = this.element;
5✔
24
        this._element = element;
5✔
25
        // requestUpdate leveraging the exported Symbol() so that the
5✔
26
        // changes can be easily tracked in the host element.
5✔
27
        this.host.requestUpdate(elementResolverUpdatedSymbol, previous);
5✔
28
    }
7✔
29

1✔
30
    private _element: HTMLElement | null = null;
1✔
31

1✔
32
    private host!: ReactiveElement;
1✔
33

1✔
34
    private observer!: MutationObserver;
1✔
35

1✔
36
    get selector(): string {
1✔
37
        return this._selector;
32✔
38
    }
32✔
39

1✔
40
    set selector(selector: string) {
1✔
41
        if (selector === this.selector) return;
2✔
42
        this.releaseElement();
1✔
43
        this._selector = selector;
1✔
44
        this.resolveElement();
1✔
45
    }
2✔
46

1✔
47
    private _selector = '';
1✔
48

1✔
49
    get selectorAsId(): string {
1✔
50
        return this.selector.slice(1);
×
51
    }
×
52

1✔
53
    get selectorIsId(): boolean {
1✔
54
        return !!this.selector && this.selector.startsWith('#');
7✔
55
    }
7✔
56

1✔
57
    constructor(
1✔
58
        host: ReactiveElement,
1✔
59
        { selector }: { selector: string } = { selector: '' }
1✔
60
    ) {
1✔
61
        this.host = host;
1✔
62
        this.selector = selector;
1✔
63
        this.observer = new MutationObserver(this.mutationCallback);
1✔
64
        // Add the controller after the MutationObserver has been created in preparation
1✔
65
        // for the `hostConnected`/`hostDisconnected` callbacks to be run.
1✔
66
        this.host.addController(this);
1✔
67
    }
1✔
68

1✔
69
    protected mutationCallback: MutationCallback = (mutationList) => {
1✔
70
        let needsResolution = false;
3✔
71
        mutationList.forEach((mutation) => {
3✔
72
            if (needsResolution) return;
4!
73
            if (mutation.type === 'childList') {
4✔
74
                const currentElementRemoved =
2✔
75
                    this.element &&
2✔
76
                    [...mutation.removedNodes].includes(this.element);
2✔
77
                const matchingElementAdded =
2✔
78
                    !!this.selector &&
2✔
79
                    ([...mutation.addedNodes] as HTMLElement[]).some(
2✔
80
                        this.elementIsSelected
2✔
81
                    );
2✔
82
                needsResolution =
2✔
83
                    needsResolution ||
2✔
84
                    currentElementRemoved ||
2✔
85
                    matchingElementAdded;
2✔
86
            }
2✔
87
            if (mutation.type === 'attributes') {
4✔
88
                const attributeChangedOnCurrentElement =
2✔
89
                    mutation.target === this.element;
2✔
90
                const attributeChangedOnMatchingElement =
2✔
91
                    !!this.selector &&
2✔
92
                    this.elementIsSelected(mutation.target as HTMLElement);
2✔
93
                needsResolution =
2✔
94
                    needsResolution ||
2✔
95
                    attributeChangedOnCurrentElement ||
2✔
96
                    attributeChangedOnMatchingElement;
1✔
97
            }
2✔
98
        });
3✔
99
        if (needsResolution) {
3✔
100
            this.resolveElement();
3✔
101
        }
3✔
102
    };
3✔
103

1✔
104
    public hostConnected(): void {
1✔
105
        this.resolveElement();
1✔
106
        this.observer.observe(this.host.getRootNode(), {
1✔
107
            subtree: true,
1✔
108
            childList: true,
1✔
109
            attributes: true,
1✔
110
        });
1✔
111
    }
1✔
112

1✔
113
    public hostDisconnected(): void {
1✔
114
        this.releaseElement();
1✔
115
        this.observer.disconnect();
1✔
116
    }
1✔
117

1✔
118
    private resolveElement(): void {
1✔
119
        if (!this.selector) {
5✔
120
            this.releaseElement();
1✔
121
            return;
1✔
122
        }
1✔
123

4✔
124
        const parent = this.host.getRootNode() as ShadowRoot;
4✔
125
        this.element = this.selectorIsId
4!
126
            ? (parent.getElementById(this.selectorAsId) as HTMLElement)
✔
127
            : (parent.querySelector(this.selector) as HTMLElement);
4✔
128
    }
5✔
129

1✔
130
    private releaseElement(): void {
1✔
131
        this.element = null;
3✔
132
    }
3✔
133

1✔
134
    private elementIsSelected = (el: HTMLElement): boolean => {
1✔
135
        return this.selectorIsId
3!
136
            ? el?.id === this.selectorAsId
×
137
            : el?.matches?.(this.selector);
3!
138
    };
3✔
139
}
1✔
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