Skip to content

Commit

Permalink
fix: do not close popover on focusout after mousedown inside (#7656)
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan authored Aug 16, 2024
1 parent 80ec293 commit c9c41af
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 3 deletions.
24 changes: 22 additions & 2 deletions packages/popover/src/vaadin-popover.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ class Popover extends PopoverPositionMixin(
?no-vertical-overlap="${this.__computeNoVerticalOverlap(effectivePosition)}"
.horizontalAlign="${this.__computeHorizontalAlign(effectivePosition)}"
.verticalAlign="${this.__computeVerticalAlign(effectivePosition)}"
@mousedown="${this.__onOverlayMouseDown}"
@mouseenter="${this.__onOverlayMouseEnter}"
@mouseleave="${this.__onOverlayMouseLeave}"
@focusin="${this.__onOverlayFocusIn}"
Expand Down Expand Up @@ -692,7 +693,7 @@ class Popover extends PopoverPositionMixin(

/** @private */
__onTargetFocusOut(event) {
if (this._overlayElement.contains(event.relatedTarget)) {
if ((this.__hasTrigger('focus') && this.__mouseDownInside) || this._overlayElement.contains(event.relatedTarget)) {
return;
}

Expand Down Expand Up @@ -734,13 +735,32 @@ class Popover extends PopoverPositionMixin(

/** @private */
__onOverlayFocusOut(event) {
if (event.relatedTarget === this.target || this._overlayElement.contains(event.relatedTarget)) {
if (
(this.__hasTrigger('focus') && this.__mouseDownInside) ||
event.relatedTarget === this.target ||
this._overlayElement.contains(event.relatedTarget)
) {
return;
}

this.__handleFocusout();
}

/** @private */
__onOverlayMouseDown() {
if (this.__hasTrigger('focus')) {
this.__mouseDownInside = true;

document.addEventListener(
'mouseup',
() => {
this.__mouseDownInside = false;
},
{ once: true },
);
}
}

/** @private */
__onOverlayMouseEnter() {
this.__hoverInside = true;
Expand Down
86 changes: 85 additions & 1 deletion packages/popover/test/trigger.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { expect } from '@vaadin/chai-plugins';
import { esc, fixtureSync, focusin, focusout, nextRender, nextUpdate, outsideClick } from '@vaadin/testing-helpers';
import {
esc,
fixtureSync,
focusin,
focusout,
middleOfNode,
mousedown,
nextRender,
nextUpdate,
outsideClick,
} from '@vaadin/testing-helpers';
import { resetMouse, sendKeys, sendMouse } from '@web/test-runner-commands';
import './not-animated-styles.js';
import '../vaadin-popover.js';
import { mouseenter, mouseleave } from './helpers.js';
Expand All @@ -14,6 +25,10 @@ describe('trigger', () => {
popover.renderer = (root) => {
if (!root.firstChild) {
root.appendChild(document.createElement('input'));

const div = document.createElement('div');
div.textContent = 'Some text content';
root.appendChild(div);
}
};
await nextRender();
Expand Down Expand Up @@ -184,6 +199,73 @@ describe('trigger', () => {
await nextRender();
expect(overlay.opened).to.be.true;
});

describe('overlay mousedown', () => {
let input;

beforeEach(async () => {
input = document.createElement('input');
target.parentNode.appendChild(input);

target.focus();
await nextRender();
});

afterEach(async () => {
input.remove();
await resetMouse();
});

it('should not close on overlay mousedown when target has focus', async () => {
const { x, y } = middleOfNode(overlay.querySelector('div'));
await sendMouse({ type: 'click', position: [Math.round(x), Math.round(y)] });
await nextUpdate();

expect(overlay.opened).to.be.true;
});

it('should not close on overlay mousedown when overlay has focus', async () => {
overlay.querySelector('input').focus();

const { x, y } = middleOfNode(overlay.querySelector('div'));
await sendMouse({ type: 'click', position: [Math.round(x), Math.round(y)] });
await nextUpdate();

expect(overlay.opened).to.be.true;
});

it('should only cancel one target focusout after the overlay mousedown', async () => {
// Remove the input so that first Tab would leave popover
overlay.querySelector('input').remove();

const { x, y } = middleOfNode(overlay.querySelector('div'));
await sendMouse({ type: 'click', position: [Math.round(x), Math.round(y)] });
await nextUpdate();

// Tab to focus input next to the target
await sendKeys({ press: 'Tab' });

// Ensure the flag for ignoring next focusout was cleared
expect(overlay.opened).to.be.false;
});

it('should only cancel one overlay focusout after the overlay mousedown', async () => {
overlay.querySelector('input').focus();

const { x, y } = middleOfNode(overlay.querySelector('div'));
await sendMouse({ type: 'click', position: [Math.round(x), Math.round(y)] });
await nextUpdate();

// Tab to focus input inside the popover
await sendKeys({ press: 'Tab' });

// Tab to focus input next to the target
await sendKeys({ press: 'Tab' });

// Ensure the flag for ignoring next focusout was cleared
expect(overlay.opened).to.be.false;
});
});
});

describe('hover and focus', () => {
Expand Down Expand Up @@ -285,13 +367,15 @@ describe('trigger', () => {
});

it('should not immediately close on target click when opened on focusin', async () => {
mousedown(target);
target.focus();
target.click();
await nextRender();
expect(overlay.opened).to.be.true;
});

it('should close on target click after a delay when opened on focusin', async () => {
mousedown(target);
target.focus();
target.click();
await nextRender();
Expand Down

0 comments on commit c9c41af

Please sign in to comment.