mirror of https://github.com/rancher/dashboard.git
283 lines
7.3 KiB
JavaScript
283 lines
7.3 KiB
JavaScript
// I grabbed the source code of this plugin from https://github.com/rodrigopv/vue3-shortkey
|
|
//
|
|
// At the time of writing this the released plugin was emitting debug console messages and
|
|
// there was a PR open for about 1 year to address this https://github.com/rodrigopv/vue3-shortkey/pull/1
|
|
// If another library becomes available I think we should use it instead
|
|
import 'element-matches';
|
|
import 'custom-event-polyfill';
|
|
|
|
const ShortKey = {};
|
|
const mapFunctions = {};
|
|
let objAvoided = [];
|
|
let elementAvoided = [];
|
|
let containerAvoided = [];
|
|
let keyPressed = false;
|
|
|
|
const parseValue = (value) => {
|
|
value = typeof value === 'string' ? JSON.parse(value.replace(/\'/gi, '"')) : value;
|
|
if (value instanceof Array) {
|
|
return { '': value };
|
|
}
|
|
|
|
return value;
|
|
};
|
|
|
|
const bindValue = (value, el, binding, vnode) => {
|
|
const push = binding.modifiers.push === true;
|
|
const avoid = binding.modifiers.avoid === true;
|
|
const focus = !binding.modifiers.focus === true;
|
|
const once = binding.modifiers.once === true;
|
|
const propagte = binding.modifiers.propagte === true;
|
|
|
|
if (avoid) {
|
|
objAvoided = objAvoided.filter((itm) => {
|
|
return !itm === el;
|
|
});
|
|
objAvoided.push(el);
|
|
} else {
|
|
mappingFunctions({
|
|
b: value, push, once, focus, propagte, el: vnode.el
|
|
});
|
|
}
|
|
};
|
|
|
|
const unbindValue = (value, el) => {
|
|
for (const key in value) {
|
|
const k = ShortKey.encodeKey(value[key]);
|
|
const idxElm = mapFunctions[k].el.indexOf(el);
|
|
|
|
if (mapFunctions[k].el.length > 1 && idxElm > -1) {
|
|
mapFunctions[k].el.splice(idxElm, 1);
|
|
} else {
|
|
delete mapFunctions[k];
|
|
}
|
|
}
|
|
};
|
|
|
|
ShortKey.install = (Vue, options) => {
|
|
elementAvoided = [...(options && options.prevent ? options.prevent : [])];
|
|
containerAvoided = [...(options && options.preventContainer ? options.preventContainer : [])];
|
|
Vue.directive('shortkey', {
|
|
beforeMount: (el, binding, vnode) => {
|
|
// Mapping the commands
|
|
const value = parseValue(binding.value);
|
|
|
|
bindValue(value, el, binding, vnode);
|
|
},
|
|
updated: (el, binding, vnode) => {
|
|
const oldValue = parseValue(binding.oldValue);
|
|
|
|
unbindValue(oldValue, el);
|
|
|
|
const newValue = parseValue(binding.value);
|
|
|
|
bindValue(newValue, el, binding, vnode);
|
|
},
|
|
unmounted: (el, binding) => {
|
|
const value = parseValue(binding.value);
|
|
|
|
unbindValue(value, el);
|
|
}
|
|
});
|
|
};
|
|
|
|
ShortKey.decodeKey = (pKey) => createShortcutIndex(pKey);
|
|
ShortKey.encodeKey = (pKey) => {
|
|
const shortKey = {};
|
|
|
|
shortKey.shiftKey = pKey.includes('shift');
|
|
shortKey.ctrlKey = pKey.includes('ctrl');
|
|
shortKey.metaKey = pKey.includes('meta');
|
|
shortKey.altKey = pKey.includes('alt');
|
|
let indexedKeys = createShortcutIndex(shortKey);
|
|
const vKey = pKey.filter((item) => !['shift', 'ctrl', 'meta', 'alt'].includes(item));
|
|
|
|
indexedKeys += vKey.join('');
|
|
|
|
return indexedKeys;
|
|
};
|
|
|
|
const createShortcutIndex = (pKey) => {
|
|
let k = '';
|
|
|
|
if (pKey.key === 'Shift' || pKey.shiftKey) {
|
|
k += 'shift';
|
|
}
|
|
if (pKey.key === 'Control' || pKey.ctrlKey) {
|
|
k += 'ctrl';
|
|
}
|
|
if (pKey.key === 'Meta' || pKey.metaKey) {
|
|
k += 'meta';
|
|
}
|
|
if (pKey.key === 'Alt' || pKey.altKey) {
|
|
k += 'alt';
|
|
}
|
|
if (pKey.key === 'ArrowUp') {
|
|
k += 'arrowup';
|
|
}
|
|
if (pKey.key === 'ArrowLeft') {
|
|
k += 'arrowleft';
|
|
}
|
|
if (pKey.key === 'ArrowRight') {
|
|
k += 'arrowright';
|
|
}
|
|
if (pKey.key === 'ArrowDown') {
|
|
k += 'arrowdown';
|
|
}
|
|
if (pKey.key === 'AltGraph') {
|
|
k += 'altgraph';
|
|
}
|
|
if (pKey.key === 'Escape') {
|
|
k += 'esc';
|
|
}
|
|
if (pKey.key === 'Enter') {
|
|
k += 'enter';
|
|
}
|
|
if (pKey.key === 'Tab') {
|
|
k += 'tab';
|
|
}
|
|
if (pKey.key === ' ') {
|
|
k += 'space';
|
|
}
|
|
if (pKey.key === 'PageUp') {
|
|
k += 'pageup';
|
|
}
|
|
if (pKey.key === 'PageDown') {
|
|
k += 'pagedown';
|
|
}
|
|
if (pKey.key === 'Home') {
|
|
k += 'home';
|
|
}
|
|
if (pKey.key === 'End') {
|
|
k += 'end';
|
|
}
|
|
if (pKey.key === 'Delete') {
|
|
k += 'del';
|
|
}
|
|
if (pKey.key === 'Backspace') {
|
|
k += 'backspace';
|
|
}
|
|
if (pKey.key === 'Insert') {
|
|
k += 'insert';
|
|
}
|
|
if (pKey.key === 'NumLock') {
|
|
k += 'numlock';
|
|
}
|
|
if (pKey.key === 'CapsLock') {
|
|
k += 'capslock';
|
|
}
|
|
if (pKey.key === 'Pause') {
|
|
k += 'pause';
|
|
}
|
|
if (pKey.key === 'ContextMenu') {
|
|
k += 'contextmenu';
|
|
}
|
|
if (pKey.key === 'ScrollLock') {
|
|
k += 'scrolllock';
|
|
}
|
|
if (pKey.key === 'BrowserHome') {
|
|
k += 'browserhome';
|
|
}
|
|
if (pKey.key === 'MediaSelect') {
|
|
k += 'mediaselect';
|
|
}
|
|
if ((pKey.key && pKey.key !== ' ' && pKey.key.length === 1) || /F\d{1,2}|\//g.test(pKey.key)) k += pKey.key.toLowerCase();
|
|
|
|
return k;
|
|
};
|
|
|
|
const dispatchShortkeyEvent = (pKey) => {
|
|
const e = new CustomEvent('shortkey', { bubbles: false });
|
|
|
|
if (mapFunctions[pKey].key) e.srcKey = mapFunctions[pKey].key;
|
|
const elm = mapFunctions[pKey].el;
|
|
|
|
if (!mapFunctions[pKey].propagte) {
|
|
elm[elm.length - 1].dispatchEvent(e);
|
|
} else {
|
|
elm.forEach((elmItem) => elmItem.dispatchEvent(e));
|
|
}
|
|
};
|
|
|
|
ShortKey.keyDown = (pKey) => {
|
|
if ((!mapFunctions[pKey].once && !mapFunctions[pKey].push) || (mapFunctions[pKey].push && !keyPressed)) {
|
|
dispatchShortkeyEvent(pKey);
|
|
}
|
|
};
|
|
|
|
if (process && process.env && process.env.NODE_ENV !== 'test') {
|
|
(function() {
|
|
document.addEventListener('keydown', (pKey) => {
|
|
const decodedKey = ShortKey.decodeKey(pKey);
|
|
|
|
// Check avoidable elements
|
|
if (availableElement(decodedKey)) {
|
|
if (!mapFunctions[decodedKey].propagte) {
|
|
pKey.preventDefault();
|
|
pKey.stopPropagation();
|
|
}
|
|
if (mapFunctions[decodedKey].focus) {
|
|
ShortKey.keyDown(decodedKey);
|
|
keyPressed = true;
|
|
} else if (!keyPressed) {
|
|
const elm = mapFunctions[decodedKey].el;
|
|
|
|
elm[elm.length - 1].focus();
|
|
keyPressed = true;
|
|
}
|
|
}
|
|
}, true);
|
|
|
|
document.addEventListener('keyup', (pKey) => {
|
|
const decodedKey = ShortKey.decodeKey(pKey);
|
|
|
|
if (availableElement(decodedKey)) {
|
|
if (!mapFunctions[decodedKey].propagte) {
|
|
pKey.preventDefault();
|
|
pKey.stopPropagation();
|
|
}
|
|
if (mapFunctions[decodedKey].once || mapFunctions[decodedKey].push) {
|
|
dispatchShortkeyEvent(decodedKey);
|
|
}
|
|
}
|
|
keyPressed = false;
|
|
}, true);
|
|
})();
|
|
}
|
|
|
|
const mappingFunctions = ({
|
|
b, push, once, focus, propagte, el
|
|
}) => {
|
|
for (const key in b) {
|
|
const k = ShortKey.encodeKey(b[key]);
|
|
const elm = mapFunctions[k] && mapFunctions[k].el ? mapFunctions[k].el : [];
|
|
const propagated = mapFunctions[k] && mapFunctions[k].propagte;
|
|
|
|
elm.push(el);
|
|
mapFunctions[k] = {
|
|
push,
|
|
once,
|
|
focus,
|
|
key,
|
|
propagte: propagated || propagte,
|
|
el: elm
|
|
};
|
|
}
|
|
};
|
|
|
|
const availableElement = (decodedKey) => {
|
|
const objectIsAvoided = !!objAvoided.find((r) => r === document.activeElement);
|
|
const filterAvoided = !!(elementAvoided.find((selector) => document.activeElement && document.activeElement.matches(selector)));
|
|
const filterAvoidedContainer = !!(containerAvoided.find((selector) => isActiveElementChildOf(selector)));
|
|
|
|
return !!mapFunctions[decodedKey] && !(objectIsAvoided || filterAvoided) && !filterAvoidedContainer;
|
|
};
|
|
|
|
const isActiveElementChildOf = (container) => {
|
|
const activeElement = document.activeElement;
|
|
|
|
return activeElement && activeElement.closest(container) !== null;
|
|
};
|
|
|
|
export default ShortKey;
|