1010 * governing permissions and limitations under the License.
1111 */
1212
13- import { getOwnerWindow , nodeContains } from '@react-aria/utils' ;
13+ import { createShadowTreeWalker , getOwnerDocument , getOwnerWindow , nodeContains } from '@react-aria/utils' ;
14+ import { shadowDOM } from '@react-stately/flags' ;
15+
1416const supportsInert = typeof HTMLElement !== 'undefined' && 'inert' in HTMLElement . prototype ;
1517
1618interface AriaHideOutsideOptions {
@@ -64,6 +66,22 @@ export function ariaHideOutside(targets: Element[], options?: AriaHideOutsideOpt
6466 }
6567 } ;
6668
69+ let shadowRootsToWatch = new Set < ShadowRoot > ( ) ;
70+ if ( shadowDOM ( ) ) {
71+ // find all shadow roots that are ancestors of the targets
72+ // traverse upwards until the root is reached
73+ for ( let target of targets ) {
74+ let node = target ;
75+ while ( node && node !== root ) {
76+ let root = node . getRootNode ( ) ;
77+ if ( 'shadowRoot' in root ) {
78+ shadowRootsToWatch . add ( root . shadowRoot as ShadowRoot ) ;
79+ }
80+ node = root . parentNode as Element ;
81+ }
82+ }
83+ }
84+
6785 let walk = ( root : Element ) => {
6886 // Keep live announcer and top layer elements (e.g. toasts) visible.
6987 for ( let element of root . querySelectorAll ( '[data-live-announcer], [data-react-aria-top-layer]' ) ) {
@@ -93,7 +111,8 @@ export function ariaHideOutside(targets: Element[], options?: AriaHideOutsideOpt
93111 return NodeFilter . FILTER_ACCEPT ;
94112 } ;
95113
96- let walker = document . createTreeWalker (
114+ let walker = createShadowTreeWalker (
115+ getOwnerDocument ( root ) ,
97116 root ,
98117 NodeFilter . SHOW_ELEMENT ,
99118 { acceptNode}
@@ -164,10 +183,65 @@ export function ariaHideOutside(targets: Element[], options?: AriaHideOutsideOpt
164183 }
165184 }
166185 }
186+
187+ if ( shadowDOM ( ) ) {
188+ // if any of the observed shadow roots were removed, stop observing them
189+ for ( let shadowRoot of shadowRootsToWatch ) {
190+ if ( ! shadowRoot . isConnected ) {
191+ observer . disconnect ( ) ;
192+ break ;
193+ }
194+ }
195+ }
167196 }
168197 } ) ;
169198
170199 observer . observe ( root , { childList : true , subtree : true } ) ;
200+ let shadowObservers = new Set < MutationObserver > ( ) ;
201+ if ( shadowDOM ( ) ) {
202+ for ( let shadowRoot of shadowRootsToWatch ) {
203+ // Disconnect single target instead of all https://github.com/whatwg/dom/issues/126
204+ let shadowObserver = new MutationObserver ( changes => {
205+ for ( let change of changes ) {
206+ if ( change . type !== 'childList' ) {
207+ continue ;
208+ }
209+
210+ // If the parent element of the added nodes is not within one of the targets,
211+ // and not already inside a hidden node, hide all of the new children.
212+ if (
213+ change . target . isConnected &&
214+ ! [ ...visibleNodes , ...hiddenNodes ] . some ( ( node ) =>
215+ nodeContains ( node , change . target )
216+ )
217+ ) {
218+ for ( let node of change . addedNodes ) {
219+ if (
220+ ( node instanceof HTMLElement || node instanceof SVGElement ) &&
221+ ( node . dataset . liveAnnouncer === 'true' || node . dataset . reactAriaTopLayer === 'true' )
222+ ) {
223+ visibleNodes . add ( node ) ;
224+ } else if ( node instanceof Element ) {
225+ walk ( node ) ;
226+ }
227+ }
228+ }
229+
230+ if ( shadowDOM ( ) ) {
231+ // if any of the observed shadow roots were removed, stop observing them
232+ for ( let shadowRoot of shadowRootsToWatch ) {
233+ if ( ! shadowRoot . isConnected ) {
234+ observer . disconnect ( ) ;
235+ break ;
236+ }
237+ }
238+ }
239+ }
240+ } ) ;
241+ shadowObserver . observe ( shadowRoot , { childList : true , subtree : true } ) ;
242+ shadowObservers . add ( shadowObserver ) ;
243+ }
244+ }
171245
172246 let observerWrapper : ObserverWrapper = {
173247 visibleNodes,
@@ -184,6 +258,11 @@ export function ariaHideOutside(targets: Element[], options?: AriaHideOutsideOpt
184258
185259 return ( ) : void => {
186260 observer . disconnect ( ) ;
261+ if ( shadowDOM ( ) ) {
262+ for ( let shadowObserver of shadowObservers ) {
263+ shadowObserver . disconnect ( ) ;
264+ }
265+ }
187266
188267 for ( let node of hiddenNodes ) {
189268 let count = refCountMap . get ( node ) ;
0 commit comments