From 6cb0ac6a5010ae88b9008a0445e357a5456975b7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 6 Jul 2020 10:18:49 +0100 Subject: [PATCH] Fix checkboxes/radios in context menus should only close on ENTER, not SPACE Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/ContextMenu.js | 38 ++++++++++++++++++--- src/components/views/rooms/RoomSublist2.tsx | 4 +++ src/components/views/rooms/RoomTile2.tsx | 16 +++++++-- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/components/structures/ContextMenu.js b/src/components/structures/ContextMenu.js index c4825ca1da..76f31a1017 100644 --- a/src/components/structures/ContextMenu.js +++ b/src/components/structures/ContextMenu.js @@ -424,7 +424,18 @@ MenuItemCheckbox.propTypes = { }; // Semantic component for representing a styled role=menuitemcheckbox -export const StyledMenuItemCheckbox = ({children, label, checked=false, disabled=false, ...props}) => { +export const StyledMenuItemCheckbox = ({children, label, onChange, onClose, checked=false, disabled=false, ...props}) => { + const onKeyDown = (ev) => { + // Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12 + if (ev.key === Key.ENTER || ev.key === Key.SPACE) { + ev.preventDefault(); + ev.stopPropagation(); + onChange(ev); + if (ev.key === Key.ENTER) { + onClose(); + } + } + }; return ( { children } ); }; StyledMenuItemCheckbox.propTypes = { - ...AccessibleButton.propTypes, + ...StyledCheckbox.propTypes, label: PropTypes.string, // optional checked: PropTypes.bool.isRequired, disabled: PropTypes.bool, // optional className: PropTypes.string, // optional - onClick: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, // gets called after onChange on Key.ENTER }; // Semantic component for representing a role=menuitemradio @@ -467,7 +481,18 @@ MenuItemRadio.propTypes = { }; // Semantic component for representing a styled role=menuitemradio -export const StyledMenuItemRadio = ({children, label, checked=false, disabled=false, ...props}) => { +export const StyledMenuItemRadio = ({children, label, onChange, onClose, checked=false, disabled=false, ...props}) => { + const onKeyDown = (ev) => { + // Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12 + if (ev.key === Key.ENTER || ev.key === Key.SPACE) { + ev.preventDefault(); + ev.stopPropagation(); + onChange(ev); + if (ev.key === Key.ENTER) { + onClose(); + } + } + }; return ( { children } @@ -488,7 +515,8 @@ StyledMenuItemRadio.propTypes = { checked: PropTypes.bool.isRequired, disabled: PropTypes.bool, // optional className: PropTypes.string, // optional - onClick: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, // gets called after onChange on Key.ENTER }; // Placement method for to position context menu to right of elementRect with chevronOffset diff --git a/src/components/views/rooms/RoomSublist2.tsx b/src/components/views/rooms/RoomSublist2.tsx index bc42f2321c..9817302445 100644 --- a/src/components/views/rooms/RoomSublist2.tsx +++ b/src/components/views/rooms/RoomSublist2.tsx @@ -334,6 +334,7 @@ export default class RoomSublist2 extends React.Component {
{_t("Sort by")}
this.onTagSortChanged(SortAlgorithm.Recent)} checked={!isAlphabetical} name={`mx_${this.props.tagId}_sortBy`} @@ -341,6 +342,7 @@ export default class RoomSublist2 extends React.Component { {_t("Activity")} this.onTagSortChanged(SortAlgorithm.Alphabetic)} checked={isAlphabetical} name={`mx_${this.props.tagId}_sortBy`} @@ -352,6 +354,7 @@ export default class RoomSublist2 extends React.Component {
{_t("Unread rooms")}
@@ -362,6 +365,7 @@ export default class RoomSublist2 extends React.Component {
{_t("Show")}
diff --git a/src/components/views/rooms/RoomTile2.tsx b/src/components/views/rooms/RoomTile2.tsx index f3b22c3a64..8ee838fbba 100644 --- a/src/components/views/rooms/RoomTile2.tsx +++ b/src/components/views/rooms/RoomTile2.tsx @@ -80,6 +80,8 @@ interface IState { generalMenuPosition: PartialDOMRect; } +type Volume = ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE; + const messagePreviewId = (roomId: string) => `mx_RoomTile2_messagePreview_${roomId}`; const contextMenuBelow = (elementRect: PartialDOMRect) => { @@ -213,6 +215,11 @@ export default class RoomTile2 extends React.Component { // TODO: Support tagging: https://github.com/vector-im/riot-web/issues/14211 // TODO: XOR favourites and low priority: https://github.com/vector-im/riot-web/issues/14210 + + if ((ev as React.KeyboardEvent).key === Key.ENTER) { + // Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12 + this.setState({generalMenuPosition: null}); // hide the menu + } }; private onLeaveRoomClick = (ev: ButtonEvent) => { @@ -237,11 +244,13 @@ export default class RoomTile2 extends React.Component { this.setState({generalMenuPosition: null}); // hide the menu }; - private async saveNotifState(ev: ButtonEvent, newState: ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE) { + private async saveNotifState(ev: ButtonEvent, newState: Volume) { ev.preventDefault(); ev.stopPropagation(); if (MatrixClientPeg.get().isGuest()) return; + // get key before we go async and React discards the nativeEvent + const key = (ev as React.KeyboardEvent).key; try { // TODO add local echo - https://github.com/vector-im/riot-web/issues/14280 await setRoomNotifsState(this.props.room.roomId, newState); @@ -251,7 +260,10 @@ export default class RoomTile2 extends React.Component { console.error(error); } - this.setState({notificationsMenuPosition: null}); // Close the context menu + if (key === Key.ENTER) { + // Implements https://www.w3.org/TR/wai-aria-practices/#keyboard-interaction-12 + this.setState({notificationsMenuPosition: null}); // hide the menu + } } private onClickAllNotifs = ev => this.saveNotifState(ev, ALL_MESSAGES);