From 92e86af162b9d37f79a0c2b1693730c8b65dd3a3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 7 Jul 2020 17:07:51 +0100 Subject: [PATCH 1/5] Show more/Show less keep focus in a relevant place Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomSublist2.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 18f8f4e2f6..9d15e64e7b 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -140,15 +140,25 @@ export default class RoomSublist2 extends React.Component { }; private onShowAllClick = () => { - // TODO a11y keep focus somewhere useful: https://github.com/vector-im/riot-web/issues/14180 + const numVisibleTiles = this.numVisibleTiles; this.props.layout.visibleTiles = this.props.layout.tilesWithPadding(this.numTiles, MAX_PADDING_HEIGHT); this.forceUpdate(); // because the layout doesn't trigger a re-render + setImmediate(this.focusRoomTile, numVisibleTiles); // focus the tile after the current bottom one }; private onShowLessClick = () => { - // TODO a11y keep focus somewhere useful: https://github.com/vector-im/riot-web/issues/14180 this.props.layout.visibleTiles = this.props.layout.defaultVisibleTiles; this.forceUpdate(); // because the layout doesn't trigger a re-render + // focus will flow to the show more button here + }; + + private focusRoomTile = (index: number) => { + if (!this.sublistRef.current) return; + const elements = this.sublistRef.current.querySelectorAll(".mx_RoomTile2"); + const element = elements && elements[index]; + if (element) { + element.focus(); + } }; private onOpenMenuClick = (ev: InputEvent) => { @@ -520,6 +530,7 @@ export default class RoomSublist2 extends React.Component { ); if (this.props.isMinimized) showMoreText = null; + // TODO Roving tab index / treeitem?: https://github.com/vector-im/riot-web/issues/14180 showNButton = ( From f18db23cc4e7138c8b5a55598430711c59bf65fe Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 7 Jul 2020 17:18:56 +0100 Subject: [PATCH 2/5] Remove some TODOs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LeftPanel2.tsx | 3 --- src/components/views/rooms/RoomSublist2.tsx | 1 - src/components/views/rooms/RoomTile2.tsx | 3 --- 3 files changed, 7 deletions(-) diff --git a/src/components/structures/LeftPanel2.tsx b/src/components/structures/LeftPanel2.tsx index 4b954d7843..b2a2384cb2 100644 --- a/src/components/structures/LeftPanel2.tsx +++ b/src/components/structures/LeftPanel2.tsx @@ -70,8 +70,6 @@ export default class LeftPanel2 extends React.Component { private tagPanelWatcherRef: string; private focusedElement = null; - // TODO: a11y: https://github.com/vector-im/riot-web/issues/14180 - constructor(props: IProps) { super(props); @@ -264,7 +262,6 @@ export default class LeftPanel2 extends React.Component { onVerticalArrow={this.onKeyDown} /> { ); - // TODO: a11y (see old component): https://github.com/vector-im/riot-web/issues/14180 // Note: the addRoomButton conditionally gets moved around // the DOM depending on whether or not the list is minimized. // If we're minimized, we want it below the header so it diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index c6cd401803..abb31a6f71 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -118,8 +118,6 @@ const NotifOption: React.FC = ({active, onClick, iconClassNam }; export default class RoomTile2 extends React.Component { - // TODO: a11y: https://github.com/vector-im/riot-web/issues/14180 - constructor(props: IProps) { super(props); @@ -390,7 +388,6 @@ export default class RoomTile2 extends React.Component { public render(): React.ReactElement { // TODO: Invites: https://github.com/vector-im/riot-web/issues/14198 - // TODO: a11y proper: https://github.com/vector-im/riot-web/issues/14180 const classes = classNames({ 'mx_RoomTile2': true, From 4edd3dfc6c9b8bc3185adaa231b82ba65fa4ea10 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 7 Jul 2020 17:46:33 +0100 Subject: [PATCH 3/5] Convert RovingTabIndex to Typescript Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../{RovingTabIndex.js => RovingTabIndex.tsx} | 87 +++++++++++++------ 1 file changed, 62 insertions(+), 25 deletions(-) rename src/accessibility/{RovingTabIndex.js => RovingTabIndex.tsx} (79%) diff --git a/src/accessibility/RovingTabIndex.js b/src/accessibility/RovingTabIndex.tsx similarity index 79% rename from src/accessibility/RovingTabIndex.js rename to src/accessibility/RovingTabIndex.tsx index b481f08fe2..32780629f2 100644 --- a/src/accessibility/RovingTabIndex.js +++ b/src/accessibility/RovingTabIndex.tsx @@ -22,9 +22,13 @@ import React, { useMemo, useRef, useReducer, + Reducer, + RefObject, + Dispatch, } from "react"; -import PropTypes from "prop-types"; + import {Key} from "../Keyboard"; +import AccessibleButton from "../components/views/elements/AccessibleButton"; /** * Module to simplify implementing the Roving TabIndex accessibility technique @@ -41,7 +45,19 @@ import {Key} from "../Keyboard"; const DOCUMENT_POSITION_PRECEDING = 2; -const RovingTabIndexContext = createContext({ +type Ref = RefObject; + +interface IState { + activeRef: Ref; + refs: Ref[]; +} + +interface IContext { + state: IState; + dispatch: Dispatch; +} + +const RovingTabIndexContext = createContext({ state: { activeRef: null, refs: [], // list of refs in DOM order @@ -50,16 +66,22 @@ const RovingTabIndexContext = createContext({ }); RovingTabIndexContext.displayName = "RovingTabIndexContext"; -// TODO use a TypeScript type here -const types = { - REGISTER: "REGISTER", - UNREGISTER: "UNREGISTER", - SET_FOCUS: "SET_FOCUS", -}; +enum Type { + Register = "REGISTER", + Unregister = "UNREGISTER", + SetFocus = "SET_FOCUS", +} -const reducer = (state, action) => { +interface IAction { + type: Type; + payload: { + ref: Ref; + }; +} + +const reducer = (state: IState, action: IAction) => { switch (action.type) { - case types.REGISTER: { + case Type.Register: { if (state.refs.length === 0) { // Our list of refs was empty, set activeRef to this first item return { @@ -92,7 +114,7 @@ const reducer = (state, action) => { ], }; } - case types.UNREGISTER: { + case Type.Unregister: { // filter out the ref which we are removing const refs = state.refs.filter(r => r !== action.payload.ref); @@ -117,7 +139,7 @@ const reducer = (state, action) => { refs, }; } - case types.SET_FOCUS: { + case Type.SetFocus: { // update active ref return { ...state, @@ -129,13 +151,21 @@ const reducer = (state, action) => { } }; -export const RovingTabIndexProvider = ({children, handleHomeEnd, onKeyDown}) => { - const [state, dispatch] = useReducer(reducer, { +interface IProps { + handleHomeEnd?: boolean; + children(renderProps: { + onKeyDownHandler(ev: React.KeyboardEvent); + }); + onKeyDown?(ev: React.KeyboardEvent); +} + +export const RovingTabIndexProvider: React.FC = ({children, handleHomeEnd, onKeyDown}) => { + const [state, dispatch] = useReducer>(reducer, { activeRef: null, refs: [], }); - const context = useMemo(() => ({state, dispatch}), [state]); + const context = useMemo(() => ({state, dispatch}), [state]); const onKeyDownHandler = useCallback((ev) => { let handled = false; @@ -171,19 +201,17 @@ export const RovingTabIndexProvider = ({children, handleHomeEnd, onKeyDown}) => { children({onKeyDownHandler}) } ; }; -RovingTabIndexProvider.propTypes = { - handleHomeEnd: PropTypes.bool, - onKeyDown: PropTypes.func, -}; + +type FocusHandler = () => void; // Hook to register a roving tab index // inputRef parameter specifies the ref to use // onFocus should be called when the index gained focus in any manner // isActive should be used to set tabIndex in a manner such as `tabIndex={isActive ? 0 : -1}` // ref should be passed to a DOM node which will be used for DOM compareDocumentPosition -export const useRovingTabIndex = (inputRef) => { +export const useRovingTabIndex = (inputRef: Ref): [FocusHandler, boolean, Ref] => { const context = useContext(RovingTabIndexContext); - let ref = useRef(null); + let ref = useRef(null); if (inputRef) { // if we are given a ref, use it instead of ours @@ -193,13 +221,13 @@ export const useRovingTabIndex = (inputRef) => { // setup (after refs) useLayoutEffect(() => { context.dispatch({ - type: types.REGISTER, + type: Type.Register, payload: {ref}, }); // teardown return () => { context.dispatch({ - type: types.UNREGISTER, + type: Type.Unregister, payload: {ref}, }); }; @@ -207,7 +235,7 @@ export const useRovingTabIndex = (inputRef) => { const onFocus = useCallback(() => { context.dispatch({ - type: types.SET_FOCUS, + type: Type.SetFocus, payload: {ref}, }); }, [ref, context]); @@ -216,8 +244,17 @@ export const useRovingTabIndex = (inputRef) => { return [onFocus, isActive, ref]; }; +interface IRovingTabIndexWrapperProps { + inputRef?: Ref; + children(renderProps: { + onFocus: FocusHandler; + isActive: boolean; + ref: Ref; + }); +} + // Wrapper to allow use of useRovingTabIndex outside of React Functional Components. -export const RovingTabIndexWrapper = ({children, inputRef}) => { +export const RovingTabIndexWrapper: React.FC = ({children, inputRef}) => { const [onFocus, isActive, ref] = useRovingTabIndex(inputRef); return children({onFocus, isActive, ref}); }; From a33717a475a74f9b4bd773169fb624de450ed509 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 7 Jul 2020 17:47:21 +0100 Subject: [PATCH 4/5] Wire up Room sublist show more/less as roving tabindex button using new helper Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/accessibility/RovingTabIndex.tsx | 10 ++++++++++ src/components/views/rooms/RoomSublist2.tsx | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/accessibility/RovingTabIndex.tsx b/src/accessibility/RovingTabIndex.tsx index 32780629f2..388d67d9f3 100644 --- a/src/accessibility/RovingTabIndex.tsx +++ b/src/accessibility/RovingTabIndex.tsx @@ -259,3 +259,13 @@ export const RovingTabIndexWrapper: React.FC = ({ch return children({onFocus, isActive, ref}); }; +interface IRovingAccessibleButtonProps extends React.ComponentProps { + inputRef?: Ref; +} + +// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components. +export const RovingAccessibleButton: React.FC = ({inputRef, ...props}) => { + const [onFocus, isActive, ref] = useRovingTabIndex(inputRef); + return ; +}; + diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 3a12e99914..56ce631604 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -20,7 +20,7 @@ import * as React from "react"; import { createRef } from "react"; import { Room } from "matrix-js-sdk/src/models/room"; import classNames from 'classnames'; -import { RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; +import {RovingAccessibleButton, RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../../views/elements/AccessibleButton"; import RoomTile2 from "./RoomTile2"; @@ -531,12 +531,12 @@ export default class RoomSublist2 extends React.Component { if (this.props.isMinimized) showMoreText = null; // TODO Roving tab index / treeitem?: https://github.com/vector-im/riot-web/issues/14180 showNButton = ( - + {/* set by CSS masking */} {showMoreText} - + ); } else if (this.numTiles <= visibleTiles.length && this.numTiles > this.props.layout.defaultVisibleTiles) { // we have all tiles visible - add a button to show less @@ -548,12 +548,12 @@ export default class RoomSublist2 extends React.Component { if (this.props.isMinimized) showLessText = null; // TODO Roving tab index / treeitem?: https://github.com/vector-im/riot-web/issues/14180 showNButton = ( - + {/* set by CSS masking */} {showLessText} - + ); } From 28310cb64848713486bf4ac05ff4c83517cf1db1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 7 Jul 2020 17:48:39 +0100 Subject: [PATCH 5/5] remove TODOs Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/RoomSublist2.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index 56ce631604..eefd29f0b7 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -529,7 +529,6 @@ export default class RoomSublist2 extends React.Component { ); if (this.props.isMinimized) showMoreText = null; - // TODO Roving tab index / treeitem?: https://github.com/vector-im/riot-web/issues/14180 showNButton = ( @@ -546,7 +545,6 @@ export default class RoomSublist2 extends React.Component { ); if (this.props.isMinimized) showLessText = null; - // TODO Roving tab index / treeitem?: https://github.com/vector-im/riot-web/issues/14180 showNButton = (