Fix accessibility regressions (#7336)

* Fix room list roving treeview

New TooltipTarget & TextWithTooltip were not roving-accessible

* Fix programmatic focus management in roving tab index not triggering onFocus handler

* Fix toolbar no longer handling left & right arrows

* Fix roving tab index focus tracking on interactive element like context menu trigger

* Fix thread list context menu roving

* add comment

* fix comment

* Fix handling vertical arrows in the wrong direction

* iterate PR

* delint

* tidy up
This commit is contained in:
Michael Telatynski 2021-12-14 14:27:35 +00:00 committed by GitHub
parent 60286f6170
commit a667677c57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 103 additions and 58 deletions

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useEffect, useState } from "react";
import React, { RefObject, useCallback, useEffect } from "react";
import { MatrixEvent } from "matrix-js-sdk/src";
import { ButtonEvent } from "../elements/AccessibleButton";
@ -22,11 +22,12 @@ import dis from '../../../dispatcher/dispatcher';
import { Action } from "../../../dispatcher/actions";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { copyPlaintext } from "../../../utils/strings";
import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextMenu";
import { ChevronFace, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
import { _t } from "../../../languageHandler";
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
interface IProps {
mxEvent: MatrixEvent;
@ -34,6 +35,13 @@ interface IProps {
onMenuToggle?: (open: boolean) => void;
}
interface IExtendedProps extends IProps {
// Props for making the button into a roving one
tabIndex?: number;
inputRef?: RefObject<HTMLElement>;
onFocus?(): void;
}
const contextMenuBelow = (elementRect: DOMRect) => {
// align the context menu's icons with the icon which opened the context menu
const left = elementRect.left + window.pageXOffset + elementRect.width;
@ -42,11 +50,27 @@ const contextMenuBelow = (elementRect: DOMRect) => {
return { left, top, chevronFace };
};
const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, onMenuToggle }) => {
const [optionsPosition, setOptionsPosition] = useState(null);
const closeThreadOptions = useCallback(() => {
setOptionsPosition(null);
}, []);
export const RovingThreadListContextMenu: React.FC<IProps> = (props) => {
const [onFocus, isActive, ref] = useRovingTabIndex();
return <ThreadListContextMenu
{...props}
onFocus={onFocus}
tabIndex={isActive ? 0 : -1}
inputRef={ref}
/>;
};
const ThreadListContextMenu: React.FC<IExtendedProps> = ({
mxEvent,
permalinkCreator,
onMenuToggle,
onFocus,
inputRef,
...props
}) => {
const [menuDisplayed, _ref, openMenu, closeThreadOptions] = useContextMenu();
const button = inputRef ?? _ref; // prefer the ref we receive via props in case we are being controlled
const viewInRoom = useCallback((evt: ButtonEvent): void => {
evt.preventDefault();
@ -68,37 +92,31 @@ const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, on
closeThreadOptions();
}, [mxEvent, closeThreadOptions, permalinkCreator]);
const toggleOptionsMenu = useCallback((ev: ButtonEvent): void => {
if (!!optionsPosition) {
closeThreadOptions();
} else {
const position = ev.currentTarget.getBoundingClientRect();
setOptionsPosition(position);
}
}, [closeThreadOptions, optionsPosition]);
useEffect(() => {
if (onMenuToggle) {
onMenuToggle(!!optionsPosition);
onMenuToggle(menuDisplayed);
}
}, [optionsPosition, onMenuToggle]);
onFocus?.();
}, [menuDisplayed, onMenuToggle, onFocus]);
const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget(
MatrixClientPeg.get().getRoom(mxEvent.getRoomId()),
);
return <React.Fragment>
<ContextMenuTooltipButton
{...props}
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
onClick={toggleOptionsMenu}
onClick={openMenu}
title={_t("Thread options")}
isExpanded={!!optionsPosition}
isExpanded={menuDisplayed}
inputRef={button}
/>
{ !!optionsPosition && (<IconizedContextMenu
{ menuDisplayed && (<IconizedContextMenu
onFinished={closeThreadOptions}
className="mx_RoomTile_contextMenu"
compact
rightAligned
{...contextMenuBelow(optionsPosition)}
{...contextMenuBelow(button.current.getBoundingClientRect())}
>
<IconizedContextMenuOptionList>
{ isMainSplitTimelineShown &&