From 8a763eab6f064a8e3c626ae843567d8facfa003d Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Wed, 26 Jun 2024 00:57:12 -0700 Subject: [PATCH] fix multiple issues with ctx menu event delegation and keyboard navigation --- src/UI/UIContextMenu.js | 36 ++++++++++++++++++++++-------------- src/keyboard.js | 19 ++++++++----------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/src/UI/UIContextMenu.js b/src/UI/UIContextMenu.js index 140b2436..d194234a 100644 --- a/src/UI/UIContextMenu.js +++ b/src/UI/UIContextMenu.js @@ -154,14 +154,14 @@ /** * Trigger a possible row activation whenever entering a new row. */ - var mouseenterRow = function () { + var mouseenterRow = function (e, data) { if (timeoutId) { // Cancel any previous activation delays clearTimeout(timeoutId); } options.enter(this); - possiblyActivate(this); + possiblyActivate(this, e, data); }, mouseleaveRow = function () { options.exit(this); @@ -177,7 +177,12 @@ /** * Activate a menu row. */ - var activate = function (row) { + var activate = function (row, e, data) { + if(mouseLocs[mouseLocs.length - 1]?.x !== undefined && mouseLocs[mouseLocs.length - 1]?.y !== undefined){ + row.pageX = mouseLocs[mouseLocs.length - 1].x; + row.pageY = mouseLocs[mouseLocs.length - 1].y; + } + if (row == activeRow) { return; } @@ -187,7 +192,7 @@ } - options.activate(row); + options.activate(row, e, data); activeRow = row; }; @@ -196,15 +201,15 @@ * shouldn't activate yet because user may be trying to enter * a submenu's content, then delay and check again later. */ - var possiblyActivate = function (row) { + var possiblyActivate = function (row, e, data) { var delay = activationDelay(); if (delay) { timeoutId = setTimeout(function () { - possiblyActivate(row); + possiblyActivate(row, e, data); }, delay); } else { - activate(row); + activate(row, e, data); } }; @@ -327,7 +332,7 @@ return 0; }; - $menu.on('mouseenter', function(e) { + $menu.on('mouseenter', function(e, data) { if($menu.find('.context-menu-item-active').length === 0 && $menu.find('.has-open-context-menu-submenu').length === 0) activeRow = null; }) @@ -395,7 +400,8 @@ function UIContextMenu(options){ h += `${options.items[i].icon ?? ''}`; h += `${options.items[i].icon_active ?? (options.items[i].icon ?? '')}`; // label - h += `${html_encode(options.items[i].html)}`; + h += `${html_encode(options.items[i].html)}`; + h += `${html_encode(options.items[i].html_active ?? options.items[i].html)}`; // arrow h += ``; h += ``; @@ -533,7 +539,12 @@ function UIContextMenu(options){ // this.activate(e); }, // activates item when mouse enters depending on mouse position and direction - activate: function (e) { + activate: function (e, event, data) { + // make sure last recorded mouse position is the same as the current one before activating + // this is because switching contexts from iframe to window can cause the mouse position to be off + if(!data?.keyboard && (e.pageX !== window.mouseX || e.pageY !== window.mouseY)){ + return; + } // activate items let item = $(e).closest('.context-menu-item'); // mark other menu items as inactive @@ -686,8 +697,7 @@ $(document).on('mouseenter', '.context-menu', function(e){ }) }) -$(document).on('mouseenter', '.context-menu-item', function(e){ - // select_ctxmenu_item(this); +$(document).on('mouseenter', '.context-menu-item', function(e, data){ }) $(document).on('mouseenter', '.context-menu-divider', function(e){ @@ -695,6 +705,4 @@ $(document).on('mouseenter', '.context-menu-divider', function(e){ $(this).siblings('.context-menu-item:not(.has-open-context-menu-submenu)').removeClass('context-menu-item-active'); }) -$(document) - export default UIContextMenu; \ No newline at end of file diff --git a/src/keyboard.js b/src/keyboard.js index 1eaaea0e..aa9e4487 100644 --- a/src/keyboard.js +++ b/src/keyboard.js @@ -161,9 +161,9 @@ $(document).bind('keydown', async function(e){ return false; } // if right arrow is pressed, open the submenu by triggering a mouseover event - else if($('.context-menu-active .context-menu-item-active').length > 0 && (e.which === 39)){ + else if($('.context-menu-active .context-menu-item-active').length > 0 && e.which === 39){ const selected_item = $('.context-menu-active .context-menu-item-active').get(0); - $(selected_item).trigger('mouseover'); + $(selected_item).trigger('mouseover', {keyboard: true}); // if the submenu is open, select the first item in the submenu if($(selected_item).hasClass('context-menu-item-submenu') === true){ $(selected_item).removeClass('context-menu-item-active'); @@ -184,6 +184,7 @@ $(document).bind('keydown', async function(e){ // select the item that opened the submenu let selected_item = $('.context-menu-active .context-menu-item-active-blurred').get(0); $(selected_item).removeClass('context-menu-item-active-blurred'); + $(selected_item).removeClass('has-open-context-menu-submenu'); $(selected_item).addClass('context-menu-item-active'); return false; @@ -191,7 +192,7 @@ $(document).bind('keydown', async function(e){ // if enter is pressed, trigger a click event on the selected item else if($('.context-menu-active .context-menu-item-active').length > 0 && (e.which === 13)){ let selected_item = $('.context-menu-active .context-menu-item-active').get(0); - $(selected_item).trigger('click'); + $(selected_item).trigger('click', {keyboard: true}); return false; } } @@ -482,10 +483,10 @@ $(document).bind('keydown', async function(e){ } // search for matches - let haystack = $('.context-menu-active').find(`.context-menu-item`); + let haystack = $('.context-menu-active').find(`.context-menu-item > .contextmenu-label`); for(let j=0; j < haystack.length; j++){ if($(haystack[j]).text().toLowerCase().startsWith(window.keypress_item_seach_term)){ - matches.push(haystack[j]) + matches.push(haystack[j].closest('.context-menu-item')); } } @@ -640,15 +641,11 @@ $(document).bind("keyup keydown", async function(e){ // if this is a selected context menu item, open it // --------------------------------------------- else if($('.context-menu-active .context-menu-item-active').length > 0 && (e.which === 13)){ - // let selected_item = $('.context-menu-active .context-menu-item-active').get(0); - // $(selected_item).trigger('mouseover'); - // $(selected_item).trigger('click'); - let selected_item = $('.context-menu-active .context-menu-item-active').get(0); $(selected_item).removeClass('context-menu-item-active'); $(selected_item).addClass('context-menu-item-active-blurred'); - $(selected_item).trigger('mouseover'); - $(selected_item).trigger('click'); + $(selected_item).trigger('mouseover', {keyboard: true}); + $(selected_item).trigger('click', {keyboard: true}); if($('.context-menu[data-is-submenu="true"]').length > 0){ let selected_item = $('.context-menu[data-is-submenu="true"] .context-menu-item').get(0); window.select_ctxmenu_item(selected_item);