• 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

93.61
/packages/picker/src/InteractionController.ts
1
/**
29✔
2
 * Copyright 2025 Adobe. All rights reserved.
29✔
3
 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
29✔
4
 * you may not use this file except in compliance with the License. You may obtain a copy
29✔
5
 * of the License at http://www.apache.org/licenses/LICENSE-2.0
29✔
6
 *
29✔
7
 * Unless required by applicable law or agreed to in writing, software distributed under
29✔
8
 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
29✔
9
 * OF ANY KIND, either express or implied. See the License for the specific language
29✔
10
 * governing permissions and limitations under the License.
29✔
11
 */
29✔
12

29✔
13
import {
29✔
14
    ReactiveController,
29✔
15
    TemplateResult,
29✔
16
} from '@spectrum-web-components/base';
29✔
17
import { AbstractOverlay } from '@spectrum-web-components/overlay/src/AbstractOverlay.js';
29✔
18
import { Overlay } from '@spectrum-web-components/overlay/src/Overlay.js';
29✔
19
import { PickerBase } from './Picker.js';
29✔
20

29✔
21
export enum InteractionTypes {
29✔
22
    'desktop',
29✔
23
    'mobile',
29✔
24
}
29✔
25
export const SAFARI_FOCUS_RING_CLASS = 'remove-focus-ring-safari-hack';
29✔
26

29✔
27
export class InteractionController implements ReactiveController {
29✔
28
    abortController!: AbortController;
29✔
29

29✔
30
    public preventNextToggle: 'no' | 'maybe' | 'yes' = 'no';
29✔
31
    public pointerdownState = false;
29✔
32
    public enterKeydownOn: EventTarget | null = null;
29✔
33

29✔
34
    public container!: TemplateResult;
29✔
35

29✔
36
    get activelyOpening(): boolean {
29✔
37
        return false;
29✔
38
    }
29✔
39

29✔
40
    private _open = false;
29✔
41

29✔
42
    public get open(): boolean {
29✔
43
        return this._open;
499✔
44
    }
499✔
45

29✔
46
    /**
29✔
47
     * Set `open`
29✔
48
     */
29✔
49
    public set open(open: boolean) {
29✔
50
        if (this._open === open) return;
1,681✔
51
        this._open = open;
323✔
52

323✔
53
        if (this.overlay) {
1,286✔
54
            this.host.open = open;
178✔
55
            return;
178✔
56
        }
178✔
57

145✔
58
        // When there is no Overlay and `open` is moving to `true`, lazily import/create
145✔
59
        // an Overlay and apply that state to it.
145✔
60
        customElements
145✔
61
            .whenDefined('sp-overlay')
145✔
62
            .then(async (): Promise<void> => {
145✔
63
                const { Overlay } = await import(
145✔
64
                    '@spectrum-web-components/overlay/src/Overlay.js'
145✔
65
                );
145✔
66
                this.overlay = new Overlay();
145✔
67
                this.host.open = true;
145✔
68
                this.host.requestUpdate();
145✔
69
            });
145✔
70
        import('@spectrum-web-components/overlay/sp-overlay.js');
145✔
71
    }
1,681✔
72

29✔
73
    private _overlay!: AbstractOverlay;
29✔
74

29✔
75
    public get overlay(): AbstractOverlay {
29✔
76
        return this._overlay;
5,458✔
77
    }
5,458✔
78

29✔
79
    public set overlay(overlay: AbstractOverlay | undefined) {
29✔
80
        if (!overlay) return;
145!
81
        if (this.overlay === overlay) return;
145!
82
        this._overlay = overlay;
145✔
83
        this.initOverlay();
145✔
84
    }
145✔
85

29✔
86
    type!: InteractionTypes;
29✔
87

29✔
88
    constructor(
29✔
89
        public target: HTMLElement,
439✔
90
        public host: PickerBase
439✔
91
    ) {
439✔
92
        this.target = target;
439✔
93
        this.host = host;
439✔
94
        this.host.addController(this);
439✔
95
        this.init();
439✔
96
    }
439✔
97

29✔
98
    releaseDescription(): void {}
29✔
99

29✔
100
    protected handleBeforetoggle(
29✔
101
        event: Event & {
298✔
102
            target: Overlay;
298✔
103
            newState: 'open' | 'closed';
298✔
104
        }
298✔
105
    ): void {
298✔
106
        if (event.composedPath()[0] !== event.target) {
298!
107
            return;
×
108
        }
×
109
        if (event.newState === 'closed') {
298✔
110
            if (this.preventNextToggle === 'no') {
149✔
111
                this.open = false;
145✔
112
            } else if (!this.pointerdownState) {
149✔
113
                // Prevent browser driven closure while opening the Picker
×
114
                // and the expected event series has not completed.
×
115
                this.overlay?.manuallyKeepOpen();
×
116
            }
×
117
        }
149✔
118
        if (!this.open) {
298✔
119
            this.host.optionsMenu.updateSelectedItemIndex();
149✔
120
            this.host.optionsMenu.closeDescendentOverlays();
149✔
121
        }
149✔
122
    }
298✔
123

29✔
124
    initOverlay(): void {
29✔
125
        if (this.overlay) {
145✔
126
            this.overlay.addEventListener('beforetoggle', (event: Event) => {
145✔
127
                this.handleBeforetoggle(
298✔
128
                    event as Event & {
298✔
129
                        target: Overlay;
298✔
130
                        newState: 'open' | 'closed';
298✔
131
                    }
298✔
132
                );
298✔
133
            });
145✔
134
            this.overlay.type =
145✔
135
                this.host.isMobile.matches && !this.host.forcePopover
145✔
136
                    ? 'modal'
2✔
137
                    : 'auto';
143✔
138
            this.overlay.triggerElement = this.host as HTMLElement;
145✔
139
            this.overlay.placement =
145✔
140
                this.host.isMobile.matches && !this.host.forcePopover
145✔
141
                    ? undefined
2✔
142
                    : this.host.placement;
143✔
143
            // We should not be applying open is set programmatically via the picker's open.property.
145✔
144
            // Focus should only be applied if a user action causes the menu to open. Otherwise,
145✔
145
            // we could be pulling focus from a user when an picker with an open menu loads.
145✔
146
            this.overlay.receivesFocus = 'false';
145✔
147
            this.overlay.willPreventClose =
145✔
148
                this.preventNextToggle !== 'no' && this.open;
145✔
149
            this.overlay.addEventListener(
145✔
150
                'slottable-request',
145✔
151
                this.host.handleSlottableRequest
145✔
152
            );
145✔
153
        }
145✔
154
    }
145✔
155

29✔
156
    public handlePointerdown(_event: PointerEvent): void {}
29✔
157

29✔
158
    public handleButtonFocus(event: FocusEvent): void {
29✔
159
        // When focus comes from a pointer event, and the related target is the Menu,
185✔
160
        // we don't want to reopen the Menu.
185✔
161
        if (
185✔
162
            this.preventNextToggle === 'maybe' &&
185✔
163
            event.relatedTarget === this.host.optionsMenu
26✔
164
        ) {
185!
165
            this.preventNextToggle = 'yes';
×
166
        }
×
167
        if (this.preventNextToggle === 'no') this.host.close();
185✔
168
    }
185✔
169

29✔
170
    public handleActivate(_event: Event): void {}
29✔
171

29✔
172
    /* c8 ignore next 3 */
29✔
173
    init(): void {}
29✔
174

29✔
175
    abort(): void {
29✔
176
        this.releaseDescription();
4✔
177
        this.abortController?.abort();
4!
178
    }
4✔
179

29✔
180
    hostConnected(): void {
29✔
181
        this.init();
445✔
182
        this.host.addEventListener('sp-opened', () => {
445✔
183
            /**
149✔
184
             * set shouldSupportDragAndSelect to false for mobile
149✔
185
             * to prevent click event being captured behind the menu-tray
149✔
186
             * we do this here because the menu gets reinitialized on overlay open
149✔
187
             */
149✔
188
            this.host.optionsMenu.shouldSupportDragAndSelect =
149✔
189
                !this.host.isMobile.matches;
149✔
190
        });
445✔
191
        this.host.addEventListener('sp-closed', () => {
445✔
192
            if (
157✔
193
                !this.open &&
157✔
194
                this.host.optionsMenu.matches(':focus-within') &&
153✔
195
                !this.host.button.matches(':focus')
×
196
            ) {
157✔
197
                this.host.button.focus();
×
198
            }
×
199
        });
445✔
200
    }
445✔
201

29✔
202
    hostDisconnected(): void {
29✔
203
        this.abortController?.abort();
445!
204
    }
445✔
205

29✔
206
    public hostUpdated(): void {
29✔
207
        if (
1,111✔
208
            this.overlay &&
1,111✔
209
            this.host.dependencyManager.loaded &&
487✔
210
            this.host.open !== this.overlay.open
480✔
211
        ) {
1,111✔
212
            this.overlay.willPreventClose = this.preventNextToggle !== 'no';
254✔
213
            this.overlay.open = this.host.open;
254✔
214
        }
254✔
215
    }
1,111✔
216
}
29✔
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