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 ( ( ) => {
@@ -139,6 +150,44 @@ const Tooltip = ({ containerClassName, content, children, position = 'top' }: IP
139150 return children ;
140151 }
141152
153+ const handleFocus = useCallback ( ( ) => {
154+ onPopoverOpen ( true ) ;
155+ } , [ onPopoverOpen ] ) ;
156+
157+ const handleBlur = useCallback ( ( ) => {
158+ onPopoverClose ( true ) ;
159+ } , [ onPopoverClose ] ) ;
160+
161+ const handleKeyDown = useCallback ( ( event : React . KeyboardEvent ) => {
162+ if ( event . key === 'Escape' && visible ) {
163+ event . preventDefault ( ) ;
164+ onPopoverClose ( true ) ;
165+ }
166+ } , [ visible , onPopoverClose ] ) ;
167+
168+ const enhancedChildren = cloneElement ( children , {
169+ 'aria-describedby' : visible ? tooltipId . current : undefined ,
170+ tabIndex : children . props . tabIndex !== undefined ? children . props . tabIndex : 0 ,
171+ onFocus : ( event : React . FocusEvent ) => {
172+ handleFocus ( ) ;
173+ if ( children . props . onFocus ) {
174+ children . props . onFocus ( event ) ;
175+ }
176+ } ,
177+ onBlur : ( event : React . FocusEvent ) => {
178+ handleBlur ( ) ;
179+ if ( children . props . onBlur ) {
180+ children . props . onBlur ( event ) ;
181+ }
182+ } ,
183+ onKeyDown : ( event : React . KeyboardEvent ) => {
184+ handleKeyDown ( event ) ;
185+ if ( children . props . onKeyDown ) {
186+ children . props . onKeyDown ( event ) ;
187+ }
188+ }
189+ } ) ;
190+
142191 return (
143192 < Popover
144193 allowClick = { true }
@@ -148,8 +197,9 @@ const Tooltip = ({ containerClassName, content, children, position = 'top' }: IP
148197 onPopoverClose = { onPopoverClose }
149198 onPopoverOpen = { onPopoverOpen }
150199 position = { position }
200+ role = 'tooltip'
151201 visible = { visible } >
152- { children }
202+ { enhancedChildren }
153203 </ Popover >
154204 ) ;
155205} ;
0 commit comments