@@ -7,6 +7,7 @@ import type { ComponentType, PropsWithChildren, ReactElement } from 'react';
77import type { Root } from 'react-dom/client' ;
88import { getQueriesForElement , queries } from '@testing-library/dom' ;
99import * as domTestingLibrary from '@testing-library/dom' ;
10+ import * as pokuDom from '@pokujs/dom' ;
1011import React from 'react' ;
1112import { createRoot } from 'react-dom/client' ;
1213import {
@@ -29,9 +30,92 @@ type InternalMounted = {
2930 ownsContainer : boolean ;
3031} ;
3132
32- const mountedRoots = new Set < InternalMounted > ( ) ;
33+ const fallbackMountedRoots = new Set < InternalMounted > ( ) ;
3334
34- const unmountMounted = ( mounted : InternalMounted ) => {
35+ type ScopeSlot < T > = {
36+ readonly value : T ;
37+ } ;
38+
39+ type ScopeLike = {
40+ getOrCreateSlot < T > ( key : symbol , init : ( ) => T ) : ScopeSlot < T > ;
41+ getSlot ?< T > ( key : symbol ) : ScopeSlot < T > | undefined ;
42+ addCleanup ?( fn : ( ) => void | Promise < void > ) : void ;
43+ } ;
44+
45+ type DomScopeApi = {
46+ defineSlotKey ?: < T > ( name : string ) => symbol ;
47+ getOrCreateScope ?: ( ) => ScopeLike | undefined ;
48+ getCurrentScope ?: ( ) => ScopeLike | undefined ;
49+ } ;
50+
51+ const domScopeApi = pokuDom as unknown as DomScopeApi ;
52+
53+ const MOUNTED_ROOTS_SLOT_KEY =
54+ typeof domScopeApi . defineSlotKey === 'function'
55+ ? domScopeApi . defineSlotKey < Set < InternalMounted > > (
56+ '@pokujs/react.mounted-roots'
57+ )
58+ : undefined ;
59+
60+ const CLEANUP_STATE_SLOT_KEY =
61+ typeof domScopeApi . defineSlotKey === 'function'
62+ ? domScopeApi . defineSlotKey < { registered : boolean } > (
63+ '@pokujs/react.cleanup-registered'
64+ )
65+ : undefined ;
66+
67+ const cleanupMountedRoots = ( mountedRoots : Set < InternalMounted > ) => {
68+ for ( const mounted of [ ...mountedRoots ] ) {
69+ unmountMounted ( mountedRoots , mounted ) ;
70+ }
71+ } ;
72+
73+ const getScopedMountedRoots = ( ) : Set < InternalMounted > | undefined => {
74+ if ( ! MOUNTED_ROOTS_SLOT_KEY ) return undefined ;
75+ if ( typeof domScopeApi . getOrCreateScope !== 'function' ) return undefined ;
76+
77+ const scope = domScopeApi . getOrCreateScope ( ) ;
78+ if ( ! scope ) return undefined ;
79+
80+ const mountedRoots = scope . getOrCreateSlot ( MOUNTED_ROOTS_SLOT_KEY , ( ) =>
81+ new Set < InternalMounted > ( )
82+ ) . value ;
83+
84+ if ( ! CLEANUP_STATE_SLOT_KEY || typeof scope . addCleanup !== 'function' ) {
85+ return mountedRoots ;
86+ }
87+
88+ const cleanupState = scope . getOrCreateSlot ( CLEANUP_STATE_SLOT_KEY , ( ) => ( {
89+ registered : false ,
90+ } ) ) . value ;
91+
92+ if ( ! cleanupState . registered ) {
93+ cleanupState . registered = true ;
94+ scope . addCleanup ( ( ) => {
95+ cleanupMountedRoots ( mountedRoots ) ;
96+ metrics . flushMetricBuffer ( ) ;
97+ } ) ;
98+ }
99+
100+ return mountedRoots ;
101+ } ;
102+
103+ const getMountedRoots = ( ) : Set < InternalMounted > =>
104+ getScopedMountedRoots ( ) ?? fallbackMountedRoots ;
105+
106+ const getCurrentScopedMountedRoots = ( ) : Set < InternalMounted > | undefined => {
107+ if ( ! MOUNTED_ROOTS_SLOT_KEY ) return undefined ;
108+ if ( typeof domScopeApi . getCurrentScope !== 'function' ) return undefined ;
109+
110+ const scope = domScopeApi . getCurrentScope ( ) ;
111+ const slot = scope ?. getSlot ?.< Set < InternalMounted > > ( MOUNTED_ROOTS_SLOT_KEY ) ;
112+ return slot ?. value ;
113+ } ;
114+
115+ const unmountMounted = (
116+ mountedRoots : Set < InternalMounted > ,
117+ mounted : InternalMounted
118+ ) => {
35119 try {
36120 act ( ( ) => {
37121 mounted . root ?. unmount ( ) ;
@@ -84,6 +168,7 @@ export const render = (
84168 ui : ReactElement ,
85169 options : RenderOptions = { }
86170) : RenderResult => {
171+ const mountedRoots = getMountedRoots ( ) ;
87172 const baseElement = options . baseElement || document . body ;
88173 const container = options . container || document . createElement ( 'div' ) ;
89174 const ownsContainer = ! options . container ;
@@ -109,7 +194,7 @@ export const render = (
109194
110195 const unmount = ( ) => {
111196 if ( ! mountedRoots . has ( mounted ) ) return ;
112- unmountMounted ( mounted ) ;
197+ unmountMounted ( mountedRoots , mounted ) ;
113198 } ;
114199
115200 const rerender = ( nextUi : ReactElement ) => {
@@ -173,9 +258,10 @@ export const renderHook = <
173258} ;
174259
175260export const cleanup = ( ) => {
176- for ( const mounted of [ ...mountedRoots ] ) {
177- unmountMounted ( mounted ) ;
178- }
261+ const scopedMountedRoots = getCurrentScopedMountedRoots ( ) ;
262+ if ( scopedMountedRoots ) cleanupMountedRoots ( scopedMountedRoots ) ;
263+
264+ cleanupMountedRoots ( fallbackMountedRoots ) ;
179265
180266 metrics . flushMetricBuffer ( ) ;
181267} ;
0 commit comments