@@ -15,7 +15,10 @@ import type {
1515import type { Lane } from './ReactFiberLane' ;
1616import type { Transition } from 'react/src/ReactStartTransition' ;
1717
18- import { requestTransitionLane } from './ReactFiberRootScheduler' ;
18+ import {
19+ requestTransitionLane ,
20+ ensureScheduleIsScheduled ,
21+ } from './ReactFiberRootScheduler' ;
1922import { NoLane } from './ReactFiberLane' ;
2023import {
2124 hasScheduledTransitionWork ,
@@ -24,9 +27,13 @@ import {
2427import {
2528 enableComponentPerformanceTrack ,
2629 enableProfilerTimer ,
30+ enableDefaultTransitionIndicator ,
2731} from 'shared/ReactFeatureFlags' ;
2832import { clearEntangledAsyncTransitionTypes } from './ReactFiberTransitionTypes' ;
2933
34+ import noop from 'shared/noop' ;
35+ import reportGlobalError from 'shared/reportGlobalError' ;
36+
3037// If there are multiple, concurrent async actions, they are entangled. All
3138// transition updates that occur while the async action is still in progress
3239// are treated as part of the action.
@@ -46,6 +53,21 @@ let currentEntangledLane: Lane = NoLane;
4653// until the async action scope has completed.
4754let currentEntangledActionThenable : Thenable < void > | null = null ;
4855
56+ // Track the default indicator for every root. undefined means we haven't
57+ // had any roots registered yet. null means there's more than one callback.
58+ // If there's more than one callback we bailout to not supporting isomorphic
59+ // default indicators.
60+ let isomorphicDefaultTransitionIndicator :
61+ | void
62+ | null
63+ | ( ( ) => void | ( ( ) => void ) ) = undefined ;
64+ // The clean up function for the currently running indicator.
65+ let pendingIsomorphicIndicator : null | ( ( ) => void ) = null ;
66+ // The number of roots that have pending Transitions that depend on the
67+ // started isomorphic indicator.
68+ let pendingEntangledRoots : number = 0 ;
69+ let needsIsomorphicIndicator : boolean = false ;
70+
4971export function entangleAsyncAction < S > (
5072 transition : Transition ,
5173 thenable : Thenable < S > ,
@@ -66,6 +88,12 @@ export function entangleAsyncAction<S>(
6688 } ,
6789 } ;
6890 currentEntangledActionThenable = entangledThenable ;
91+ if ( enableDefaultTransitionIndicator ) {
92+ needsIsomorphicIndicator = true ;
93+ // We'll check if we need a default indicator in a microtask. Ensure
94+ // we have this scheduled even if no root is scheduled.
95+ ensureScheduleIsScheduled ( ) ;
96+ }
6997 }
7098 currentEntangledPendingCount ++ ;
7199 thenable . then ( pingEngtangledActionScope , pingEngtangledActionScope ) ;
@@ -86,6 +114,9 @@ function pingEngtangledActionScope() {
86114 }
87115 }
88116 clearEntangledAsyncTransitionTypes ( ) ;
117+ if ( pendingEntangledRoots === 0 ) {
118+ stopIsomorphicDefaultIndicator ( ) ;
119+ }
89120 if ( currentEntangledListeners !== null ) {
90121 // All the actions have finished. Close the entangled async action scope
91122 // and notify all the listeners.
@@ -98,6 +129,7 @@ function pingEngtangledActionScope() {
98129 currentEntangledListeners = null ;
99130 currentEntangledLane = NoLane ;
100131 currentEntangledActionThenable = null ;
132+ needsIsomorphicIndicator = false ;
101133 for ( let i = 0 ; i < listeners . length ; i ++ ) {
102134 const listener = listeners [ i ] ;
103135 listener ( ) ;
@@ -161,3 +193,71 @@ export function peekEntangledActionLane(): Lane {
161193export function peekEntangledActionThenable ( ) : Thenable < void > | null {
162194 return currentEntangledActionThenable ;
163195}
196+
197+ export function registerDefaultIndicator(
198+ onDefaultTransitionIndicator: () => void | ( ( ) => void ) ,
199+ ) : void {
200+ if ( ! enableDefaultTransitionIndicator ) {
201+ return ;
202+ }
203+ if (isomorphicDefaultTransitionIndicator === undefined) {
204+ isomorphicDefaultTransitionIndicator = onDefaultTransitionIndicator ;
205+ } else if (
206+ isomorphicDefaultTransitionIndicator !== onDefaultTransitionIndicator
207+ ) {
208+ isomorphicDefaultTransitionIndicator = null ;
209+ // Stop any on-going indicator since it's now ambiguous.
210+ stopIsomorphicDefaultIndicator ( ) ;
211+ }
212+ }
213+
214+ export function startIsomorphicDefaultIndicatorIfNeeded ( ) {
215+ if ( ! enableDefaultTransitionIndicator ) {
216+ return ;
217+ }
218+ if (!needsIsomorphicIndicator) {
219+ return ;
220+ }
221+ if (
222+ isomorphicDefaultTransitionIndicator != null &&
223+ pendingIsomorphicIndicator === null
224+ ) {
225+ try {
226+ pendingIsomorphicIndicator =
227+ isomorphicDefaultTransitionIndicator ( ) || noop ;
228+ } catch ( x ) {
229+ pendingIsomorphicIndicator = noop ;
230+ reportGlobalError ( x ) ;
231+ }
232+ }
233+ }
234+
235+ function stopIsomorphicDefaultIndicator() {
236+ if ( ! enableDefaultTransitionIndicator ) {
237+ return ;
238+ }
239+ if (pendingIsomorphicIndicator !== null) {
240+ const cleanup = pendingIsomorphicIndicator ;
241+ pendingIsomorphicIndicator = null ;
242+ cleanup ( ) ;
243+ }
244+ }
245+
246+ function releaseIsomorphicIndicator ( ) {
247+ if ( -- pendingEntangledRoots === 0 ) {
248+ stopIsomorphicDefaultIndicator ( ) ;
249+ }
250+ }
251+
252+ export function hasOngoingIsomorphicIndicator ( ) : boolean {
253+ return pendingIsomorphicIndicator !== null ;
254+ }
255+
256+ export function retainIsomorphicIndicator(): () => void {
257+ pendingEntangledRoots ++ ;
258+ return releaseIsomorphicIndicator ;
259+ }
260+
261+ export function markIsomorphicIndicatorHandled(): void {
262+ needsIsomorphicIndicator = false ;
263+ }
0 commit comments