Merge pull request #6239 from matrix-org/t3chguy/fix/17605

This commit is contained in:
Michael Telatynski 2021-06-22 22:56:49 +01:00 committed by GitHub
commit d5581f00cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 91 additions and 40 deletions

View file

@ -62,6 +62,8 @@ export default function AccessibleButton({
disabled, disabled,
inputRef, inputRef,
className, className,
onKeyDown,
onKeyUp,
...restProps ...restProps
}: IProps) { }: IProps) {
const newProps: IAccessibleButtonProps = restProps; const newProps: IAccessibleButtonProps = restProps;
@ -83,6 +85,8 @@ export default function AccessibleButton({
if (e.key === Key.SPACE) { if (e.key === Key.SPACE) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} else {
onKeyDown?.(e);
} }
}; };
newProps.onKeyUp = (e) => { newProps.onKeyUp = (e) => {
@ -94,6 +98,8 @@ export default function AccessibleButton({
if (e.key === Key.ENTER) { if (e.key === Key.ENTER) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} else {
onKeyUp?.(e);
} }
}; };
} }

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { InputHTMLAttributes, LegacyRef } from "react"; import React, { createRef, InputHTMLAttributes, LegacyRef } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
@ -22,7 +22,6 @@ import RoomAvatar from "../avatars/RoomAvatar";
import SpaceStore from "../../../stores/SpaceStore"; import SpaceStore from "../../../stores/SpaceStore";
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore"; import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
import NotificationBadge from "../rooms/NotificationBadge"; import NotificationBadge from "../rooms/NotificationBadge";
import { RovingAccessibleButton } from "../../../accessibility/roving/RovingAccessibleButton";
import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton"; import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton";
import IconizedContextMenu, { import IconizedContextMenu, {
IconizedContextMenuOption, IconizedContextMenuOption,
@ -48,6 +47,7 @@ import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventType } from "matrix-js-sdk/src/@types/event";
import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
interface IItemProps extends InputHTMLAttributes<HTMLLIElement> { interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
space?: Room; space?: Room;
@ -62,11 +62,14 @@ interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
interface IItemState { interface IItemState {
collapsed: boolean; collapsed: boolean;
contextMenuPosition: Pick<DOMRect, "right" | "top" | "height">; contextMenuPosition: Pick<DOMRect, "right" | "top" | "height">;
childSpaces: Room[];
} }
export class SpaceItem extends React.PureComponent<IItemProps, IItemState> { export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
static contextType = MatrixClientContext; static contextType = MatrixClientContext;
private buttonRef = createRef<HTMLDivElement>();
constructor(props) { constructor(props) {
super(props); super(props);
@ -79,14 +82,36 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
this.state = { this.state = {
collapsed: collapsed, collapsed: collapsed,
contextMenuPosition: null, contextMenuPosition: null,
childSpaces: this.childSpaces,
}; };
SpaceStore.instance.on(this.props.space.roomId, this.onSpaceUpdate);
} }
private toggleCollapse(evt) { componentWillUnmount() {
if (this.props.onExpand && this.state.collapsed) { SpaceStore.instance.off(this.props.space.roomId, this.onSpaceUpdate);
}
private onSpaceUpdate = () => {
this.setState({
childSpaces: this.childSpaces,
});
};
private get childSpaces() {
return SpaceStore.instance.getChildSpaces(this.props.space.roomId)
.filter(s => !this.props.parents?.has(s.roomId));
}
private get isCollapsed() {
return this.state.collapsed || this.props.isPanelCollapsed;
}
private toggleCollapse = evt => {
if (this.props.onExpand && this.isCollapsed) {
this.props.onExpand(); this.props.onExpand();
} }
const newCollapsedState = !this.state.collapsed; const newCollapsedState = !this.isCollapsed;
SpaceTreeLevelLayoutStore.instance.setSpaceCollapsedState( SpaceTreeLevelLayoutStore.instance.setSpaceCollapsedState(
this.props.space.roomId, this.props.space.roomId,
@ -97,7 +122,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
// don't bubble up so encapsulating button for space // don't bubble up so encapsulating button for space
// doesn't get triggered // doesn't get triggered
evt.stopPropagation(); evt.stopPropagation();
} };
private onContextMenu = (ev: React.MouseEvent) => { private onContextMenu = (ev: React.MouseEvent) => {
if (this.props.space.getMyMembership() !== "join") return; if (this.props.space.getMyMembership() !== "join") return;
@ -112,6 +137,43 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
}); });
} }
private onKeyDown = (ev: React.KeyboardEvent) => {
let handled = true;
const action = getKeyBindingsManager().getRoomListAction(ev);
const hasChildren = this.state.childSpaces?.length;
switch (action) {
case RoomListAction.CollapseSection:
if (hasChildren && !this.isCollapsed) {
this.toggleCollapse(ev);
} else {
const parentItem = this.buttonRef?.current?.parentElement?.parentElement;
const parentButton = parentItem?.previousElementSibling as HTMLElement;
parentButton?.focus();
}
break;
case RoomListAction.ExpandSection:
if (hasChildren) {
if (this.isCollapsed) {
this.toggleCollapse(ev);
} else {
const childLevel = this.buttonRef?.current?.nextElementSibling;
const firstSpaceItemChild = childLevel?.querySelector<HTMLLIElement>(".mx_SpaceItem");
firstSpaceItemChild?.querySelector<HTMLDivElement>(".mx_SpaceButton")?.focus();
}
}
break;
default:
handled = false;
}
if (handled) {
ev.stopPropagation();
ev.preventDefault();
}
};
private onClick = (ev: React.MouseEvent) => { private onClick = (ev: React.MouseEvent) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
@ -305,16 +367,14 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
const { space, activeSpaces, isNested, isPanelCollapsed, onExpand, parents, innerRef, const { space, activeSpaces, isNested, isPanelCollapsed, onExpand, parents, innerRef,
...otherProps } = this.props; ...otherProps } = this.props;
const collapsed = this.state.collapsed || isPanelCollapsed; const collapsed = this.isCollapsed;
const childSpaces = SpaceStore.instance.getChildSpaces(space.roomId)
.filter(s => !parents?.has(s.roomId));
const isActive = activeSpaces.includes(space); const isActive = activeSpaces.includes(space);
const itemClasses = classNames(this.props.className, { const itemClasses = classNames(this.props.className, {
"mx_SpaceItem": true, "mx_SpaceItem": true,
"mx_SpaceItem_narrow": isPanelCollapsed, "mx_SpaceItem_narrow": isPanelCollapsed,
"collapsed": collapsed, "collapsed": collapsed,
"hasSubSpaces": childSpaces && childSpaces.length, "hasSubSpaces": this.state.childSpaces?.length,
}); });
const isInvite = space.getMyMembership() === "invite"; const isInvite = space.getMyMembership() === "invite";
@ -329,9 +389,9 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
: SpaceStore.instance.getNotificationState(space.roomId); : SpaceStore.instance.getNotificationState(space.roomId);
let childItems; let childItems;
if (childSpaces && !collapsed) { if (this.state.childSpaces?.length && !collapsed) {
childItems = <SpaceTreeLevel childItems = <SpaceTreeLevel
spaces={childSpaces} spaces={this.state.childSpaces}
activeSpaces={activeSpaces} activeSpaces={activeSpaces}
isNested={true} isNested={true}
parents={new Set(parents).add(space.roomId)} parents={new Set(parents).add(space.roomId)}
@ -347,53 +407,36 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
const avatarSize = isNested ? 24 : 32; const avatarSize = isNested ? 24 : 32;
const toggleCollapseButton = childSpaces && childSpaces.length ? const toggleCollapseButton = this.state.childSpaces?.length ?
<AccessibleButton <AccessibleButton
className="mx_SpaceButton_toggleCollapse" className="mx_SpaceButton_toggleCollapse"
onClick={evt => this.toggleCollapse(evt)} onClick={this.toggleCollapse}
tabIndex={-1}
aria-label={collapsed ? _t("Expand") : _t("Collapse")}
/> : null; /> : null;
let button; return (
if (isPanelCollapsed) { <li {...otherProps} className={itemClasses} ref={innerRef}>
button = (
<RovingAccessibleTooltipButton <RovingAccessibleTooltipButton
className={classes} className={classes}
title={space.name} title={space.name}
onClick={this.onClick} onClick={this.onClick}
onContextMenu={this.onContextMenu} onContextMenu={this.onContextMenu}
forceHide={!!this.state.contextMenuPosition} forceHide={!isPanelCollapsed || !!this.state.contextMenuPosition}
role="treeitem" role="treeitem"
aria-expanded={!collapsed}
inputRef={this.buttonRef}
onKeyDown={this.onKeyDown}
> >
{ toggleCollapseButton } { toggleCollapseButton }
<div className="mx_SpaceButton_selectionWrapper"> <div className="mx_SpaceButton_selectionWrapper">
<RoomAvatar width={avatarSize} height={avatarSize} room={space} /> <RoomAvatar width={avatarSize} height={avatarSize} room={space} />
{ !isPanelCollapsed && <span className="mx_SpaceButton_name">{ space.name }</span> }
{ notifBadge } { notifBadge }
{ this.renderContextMenu() } { this.renderContextMenu() }
</div> </div>
</RovingAccessibleTooltipButton> </RovingAccessibleTooltipButton>
);
} else {
button = (
<RovingAccessibleButton
className={classes}
onClick={this.onClick}
onContextMenu={this.onContextMenu}
role="treeitem"
>
{ toggleCollapseButton }
<div className="mx_SpaceButton_selectionWrapper">
<RoomAvatar width={avatarSize} height={avatarSize} room={space} />
<span className="mx_SpaceButton_name">{ space.name }</span>
{ notifBadge }
{ this.renderContextMenu() }
</div>
</RovingAccessibleButton>
);
}
return (
<li {...otherProps} className={itemClasses} ref={innerRef}>
{ button }
{ childItems } { childItems }
</li> </li>
); );

View file

@ -1067,6 +1067,8 @@
"Manage & explore rooms": "Manage & explore rooms", "Manage & explore rooms": "Manage & explore rooms",
"Explore rooms": "Explore rooms", "Explore rooms": "Explore rooms",
"Space options": "Space options", "Space options": "Space options",
"Expand": "Expand",
"Collapse": "Collapse",
"Remove": "Remove", "Remove": "Remove",
"This bridge was provisioned by <user />.": "This bridge was provisioned by <user />.", "This bridge was provisioned by <user />.": "This bridge was provisioned by <user />.",
"This bridge is managed by <user />.": "This bridge is managed by <user />.", "This bridge is managed by <user />.": "This bridge is managed by <user />.",