From 95ddec7b20e55ecb2fd7336f4e049da5ed8716e1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 18:23:02 +0000 Subject: [PATCH 1/4] 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. --- js/src/tooltip.js | 5 + test-popover.html | 1012 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1017 insertions(+) create mode 100644 test-popover.html diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 097477f7a1a8..898558fadc88 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -448,6 +448,11 @@ 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) + if (context._isShown() && context._activeTrigger[TRIGGER_CLICK]) { + context._activeTrigger[TRIGGER_CLICK] = false + } else { + context._activeTrigger[TRIGGER_CLICK] = true + } context.toggle() }) } else if (trigger !== TRIGGER_MANUAL) { diff --git a/test-popover.html b/test-popover.html new file mode 100644 index 000000000000..fc9cb335340e --- /dev/null +++ b/test-popover.html @@ -0,0 +1,1012 @@ + + + + + + Popover Test (Corrected Dependencies) + + + + +
+ +
+

Manual Testing Instructions:

+
    +
  1. Test 1: Click to open, mouseleave (should stay open)
    • Click the "Test Popover" button. Popover appears.
    • Move mouse away.
    • Expected: Popover remains visible.
  2. +
  3. Test 2: Click again to close (should close)
    • Ensure popover is open.
    • Click button again.
    • Expected: Popover disappears.
  4. +
  5. Test 3: Hover to open, mouseleave (should close)
    • Ensure popover is closed.
    • Hover button. Popover appears.
    • Move mouse away.
    • Expected: Popover disappears.
  6. +
  7. Test 4: Hover to open, click, mouseleave (should stay), click again (should close)
    • Ensure popover is closed.
    • Hover button. Popover appears.
    • Click button while popover is visible from hover.
    • Move mouse away.
    • Expected: Popover remains visible.
    • Click button again.
    • Expected: Popover disappears.
  8. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 15399f46e7abf33f05e3cc9ff748a36d9a8062da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20D=C3=A9ramond?= Date: Mon, 2 Jun 2025 21:19:12 +0200 Subject: [PATCH 2/4] Removed `test-popover.html` --- test-popover.html | 1012 --------------------------------------------- 1 file changed, 1012 deletions(-) delete mode 100644 test-popover.html diff --git a/test-popover.html b/test-popover.html deleted file mode 100644 index fc9cb335340e..000000000000 --- a/test-popover.html +++ /dev/null @@ -1,1012 +0,0 @@ - - - - - - Popover Test (Corrected Dependencies) - - - - -
- -
-

Manual Testing Instructions:

-
    -
  1. Test 1: Click to open, mouseleave (should stay open)
    • Click the "Test Popover" button. Popover appears.
    • Move mouse away.
    • Expected: Popover remains visible.
  2. -
  3. Test 2: Click again to close (should close)
    • Ensure popover is open.
    • Click button again.
    • Expected: Popover disappears.
  4. -
  5. Test 3: Hover to open, mouseleave (should close)
    • Ensure popover is closed.
    • Hover button. Popover appears.
    • Move mouse away.
    • Expected: Popover disappears.
  6. -
  7. Test 4: Hover to open, click, mouseleave (should stay), click again (should close)
    • Ensure popover is closed.
    • Hover button. Popover appears.
    • Click button while popover is visible from hover.
    • Move mouse away.
    • Expected: Popover remains visible.
    • Click button again.
    • Expected: Popover disappears.
  8. -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From ee34fa6782d846f0d8c4c9555779005e6ecc09ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20D=C3=A9ramond?= Date: Mon, 2 Jun 2025 21:21:44 +0200 Subject: [PATCH 3/4] Fix linting issues --- js/src/tooltip.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 898558fadc88..1ad5615c2174 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -448,11 +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) - if (context._isShown() && context._activeTrigger[TRIGGER_CLICK]) { - context._activeTrigger[TRIGGER_CLICK] = false - } else { - context._activeTrigger[TRIGGER_CLICK] = true - } + context._activeTrigger[TRIGGER_CLICK] = !(context._isShown() && context._activeTrigger[TRIGGER_CLICK]) context.toggle() }) } else if (trigger !== TRIGGER_MANUAL) { From ec5cf1d33c3088b5abaec9a107288d359ca13823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20D=C3=A9ramond?= Date: Mon, 2 Jun 2025 21:37:55 +0200 Subject: [PATCH 4/4] Add unit test --- js/tests/unit/popover.spec.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/js/tests/unit/popover.spec.js b/js/tests/unit/popover.spec.js index da5821e760d6..1338821bc86d 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', () => {