1- import { useCallback } from 'react' ;
1+ import { useCallback , useEffect , useRef } from 'react' ;
22
33import { makeMutable , type SharedValue } from 'react-native-reanimated' ;
44
@@ -8,6 +8,7 @@ import { useSyncExternalStore } from 'use-sync-external-store/shim';
88import { useStateStore } from '../hooks' ;
99
1010type OverlayState = {
11+ closingPortalHostBlacklist : string [ ] ;
1112 id : string | undefined ;
1213 closing : boolean ;
1314} ;
@@ -22,6 +23,7 @@ type ClosingPortalLayoutsState = {
2223} ;
2324
2425const DefaultState = {
26+ closingPortalHostBlacklist : [ ] ,
2527 closing : false ,
2628 id : undefined ,
2729} ;
@@ -30,6 +32,8 @@ const DefaultClosingPortalLayoutsState: ClosingPortalLayoutsState = {
3032} ;
3133
3234let closingPortalLayoutRegistrationCounter = 0 ;
35+ let closingPortalHostBlacklistRegistrationCounter = 0 ;
36+ let closingPortalHostBlacklistStack : Array < { hostNames : string [ ] ; id : string } > = [ ] ;
3337
3438type OverlaySharedValueController = {
3539 incrementCloseCorrectionY : ( deltaY : number ) => void ;
@@ -69,7 +73,11 @@ export const bumpOverlayLayoutRevision = (closeCorrectionDeltaY = 0) => {
6973
7074export const openOverlay = ( id : string ) => {
7175 sharedValueController ?. resetCloseCorrectionY ( ) ;
72- overlayStore . partialNext ( { closing : false , id } ) ;
76+ overlayStore . partialNext ( {
77+ closing : false ,
78+ closingPortalHostBlacklist : getCurrentClosingPortalHostBlacklist ( ) ,
79+ id,
80+ } ) ;
7381} ;
7482
7583export const closeOverlay = ( ) => {
@@ -90,7 +98,10 @@ export const scheduleActionOnClose = (action: () => void | Promise<void>) => {
9098} ;
9199
92100export const finalizeCloseOverlay = ( ) => {
93- overlayStore . partialNext ( DefaultState ) ;
101+ overlayStore . next ( {
102+ ...DefaultState ,
103+ closingPortalHostBlacklist : getCurrentClosingPortalHostBlacklist ( ) ,
104+ } ) ;
94105 sharedValueController ?. reset ( ) ;
95106} ;
96107
@@ -102,6 +113,43 @@ const closingPortalLayoutsStore = new StateStore<ClosingPortalLayoutsState>(
102113export const createClosingPortalLayoutRegistrationId = ( ) =>
103114 `closing-portal-layout-${ closingPortalLayoutRegistrationCounter ++ } ` ;
104115
116+ const getCurrentClosingPortalHostBlacklist = ( ) =>
117+ closingPortalHostBlacklistStack [ closingPortalHostBlacklistStack . length - 1 ] ?. hostNames ?? [ ] ;
118+
119+ const syncClosingPortalHostBlacklist = ( ) => {
120+ overlayStore . partialNext ( {
121+ closingPortalHostBlacklist : getCurrentClosingPortalHostBlacklist ( ) ,
122+ } ) ;
123+ } ;
124+
125+ const createClosingPortalHostBlacklistRegistrationId = ( ) =>
126+ `closing-portal-host-blacklist-${ closingPortalHostBlacklistRegistrationCounter ++ } ` ;
127+
128+ const setClosingPortalHostBlacklist = ( id : string , hostNames : string [ ] ) => {
129+ const existingEntryIndex = closingPortalHostBlacklistStack . findIndex ( ( entry ) => entry . id === id ) ;
130+
131+ if ( existingEntryIndex === - 1 ) {
132+ closingPortalHostBlacklistStack = [ ...closingPortalHostBlacklistStack , { hostNames, id } ] ;
133+ } else {
134+ closingPortalHostBlacklistStack = closingPortalHostBlacklistStack . map ( ( entry , index ) =>
135+ index === existingEntryIndex ? { ...entry , hostNames } : entry ,
136+ ) ;
137+ }
138+
139+ syncClosingPortalHostBlacklist ( ) ;
140+ } ;
141+
142+ const clearClosingPortalHostBlacklist = ( id : string ) => {
143+ const nextBlacklistStack = closingPortalHostBlacklistStack . filter ( ( entry ) => entry . id !== id ) ;
144+
145+ if ( nextBlacklistStack . length === closingPortalHostBlacklistStack . length ) {
146+ return ;
147+ }
148+
149+ closingPortalHostBlacklistStack = nextBlacklistStack ;
150+ syncClosingPortalHostBlacklist ( ) ;
151+ } ;
152+
105153export const setClosingPortalLayout = ( hostName : string , id : string , layout : Rect ) => {
106154 const { layouts } = closingPortalLayoutsStore . getLatestValue ( ) ;
107155 const hostEntries = layouts [ hostName ] ?? [ ] ;
@@ -170,16 +218,66 @@ const overlayClosingSelector = (nextState: OverlayState) => ({
170218 closing : nextState . closing ,
171219} ) ;
172220
221+ const closingPortalHostBlacklistSelector = ( nextState : OverlayState ) => ( {
222+ closingPortalHostBlacklist : nextState . closingPortalHostBlacklist ,
223+ } ) ;
224+
173225export const useIsOverlayClosing = ( ) => {
174226 return useStateStore ( overlayStore , overlayClosingSelector ) . closing ;
175227} ;
176228
229+ export const useClosingPortalHostBlacklistState = ( ) => {
230+ return useStateStore ( overlayStore , closingPortalHostBlacklistSelector ) . closingPortalHostBlacklist ;
231+ } ;
232+
177233export const useShouldTeleportToClosingPortal = ( hostName : string , id : string ) => {
178234 const closing = useIsOverlayClosing ( ) ;
235+ const closingPortalHostBlacklist = useClosingPortalHostBlacklistState ( ) ;
179236 const closingPortalLayouts = useClosingPortalLayouts ( ) ;
180237 const hostEntries = closingPortalLayouts [ hostName ] ;
181238
182- return ! ! closing && hostEntries ?. [ hostEntries . length - 1 ] ?. id === id ;
239+ return (
240+ closing &&
241+ ! closingPortalHostBlacklist . includes ( hostName ) &&
242+ hostEntries ?. [ hostEntries . length - 1 ] ?. id === id
243+ ) ;
244+ } ;
245+
246+ /**
247+ * Registers a screen-level blacklist of closing portal hosts that should not render while this hook is active.
248+ *
249+ * The blacklist uses stack semantics:
250+ * - mounting/enabling a new instance makes its blacklist active
251+ * - unmounting/disabling restores the previous active blacklist automatically
252+ *
253+ * This keeps stacked screens predictable without requiring previous screens to rerun effects when the top screen
254+ * disappears.
255+ */
256+ export const useClosingPortalHostBlacklist = ( hostNames : string [ ] , enabled = true ) => {
257+ const registrationIdRef = useRef < string | null > ( null ) ;
258+
259+ if ( ! registrationIdRef . current ) {
260+ registrationIdRef . current = createClosingPortalHostBlacklistRegistrationId ( ) ;
261+ }
262+
263+ const registrationId = registrationIdRef . current ;
264+ const serializedNormalizedHostNames = JSON . stringify ( [ ...new Set ( hostNames ) ] ) ;
265+
266+ useEffect ( ( ) => {
267+ if ( ! enabled ) {
268+ clearClosingPortalHostBlacklist ( registrationId ) ;
269+ return ;
270+ }
271+
272+ setClosingPortalHostBlacklist (
273+ registrationId ,
274+ JSON . parse ( serializedNormalizedHostNames ) as string [ ] ,
275+ ) ;
276+
277+ return ( ) => {
278+ clearClosingPortalHostBlacklist ( registrationId ) ;
279+ } ;
280+ } , [ enabled , registrationId , serializedNormalizedHostNames ] ) ;
183281} ;
184282
185283/**
0 commit comments