Make everything use the KeyBindingManager (#7907)

This commit is contained in:
Šimon Brandner 2022-02-28 17:05:52 +01:00 committed by GitHub
parent 5f8441216c
commit df591ee835
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 529 additions and 277 deletions

View file

@ -130,10 +130,20 @@ export enum KeyBindingAction {
/** Toggles webcam while on a call */
ToggleWebcamInCall = "KeyBinding.toggleWebcamInCall",
/** Closes a dialog or a context menu */
CloseDialogOrContextMenu = "KeyBinding.closeDialogOrContextMenu",
/** Clicks the selected button */
ActivateSelectedButton = "KeyBinding.activateSelectedButton",
/** Accessibility actions */
Escape = "KeyBinding.escape",
Enter = "KeyBinding.enter",
Space = "KeyBinding.space",
Backspace = "KeyBinding.backspace",
Delete = "KeyBinding.delete",
Home = "KeyBinding.home",
End = "KeyBinding.end",
ArrowLeft = "KeyBinding.arrowLeft",
ArrowUp = "KeyBinding.arrowUp",
ArrowRight = "KeyBinding.arrowRight",
ArrowDown = "KeyBinding.arrowDown",
Tab = "KeyBinding.tab",
Comma = "KeyBinding.comma",
/** Toggle visibility of hidden events */
ToggleHiddenEventVisibility = 'KeyBinding.toggleHiddenEventVisibility',
@ -156,13 +166,14 @@ type IKeyboardShortcuts = {
};
export interface ICategory {
categoryLabel: string;
categoryLabel?: string;
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
settingNames: (KeyBindingAction)[];
}
export enum CategoryName {
NAVIGATION = "Navigation",
ACCESSIBILITY = "Accessibility",
CALLS = "Calls",
COMPOSER = "Composer",
ROOM_LIST = "Room List",
@ -245,12 +256,26 @@ export const CATEGORIES: Record<CategoryName, ICategory> = {
KeyBindingAction.NextRoom,
KeyBindingAction.PrevRoom,
],
}, [CategoryName.ACCESSIBILITY]: {
categoryLabel: _td("Accessibility"),
settingNames: [
KeyBindingAction.Escape,
KeyBindingAction.Enter,
KeyBindingAction.Space,
KeyBindingAction.Backspace,
KeyBindingAction.Delete,
KeyBindingAction.Home,
KeyBindingAction.End,
KeyBindingAction.ArrowLeft,
KeyBindingAction.ArrowUp,
KeyBindingAction.ArrowRight,
KeyBindingAction.ArrowDown,
KeyBindingAction.Comma,
],
}, [CategoryName.NAVIGATION]: {
categoryLabel: _td("Navigation"),
settingNames: [
KeyBindingAction.ToggleUserMenu,
KeyBindingAction.CloseDialogOrContextMenu,
KeyBindingAction.ActivateSelectedButton,
KeyBindingAction.ToggleRoomSidePanel,
KeyBindingAction.ToggleSpacePanel,
KeyBindingAction.ShowKeyboardSettings,
@ -611,6 +636,68 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = {
},
displayName: _td("Open user settings"),
},
[KeyBindingAction.Escape]: {
default: {
key: Key.ESCAPE,
},
displayName: _td("Close dialog or context menu"),
},
[KeyBindingAction.Enter]: {
default: {
key: Key.ENTER,
},
displayName: _td("Activate selected button"),
},
[KeyBindingAction.Space]: {
default: {
key: Key.SPACE,
},
},
[KeyBindingAction.Backspace]: {
default: {
key: Key.BACKSPACE,
},
},
[KeyBindingAction.Delete]: {
default: {
key: Key.DELETE,
},
},
[KeyBindingAction.Home]: {
default: {
key: Key.HOME,
},
},
[KeyBindingAction.End]: {
default: {
key: Key.END,
},
},
[KeyBindingAction.ArrowLeft]: {
default: {
key: Key.ARROW_LEFT,
},
},
[KeyBindingAction.ArrowUp]: {
default: {
key: Key.ARROW_UP,
},
},
[KeyBindingAction.ArrowRight]: {
default: {
key: Key.ARROW_RIGHT,
},
},
[KeyBindingAction.ArrowDown]: {
default: {
key: Key.ARROW_DOWN,
},
},
[KeyBindingAction.Comma]: {
default: {
key: Key.COMMA,
},
},
};
// XXX: These have to be manually mirrored in KeyBindingDefaults
@ -651,18 +738,6 @@ const getNonCustomizableShortcuts = (): IKeyboardShortcuts => {
},
displayName: _td("Search (must be enabled)"),
},
[KeyBindingAction.CloseDialogOrContextMenu]: {
default: {
key: Key.ESCAPE,
},
displayName: _td("Close dialog or context menu"),
},
[KeyBindingAction.ActivateSelectedButton]: {
default: {
key: Key.ENTER,
},
displayName: _td("Activate selected button"),
},
};
if (PlatformPeg.get().overrideBrowserShortcuts()) {

View file

@ -27,7 +27,8 @@ import React, {
RefObject,
} from "react";
import { Key } from "../Keyboard";
import { getKeyBindingsManager } from "../KeyBindingsManager";
import { KeyBindingAction } from "./KeyboardShortcuts";
import { FocusHandler, Ref } from "./roving/types";
/**
@ -207,12 +208,13 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
}
let handled = false;
const action = getKeyBindingsManager().getAccessibilityAction(ev);
let focusRef: RefObject<HTMLElement>;
// Don't interfere with input default keydown behaviour
// but allow people to move focus from it with Tab.
if (checkInputableElement(ev.target as HTMLElement)) {
switch (ev.key) {
case Key.TAB:
switch (action) {
case KeyBindingAction.Tab:
handled = true;
if (context.state.refs.length > 0) {
const idx = context.state.refs.indexOf(context.state.activeRef);
@ -222,8 +224,8 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
}
} else {
// check if we actually have any items
switch (ev.key) {
case Key.HOME:
switch (action) {
case KeyBindingAction.Home:
if (handleHomeEnd) {
handled = true;
// move focus to first (visible) item
@ -231,7 +233,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
}
break;
case Key.END:
case KeyBindingAction.End:
if (handleHomeEnd) {
handled = true;
// move focus to last (visible) item
@ -239,10 +241,10 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
}
break;
case Key.ARROW_DOWN:
case Key.ARROW_RIGHT:
if ((ev.key === Key.ARROW_DOWN && handleUpDown) ||
(ev.key === Key.ARROW_RIGHT && handleLeftRight)
case KeyBindingAction.ArrowDown:
case KeyBindingAction.ArrowRight:
if ((action === KeyBindingAction.ArrowDown && handleUpDown) ||
(action === KeyBindingAction.ArrowRight && handleLeftRight)
) {
handled = true;
if (context.state.refs.length > 0) {
@ -252,9 +254,11 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
}
break;
case Key.ARROW_UP:
case Key.ARROW_LEFT:
if ((ev.key === Key.ARROW_UP && handleUpDown) || (ev.key === Key.ARROW_LEFT && handleLeftRight)) {
case KeyBindingAction.ArrowUp:
case KeyBindingAction.ArrowLeft:
if ((action === KeyBindingAction.ArrowUp && handleUpDown) ||
(action === KeyBindingAction.ArrowLeft && handleLeftRight)
) {
handled = true;
if (context.state.refs.length > 0) {
const idx = context.state.refs.indexOf(context.state.activeRef);

View file

@ -17,7 +17,8 @@ limitations under the License.
import React from "react";
import { RovingTabIndexProvider } from "./RovingTabIndex";
import { Key } from "../Keyboard";
import { getKeyBindingsManager } from "../KeyBindingsManager";
import { KeyBindingAction } from "./KeyboardShortcuts";
interface IProps extends Omit<React.HTMLProps<HTMLDivElement>, "onKeyDown"> {
}
@ -34,9 +35,10 @@ const Toolbar: React.FC<IProps> = ({ children, ...props }) => {
let handled = true;
// HOME and END are handled by RovingTabIndexProvider
switch (ev.key) {
case Key.ARROW_UP:
case Key.ARROW_DOWN:
const action = getKeyBindingsManager().getAccessibilityAction(ev);
switch (action) {
case KeyBindingAction.ArrowUp:
case KeyBindingAction.ArrowDown:
if (target.hasAttribute('aria-haspopup')) {
target.click();
}

View file

@ -18,14 +18,15 @@ limitations under the License.
import React from "react";
import { Key } from "../../Keyboard";
import { useRovingTabIndex } from "../RovingTabIndex";
import StyledCheckbox from "../../components/views/elements/StyledCheckbox";
import { KeyBindingAction } from "../KeyboardShortcuts";
import { getKeyBindingsManager } from "../../KeyBindingsManager";
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
label?: string;
onChange(); // we handle keyup/down ourselves so lose the ChangeEvent
onClose(): void; // gets called after onChange on Key.ENTER
onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
}
// Semantic component for representing a styled role=menuitemcheckbox
@ -33,22 +34,37 @@ export const StyledMenuItemCheckbox: React.FC<IProps> = ({ children, label, onCh
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLInputElement>();
const onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === Key.ENTER || e.key === Key.SPACE) {
let handled = true;
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Space:
onChange();
break;
case KeyBindingAction.Enter:
onChange();
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
onClose();
break;
default:
handled = false;
}
if (handled) {
e.stopPropagation();
e.preventDefault();
onChange();
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
if (e.key === Key.ENTER) {
onClose();
}
}
};
const onKeyUp = (e: React.KeyboardEvent) => {
// prevent the input default handler as we handle it on keydown to match
// https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html
if (e.key === Key.SPACE || e.key === Key.ENTER) {
e.stopPropagation();
e.preventDefault();
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Space:
case KeyBindingAction.Enter:
// prevent the input default handler as we handle it on keydown to match
// https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html
e.stopPropagation();
e.preventDefault();
break;
}
};
return (

View file

@ -18,14 +18,15 @@ limitations under the License.
import React from "react";
import { Key } from "../../Keyboard";
import { useRovingTabIndex } from "../RovingTabIndex";
import StyledRadioButton from "../../components/views/elements/StyledRadioButton";
import { KeyBindingAction } from "../KeyboardShortcuts";
import { getKeyBindingsManager } from "../../KeyBindingsManager";
interface IProps extends React.ComponentProps<typeof StyledRadioButton> {
label?: string;
onChange(); // we handle keyup/down ourselves so lose the ChangeEvent
onClose(): void; // gets called after onChange on Key.ENTER
onClose(): void; // gets called after onChange on KeyBindingAction.Enter
}
// Semantic component for representing a styled role=menuitemradio
@ -33,22 +34,37 @@ export const StyledMenuItemRadio: React.FC<IProps> = ({ children, label, onChang
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLInputElement>();
const onKeyDown = (e: React.KeyboardEvent) => {
if (e.key === Key.ENTER || e.key === Key.SPACE) {
let handled = true;
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Space:
onChange();
break;
case KeyBindingAction.Enter:
onChange();
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
onClose();
break;
default:
handled = false;
}
if (handled) {
e.stopPropagation();
e.preventDefault();
onChange();
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
if (e.key === Key.ENTER) {
onClose();
}
}
};
const onKeyUp = (e: React.KeyboardEvent) => {
// prevent the input default handler as we handle it on keydown to match
// https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html
if (e.key === Key.SPACE || e.key === Key.ENTER) {
e.stopPropagation();
e.preventDefault();
const action = getKeyBindingsManager().getAccessibilityAction(e);
switch (action) {
case KeyBindingAction.Enter:
case KeyBindingAction.Space:
// prevent the input default handler as we handle it on keydown to match
// https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html
e.stopPropagation();
e.preventDefault();
break;
}
};
return (