1313import  { ariaHideOutside }  from  './ariaHideOutside' ; 
1414import  { AriaOverlayProps ,  useOverlay }  from  './useOverlay' ; 
1515import  { DOMAttributes ,  RefObject }  from  '@react-types/shared' ; 
16+ import  { isElementVisible }  from  '../../utils/src/isElementVisible' ; 
1617import  { mergeProps }  from  '@react-aria/utils' ; 
1718import  { OverlayTriggerState }  from  '@react-stately/overlays' ; 
1819import  { useEffect }  from  'react' ; 
@@ -58,7 +59,7 @@ export function useModalOverlay(props: AriaModalOverlayProps, state: OverlayTrig
5859
5960  useEffect ( ( )  =>  { 
6061    if  ( state . isOpen  &&  ref . current )  { 
61-       return  ariaHideOutside ( [ ref . current ] ,   { shouldUseInert :  true } ) ; 
62+       return  hideElementsBehind ( ref . current ) ; 
6263    } 
6364  } ,  [ state . isOpen ,  ref ] ) ; 
6465
@@ -67,3 +68,120 @@ export function useModalOverlay(props: AriaModalOverlayProps, state: OverlayTrig
6768    underlayProps
6869  } ; 
6970} 
71+ 
72+ function  hideElementsBehind ( element : Element ,  root  =  document . body )  { 
73+   // TODO: automatically determine root based on parent stacking context of element? 
74+   let  roots  =  getStackingContextRoots ( root ) ; 
75+   let  rootStackingContext  =  roots . find ( r  =>  r . contains ( element ) )  ||  document . documentElement ; 
76+   let  elementZIndex  =  getZIndex ( rootStackingContext ) ; 
77+ 
78+   return  ariaHideOutside ( [ element ] ,  { 
79+     shouldUseInert : true , 
80+     getVisibleNodes : el  =>  { 
81+       let  node : Element  |  null  =  el ; 
82+       let  ancestors : Element [ ]  =  [ ] ; 
83+       while  ( node  &&  node  !==  root )  { 
84+         ancestors . unshift ( node ) ; 
85+         node  =  node . parentElement ; 
86+       } 
87+       
88+       // If an ancestor element of the added target is a stacking context root, 
89+       // use that to determine if the element should be preserved. 
90+       let  stackingContext  =  ancestors . find ( el  =>  isStackingContext ( el ) ) ; 
91+       if  ( stackingContext )  { 
92+         if  ( shouldPreserve ( element ,  elementZIndex ,  stackingContext ) )  { 
93+           return  [ el ] ; 
94+         } 
95+         return  [ ] ; 
96+       }  else  { 
97+         // Otherwise, find stacking context roots within the added element, and compare with the modal element. 
98+         let  roots  =  getStackingContextRoots ( el ) ; 
99+         let  preservedElements : Element [ ]  =  [ ] ; 
100+         for  ( let  root  of  roots )  { 
101+           if  ( shouldPreserve ( element ,  elementZIndex ,  root ) )  { 
102+             preservedElements . push ( root ) ; 
103+           } 
104+         } 
105+         return  preservedElements ; 
106+       } 
107+     } 
108+   } ) ; 
109+ } 
110+ 
111+ function  shouldPreserve ( baseElement : Element ,  baseZIndex : number ,  element : Element )  { 
112+   if  ( baseElement . contains ( element ) )  { 
113+     return  true ; 
114+   } 
115+ 
116+   let  zIndex  =  getZIndex ( element ) ; 
117+   if  ( zIndex  ===  baseZIndex )  { 
118+     // If two elements have the same z-index, compare their document order. 
119+     if  ( element . compareDocumentPosition ( baseElement )  &  Node . DOCUMENT_POSITION_FOLLOWING )  { 
120+       return  true ; 
121+     } 
122+   }  else  if  ( zIndex  >  baseZIndex )  { 
123+     return  true ; 
124+   } 
125+ 
126+   return  false ; 
127+ } 
128+ 
129+ function  isStackingContext ( el : Element )  { 
130+   let  style  =  getComputedStyle ( el ) ; 
131+ 
132+   // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context#features_creating_stacking_contexts 
133+   return  ( 
134+     el  ===  document . documentElement  || 
135+     ( style . position  !==  'static'  &&  style . zIndex  !==  'auto' )  || 
136+     ( 'containerType'  in  style  &&  style . containerType . includes ( 'size' ) )  || 
137+     ( style . zIndex  !==  'auto'  &&  isFlexOrGridItem ( el ) )  || 
138+     parseFloat ( style . opacity )  <  1  || 
139+     ( 'mixBlendMode'  in  style  &&  style . mixBlendMode  !==  'normal' )  || 
140+     ( 'transform'  in  style  &&  style . transform  !==  'none' )  || 
141+     ( 'webkitTransform'  in  style  &&  style . webkitTransform  !==  'none' )  || 
142+     ( 'scale'  in  style  &&  style . scale  !==  'none' )  || 
143+     ( 'rotate'  in  style  &&  style . rotate  !==  'none' )  || 
144+     ( 'translate'  in  style  &&  style . translate  !==  'none' )  || 
145+     ( 'filter'  in  style  &&  style . filter  !==  'none' )  || 
146+     ( 'webkitFilter'  in  style  &&  style . webkitFilter  !==  'none' )  || 
147+     ( 'backdropFilter'  in  style  &&  style . backdropFilter  !==  'none' )  || 
148+     ( 'perspective'  in  style  &&  style . perspective  !==  'none' )  || 
149+     ( 'clipPath'  in  style  &&  style . clipPath  !==  'none' )  || 
150+     ( 'mask'  in  style  &&  style . mask  !==  'none' )  || 
151+     ( 'maskImage'  in  style  &&  style . maskImage  !==  'none' )  || 
152+     ( 'maskBorder'  in  style  &&  style . maskBorder  !==  'none' )  || 
153+     style . isolation  ===  'isolate'  || 
154+     / p o s i t i o n | z - i n d e x | o p a c i t y | m i x - b l e n d - m o d e | t r a n s f o r m | w e b k i t - t r a n s f o r m | s c a l e | r o t a t e | t r a n s l a t e | f i l t e r | w e b k i t - f i l t e r | b a c k d r o p - f i l t e r | p e r s p e c t i v e | c l i p - p a t h | m a s k | m a s k - i m a g e | m a s k - b o r d e r | i s o l a t i o n / . test ( style . willChange )  || 
155+     / l a y o u t | p a i n t | s t r i c t | c o n t e n t / . test ( style . contain ) 
156+   ) ; 
157+ } 
158+ 
159+ function  getStackingContextRoots ( root : Element  =  document . body )  { 
160+   let  roots : Element [ ]  =  [ ] ; 
161+ 
162+   function  walk ( el : Element )  { 
163+     if  ( ! isElementVisible ( el ) )  { 
164+       return ; 
165+     } 
166+ 
167+     if  ( isStackingContext ( el ) )  { 
168+       roots . push ( el ) ; 
169+     }  else  { 
170+       for  ( const  child  of  el . children )  { 
171+         walk ( child ) ; 
172+       } 
173+     } 
174+   } 
175+ 
176+   walk ( root ) ; 
177+   return  roots ; 
178+ } 
179+ 
180+ function  getZIndex ( element : Element )  { 
181+   return  Number ( getComputedStyle ( element ) . zIndex )  ||  0 ; 
182+ } 
183+ 
184+ function  isFlexOrGridItem ( element : Element )  { 
185+   let  parent  =  element . parentElement ; 
186+   return  parent  &&  / f l e x | g r i d / . test ( getComputedStyle ( parent ) . display ) ; 
187+ } 
0 commit comments