Skip to content

Commit 1d76b70

Browse files
authored
fix(overlay): additional content not dismissible (#5893)
Fix SWC-1047: hover overlays should close with the Esc key when trigger is not focus.
1 parent 424b7c5 commit 1d76b70

File tree

5 files changed

+83
-1
lines changed

5 files changed

+83
-1
lines changed

.changeset/funny-eggs-sell.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@spectrum-web-components/overlay': patch
3+
'@spectrum-web-components/core': patch
4+
---
5+
6+
hover overlays should close with the Esc key when trigger is not focused

1st-gen/packages/overlay/src/OverlayStack.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ class OverlayStack {
176176
if (event.code !== 'Escape') return;
177177
if (!this.stack.length) return;
178178
const last = this.stack[this.stack.length - 1];
179+
if (last?.type === 'hint') {
180+
// Close hint/tooltip overlays on "Escape" key and prevent further handling of the event.
181+
event.preventDefault();
182+
event.stopPropagation();
183+
this.closeOverlay(last);
184+
return;
185+
}
179186
if (last?.type === 'page') {
180187
event.preventDefault();
181188
return;

1st-gen/packages/overlay/test/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,32 @@ export const runOverlayTriggerTests = (type: string): void => {
624624
).to.be.false;
625625
});
626626

627+
it('Escape key closes a hover popover', async function () {
628+
expect(await isOnTopLayer(this.hoverContent)).to.be.false;
629+
630+
const rect = this.outerTrigger.getBoundingClientRect();
631+
const open = oneEvent(this.outerTrigger, 'sp-opened');
632+
await sendMouse({
633+
type: 'move',
634+
position: [
635+
rect.left + rect.width / 2,
636+
rect.top + rect.height / 2,
637+
],
638+
});
639+
await open;
640+
const close = oneEvent(this.outerTrigger, 'sp-closed');
641+
expect(
642+
await isOnTopLayer(this.hoverContent),
643+
'hover content is available at point'
644+
).to.be.true;
645+
await sendKeys({ press: 'Escape' });
646+
await close;
647+
expect(
648+
await isOnTopLayer(this.hoverContent),
649+
'hover content is not available at point'
650+
).to.be.false;
651+
});
652+
627653
it('dispatches events on open/close', async function () {
628654
const opened = oneEvent(this.outerButton, 'sp-opened');
629655
this.outerButton.click();

1st-gen/packages/overlay/test/overlay-trigger-hover.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,14 @@ describe('Overlay Trigger - Hover', () => {
140140
);
141141
await elementUpdated(tooltip);
142142

143-
button.dispatchEvent(
143+
tooltip.dispatchEvent(
144144
new MouseEvent('pointerenter', {
145145
bubbles: true,
146146
composed: true,
147147
})
148148
);
149149
await elementUpdated(tooltip);
150+
expect(tooltip.open).to.be.true;
150151

151152
tooltip.dispatchEvent(
152153
new MouseEvent('pointerleave', {
@@ -210,6 +211,36 @@ describe('Overlay Trigger - Hover', () => {
210211

211212
expect(el.open).to.be.undefined;
212213
});
214+
it('closes the "tooltip" on "escape" keydown', async () => {
215+
// Open the tooltip
216+
const opened = oneEvent(button, 'sp-opened');
217+
button.dispatchEvent(
218+
new MouseEvent('pointerenter', {
219+
bubbles: true,
220+
composed: true,
221+
})
222+
);
223+
await waitUntil(
224+
() => tooltip.open === true,
225+
'tooltip should open',
226+
{ timeout: 500 }
227+
);
228+
await opened;
229+
expect(el.open).to.equal('hover');
230+
231+
// Test escape key closes tooltip when focus is not on trigger
232+
const body = el.ownerDocument.body;
233+
body.focus();
234+
const closed = oneEvent(button, 'sp-closed');
235+
const escapeKeydown = new KeyboardEvent('keydown', {
236+
code: 'Escape',
237+
bubbles: true,
238+
composed: true,
239+
});
240+
body.dispatchEvent(escapeKeydown);
241+
await closed;
242+
expect(el.open).to.be.undefined;
243+
});
213244
});
214245
it('persists hover content', async () => {
215246
const el = await fixture<OverlayTrigger>(
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
1+
/**
2+
* Copyright 2025 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
113
// Generated by genversion.
214
export const version = '1.10.0';

0 commit comments

Comments
 (0)