From bb91561b9e7a16f08bfabdbd7b321d3f97ae3429 Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Wed, 5 Feb 2025 18:01:37 +0300 Subject: [PATCH] FIX: Propagate pointerdown events on DMenu trigger when the menu isn't expanded (#31104) Stopping propagation when a `DMenu`'s trigger is clicked could prevent another floating UI element, e.g. the search menu in the header, from closing when clicking outside of it. We call `stopPropagation` on the event to allow more clicks within a `DMenu`'s trigger without it getting closed, so we shouldn't stop the event propagation if the `DMenu` hasn't been expanded yet. --- .../components/float-kit/d-menu-test.js | 42 +++++++++++++++++++ .../float-kit/addon/lib/d-menu-instance.js | 4 ++ .../float-kit/addon/lib/float-kit-instance.js | 10 ++++- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js index c1aba983fd1..f83f1d110f5 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js @@ -386,4 +386,46 @@ module("Integration | Component | FloatKit | d-menu", function (hooks) { assert.dom(document.body).isFocused(); }); + + test("traps pointerdown events only when expanded ", async function (assert) { + let propagated = false; + + const listener = () => { + propagated = true; + }; + + this.didInsert = (element) => { + element.addEventListener("pointerdown", listener); + }; + this.willDestroy = (element) => { + element.removeEventListener("pointerdown", listener); + }; + + await render(hbs` +
+ +
+ `); + + await triggerEvent(".d-menu-pointerdown-trap-test-trigger", "pointerdown"); + + assert.true( + propagated, + "the pointerdown event is propagated to the parent element when the menu isn't expanded" + ); + + propagated = false; + + await open(); + await triggerEvent(".d-menu-pointerdown-trap-test-trigger", "pointerdown"); + + assert.false( + propagated, + "the pointerdown event isn't propagated to the parent element when the menu is expanded" + ); + }); }); diff --git a/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js b/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js index 5ceaa0fea80..23eeaf01ce4 100644 --- a/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js +++ b/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js @@ -53,6 +53,10 @@ export default class DMenuInstance extends FloatKitInstance { this.setupListeners(); } + get shouldTrapPointerDown() { + return this.expanded; + } + @action async close(options = { focusTrigger: true }) { if (getOwner(this).isDestroying) { diff --git a/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js b/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js index 9c6c04e1aa9..ef8dc3ee665 100644 --- a/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js +++ b/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js @@ -53,7 +53,9 @@ export default class FloatKitInstance { // this is done to avoid trigger on click outside when you click on your own trigger // given trigger and content are not in the same div, we can't just check if target is // inside the menu - event.stopPropagation(); + if (this.shouldTrapPointerDown) { + event.stopPropagation(); + } } @action @@ -117,7 +119,7 @@ export default class FloatKitInstance { .forEach((trigger) => { switch (trigger) { case "hold": - this.trigger.addEventListener("touchstart", this.onTouchStart); + this.trigger.removeEventListener("touchstart", this.onTouchStart); break; case "focus": this.trigger.removeEventListener("focus", this.onFocus); @@ -231,4 +233,8 @@ export default class FloatKitInstance { return this.options.untriggers ?? ["click"]; } + + get shouldTrapPointerDown() { + return true; + } }