2 * The default mapping of unique id to shortcut key.
3 * @type {Object<string, string>}
11 "favorites_view": "5",
33 function reverseMap(map) {
35 for (const [key, value] of Object.entries(map)) {
36 reversed[value] = key;
42 * @extends {Component}
47 this.container = this.$el;
48 this.mapById = defaultMap;
49 this.mapByShortcut = reverseMap(this.mapById);
51 this.hintsShowing = false;
53 this.hideHints = this.hideHints.bind(this);
54 // TODO - Allow custom key maps
55 // TODO - Allow turning off shortcuts
57 this.setupListeners();
61 window.addEventListener('keydown', event => {
63 if (event.target.closest('input, select, textarea')) {
67 const shortcutId = this.mapByShortcut[event.key];
69 const wasHandled = this.runShortcut(shortcutId);
71 event.preventDefault();
76 window.addEventListener('keydown', event => {
77 if (event.key === '?') {
78 this.hintsShowing ? this.hideHints() : this.showHints();
84 * Run the given shortcut, and return a boolean to indicate if the event
85 * was successfully handled by a shortcut action.
90 const el = this.container.querySelector(`[data-shortcut="${id}"]`);
91 console.info('Shortcut run', el);
96 if (el.matches('input, textarea, select')) {
101 if (el.matches('a, button')) {
106 if (el.matches('div[tabindex]')) {
112 console.error(`Shortcut attempted to be ran for element type that does not have handling setup`, el);
118 const wrapper = document.createElement('div');
119 wrapper.classList.add('shortcut-container');
120 this.container.append(wrapper);
122 const shortcutEls = this.container.querySelectorAll('[data-shortcut]');
123 const displayedIds = new Set();
124 for (const shortcutEl of shortcutEls) {
125 const id = shortcutEl.getAttribute('data-shortcut');
126 if (displayedIds.has(id)) {
130 const key = this.mapById[id];
131 this.showHintLabel(shortcutEl, key, wrapper);
132 displayedIds.add(id);
135 window.addEventListener('scroll', this.hideHints);
136 window.addEventListener('focus', this.hideHints);
137 window.addEventListener('blur', this.hideHints);
138 window.addEventListener('click', this.hideHints);
140 this.hintsShowing = true;
144 * @param {Element} targetEl
145 * @param {String} key
146 * @param {Element} wrapper
148 showHintLabel(targetEl, key, wrapper) {
149 const targetBounds = targetEl.getBoundingClientRect();
151 const label = document.createElement('div');
152 label.classList.add('shortcut-hint');
153 label.textContent = key;
155 const linkage = document.createElement('div');
156 linkage.classList.add('shortcut-linkage');
157 linkage.style.left = targetBounds.x + 'px';
158 linkage.style.top = targetBounds.y + 'px';
159 linkage.style.width = targetBounds.width + 'px';
160 linkage.style.height = targetBounds.height + 'px';
162 wrapper.append(label, linkage);
164 const labelBounds = label.getBoundingClientRect();
166 label.style.insetInlineStart = `${((targetBounds.x + targetBounds.width) - (labelBounds.width + 6))}px`;
167 label.style.insetBlockStart = `${(targetBounds.y + (targetBounds.height - labelBounds.height) / 2)}px`;
171 const wrapper = this.container.querySelector('.shortcut-container');
174 window.removeEventListener('scroll', this.hideHints);
175 window.removeEventListener('focus', this.hideHints);
176 window.removeEventListener('blur', this.hideHints);
177 window.removeEventListener('click', this.hideHints);
179 this.hintsShowing = false;
183 export default Shortcuts;