From 7707d417e50566cf42470860f3f536d765e18ecd Mon Sep 17 00:00:00 2001 From: bjorn Date: Wed, 29 Jul 2026 21:24:35 +0200 Subject: [PATCH] fix: don't crash when the menu is gone by the time the layer click arrives With `useModal: false` the menu is dismissed by a document-level mousedown listener rather than by the transparent layer. That listener outlives the menu it was registered for, so the menu can already be gone when the next click arrives: a `build` menu empties its own options object once it has finished hiding, and a `hide` handler calling contextMenu('destroy') tears it down outright. handle.layerClick() guarded that case with root.$menu === null || typeof root.$menu === 'undefined' || ... and then called root.$menu.trigger('contextmenu:hide') inside the block it opened for it, so the very case the guard detected was the one that threw "Cannot read properties of undefined (reading 'trigger')". The throw also happened before `onhide` could run, so the dismiss listener was never unregistered and one more piled up for every menu that had been opened. Skip straight to the cleanup when there is nothing left to hide, covering both an absent `$menu` and an empty jQuery object. Everything else on the path is untouched: the same events fire in the same order, `onhide` keeps its timing, and the `isNearRecentSelectChange()` grace period still takes precedence exactly as before. Fixes #805 --- CHANGELOG.md | 1 + src/jquery.contextMenu.js | 27 ++- test/specs/issue-805-no-modal-destroy.js | 109 +++++++++ ...issue-805-layerclick-after-destroy.test.js | 221 ++++++++++++++++++ 4 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 test/specs/issue-805-no-modal-destroy.js create mode 100644 test/unit/issue-805-layerclick-after-destroy.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 918b6628..511c2669 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ * A `select`'s phantom mousedown is no longer treated as an outside click (fixes #744) * A re-dispatched layer click now targets the element actually clicked (fixes #771) * Guard against undefined `e.data` in the contextmenu handler (fixes #777) +* Clicking on after the menu was destroyed no longer throws with `useModal: false` (fixes #805) #### Documentation diff --git a/src/jquery.contextMenu.js b/src/jquery.contextMenu.js index 5f77c0d9..da8c4636 100644 --- a/src/jquery.contextMenu.js +++ b/src/jquery.contextMenu.js @@ -739,8 +739,20 @@ // if the click closing is done through windwow event listener rather than a transparent layer if (!root.$layer) { + // There may be no menu left to hide at all: this listener + // outlives the menu it was registered for, so the menu can + // already be gone by the time the next click arrives - a + // `build` menu empties its own options object once it has + // finished hiding (see op.hide()), and a `hide` event + // handler calling $(selector).contextMenu('destroy') tears + // it down outright. `$menu` is left either dropped + // altogether or as an empty jQuery object, and the latter + // used to throw on the $menu[0] dereference below. + // See https://github.com/swisnl/jQuery-contextMenu/issues/805 + var menuIsGone = !root.$menu || !root.$menu.length; + target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop()); - if (root.$menu === null || typeof root.$menu === 'undefined' || (!root.$menu[0].contains(target) && !isWithinDetachedSubmenus(root, target))) { + if (menuIsGone || (!root.$menu[0].contains(target) && !isWithinDetachedSubmenus(root, target))) { // Choosing an option from a native