7bcb57b89e
* resolve TagAddButton a11y better comments to document tagindex reasoning * resolved a11y for TagsSelectedButton allow focus to TagsSelectedButton with Shift+Tab and Enter or Space to show popover * safely ignore a11y warning as container for interactables is not itself interactable * Update CONTRIBUTORS * quick fix syntax * quick fix syntax * quick fix syntax * quick fix syntax * resolved a11y in accordance with ARIA APG Disclure pattern * resolved a11y ideally should replace with with a11y-click-events-have-key-events is explicitly ignored as the alternative (adding ) seems more clunky * resolved SpinBox a11y cannot focus on these buttons, so no key event handling needed (keyboard editting already possible by just typing in the field) widget already properly follows ARIA APG Spinbutton pattern * cleanup * onEnterOrSpace() function implemented as discussed in #2787 and #2564 * quick syntax and such changes
134 lines
3.9 KiB
TypeScript
134 lines
3.9 KiB
TypeScript
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
import * as tr from "@tslib/ftl";
|
|
|
|
import { isApplePlatform } from "./platform";
|
|
|
|
// those are the modifiers that Anki works with
|
|
export type Modifier = "Control" | "Alt" | "Shift" | "Meta";
|
|
const allModifiers: Modifier[] = ["Control", "Alt", "Shift", "Meta"];
|
|
|
|
const platformModifiers: string[] = isApplePlatform()
|
|
? ["Meta", "Alt", "Shift", "Control"]
|
|
: ["Control", "Alt", "Shift", "OS"];
|
|
|
|
function translateModifierToPlatform(modifier: Modifier): string {
|
|
return platformModifiers[allModifiers.indexOf(modifier)];
|
|
}
|
|
|
|
export function checkIfModifierKey(event: KeyboardEvent): boolean {
|
|
// At least the web view on Desktop Anki gives out the wrong values for
|
|
// `event.location`, which is why we do it like this.
|
|
let isInputKey = false;
|
|
|
|
for (const modifier of allModifiers) {
|
|
isInputKey ||= event.code.startsWith(modifier);
|
|
}
|
|
|
|
return isInputKey;
|
|
}
|
|
|
|
export function keyboardEventIsPrintableKey(event: KeyboardEvent): boolean {
|
|
return event.key.length === 1;
|
|
}
|
|
|
|
export const checkModifiers = (required: Modifier[], optional: Modifier[] = []) => (event: KeyboardEvent): boolean => {
|
|
return allModifiers.reduce(
|
|
(
|
|
matches: boolean,
|
|
currentModifier: Modifier,
|
|
currentIndex: number,
|
|
): boolean =>
|
|
matches
|
|
&& (optional.includes(currentModifier as Modifier)
|
|
|| event.getModifierState(platformModifiers[currentIndex])
|
|
=== required.includes(currentModifier)),
|
|
true,
|
|
);
|
|
};
|
|
|
|
const modifierPressed = (modifier: Modifier) => (event: MouseEvent | KeyboardEvent): boolean => {
|
|
const translated = translateModifierToPlatform(modifier);
|
|
const state = event.getModifierState(translated);
|
|
return event.type === "keyup"
|
|
? state && (event as KeyboardEvent).key !== translated
|
|
: state;
|
|
};
|
|
|
|
export const controlPressed = modifierPressed("Control");
|
|
export const shiftPressed = modifierPressed("Shift");
|
|
export const altPressed = modifierPressed("Alt");
|
|
export const metaPressed = modifierPressed("Meta");
|
|
|
|
export function modifiersToPlatformString(modifiers: string[]): string {
|
|
const displayModifiers = isApplePlatform()
|
|
? ["^", "⌥", "⇧", "⌘"]
|
|
: [`${tr.keyboardCtrl()}+`, "Alt+", `${tr.keyboardShift()}+`, "Win+"];
|
|
|
|
let result = "";
|
|
|
|
for (const modifier of modifiers) {
|
|
result += displayModifiers[platformModifiers.indexOf(modifier)];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
export function keyToPlatformString(key: string): string {
|
|
switch (key) {
|
|
case "Backspace":
|
|
return "⌫";
|
|
case "Delete":
|
|
return "⌦";
|
|
case "Escape":
|
|
return "⎋";
|
|
|
|
default:
|
|
return key;
|
|
}
|
|
}
|
|
|
|
export function isArrowLeft(event: KeyboardEvent): boolean {
|
|
if (event.code === "ArrowLeft") {
|
|
return true;
|
|
}
|
|
|
|
return isApplePlatform() && metaPressed(event) && event.code === "KeyB";
|
|
}
|
|
|
|
export function isArrowRight(event: KeyboardEvent): boolean {
|
|
if (event.code === "ArrowRight") {
|
|
return true;
|
|
}
|
|
|
|
return isApplePlatform() && metaPressed(event) && event.code === "KeyF";
|
|
}
|
|
|
|
export function isArrowUp(event: KeyboardEvent): boolean {
|
|
if (event.code === "ArrowUp") {
|
|
return true;
|
|
}
|
|
|
|
return isApplePlatform() && metaPressed(event) && event.code === "KeyP";
|
|
}
|
|
|
|
export function isArrowDown(event: KeyboardEvent): boolean {
|
|
if (event.code === "ArrowDown") {
|
|
return true;
|
|
}
|
|
|
|
return isApplePlatform() && metaPressed(event) && event.code === "KeyN";
|
|
}
|
|
|
|
export function onEnterOrSpace(callback: () => void): (event: KeyboardEvent) => void {
|
|
return (event: KeyboardEvent) => {
|
|
switch (event.code) {
|
|
case "Enter":
|
|
case "Space":
|
|
callback();
|
|
break;
|
|
}
|
|
};
|
|
}
|