Skip to content

Commit 134a862

Browse files
committed
Stop any running View Transition when we cancel
If we have any scheduled work up until that point, we wait for it to commit first so that if the new commit is the same as the target we don't snap back first.
1 parent 515dbfe commit 134a862

File tree

4 files changed

+82
-19
lines changed

4 files changed

+82
-19
lines changed

packages/react-reconciler/src/ReactFiberGestureScheduler.js

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,17 @@
88
*/
99

1010
import type {FiberRoot} from './ReactInternalTypes';
11-
import type {GestureTimeline} from './ReactFiberConfig';
11+
import type {
12+
GestureTimeline,
13+
RunningGestureTransition,
14+
} from './ReactFiberConfig';
1215

13-
import {GestureLane} from './ReactFiberLane';
16+
import {GestureLane, NoLanes} from './ReactFiberLane';
1417
import {ensureRootIsScheduled} from './ReactFiberRootScheduler';
15-
import {subscribeToGestureDirection} from './ReactFiberConfig';
18+
import {
19+
subscribeToGestureDirection,
20+
stopGestureTransition,
21+
} from './ReactFiberConfig';
1622

1723
// This type keeps track of any scheduled or active gestures.
1824
export type ScheduledGesture = {
@@ -23,6 +29,7 @@ export type ScheduledGesture = {
2329
rangeCurrent: number, // The starting offset along the timeline.
2430
rangeNext: number, // The end along the timeline where the next state is reached.
2531
cancel: () => void, // Cancel the subscription to direction change.
32+
running: null | RunningGestureTransition, // Used to cancel the running transition after we're done.
2633
prev: null | ScheduledGesture, // The previous scheduled gesture in the queue for this root.
2734
next: null | ScheduledGesture, // The next scheduled gesture in the queue for this root.
2835
};
@@ -86,6 +93,7 @@ export function scheduleGesture(
8693
rangeCurrent: rangeCurrent,
8794
rangeNext: rangeNext,
8895
cancel: cancel,
96+
running: null,
8997
prev: prev,
9098
next: null,
9199
};
@@ -106,10 +114,30 @@ export function cancelScheduledGesture(
106114
if (gesture.count === 0) {
107115
const cancelDirectionSubscription = gesture.cancel;
108116
cancelDirectionSubscription();
109-
// Delete the scheduled gesture from the queue.
117+
// Delete the scheduled gesture from the pending queue.
110118
deleteScheduledGesture(root, gesture);
111119
// TODO: If we're currently rendering this gesture, we need to restart the render
112120
// on a different gesture or cancel the render..
121+
// TODO: We might want to pause the View Transition at this point since you should
122+
// no longer be able to update the position of anything but it might be better to
123+
// just commit the gesture state.
124+
if (gesture.running !== null) {
125+
if (root.pendingLanes === NoLanes) {
126+
// There's no work scheduled so we can stop the View Transition right away.
127+
stopGestureTransition(gesture.running);
128+
gesture.running = null;
129+
} else {
130+
// Otherwise we schedule the gesture to be stopped at the next commit.
131+
// This ensures that we don't snap back to the previous state until we have
132+
// had a chance to commit any resulting updates.
133+
const existing = root.stoppingGestures;
134+
if (existing !== null) {
135+
gesture.next = existing;
136+
existing.prev = gesture;
137+
}
138+
root.stoppingGestures = gesture;
139+
}
140+
}
113141
}
114142
}
115143

@@ -127,6 +155,10 @@ export function deleteScheduledGesture(
127155
root.pendingLanes &= ~GestureLane;
128156
}
129157
}
158+
if (root.stoppingGestures === gesture) {
159+
// This should not really happen the way we use it now but just in case we start.
160+
root.stoppingGestures = gesture.next;
161+
}
130162
} else {
131163
gesture.prev.next = gesture.next;
132164
if (gesture.next !== null) {
@@ -136,3 +168,18 @@ export function deleteScheduledGesture(
136168
gesture.next = null;
137169
}
138170
}
171+
172+
export function stopCompletedGestures(root: FiberRoot) {
173+
let gesture = root.stoppingGestures;
174+
root.stoppingGestures = null;
175+
while (gesture !== null) {
176+
if (gesture.running !== null) {
177+
stopGestureTransition(gesture.running);
178+
gesture.running = null;
179+
}
180+
const nextGesture = gesture.next;
181+
gesture.next = null;
182+
gesture.prev = null;
183+
gesture = nextGesture;
184+
}
185+
}

packages/react-reconciler/src/ReactFiberRoot.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ function FiberRootNode(
100100

101101
if (enableSwipeTransition) {
102102
this.pendingGestures = null;
103+
this.stoppingGestures = null;
103104
}
104105

105106
this.incompleteTransitions = new Map();

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,10 @@ import {
349349
import {getMaskedContext, getUnmaskedContext} from './ReactFiberContext';
350350
import {peekEntangledActionLane} from './ReactFiberAsyncAction';
351351
import {logUncaughtError} from './ReactFiberErrorLogger';
352-
import {deleteScheduledGesture} from './ReactFiberGestureScheduler';
352+
import {
353+
deleteScheduledGesture,
354+
stopCompletedGestures,
355+
} from './ReactFiberGestureScheduler';
353356

354357
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
355358

@@ -1451,7 +1454,11 @@ function commitRootWhenReady(
14511454
// components for the purposes of forming pairs.
14521455
accumulateSuspenseyCommit(finishedWork);
14531456
if (isViewTransitionEligible || isGestureTransition) {
1454-
suspendOnActiveViewTransition(root.containerInfo);
1457+
// If we're stopping gestures we don't have to wait for any pending
1458+
// view transition. We'll stop it when we commit.
1459+
if (!enableSwipeTransition || root.stoppingGestures === null) {
1460+
suspendOnActiveViewTransition(root.containerInfo);
1461+
}
14551462
}
14561463
// At the end, ask the renderer if it's ready to commit, or if we should
14571464
// suspend. If it's not ready, it will return a callback to subscribe to
@@ -3472,10 +3479,23 @@ function commitRoot(
34723479
ReactSharedInternals.T = prevTransition;
34733480
}
34743481
}
3482+
3483+
let willStartViewTransition = shouldStartViewTransition;
3484+
if (enableSwipeTransition) {
3485+
// Stop any gestures that were completed and is now being committed.
3486+
if (root.stoppingGestures !== null) {
3487+
stopCompletedGestures(root);
3488+
// If we are in the process of stopping some gesture we shouldn't start
3489+
// a View Transition because that would start from the previous state to
3490+
// the next state.
3491+
willStartViewTransition = false;
3492+
}
3493+
}
3494+
34753495
pendingEffectsStatus = PENDING_MUTATION_PHASE;
34763496
const startedViewTransition =
34773497
enableViewTransition &&
3478-
shouldStartViewTransition &&
3498+
willStartViewTransition &&
34793499
startViewTransition(
34803500
root.containerInfo,
34813501
pendingTransitionTypes,
@@ -3876,18 +3896,12 @@ function commitGestureOnRoot(
38763896
pendingTransitionTypes = null;
38773897
pendingEffectsStatus = PENDING_GESTURE_MUTATION_PHASE;
38783898

3879-
if (shouldStartViewTransition) {
3880-
startGestureTransition(
3881-
root.containerInfo,
3882-
pendingTransitionTypes,
3883-
flushGestureMutations,
3884-
flushGestureAnimations,
3885-
);
3886-
} else {
3887-
// Flush synchronously just to get through the state.
3888-
flushGestureMutations();
3889-
flushGestureAnimations();
3890-
}
3899+
finishedGesture.running = startGestureTransition(
3900+
root.containerInfo,
3901+
pendingTransitionTypes,
3902+
flushGestureMutations,
3903+
flushGestureAnimations,
3904+
);
38913905
}
38923906

38933907
function flushGestureMutations(): void {

packages/react-reconciler/src/ReactInternalTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ type BaseFiberRootProperties = {
285285

286286
// enableSwipeTransition only
287287
pendingGestures: null | ScheduledGesture,
288+
stoppingGestures: null | ScheduledGesture,
288289
};
289290

290291
// The following attributes are only used by DevTools and are only present in DEV builds.

0 commit comments

Comments
 (0)