From 13aa16a99bff6920b2fd44320e829067bdec1798 Mon Sep 17 00:00:00 2001 From: Amit Rathiesh <31440869+amitrathiesh@users.noreply.github.com> Date: Tue, 3 Jun 2025 15:19:24 -0400 Subject: [PATCH] Fix: Popover with hover and click triggers closes on mouseleave (#41511) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * Fix: Popover with hover and click triggers closes on mouseleave When a popover is configured with `trigger: 'hover click'`, if you open it by a click, it would incorrectly close when the mouse pointer leaves the trigger element. This was because the `mouseleave` event (part of the hover trigger) would hide the popover without adequately respecting the click trigger's intent to keep it open. This commit modifies the click event listener within `Tooltip.js` (which Popover extends) to explicitly manage the `_activeTrigger[TRIGGER_CLICK]` state: - When a click opens the popover or makes a hover-opened popover sticky, `_activeTrigger[TRIGGER_CLICK]` is set to `true`. - When a click closes an already click-activated popover, `_activeTrigger[TRIGGER_CLICK]` is set to `false`. The `_leave()` method, called by `mouseleave`, already checks `_isWithActiveTrigger()`. With `_activeTrigger[TRIGGER_CLICK]` now accurately reflecting the click state, `_leave()` will not hide a click-activated popover when the mouse leaves the trigger element. The popover will now correctly remain open until a subsequent click closes it. * Removed `test-popover.html` * Fix linting issues * Add unit test --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Julien Déramond --- js/src/tooltip.js | 1 + js/tests/unit/popover.spec.js | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 097477f7a1..1ad5615c21 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -448,6 +448,7 @@ class Tooltip extends BaseComponent { if (trigger === 'click') { EventHandler.on(this._element, this.constructor.eventName(EVENT_CLICK), this._config.selector, event => { const context = this._initializeOnDelegatedTarget(event) + context._activeTrigger[TRIGGER_CLICK] = !(context._isShown() && context._activeTrigger[TRIGGER_CLICK]) context.toggle() }) } else if (trigger !== TRIGGER_MANUAL) { diff --git a/js/tests/unit/popover.spec.js b/js/tests/unit/popover.spec.js index da5821e760..1338821bc8 100644 --- a/js/tests/unit/popover.spec.js +++ b/js/tests/unit/popover.spec.js @@ -1,6 +1,8 @@ import EventHandler from '../../src/dom/event-handler.js' import Popover from '../../src/popover.js' -import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture.js' +import { + clearFixture, getFixture, jQueryMock, createEvent +} from '../helpers/fixture.js' describe('Popover', () => { let fixtureEl @@ -313,6 +315,28 @@ describe('Popover', () => { popover.show() }) }) + + it('should keep popover open when mouse leaves after click trigger', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = 'BS X' + + const popoverEl = fixtureEl.querySelector('a') + new Popover(popoverEl) // eslint-disable-line no-new + + popoverEl.addEventListener('shown.bs.popover', () => { + popoverEl.dispatchEvent(createEvent('mouseout')) + + popoverEl.addEventListener('hide.bs.popover', () => { + throw new Error('Popover should not hide when mouse leaves after click') + }) + + expect(document.querySelector('.popover')).not.toBeNull() + resolve() + }) + + popoverEl.click() + }) + }) }) describe('hide', () => { -- 2.47.2