mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -04:00
Merge pull request #1183 from hgiesel/keykey
Switch to event.key for keyboard sequences
This commit is contained in:
commit
a361313622
10 changed files with 134 additions and 117 deletions
|
@ -3,13 +3,10 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="typescript">
|
<script lang="typescript">
|
||||||
import type { Modifier } from "lib/shortcuts";
|
|
||||||
|
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from "svelte";
|
||||||
import { registerShortcut, getPlatformString } from "lib/shortcuts";
|
import { registerShortcut, getPlatformString } from "lib/shortcuts";
|
||||||
|
|
||||||
export let shortcut: string;
|
export let shortcut: string;
|
||||||
export let optionalModifiers: Modifier[] | undefined = [];
|
|
||||||
|
|
||||||
const shortcutLabel = getPlatformString(shortcut);
|
const shortcutLabel = getPlatformString(shortcut);
|
||||||
|
|
||||||
|
@ -17,14 +14,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
function createShortcut({ detail }: CustomEvent): void {
|
function createShortcut({ detail }: CustomEvent): void {
|
||||||
const mounted: HTMLButtonElement = detail.button;
|
const mounted: HTMLButtonElement = detail.button;
|
||||||
deregister = registerShortcut(
|
deregister = registerShortcut((event: KeyboardEvent) => {
|
||||||
(event: KeyboardEvent) => {
|
mounted.dispatchEvent(new MouseEvent("click", event));
|
||||||
mounted.dispatchEvent(new MouseEvent("click", event));
|
event.preventDefault();
|
||||||
event.preventDefault();
|
}, shortcut);
|
||||||
},
|
|
||||||
shortcut,
|
|
||||||
optionalModifiers
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => deregister());
|
onDestroy(() => deregister());
|
||||||
|
|
|
@ -51,5 +51,5 @@ code {
|
||||||
|
|
||||||
// override the default down arrow colour in <select> elements
|
// override the default down arrow colour in <select> elements
|
||||||
.night-mode select {
|
.night-mode select {
|
||||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23FFFFFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e")
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23FFFFFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,11 +41,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<WithShortcut
|
<WithShortcut shortcut={'Control+Alt?+Shift+C'} let:createShortcut let:shortcutLabel>
|
||||||
shortcut="Control+Shift+KeyC"
|
|
||||||
optionalModifiers={['Alt']}
|
|
||||||
let:createShortcut
|
|
||||||
let:shortcutLabel>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={`${tr.editingClozeDeletion()} (${shortcutLabel})`}
|
tooltip={`${tr.editingClozeDeletion()} (${shortcutLabel})`}
|
||||||
on:click={onCloze}
|
on:click={onCloze}
|
||||||
|
|
|
@ -36,7 +36,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<ButtonGroup {api}>
|
<ButtonGroup {api}>
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut shortcut="F7" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'F7'} let:createShortcut let:shortcutLabel>
|
||||||
<IconButton
|
<IconButton
|
||||||
class="forecolor"
|
class="forecolor"
|
||||||
tooltip={appendInParentheses(tr.editingSetForegroundColor(), shortcutLabel)}
|
tooltip={appendInParentheses(tr.editingSetForegroundColor(), shortcutLabel)}
|
||||||
|
@ -48,7 +48,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut shortcut="F8" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'F8'} let:createShortcut let:shortcutLabel>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
tooltip={appendInParentheses(tr.editingChangeColor(), shortcutLabel)}
|
tooltip={appendInParentheses(tr.editingChangeColor(), shortcutLabel)}
|
||||||
on:change={setWithCurrentColor}
|
on:change={setWithCurrentColor}
|
||||||
|
|
|
@ -26,7 +26,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<ButtonGroup {api}>
|
<ButtonGroup {api}>
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut shortcut="Control+KeyB" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'Control+B'} let:createShortcut let:shortcutLabel>
|
||||||
<WithState
|
<WithState
|
||||||
key="bold"
|
key="bold"
|
||||||
update={() => document.queryCommandState('bold')}
|
update={() => document.queryCommandState('bold')}
|
||||||
|
@ -47,7 +47,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut shortcut="Control+KeyI" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'Control+I'} let:createShortcut let:shortcutLabel>
|
||||||
<WithState
|
<WithState
|
||||||
key="italic"
|
key="italic"
|
||||||
update={() => document.queryCommandState('italic')}
|
update={() => document.queryCommandState('italic')}
|
||||||
|
@ -68,7 +68,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut shortcut="Control+KeyU" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'Control+U'} let:createShortcut let:shortcutLabel>
|
||||||
<WithState
|
<WithState
|
||||||
key="underline"
|
key="underline"
|
||||||
update={() => document.queryCommandState('underline')}
|
update={() => document.queryCommandState('underline')}
|
||||||
|
@ -89,10 +89,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut
|
<WithShortcut shortcut={'Control+='} let:createShortcut let:shortcutLabel>
|
||||||
shortcut="Control+Shift+Equal"
|
|
||||||
let:createShortcut
|
|
||||||
let:shortcutLabel>
|
|
||||||
<WithState
|
<WithState
|
||||||
key="superscript"
|
key="superscript"
|
||||||
update={() => document.queryCommandState('superscript')}
|
update={() => document.queryCommandState('superscript')}
|
||||||
|
@ -113,7 +110,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut shortcut="Control+Equal" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'Control+Shift+='} let:createShortcut let:shortcutLabel>
|
||||||
<WithState
|
<WithState
|
||||||
key="subscript"
|
key="subscript"
|
||||||
update={() => document.queryCommandState('subscript')}
|
update={() => document.queryCommandState('subscript')}
|
||||||
|
@ -134,7 +131,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut shortcut="Control+KeyR" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'Control+R'} let:createShortcut let:shortcutLabel>
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={appendInParentheses(tr.editingRemoveFormatting(), shortcutLabel)}
|
tooltip={appendInParentheses(tr.editingRemoveFormatting(), shortcutLabel)}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
|
|
@ -25,7 +25,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut shortcut="Control+KeyL" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'Control+L'} let:createShortcut let:shortcutLabel>
|
||||||
<LabelButton
|
<LabelButton
|
||||||
disables={false}
|
disables={false}
|
||||||
tooltip={`${tr.editingCustomizeCardTemplates()} (${shortcutLabel})`}
|
tooltip={`${tr.editingCustomizeCardTemplates()} (${shortcutLabel})`}
|
||||||
|
|
|
@ -10,7 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import LabelButton from "components/LabelButton.svelte";
|
import LabelButton from "components/LabelButton.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<WithShortcut shortcut="Control+Shift+KeyP" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'Control+Shift+P'} let:createShortcut let:shortcutLabel>
|
||||||
<LabelButton
|
<LabelButton
|
||||||
tooltip={tr.browsingPreviewSelectedCard({ val: shortcutLabel })}
|
tooltip={tr.browsingPreviewSelectedCard({ val: shortcutLabel })}
|
||||||
disables={false}
|
disables={false}
|
||||||
|
|
|
@ -36,7 +36,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<ButtonGroup {api}>
|
<ButtonGroup {api}>
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut shortcut="F3" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'F3'} let:createShortcut let:shortcutLabel>
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={appendInParentheses(tr.editingAttachPicturesaudiovideo(), shortcutLabel)}
|
tooltip={appendInParentheses(tr.editingAttachPicturesaudiovideo(), shortcutLabel)}
|
||||||
on:click={onAttachment}
|
on:click={onAttachment}
|
||||||
|
@ -47,7 +47,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut shortcut="F5" let:createShortcut let:shortcutLabel>
|
<WithShortcut shortcut={'F5'} let:createShortcut let:shortcutLabel>
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={appendInParentheses(tr.editingRecordAudio(), shortcutLabel)}
|
tooltip={appendInParentheses(tr.editingRecordAudio(), shortcutLabel)}
|
||||||
on:click={onRecord}
|
on:click={onRecord}
|
||||||
|
@ -69,7 +69,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<DropdownMenu id={menuId}>
|
<DropdownMenu id={menuId}>
|
||||||
<WithShortcut
|
<WithShortcut
|
||||||
shortcut="Control+KeyM, KeyM"
|
shortcut={'Control+M, M'}
|
||||||
let:createShortcut
|
let:createShortcut
|
||||||
let:shortcutLabel>
|
let:shortcutLabel>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
@ -81,7 +81,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</WithShortcut>
|
</WithShortcut>
|
||||||
|
|
||||||
<WithShortcut
|
<WithShortcut
|
||||||
shortcut="Control+KeyM, KeyE"
|
shortcut={'Control+M, E'}
|
||||||
let:createShortcut
|
let:createShortcut
|
||||||
let:shortcutLabel>
|
let:shortcutLabel>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
@ -93,7 +93,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</WithShortcut>
|
</WithShortcut>
|
||||||
|
|
||||||
<WithShortcut
|
<WithShortcut
|
||||||
shortcut="Control+KeyM, KeyC"
|
shortcut={'Control+M, C'}
|
||||||
let:createShortcut
|
let:createShortcut
|
||||||
let:shortcutLabel>
|
let:shortcutLabel>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
@ -105,7 +105,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</WithShortcut>
|
</WithShortcut>
|
||||||
|
|
||||||
<WithShortcut
|
<WithShortcut
|
||||||
shortcut="Control+KeyT, KeyT"
|
shortcut={'Control+T, T'}
|
||||||
let:createShortcut
|
let:createShortcut
|
||||||
let:shortcutLabel>
|
let:shortcutLabel>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
@ -117,7 +117,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</WithShortcut>
|
</WithShortcut>
|
||||||
|
|
||||||
<WithShortcut
|
<WithShortcut
|
||||||
shortcut="Control+KeyT, KeyE"
|
shortcut={'Control+T, E'}
|
||||||
let:createShortcut
|
let:createShortcut
|
||||||
let:shortcutLabel>
|
let:shortcutLabel>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
@ -129,7 +129,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</WithShortcut>
|
</WithShortcut>
|
||||||
|
|
||||||
<WithShortcut
|
<WithShortcut
|
||||||
shortcut="Control+KeyT, KeyM"
|
shortcut={'Control+T, M'}
|
||||||
let:createShortcut
|
let:createShortcut
|
||||||
let:shortcutLabel>
|
let:shortcutLabel>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
@ -144,10 +144,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</ButtonGroupItem>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
<ButtonGroupItem>
|
<ButtonGroupItem>
|
||||||
<WithShortcut
|
<WithShortcut shortcut={'Control+Shift+X'} let:createShortcut let:shortcutLabel>
|
||||||
shortcut="Control+Shift+KeyX"
|
|
||||||
let:createShortcut
|
|
||||||
let:shortcutLabel>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={appendInParentheses(tr.editingHtmlEditor(), shortcutLabel)}
|
tooltip={appendInParentheses(tr.editingHtmlEditor(), shortcutLabel)}
|
||||||
on:click={onHtmlEdit}
|
on:click={onHtmlEdit}
|
||||||
|
|
|
@ -66,8 +66,7 @@ function updateFocus(evt: FocusEvent) {
|
||||||
|
|
||||||
registerShortcut(
|
registerShortcut(
|
||||||
() => document.addEventListener("focusin", updateFocus, { once: true }),
|
() => document.addEventListener("focusin", updateFocus, { once: true }),
|
||||||
"Tab",
|
"Shift?+Tab"
|
||||||
["Shift"]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export function onKeyUp(evt: KeyboardEvent): void {
|
export function onKeyUp(evt: KeyboardEvent): void {
|
||||||
|
|
|
@ -4,8 +4,6 @@ import * as tr from "./i18n";
|
||||||
|
|
||||||
export type Modifier = "Control" | "Alt" | "Shift" | "Meta";
|
export type Modifier = "Control" | "Alt" | "Shift" | "Meta";
|
||||||
|
|
||||||
const modifiers: Modifier[] = ["Control", "Alt", "Shift", "Meta"];
|
|
||||||
|
|
||||||
function isApplePlatform(): boolean {
|
function isApplePlatform(): boolean {
|
||||||
return (
|
return (
|
||||||
window.navigator.platform.startsWith("Mac") ||
|
window.navigator.platform.startsWith("Mac") ||
|
||||||
|
@ -18,10 +16,6 @@ const platformModifiers = isApplePlatform()
|
||||||
? ["Meta", "Alt", "Shift", "Control"]
|
? ["Meta", "Alt", "Shift", "Control"]
|
||||||
: ["Control", "Alt", "Shift", "OS"];
|
: ["Control", "Alt", "Shift", "OS"];
|
||||||
|
|
||||||
function splitKeyCombinationString(keyCombinationString: string): string[][] {
|
|
||||||
return keyCombinationString.split(", ").map((segment) => segment.split("+"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function modifiersToPlatformString(modifiers: string[]): string {
|
function modifiersToPlatformString(modifiers: string[]): string {
|
||||||
const displayModifiers = isApplePlatform()
|
const displayModifiers = isApplePlatform()
|
||||||
? ["^", "⌥", "⇧", "⌘"]
|
? ["^", "⌥", "⇧", "⌘"]
|
||||||
|
@ -36,38 +30,50 @@ function modifiersToPlatformString(modifiers: string[]): string {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const alphabeticPrefix = "Key";
|
const keyCodeLookup = {
|
||||||
const numericPrefix = "Digit";
|
Backspace: 8,
|
||||||
const keyToCharacterMap = {
|
Delete: 46,
|
||||||
Backslash: "\\",
|
Tab: 9,
|
||||||
Backquote: "`",
|
Enter: 13,
|
||||||
BracketLeft: "[",
|
F1: 112,
|
||||||
BrackerRight: "]",
|
F2: 113,
|
||||||
Quote: "'",
|
F3: 114,
|
||||||
Semicolon: ";",
|
F4: 115,
|
||||||
Minus: "-",
|
F5: 116,
|
||||||
Equal: "=",
|
F6: 117,
|
||||||
Comma: ",",
|
F7: 118,
|
||||||
Period: ".",
|
F8: 119,
|
||||||
Slash: "/",
|
F9: 120,
|
||||||
|
F10: 121,
|
||||||
|
F11: 122,
|
||||||
|
F12: 123,
|
||||||
|
"=": 187,
|
||||||
|
"-": 189,
|
||||||
|
"[": 219,
|
||||||
|
"]": 221,
|
||||||
|
"\\": 220,
|
||||||
|
";": 186,
|
||||||
|
"'": 222,
|
||||||
|
",": 188,
|
||||||
|
".": 190,
|
||||||
|
"/": 191,
|
||||||
|
"`": 192,
|
||||||
};
|
};
|
||||||
|
|
||||||
function keyToPlatformString(key: string): string {
|
function isRequiredModifier(modifier: string): boolean {
|
||||||
if (key.startsWith(alphabeticPrefix)) {
|
return !modifier.endsWith("?");
|
||||||
return key.slice(alphabeticPrefix.length);
|
|
||||||
} else if (key.startsWith(numericPrefix)) {
|
|
||||||
return key.slice(numericPrefix.length);
|
|
||||||
} else if (Object.prototype.hasOwnProperty.call(keyToCharacterMap, key)) {
|
|
||||||
return keyToCharacterMap[key];
|
|
||||||
} else {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toPlatformString(modifiersAndKey: string[]): string {
|
function splitKeyCombinationString(keyCombinationString: string): string[][] {
|
||||||
return `${modifiersToPlatformString(
|
return keyCombinationString.split(", ").map((segment) => segment.split("+"));
|
||||||
modifiersAndKey.slice(0, -1)
|
}
|
||||||
)}${keyToPlatformString(modifiersAndKey[modifiersAndKey.length - 1])}`;
|
|
||||||
|
function toPlatformString(keyCombination: string[]): string {
|
||||||
|
return (
|
||||||
|
modifiersToPlatformString(
|
||||||
|
keyCombination.slice(0, -1).filter(isRequiredModifier)
|
||||||
|
) + keyCombination[keyCombination.length - 1]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPlatformString(keyCombinationString: string): string {
|
export function getPlatformString(keyCombinationString: string): string {
|
||||||
|
@ -76,78 +82,107 @@ export function getPlatformString(keyCombinationString: string): string {
|
||||||
.join(", ");
|
.join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkKey(event: KeyboardEvent, key: string): boolean {
|
function checkKey(event: KeyboardEvent, key: number): boolean {
|
||||||
return event.code === key;
|
return event.which === key;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkModifiers(
|
const allModifiers: Modifier[] = ["Control", "Alt", "Shift", "Meta"];
|
||||||
event: KeyboardEvent,
|
|
||||||
optionalModifiers: Modifier[],
|
function partition<T>(predicate: (t: T) => boolean, items: T[]): [T[], T[]] {
|
||||||
activeModifiers: string[]
|
const trueItems: T[] = [];
|
||||||
): boolean {
|
const falseItems: T[] = [];
|
||||||
return modifiers.reduce(
|
|
||||||
(matches: boolean, modifier: string, currentIndex: number): boolean =>
|
items.forEach((t) => {
|
||||||
|
const target = predicate(t) ? trueItems : falseItems;
|
||||||
|
target.push(t);
|
||||||
|
});
|
||||||
|
|
||||||
|
return [trueItems, falseItems];
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeTrailing(modifier: string): string {
|
||||||
|
return modifier.substring(0, modifier.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkModifiers(event: KeyboardEvent, modifiers: string[]): boolean {
|
||||||
|
const [requiredModifiers, otherModifiers] = partition(
|
||||||
|
isRequiredModifier,
|
||||||
|
modifiers
|
||||||
|
);
|
||||||
|
|
||||||
|
const optionalModifiers = otherModifiers.map(removeTrailing);
|
||||||
|
|
||||||
|
return allModifiers.reduce(
|
||||||
|
(matches: boolean, currentModifier: string, currentIndex: number): boolean =>
|
||||||
matches &&
|
matches &&
|
||||||
(optionalModifiers.includes(modifier as Modifier) ||
|
(optionalModifiers.includes(currentModifier as Modifier) ||
|
||||||
event.getModifierState(platformModifiers[currentIndex]) ===
|
event.getModifierState(platformModifiers[currentIndex]) ===
|
||||||
activeModifiers.includes(modifier)),
|
requiredModifiers.includes(currentModifier)),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function check(
|
const check = (keyCode: number, modifiers: string[]) => (
|
||||||
event: KeyboardEvent,
|
event: KeyboardEvent
|
||||||
optionalModifiers: Modifier[],
|
): boolean => {
|
||||||
modifiersAndKey: string[]
|
return checkKey(event, keyCode) && checkModifiers(event, modifiers);
|
||||||
): boolean {
|
};
|
||||||
return (
|
|
||||||
checkKey(event, modifiersAndKey[modifiersAndKey.length - 1]) &&
|
function keyToCode(key: string): number {
|
||||||
checkModifiers(event, optionalModifiers, modifiersAndKey.slice(0, -1))
|
return keyCodeLookup[key] || key.toUpperCase().charCodeAt(0);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const shortcutTimeoutMs = 400;
|
function keyCombinationToCheck(
|
||||||
|
keyCombination: string[]
|
||||||
|
): (event: KeyboardEvent) => boolean {
|
||||||
|
const keyCode = keyToCode(keyCombination[keyCombination.length - 1]);
|
||||||
|
const modifiers = keyCombination.slice(0, -1);
|
||||||
|
|
||||||
|
return check(keyCode, modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
const GENERAL_KEY = 0;
|
||||||
|
const NUMPAD_KEY = 3;
|
||||||
|
|
||||||
function innerShortcut(
|
function innerShortcut(
|
||||||
lastEvent: KeyboardEvent,
|
lastEvent: KeyboardEvent,
|
||||||
callback: (event: KeyboardEvent) => void,
|
callback: (event: KeyboardEvent) => void,
|
||||||
optionalModifiers: Modifier[],
|
...checks: ((event: KeyboardEvent) => boolean)[]
|
||||||
...keyCombination: string[][]
|
|
||||||
): void {
|
): void {
|
||||||
let interval: number;
|
let interval: number;
|
||||||
|
|
||||||
if (keyCombination.length === 0) {
|
if (checks.length === 0) {
|
||||||
callback(lastEvent);
|
callback(lastEvent);
|
||||||
} else {
|
} else {
|
||||||
const [nextKey, ...restKeys] = keyCombination;
|
const [nextCheck, ...restChecks] = checks;
|
||||||
|
|
||||||
const handler = (event: KeyboardEvent): void => {
|
const handler = (event: KeyboardEvent): void => {
|
||||||
if (check(event, optionalModifiers, nextKey)) {
|
if (nextCheck(event)) {
|
||||||
innerShortcut(event, callback, optionalModifiers, ...restKeys);
|
innerShortcut(event, callback, ...restChecks);
|
||||||
clearTimeout(interval);
|
clearTimeout(interval);
|
||||||
|
} else if (
|
||||||
|
event.location === GENERAL_KEY ||
|
||||||
|
event.location === NUMPAD_KEY
|
||||||
|
) {
|
||||||
|
// Any non-modifier key will cancel the shortcut sequence
|
||||||
|
document.removeEventListener("keydown", handler);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
interval = setTimeout(
|
|
||||||
(): void => document.removeEventListener("keydown", handler),
|
|
||||||
shortcutTimeoutMs
|
|
||||||
);
|
|
||||||
|
|
||||||
document.addEventListener("keydown", handler, { once: true });
|
document.addEventListener("keydown", handler, { once: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerShortcut(
|
export function registerShortcut(
|
||||||
callback: (event: KeyboardEvent) => void,
|
callback: (event: KeyboardEvent) => void,
|
||||||
keyCombinationString: string,
|
keyCombinationString: string
|
||||||
optionalModifiers: Modifier[] = []
|
|
||||||
): () => void {
|
): () => void {
|
||||||
const keyCombination = splitKeyCombinationString(keyCombinationString);
|
const [check, ...restChecks] = splitKeyCombinationString(keyCombinationString).map(
|
||||||
const [firstKey, ...restKeys] = keyCombination;
|
keyCombinationToCheck
|
||||||
|
);
|
||||||
|
|
||||||
const handler = (event: KeyboardEvent): void => {
|
const handler = (event: KeyboardEvent): void => {
|
||||||
if (check(event, optionalModifiers, firstKey)) {
|
if (check(event)) {
|
||||||
innerShortcut(event, callback, optionalModifiers, ...restKeys);
|
innerShortcut(event, callback, ...restChecks);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue