@@ -14,26 +14,34 @@ See the License for the specific language governing permissions and
1414limitations under the License.
1515*/
1616
17- import React , { useCallback , useEffect , useState } from "react" ;
17+ import React , { RefObject , useCallback , useEffect } from "react" ;
1818import { MatrixEvent } from "matrix-js-sdk/src" ;
1919
2020import { ButtonEvent } from "../elements/AccessibleButton" ;
2121import dis from '../../../dispatcher/dispatcher' ;
2222import { Action } from "../../../dispatcher/actions" ;
2323import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks" ;
2424import { copyPlaintext } from "../../../utils/strings" ;
25- import { ChevronFace , ContextMenuTooltipButton } from "../../structures/ContextMenu" ;
25+ import { ChevronFace , ContextMenuTooltipButton , useContextMenu } from "../../structures/ContextMenu" ;
2626import { _t } from "../../../languageHandler" ;
2727import IconizedContextMenu , { IconizedContextMenuOption , IconizedContextMenuOptionList } from "./IconizedContextMenu" ;
2828import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore" ;
2929import { MatrixClientPeg } from "../../../MatrixClientPeg" ;
30+ import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex" ;
3031
3132interface IProps {
3233 mxEvent : MatrixEvent ;
3334 permalinkCreator : RoomPermalinkCreator ;
3435 onMenuToggle ?: ( open : boolean ) => void ;
3536}
3637
38+ interface IExtendedProps extends IProps {
39+ // Props for making the button into a roving one
40+ tabIndex ?: number ;
41+ inputRef ?: RefObject < HTMLElement > ;
42+ onFocus ?( ) : void ;
43+ }
44+
3745const contextMenuBelow = ( elementRect : DOMRect ) => {
3846 // align the context menu's icons with the icon which opened the context menu
3947 const left = elementRect . left + window . pageXOffset + elementRect . width ;
@@ -42,11 +50,27 @@ const contextMenuBelow = (elementRect: DOMRect) => {
4250 return { left, top, chevronFace } ;
4351} ;
4452
45- const ThreadListContextMenu : React . FC < IProps > = ( { mxEvent, permalinkCreator, onMenuToggle } ) => {
46- const [ optionsPosition , setOptionsPosition ] = useState ( null ) ;
47- const closeThreadOptions = useCallback ( ( ) => {
48- setOptionsPosition ( null ) ;
49- } , [ ] ) ;
53+ export const RovingThreadListContextMenu : React . FC < IProps > = ( props ) => {
54+ const [ onFocus , isActive , ref ] = useRovingTabIndex ( ) ;
55+
56+ return < ThreadListContextMenu
57+ { ...props }
58+ onFocus = { onFocus }
59+ tabIndex = { isActive ? 0 : - 1 }
60+ inputRef = { ref }
61+ /> ;
62+ } ;
63+
64+ const ThreadListContextMenu : React . FC < IExtendedProps > = ( {
65+ mxEvent,
66+ permalinkCreator,
67+ onMenuToggle,
68+ onFocus,
69+ inputRef,
70+ ...props
71+ } ) => {
72+ const [ menuDisplayed , _ref , openMenu , closeThreadOptions ] = useContextMenu ( ) ;
73+ const button = inputRef ?? _ref ; // prefer the ref we receive via props in case we are being controlled
5074
5175 const viewInRoom = useCallback ( ( evt : ButtonEvent ) : void => {
5276 evt . preventDefault ( ) ;
@@ -68,37 +92,31 @@ const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, on
6892 closeThreadOptions ( ) ;
6993 } , [ mxEvent , closeThreadOptions , permalinkCreator ] ) ;
7094
71- const toggleOptionsMenu = useCallback ( ( ev : ButtonEvent ) : void => {
72- if ( ! ! optionsPosition ) {
73- closeThreadOptions ( ) ;
74- } else {
75- const position = ev . currentTarget . getBoundingClientRect ( ) ;
76- setOptionsPosition ( position ) ;
77- }
78- } , [ closeThreadOptions , optionsPosition ] ) ;
79-
8095 useEffect ( ( ) => {
8196 if ( onMenuToggle ) {
82- onMenuToggle ( ! ! optionsPosition ) ;
97+ onMenuToggle ( menuDisplayed ) ;
8398 }
84- } , [ optionsPosition , onMenuToggle ] ) ;
99+ onFocus ?.( ) ;
100+ } , [ menuDisplayed , onMenuToggle , onFocus ] ) ;
85101
86102 const isMainSplitTimelineShown = ! WidgetLayoutStore . instance . hasMaximisedWidget (
87103 MatrixClientPeg . get ( ) . getRoom ( mxEvent . getRoomId ( ) ) ,
88104 ) ;
89105 return < React . Fragment >
90106 < ContextMenuTooltipButton
107+ { ...props }
91108 className = "mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
92- onClick = { toggleOptionsMenu }
109+ onClick = { openMenu }
93110 title = { _t ( "Thread options" ) }
94- isExpanded = { ! ! optionsPosition }
111+ isExpanded = { menuDisplayed }
112+ inputRef = { button }
95113 />
96- { ! ! optionsPosition && ( < IconizedContextMenu
114+ { menuDisplayed && ( < IconizedContextMenu
97115 onFinished = { closeThreadOptions }
98116 className = "mx_RoomTile_contextMenu"
99117 compact
100118 rightAligned
101- { ...contextMenuBelow ( optionsPosition ) }
119+ { ...contextMenuBelow ( button . current . getBoundingClientRect ( ) ) }
102120 >
103121 < IconizedContextMenuOptionList >
104122 { isMainSplitTimelineShown &&
0 commit comments