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

@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactComponentElement } from "react";
import React, { createRef, ReactComponentElement } from "react";
import { Dispatcher } from "flux";
import { Room } from "matrix-js-sdk/src/models/room";
import * as fbEmitter from "fbemitter";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { _t, _td } from "../../../languageHandler";
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
import { RovingTabIndexProvider, IState as IRovingTabIndexState } from "../../../accessibility/RovingTabIndex";
import ResizeNotifier from "../../../utils/ResizeNotifier";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import RoomViewStore from "../../../stores/RoomViewStore";
@ -54,7 +54,7 @@ import { UIComponent } from "../../../settings/UIFeature";
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
interface IProps {
onKeyDown: (ev: React.KeyboardEvent) => void;
onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void;
onFocus: (ev: React.FocusEvent) => void;
onBlur: (ev: React.FocusEvent) => void;
onResize: () => void;
@ -249,6 +249,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
private dispatcherRef;
private customTagStoreRef;
private roomStoreToken: fbEmitter.EventSubscription;
private treeRef = createRef<HTMLDivElement>();
constructor(props: IProps) {
super(props);
@ -505,6 +506,12 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
});
}
public focus(): void {
// focus the first focusable element in this aria treeview widget
[...this.treeRef.current?.querySelectorAll<HTMLElement>('[role="treeitem"]')]
.find(e => e.offsetParent !== null)?.focus();
}
public render() {
const cli = MatrixClientPeg.get();
const userId = cli.getUserId();
@ -584,7 +591,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
const sublists = this.renderSublists();
return (
<RovingTabIndexProvider handleHomeEnd={true} onKeyDown={this.props.onKeyDown}>
<RovingTabIndexProvider handleHomeEnd handleUpDown onKeyDown={this.props.onKeyDown}>
{ ({ onKeyDownHandler }) => (
<div
onFocus={this.props.onFocus}
@ -593,6 +600,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
className="mx_RoomList"
role="tree"
aria-label={_t("Rooms")}
ref={this.treeRef}
>
{ sublists }
{ explorePrompt }

View file

@ -43,7 +43,6 @@ import SpaceStore, {
} from "../../../stores/SpaceStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
import { Key } from "../../../Keyboard";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import SpaceContextMenu from "../context_menus/SpaceContextMenu";
import IconizedContextMenu, {
@ -228,75 +227,12 @@ const SpacePanel = () => {
return () => UIStore.instance.stopTrackingElementDimensions("SpacePanel");
}, []);
const onKeyDown = (ev: React.KeyboardEvent) => {
if (ev.defaultPrevented) return;
let handled = true;
switch (ev.key) {
case Key.ARROW_UP:
onMoveFocus(ev.target as Element, true);
break;
case Key.ARROW_DOWN:
onMoveFocus(ev.target as Element, false);
break;
default:
handled = false;
}
if (handled) {
// consume all other keys in context menu
ev.stopPropagation();
ev.preventDefault();
}
};
const onMoveFocus = (element: Element, up: boolean) => {
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) {
if (element.classList.contains("mx_ContextualMenu")) { // we hit the top
element = up ? element.lastElementChild : element.firstElementChild;
descending = true;
}
classes = element.classList;
}
} while (element && !classes.contains("mx_SpaceButton"));
if (element) {
(element as HTMLElement).focus();
}
};
return (
<DragDropContext onDragEnd={result => {
if (!result.destination) return; // dropped outside the list
SpaceStore.instance.moveRootSpace(result.source.index, result.destination.index);
}}>
<RovingTabIndexProvider handleHomeEnd={true} onKeyDown={onKeyDown}>
<RovingTabIndexProvider handleHomeEnd handleUpDown>
{ ({ onKeyDownHandler }) => (
<ul
className={classNames("mx_SpacePanel", { collapsed: isPanelCollapsed })}