From e2827b40825c1f96b09c85e2e3ded0b0e5b771cb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 21 Feb 2022 15:46:13 +0000 Subject: [PATCH] Fix accessibility around the room list treeview and new search beta (#7856) --- res/css/views/dialogs/_SpotlightDialog.scss | 9 ++--- src/accessibility/KeyboardShortcuts.ts | 4 +-- src/components/structures/RoomSearch.tsx | 29 ++++++++-------- src/components/views/dialogs/BaseDialog.tsx | 33 ++++++++++++------- .../views/dialogs/SpotlightDialog.tsx | 27 ++++++++------- src/i18n/strings/en_EN.json | 3 +- 6 files changed, 60 insertions(+), 45 deletions(-) diff --git a/res/css/views/dialogs/_SpotlightDialog.scss b/res/css/views/dialogs/_SpotlightDialog.scss index 62df1b143c..31358ad8a3 100644 --- a/res/css/views/dialogs/_SpotlightDialog.scss +++ b/res/css/views/dialogs/_SpotlightDialog.scss @@ -20,9 +20,9 @@ limitations under the License. position: relative; height: 60%; padding: 0; - contain: unset; // needed for .mx_SpotlightDialog_keyboardPrompt to not be culled + contain: unset; // needed for #mx_SpotlightDialog_keyboardPrompt to not be culled - .mx_SpotlightDialog_keyboardPrompt { + #mx_SpotlightDialog_keyboardPrompt { position: absolute; padding: 8px; border-radius: 8px; @@ -146,8 +146,9 @@ limitations under the License. text-overflow: ellipsis; overflow: hidden; - .mx_SpotlightDialog_metaspaceResult, - .mx_DecoratedRoomAvatar { + > .mx_SpotlightDialog_metaspaceResult, + > .mx_DecoratedRoomAvatar, + > .mx_BaseAvatar { margin-right: 8px; width: 20px; height: 20px; diff --git a/src/accessibility/KeyboardShortcuts.ts b/src/accessibility/KeyboardShortcuts.ts index 85a9afbc46..254aedb89e 100644 --- a/src/accessibility/KeyboardShortcuts.ts +++ b/src/accessibility/KeyboardShortcuts.ts @@ -404,13 +404,13 @@ export const KEYBOARD_SHORTCUTS: IKeyboardShortcuts = { default: { key: Key.ARROW_DOWN, }, - displayName: _td("Navigate up in the room list"), + displayName: _td("Navigate down in the room list"), }, [KeyBindingAction.PrevRoom]: { default: { key: Key.ARROW_UP, }, - displayName: _td("Navigate down in the room list"), + displayName: _td("Navigate up in the room list"), }, [KeyBindingAction.ToggleUserMenu]: { default: { diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx index c7854c7453..5591e382ea 100644 --- a/src/components/structures/RoomSearch.tsx +++ b/src/components/structures/RoomSearch.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import * as React from "react"; -import { createRef } from "react"; +import { createRef, RefObject } from "react"; import classNames from "classnames"; import defaultDispatcher from "../../dispatcher/dispatcher"; @@ -54,7 +54,7 @@ interface IState { export default class RoomSearch extends React.PureComponent { private readonly dispatcherRef: string; private readonly betaRef: string; - private inputRef: React.RefObject = createRef(); + private elementRef: React.RefObject = createRef(); private searchFilter: NameFilterCondition = new NameFilterCondition(); constructor(props: IProps) { @@ -113,13 +113,17 @@ export default class RoomSearch extends React.PureComponent { if (payload.action === Action.ViewRoom && payload.clear_search) { this.clearInput(); } else if (payload.action === 'focus_room_filter') { - this.focus(); + if (this.state.spotlightBetaEnabled) { + this.openSpotlight(); + } else { + this.focus(); + } } }; private clearInput = () => { - if (!this.inputRef.current) return; - this.inputRef.current.value = ""; + if (this.elementRef.current?.tagName !== "INPUT") return; + (this.elementRef.current as HTMLInputElement).value = ""; this.onChange(); }; @@ -133,8 +137,8 @@ export default class RoomSearch extends React.PureComponent { }; private onChange = () => { - if (!this.inputRef.current) return; - this.setState({ query: this.inputRef.current.value }); + if (this.elementRef.current?.tagName !== "INPUT") return; + this.setState({ query: (this.elementRef.current as HTMLInputElement).value }); }; private onFocus = (ev: React.FocusEvent) => { @@ -167,11 +171,7 @@ export default class RoomSearch extends React.PureComponent { }; public focus = (): void => { - if (this.state.spotlightBetaEnabled) { - this.openSpotlight(); - } else { - this.inputRef.current?.focus(); - } + this.elementRef.current?.focus(); }; public render(): React.ReactNode { @@ -195,7 +195,7 @@ export default class RoomSearch extends React.PureComponent { let input = ( } className={inputClasses} value={this.state.query} onFocus={this.onFocus} @@ -217,7 +217,7 @@ export default class RoomSearch extends React.PureComponent { } if (this.state.spotlightBetaEnabled) { - return + return { icon } { input &&
{ _t("Search") } @@ -229,6 +229,7 @@ export default class RoomSearch extends React.PureComponent { onClick={this.openSearch} className="mx_RoomSearch mx_RoomSearch_minimized" title={_t("Filter rooms and people")} + inputRef={this.elementRef} > { icon } ; diff --git a/src/components/views/dialogs/BaseDialog.tsx b/src/components/views/dialogs/BaseDialog.tsx index bbb9266084..544207151d 100644 --- a/src/components/views/dialogs/BaseDialog.tsx +++ b/src/components/views/dialogs/BaseDialog.tsx @@ -52,6 +52,8 @@ interface IProps extends IDialogProps { // Title for the dialog. title?: JSX.Element | string; + // Specific aria label to use, if not provided will set aria-labelledBy to mx_Dialog_title + "aria-label"?: string; // Path to an icon to put in the header headerImage?: string; @@ -121,23 +123,30 @@ export default class BaseDialog extends React.Component { headerImage = ; } + const lockProps = { + "onKeyDown": this.onKeyDown, + "role": "dialog", + // This should point to a node describing the dialog. + // If we were about to completely follow this recommendation we'd need to + // make all the components relying on BaseDialog to be aware of it. + // So instead we will use the whole content as the description. + // Description comes first and if the content contains more text, + // AT users can skip its presentation. + "aria-describedby": this.props.contentId, + }; + + if (this.props["aria-label"]) { + lockProps["aria-label"] = this.props["aria-label"]; + } else { + lockProps["aria-labelledby"] = "mx_BaseDialog_title"; + } + return ( > = ({ inputRef, ...props }) => { +const Option: React.FC> = ({ inputRef, children, ...props }) => { const [onFocus, isActive, ref] = useRovingTabIndex(inputRef); return > = ({ input tabIndex={-1} aria-selected={isActive} role="option" - />; + > + { children } +
+
; }; const TooltipOption: React.FC> = ({ inputRef, ...props }) => { @@ -357,7 +360,6 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => { result.room.name } -
); } @@ -372,7 +374,6 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => { otherResult.avatar } { otherResult.name } { otherResult.description } -
); }; @@ -431,7 +432,6 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => { room.name && room.canonical_alias &&
{ room.canonical_alias }
} -
)) } { spaceResultsLoading && } @@ -463,7 +463,6 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => { _t("Join %(roomAddress)s", { roomAddress: trimmedQuery, }) } -
; @@ -490,7 +489,6 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => }} > { _t("Public rooms") } -
@@ -544,7 +541,6 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => { room.name } -
)) } @@ -590,7 +586,6 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => }} > { _t("Explore public rooms") } -
@@ -679,7 +674,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => const activeDescendant = rovingContext.state.activeRef?.current?.id; return <> -
+
{ _t("Use to scroll", {}, { arrows: () => <>
@@ -696,6 +691,7 @@ const SpotlightDialog: React.FC = ({ initialText = "", onFinished }) => hasCancel={false} onKeyDown={onDialogKeyDown} screenName="UnifiedSearch" + aria-label={_t("Search Dialog")} >
= ({ initialText = "", onFinished }) => onKeyDown={onKeyDown} aria-owns="mx_SpotlightDialog_content" aria-activedescendant={activeDescendant} + aria-label={_t("Search")} + aria-describedby="mx_SpotlightDialog_keyboardPrompt" />
-
+
{ content }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f1bb44f49c..49c6f30bb7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2809,6 +2809,7 @@ "Recent searches": "Recent searches", "Clear": "Clear", "Use to scroll": "Use to scroll", + "Search Dialog": "Search Dialog", "Results not as expected? Please give feedback.": "Results not as expected? Please give feedback.", "To help us prevent this in future, please send us logs.": "To help us prevent this in future, please send us logs.", "Missing session data": "Missing session data", @@ -3423,8 +3424,8 @@ "Collapse room list section": "Collapse room list section", "Expand room list section": "Expand room list section", "Clear room list filter field": "Clear room list filter field", - "Navigate up in the room list": "Navigate up in the room list", "Navigate down in the room list": "Navigate down in the room list", + "Navigate up in the room list": "Navigate up in the room list", "Toggle the top left menu": "Toggle the top left menu", "Toggle right panel": "Toggle right panel", "Open this settings tab": "Open this settings tab",