Improve RovingTabIndex & Room List filtering performance (#6987)

This commit is contained in:
Michael Telatynski 2021-10-26 12:16:50 +01:00 committed by GitHub
parent 39e61c4fa3
commit 04c06b6aa8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 471 additions and 327 deletions

View file

@ -249,6 +249,8 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
let handled = true;
switch (ev.key) {
// XXX: this is imitating roving behaviour, it should really use the RovingTabIndex utils
// to inherit proper handling of unmount edge cases
case Key.TAB:
case Key.ESCAPE:
case Key.ARROW_LEFT: // close on left and right arrows too for when it is a context menu on a <Toolbar />

View file

@ -40,6 +40,7 @@ import { replaceableComponent } from "../../utils/replaceableComponent";
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore";
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
import UIStore from "../../stores/UIStore";
import { findSiblingElement, IState as IRovingTabIndexState } from "../../accessibility/RovingTabIndex";
interface IProps {
isMinimized: boolean;
@ -51,19 +52,12 @@ interface IState {
activeSpace?: Room;
}
// List of CSS classes which should be included in keyboard navigation within the room list
const cssClasses = [
"mx_RoomSearch_input",
"mx_RoomSearch_minimizedHandle", // minimized <RoomSearch />
"mx_RoomSublist_headerText",
"mx_RoomTile",
"mx_RoomSublist_showNButton",
];
@replaceableComponent("structures.LeftPanel")
export default class LeftPanel extends React.Component<IProps, IState> {
private ref: React.RefObject<HTMLDivElement> = createRef();
private listContainerRef: React.RefObject<HTMLDivElement> = createRef();
private ref = createRef<HTMLDivElement>();
private listContainerRef = createRef<HTMLDivElement>();
private roomSearchRef = createRef<RoomSearch>();
private roomListRef = createRef<RoomList>();
private focusedElement = null;
private isDoingStickyHeaders = false;
@ -283,16 +277,25 @@ export default class LeftPanel extends React.Component<IProps, IState> {
this.focusedElement = null;
};
private onKeyDown = (ev: React.KeyboardEvent) => {
private onKeyDown = (ev: React.KeyboardEvent, state?: IRovingTabIndexState) => {
if (!this.focusedElement) return;
const action = getKeyBindingsManager().getRoomListAction(ev);
switch (action) {
case RoomListAction.NextRoom:
if (!state) {
ev.stopPropagation();
ev.preventDefault();
this.roomListRef.current?.focus();
}
break;
case RoomListAction.PrevRoom:
ev.stopPropagation();
ev.preventDefault();
this.onMoveFocus(action === RoomListAction.PrevRoom);
if (state && state.activeRef === findSiblingElement(state.refs, 0)) {
ev.stopPropagation();
ev.preventDefault();
this.roomSearchRef.current?.focus();
}
break;
}
};
@ -305,45 +308,6 @@ export default class LeftPanel extends React.Component<IProps, IState> {
}
};
private onMoveFocus = (up: boolean) => {
let element = this.focusedElement;
let descending = false; // are we currently descending or ascending through the DOM tree?
let classes: DOMTokenList;
do {
const child = up ? element.lastElementChild : element.firstElementChild;
const sibling = up ? element.previousElementSibling : element.nextElementSibling;
if (descending) {
if (child) {
element = child;
} else if (sibling) {
element = sibling;
} else {
descending = false;
element = element.parentElement;
}
} else {
if (sibling) {
element = sibling;
descending = true;
} else {
element = element.parentElement;
}
}
if (element) {
classes = element.classList;
}
} while (element && (!cssClasses.some(c => classes.contains(c)) || element.offsetParent === null));
if (element) {
element.focus();
this.focusedElement = element;
}
};
private renderHeader(): React.ReactNode {
return (
<div className="mx_LeftPanel_userHeader">
@ -388,7 +352,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
>
<RoomSearch
isMinimized={this.props.isMinimized}
onKeyDown={this.onKeyDown}
ref={this.roomSearchRef}
onSelectRoom={this.selectRoom}
/>
@ -417,6 +381,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
activeSpace={this.state.activeSpace}
onResize={this.refreshStickyHeaders}
onListCollapse={this.refreshStickyHeaders}
ref={this.roomListRef}
/>;
const containerClasses = classNames({

View file

@ -32,7 +32,6 @@ import SpaceStore, { UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES } from "../.
interface IProps {
isMinimized: boolean;
onKeyDown(ev: React.KeyboardEvent): void;
/**
* @returns true if a room has been selected and the search field should be cleared
*/
@ -133,11 +132,6 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
this.clearInput();
defaultDispatcher.fire(Action.FocusSendMessageComposer);
break;
case RoomListAction.NextRoom:
case RoomListAction.PrevRoom:
// we don't handle these actions here put pass the event on to the interested party (LeftPanel)
this.props.onKeyDown(ev);
break;
case RoomListAction.SelectRoom: {
const shouldClear = this.props.onSelectRoom();
if (shouldClear) {
@ -151,6 +145,10 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
}
};
public focus(): void {
this.inputRef.current?.focus();
}
public render(): React.ReactNode {
const classes = classNames({
'mx_RoomSearch': true,