Skip to content

Commit 12d6642

Browse files
committed
Update dist/jquery.contextMenu.js
Resolved submenu click passthrough on mobile swisnl#575 swisnl#471
1 parent e6bb113 commit 12d6642

File tree

1 file changed

+73
-1
lines changed

1 file changed

+73
-1
lines changed

dist/jquery.contextMenu.js

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,11 @@
824824
opt = data.contextMenu,
825825
root = data.contextMenuRoot;
826826

827+
// prevent fast hover on mobile tap-through
828+
if (isInteractionTooFast($this)) {
829+
return;
830+
}
831+
827832
root.hovering = true;
828833

829834
// abort if we're re-entering
@@ -877,11 +882,27 @@
877882
key = data.contextMenuKey,
878883
callback;
879884

885+
// prevent fast click-through on mobile taps
886+
if (isInteractionTooFast($this)) {
887+
e.preventDefault();
888+
e.stopImmediatePropagation();
889+
return;
890+
}
891+
880892
// 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 )) {
893+
// explicitly handle non-selectable submenu clicks first to stop propagation
894+
if ($this.is('.context-menu-submenu') && root.selectableSubMenu === false) {
895+
e.preventDefault();
896+
e.stopImmediatePropagation(); // Stop event here for non-selectable submenus
882897
return;
883898
}
884899

900+
// original check for other non-clickable/disabled items
901+
if (!opt.items[key] || $this.is('.' + root.classNames.disabled + ', .context-menu-separator, .' + root.classNames.notSelectable)) {
902+
return;
903+
}
904+
905+
// if it wasn't a non-selectable submenu or other disabled item, prevent default and stop propagation before callback
885906
e.preventDefault();
886907
e.stopImmediatePropagation();
887908

@@ -943,6 +964,10 @@
943964
// position sub-menu - do after show so dumb $.ui.position can keep up
944965
if (opt.$node) {
945966
root.positionSubmenu.call(opt.$node, opt.$menu);
967+
if (opt.$menu) {
968+
var focusShowTimestamp = Date.now();
969+
opt.$menu.data('_showTimestamp', focusShowTimestamp);
970+
}
946971
}
947972
},
948973
// blur <command>
@@ -1008,6 +1033,9 @@
10081033
opt.$menu.css(css)[opt.animation.show](opt.animation.duration, function () {
10091034
$trigger.trigger('contextmenu:visible');
10101035

1036+
var rootShowTimestamp = Date.now();
1037+
opt.$menu.data('_showTimestamp', rootShowTimestamp);
1038+
10111039
op.activated(opt);
10121040
opt.events.activated(opt);
10131041
});
@@ -1116,6 +1144,16 @@
11161144
root = opt;
11171145
}
11181146

1147+
// define handler for fast input clicks
1148+
var handleFastInputClick = function(e) {
1149+
var $inputClicked = $(this);
1150+
if (isInteractionTooFast($inputClicked)) {
1151+
e.preventDefault();
1152+
e.stopImmediatePropagation();
1153+
return false;
1154+
}
1155+
};
1156+
11191157
// create contextMenu
11201158
opt.$menu = $('<ul class="context-menu-list"></ul>').addClass(opt.className || '').data({
11211159
'contextMenu': opt,
@@ -1266,6 +1304,8 @@
12661304
.val(item.value || '')
12671305
.prop('checked', !!item.selected)
12681306
.prependTo($label);
1307+
// prevent checkbox default action on fast click-through
1308+
$input.on('click', handleFastInputClick);
12691309
break;
12701310

12711311
case 'radio':
@@ -1274,6 +1314,8 @@
12741314
.val(item.value || '')
12751315
.prop('checked', !!item.selected)
12761316
.prependTo($label);
1317+
// prevent radio default action on fast click-through
1318+
$input.on('click', handleFastInputClick);
12771319
break;
12781320

12791321
case 'select':
@@ -2131,4 +2173,34 @@
21312173
$.contextMenu.handle = handle;
21322174
$.contextMenu.op = op;
21332175
$.contextMenu.menus = menus;
2176+
2177+
// helper function to check for rapid interactions after menu display
2178+
var isInteractionTooFast = function($element) {
2179+
if (!('ontouchstart' in window
2180+
|| navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0)) {
2181+
return false;
2182+
}
2183+
var interactionTime = Date.now();
2184+
var $liItem = $element.is('input, textarea, select') ? $element.closest('.context-menu-item') : $element;
2185+
if (!$liItem || !$liItem.length) {
2186+
return false;
2187+
}
2188+
var $parentMenu = $liItem.parent();
2189+
if (!$parentMenu || !$parentMenu.length) {
2190+
return false;
2191+
}
2192+
2193+
// only apply the check for items within submenus
2194+
if ($parentMenu.hasClass('context-menu-root')) {
2195+
return false;
2196+
}
2197+
2198+
var showTimestamp = $parentMenu.data('_showTimestamp');
2199+
var timeDifference = showTimestamp ? interactionTime - showTimestamp : Infinity;
2200+
2201+
// threshold for fast interaction (e.g., mobile tap)
2202+
var threshold = 50; // ms
2203+
2204+
return timeDifference < threshold;
2205+
};
21342206
});

0 commit comments

Comments
 (0)