@@ -14,15 +14,14 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17- import React , { InputHTMLAttributes , LegacyRef } from "react" ;
17+ import React , { createRef , InputHTMLAttributes , LegacyRef } from "react" ;
1818import classNames from "classnames" ;
1919import { Room } from "matrix-js-sdk/src/models/room" ;
2020
2121import RoomAvatar from "../avatars/RoomAvatar" ;
2222import SpaceStore from "../../../stores/SpaceStore" ;
2323import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore" ;
2424import NotificationBadge from "../rooms/NotificationBadge" ;
25- import { RovingAccessibleButton } from "../../../accessibility/roving/RovingAccessibleButton" ;
2625import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton" ;
2726import IconizedContextMenu , {
2827 IconizedContextMenuOption ,
@@ -48,6 +47,7 @@ import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
4847import { EventType } from "matrix-js-sdk/src/@types/event" ;
4948import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState" ;
5049import { NotificationColor } from "../../../stores/notifications/NotificationColor" ;
50+ import { getKeyBindingsManager , RoomListAction } from "../../../KeyBindingsManager" ;
5151
5252interface IItemProps extends InputHTMLAttributes < HTMLLIElement > {
5353 space ?: Room ;
@@ -62,11 +62,14 @@ interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
6262interface IItemState {
6363 collapsed : boolean ;
6464 contextMenuPosition : Pick < DOMRect , "right" | "top" | "height" > ;
65+ childSpaces : Room [ ] ;
6566}
6667
6768export class SpaceItem extends React . PureComponent < IItemProps , IItemState > {
6869 static contextType = MatrixClientContext ;
6970
71+ private buttonRef = createRef < HTMLDivElement > ( ) ;
72+
7073 constructor ( props ) {
7174 super ( props ) ;
7275
@@ -79,14 +82,36 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
7982 this . state = {
8083 collapsed : collapsed ,
8184 contextMenuPosition : null ,
85+ childSpaces : this . childSpaces ,
8286 } ;
87+
88+ SpaceStore . instance . on ( this . props . space . roomId , this . onSpaceUpdate ) ;
89+ }
90+
91+ componentWillUnmount ( ) {
92+ SpaceStore . instance . off ( this . props . space . roomId , this . onSpaceUpdate ) ;
93+ }
94+
95+ private onSpaceUpdate = ( ) => {
96+ this . setState ( {
97+ childSpaces : this . childSpaces ,
98+ } ) ;
99+ } ;
100+
101+ private get childSpaces ( ) {
102+ return SpaceStore . instance . getChildSpaces ( this . props . space . roomId )
103+ . filter ( s => ! this . props . parents ?. has ( s . roomId ) ) ;
104+ }
105+
106+ private get isCollapsed ( ) {
107+ return this . state . collapsed || this . props . isPanelCollapsed ;
83108 }
84109
85- private toggleCollapse ( evt ) {
86- if ( this . props . onExpand && this . state . collapsed ) {
110+ private toggleCollapse = evt => {
111+ if ( this . props . onExpand && this . isCollapsed ) {
87112 this . props . onExpand ( ) ;
88113 }
89- const newCollapsedState = ! this . state . collapsed ;
114+ const newCollapsedState = ! this . isCollapsed ;
90115
91116 SpaceTreeLevelLayoutStore . instance . setSpaceCollapsedState (
92117 this . props . space . roomId ,
@@ -97,7 +122,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
97122 // don't bubble up so encapsulating button for space
98123 // doesn't get triggered
99124 evt . stopPropagation ( ) ;
100- }
125+ } ;
101126
102127 private onContextMenu = ( ev : React . MouseEvent ) => {
103128 if ( this . props . space . getMyMembership ( ) !== "join" ) return ;
@@ -112,6 +137,43 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
112137 } ) ;
113138 }
114139
140+ private onKeyDown = ( ev : React . KeyboardEvent ) => {
141+ let handled = true ;
142+ const action = getKeyBindingsManager ( ) . getRoomListAction ( ev ) ;
143+ const hasChildren = this . state . childSpaces ?. length ;
144+ switch ( action ) {
145+ case RoomListAction . CollapseSection :
146+ if ( hasChildren && ! this . isCollapsed ) {
147+ this . toggleCollapse ( ev ) ;
148+ } else {
149+ const parentItem = this . buttonRef ?. current ?. parentElement ?. parentElement ;
150+ const parentButton = parentItem ?. previousElementSibling as HTMLElement ;
151+ parentButton ?. focus ( ) ;
152+ }
153+ break ;
154+
155+ case RoomListAction . ExpandSection :
156+ if ( hasChildren ) {
157+ if ( this . isCollapsed ) {
158+ this . toggleCollapse ( ev ) ;
159+ } else {
160+ const childLevel = this . buttonRef ?. current ?. nextElementSibling ;
161+ const firstSpaceItemChild = childLevel ?. querySelector < HTMLLIElement > ( ".mx_SpaceItem" ) ;
162+ firstSpaceItemChild ?. querySelector < HTMLDivElement > ( ".mx_SpaceButton" ) ?. focus ( ) ;
163+ }
164+ }
165+ break ;
166+
167+ default :
168+ handled = false ;
169+ }
170+
171+ if ( handled ) {
172+ ev . stopPropagation ( ) ;
173+ ev . preventDefault ( ) ;
174+ }
175+ } ;
176+
115177 private onClick = ( ev : React . MouseEvent ) => {
116178 ev . preventDefault ( ) ;
117179 ev . stopPropagation ( ) ;
@@ -305,16 +367,14 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
305367 const { space, activeSpaces, isNested, isPanelCollapsed, onExpand, parents, innerRef,
306368 ...otherProps } = this . props ;
307369
308- const collapsed = this . state . collapsed || isPanelCollapsed ;
370+ const collapsed = this . isCollapsed ;
309371
310- const childSpaces = SpaceStore . instance . getChildSpaces ( space . roomId )
311- . filter ( s => ! parents ?. has ( s . roomId ) ) ;
312372 const isActive = activeSpaces . includes ( space ) ;
313373 const itemClasses = classNames ( this . props . className , {
314374 "mx_SpaceItem" : true ,
315375 "mx_SpaceItem_narrow" : isPanelCollapsed ,
316376 "collapsed" : collapsed ,
317- "hasSubSpaces" : childSpaces && childSpaces . length ,
377+ "hasSubSpaces" : this . state . childSpaces ? .length ,
318378 } ) ;
319379
320380 const isInvite = space . getMyMembership ( ) === "invite" ;
@@ -329,9 +389,9 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
329389 : SpaceStore . instance . getNotificationState ( space . roomId ) ;
330390
331391 let childItems ;
332- if ( childSpaces && ! collapsed ) {
392+ if ( this . state . childSpaces ?. length && ! collapsed ) {
333393 childItems = < SpaceTreeLevel
334- spaces = { childSpaces }
394+ spaces = { this . state . childSpaces }
335395 activeSpaces = { activeSpaces }
336396 isNested = { true }
337397 parents = { new Set ( parents ) . add ( space . roomId ) }
@@ -347,53 +407,36 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
347407
348408 const avatarSize = isNested ? 24 : 32 ;
349409
350- const toggleCollapseButton = childSpaces && childSpaces . length ?
410+ const toggleCollapseButton = this . state . childSpaces ? .length ?
351411 < AccessibleButton
352412 className = "mx_SpaceButton_toggleCollapse"
353- onClick = { evt => this . toggleCollapse ( evt ) }
413+ onClick = { this . toggleCollapse }
414+ tabIndex = { - 1 }
415+ aria-label = { collapsed ? _t ( "Expand" ) : _t ( "Collapse" ) }
354416 /> : null ;
355417
356- let button ;
357- if ( isPanelCollapsed ) {
358- button = (
418+ return (
419+ < li { ...otherProps } className = { itemClasses } ref = { innerRef } >
359420 < RovingAccessibleTooltipButton
360421 className = { classes }
361422 title = { space . name }
362423 onClick = { this . onClick }
363424 onContextMenu = { this . onContextMenu }
364- forceHide = { ! ! this . state . contextMenuPosition }
425+ forceHide = { ! isPanelCollapsed || ! ! this . state . contextMenuPosition }
365426 role = "treeitem"
427+ aria-expanded = { ! collapsed }
428+ inputRef = { this . buttonRef }
429+ onKeyDown = { this . onKeyDown }
366430 >
367431 { toggleCollapseButton }
368432 < div className = "mx_SpaceButton_selectionWrapper" >
369433 < RoomAvatar width = { avatarSize } height = { avatarSize } room = { space } />
434+ { ! isPanelCollapsed && < span className = "mx_SpaceButton_name" > { space . name } </ span > }
370435 { notifBadge }
371436 { this . renderContextMenu ( ) }
372437 </ div >
373438 </ RovingAccessibleTooltipButton >
374- ) ;
375- } else {
376- button = (
377- < RovingAccessibleButton
378- className = { classes }
379- onClick = { this . onClick }
380- onContextMenu = { this . onContextMenu }
381- role = "treeitem"
382- >
383- { toggleCollapseButton }
384- < div className = "mx_SpaceButton_selectionWrapper" >
385- < RoomAvatar width = { avatarSize } height = { avatarSize } room = { space } />
386- < span className = "mx_SpaceButton_name" > { space . name } </ span >
387- { notifBadge }
388- { this . renderContextMenu ( ) }
389- </ div >
390- </ RovingAccessibleButton >
391- ) ;
392- }
393439
394- return (
395- < li { ...otherProps } className = { itemClasses } ref = { innerRef } >
396- { button }
397440 { childItems }
398441 </ li >
399442 ) ;
0 commit comments