@@ -23,40 +23,64 @@ function BorderedAppMenuItem({
2323 itemNo : number ;
2424 position : { x : number ; y : number } ;
2525} ) {
26+ const hoverDelayMs = 400 ;
2627 const elementRef = useRef < HTMLDivElement > ( null ) ;
2728 const elementPosition = useRef ( { ...position } ) ;
28- const hoverRevealDelayRef = useRef < NodeJS . Timeout > ( ) ;
29+ const hoverOpenDelayRef = useRef < NodeJS . Timeout > ( ) ;
30+ const hoverCloseDelayRef = useRef < NodeJS . Timeout > ( ) ;
31+ const [ open , setOpen ] = useState < boolean > ( false ) ;
2932
3033 useEffect ( ( ) => {
31- const rect = elementRef . current ?. getBoundingClientRect ( ) ;
34+ return ( ) => {
35+ clearTimeout ( hoverCloseDelayRef . current ) ;
36+ clearTimeout ( hoverOpenDelayRef . current ) ;
37+ } ;
38+ } , [ ] ) ;
3239
33- // Since we're adding to the ref value, we first check if it's
34- // still it's initial value. This avoids incorrectly adding
35- // multiple times when the effect fires multiple times.
36- if ( elementPosition . current . x === position . x ) {
37- elementPosition . current . x = rect ?. width ?? 0 ;
38- }
39- if ( elementPosition . current . y === position . y ) {
40- elementPosition . current . y = ( rect ?. height ?? 0 ) * ( itemNo - 1 ) ;
41- }
40+ useEffect ( ( ) => {
41+ const rect = elementRef . current ?. getBoundingClientRect ( ) ;
42+ elementPosition . current . x = rect ?. width ?? 0 ;
43+ elementPosition . current . y = ( rect ?. height ?? 0 ) * ( itemNo - 1 ) ;
4244 } , [ elementRef , itemNo , position ] ) ;
43- const [ open , setOpen ] = useState < boolean > ( false ) ;
4445
4546 function handleOnMouseEnter ( ) {
46- if ( ! open ) {
47- hoverRevealDelayRef . current = setTimeout ( ( ) => {
48- if ( ! open ) setOpen ( true ) ;
49- } , 400 ) ;
47+ // Now that we've entered the item, if it's open
48+ // and there's a pending timeout to close it,
49+ // we want to cancel that
50+ if ( open && hoverCloseDelayRef . current ) {
51+ clearTimeout ( hoverCloseDelayRef . current ) ;
52+ hoverCloseDelayRef . current = undefined ;
53+ }
54+
55+ // If it's not open yet and there's no pending
56+ // timeout to open it, lets start one.
57+ if ( ! open && ! hoverOpenDelayRef . current ) {
58+ hoverOpenDelayRef . current = setTimeout ( ( ) => {
59+ if ( ! open ) {
60+ setOpen ( true ) ;
61+ hoverOpenDelayRef . current = undefined ;
62+ }
63+ } , hoverDelayMs ) ;
5064 }
5165 }
5266 function handleOnMouseLeave ( ) {
53- if ( hoverRevealDelayRef . current ) {
54- clearTimeout ( hoverRevealDelayRef . current ) ;
67+ // Now that we've left the item, if it's closed
68+ // and there's a pending timeout to open it,
69+ // we want to cancel that
70+ if ( ! open && hoverOpenDelayRef . current ) {
71+ clearTimeout ( hoverOpenDelayRef . current ) ;
72+ hoverOpenDelayRef . current = undefined ;
5573 }
56- if ( open ) {
57- hoverRevealDelayRef . current = setTimeout ( ( ) => {
58- if ( open ) setOpen ( false ) ;
59- } ) ;
74+
75+ // If it's not closed yet and there's no pending
76+ // timeout to close it, lets start one.
77+ if ( open && ! hoverCloseDelayRef . current ) {
78+ hoverCloseDelayRef . current = setTimeout ( ( ) => {
79+ if ( open ) {
80+ setOpen ( false ) ;
81+ hoverCloseDelayRef . current = undefined ;
82+ }
83+ } , hoverDelayMs ) ;
6084 }
6185 }
6286
0 commit comments