@@ -14,26 +14,34 @@ See the License for the specific language governing permissions and
14
14
limitations under the License.
15
15
*/
16
16
17
- import React , { useCallback , useEffect , useState } from "react" ;
17
+ import React , { RefObject , useCallback , useEffect } from "react" ;
18
18
import { MatrixEvent } from "matrix-js-sdk/src" ;
19
19
20
20
import { ButtonEvent } from "../elements/AccessibleButton" ;
21
21
import dis from '../../../dispatcher/dispatcher' ;
22
22
import { Action } from "../../../dispatcher/actions" ;
23
23
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks" ;
24
24
import { copyPlaintext } from "../../../utils/strings" ;
25
- import { ChevronFace , ContextMenuTooltipButton } from "../../structures/ContextMenu" ;
25
+ import { ChevronFace , ContextMenuTooltipButton , useContextMenu } from "../../structures/ContextMenu" ;
26
26
import { _t } from "../../../languageHandler" ;
27
27
import IconizedContextMenu , { IconizedContextMenuOption , IconizedContextMenuOptionList } from "./IconizedContextMenu" ;
28
28
import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore" ;
29
29
import { MatrixClientPeg } from "../../../MatrixClientPeg" ;
30
+ import { useRovingTabIndex } from "../../../accessibility/RovingTabIndex" ;
30
31
31
32
interface IProps {
32
33
mxEvent : MatrixEvent ;
33
34
permalinkCreator : RoomPermalinkCreator ;
34
35
onMenuToggle ?: ( open : boolean ) => void ;
35
36
}
36
37
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
+
37
45
const contextMenuBelow = ( elementRect : DOMRect ) => {
38
46
// align the context menu's icons with the icon which opened the context menu
39
47
const left = elementRect . left + window . pageXOffset + elementRect . width ;
@@ -42,11 +50,27 @@ const contextMenuBelow = (elementRect: DOMRect) => {
42
50
return { left, top, chevronFace } ;
43
51
} ;
44
52
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
50
74
51
75
const viewInRoom = useCallback ( ( evt : ButtonEvent ) : void => {
52
76
evt . preventDefault ( ) ;
@@ -68,37 +92,31 @@ const ThreadListContextMenu: React.FC<IProps> = ({ mxEvent, permalinkCreator, on
68
92
closeThreadOptions ( ) ;
69
93
} , [ mxEvent , closeThreadOptions , permalinkCreator ] ) ;
70
94
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
-
80
95
useEffect ( ( ) => {
81
96
if ( onMenuToggle ) {
82
- onMenuToggle ( ! ! optionsPosition ) ;
97
+ onMenuToggle ( menuDisplayed ) ;
83
98
}
84
- } , [ optionsPosition , onMenuToggle ] ) ;
99
+ onFocus ?.( ) ;
100
+ } , [ menuDisplayed , onMenuToggle , onFocus ] ) ;
85
101
86
102
const isMainSplitTimelineShown = ! WidgetLayoutStore . instance . hasMaximisedWidget (
87
103
MatrixClientPeg . get ( ) . getRoom ( mxEvent . getRoomId ( ) ) ,
88
104
) ;
89
105
return < React . Fragment >
90
106
< ContextMenuTooltipButton
107
+ { ...props }
91
108
className = "mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
92
- onClick = { toggleOptionsMenu }
109
+ onClick = { openMenu }
93
110
title = { _t ( "Thread options" ) }
94
- isExpanded = { ! ! optionsPosition }
111
+ isExpanded = { menuDisplayed }
112
+ inputRef = { button }
95
113
/>
96
- { ! ! optionsPosition && ( < IconizedContextMenu
114
+ { menuDisplayed && ( < IconizedContextMenu
97
115
onFinished = { closeThreadOptions }
98
116
className = "mx_RoomTile_contextMenu"
99
117
compact
100
118
rightAligned
101
- { ...contextMenuBelow ( optionsPosition ) }
119
+ { ...contextMenuBelow ( button . current . getBoundingClientRect ( ) ) }
102
120
>
103
121
< IconizedContextMenuOptionList >
104
122
{ isMainSplitTimelineShown &&
0 commit comments