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 shortcutEls = this.container.querySelectorAll('[data-shortcut]');
119 const displayedIds = new Set();
120 for (const shortcutEl of shortcutEls) {
121 const id = shortcutEl.getAttribute('data-shortcut');
122 if (displayedIds.has(id)) {
126 const key = this.mapById[id];
127 this.showHintLabel(shortcutEl, key);
128 displayedIds.add(id);
131 window.addEventListener('scroll', this.hideHints);
132 window.addEventListener('focus', this.hideHints);
133 window.addEventListener('blur', this.hideHints);
134 window.addEventListener('click', this.hideHints);
136 this.hintsShowing = true;
139 showHintLabel(targetEl, key) {
140 const targetBounds = targetEl.getBoundingClientRect();
141 const label = document.createElement('div');
142 label.classList.add('shortcut-hint');
143 label.textContent = key;
144 this.container.append(label);
146 const labelBounds = label.getBoundingClientRect();
148 label.style.insetInlineStart = `${((targetBounds.x + targetBounds.width) - (labelBounds.width + 12))}px`;
149 label.style.insetBlockStart = `${(targetBounds.y + (targetBounds.height - labelBounds.height) / 2)}px`;
153 const hints = this.container.querySelectorAll('.shortcut-hint');
154 for (const hint of hints) {
158 window.removeEventListener('scroll', this.hideHints);
159 window.removeEventListener('focus', this.hideHints);
160 window.removeEventListener('blur', this.hideHints);
161 window.removeEventListener('click', this.hideHints);
163 this.hintsShowing = false;
167 export default Shortcuts;