Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/funny-eggs-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@spectrum-web-components/overlay': patch
'@spectrum-web-components/core': patch
---

hover overlays should close with the Esc key when trigger is not focus
7 changes: 7 additions & 0 deletions 1st-gen/packages/overlay/src/OverlayStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ class OverlayStack {
if (event.code !== 'Escape') return;
if (!this.stack.length) return;
const last = this.stack[this.stack.length - 1];
if (last?.type === 'hint') {
// Close hint/tooltip overlays on "Escape" key and prevent further handling of the event.
event.preventDefault();
event.stopPropagation();
this.closeOverlay(last);
return;
}
if (last?.type === 'page') {
event.preventDefault();
return;
Expand Down
26 changes: 26 additions & 0 deletions 1st-gen/packages/overlay/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,32 @@ export const runOverlayTriggerTests = (type: string): void => {
).to.be.false;
});

it('Escape key closes a hover popover', async function () {
expect(await isOnTopLayer(this.hoverContent)).to.be.false;

const rect = this.outerTrigger.getBoundingClientRect();
const open = oneEvent(this.outerTrigger, 'sp-opened');
await sendMouse({
type: 'move',
position: [
rect.left + rect.width / 2,
rect.top + rect.height / 2,
],
});
await open;
const close = oneEvent(this.outerTrigger, 'sp-closed');
expect(
await isOnTopLayer(this.hoverContent),
'hover content is available at point'
).to.be.true;
await sendKeys({ press: 'Escape' });
await close;
expect(
await isOnTopLayer(this.hoverContent),
'hover content is not available at point'
).to.be.false;
});

it('dispatches events on open/close', async function () {
const opened = oneEvent(this.outerButton, 'sp-opened');
this.outerButton.click();
Expand Down
33 changes: 32 additions & 1 deletion 1st-gen/packages/overlay/test/overlay-trigger-hover.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,14 @@ describe('Overlay Trigger - Hover', () => {
);
await elementUpdated(tooltip);

button.dispatchEvent(
tooltip.dispatchEvent(
new MouseEvent('pointerenter', {
bubbles: true,
composed: true,
})
);
await elementUpdated(tooltip);
expect(tooltip.open).to.be.true;

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

expect(el.open).to.be.undefined;
});
it('closes the "tooltip" on "escape" keydown', async () => {
// Open the tooltip
const opened = oneEvent(button, 'sp-opened');
button.dispatchEvent(
new MouseEvent('pointerenter', {
bubbles: true,
composed: true,
})
);
await waitUntil(
() => tooltip.open === true,
'tooltip should open',
{ timeout: 500 }
);
await opened;
expect(el.open).to.equal('hover');

// Test escape key closes tooltip when focus is not on trigger
const body = el.ownerDocument.body;
body.focus();
const closed = oneEvent(button, 'sp-closed');
const escapeKeydown = new KeyboardEvent('keydown', {
code: 'Escape',
bubbles: true,
composed: true,
});
body.dispatchEvent(escapeKeydown);
await closed;
expect(el.open).to.be.undefined;
});
});
it('persists hover content', async () => {
const el = await fixture<OverlayTrigger>(
Expand Down
12 changes: 12 additions & 0 deletions 2nd-gen/packages/core/shared/base/version.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
/**
* Copyright 2025 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

// Generated by genversion.
export const version = '1.10.0';
Loading