mirror of https://github.com/vector-im/riot-web
Make everything use the `KeyBindingManager` (#7907)
parent
5f8441216c
commit
df591ee835
|
@ -149,16 +149,11 @@ const roomBindings = (): KeyBinding[] => {
|
|||
};
|
||||
|
||||
const navigationBindings = (): KeyBinding[] => {
|
||||
const bindings = getBindingsByCategory(CategoryName.NAVIGATION);
|
||||
return getBindingsByCategory(CategoryName.NAVIGATION);
|
||||
};
|
||||
|
||||
bindings.push({
|
||||
action: KeyBindingAction.CloseDialogOrContextMenu,
|
||||
keyCombo: {
|
||||
key: Key.ESCAPE,
|
||||
},
|
||||
});
|
||||
|
||||
return bindings;
|
||||
const accessibilityBindings = (): KeyBinding[] => {
|
||||
return getBindingsByCategory(CategoryName.ACCESSIBILITY);
|
||||
};
|
||||
|
||||
const callBindings = (): KeyBinding[] => {
|
||||
|
@ -177,6 +172,7 @@ export const defaultBindingsProvider: IKeyBindingsProvider = {
|
|||
getRoomListBindings: roomListBindings,
|
||||
getRoomBindings: roomBindings,
|
||||
getNavigationBindings: navigationBindings,
|
||||
getAccessibilityBindings: accessibilityBindings,
|
||||
getCallBindings: callBindings,
|
||||
getLabsBindings: labsBindings,
|
||||
};
|
||||
|
|
|
@ -155,6 +155,10 @@ export class KeyBindingsManager {
|
|||
return this.getAction(this.bindingsProviders.map(it => it.getNavigationBindings), ev);
|
||||
}
|
||||
|
||||
getAccessibilityAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined {
|
||||
return this.getAction(this.bindingsProviders.map(it => it.getAccessibilityBindings), ev);
|
||||
}
|
||||
|
||||
getCallAction(ev: KeyboardEvent | React.KeyboardEvent): KeyBindingAction | undefined {
|
||||
return this.getAction(this.bindingsProviders.map(it => it.getCallBindings), ev);
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -21,11 +21,12 @@ import ReactDOM from "react-dom";
|
|||
import classNames from "classnames";
|
||||
import FocusLock from "react-focus-lock";
|
||||
|
||||
import { Key } from "../../Keyboard";
|
||||
import { Writeable } from "../../@types/common";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
import { checkInputableElement, RovingTabIndexProvider } from "../../accessibility/RovingTabIndex";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
|
||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||
|
@ -191,30 +192,32 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
|||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||
ev.stopPropagation(); // prevent keyboard propagating out of the context menu, we're focus-locked
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
|
||||
// If someone is managing their own focus, we will only exit for them with Escape.
|
||||
// They are probably using props.focusLock along with this option as well.
|
||||
if (!this.props.managed) {
|
||||
if (ev.key === Key.ESCAPE) {
|
||||
if (action === KeyBindingAction.Escape) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// When an <input> is focused, only handle the Escape key
|
||||
if (checkInputableElement(ev.target as HTMLElement) && ev.key !== Key.ESCAPE) {
|
||||
if (checkInputableElement(ev.target as HTMLElement) && action !== KeyBindingAction.Escape) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
ev.key === Key.ESCAPE ||
|
||||
if ([
|
||||
KeyBindingAction.Escape,
|
||||
// You can only navigate the ContextMenu by arrow keys and Home/End (see RovingTabIndex).
|
||||
// Tabbing to the next section of the page, will close the ContextMenu.
|
||||
ev.key === Key.TAB ||
|
||||
KeyBindingAction.Tab,
|
||||
// When someone moves left or right along a <Toolbar /> (like the
|
||||
// MessageActionBar), we should close any ContextMenu that is open.
|
||||
ev.key === Key.ARROW_LEFT ||
|
||||
ev.key === Key.ARROW_RIGHT
|
||||
) {
|
||||
KeyBindingAction.ArrowLeft,
|
||||
KeyBindingAction.ArrowRight,
|
||||
].includes(action)) {
|
||||
this.props.onFinished();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -35,7 +35,6 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
|||
import UIStore from "../../stores/UIStore";
|
||||
import { findSiblingElement, IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex";
|
||||
import RoomListHeader from "../views/rooms/RoomListHeader";
|
||||
import { Key } from "../../Keyboard";
|
||||
import RecentlyViewedButton from "../views/rooms/RecentlyViewedButton";
|
||||
import { BreadcrumbsStore } from "../../stores/BreadcrumbsStore";
|
||||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomListStore";
|
||||
|
@ -316,12 +315,15 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
private onRoomListKeydown = (ev: React.KeyboardEvent) => {
|
||||
if (ev.altKey || ev.ctrlKey || ev.metaKey) return;
|
||||
if (SettingsStore.getValue("feature_spotlight")) return;
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
|
||||
// we cannot handle Space as that is an activation key for all focusable elements in this widget
|
||||
if (ev.key.length === 1) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.roomSearchRef.current?.appendChar(ev.key);
|
||||
} else if (ev.key === Key.BACKSPACE) {
|
||||
} else if (action === KeyBindingAction.Backspace) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.roomSearchRef.current?.backspace();
|
||||
|
|
|
@ -20,7 +20,6 @@ import classNames from "classnames";
|
|||
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import { useRovingTabIndex } from "../../accessibility/RovingTabIndex";
|
||||
import { Key } from "../../Keyboard";
|
||||
import { useLocalStorageState } from "../../hooks/useLocalStorageState";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import WidgetUtils, { IWidgetEvent } from "../../utils/WidgetUtils";
|
||||
|
@ -28,6 +27,8 @@ import { useAccountData } from "../../hooks/useAccountData";
|
|||
import AppTile from "../views/elements/AppTile";
|
||||
import { useSettingValue } from "../../hooks/useSettings";
|
||||
import UIStore from "../../stores/UIStore";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
|
||||
const MIN_HEIGHT = 100;
|
||||
const MAX_HEIGHT = 500; // or 50% of the window height
|
||||
|
@ -91,16 +92,16 @@ const LeftPanelWidget: React.FC = () => {
|
|||
onFocus={onFocus}
|
||||
className="mx_LeftPanelWidget_headerContainer"
|
||||
onKeyDown={(ev: React.KeyboardEvent) => {
|
||||
switch (ev.key) {
|
||||
case Key.ARROW_LEFT:
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (action) {
|
||||
case KeyBindingAction.ArrowLeft:
|
||||
ev.stopPropagation();
|
||||
setExpanded(false);
|
||||
break;
|
||||
case Key.ARROW_RIGHT: {
|
||||
case KeyBindingAction.ArrowRight:
|
||||
ev.stopPropagation();
|
||||
setExpanded(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -19,9 +19,10 @@ import React, { createRef, HTMLProps } from 'react';
|
|||
import { throttle } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Key } from '../../Keyboard';
|
||||
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
|
||||
interface IProps extends HTMLProps<HTMLInputElement> {
|
||||
onSearch?: (query: string) => void;
|
||||
|
@ -66,8 +67,9 @@ export default class SearchBox extends React.Component<IProps, IState> {
|
|||
}, 200, { trailing: true, leading: true });
|
||||
|
||||
private onKeyDown = (ev: React.KeyboardEvent): void => {
|
||||
switch (ev.key) {
|
||||
case Key.ESCAPE:
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Escape:
|
||||
this.clearSearch("keyboard");
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
|||
import { linkifyElement } from "../../HtmlUtils";
|
||||
import { useDispatcher } from "../../hooks/useDispatcher";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import { Key } from "../../Keyboard";
|
||||
import { IState, RovingTabIndexProvider, useRovingTabIndex } from "../../accessibility/RovingTabIndex";
|
||||
import { getDisplayAliasForRoom } from "./RoomDirectory";
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
|
@ -64,6 +63,8 @@ import { awaitRoomDownSync } from "../../utils/RoomUpgrade";
|
|||
import RoomViewStore from "../../stores/RoomViewStore";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { JoinRoomReadyPayload } from "../../dispatcher/payloads/JoinRoomReadyPayload";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
|
||||
interface IProps {
|
||||
space: Room;
|
||||
|
@ -247,10 +248,13 @@ const Tile: React.FC<ITileProps> = ({
|
|||
|
||||
if (showChildren) {
|
||||
const onChildrenKeyDown = (e) => {
|
||||
if (e.key === Key.ARROW_LEFT) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
ref.current?.focus();
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
switch (action) {
|
||||
case KeyBindingAction.ArrowLeft:
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
ref.current?.focus();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -266,15 +270,16 @@ const Tile: React.FC<ITileProps> = ({
|
|||
onKeyDown = (e) => {
|
||||
let handled = false;
|
||||
|
||||
switch (e.key) {
|
||||
case Key.ARROW_LEFT:
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
switch (action) {
|
||||
case KeyBindingAction.ArrowLeft:
|
||||
if (showChildren) {
|
||||
handled = true;
|
||||
toggleShowChildren();
|
||||
}
|
||||
break;
|
||||
|
||||
case Key.ARROW_RIGHT:
|
||||
case KeyBindingAction.ArrowRight:
|
||||
handled = true;
|
||||
if (showChildren) {
|
||||
const childSection = ref.current?.nextElementSibling;
|
||||
|
@ -700,7 +705,11 @@ const SpaceHierarchy = ({
|
|||
}
|
||||
|
||||
const onKeyDown = (ev: KeyboardEvent, state: IState): void => {
|
||||
if (ev.key === Key.ARROW_DOWN && ev.currentTarget.classList.contains("mx_SpaceHierarchy_search")) {
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
if (
|
||||
action === KeyBindingAction.ArrowDown &&
|
||||
ev.currentTarget.classList.contains("mx_SpaceHierarchy_search")
|
||||
) {
|
||||
state.refs[0]?.current?.focus();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -37,7 +37,6 @@ import UserActivity from "../../UserActivity";
|
|||
import Modal from "../../Modal";
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import { Action } from '../../dispatcher/actions';
|
||||
import { Key } from '../../Keyboard';
|
||||
import Timer from '../../utils/Timer';
|
||||
import shouldHideEvent from '../../shouldHideEvent';
|
||||
import { haveTileForEvent } from "../views/rooms/EventTile";
|
||||
|
@ -54,6 +53,8 @@ import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
|||
import ErrorDialog from '../views/dialogs/ErrorDialog';
|
||||
import CallEventGrouper, { buildCallEventGroupers } from "./CallEventGrouper";
|
||||
import { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
|
||||
const PAGINATE_SIZE = 20;
|
||||
const INITIAL_SIZE = 20;
|
||||
|
@ -1086,11 +1087,12 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
* We pass it down to the scroll panel.
|
||||
*/
|
||||
public handleScrollKey = ev => {
|
||||
if (!this.messagePanel.current) { return; }
|
||||
if (!this.messagePanel.current) return;
|
||||
|
||||
// jump to the live timeline on ctrl-end, rather than the end of the
|
||||
// timeline window.
|
||||
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey && ev.key === Key.END) {
|
||||
const action = getKeyBindingsManager().getRoomAction(ev);
|
||||
if (action === KeyBindingAction.JumpToLatestMessage) {
|
||||
this.jumpToLiveTimeline();
|
||||
} else {
|
||||
this.messagePanel.current.handleScrollKey(ev);
|
||||
|
|
|
@ -20,11 +20,12 @@ import PlayPauseButton from "./PlayPauseButton";
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { formatBytes } from "../../../utils/FormattingUtils";
|
||||
import DurationClock from "./DurationClock";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import SeekBar from "./SeekBar";
|
||||
import PlaybackClock from "./PlaybackClock";
|
||||
import AudioPlayerBase from "./AudioPlayerBase";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
@replaceableComponent("views.audio_messages.AudioPlayer")
|
||||
export default class AudioPlayer extends AudioPlayerBase {
|
||||
|
@ -32,18 +33,29 @@ export default class AudioPlayer extends AudioPlayerBase {
|
|||
private seekRef: RefObject<SeekBar> = createRef();
|
||||
|
||||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||
let handled = true;
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
|
||||
switch (action) {
|
||||
case KeyBindingAction.Space:
|
||||
this.playPauseRef.current?.toggleState();
|
||||
break;
|
||||
case KeyBindingAction.ArrowLeft:
|
||||
this.seekRef.current?.left();
|
||||
break;
|
||||
case KeyBindingAction.ArrowRight:
|
||||
this.seekRef.current?.right();
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// stopPropagation() prevents the FocusComposer catch-all from triggering,
|
||||
// but we need to do it on key down instead of press (even though the user
|
||||
// interaction is typically on press).
|
||||
if (ev.key === Key.SPACE) {
|
||||
if (handled) {
|
||||
ev.stopPropagation();
|
||||
this.playPauseRef.current?.toggleState();
|
||||
} else if (ev.key === Key.ARROW_LEFT) {
|
||||
ev.stopPropagation();
|
||||
this.seekRef.current?.left();
|
||||
} else if (ev.key === Key.ARROW_RIGHT) {
|
||||
ev.stopPropagation();
|
||||
this.seekRef.current?.right();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ import { DefaultTagID, TagID } from "../../../stores/room-list/models";
|
|||
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import RoomListActions from "../../../actions/RoomListActions";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
|
||||
import { RoomNotifState } from "../../../RoomNotifs";
|
||||
import Modal from "../../../Modal";
|
||||
|
@ -47,6 +46,8 @@ import DMRoomMap from "../../../utils/DMRoomMap";
|
|||
import { Action } from "../../../dispatcher/actions";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
interface IProps extends IContextMenuProps {
|
||||
room: Room;
|
||||
|
@ -267,9 +268,12 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
|
|||
logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`);
|
||||
}
|
||||
|
||||
if ((ev as React.KeyboardEvent).key === Key.ENTER) {
|
||||
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
||||
onFinished();
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
||||
onFinished();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ import * as Email from '../../../email';
|
|||
import IdentityAuthClient from '../../../IdentityAuthClient';
|
||||
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from '../../../utils/IdentityServerUtils';
|
||||
import { abbreviateUrl } from '../../../utils/UrlUtils';
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AddressSelector from '../elements/AddressSelector';
|
||||
|
@ -38,6 +37,8 @@ import AddressTile from '../elements/AddressTile';
|
|||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
const TRUNCATE_QUERY_LIST = 40;
|
||||
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
|
||||
|
@ -167,40 +168,38 @@ export default class AddressPickerDialog extends React.Component<IProps, IState>
|
|||
|
||||
private onKeyDown = (e: React.KeyboardEvent): void => {
|
||||
const textInput = this.textinput.current ? this.textinput.current.value : undefined;
|
||||
let handled = true;
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
|
||||
if (e.key === Key.ESCAPE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (action === KeyBindingAction.Escape) {
|
||||
this.props.onFinished(false);
|
||||
} else if (e.key === Key.ARROW_UP) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this.addressSelector.current) this.addressSelector.current.moveSelectionUp();
|
||||
} else if (e.key === Key.ARROW_DOWN) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this.addressSelector.current) this.addressSelector.current.moveSelectionDown();
|
||||
} else if (this.state.suggestedList.length > 0 && [Key.COMMA, Key.ENTER, Key.TAB].includes(e.key)) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this.addressSelector.current) this.addressSelector.current.chooseSelection();
|
||||
} else if (textInput.length === 0 && this.state.selectedList.length && e.key === Key.BACKSPACE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
} else if (e.key === KeyBindingAction.ArrowUp) {
|
||||
this.addressSelector.current?.moveSelectionUp();
|
||||
} else if (e.key === KeyBindingAction.ArrowDown) {
|
||||
this.addressSelector.current?.moveSelectionDown();
|
||||
} else if (
|
||||
[KeyBindingAction.Comma, KeyBindingAction.Enter, KeyBindingAction.Tab].includes(action) &&
|
||||
this.state.suggestedList.length > 0
|
||||
) {
|
||||
this.addressSelector.current?.chooseSelection();
|
||||
} else if (textInput.length === 0 && this.state.selectedList.length && action === KeyBindingAction.Backspace) {
|
||||
this.onDismissed(this.state.selectedList.length - 1)();
|
||||
} else if (e.key === Key.ENTER) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
} else if (e.key === KeyBindingAction.Enter) {
|
||||
if (textInput === '') {
|
||||
// if there's nothing in the input box, submit the form
|
||||
this.onButtonClick();
|
||||
} else {
|
||||
this.addAddressesToList([textInput]);
|
||||
}
|
||||
} else if (textInput && (e.key === Key.COMMA || e.key === Key.TAB)) {
|
||||
} else if (textInput && [KeyBindingAction.Comma, KeyBindingAction.Tab].includes(action)) {
|
||||
this.addAddressesToList([textInput]);
|
||||
} else {
|
||||
handled = false;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.addAddressesToList([textInput]);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ import FocusLock from 'react-focus-lock';
|
|||
import classNames from 'classnames';
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
|
||||
import { Key } from '../../../Keyboard';
|
||||
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
@ -30,6 +29,8 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
|
|||
import Heading from '../typography/Heading';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import { PosthogScreenTracker, ScreenName } from "../../../PosthogTrackers";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
// Whether the dialog should have a 'close' button that will
|
||||
|
@ -99,10 +100,16 @@ export default class BaseDialog extends React.Component<IProps> {
|
|||
if (this.props.onKeyDown) {
|
||||
this.props.onKeyDown(e);
|
||||
}
|
||||
if (this.props.hasCancel && e.key === Key.ESCAPE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.props.onFinished(false);
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Escape:
|
||||
if (!this.props.hasCancel) break;
|
||||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.props.onFinished(false);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import SdkConfig from '../../../SdkConfig';
|
|||
import withValidation, { IFieldState } from '../elements/Validation';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { IOpts, privateShouldBeEncrypted } from "../../../createRoom";
|
||||
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
@ -34,6 +33,8 @@ import DialogButtons from "../elements/DialogButtons";
|
|||
import BaseDialog from "../dialogs/BaseDialog";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
import JoinRuleDropdown from "../elements/JoinRuleDropdown";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
interface IProps {
|
||||
defaultPublic?: boolean;
|
||||
|
@ -136,10 +137,13 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === Key.ENTER) {
|
||||
this.onOk();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(event);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
this.onOk();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ import {
|
|||
showAnyInviteErrors,
|
||||
showCommunityInviteDialog,
|
||||
} from "../../../RoomInvite";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { DefaultTagID } from "../../../stores/room-list/models";
|
||||
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||
|
@ -71,6 +70,8 @@ import UserIdentifierCustomisations from '../../../customisations/UserIdentifier
|
|||
import CopyableText from "../elements/CopyableText";
|
||||
import { ScreenName } from '../../../PosthogTrackers';
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
|
||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||
/* eslint-disable camelcase */
|
||||
|
@ -803,20 +804,36 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
|
||||
private onKeyDown = (e) => {
|
||||
if (this.state.busy) return;
|
||||
|
||||
let handled = true;
|
||||
const value = e.target.value.trim();
|
||||
const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey;
|
||||
if (!value && this.state.targets.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) {
|
||||
// when the field is empty and the user hits backspace remove the right-most target
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
|
||||
switch (action) {
|
||||
case KeyBindingAction.Backspace:
|
||||
if (value || this.state.targets.length <= 0) break;
|
||||
|
||||
// when the field is empty and the user hits backspace remove the right-most target
|
||||
this.removeMember(this.state.targets[this.state.targets.length - 1]);
|
||||
break;
|
||||
case KeyBindingAction.Space:
|
||||
if (!value || !value.includes("@") || value.includes(" ")) break;
|
||||
|
||||
// when the user hits space and their input looks like an e-mail/MXID then try to convert it
|
||||
this.convertFilter();
|
||||
break;
|
||||
case KeyBindingAction.Enter:
|
||||
if (!value) break;
|
||||
|
||||
// when the user hits enter with something in their field try to convert it
|
||||
this.convertFilter();
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
e.preventDefault();
|
||||
this.removeMember(this.state.targets[this.state.targets.length - 1]);
|
||||
} else if (value && e.key === Key.ENTER && !hasModifiers) {
|
||||
// when the user hits enter with something in their field try to convert it
|
||||
e.preventDefault();
|
||||
this.convertFilter();
|
||||
} else if (value && e.key === Key.SPACE && !hasModifiers && value.includes("@") && !value.includes(" ")) {
|
||||
// when the user hits space and their input looks like an e-mail/MXID then try to convert it
|
||||
e.preventDefault();
|
||||
this.convertFilter();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -19,11 +19,12 @@ import { MatrixClient } from "matrix-js-sdk/src/client";
|
|||
import FocusLock from "react-focus-lock";
|
||||
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
|
||||
export interface IScrollableBaseState {
|
||||
canSubmit: boolean;
|
||||
|
@ -45,10 +46,13 @@ export default abstract class ScrollableBaseModal<TProps extends IDialogProps, T
|
|||
}
|
||||
|
||||
private onKeyDown = (e: KeyboardEvent | React.KeyboardEvent): void => {
|
||||
if (e.key === Key.ESCAPE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.cancel();
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Escape:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this.cancel();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ import {
|
|||
Type,
|
||||
useRovingTabIndex,
|
||||
} from "../../../accessibility/RovingTabIndex";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import SpaceStore from "../../../stores/spaces/SpaceStore";
|
||||
|
@ -596,23 +595,33 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", onFinished }) =>
|
|||
}
|
||||
|
||||
const onDialogKeyDown = (ev: KeyboardEvent) => {
|
||||
const navAction = getKeyBindingsManager().getNavigationAction(ev);
|
||||
switch (navAction) {
|
||||
case "KeyBinding.closeDialogOrContextMenu" as KeyBindingAction:
|
||||
const navigationAction = getKeyBindingsManager().getNavigationAction(ev);
|
||||
switch (navigationAction) {
|
||||
case KeyBindingAction.FilterRooms:
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
onFinished();
|
||||
break;
|
||||
}
|
||||
|
||||
const accessibilityAction = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (accessibilityAction) {
|
||||
case KeyBindingAction.Escape:
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
onFinished();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (ev: KeyboardEvent) => {
|
||||
let ref: RefObject<HTMLElement>;
|
||||
|
||||
switch (ev.key) {
|
||||
case Key.ARROW_UP:
|
||||
case Key.ARROW_DOWN:
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
|
||||
switch (action) {
|
||||
case KeyBindingAction.ArrowUp:
|
||||
case KeyBindingAction.ArrowDown:
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
|
@ -629,12 +638,12 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", onFinished }) =>
|
|||
}
|
||||
|
||||
const idx = refs.indexOf(rovingContext.state.activeRef);
|
||||
ref = findSiblingElement(refs, idx + (ev.key === Key.ARROW_UP ? -1 : 1));
|
||||
ref = findSiblingElement(refs, idx + (action === KeyBindingAction.ArrowUp ? -1 : 1));
|
||||
}
|
||||
break;
|
||||
|
||||
case Key.ARROW_LEFT:
|
||||
case Key.ARROW_RIGHT:
|
||||
case KeyBindingAction.ArrowLeft:
|
||||
case KeyBindingAction.ArrowRight:
|
||||
// only handle these keys when we are in the recently viewed row of options
|
||||
if (!query &&
|
||||
rovingContext.state.refs.length > 0 &&
|
||||
|
@ -646,11 +655,10 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", onFinished }) =>
|
|||
|
||||
const refs = rovingContext.state.refs.filter(refIsForRecentlyViewed);
|
||||
const idx = refs.indexOf(rovingContext.state.activeRef);
|
||||
ref = findSiblingElement(refs, idx + (ev.key === Key.ARROW_LEFT ? -1 : 1));
|
||||
ref = findSiblingElement(refs, idx + (action === KeyBindingAction.ArrowLeft ? -1 : 1));
|
||||
}
|
||||
break;
|
||||
|
||||
case Key.ENTER:
|
||||
case KeyBindingAction.Enter:
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
rovingContext.state.activeRef?.current?.click();
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
import React, { ReactHTML } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { Key } from '../../../Keyboard';
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Element> | React.FormEvent<Element>;
|
||||
|
||||
|
@ -92,29 +93,36 @@ export default function AccessibleButton({
|
|||
// Browsers handle space and enter keypresses differently and we are only adjusting to the
|
||||
// inconsistencies here
|
||||
newProps.onKeyDown = (e) => {
|
||||
if (e.key === Key.ENTER) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick(e);
|
||||
}
|
||||
if (e.key === Key.SPACE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
} else {
|
||||
onKeyDown?.(e);
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick(e);
|
||||
case KeyBindingAction.Space:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
default:
|
||||
onKeyDown?.(e);
|
||||
}
|
||||
};
|
||||
newProps.onKeyUp = (e) => {
|
||||
if (e.key === Key.SPACE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick(e);
|
||||
}
|
||||
if (e.key === Key.ENTER) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
} else {
|
||||
onKeyUp?.(e);
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
case KeyBindingAction.Space:
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
return onClick(e);
|
||||
default:
|
||||
onKeyUp?.(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -20,8 +20,9 @@ import classnames from 'classnames';
|
|||
|
||||
import AccessibleButton, { ButtonEvent } from './AccessibleButton';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
interface IMenuOptionProps {
|
||||
children: ReactElement;
|
||||
|
@ -181,10 +182,12 @@ export default class Dropdown extends React.Component<IProps, IState> {
|
|||
private onAccessibleButtonClick = (ev: ButtonEvent) => {
|
||||
if (this.props.disabled) return;
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent);
|
||||
|
||||
if (!this.state.expanded) {
|
||||
this.setState({ expanded: true });
|
||||
ev.preventDefault();
|
||||
} else if ((ev as React.KeyboardEvent).key === Key.ENTER) {
|
||||
} else if (action === KeyBindingAction.Enter) {
|
||||
// the accessible button consumes enter onKeyDown for firing onClick, so handle it here
|
||||
this.props.onOptionChange(this.state.highlightedOption);
|
||||
this.close();
|
||||
|
@ -214,14 +217,15 @@ export default class Dropdown extends React.Component<IProps, IState> {
|
|||
let handled = true;
|
||||
|
||||
// These keys don't generate keypress events and so needs to be on keyup
|
||||
switch (e.key) {
|
||||
case Key.ENTER:
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
this.props.onOptionChange(this.state.highlightedOption);
|
||||
// fallthrough
|
||||
case Key.ESCAPE:
|
||||
case KeyBindingAction.Escape:
|
||||
this.close();
|
||||
break;
|
||||
case Key.ARROW_DOWN:
|
||||
case KeyBindingAction.ArrowDown:
|
||||
if (this.state.expanded) {
|
||||
this.setState({
|
||||
highlightedOption: this.nextOption(this.state.highlightedOption),
|
||||
|
@ -230,7 +234,7 @@ export default class Dropdown extends React.Component<IProps, IState> {
|
|||
this.setState({ expanded: true });
|
||||
}
|
||||
break;
|
||||
case Key.ARROW_UP:
|
||||
case KeyBindingAction.ArrowUp:
|
||||
if (this.state.expanded) {
|
||||
this.setState({
|
||||
highlightedOption: this.prevOption(this.state.highlightedOption),
|
||||
|
|
|
@ -17,7 +17,8 @@ limitations under the License.
|
|||
|
||||
import React, { createRef } from 'react';
|
||||
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
enum Phases {
|
||||
|
@ -124,9 +125,12 @@ export default class EditableText extends React.Component<IProps, IState> {
|
|||
this.showPlaceholder(false);
|
||||
}
|
||||
|
||||
if (ev.key === Key.ENTER) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
// console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
|
||||
|
@ -141,10 +145,14 @@ export default class EditableText extends React.Component<IProps, IState> {
|
|||
this.value = (ev.target as HTMLDivElement).textContent;
|
||||
}
|
||||
|
||||
if (ev.key === Key.ENTER) {
|
||||
this.onFinish(ev);
|
||||
} else if (ev.key === Key.ESCAPE) {
|
||||
this.cancelEdit();
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Escape:
|
||||
this.cancelEdit();
|
||||
break;
|
||||
case KeyBindingAction.Enter:
|
||||
this.onFinish(ev);
|
||||
break;
|
||||
}
|
||||
|
||||
// console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
|
||||
|
@ -179,7 +187,8 @@ export default class EditableText extends React.Component<IProps, IState> {
|
|||
): void => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self = this;
|
||||
const submit = ("key" in ev && ev.key === Key.ENTER) || shouldSubmit;
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent);
|
||||
const submit = action === KeyBindingAction.Enter || shouldSubmit;
|
||||
this.setState({
|
||||
phase: Phases.Display,
|
||||
}, () => {
|
||||
|
|
|
@ -22,7 +22,6 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
|||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import MemberAvatar from "../avatars/MemberAvatar";
|
||||
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
|
||||
import MessageContextMenu from "../context_menus/MessageContextMenu";
|
||||
|
@ -38,6 +37,8 @@ import { normalizeWheelEvent } from "../../../utils/Mouse";
|
|||
import { IDialogProps } from '../dialogs/IDialogProps';
|
||||
import UIStore from '../../../stores/UIStore';
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
|
||||
// Max scale to keep gaps around the image
|
||||
const MAX_SCALE = 0.95;
|
||||
|
@ -292,10 +293,13 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private onKeyDown = (ev: KeyboardEvent) => {
|
||||
if (ev.key === Key.ESCAPE) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.props.onFinished();
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Escape:
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.props.onFinished();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -19,8 +19,9 @@ import React from 'react';
|
|||
import * as Roles from '../../../Roles';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Field from "./Field";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
|
||||
const CUSTOM_VALUE = "SELECT_VALUE_CUSTOM";
|
||||
|
||||
|
@ -130,16 +131,19 @@ export default class PowerSelector extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private onCustomKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
|
||||
if (event.key === Key.ENTER) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(event);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Do not call the onChange handler directly here - it can cause an infinite loop.
|
||||
// Long story short, a user hits Enter to submit the value which onChange handles as
|
||||
// raising a dialog which causes a blur which causes a dialog which causes a blur and
|
||||
// so on. By not causing the onChange to be called here, we avoid the loop because we
|
||||
// handle the onBlur safely.
|
||||
(event.target as HTMLInputElement).blur();
|
||||
// Do not call the onChange handler directly here - it can cause an infinite loop.
|
||||
// Long story short, a user hits Enter to submit the value which onChange handles as
|
||||
// raising a dialog which causes a blur which causes a dialog which causes a blur and
|
||||
// so on. By not causing the onChange to be called here, we avoid the loop because we
|
||||
// handle the onBlur safely.
|
||||
(event.target as HTMLInputElement).blur();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -19,9 +19,10 @@ import React from 'react';
|
|||
import classNames from "classnames";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { CategoryKey, ICategory } from "./Category";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
interface IProps {
|
||||
categories: ICategory[];
|
||||
|
@ -57,18 +58,20 @@ class Header extends React.PureComponent<IProps> {
|
|||
// https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html
|
||||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||
let handled = true;
|
||||
switch (ev.key) {
|
||||
case Key.ARROW_LEFT:
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (action) {
|
||||
case KeyBindingAction.ArrowLeft:
|
||||
this.changeCategoryRelative(-1);
|
||||
break;
|
||||
case Key.ARROW_RIGHT:
|
||||
case KeyBindingAction.ArrowRight:
|
||||
this.changeCategoryRelative(1);
|
||||
break;
|
||||
|
||||
case Key.HOME:
|
||||
case KeyBindingAction.Home:
|
||||
this.changeCategoryAbsolute(0);
|
||||
break;
|
||||
case Key.END:
|
||||
case KeyBindingAction.End:
|
||||
this.changeCategoryAbsolute(this.props.categories.length - 1, -1);
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -18,8 +18,9 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
|
||||
interface IProps {
|
||||
query: string;
|
||||
|
@ -37,10 +38,13 @@ class Search extends React.PureComponent<IProps> {
|
|||
}
|
||||
|
||||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||
if (ev.key === Key.ENTER) {
|
||||
this.props.onEnter();
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
this.props.onEnter();
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -480,6 +480,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
const autocompleteAction = getKeyBindingsManager().getAutocompleteAction(event);
|
||||
const accessibilityAction = getKeyBindingsManager().getAccessibilityAction(event);
|
||||
if (model.autoComplete?.hasCompletions()) {
|
||||
const autoComplete = model.autoComplete;
|
||||
switch (autocompleteAction) {
|
||||
|
@ -509,7 +510,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
|
|||
// there is no current autocomplete window, try to open it
|
||||
this.tabCompleteName();
|
||||
handled = true;
|
||||
} else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) {
|
||||
} else if ([KeyBindingAction.Delete, KeyBindingAction.Backspace].includes(accessibilityAction)) {
|
||||
this.formatBarRef.current.hide();
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
|
|||
import { Action } from "../../../dispatcher/actions";
|
||||
import NotificationBadge from "./NotificationBadge";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||
import { polyfillTouchEvent } from "../../../@types/polyfill";
|
||||
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
||||
|
@ -503,14 +502,15 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||
switch (ev.key) {
|
||||
// On ARROW_LEFT go to the sublist header
|
||||
case Key.ARROW_LEFT:
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (action) {
|
||||
// On ArrowLeft go to the sublist header
|
||||
case KeyBindingAction.ArrowLeft:
|
||||
ev.stopPropagation();
|
||||
this.headerButton.current.focus();
|
||||
break;
|
||||
// Consume ARROW_RIGHT so it doesn't cause focus to get sent to composer
|
||||
case Key.ARROW_RIGHT:
|
||||
// Consume ArrowRight so it doesn't cause focus to get sent to composer
|
||||
case KeyBindingAction.ArrowRight:
|
||||
ev.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -25,7 +25,6 @@ import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleBu
|
|||
import dis from '../../../dispatcher/dispatcher';
|
||||
import defaultDispatcher from '../../../dispatcher/dispatcher';
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import ActiveRoomObserver from "../../../ActiveRoomObserver";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
|
||||
|
@ -54,6 +53,8 @@ import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/Community
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import PosthogTrackers from "../../../PosthogTrackers";
|
||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -240,11 +241,14 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
private onTileClick = (ev: React.KeyboardEvent) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
|
||||
dis.dispatch<ViewRoomPayload>({
|
||||
action: Action.ViewRoom,
|
||||
show_room_tile: true, // make sure the room is visible in the list
|
||||
room_id: this.props.room.roomId,
|
||||
clear_search: (ev && (ev.key === Key.ENTER || ev.key === Key.SPACE)),
|
||||
clear_search: [KeyBindingAction.Enter, KeyBindingAction.Space].includes(action),
|
||||
metricsTrigger: "RoomList",
|
||||
metricsViaKeyboard: ev.type !== "click",
|
||||
});
|
||||
|
@ -313,9 +317,12 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
logger.warn(`Unexpected tag ${tagId} applied to ${this.props.room.roomId}`);
|
||||
}
|
||||
|
||||
if ((ev as React.KeyboardEvent).key === Key.ENTER) {
|
||||
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
||||
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
||||
this.setState({ generalMenuPosition: null }); // hide the menu
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -387,10 +394,12 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
|
||||
this.roomProps.notificationVolume = newState;
|
||||
|
||||
const key = (ev as React.KeyboardEvent).key;
|
||||
if (key === Key.ENTER) {
|
||||
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
||||
this.setState({ notificationsMenuPosition: null }); // hide the menu
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev as React.KeyboardEvent);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
// Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12
|
||||
this.setState({ notificationsMenuPosition: null }); // hide the menu
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,10 +20,11 @@ import classNames from "classnames";
|
|||
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { Key } from "../../../Keyboard";
|
||||
import DesktopBuildsNotice, { WarningKind } from "../elements/DesktopBuildsNotice";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { PosthogScreenTracker } from '../../../PosthogTrackers';
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
interface IProps {
|
||||
onCancelClick: () => void;
|
||||
|
@ -61,11 +62,12 @@ export default class SearchBar extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private onSearchChange = (e: React.KeyboardEvent) => {
|
||||
switch (e.key) {
|
||||
case Key.ENTER:
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
this.onSearch();
|
||||
break;
|
||||
case Key.ESCAPE:
|
||||
case KeyBindingAction.Escape:
|
||||
this.props.onCancelClick();
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -18,10 +18,11 @@ import React from 'react';
|
|||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { ActionPayload } from '../../../dispatcher/payloads';
|
||||
import Spinner from "../elements/Spinner";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
interface IProps {
|
||||
// false to display an error saying that we couldn't connect to the integration manager
|
||||
|
@ -65,10 +66,13 @@ export default class IntegrationManager extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
private onKeyDown = (ev: KeyboardEvent): void => {
|
||||
if (ev.key === Key.ESCAPE) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.props.onFinished();
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Escape:
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.props.onFinished();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ const getKeyboardShortcutValue = (name: string): KeyBindingConfig => {
|
|||
return getKeyboardShortcuts()[name]?.default;
|
||||
};
|
||||
|
||||
const getKeyboardShortcutDisplayName = (name: string): string => {
|
||||
const keyboardShortcutDisplayName = getKeyboardShortcuts()[name]?.displayName as string;
|
||||
const getKeyboardShortcutDisplayName = (name: string): string | null => {
|
||||
const keyboardShortcutDisplayName = getKeyboardShortcuts()[name]?.displayName;
|
||||
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName);
|
||||
};
|
||||
|
||||
|
@ -93,8 +93,11 @@ const visibleCategories = Object.entries(CATEGORIES).filter(([categoryName]) =>
|
|||
categoryName !== CategoryName.LABS || SdkConfig.get()['showLabsSettings']);
|
||||
|
||||
const KeyboardShortcutRow: React.FC<IKeyboardShortcutRowProps> = ({ name }) => {
|
||||
const displayName = getKeyboardShortcutDisplayName(name);
|
||||
if (!displayName) return null;
|
||||
|
||||
return <div className="mx_KeyboardShortcut_shortcutRow">
|
||||
{ getKeyboardShortcutDisplayName(name) }
|
||||
{ displayName }
|
||||
<KeyboardShortcut name={name} />
|
||||
</div>;
|
||||
};
|
||||
|
@ -105,6 +108,8 @@ interface IKeyboardShortcutSectionProps {
|
|||
}
|
||||
|
||||
const KeyboardShortcutSection: React.FC<IKeyboardShortcutSectionProps> = ({ categoryName, category }) => {
|
||||
if (!category.categoryLabel) return null;
|
||||
|
||||
return <div className="mx_SettingsTab_section" key={categoryName}>
|
||||
<div className="mx_SettingsTab_subheading">{ _t(category.categoryLabel) }</div>
|
||||
<div> { category.settingNames.map((shortcutName) => {
|
||||
|
|
|
@ -38,7 +38,8 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { UserTab } from "../dialogs/UserSettingsDialog";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||
|
||||
export const createSpace = async (
|
||||
name: string,
|
||||
|
@ -159,8 +160,11 @@ export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
|
|||
const domain = cli.getDomain();
|
||||
|
||||
const onKeyDown = (ev: KeyboardEvent) => {
|
||||
if (ev.key === Key.ENTER) {
|
||||
onSubmit(ev);
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||
switch (action) {
|
||||
case KeyBindingAction.Enter:
|
||||
onSubmit(ev);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3404,6 +3404,7 @@
|
|||
"[number]": "[number]",
|
||||
"Calls": "Calls",
|
||||
"Room List": "Room List",
|
||||
"Accessibility": "Accessibility",
|
||||
"Navigation": "Navigation",
|
||||
"Autocomplete": "Autocomplete",
|
||||
"Toggle Bold": "Toggle Bold",
|
||||
|
@ -3451,9 +3452,9 @@
|
|||
"Next recently visited room or community": "Next recently visited room or community",
|
||||
"Switch to space by number": "Switch to space by number",
|
||||
"Open user settings": "Open user settings",
|
||||
"Close dialog or context menu": "Close dialog or context menu",
|
||||
"Activate selected button": "Activate selected button",
|
||||
"New line": "New line",
|
||||
"Force complete": "Force complete",
|
||||
"Search (must be enabled)": "Search (must be enabled)",
|
||||
"Close dialog or context menu": "Close dialog or context menu",
|
||||
"Activate selected button": "Activate selected button"
|
||||
"Search (must be enabled)": "Search (must be enabled)"
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import { findById } from '../../../test-utils';
|
|||
import { SettingLevel } from '../../../../src/settings/SettingLevel';
|
||||
import dis from '../../../../src/dispatcher/dispatcher';
|
||||
import { Action } from '../../../../src/dispatcher/actions';
|
||||
import PlatformPeg from "../../../../src/PlatformPeg";
|
||||
|
||||
jest.mock('../../../../src/theme');
|
||||
jest.mock('../../../../src/components/views/settings/ThemeChoicePanel', () => ({
|
||||
|
@ -44,6 +45,8 @@ jest.mock('../../../../src/dispatcher/dispatcher', () => ({
|
|||
register: jest.fn(),
|
||||
}));
|
||||
|
||||
PlatformPeg.get = () => ({ overrideBrowserShortcuts: () => false });
|
||||
|
||||
describe('<QuickThemeSwitcher />', () => {
|
||||
const defaultProps = {
|
||||
requestClose: jest.fn(),
|
||||
|
|
Loading…
Reference in New Issue