Skip to content

Commit c9c41af

Browse files
authored
fix: do not close popover on focusout after mousedown inside (#7656)
1 parent 80ec293 commit c9c41af

File tree

2 files changed

+107
-3
lines changed

2 files changed

+107
-3
lines changed

packages/popover/src/vaadin-popover.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ class Popover extends PopoverPositionMixin(
424424
?no-vertical-overlap="${this.__computeNoVerticalOverlap(effectivePosition)}"
425425
.horizontalAlign="${this.__computeHorizontalAlign(effectivePosition)}"
426426
.verticalAlign="${this.__computeVerticalAlign(effectivePosition)}"
427+
@mousedown="${this.__onOverlayMouseDown}"
427428
@mouseenter="${this.__onOverlayMouseEnter}"
428429
@mouseleave="${this.__onOverlayMouseLeave}"
429430
@focusin="${this.__onOverlayFocusIn}"
@@ -692,7 +693,7 @@ class Popover extends PopoverPositionMixin(
692693

693694
/** @private */
694695
__onTargetFocusOut(event) {
695-
if (this._overlayElement.contains(event.relatedTarget)) {
696+
if ((this.__hasTrigger('focus') && this.__mouseDownInside) || this._overlayElement.contains(event.relatedTarget)) {
696697
return;
697698
}
698699

@@ -734,13 +735,32 @@ class Popover extends PopoverPositionMixin(
734735

735736
/** @private */
736737
__onOverlayFocusOut(event) {
737-
if (event.relatedTarget === this.target || this._overlayElement.contains(event.relatedTarget)) {
738+
if (
739+
(this.__hasTrigger('focus') && this.__mouseDownInside) ||
740+
event.relatedTarget === this.target ||
741+
this._overlayElement.contains(event.relatedTarget)
742+
) {
738743
return;
739744
}
740745

741746
this.__handleFocusout();
742747
}
743748

749+
/** @private */
750+
__onOverlayMouseDown() {
751+
if (this.__hasTrigger('focus')) {
752+
this.__mouseDownInside = true;
753+
754+
document.addEventListener(
755+
'mouseup',
756+
() => {
757+
this.__mouseDownInside = false;
758+
},
759+
{ once: true },
760+
);
761+
}
762+
}
763+
744764
/** @private */
745765
__onOverlayMouseEnter() {
746766
this.__hoverInside = true;

packages/popover/test/trigger.test.js

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
import { expect } from '@vaadin/chai-plugins';
2-
import { esc, fixtureSync, focusin, focusout, nextRender, nextUpdate, outsideClick } from '@vaadin/testing-helpers';
2+
import {
3+
esc,
4+
fixtureSync,
5+
focusin,
6+
focusout,
7+
middleOfNode,
8+
mousedown,
9+
nextRender,
10+
nextUpdate,
11+
outsideClick,
12+
} from '@vaadin/testing-helpers';
13+
import { resetMouse, sendKeys, sendMouse } from '@web/test-runner-commands';
314
import './not-animated-styles.js';
415
import '../vaadin-popover.js';
516
import { mouseenter, mouseleave } from './helpers.js';
@@ -14,6 +25,10 @@ describe('trigger', () => {
1425
popover.renderer = (root) => {
1526
if (!root.firstChild) {
1627
root.appendChild(document.createElement('input'));
28+
29+
const div = document.createElement('div');
30+
div.textContent = 'Some text content';
31+
root.appendChild(div);
1732
}
1833
};
1934
await nextRender();
@@ -184,6 +199,73 @@ describe('trigger', () => {
184199
await nextRender();
185200
expect(overlay.opened).to.be.true;
186201
});
202+
203+
describe('overlay mousedown', () => {
204+
let input;
205+
206+
beforeEach(async () => {
207+
input = document.createElement('input');
208+
target.parentNode.appendChild(input);
209+
210+
target.focus();
211+
await nextRender();
212+
});
213+
214+
afterEach(async () => {
215+
input.remove();
216+
await resetMouse();
217+
});
218+
219+
it('should not close on overlay mousedown when target has focus', async () => {
220+
const { x, y } = middleOfNode(overlay.querySelector('div'));
221+
await sendMouse({ type: 'click', position: [Math.round(x), Math.round(y)] });
222+
await nextUpdate();
223+
224+
expect(overlay.opened).to.be.true;
225+
});
226+
227+
it('should not close on overlay mousedown when overlay has focus', async () => {
228+
overlay.querySelector('input').focus();
229+
230+
const { x, y } = middleOfNode(overlay.querySelector('div'));
231+
await sendMouse({ type: 'click', position: [Math.round(x), Math.round(y)] });
232+
await nextUpdate();
233+
234+
expect(overlay.opened).to.be.true;
235+
});
236+
237+
it('should only cancel one target focusout after the overlay mousedown', async () => {
238+
// Remove the input so that first Tab would leave popover
239+
overlay.querySelector('input').remove();
240+
241+
const { x, y } = middleOfNode(overlay.querySelector('div'));
242+
await sendMouse({ type: 'click', position: [Math.round(x), Math.round(y)] });
243+
await nextUpdate();
244+
245+
// Tab to focus input next to the target
246+
await sendKeys({ press: 'Tab' });
247+
248+
// Ensure the flag for ignoring next focusout was cleared
249+
expect(overlay.opened).to.be.false;
250+
});
251+
252+
it('should only cancel one overlay focusout after the overlay mousedown', async () => {
253+
overlay.querySelector('input').focus();
254+
255+
const { x, y } = middleOfNode(overlay.querySelector('div'));
256+
await sendMouse({ type: 'click', position: [Math.round(x), Math.round(y)] });
257+
await nextUpdate();
258+
259+
// Tab to focus input inside the popover
260+
await sendKeys({ press: 'Tab' });
261+
262+
// Tab to focus input next to the target
263+
await sendKeys({ press: 'Tab' });
264+
265+
// Ensure the flag for ignoring next focusout was cleared
266+
expect(overlay.opened).to.be.false;
267+
});
268+
});
187269
});
188270

189271
describe('hover and focus', () => {
@@ -285,13 +367,15 @@ describe('trigger', () => {
285367
});
286368

287369
it('should not immediately close on target click when opened on focusin', async () => {
370+
mousedown(target);
288371
target.focus();
289372
target.click();
290373
await nextRender();
291374
expect(overlay.opened).to.be.true;
292375
});
293376

294377
it('should close on target click after a delay when opened on focusin', async () => {
378+
mousedown(target);
295379
target.focus();
296380
target.click();
297381
await nextRender();

0 commit comments

Comments
 (0)