2018-09-17 22:35:06 +02:00
|
|
|
import keyCode from './keycode';
|
2018-09-18 05:34:41 +02:00
|
|
|
import { concat } from '../../../prelude/array';
|
2018-09-17 22:35:06 +02:00
|
|
|
|
2018-09-18 05:34:41 +02:00
|
|
|
type pattern = {
|
|
|
|
which: string[];
|
|
|
|
ctrl?: boolean;
|
|
|
|
shift?: boolean;
|
|
|
|
alt?: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
type action = {
|
|
|
|
patterns: pattern[];
|
|
|
|
|
|
|
|
callback: Function;
|
|
|
|
};
|
|
|
|
|
|
|
|
const getKeyMap = keymap => Object.entries(keymap).map(([patterns, callback]): action => {
|
|
|
|
const result = {
|
|
|
|
patterns: [],
|
|
|
|
callback: callback
|
|
|
|
} as action;
|
|
|
|
|
|
|
|
result.patterns = patterns.split('|').map(part => {
|
|
|
|
const pattern = {
|
2018-09-18 08:02:15 +02:00
|
|
|
which: [],
|
|
|
|
ctrl: false,
|
|
|
|
alt: false,
|
|
|
|
shift: false
|
2018-09-18 05:34:41 +02:00
|
|
|
} as pattern;
|
|
|
|
|
|
|
|
part.trim().split('+').forEach(key => {
|
|
|
|
key = key.trim().toLowerCase();
|
|
|
|
switch (key) {
|
|
|
|
case 'ctrl': pattern.ctrl = true; break;
|
|
|
|
case 'alt': pattern.alt = true; break;
|
|
|
|
case 'shift': pattern.shift = true; break;
|
|
|
|
default: pattern.which = keyCode(key).map(k => k.toLowerCase());
|
2018-09-18 02:11:52 +02:00
|
|
|
}
|
2018-09-18 05:34:41 +02:00
|
|
|
});
|
2018-09-17 22:35:06 +02:00
|
|
|
|
2018-09-18 05:34:41 +02:00
|
|
|
return pattern;
|
|
|
|
});
|
2018-09-17 22:35:06 +02:00
|
|
|
|
|
|
|
return result;
|
|
|
|
});
|
|
|
|
|
|
|
|
const ignoreElemens = ['input', 'textarea'];
|
|
|
|
|
2018-10-19 18:45:31 +02:00
|
|
|
function match(e: KeyboardEvent, patterns: action['patterns']): boolean {
|
|
|
|
const key = e.code.toLowerCase();
|
|
|
|
return patterns.some(pattern => pattern.which.includes(key) &&
|
|
|
|
pattern.ctrl == e.ctrlKey &&
|
|
|
|
pattern.shift == e.shiftKey &&
|
|
|
|
pattern.alt == e.altKey &&
|
|
|
|
e.metaKey == false
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-09-17 22:35:06 +02:00
|
|
|
export default {
|
|
|
|
install(Vue) {
|
|
|
|
Vue.directive('hotkey', {
|
|
|
|
bind(el, binding) {
|
|
|
|
el._hotkey_global = binding.modifiers.global === true;
|
|
|
|
|
2018-09-18 05:34:41 +02:00
|
|
|
const actions = getKeyMap(binding.value);
|
2018-09-17 22:35:06 +02:00
|
|
|
|
2018-09-18 05:42:56 +02:00
|
|
|
// flatten
|
2018-10-19 18:45:31 +02:00
|
|
|
const reservedKeys = concat(actions.map(a => a.patterns));
|
2018-09-18 05:34:41 +02:00
|
|
|
|
2018-10-19 18:45:31 +02:00
|
|
|
el.dataset.reservedKeys = JSON.stringify(reservedKeys);
|
2018-09-17 22:35:06 +02:00
|
|
|
|
2018-09-24 13:57:32 +02:00
|
|
|
el._keyHandler = (e: KeyboardEvent) => {
|
2018-10-19 18:45:31 +02:00
|
|
|
const targetReservedKeys = JSON.parse(document.activeElement ? ((document.activeElement as any).dataset || {}).reservedKeys || '[]' : '[]');
|
2018-09-17 22:35:06 +02:00
|
|
|
if (document.activeElement && ignoreElemens.some(el => document.activeElement.matches(el))) return;
|
|
|
|
|
2018-09-18 05:34:41 +02:00
|
|
|
for (const action of actions) {
|
2018-10-19 18:45:31 +02:00
|
|
|
const matched = match(e, action.patterns);
|
2018-09-17 22:35:06 +02:00
|
|
|
|
2018-09-18 05:34:41 +02:00
|
|
|
if (matched) {
|
2018-10-19 18:45:31 +02:00
|
|
|
if (el._hotkey_global) {
|
|
|
|
if (match(e, targetReservedKeys)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
action.callback(e);
|
2018-09-18 05:34:41 +02:00
|
|
|
break;
|
2018-09-17 22:35:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (el._hotkey_global) {
|
|
|
|
document.addEventListener('keydown', el._keyHandler);
|
|
|
|
} else {
|
|
|
|
el.addEventListener('keydown', el._keyHandler);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
unbind(el) {
|
|
|
|
if (el._hotkey_global) {
|
|
|
|
document.removeEventListener('keydown', el._keyHandler);
|
|
|
|
} else {
|
|
|
|
el.removeEventListener('keydown', el._keyHandler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|