From 2de88449aa617b9ee5f68d0423c5c42fde5aeffd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 14 Oct 2019 16:08:56 +0100 Subject: [PATCH 01/11] Clean up RoomSubList from stale unused code paths Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 57 +++++++++++++----------- src/components/views/rooms/RoomList.js | 40 ++--------------- 2 files changed, 33 insertions(+), 64 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 3d09c05c43..60be1d7b34 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -57,8 +57,8 @@ const RoomSubList = createReactClass({ onHeaderClick: PropTypes.func, incomingCall: PropTypes.object, isFiltered: PropTypes.bool, - headerItems: PropTypes.node, // content shown in the sublist header extraTiles: PropTypes.arrayOf(PropTypes.node), // extra elements added beneath tiles + forceExpand: PropTypes.bool, }, getInitialState: function() { @@ -299,21 +299,20 @@ const RoomSubList = createReactClass({ render: function() { const len = this.props.list.length + this.props.extraTiles.length; const isCollapsed = this.state.hidden && !this.props.forceExpand; - if (len) { - const subListClasses = classNames({ - "mx_RoomSubList": true, - "mx_RoomSubList_hidden": isCollapsed, - "mx_RoomSubList_nonEmpty": len && !isCollapsed, - }); + const subListClasses = classNames({ + "mx_RoomSubList": true, + "mx_RoomSubList_hidden": len && isCollapsed, + "mx_RoomSubList_nonEmpty": len && !isCollapsed, + }); + + let content; + if (len) { if (isCollapsed) { - return
- {this._getHeaderJsx(isCollapsed)} -
; + // no body } else if (this._canUseLazyListRendering()) { - return
- {this._getHeaderJsx(isCollapsed)} - + content = ( + -
; + ); } else { const roomTiles = this.props.list.map(r => this.makeRoomTile(r)); const tiles = roomTiles.concat(this.props.extraTiles); - return
- {this._getHeaderJsx(isCollapsed)} - + content = ( + { tiles } -
; + ); } } else { - const Loader = sdk.getComponent("elements.Spinner"); - let content; if (this.props.showSpinner && !isCollapsed) { + const Loader = sdk.getComponent("elements.Spinner"); content = ; } - - return ( -
- { this._getHeaderJsx(isCollapsed) } - { content } -
- ); } + + return ( +
+ { this._getHeaderJsx(isCollapsed) } + { content } +
+ ); }, }); diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index 6c031563cd..d8092eae22 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -49,21 +49,6 @@ function labelForTagName(tagName) { return tagName; } -function phraseForSection(section) { - switch (section) { - case 'm.favourite': - return _t('Drop here to favourite'); - case 'im.vector.fake.direct': - return _t('Drop here to tag direct chat'); - case 'im.vector.fake.recent': - return _t('Drop here to restore'); - case 'm.lowpriority': - return _t('Drop here to demote'); - default: - return _t('Drop here to tag %(section)s', {section: section}); - } -} - module.exports = createReactClass({ displayName: 'RoomList', @@ -203,7 +188,7 @@ module.exports = createReactClass({ this.resizer.setClassNames({ handle: "mx_ResizeHandle", vertical: "mx_ResizeHandle_vertical", - reverse: "mx_ResizeHandle_reverse" + reverse: "mx_ResizeHandle_reverse", }); this._layout.update( this._layoutSections, @@ -584,23 +569,6 @@ module.exports = createReactClass({ } }, - _getHeaderItems: function(section) { - const StartChatButton = sdk.getComponent('elements.StartChatButton'); - const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton'); - const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton'); - switch (section) { - case 'im.vector.fake.direct': - return - - ; - case 'im.vector.fake.recent': - return - - - ; - } - }, - _makeGroupInviteTiles(filter) { const ret = []; const lcFilter = filter && filter.toLowerCase(); @@ -676,7 +644,7 @@ module.exports = createReactClass({ props = Object.assign({}, defaultProps, props); const isLast = i === subListsProps.length - 1; const len = props.list.length + (props.extraTiles ? props.extraTiles.length : 0); - const {key, label, onHeaderClick, ... otherProps} = props; + const {key, label, onHeaderClick, ...otherProps} = props; const chosenKey = key || label; const onSubListHeaderClick = (collapsed) => { this._handleCollapsedState(chosenKey, collapsed); @@ -746,16 +714,14 @@ module.exports = createReactClass({ list: this.state.lists['im.vector.fake.direct'], label: _t('People'), tagName: "im.vector.fake.direct", - headerItems: this._getHeaderItems('im.vector.fake.direct'), order: "recent", incomingCall: incomingCallIfTaggedAs('im.vector.fake.direct'), - onAddRoom: () => {dis.dispatch({action: 'view_create_chat'})}, + onAddRoom: () => {dis.dispatch({action: 'view_create_chat'});}, addRoomLabel: _t("Start chat"), }, { list: this.state.lists['im.vector.fake.recent'], label: _t('Rooms'), - headerItems: this._getHeaderItems('im.vector.fake.recent'), order: "recent", incomingCall: incomingCallIfTaggedAs('im.vector.fake.recent'), onAddRoom: () => {dis.dispatch({action: 'view_create_room'});}, From 3400808f6ef6497968c32561e925f8246ae878e1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 15:53:39 +0100 Subject: [PATCH 02/11] Use navigation treeview aria pattern for roomlist sublists and tiles Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_RoomTile.scss | 2 + src/Keyboard.js | 2 + src/components/structures/LeftPanel.js | 1 + src/components/structures/RoomSubList.js | 74 +++++++++++++++---- .../views/elements/AccessibleButton.js | 15 +++- src/components/views/rooms/RoomList.js | 2 +- src/components/views/rooms/RoomTile.js | 3 +- src/i18n/strings/en_EN.json | 7 +- 8 files changed, 81 insertions(+), 25 deletions(-) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 2acddc233c..1814919b61 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -143,6 +143,8 @@ limitations under the License. // toggle menuButton and badge on hover/menu displayed .mx_RoomTile_menuDisplayed, +// or on keyboard focus of room tile +.mx_RoomTile.focus-visible:focus-within, .mx_LeftPanel_container:not(.collapsed) .mx_RoomTile:hover { .mx_RoomTile_menuButton { display: block; diff --git a/src/Keyboard.js b/src/Keyboard.js index 738da478e4..f63956777f 100644 --- a/src/Keyboard.js +++ b/src/Keyboard.js @@ -69,6 +69,8 @@ export const Key = { BACKSPACE: "Backspace", ARROW_UP: "ArrowUp", ARROW_DOWN: "ArrowDown", + ARROW_LEFT: "ArrowLeft", + ARROW_RIGHT: "ArrowRight", TAB: "Tab", ESCAPE: "Escape", ENTER: "Enter", diff --git a/src/components/structures/LeftPanel.js b/src/components/structures/LeftPanel.js index 36dd3a7a61..d1d3bb1b63 100644 --- a/src/components/structures/LeftPanel.js +++ b/src/components/structures/LeftPanel.js @@ -186,6 +186,7 @@ const LeftPanel = createReactClass({ } } while (element && !( classes.contains("mx_RoomTile") || + classes.contains("mx_RoomSubList_label") || classes.contains("mx_textinput_search"))); if (element) { diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 60be1d7b34..92b9d91e0e 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -2,6 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd Copyright 2018, 2019 New Vector Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,7 +17,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, {createRef} from 'react'; import createReactClass from 'create-react-class'; import classNames from 'classnames'; import sdk from '../../index'; @@ -25,7 +26,7 @@ import Unread from '../../Unread'; import * as RoomNotifs from '../../RoomNotifs'; import * as FormattingUtils from '../../utils/FormattingUtils'; import IndicatorScrollbar from './IndicatorScrollbar'; -import { KeyCode } from '../../Keyboard'; +import {Key, KeyCode} from '../../Keyboard'; import { Group } from 'matrix-js-sdk'; import PropTypes from 'prop-types'; import RoomTile from "../views/rooms/RoomTile"; @@ -56,7 +57,6 @@ const RoomSubList = createReactClass({ collapsed: PropTypes.bool.isRequired, // is LeftPanel collapsed? onHeaderClick: PropTypes.func, incomingCall: PropTypes.object, - isFiltered: PropTypes.bool, extraTiles: PropTypes.arrayOf(PropTypes.node), // extra elements added beneath tiles forceExpand: PropTypes.bool, }, @@ -80,6 +80,7 @@ const RoomSubList = createReactClass({ }, componentWillMount: function() { + this._headerButton = createRef(); this.dispatcherRef = dis.register(this.onAction); }, @@ -87,9 +88,9 @@ const RoomSubList = createReactClass({ dis.unregister(this.dispatcherRef); }, - // The header is collapsable if it is hidden or not stuck + // The header is collapsible if it is hidden or not stuck // The dataset elements are added in the RoomList _initAndPositionStickyHeaders method - isCollapsableOnClick: function() { + isCollapsibleOnClick: function() { const stuck = this.refs.header.dataset.stuck; if (!this.props.forceExpand && (this.state.hidden || stuck === undefined || stuck === "none")) { return true; @@ -114,8 +115,8 @@ const RoomSubList = createReactClass({ }, onClick: function(ev) { - if (this.isCollapsableOnClick()) { - // The header isCollapsable, so the click is to be interpreted as collapse and truncation logic + if (this.isCollapsibleOnClick()) { + // The header isCollapsible, so the click is to be interpreted as collapse and truncation logic const isHidden = !this.state.hidden; this.setState({hidden: isHidden}, () => { this.props.onHeaderClick(isHidden); @@ -124,6 +125,30 @@ const RoomSubList = createReactClass({ // The header is stuck, so the click is to be interpreted as a scroll to the header this.props.onHeaderClick(this.state.hidden, this.refs.header.dataset.originalPosition); } + this._headerButton.current.focus(); + }, + + onKeyDown: function(ev) { + switch (ev.key) { + case Key.TAB: + // Prevent LeftPanel handling Tab if focus is on the sublist header itself + ev.stopPropagation(); + break; + case Key.ARROW_LEFT: + ev.stopPropagation(); + if (!this.state.hidden && !this.props.forceExpand) { + this.onClick(); + } + break; + case Key.ARROW_RIGHT: + ev.stopPropagation(); + if (this.state.hidden && !this.props.forceExpand) { + this.onClick(); + } else { + // TODO go to first element in subtree + } + break; + } }, onRoomTileClick(roomId, ev) { @@ -193,6 +218,11 @@ const RoomSubList = createReactClass({ } }, + onAddRoom: function(e) { + e.stopPropagation(); + if (this.props.onAddRoom) this.props.onAddRoom(); + }, + _getHeaderJsx: function(isCollapsed) { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleTooltipButton = sdk.getComponent('elements.AccessibleTooltipButton'); @@ -209,12 +239,18 @@ const RoomSubList = createReactClass({ 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, }); if (subListNotifCount > 0) { - badge =
- { FormattingUtils.formatCount(subListNotifCount) } -
; + badge = ( + + { FormattingUtils.formatCount(subListNotifCount) } + + ); } else if (this.props.isInvite && this.props.list.length) { // no notifications but highlight anyway because this is an invite badge - badge =
{this.props.list.length}
; + badge = ( + + { this.props.list.length } + + ); } } @@ -237,7 +273,7 @@ const RoomSubList = createReactClass({ if (this.props.onAddRoom) { addRoomButton = ( @@ -255,10 +291,17 @@ const RoomSubList = createReactClass({ chevron = (
); } - const tabindex = this.props.isFiltered ? "0" : "-1"; return ( -
- +
+ { chevron } {this.props.label} { incomingCall } @@ -344,6 +387,7 @@ const RoomSubList = createReactClass({ role="group" aria-label={this.props.label} aria-expanded={!isCollapsed} + onKeyDown={this.onKeyDown} > { this._getHeaderJsx(isCollapsed) } { content } diff --git a/src/components/views/elements/AccessibleButton.js b/src/components/views/elements/AccessibleButton.js index bfc3e45246..1ccb7d0796 100644 --- a/src/components/views/elements/AccessibleButton.js +++ b/src/components/views/elements/AccessibleButton.js @@ -67,8 +67,6 @@ export default function AccessibleButton(props) { restProps.ref = restProps.inputRef; delete restProps.inputRef; - restProps.tabIndex = restProps.tabIndex || "0"; - restProps.role = restProps.role || "button"; restProps.className = (restProps.className ? restProps.className + " " : "") + "mx_AccessibleButton"; if (kind) { @@ -93,19 +91,30 @@ export default function AccessibleButton(props) { */ AccessibleButton.propTypes = { children: PropTypes.node, - inputRef: PropTypes.func, + inputRef: PropTypes.oneOfType([ + // Either a function + PropTypes.func, + // Or the instance of a DOM native element + PropTypes.shape({ current: PropTypes.instanceOf(Element) }), + ]), element: PropTypes.string, onClick: PropTypes.func.isRequired, // The kind of button, similar to how Bootstrap works. // See available classes for AccessibleButton for options. kind: PropTypes.string, + // The ARIA role + role: PropTypes.string, + // The tabIndex + tabIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), disabled: PropTypes.bool, }; AccessibleButton.defaultProps = { element: 'div', + role: 'button', + tabIndex: "0", }; AccessibleButton.displayName = "AccessibleButton"; diff --git a/src/components/views/rooms/RoomList.js b/src/components/views/rooms/RoomList.js index d8092eae22..036f50d899 100644 --- a/src/components/views/rooms/RoomList.js +++ b/src/components/views/rooms/RoomList.js @@ -771,7 +771,7 @@ module.exports = createReactClass({ const subListComponents = this._mapSubListProps(subLists); return ( -
{ subListComponents }
diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index b727abd261..1398e03b10 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -398,7 +398,8 @@ module.exports = createReactClass({ onMouseLeave={this.onMouseLeave} onContextMenu={this.onContextMenu} aria-label={ariaLabel} - role="option" + aria-selected={this.state.selected} + role="treeitem" >
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f524a22d4b..13945a9ce8 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -901,11 +901,6 @@ "Forget room": "Forget room", "Search": "Search", "Share room": "Share room", - "Drop here to favourite": "Drop here to favourite", - "Drop here to tag direct chat": "Drop here to tag direct chat", - "Drop here to restore": "Drop here to restore", - "Drop here to demote": "Drop here to demote", - "Drop here to tag %(section)s": "Drop here to tag %(section)s", "Community Invites": "Community Invites", "Invites": "Invites", "Favourites": "Favourites", @@ -1653,6 +1648,8 @@ "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", "Active call": "Active call", "There's no one else here! Would you like to invite others or stop warning about the empty room?": "There's no one else here! Would you like to invite others or stop warning about the empty room?", + "Jump to first unread room.": "Jump to first unread room.", + "Jump to first invite.": "Jump to first invite.", "Add room": "Add room", "You seem to be uploading files, are you sure you want to quit?": "You seem to be uploading files, are you sure you want to quit?", "You seem to be in a call, are you sure you want to quit?": "You seem to be in a call, are you sure you want to quit?", From 8b5d3b93f4b84fa7c6625ab58d27543387d99995 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 15:59:32 +0100 Subject: [PATCH 03/11] Prevent double read of ARIA expanded Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 92b9d91e0e..d218fdf1e8 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -386,7 +386,6 @@ const RoomSubList = createReactClass({ className={subListClasses} role="group" aria-label={this.props.label} - aria-expanded={!isCollapsed} onKeyDown={this.onKeyDown} > { this._getHeaderJsx(isCollapsed) } From f09a3b42812d8216c16f534215c3d935ddd76932 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 16:21:33 +0100 Subject: [PATCH 04/11] Fix outline on RoomSubList badges Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/structures/_RoomSubList.scss | 4 ++-- src/components/structures/RoomSubList.js | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index fc61395bf9..0e0d5c68af 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -67,7 +67,7 @@ limitations under the License. margin-left: 8px; } -.mx_RoomSubList_badge { +.mx_RoomSubList_badge > div { flex: 0 0 auto; border-radius: 8px; font-weight: 600; @@ -103,7 +103,7 @@ limitations under the License. } } -.mx_RoomSubList_badgeHighlight { +.mx_RoomSubList_badgeHighlight > div { color: $accent-fg-color; background-color: $warning-color; } diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index d218fdf1e8..bca6b8ad42 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -238,17 +238,22 @@ const RoomSubList = createReactClass({ 'mx_RoomSubList_badge': true, 'mx_RoomSubList_badgeHighlight': subListNotifHighlight, }); + // Wrap the contents in a div and apply styles to the child div so that the browser default outline works if (subListNotifCount > 0) { badge = ( - { FormattingUtils.formatCount(subListNotifCount) } +
+ { FormattingUtils.formatCount(subListNotifCount) } +
); } else if (this.props.isInvite && this.props.list.length) { // no notifications but highlight anyway because this is an invite badge badge = ( - { this.props.list.length } +
+ { this.props.list.length } +
); } From 3eef1bf87ec5ebdfbdcc71ee734b2d1598eabada Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 17:03:37 +0100 Subject: [PATCH 05/11] Fix tabbing through room sublist Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index bca6b8ad42..843be3e50b 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -128,7 +128,7 @@ const RoomSubList = createReactClass({ this._headerButton.current.focus(); }, - onKeyDown: function(ev) { + onHeaderKeyDown: function(ev) { switch (ev.key) { case Key.TAB: // Prevent LeftPanel handling Tab if focus is on the sublist header itself @@ -297,7 +297,7 @@ const RoomSubList = createReactClass({ } return ( -
+
{ this._getHeaderJsx(isCollapsed) } { content } From 1286cf287e3a50ab96f6e6f772628dfc25f64fea Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 17:09:43 +0100 Subject: [PATCH 06/11] remove TODO for now Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 843be3e50b..a3c28a74bb 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -144,8 +144,6 @@ const RoomSubList = createReactClass({ ev.stopPropagation(); if (this.state.hidden && !this.props.forceExpand) { this.onClick(); - } else { - // TODO go to first element in subtree } break; } From afe2226cb8dd3a0f23b0d119e3371189889d4f31 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 17:14:00 +0100 Subject: [PATCH 07/11] Handle ARROW_LEFT correctly on any room tile in sublist Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index a3c28a74bb..f97b0e5112 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -125,7 +125,6 @@ const RoomSubList = createReactClass({ // The header is stuck, so the click is to be interpreted as a scroll to the header this.props.onHeaderClick(this.state.hidden, this.refs.header.dataset.originalPosition); } - this._headerButton.current.focus(); }, onHeaderKeyDown: function(ev) { @@ -134,21 +133,28 @@ const RoomSubList = createReactClass({ // Prevent LeftPanel handling Tab if focus is on the sublist header itself ev.stopPropagation(); break; - case Key.ARROW_LEFT: - ev.stopPropagation(); - if (!this.state.hidden && !this.props.forceExpand) { - this.onClick(); - } - break; case Key.ARROW_RIGHT: ev.stopPropagation(); if (this.state.hidden && !this.props.forceExpand) { this.onClick(); + } else { + // TODO go to first element in subtree } break; } }, + onKeyDown: function(ev) { + // On ARROW_LEFT collapse the room sublist + if (ev.key === Key.ARROW_LEFT) { + ev.stopPropagation(); + if (!this.state.hidden && !this.props.forceExpand) { + this.onClick(); + this._headerButton.current.focus(); + } + } + }, + onRoomTileClick(roomId, ev) { dis.dispatch({ action: 'view_room', @@ -389,6 +395,7 @@ const RoomSubList = createReactClass({ className={subListClasses} role="group" aria-label={this.props.label} + onKeyDown={this.onKeyDown} > { this._getHeaderJsx(isCollapsed) } { content } From f72ff95efb25f8c5b75dd74503e862859d2a5834 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 17:30:37 +0100 Subject: [PATCH 08/11] Handle ARROW_RIGHT on group node in treelist as per aria suggestions Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 42 ++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index f97b0e5112..d9fa553782 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -133,14 +133,50 @@ const RoomSubList = createReactClass({ // Prevent LeftPanel handling Tab if focus is on the sublist header itself ev.stopPropagation(); break; - case Key.ARROW_RIGHT: + case Key.ARROW_RIGHT: { ev.stopPropagation(); if (this.state.hidden && !this.props.forceExpand) { + // sublist is collapsed, expand it this.onClick(); - } else { - // TODO go to first element in subtree + } else if (!this.props.forceExpand) { + // sublist is expanded, go to first room + let element = document.activeElement; + let descending = true; + let classes; + + do { + const child = element.firstElementChild; + const sibling = 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 && !classes.contains("mx_RoomTile")); + + if (element) { + element.focus(); + } } break; + } } }, From fe46925c00a457ef726d07803c6ff52f46c3e7a2 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 17 Oct 2019 17:49:28 +0100 Subject: [PATCH 09/11] Handle LEFT Arrow as expected by Aria Treeview Widget pattern Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomSubList.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index d9fa553782..4ff273303a 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -133,6 +133,13 @@ const RoomSubList = createReactClass({ // Prevent LeftPanel handling Tab if focus is on the sublist header itself ev.stopPropagation(); break; + case Key.ARROW_LEFT: + // On ARROW_LEFT collapse the room sublist + if (!this.state.hidden && !this.props.forceExpand) { + this.onClick(); + } + ev.stopPropagation(); + break; case Key.ARROW_RIGHT: { ev.stopPropagation(); if (this.state.hidden && !this.props.forceExpand) { @@ -181,13 +188,10 @@ const RoomSubList = createReactClass({ }, onKeyDown: function(ev) { - // On ARROW_LEFT collapse the room sublist + // On ARROW_LEFT go to the sublist header if (ev.key === Key.ARROW_LEFT) { ev.stopPropagation(); - if (!this.state.hidden && !this.props.forceExpand) { - this.onClick(); - this._headerButton.current.focus(); - } + this._headerButton.current.focus(); } }, From 2a3655e9b832ba632049d57a39e5b6dff8901c6e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 22 Oct 2019 11:10:25 +0100 Subject: [PATCH 10/11] Focus highlight room sublist label, catch right arrow and simplify code Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/structures/_RoomSubList.scss | 7 +++- src/components/structures/RoomSubList.js | 48 ++++++------------------ 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index 0e0d5c68af..39ccf9bef2 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -46,10 +46,15 @@ limitations under the License. flex-direction: row; align-items: center; flex: 0 0 auto; - margin: 0 16px; + margin: 0 8px; + padding: 0 8px; height: 36px; } +.mx_RoomSubList_labelContainer:focus-within { + background-color: $roomtile-focused-bg-color; +} + .mx_RoomSubList_label { flex: 1; cursor: pointer; diff --git a/src/components/structures/RoomSubList.js b/src/components/structures/RoomSubList.js index 4ff273303a..8054ef01be 100644 --- a/src/components/structures/RoomSubList.js +++ b/src/components/structures/RoomSubList.js @@ -147,37 +147,7 @@ const RoomSubList = createReactClass({ this.onClick(); } else if (!this.props.forceExpand) { // sublist is expanded, go to first room - let element = document.activeElement; - let descending = true; - let classes; - - do { - const child = element.firstElementChild; - const sibling = 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 && !classes.contains("mx_RoomTile")); - + const element = this.refs.subList && this.refs.subList.querySelector(".mx_RoomTile"); if (element) { element.focus(); } @@ -188,10 +158,15 @@ const RoomSubList = createReactClass({ }, onKeyDown: function(ev) { - // On ARROW_LEFT go to the sublist header - if (ev.key === Key.ARROW_LEFT) { - ev.stopPropagation(); - this._headerButton.current.focus(); + switch (ev.key) { + // On ARROW_LEFT go to the sublist header + case Key.ARROW_LEFT: + ev.stopPropagation(); + this._headerButton.current.focus(); + break; + // Consume ARROW_RIGHT so it doesn't cause focus to get sent to composer + case Key.ARROW_RIGHT: + ev.stopPropagation(); } }, @@ -348,8 +323,7 @@ const RoomSubList = createReactClass({ tabIndex={0} aria-expanded={!isCollapsed} inputRef={this._headerButton} - // cancel out role so this button behaves as the toggle-header of this group - role="none" + role="treeitem" > { chevron } {this.props.label} From ebc562878283389117cc082a21926ac243ff5659 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 22 Oct 2019 11:30:47 +0100 Subject: [PATCH 11/11] fix typo Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/structures/_RoomSubList.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index 39ccf9bef2..b39504593a 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -20,7 +20,7 @@ limitations under the License. so they ideally wouldn't affect each other. lowest category: .mx_RoomSubList flex-shrink: 10000000 - distribute size of items within the same categery by their size + distribute size of items within the same category by their size middle category: .mx_RoomSubList.resized-sized flex-shrink: 1000 applied when using the resizer, will have a max-height set to it,