Skip to content

Commit 8d130b6

Browse files
committed
Indicate keyboard shortcuts by underlining the letter
1 parent bba4f18 commit 8d130b6

2 files changed

Lines changed: 48 additions & 2 deletions

File tree

frontend/src/lib/styles.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,13 @@
3333
.fm-custom-range-with-label {
3434
// Same padding-top as .col-form-label plus half line-height (1.5) minus half range input height (16px)
3535
padding-top: calc(0.375rem + var(--bs-border-width) + 0.75rem - 8px);
36+
}
37+
38+
kbd.fm-shortcut {
39+
background: none;
40+
color: inherit;
41+
font-size: inherit;
42+
font-family: inherit;
43+
padding: 0;
44+
text-decoration: underline;
3645
}

frontend/src/lib/utils/vue.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { cloneDeep, isEqual } from "lodash-es";
1+
import { cloneDeep, isEqual, sortBy } from "lodash-es";
22
import { type ComponentPublicInstance, type DeepReadonly, type Directive, type Ref, computed, onScopeDispose, readonly, ref, shallowReadonly, shallowRef, watch, type ComputedGetter, type Component, type VNodeProps, type AllowedComponentProps, onBeforeUnmount, onMounted, toRaw, type FunctionDirective, effectScope, toRef } from "vue";
33
import { shouldHandleGlobalShortcut, useDomEventListener, type AnyRef } from "./utils";
44

@@ -219,5 +219,42 @@ export function useKeyboardShortcut(el: Ref<HTMLElement | undefined>, key: AnyRe
219219
}
220220

221221
export const vKeyboardShortcut = vDirectiveWithScope<HTMLElement, string[] | string | undefined>((el, binding) => {
222-
useKeyboardShortcut(toRef(el), binding.value);
222+
const keys = Array.isArray(binding.value) ? binding.value : binding.value ? [binding.value] : [];
223+
useKeyboardShortcut(toRef(el), keys);
224+
225+
// If the shortcut is a letter key and the letter is contained in the element text, wrap it in a <kbd> element to indicate the shortcut
226+
if (!el.querySelector("kbd")) {
227+
const letterKeys = keys.flatMap((k) => k.length === 1 ? [k.toLowerCase()] : []);
228+
if (letterKeys.length > 0) {
229+
const nodeIterator = document.createNodeIterator(el, NodeFilter.SHOW_TEXT);
230+
let currentNode: Text | null;
231+
while (currentNode = nodeIterator.nextNode() as Text | null) {
232+
const node = currentNode;
233+
234+
const lowerText = node.data.toLowerCase();
235+
const positions = sortBy(letterKeys.map((k) => [k, lowerText.indexOf(k)] as const).filter(([k, i]) => i !== -1), ([k, i]) => i);
236+
if (positions.length > 0) {
237+
const text = node.data;
238+
239+
node.data = text.slice(positions[0][1] + 1);
240+
241+
const beforeNode = document.createTextNode(text.slice(0, positions[0][1]));
242+
node.parentNode!.insertBefore(beforeNode, node);
243+
244+
const kbd = document.createElement("kbd");
245+
kbd.classList.add("fm-shortcut");
246+
kbd.appendChild(document.createTextNode(text.slice(positions[0][1], positions[0][1] + 1)));
247+
node.parentNode!.insertBefore(kbd, node);
248+
249+
onScopeDispose(() => {
250+
beforeNode.remove();
251+
kbd.remove();
252+
node.data = text;
253+
});
254+
255+
break;
256+
}
257+
}
258+
}
259+
}
223260
});

0 commit comments

Comments
 (0)