1- import React , { ReactElement , useCallback , useEffect , useRef , useState } from 'react' ;
1+ import React , { ReactElement , cloneElement , useCallback , useEffect , useRef , useState } from 'react' ;
22import { useDispatch , useSelector } from 'react-redux' ;
33import { keyframes } from 'tss-react' ;
44import { makeStyles } from 'tss-react/mui' ;
@@ -58,12 +58,14 @@ const Tooltip = ({ containerClassName, content, children, position = 'top' }: IP
5858 const dispatch = useDispatch ( ) ;
5959 const [ visible , setVisible ] = useState ( false ) ;
6060 const [ isUnmounting , setIsUnmounting ] = useState ( false ) ;
61+ const [ wasOpenedWithKeyboard , setWasOpenedWithKeyboard ] = useState ( false ) ;
6162 const overflowDrawer = useSelector ( ( state : IReduxState ) => state [ 'features/toolbox' ] . overflowDrawer ) ;
6263 const { classes, cx } = useStyles ( ) ;
6364 const timeoutID = useRef ( {
6465 open : 0 ,
6566 close : 0
6667 } ) ;
68+ const tooltipId = useRef ( `tooltip-${ Math . random ( ) . toString ( 36 ) . substring ( 2 , 11 ) } ` ) ;
6769 const {
6870 content : storeContent ,
6971 previousContent,
@@ -73,7 +75,10 @@ const Tooltip = ({ containerClassName, content, children, position = 'top' }: IP
7375 const contentComponent = (
7476 < div
7577 className = { cx ( classes . container , previousContent === '' && 'mounting-animation' ,
76- isUnmounting && 'unmounting' ) } >
78+ isUnmounting && 'unmounting' ) }
79+ id = { tooltipId . current }
80+ role = 'tooltip'
81+ tabIndex = { wasOpenedWithKeyboard ? 0 : - 1 } >
7782 { content }
7883 </ div >
7984 ) ;
@@ -89,31 +94,37 @@ const Tooltip = ({ containerClassName, content, children, position = 'top' }: IP
8994 setIsUnmounting ( false ) ;
9095 } ;
9196
92- const onPopoverOpen = useCallback ( ( ) => {
97+ const onPopoverOpen = useCallback ( ( keyboardTriggered = false ) => {
9398 if ( isUnmounting ) {
9499 return ;
95100 }
96101
102+ setWasOpenedWithKeyboard ( keyboardTriggered ) ;
97103 clearTimeout ( timeoutID . current . close ) ;
98104 timeoutID . current . close = 0 ;
99105 if ( ! visible ) {
100106 if ( isVisible ) {
101107 openPopover ( ) ;
102108 } else {
109+ const delay = keyboardTriggered ? 0 : TOOLTIP_DELAY ;
110+
103111 timeoutID . current . open = window . setTimeout ( ( ) => {
104112 openPopover ( ) ;
105- } , TOOLTIP_DELAY ) ;
113+ } , delay ) ;
106114 }
107115 }
108116 } , [ visible , isVisible , isUnmounting ] ) ;
109117
110- const onPopoverClose = useCallback ( ( ) => {
118+ const onPopoverClose = useCallback ( ( immediate = false ) => {
111119 clearTimeout ( timeoutID . current . open ) ;
112120 if ( visible ) {
121+ const delay = immediate ? 0 : TOOLTIP_DELAY ;
122+
113123 timeoutID . current . close = window . setTimeout ( ( ) => {
114124 setIsUnmounting ( true ) ;
115- } , TOOLTIP_DELAY ) ;
125+ } , delay ) ;
116126 }
127+ setWasOpenedWithKeyboard ( false ) ;
117128 } , [ visible ] ) ;
118129
119130 useEffect ( ( ) => {
@@ -132,13 +143,50 @@ const Tooltip = ({ containerClassName, content, children, position = 'top' }: IP
132143 clearTimeout ( timeoutID . current . close ) ;
133144 timeoutID . current . close = 0 ;
134145 }
135- } , [ storeContent ] ) ;
146+ } , [ storeContent , content ] ) ;
147+
148+ const handleFocus = useCallback ( ( ) => {
149+ onPopoverOpen ( true ) ;
150+ } , [ onPopoverOpen ] ) ;
136151
152+ const handleBlur = useCallback ( ( ) => {
153+ onPopoverClose ( true ) ;
154+ } , [ onPopoverClose ] ) ;
155+
156+ const handleKeyDown = useCallback ( ( event : React . KeyboardEvent ) => {
157+ if ( event . key === 'Escape' && visible ) {
158+ event . preventDefault ( ) ;
159+ onPopoverClose ( true ) ;
160+ }
161+ } , [ visible , onPopoverClose ] ) ;
137162
138163 if ( isMobileBrowser ( ) || overflowDrawer ) {
139164 return children ;
140165 }
141166
167+ const enhancedChildren = cloneElement ( children , {
168+ 'aria-describedby' : visible ? tooltipId . current : undefined ,
169+ tabIndex : children . props . tabIndex !== undefined ? children . props . tabIndex : 0 ,
170+ onFocus : ( event : React . FocusEvent ) => {
171+ handleFocus ( ) ;
172+ if ( children . props . onFocus ) {
173+ children . props . onFocus ( event ) ;
174+ }
175+ } ,
176+ onBlur : ( event : React . FocusEvent ) => {
177+ handleBlur ( ) ;
178+ if ( children . props . onBlur ) {
179+ children . props . onBlur ( event ) ;
180+ }
181+ } ,
182+ onKeyDown : ( event : React . KeyboardEvent ) => {
183+ handleKeyDown ( event ) ;
184+ if ( children . props . onKeyDown ) {
185+ children . props . onKeyDown ( event ) ;
186+ }
187+ }
188+ } ) ;
189+
142190 return (
143191 < Popover
144192 allowClick = { true }
@@ -148,8 +196,9 @@ const Tooltip = ({ containerClassName, content, children, position = 'top' }: IP
148196 onPopoverClose = { onPopoverClose }
149197 onPopoverOpen = { onPopoverOpen }
150198 position = { position }
199+ role = 'tooltip'
151200 visible = { visible } >
152- { children }
201+ { enhancedChildren }
153202 </ Popover >
154203 ) ;
155204} ;
0 commit comments