Skip to content

Commit 2a344fa

Browse files
committed
Implement Navigation API backed default indicator
1 parent 8901a72 commit 2a344fa

File tree

2 files changed

+90
-5
lines changed

2 files changed

+90
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
export function defaultOnDefaultTransitionIndicator(): void | (() => void) {
11+
if (typeof navigation !== 'object') {
12+
// If the Navigation API is not available, then this is a noop.
13+
return;
14+
}
15+
16+
let isCancelled = false;
17+
let pendingResolve: null | (() => void) = null;
18+
19+
function handleNavigate(event: NavigateEvent) {
20+
if (event.canIntercept && event.info === 'react-transition') {
21+
event.intercept({
22+
handler() {
23+
return new Promise(resolve => (pendingResolve = resolve));
24+
},
25+
focusReset: 'manual',
26+
scroll: 'manual',
27+
});
28+
}
29+
}
30+
31+
function handleNavigateComplete() {
32+
if (pendingResolve !== null) {
33+
// If this was not our navigation completing, we were probably cancelled.
34+
// We'll start a new one below.
35+
pendingResolve();
36+
pendingResolve = null;
37+
}
38+
if (!isCancelled) {
39+
// Some other navigation completed but we should still be running.
40+
// Start another fake one to keep the loading indicator going.
41+
startFakeNavigation();
42+
}
43+
}
44+
45+
// $FlowFixMe
46+
navigation.addEventListener('navigate', handleNavigate);
47+
// $FlowFixMe
48+
navigation.addEventListener('navigatesuccess', handleNavigateComplete);
49+
// $FlowFixMe
50+
navigation.addEventListener('navigateerror', handleNavigateComplete);
51+
52+
function startFakeNavigation() {
53+
if (isCancelled) {
54+
// We already stopped this Transition.
55+
return;
56+
}
57+
if (navigation.transition) {
58+
// There is an on-going Navigation already happening. Let's wait for it to
59+
// finish before starting our fake one.
60+
return;
61+
}
62+
// Trigger a fake navigation to the same page
63+
const currentEntry = navigation.currentEntry;
64+
if (currentEntry && currentEntry.url != null) {
65+
navigation.navigate(currentEntry.url, {
66+
state: currentEntry.getState(),
67+
info: 'react-transition', // indicator to routers to ignore this navigation
68+
history: 'replace',
69+
});
70+
}
71+
}
72+
73+
// Delay the start a bit in case this is a fast navigation.
74+
setTimeout(startFakeNavigation, 100);
75+
76+
return function () {
77+
isCancelled = true;
78+
// $FlowFixMe
79+
navigation.removeEventListener('navigate', handleNavigate);
80+
// $FlowFixMe
81+
navigation.removeEventListener('navigatesuccess', handleNavigateComplete);
82+
// $FlowFixMe
83+
navigation.removeEventListener('navigateerror', handleNavigateComplete);
84+
if (pendingResolve !== null) {
85+
pendingResolve();
86+
pendingResolve = null;
87+
}
88+
};
89+
}

packages/react-dom/src/client/ReactDOMRoot.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,9 @@ import {
9595
defaultOnCaughtError,
9696
defaultOnRecoverableError,
9797
} from 'react-reconciler/src/ReactFiberReconciler';
98+
import {defaultOnDefaultTransitionIndicator} from './ReactDOMDefaultTransitionIndicator';
9899
import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
99100

100-
function defaultOnDefaultTransitionIndicator(): void | (() => void) {
101-
// TODO: Implement the default
102-
return function () {};
103-
}
104-
105101
// $FlowFixMe[missing-this-annot]
106102
function ReactDOMRoot(internalRoot: FiberRoot) {
107103
this._internalRoot = internalRoot;

0 commit comments

Comments
 (0)