import defaults from '../defaults';
/**
* @typedef {jQuery.Event} ContextMenuEvent
* @augments jQuery.Event
* @property {ContextMenuData} data
*/
export default class ContextMenuEventHandler {
/**
* @constructs ContextMenuEventHandler
* @constructor
* @property {?JQuery} $currentTrigger
* @property {Object} hoveract
*/
constructor() {
this.$currentTrigger = null;
this.hoveract = {};
}
/**
* Helper to abort an event
*
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
abortevent(e) {
e.preventDefault();
e.stopImmediatePropagation();
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
contextmenu(e) {
const $this = $(e.currentTarget);
if (!e.data) {
throw new Error('No data attached');
}
// disable actual context-menu if we are using the right mouse button as the trigger
if (e.data.trigger === 'right') {
e.preventDefault();
e.stopImmediatePropagation();
}
// abort native-triggered events unless we're triggering on right click
if ((e.data.trigger !== 'right' && e.data.trigger !== 'demand') && e.originalEvent) {
return;
}
// Let the current contextmenu decide if it should show or not based on its own trigger settings
if (typeof e.mouseButton !== 'undefined') {
if (!(e.data.trigger === 'left' && e.mouseButton === 0) && !(e.data.trigger === 'right' && e.mouseButton === 2)) {
// Mouse click is not valid.
return;
}
}
// abort event if menu is visible for this trigger
if ($this.hasClass('context-menu-active')) {
return;
}
if (!$this.hasClass('context-menu-disabled')) {
// theoretically need to fire a show event at <menu>
// http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus
// var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this });
// e.data.$menu.trigger(evt);
e.data.manager.handler.$currentTrigger = $this;
if (e.data.build) {
const built = e.data.build(e, $this);
// abort if build() returned false
if (built === false) {
return;
}
// dynamically build menu on invocation
e.data = $.extend(true, {}, defaults, e.data, built || {});
// abort if there are no items to display
if (!e.data.items || $.isEmptyObject(e.data.items)) {
// Note: jQuery captures and ignores errors from event handlers
if (window.console) {
(console.error || console.log).call(console, 'No items specified to show in contextMenu');
}
throw new Error('No Items specified');
}
// backreference for custom command type creation
e.data.$trigger = e.data.manager.handler.$currentTrigger;
e.data.manager.operations.create(e, e.data);
}
let showMenu = false;
for (let item in e.data.items) {
if (e.data.items.hasOwnProperty(item)) {
let visible;
if ($.isFunction(e.data.items[item].visible)) {
visible = e.data.items[item].visible.call($this, e, item, e.data, e.data);
} else if (typeof e.data.items[item] !== 'undefined' && e.data.items[item].visible) {
visible = e.data.items[item].visible === true;
} else {
visible = true;
}
if (visible) {
showMenu = true;
}
}
}
if (showMenu) {
// show menu
e.data.manager.operations.show.call($this, e, e.data, e.pageX, e.pageY);
}
}
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
click(e) {
e.preventDefault();
e.stopImmediatePropagation();
$(this).trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY, originalEvent: e}));
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
mousedown(e) {
// register mouse down
const $this = $(this);
// hide any previous menus
if (e.data.manager.handler.$currentTrigger && e.data.manager.handler.$currentTrigger.length && !e.data.manager.handler.$currentTrigger.is($this)) {
e.data.manager.handler.$currentTrigger.data('contextMenu').$menu.trigger($.Event('contextmenu', {data: e.data, originalEvent: e}));
}
// activate on right click
if (e.button === 2) {
e.data.manager.handler.$currentTrigger = $this.data('contextMenuActive', true);
}
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
mouseup(e) {
// show menu
const $this = $(this);
if ($this.data('contextMenuActive') && e.data.manager.handler.$currentTrigger && e.data.manager.handler.$currentTrigger.length && e.data.manager.handler.$currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) {
e.preventDefault();
e.stopImmediatePropagation();
e.data.manager.handler.$currentTrigger = $this;
$this.trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY, originalEvent: e}));
}
$this.removeData('contextMenuActive');
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
mouseenter(e) {
const $this = $(this);
const $related = $(e.relatedTarget);
const $document = $(document);
// abort if we're coming from a menu
if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
return;
}
// abort if a menu is shown
if (e.data.manager.handler.$currentTrigger && e.data.manager.handler.$currentTrigger.length) {
return;
}
e.data.manager.handler.hoveract.pageX = e.pageX;
e.data.manager.handler.hoveract.pageY = e.pageY;
e.data.manager.handler.hoveract.data = e.data;
$document.on('mousemove.contextMenuShow', e.data.manager.handler.mousemove);
e.data.manager.handler.hoveract.timer = setTimeout(function () {
e.data.manager.handler.hoveract.timer = null;
$document.off('mousemove.contextMenuShow');
e.data.manager.handler.$currentTrigger = $this;
$this.trigger($.Event('contextmenu', {
data: e.data.manager.handler.hoveract.data,
pageX: e.data.manager.handler.hoveract.pageX,
pageY: e.data.manager.handler.hoveract.pageY
}));
}, e.data.delay);
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
mousemove(e) {
e.data.manager.handler.hoveract.pageX = e.pageX;
e.data.manager.handler.hoveract.pageY = e.pageY;
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
mouseleave(e) {
// abort if we're leaving for a menu
const $related = $(e.relatedTarget);
if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
return;
}
try {
clearTimeout(e.data.manager.handler.hoveract.timer);
} catch (e) {
}
e.data.manager.handler.hoveract.timer = null;
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
layerClick(e) {
let $this = $(this);
let root = $this.data('contextMenuRoot');
if (root === null || typeof root === 'undefined') {
throw new Error('No ContextMenuData found');
}
let button = e.button;
let x = e.pageX;
let y = e.pageY;
let target;
let offset;
e.preventDefault();
setTimeout(function () {
let $window = $(window);
let triggerAction = ((root.trigger === 'left' && button === 0) || (root.trigger === 'right' && button === 2));
// find the element that would've been clicked, wasn't the layer in the way
if (document.elementFromPoint && root.$layer) {
root.$layer.hide();
target = document.elementFromPoint(x - $window.scrollLeft(), y - $window.scrollTop());
// also need to try and focus this element if we're in a contenteditable area,
// as the layer will prevent the browser mouse action we want
if (target.isContentEditable) {
const range = document.createRange();
const sel = window.getSelection();
range.selectNode(target);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
$(target).trigger(e);
root.$layer.show();
}
if (root.hideOnSecondTrigger && triggerAction && root.$menu !== null && typeof root.$menu !== 'undefined') {
root.$menu.trigger('contextmenu:hide', {data: root, originalEvent: e});
return;
}
if (root.reposition && triggerAction) {
if (document.elementFromPoint) {
if (root.$trigger.is(target)) {
root.position.call(root.$trigger, e, root, x, y);
return;
}
} else {
offset = root.$trigger.offset();
const $window = $(window);
// while this looks kinda awful, it's the best way to avoid
// unnecessarily calculating any positions
offset.top += $window.scrollTop();
if (offset.top <= e.pageY) {
offset.left += $window.scrollLeft();
if (offset.left <= e.pageX) {
offset.bottom = offset.top + root.$trigger.outerHeight();
if (offset.bottom >= e.pageY) {
offset.right = offset.left + root.$trigger.outerWidth();
if (offset.right >= e.pageX) {
// reposition
root.position.call(root.$trigger, e, root, x, y);
return;
}
}
}
}
}
}
if (target && triggerAction) {
root.$trigger.one('contextmenu:hidden', function () {
$(target).contextMenu({x: x, y: y, button: button, originalEvent: e});
});
}
if (root.$menu !== null && typeof root.$menu !== 'undefined') {
root.$menu.trigger('contextmenu:hide', {data: root, originalEvent: e});
}
}, 50);
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
* @param {ContextMenuItem} currentMenuData
*/
keyStop(e, currentMenuData) {
if (!currentMenuData.isInput) {
e.preventDefault();
}
e.stopPropagation();
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
key(e) {
let rootMenuData = {};
// Only get the data from this.$currentTrigger if it exists
if (e.data.manager.handler.$currentTrigger) {
rootMenuData = e.data.manager.handler.$currentTrigger.data('contextMenu') || {};
}
// If the trigger happen on a element that are above the contextmenu do this
if (typeof rootMenuData.zIndex === 'undefined') {
rootMenuData.zIndex = 0;
}
const getZIndexOfTriggerTarget = function (target) {
if (target.style.zIndex !== '') {
return target.style.zIndex;
} else {
if (target.offsetParent !== null && typeof target.offsetParent !== 'undefined') {
return getZIndexOfTriggerTarget(target.offsetParent);
} else if (target.parentElement !== null && typeof target.parentElement !== 'undefined') {
return getZIndexOfTriggerTarget(target.parentElement);
}
}
};
let targetZIndex = getZIndexOfTriggerTarget(e.target);
// If targetZIndex is heigher then rootMenuData.zIndex dont progress any futher.
// This is used to make sure that if you are using a dialog with a input / textarea / contenteditable div
// and its above the contextmenu it wont steal keys events
if (rootMenuData.$menu && parseInt(targetZIndex, 10) > parseInt(rootMenuData.$menu.css('zIndex'), 10)) {
return;
}
switch (e.keyCode) {
case 9:
case 38: // up
e.data.manager.handler.keyStop(e, rootMenuData);
// if keyCode is [38 (up)] or [9 (tab) with shift]
if (rootMenuData.isInput) {
if (e.keyCode === 9 && e.shiftKey) {
e.preventDefault();
if (rootMenuData.$selected) {
rootMenuData.$selected.find('input, textarea, select').blur();
}
if (rootMenuData.$menu !== null && typeof rootMenuData.$menu !== 'undefined') {
rootMenuData.$menu.trigger('prevcommand', {data: rootMenuData, originalEvent: e});
}
return;
} else if (e.keyCode === 38 && rootMenuData.$selected.find('input, textarea, select').prop('type') === 'checkbox') {
// checkboxes don't capture this key
e.preventDefault();
return;
}
} else if (e.keyCode !== 9 || e.shiftKey) {
if (rootMenuData.$menu !== null && typeof rootMenuData.$menu !== 'undefined') {
rootMenuData.$menu.trigger('prevcommand', {data: rootMenuData, originalEvent: e});
}
return;
}
break;
// omitting break;
// case 9: // tab - reached through omitted break;
case 40: // down
e.data.manager.handler.keyStop(e, rootMenuData);
if (rootMenuData.isInput) {
if (e.keyCode === 9) {
e.preventDefault();
if (rootMenuData.$selected) {
rootMenuData.$selected.find('input, textarea, select').blur();
}
if (rootMenuData.$menu !== null && typeof rootMenuData.$menu !== 'undefined') {
rootMenuData.$menu.trigger('nextcommand', {data: rootMenuData, originalEvent: e});
}
return;
} else if (e.keyCode === 40 && rootMenuData.$selected.find('input, textarea, select').prop('type') === 'checkbox') {
// checkboxes don't capture this key
e.preventDefault();
return;
}
} else {
if (rootMenuData.$menu !== null && typeof rootMenuData.$menu !== 'undefined') {
rootMenuData.$menu.trigger('nextcommand', {data: rootMenuData, originalEvent: e});
}
return;
}
break;
case 37: // left
e.data.manager.handler.keyStop(e, rootMenuData);
if (rootMenuData.isInput || !rootMenuData.$selected || !rootMenuData.$selected.length) {
break;
}
if (!rootMenuData.$selected.parent().hasClass('context-menu-root')) {
const $parent = rootMenuData.$selected.parent().parent();
rootMenuData.$selected.trigger('contextmenu:blur', {data: rootMenuData, originalEvent: e});
rootMenuData.$selected = $parent;
return;
}
break;
case 39: // right
e.data.manager.handler.keyStop(e, rootMenuData);
if (rootMenuData.isInput || !rootMenuData.$selected || !rootMenuData.$selected.length) {
break;
}
const itemdata = rootMenuData.$selected.data('contextMenu') || {};
if (itemdata.$menu && rootMenuData.$selected.hasClass('context-menu-submenu')) {
rootMenuData.$selected = null;
itemdata.$selected = null;
itemdata.$menu.trigger('nextcommand', {data: itemdata, originalEvent: e});
return;
}
break;
case 35: // end
case 36: // home
if (rootMenuData.$selected && rootMenuData.$selected.find('input, textarea, select').length) {
break;
} else {
((rootMenuData.$selected && rootMenuData.$selected.parent()) || rootMenuData.$menu)
.children(':not(.' + rootMenuData.classNames.disabled + ', .' + rootMenuData.classNames.notSelectable + ')')[e.keyCode === 36 ? 'first' : 'last']()
.trigger('contextmenu:focus', {data: rootMenuData, originalEvent: e});
e.preventDefault();
break;
}
case 13: // enter
e.data.manager.handler.keyStop(e, rootMenuData);
if (rootMenuData.isInput) {
if (rootMenuData.$selected && !rootMenuData.$selected.is('textarea, select')) {
e.preventDefault();
return;
}
break;
}
if (typeof rootMenuData.$selected !== 'undefined' && rootMenuData.$selected !== null) {
rootMenuData.$selected.trigger('mouseup', {data: rootMenuData, originalEvent: e});
}
return;
case 32: // space
case 33: // page up
case 34: // page down
// prevent browser from scrolling down while menu is visible
e.data.manager.handler.keyStop(e, rootMenuData);
return;
case 27: // esc
e.data.manager.handler.keyStop(e, rootMenuData);
if (rootMenuData.$menu !== null && typeof rootMenuData.$menu !== 'undefined') {
rootMenuData.$menu.trigger('contextmenu:hide', {data: rootMenuData, originalEvent: e});
}
return;
default: // 0-9, a-z
const k = (String.fromCharCode(e.keyCode)).toUpperCase();
if (rootMenuData.accesskeys && rootMenuData.accesskeys[k]) {
// according to the specs accesskeys must be invoked immediately
rootMenuData.accesskeys[k].$node.trigger(rootMenuData.accesskeys[k].$menu ? 'contextmenu:focus' : 'mouseup', {data: rootMenuData, originalEvent: e});
return;
}
break;
}
// pass event to selected item,
// stop propagation to avoid endless recursion
e.stopPropagation();
if (typeof rootMenuData.$selected !== 'undefined' && rootMenuData.$selected !== null) {
rootMenuData.$selected.trigger(e);
}
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
prevItem(e) {
e.stopPropagation();
let currentMenuData = $(this).data('contextMenu') || {};
const rootMenuData = $(this).data('contextMenuRoot') || {};
// obtain currently selected menu
if (currentMenuData.$selected) {
const $s = currentMenuData.$selected;
currentMenuData = currentMenuData.$selected.parent().data('contextMenu') || {};
currentMenuData.$selected = $s;
}
const $children = currentMenuData.$menu.children();
let $prev = !currentMenuData.$selected || !currentMenuData.$selected.prev().length ? $children.last() : currentMenuData.$selected.prev();
const $round = $prev;
// skip disabled or hidden elements
while ($prev.hasClass(rootMenuData.classNames.disabled) || $prev.hasClass(rootMenuData.classNames.notSelectable) || $prev.is(':hidden')) {
if ($prev.prev().length) {
$prev = $prev.prev();
} else {
$prev = $children.last();
}
if ($prev.is($round)) {
// break endless loop
return;
}
}
// leave current
if (currentMenuData.$selected) {
rootMenuData.manager.handler.itemMouseleave.call(currentMenuData.$selected.get(0), e);
}
// activate next
rootMenuData.manager.handler.itemMouseenter.call($prev.get(0), e);
// focus input
const $input = $prev.find('input, textarea, select');
if ($input.length) {
$input.focus();
}
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
nextItem(e) {
e.stopPropagation();
let currentMenuData = $(this).data('contextMenu') || {};
let rootMenuData = $(this).data('contextMenuRoot') || {};
// obtain currently selected menu
if (currentMenuData.$selected) {
const $s = currentMenuData.$selected;
currentMenuData = currentMenuData.$selected.parent().data('contextMenu') || {};
currentMenuData.$selected = $s;
}
const $children = currentMenuData.$menu.children();
let $next = !currentMenuData.$selected || !currentMenuData.$selected.next().length ? $children.first() : currentMenuData.$selected.next();
const $round = $next;
// skip disabled
while ($next.hasClass(rootMenuData.classNames.disabled) || $next.hasClass(rootMenuData.classNames.notSelectable) || $next.is(':hidden')) {
if ($next.next().length) {
$next = $next.next();
} else {
$next = $children.first();
}
if ($next.is($round)) {
// break endless loop
return;
}
}
// leave current
if (currentMenuData.$selected) {
rootMenuData.manager.handler.itemMouseleave.call(currentMenuData.$selected.get(0), e);
}
// activate next
rootMenuData.manager.handler.itemMouseenter.call($next.get(0), e);
// focus input
const $input = $next.find('input, textarea, select');
if ($input.length) {
$input.focus();
}
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
focusInput(e) {
let $this = $(this).closest('.context-menu-item');
let data = $this.data();
let currentMenuData = data.contextMenu;
let rootMenuData = data.contextMenuRoot;
rootMenuData.$selected = currentMenuData.$selected = $this;
rootMenuData.isInput = currentMenuData.isInput = true;
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
blurInput(e) {
let $this = $(this).closest('.context-menu-item');
let data = $this.data();
let currentMenuData = data.contextMenu;
let rootMenuData = data.contextMenuRoot;
rootMenuData.isInput = currentMenuData.isInput = false;
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
menuMouseenter(e) {
let root = $(this).data().contextMenuRoot;
root.hovering = true;
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
menuMouseleave(e) {
let root = $(this).data().contextMenuRoot;
if (root.$layer && root.$layer.is(e.relatedTarget)) {
root.hovering = false;
}
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
itemMouseenter(e) {
let $this = $(this);
let data = $this.data();
let currentMenuData = data.contextMenu;
let rootMenuData = data.contextMenuRoot;
rootMenuData.hovering = true;
// abort if we're re-entering
if (e && rootMenuData.$layer && rootMenuData.$layer.is(e.relatedTarget)) {
e.preventDefault();
e.stopImmediatePropagation();
}
// make sure only one item is selected
let targetMenu = (currentMenuData.$menu ? currentMenuData : rootMenuData);
targetMenu.$menu
.children('.' + rootMenuData.classNames.hover).trigger('contextmenu:blur', {data: targetMenu, originalEvent: e})
.children('.hover').trigger('contextmenu:blur', {data: targetMenu, originalEvent: e});
if ($this.hasClass(rootMenuData.classNames.disabled) || $this.hasClass(rootMenuData.classNames.notSelectable)) {
currentMenuData.$selected = null;
return;
}
$this.trigger('contextmenu:focus', {data: currentMenuData, originalEvent: e});
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
itemMouseleave(e) {
let $this = $(this);
let data = $this.data();
let currentMenuData = data.contextMenu;
let rootMenuData = data.contextMenuRoot;
if (rootMenuData !== currentMenuData && rootMenuData.$layer && rootMenuData.$layer.is(e.relatedTarget)) {
if (typeof rootMenuData.$selected !== 'undefined' && rootMenuData.$selected !== null) {
rootMenuData.$selected.trigger('contextmenu:blur', {data: rootMenuData, originalEvent: e});
}
e.preventDefault();
e.stopImmediatePropagation();
rootMenuData.$selected = currentMenuData.$selected = currentMenuData.$node;
return;
}
if (currentMenuData && currentMenuData.$menu && currentMenuData.$menu.hasClass(rootMenuData.classNames.visible)) {
return;
}
$this.trigger('contextmenu:blur');
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
itemClick(e) {
let $this = $(this);
let data = $this.data();
let currentMenuData = data.contextMenu;
let rootMenuData = data.contextMenuRoot;
let key = data.contextMenuKey;
let callback;
// abort if the key is unknown or disabled or is a menu
if (!currentMenuData.items[key] || $this.is('.' + rootMenuData.classNames.disabled + ', .context-menu-separator, .' + rootMenuData.classNames.notSelectable) || ($this.is('.context-menu-submenu') && rootMenuData.selectableSubMenu === false)) {
return;
}
e.preventDefault();
e.stopImmediatePropagation();
if ($.isFunction(currentMenuData.callbacks[key]) && Object.prototype.hasOwnProperty.call(currentMenuData.callbacks, key)) {
// item-specific callback
callback = currentMenuData.callbacks[key];
} else if ($.isFunction(rootMenuData.callback)) {
// default callback
callback = rootMenuData.callback;
} else {
// no callback, no action
return;
}
// hide menu if callback doesn't stop that
if (callback.call(rootMenuData.$trigger, e, key, currentMenuData, rootMenuData) !== false) {
rootMenuData.$menu.trigger('contextmenu:hide');
} else if (rootMenuData.$menu.parent().length) {
rootMenuData.manager.operations.update.call(rootMenuData.$trigger, e, rootMenuData);
}
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
inputClick(e) {
e.stopImmediatePropagation();
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
* @param {Object} data
*/
hideMenu(e, data) {
console.log(e);
console.log(e.originalEvent);
const root = $(this).data('contextMenuRoot');
root.manager.operations.hide.call(root.$trigger, e, root, data && data.force);
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
focusItem(e) {
e.stopPropagation();
const $this = $(this);
const data = $this.data();
const currentMenuData = data.contextMenu;
const rootMenuData = data.contextMenuRoot;
if ($this.hasClass(rootMenuData.classNames.disabled) || $this.hasClass(rootMenuData.classNames.notSelectable)) {
return;
}
$this
.addClass([rootMenuData.classNames.hover, rootMenuData.classNames.visible].join(' '))
// select other items and included items
.parent().find('.context-menu-item').not($this)
.removeClass(rootMenuData.classNames.visible)
.filter('.' + rootMenuData.classNames.hover)
.trigger('contextmenu:blur');
// remember selected
currentMenuData.$selected = rootMenuData.$selected = $this;
if (currentMenuData.$node && currentMenuData.$node.hasClass('context-menu-submenu')) {
currentMenuData.$node.addClass(rootMenuData.classNames.hover);
}
// position sub-menu - do after show so dumb $.ui.position can keep up
if (currentMenuData.$node) {
rootMenuData.positionSubmenu.call(currentMenuData.$node, e, currentMenuData.$menu);
}
}
/**
* @method
* @memberOf ContextMenuEventHandler
* @instance
*
* @param {ContextMenuEvent|JQuery.Event} e
*/
blurItem(e) {
e.stopPropagation();
const $this = $(this);
const data = $this.data();
const currentMenuData = data.contextMenu;
const rootMenuData = data.contextMenuRoot;
if (rootMenuData.autoHide) { // for tablets and touch screens this needs to remain
$this.removeClass(rootMenuData.classNames.visible);
}
$this.removeClass(rootMenuData.classNames.hover);
currentMenuData.$selected = null;
}
};