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

adobe / spectrum-web-components / 12282443963

11 Dec 2024 06:17PM UTC coverage: 97.436% (-0.8%) from 98.206%
12282443963

Pull #4989

github

web-flow
Merge 92e762395 into 5b674b468
Pull Request #4989: chore: testing something

4996 of 5296 branches covered (94.34%)

Branch coverage included in aggregate %.

7 of 19 new or added lines in 1 file covered. (36.84%)

257 existing lines in 8 files now uncovered.

32739 of 33432 relevant lines covered (97.93%)

380.42 hits per line

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

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

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

20✔
13
import {
20✔
14
    CSSResultArray,
20✔
15
    html,
20✔
16
    PropertyValues,
20✔
17
    SpectrumElement,
20✔
18
    TemplateResult,
20✔
19
} from '@spectrum-web-components/base';
20✔
20
import {
20✔
21
    property,
20✔
22
    query,
20✔
23
    state,
20✔
24
} from '@spectrum-web-components/base/src/decorators.js';
20✔
25
import type { Placement } from '@floating-ui/dom';
20✔
26

20✔
27
import type { BeforetoggleOpenEvent } from './events.js';
20✔
28
import type { Overlay } from './Overlay.js';
20✔
29
import type { OverlayTriggerInteractions } from './overlay-types';
20✔
30

20✔
31
import overlayTriggerStyles from './overlay-trigger.css.js';
20✔
32

20✔
33
export type OverlayContentTypes = 'click' | 'hover' | 'longpress';
20✔
34

20✔
35
/**
20✔
36
 * @element overlay-trigger
20✔
37
 *
20✔
38
 * @slot trigger - The content that will trigger the various overlays
20✔
39
 * @slot hover-content - The content that will be displayed on hover
20✔
40
 * @slot click-content - The content that will be displayed on click
20✔
41
 * @slot longpress-content - The content that will be displayed on click
20✔
42
 *
20✔
43
 * @fires sp-opened - Announces that the overlay has been opened
20✔
44
 * @fires sp-closed - Announces that the overlay has been closed
20✔
45
 */
20✔
46
export class OverlayTrigger extends SpectrumElement {
20✔
47
    public static override get styles(): CSSResultArray {
322✔
48
        return [overlayTriggerStyles];
322✔
49
    }
322✔
50

322✔
51
    @property()
322✔
52
    content = 'click hover longpress';
322✔
53

322✔
54
    /**
322✔
55
     * @type {"top" | "top-start" | "top-end" | "right" | "right-start" | "right-end" | "bottom" | "bottom-start" | "bottom-end" | "left" | "left-start" | "left-end"}
322✔
56
     * @attr
322✔
57
     */
322✔
58
    @property({ reflect: true })
322✔
59
    public placement?: Placement;
322✔
60

322✔
61
    @property()
322✔
62
    public type?: OverlayTriggerInteractions;
322✔
63

322✔
64
    @property({ type: Number })
322✔
65
    public offset = 6;
322✔
66

322✔
67
    @property({ reflect: true })
322✔
68
    public open?: OverlayContentTypes;
322✔
69

322✔
70
    @property({ type: Boolean, reflect: true })
322✔
71
    public disabled = false;
322✔
72

322✔
73
    @property({ attribute: 'receives-focus' })
322✔
74
    public receivesFocus: 'true' | 'false' | 'auto' = 'auto';
322✔
75

322✔
76
    @state()
322✔
77
    private clickContent: HTMLElement[] = [];
322✔
78

322✔
79
    private clickPlacement?: Placement;
322✔
80

322✔
81
    @state()
322✔
82
    private longpressContent: HTMLElement[] = [];
322✔
83

322✔
84
    private longpressPlacement?: Placement;
322✔
85

322✔
86
    @state()
322✔
87
    private hoverContent: HTMLElement[] = [];
322✔
88

322✔
89
    private hoverPlacement?: Placement;
322✔
90

322✔
91
    @state()
322✔
92
    private targetContent: HTMLElement[] = [];
322✔
93

20✔
94
    @query('#click-overlay', true)
20✔
95
    clickOverlayElement!: Overlay;
20✔
96

20✔
97
    @query('#longpress-overlay', true)
20✔
98
    longpressOverlayElement!: Overlay;
20✔
99

20✔
100
    @query('#hover-overlay', true)
20✔
101
    hoverOverlayElement!: Overlay;
20✔
102

20✔
103
    private getAssignedElementsFromSlot(slot: HTMLSlotElement): HTMLElement[] {
20✔
104
        return slot.assignedElements({ flatten: true }) as HTMLElement[];
325✔
105
    }
325✔
106

20✔
107
    private handleTriggerContent(
20✔
108
        event: Event & { target: HTMLSlotElement }
325✔
109
    ): void {
325✔
110
        this.targetContent = this.getAssignedElementsFromSlot(event.target);
325✔
111
    }
325✔
112

20✔
113
    private handleSlotContent(
20✔
114
        event: Event & { target: HTMLSlotElement }
424✔
115
    ): void {
424✔
116
        requestAnimationFrame(() => {
424✔
117
            switch (event.target.name) {
424✔
118
                case 'click-content':
424!
NEW
119
                    this.clickContent = this.getAssignedElementsFromSlot(
×
NEW
120
                        event.target
×
NEW
121
                    );
×
NEW
122
                    break;
×
123
                case 'longpress-content':
424!
NEW
124
                    this.longpressContent = this.getAssignedElementsFromSlot(
×
NEW
125
                        event.target
×
NEW
126
                    );
×
NEW
127
                    break;
×
128
                case 'hover-content':
424!
NEW
129
                    this.hoverContent = this.getAssignedElementsFromSlot(
×
NEW
130
                        event.target
×
NEW
131
                    );
×
NEW
132
                    break;
×
133
            }
424✔
134
        });
424✔
135
    }
424✔
136

20✔
137
    private handleBeforetoggle(event: BeforetoggleOpenEvent): void {
20✔
UNCOV
138
        const { target } = event;
×
UNCOV
139
        let type: OverlayContentTypes;
×
UNCOV
140
        if (target === this.clickOverlayElement) {
×
UNCOV
141
            type = 'click';
×
UNCOV
142
        } else if (target === this.longpressOverlayElement) {
×
UNCOV
143
            type = 'longpress';
×
UNCOV
144
        } else if (target === this.hoverOverlayElement) {
×
UNCOV
145
            type = 'hover';
×
146
            /* c8 ignore next 3 */
20✔
147
        } else {
20✔
148
            return;
20✔
149
        }
20✔
UNCOV
150
        if (event.newState === 'open') {
×
UNCOV
151
            this.open = type;
×
UNCOV
152
        } else if (this.open === type) {
×
UNCOV
153
            this.open = undefined;
×
UNCOV
154
        }
×
UNCOV
155
    }
×
156

20✔
157
    protected override update(changes: PropertyValues): void {
20✔
158
        if (changes.has('clickContent')) {
716✔
159
            this.clickPlacement =
322✔
160
                ((this.clickContent[0]?.getAttribute('placement') ||
322!
161
                    this.clickContent[0]?.getAttribute(
322!
UNCOV
162
                        'direction'
×
163
                    )) as Placement) || undefined;
322✔
164
        }
322✔
165
        if (changes.has('hoverContent')) {
716✔
166
            this.hoverPlacement =
322✔
167
                ((this.hoverContent[0]?.getAttribute('placement') ||
322!
168
                    this.hoverContent[0]?.getAttribute(
322!
UNCOV
169
                        'direction'
×
170
                    )) as Placement) || undefined;
322✔
171
        }
322✔
172
        if (changes.has('longpressContent')) {
716✔
173
            this.longpressPlacement =
322✔
174
                ((this.longpressContent[0]?.getAttribute('placement') ||
322!
175
                    this.longpressContent[0]?.getAttribute(
322!
UNCOV
176
                        'direction'
×
177
                    )) as Placement) || undefined;
322✔
178
        }
322✔
179
        super.update(changes);
716✔
180
    }
716✔
181

20✔
182
    protected renderSlot(name: string): TemplateResult {
20✔
183
        return html`
2,088✔
184
            <slot name=${name} @slotchange=${this.handleSlotContent}></slot>
2,088✔
185
        `;
2,088✔
186
    }
2,088✔
187

20✔
188
    protected renderClickOverlay(): TemplateResult {
20✔
189
        import('@spectrum-web-components/overlay/sp-overlay.js');
716✔
190
        const slot = this.renderSlot('click-content');
716✔
191
        if (!this.clickContent.length) {
716✔
192
            return slot;
716✔
193
        }
716!
UNCOV
194
        return html`
×
UNCOV
195
            <sp-overlay
×
UNCOV
196
                id="click-overlay"
×
197
                ?disabled=${this.disabled || !this.clickContent.length}
716✔
198
                ?open=${this.open === 'click' && !!this.clickContent.length}
716!
199
                .offset=${this.offset}
716✔
200
                .placement=${this.clickPlacement || this.placement}
716!
201
                .triggerElement=${this.targetContent[0]}
716✔
202
                .triggerInteraction=${'click'}
716✔
203
                .type=${this.type !== 'modal' ? 'auto' : 'modal'}
716!
204
                @beforetoggle=${this.handleBeforetoggle}
716✔
205
                .receivesFocus=${this.receivesFocus}
716✔
206
            >
716✔
207
                ${slot}
716✔
208
            </sp-overlay>
716✔
209
        `;
716✔
210
    }
716✔
211

20✔
212
    protected renderHoverOverlay(): TemplateResult {
20✔
213
        import('@spectrum-web-components/overlay/sp-overlay.js');
686✔
214
        const slot = this.renderSlot('hover-content');
686✔
215
        if (!this.hoverContent.length) {
686✔
216
            return slot;
686✔
217
        }
686!
UNCOV
218
        return html`
×
UNCOV
219
            <sp-overlay
×
UNCOV
220
                id="hover-overlay"
×
221
                ?open=${this.open === 'hover' && !!this.hoverContent.length}
686✔
222
                ?disabled=${this.disabled ||
686!
UNCOV
223
                !this.hoverContent.length ||
×
224
                (!!this.open && this.open !== 'hover')}
686✔
225
                .offset=${this.offset}
686✔
226
                .placement=${this.hoverPlacement || this.placement}
686!
227
                .triggerElement=${this.targetContent[0]}
686✔
228
                .triggerInteraction=${'hover'}
686✔
229
                .type=${'hint'}
686✔
230
                @beforetoggle=${this.handleBeforetoggle}
686✔
231
                .receivesFocus=${this.receivesFocus}
686✔
232
            >
686✔
233
                ${slot}
686✔
234
            </sp-overlay>
686✔
235
        `;
686✔
236
    }
686✔
237

20✔
238
    protected renderLongpressOverlay(): TemplateResult {
20✔
239
        import('@spectrum-web-components/overlay/sp-overlay.js');
686✔
240
        const slot = this.renderSlot('longpress-content');
686✔
241
        if (!this.longpressContent.length) {
686✔
242
            return slot;
686✔
243
        }
686!
UNCOV
244
        return html`
×
UNCOV
245
            <sp-overlay
×
UNCOV
246
                id="longpress-overlay"
×
247
                ?disabled=${this.disabled || !this.longpressContent.length}
686✔
248
                ?open=${this.open === 'longpress' &&
686!
249
                !!this.longpressContent.length}
686✔
250
                .offset=${this.offset}
686✔
251
                .placement=${this.longpressPlacement || this.placement}
686!
252
                .triggerElement=${this.targetContent[0]}
686✔
253
                .triggerInteraction=${'longpress'}
686✔
254
                .type=${'auto'}
686✔
255
                @beforetoggle=${this.handleBeforetoggle}
686✔
256
                .receivesFocus=${this.receivesFocus}
686✔
257
            >
686✔
258
                ${slot}
686✔
259
            </sp-overlay>
686✔
260
            <slot name="longpress-describedby-descriptor"></slot>
686✔
261
        `;
686✔
262
    }
686✔
263

20✔
264
    protected override render(): TemplateResult {
20✔
265
        const content = this.content.split(' ');
716✔
266
        // Keyboard event availability documented in README.md
716✔
267
        /* eslint-disable lit-a11y/click-events-have-key-events */
716✔
268
        return html`
716✔
269
            <slot
716✔
270
                id="trigger"
716✔
271
                name="trigger"
716✔
272
                @slotchange=${this.handleTriggerContent}
716✔
273
            ></slot>
716✔
274
            ${[
716✔
275
                content.includes('click') ? this.renderClickOverlay() : html``,
716!
276
                content.includes('hover') ? this.renderHoverOverlay() : html``,
716✔
277
                content.includes('longpress')
716✔
278
                    ? this.renderLongpressOverlay()
686✔
279
                    : html``,
30✔
280
            ]}
716✔
281
        `;
716✔
282
        /* eslint-enable lit-a11y/click-events-have-key-events */
716✔
283
    }
716✔
284

20✔
285
    protected override updated(changes: PropertyValues): void {
20✔
286
        super.updated(changes);
716✔
287
        if (this.disabled && changes.has('disabled')) {
716!
UNCOV
288
            this.open = undefined;
×
UNCOV
289
            return;
×
UNCOV
290
        }
×
291
    }
716✔
292

20✔
293
    protected override async getUpdateComplete(): Promise<boolean> {
20✔
294
        const complete = (await super.getUpdateComplete()) as boolean;
275✔
295
        return complete;
275✔
296
    }
275✔
297
}
20✔
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