main
js 90 lines 2.88 KB
Raw
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 // There needs to be an async gap to work around https://issues.chromium.org/u/1/issues/419746417.
42 setTimeout(startFakeNavigation, 20);
43 }
44 }
45
46 // $FlowFixMe[incompatible-type]
47 navigation.addEventListener('navigate', handleNavigate);
48 // $FlowFixMe[incompatible-type]
49 navigation.addEventListener('navigatesuccess', handleNavigateComplete);
50 // $FlowFixMe[incompatible-type]
51 navigation.addEventListener('navigateerror', handleNavigateComplete);
52
53 function startFakeNavigation() {
54 if (isCancelled) {
55 // We already stopped this Transition.
56 return;
57 }
58 if (navigation.transition) {
59 // There is an on-going Navigation already happening. Let's wait for it to
60 // finish before starting our fake one.
61 return;
62 }
63 // Trigger a fake navigation to the same page
64 const currentEntry = navigation.currentEntry;
65 if (currentEntry && currentEntry.url != null) {
66 navigation.navigate(currentEntry.url, {
67 state: currentEntry.getState(),
68 info: 'react-transition', // indicator to routers to ignore this navigation
69 history: 'replace',
70 });
71 }
72 }
73
74 // Delay the start a bit in case this is a fast Transition.
75 setTimeout(startFakeNavigation, 100);
76
77 return function () {
78 isCancelled = true;
79 // $FlowFixMe[incompatible-type]
80 navigation.removeEventListener('navigate', handleNavigate);
81 // $FlowFixMe[incompatible-type]
82 navigation.removeEventListener('navigatesuccess', handleNavigateComplete);
83 // $FlowFixMe[incompatible-type]
84 navigation.removeEventListener('navigateerror', handleNavigateComplete);
85 if (pendingResolve !== null) {
86 pendingResolve();
87 pendingResolve = null;
88 }
89 };
90 }