1- import { useLayoutEffect , useRef , useState } from 'react'
1+ import { MouseEvent , useLayoutEffect , useRef , useState } from 'react'
22
33import { UsePopoverProps , UsePopoverReturnType } from './types'
44import {
@@ -21,6 +21,7 @@ export const usePopover = ({
2121 const [ open , setOpen ] = useState ( false )
2222 const [ actualPosition , setActualPosition ] = useState < UsePopoverProps [ 'position' ] > ( position )
2323 const [ actualAlignment , setActualAlignment ] = useState < UsePopoverProps [ 'alignment' ] > ( alignment )
24+ const [ triggerBounds , setTriggerBounds ] = useState < UsePopoverReturnType [ 'triggerProps' ] [ 'bounds' ] | null > ( null )
2425
2526 // CONSTANTS
2627 const isAutoWidth = width === 'auto'
@@ -51,23 +52,40 @@ export const usePopover = ({
5152
5253 const handlePopoverKeyDown = ( e : React . KeyboardEvent ) => onPopoverKeyDown ( e , open , closePopover )
5354
55+ const handleOverlayClick = ( e : MouseEvent < HTMLDivElement > ) => {
56+ if ( ! popover . current ?. contains ( e . target as Node ) ) {
57+ closePopover ( )
58+ }
59+ }
60+
5461 useLayoutEffect ( ( ) => {
5562 if ( ! open || ! triggerRef . current || ! popover . current || ! scrollableRef . current ) {
5663 return
5764 }
5865
59- const triggerRect = triggerRef . current . getBoundingClientRect ( )
60- const popoverRect = popover . current . getBoundingClientRect ( )
61-
62- const { fallbackPosition, fallbackAlignment } = getPopoverActualPositionAlignment ( {
63- position,
64- alignment,
65- triggerRect,
66- popoverRect,
67- } )
66+ const updatePopoverPosition = ( ) => {
67+ const triggerRect = triggerRef . current . getBoundingClientRect ( )
68+ const popoverRect = popover . current . getBoundingClientRect ( )
69+
70+ const { fallbackPosition, fallbackAlignment } = getPopoverActualPositionAlignment ( {
71+ position,
72+ alignment,
73+ triggerRect,
74+ popoverRect,
75+ } )
76+
77+ setActualPosition ( fallbackPosition )
78+ setActualAlignment ( fallbackAlignment )
79+ setTriggerBounds ( {
80+ left : triggerRect . left ,
81+ top : triggerRect . top ,
82+ height : triggerRect . height ,
83+ width : triggerRect . width ,
84+ } )
85+ }
6886
69- setActualPosition ( fallbackPosition )
70- setActualAlignment ( fallbackAlignment )
87+ // update position on open
88+ updatePopoverPosition ( )
7189
7290 // prevent scroll propagation unless scrollable
7391 const handleWheel = ( e : WheelEvent ) => {
@@ -84,9 +102,12 @@ export const usePopover = ({
84102 }
85103
86104 scrollableRef . current . addEventListener ( 'wheel' , handleWheel , { passive : false } )
105+ window . addEventListener ( 'resize' , updatePopoverPosition )
106+
87107 // eslint-disable-next-line consistent-return
88108 return ( ) => {
89109 scrollableRef . current . removeEventListener ( 'wheel' , handleWheel )
110+ window . removeEventListener ( 'resize' , updatePopoverPosition )
90111 }
91112 } , [ open , position , alignment ] )
92113
@@ -100,17 +121,18 @@ export const usePopover = ({
100121 'aria-haspopup' : 'listbox' ,
101122 'aria-expanded' : open ,
102123 tabIndex : 0 ,
124+ bounds : triggerBounds ?? { left : 0 , top : 0 , height : 0 , width : 0 } ,
103125 } ,
104126 overlayProps : {
105127 role : 'dialog' ,
106- onClick : closePopover ,
128+ onClick : handleOverlayClick ,
107129 className : 'popover-overlay' ,
108130 } ,
109131 popoverProps : {
110132 id,
111133 ref : popover ,
112134 role : 'listbox' ,
113- className : `popover-content dc__position-abs bg__menu--primary shadow__menu border__primary br-6 dc__overflow-hidden ${ isAutoWidth ? 'dc_width-max-content dc__mxw-250' : '' } ` ,
135+ className : `dc__position-abs bg__menu--primary shadow__menu border__primary br-6 dc__overflow-hidden ${ isAutoWidth ? 'dc_width-max-content dc__mxw-250' : '' } ` ,
114136 onKeyDown : handlePopoverKeyDown ,
115137 style : {
116138 width : ! isAutoWidth ? `${ width } px` : undefined ,
0 commit comments