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 this.handleShortcutPress(event);
36 window.addEventListener('keydown', event => {
37 if (event.key === '?') {
38 this.hintsShowing ? this.hideHints() : this.showHints();
44 * @param {KeyboardEvent} event
46 handleShortcutPress(event) {
49 event.ctrlKey ? 'Ctrl' : '',
50 event.metaKey ? 'Cmd' : '',
54 const combo = keys.filter(s => Boolean(s)).join(' + ');
56 const shortcutId = this.mapByShortcut[combo];
58 const wasHandled = this.runShortcut(shortcutId);
60 event.preventDefault();
66 * Run the given shortcut, and return a boolean to indicate if the event
67 * was successfully handled by a shortcut action.
72 const el = this.container.querySelector(`[data-shortcut="${id}"]`);
77 if (el.matches('input, textarea, select')) {
82 if (el.matches('a, button')) {
87 if (el.matches('div[tabindex]')) {
93 console.error(`Shortcut attempted to be ran for element type that does not have handling setup`, el);
99 const wrapper = document.createElement('div');
100 wrapper.classList.add('shortcut-container');
101 this.container.append(wrapper);
103 const shortcutEls = this.container.querySelectorAll('[data-shortcut]');
104 const displayedIds = new Set();
105 for (const shortcutEl of shortcutEls) {
106 const id = shortcutEl.getAttribute('data-shortcut');
107 if (displayedIds.has(id)) {
111 const key = this.mapById[id];
112 this.showHintLabel(shortcutEl, key, wrapper);
113 displayedIds.add(id);
116 window.addEventListener('scroll', this.hideHints);
117 window.addEventListener('focus', this.hideHints);
118 window.addEventListener('blur', this.hideHints);
119 window.addEventListener('click', this.hideHints);
121 this.hintsShowing = true;
125 * @param {Element} targetEl
126 * @param {String} key
127 * @param {Element} wrapper
129 showHintLabel(targetEl, key, wrapper) {
130 const targetBounds = targetEl.getBoundingClientRect();
132 const label = document.createElement('div');
133 label.classList.add('shortcut-hint');
134 label.textContent = key;
136 const linkage = document.createElement('div');
137 linkage.classList.add('shortcut-linkage');
138 linkage.style.left = targetBounds.x + 'px';
139 linkage.style.top = targetBounds.y + 'px';
140 linkage.style.width = targetBounds.width + 'px';
141 linkage.style.height = targetBounds.height + 'px';
143 wrapper.append(label, linkage);
145 const labelBounds = label.getBoundingClientRect();
147 label.style.insetInlineStart = `${((targetBounds.x + targetBounds.width) - (labelBounds.width + 6))}px`;
148 label.style.insetBlockStart = `${(targetBounds.y + (targetBounds.height - labelBounds.height) / 2)}px`;
152 const wrapper = this.container.querySelector('.shortcut-container');
155 window.removeEventListener('scroll', this.hideHints);
156 window.removeEventListener('focus', this.hideHints);
157 window.removeEventListener('blur', this.hideHints);
158 window.removeEventListener('click', this.hideHints);
160 this.hintsShowing = false;
164 export default Shortcuts;