Skip to content

fix: autoHide never applied to a nested trigger with a different trigger mode - #808

Merged
bbrala merged 2 commits into
masterfrom
fix/issue-727-nested-trigger-autohide
Jul 29, 2026
Merged

fix: autoHide never applied to a nested trigger with a different trigger mode#808
bbrala merged 2 commits into
masterfrom
fix/issue-727-nested-trigger-autohide

Conversation

@bbrala

@bbrala bbrala commented Jul 29, 2026

Copy link
Copy Markdown
Member

Problem

Registering two menus on nested elements with different trigger modes, the setup from #727, a trigger: 'left' button inside a row registered with trigger: 'right', both with autoHide: true, leaves the inner menu unusable once the outer one has been opened, and autoHide never kicks in for it.

Root cause

Three things go wrong, all in the hand-off between the menu that is closing and the one that should open.

  1. handle.layerClick() decided whether the click that dismissed the open menu should also open another one from the closing menu's own trigger option (triggerAction). A left-click while a right-click menu is open therefore never re-dispatched, so the nested trigger's menu simply never showed. The button is now also matched against the triggers actually registered for the element under the cursor. The menu that gets asked to show still validates the button against its own trigger option in handle.contextmenu(), so nothing new can slip through.
  2. Resolving what the click landed on required root.$layer to still be present 50ms later. autoHide's own timer routinely closes the menu inside that window, and the layer element lingers in the document for another few milliseconds after root.$layer is gone, so elementFromPoint() reported the layer itself as the click target.
  3. op.hide() unbound the document-level keyboard and autoHide handlers unconditionally. A menu shown while the previous one was still tearing down lost its handlers again immediately and never auto hid. The same applied to $currentTrigger. Both are now only cleared when they still belong to the menu being hidden.

Tests

New demo page documentation/demo/nested-triggers-autohide.md with the reporter's configuration, plus test/specs/nested-triggers-autohide.js covering:

  • the inner (left-click) menu auto hides on its own
  • the outer (right-click) menu auto hides on its own
  • the inner menu opens, replaces the outer one, and auto hides while the outer menu is open
  • the outer menu opens, replaces the inner one, and auto hides while the inner menu is open

The last two fail on master and pass with this change. The single-trigger cases pass either way and are kept as regression cover. Both flows also assert that no #context-menu-layer is left behind, so no orphaned menu stays on screen.

Unit tests, acceptance tests against jQuery 1.12.4 / 2.2.4 / 3.7.1 / 4.0.0, eslint and npm run docs:build are all green.

Closes #727

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a58f812f29

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/jquery.contextMenu.js
// menu. See https://github.com/swisnl/jQuery-contextMenu/issues/727
if (target && (triggerAction || activatesOtherTrigger(target, button))) {
var showTarget = function () {
$(target).contextMenu({x: x, y: y, button: button});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid bubbling a contextmenu for nested left clicks

When a right-click menu is open and the user left-clicks a nested left-trigger, showTarget calls $.fn.contextMenu, whose coordinate overload triggers a bubbling synthetic contextmenu event. Any application contextmenu listener on an ancestor of the nested trigger therefore fires for a plain left click before the plugin's delegated document handler can stop it. Dispatch the matched menu directly, as handle.click does, rather than reintroducing the synthetic event leakage that direct dispatch was designed to avoid.

Useful? React with 👍 / 👎.

@bbrala

bbrala commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

Thanks, that one is fair and now addressed in 8b66cc2.

Addressed: the P2 about bubbling a synthetic contextmenu for nested left-clicks. Going through $.fn.contextMenu's coordinate overload did reintroduce exactly the leak #754 removed from handle.click: a jQuery contextmenu listener bound on an ancestor of the nested trigger fired on a plain left-click. The trigger lookup now returns the matched element together with its options, so the menu is dispatched straight to handle.contextmenu instead. Two details came with that:

  • it resolves the innermost match, mirroring which delegated handler a bubbling event would have reached first, so nested registrations with the same trigger mode still pick the closest one;
  • it skips selector-string registrations whose context does not contain the element, since those are delegated from that context and would otherwise be over-matched by a direct dispatch. Element/jQuery-object selectors are bound to the element directly and are deliberately not scoped that way.

The new assertion in test/specs/nested-triggers-autohide.js fails against the previous commit and passes now, so the leak is covered going forward.

Not changed: the $(target).contextMenu({x, y, button}) call is kept as the fallback for the case where only triggerAction matched and the lookup found nothing. That path is unchanged from master, so this PR does not widen it, and reworking it is out of scope here.

Unit tests, acceptance tests against jQuery 1.12.4 / 2.2.4 / 3.7.1 / 4.0.0 and eslint are green locally.

@bbrala
bbrala force-pushed the fix/issue-727-nested-trigger-autohide branch from 8b66cc2 to a97f804 Compare July 29, 2026 15:23
@bbrala
bbrala changed the base branch from master to feat/issue-739-animation-enhancements July 29, 2026 15:23
@bbrala

bbrala commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

Rebased onto feat/issue-739-animation-enhancements (#807) and retargeted this PR's base to that branch, so the diff here stays limited to the #727 work. GitHub will retarget it back to master automatically once #807 merges.

The rebase had one real conflict. Both PRs restructure the same deferred re-open block in handle.layerClick, for different reasons:

  • feat: separate show/hide animation durations and optional silent re-open #807 wraps the re-open so it can skip the wait when the same menu is re-opened with animation.animateOnReopen: false, and fires it after contextmenu:hide so op.show() still samples the menu as visible.
  • This PR replaces the block with matchTriggerForClick plus direct dispatch, and in its immediate case fired before the hide.

Taking either side wholesale drops the other's fix, so the two conditions are now combined: wait for contextmenu:hidden only when the menu is still active and this is not a no-animation re-open. The immediate case assigns openTargetMenu, which is invoked just after the hide block rather than inside it, so it still runs when the menu was already gone (the autoHide race this PR is about).

Verified together, not just separately: animation-reopen.js and all four nested-triggers-autohide.js specs pass in the same run, alongside the full unit suite.

@bbrala

bbrala commented Jul 29, 2026

Copy link
Copy Markdown
Member Author

👍

bbrala added 2 commits July 29, 2026 20:58
…ger mode

Registering two menus on nested elements with different trigger modes, e.g.
`trigger: 'left'` on a button inside a row registered with `trigger: 'right'`,
left the inner menu unusable once the outer one had been opened, and autoHide
never kicked in for it.

Three things went wrong, all in the hand-off between the menu that is closing
and the one that should open:

- handle.layerClick() decided whether the click that dismissed the open menu
  should also open another one from the *closing* menu's own `trigger` option.
  A left-click while a right-click menu was open therefore never re-dispatched,
  so the nested trigger's menu simply never showed. The button is now matched
  against the triggers that are actually registered for the element under the
  cursor, and the menu asked to show still validates the button against its own
  `trigger` option as before.
- Resolving what the click landed on required `root.$layer` to still be there
  50ms later. autoHide's own timer routinely closes the menu inside that window,
  which left the lingering layer element itself reported as the click target.
- op.hide() unbound the document-level keyboard and autoHide handlers
  unconditionally, so a menu that had just been shown while the previous one was
  still tearing down lost its handlers again and never auto hid. The same
  applied to `$currentTrigger`. Both are now only cleared when they still belong
  to the menu being hidden.

Closes #727
…bling event

Re-opening through $.fn.contextMenu's coordinate overload triggers a bubbling
synthetic 'contextmenu' event, which an application listener on an ancestor of
the trigger cannot tell apart from a real right-click. On a plain left-click on
a nested left-trigger that is exactly the leak #754 removed from handle.click.

The trigger lookup now returns the matched element and its options, so the menu
can be dispatched straight to handle.contextmenu the way handle.click does. It
resolves the innermost match, mirroring which delegated handler a bubbling event
would have reached first, and skips selector-string registrations whose context
does not contain the element.

Covered by an added assertion that a jQuery 'contextmenu' listener on an
ancestor stays silent while the nested menu opens on left-click.
@bbrala
bbrala force-pushed the fix/issue-727-nested-trigger-autohide branch from a97f804 to e6d0377 Compare July 29, 2026 19:00
@bbrala
bbrala changed the base branch from feat/issue-739-animation-enhancements to master July 29, 2026 19:00
@bbrala
bbrala merged commit 9aa9bec into master Jul 29, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto hide does not work for children elements

1 participant