Skip to content

Commit 9b38fa4

Browse files
bbralaKevinAHM
andauthored
Fix click through issue and rebuild dist (#779)
* Update dist/jquery.contextMenu.js Resolved submenu click passthrough on mobile #575 #471 * fix: also update source and rebuild dist --------- Co-authored-by: kjjk10 <131084116+kjjk10@users.noreply.github.com>
1 parent e6bb113 commit 9b38fa4

File tree

7 files changed

+173
-13
lines changed

7 files changed

+173
-13
lines changed

dist/jquery.contextMenu.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
* Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF)
88
* Web: http://swisnl.github.io/jQuery-contextMenu/
99
*
10-
* Copyright (c) 2011-2020 SWIS BV and contributors
10+
* Copyright (c) 2011-2025 SWIS BV and contributors
1111
*
1212
* Licensed under
1313
* MIT License http://www.opensource.org/licenses/mit-license
1414
*
15-
* Date: 2020-05-13T13:55:37.023Z
15+
* Date: 2025-11-04T11:10:12.861Z
1616
*/
1717
@-webkit-keyframes cm-spin {
1818
0% {

dist/jquery.contextMenu.js

100755100644
Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
* Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF)
77
* Web: http://swisnl.github.io/jQuery-contextMenu/
88
*
9-
* Copyright (c) 2011-2020 SWIS BV and contributors
9+
* Copyright (c) 2011-2025 SWIS BV and contributors
1010
*
1111
* Licensed under
1212
* MIT License http://www.opensource.org/licenses/mit-license
1313
*
14-
* Date: 2020-05-13T13:55:36.983Z
14+
* Date: 2025-11-04T11:10:13.179Z
1515
*/
1616

1717
// jscs:disable
@@ -31,6 +31,36 @@
3131

3232
'use strict';
3333

34+
// helper function to check for rapid interactions after menu display
35+
var isInteractionTooFast = function($element) {
36+
if (!('ontouchstart' in window
37+
|| navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0)) {
38+
return false;
39+
}
40+
var interactionTime = Date.now();
41+
var $liItem = $element.is('input, textarea, select') ? $element.closest('.context-menu-item') : $element;
42+
if (!$liItem || !$liItem.length) {
43+
return false;
44+
}
45+
var $parentMenu = $liItem.parent();
46+
if (!$parentMenu || !$parentMenu.length) {
47+
return false;
48+
}
49+
50+
// only apply the check for items within submenus
51+
if ($parentMenu.hasClass('context-menu-root')) {
52+
return false;
53+
}
54+
55+
var showTimestamp = $parentMenu.data('_showTimestamp');
56+
var timeDifference = showTimestamp ? interactionTime - showTimestamp : Infinity;
57+
58+
// threshold for fast interaction (e.g., mobile tap)
59+
var threshold = 50; // ms
60+
61+
return timeDifference < threshold;
62+
};
63+
3464
// TODO: -
3565
// ARIA stuff: menuitem, menuitemcheckbox und menuitemradio
3666
// create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative
@@ -824,6 +854,11 @@
824854
opt = data.contextMenu,
825855
root = data.contextMenuRoot;
826856

857+
// prevent fast hover on mobile tap-through
858+
if (isInteractionTooFast($this)) {
859+
return;
860+
}
861+
827862
root.hovering = true;
828863

829864
// abort if we're re-entering
@@ -877,11 +912,27 @@
877912
key = data.contextMenuKey,
878913
callback;
879914

915+
// prevent fast click-through on mobile taps
916+
if (isInteractionTooFast($this)) {
917+
e.preventDefault();
918+
e.stopImmediatePropagation();
919+
return;
920+
}
921+
880922
// abort if the key is unknown or disabled or is a menu
881-
if (!opt.items[key] || $this.is('.' + root.classNames.disabled + ', .context-menu-separator, .' + root.classNames.notSelectable) || ($this.is('.context-menu-submenu') && root.selectableSubMenu === false )) {
923+
// explicitly handle non-selectable submenu clicks first to stop propagation
924+
if ($this.is('.context-menu-submenu') && root.selectableSubMenu === false) {
925+
e.preventDefault();
926+
e.stopImmediatePropagation(); // Stop event here for non-selectable submenus
882927
return;
883928
}
884929

930+
// original check for other non-clickable/disabled items
931+
if (!opt.items[key] || $this.is('.' + root.classNames.disabled + ', .context-menu-separator, .' + root.classNames.notSelectable)) {
932+
return;
933+
}
934+
935+
// if it wasn't a non-selectable submenu or other disabled item, prevent default and stop propagation before callback
885936
e.preventDefault();
886937
e.stopImmediatePropagation();
887938

@@ -943,6 +994,10 @@
943994
// position sub-menu - do after show so dumb $.ui.position can keep up
944995
if (opt.$node) {
945996
root.positionSubmenu.call(opt.$node, opt.$menu);
997+
if (opt.$menu) {
998+
var focusShowTimestamp = Date.now();
999+
opt.$menu.data('_showTimestamp', focusShowTimestamp);
1000+
}
9461001
}
9471002
},
9481003
// blur <command>
@@ -1008,6 +1063,9 @@
10081063
opt.$menu.css(css)[opt.animation.show](opt.animation.duration, function () {
10091064
$trigger.trigger('contextmenu:visible');
10101065

1066+
var rootShowTimestamp = Date.now();
1067+
opt.$menu.data('_showTimestamp', rootShowTimestamp);
1068+
10111069
op.activated(opt);
10121070
opt.events.activated(opt);
10131071
});
@@ -1116,6 +1174,16 @@
11161174
root = opt;
11171175
}
11181176

1177+
// define handler for fast input clicks
1178+
var handleFastInputClick = function(e) {
1179+
var $inputClicked = $(this);
1180+
if (isInteractionTooFast($inputClicked)) {
1181+
e.preventDefault();
1182+
e.stopImmediatePropagation();
1183+
return false;
1184+
}
1185+
};
1186+
11191187
// create contextMenu
11201188
opt.$menu = $('<ul class="context-menu-list"></ul>').addClass(opt.className || '').data({
11211189
'contextMenu': opt,
@@ -1266,6 +1334,8 @@
12661334
.val(item.value || '')
12671335
.prop('checked', !!item.selected)
12681336
.prependTo($label);
1337+
// prevent checkbox default action on fast click-through
1338+
$input.on('click', handleFastInputClick);
12691339
break;
12701340

12711341
case 'radio':
@@ -1274,6 +1344,8 @@
12741344
.val(item.value || '')
12751345
.prop('checked', !!item.selected)
12761346
.prependTo($label);
1347+
// prevent radio default action on fast click-through
1348+
$input.on('click', handleFastInputClick);
12771349
break;
12781350

12791351
case 'select':
@@ -1579,18 +1651,32 @@
15791651
var $menu = opt.$menu;
15801652
var $menuOffset = $menu.offset();
15811653
var winHeight = $(window).height();
1654+
var winWidth = $(window).width();
15821655
var winScrollTop = $(window).scrollTop();
1656+
var winScrollLeft = $(window).scrollLeft();
15831657
var menuHeight = $menu.height();
1658+
var outerHeight = $menu.outerHeight();
1659+
var outerWidth = $menu.outerWidth();
1660+
15841661
if(menuHeight > winHeight){
15851662
$menu.css({
15861663
'height' : winHeight + 'px',
15871664
'overflow-x': 'hidden',
15881665
'overflow-y': 'auto',
15891666
'top': winScrollTop + 'px'
15901667
});
1591-
} else if(($menuOffset.top < winScrollTop) || ($menuOffset.top + menuHeight > winScrollTop + winHeight)){
1668+
} else if($menuOffset.top < winScrollTop){
15921669
$menu.css({
1593-
'top': winScrollTop + 'px'
1670+
'top': winScrollTop + 'px'
1671+
});
1672+
} else if($menuOffset.top + outerHeight > winScrollTop + winHeight){
1673+
$menu.css({
1674+
'top': $menuOffset.top - (($menuOffset.top + outerHeight) - (winScrollTop + winHeight)) + "px"
1675+
});
1676+
}
1677+
if($menuOffset.left + outerWidth > winScrollLeft + winWidth){
1678+
$menu.css({
1679+
'left': $menuOffset.left - (($menuOffset.left + outerWidth) - (winScrollLeft + winWidth)) + "px"
15941680
});
15951681
}
15961682
}
@@ -2131,4 +2217,5 @@
21312217
$.contextMenu.handle = handle;
21322218
$.contextMenu.op = op;
21332219
$.contextMenu.menus = menus;
2220+
21342221
});

dist/jquery.contextMenu.min.css

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)