Make everything use the KeyBindingManager
(#7907)
This commit is contained in:
parent
5f8441216c
commit
df591ee835
37 changed files with 529 additions and 277 deletions
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue