1 function reverseMap(map) {
3 for (const [key, value] of Object.entries(map)) {
10 * @extends {Component}
15 this.container = this.$el;
16 this.mapById = JSON.parse(this.$opts.keyMap);
17 this.mapByShortcut = reverseMap(this.mapById);
19 this.hintsShowing = false;
21 this.hideHints = this.hideHints.bind(this);
23 this.setupListeners();
27 window.addEventListener('keydown', event => {
29 if (event.target.closest('input, select, textarea')) {
33 const shortcutId = this.mapByShortcut[event.key];
35 const wasHandled = this.runShortcut(shortcutId);
37 event.preventDefault();
42 window.addEventListener('keydown', event => {
43 if (event.key === '?') {
44 this.hintsShowing ? this.hideHints() : this.showHints();
50 * Run the given shortcut, and return a boolean to indicate if the event
51 * was successfully handled by a shortcut action.
56 const el = this.container.querySelector(`[data-shortcut="${id}"]`);
57 console.info('Shortcut run', el);
62 if (el.matches('input, textarea, select')) {
67 if (el.matches('a, button')) {
72 if (el.matches('div[tabindex]')) {
78 console.error(`Shortcut attempted to be ran for element type that does not have handling setup`, el);
84 const wrapper = document.createElement('div');
85 wrapper.classList.add('shortcut-container');
86 this.container.append(wrapper);
88 const shortcutEls = this.container.querySelectorAll('[data-shortcut]');
89 const displayedIds = new Set();
90 for (const shortcutEl of shortcutEls) {
91 const id = shortcutEl.getAttribute('data-shortcut');
92 if (displayedIds.has(id)) {
96 const key = this.mapById[id];
97 this.showHintLabel(shortcutEl, key, wrapper);
101 window.addEventListener('scroll', this.hideHints);
102 window.addEventListener('focus', this.hideHints);
103 window.addEventListener('blur', this.hideHints);
104 window.addEventListener('click', this.hideHints);
106 this.hintsShowing = true;
110 * @param {Element} targetEl
111 * @param {String} key
112 * @param {Element} wrapper
114 showHintLabel(targetEl, key, wrapper) {
115 const targetBounds = targetEl.getBoundingClientRect();
117 const label = document.createElement('div');
118 label.classList.add('shortcut-hint');
119 label.textContent = key;
121 const linkage = document.createElement('div');
122 linkage.classList.add('shortcut-linkage');
123 linkage.style.left = targetBounds.x + 'px';
124 linkage.style.top = targetBounds.y + 'px';
125 linkage.style.width = targetBounds.width + 'px';
126 linkage.style.height = targetBounds.height + 'px';
128 wrapper.append(label, linkage);
130 const labelBounds = label.getBoundingClientRect();
132 label.style.insetInlineStart = `${((targetBounds.x + targetBounds.width) - (labelBounds.width + 6))}px`;
133 label.style.insetBlockStart = `${(targetBounds.y + (targetBounds.height - labelBounds.height) / 2)}px`;
137 const wrapper = this.container.querySelector('.shortcut-container');
140 window.removeEventListener('scroll', this.hideHints);
141 window.removeEventListener('focus', this.hideHints);
142 window.removeEventListener('blur', this.hideHints);
143 window.removeEventListener('click', this.hideHints);
145 this.hintsShowing = false;
149 export default Shortcuts;