From: Amit Rathiesh <31440869+amitrathiesh@users.noreply.github.com> Date: Tue, 3 Jun 2025 19:19:24 +0000 (-0400) Subject: Fix: Popover with hover and click triggers closes on mouseleave (#41511) X-Git-Tag: v5.3.7~10 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=13aa16a99bff6920b2fd44320e829067bdec1798;p=thirdparty%2Fbootstrap.git Fix: Popover with hover and click triggers closes on mouseleave (#41511) * 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 --- 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', () => {