import type {FiberRoot} from './ReactInternalTypes';
import type {GestureOptions} from 'shared/ReactTypes';
import type {GestureTimeline, RunningViewTransition} from './ReactFiberConfig';
import type {TransitionTypes} from 'react/src/ReactTransitionType';
import type {Lane} from './ReactFiberLane';
import {
GestureLane,
markRootEntangled,
markRootFinished,
NoLane,
NoLanes,
} from './ReactFiberLane';
import {
ensureRootIsScheduled,
requestTransitionLane,
} from './ReactFiberRootScheduler';
import {getCurrentGestureOffset, stopViewTransition} from './ReactFiberConfig';
import {pingGestureRoot, restartGestureRoot} from './ReactFiberWorkLoop';
export type ScheduledGesture = {
provider: GestureTimeline,
count: number,
rangeStart: number,
rangeEnd: number,
types: null | TransitionTypes,
running: null | RunningViewTransition,
commit: null | (() => void),
committing: boolean,
revertLane: Lane,
prev: null | ScheduledGesture,
next: null | ScheduledGesture,
};
export function scheduleGesture(
root: FiberRoot,
provider: GestureTimeline,
): ScheduledGesture {
let prev = root.pendingGestures;
while (prev !== null) {
if (prev.provider === provider) {
return prev;
}
const next = prev.next;
if (next === null) {
break;
}
prev = next;
}
const gesture: ScheduledGesture = {
provider: provider,
count: 0,
rangeStart: 0,
rangeEnd: 100,
types: null,
running: null,
commit: null,
committing: false,
revertLane: NoLane,
prev: prev,
next: null,
};
if (prev === null) {
root.pendingGestures = gesture;
} else {
prev.next = gesture;
}
ensureRootIsScheduled(root);
return gesture;
}
export function startScheduledGesture(
root: FiberRoot,
gestureTimeline: GestureTimeline,
gestureOptions: ?GestureOptions,
transitionTypes: null | TransitionTypes,
): null | ScheduledGesture {
const rangeStart =
gestureOptions && gestureOptions.rangeStart != null
? gestureOptions.rangeStart
: getCurrentGestureOffset(gestureTimeline);
const rangeEnd =
gestureOptions && gestureOptions.rangeEnd != null
? gestureOptions.rangeEnd
: rangeStart < 50
? 100
: 0;
let prev = root.pendingGestures;
while (prev !== null) {
if (prev.provider === gestureTimeline) {
prev.count++;
prev.rangeStart = rangeStart;
prev.rangeEnd = rangeEnd;
if (transitionTypes !== null) {
let scheduledTypes = prev.types;
if (scheduledTypes === null) {
scheduledTypes = prev.types = [];
}
for (let i = 0; i < transitionTypes.length; i++) {
const transitionType = transitionTypes[i];
if (scheduledTypes.indexOf(transitionType) === -1) {
scheduledTypes.push(transitionType);
}
}
}
return prev;
}
const next = prev.next;
if (next === null) {
break;
}
prev = next;
}
return null;
}
export function cancelScheduledGesture(
root: FiberRoot,
gesture: ScheduledGesture,
): void {
if (gesture.revertLane !== NoLane) {
const entangledLanes = gesture.revertLane | requestTransitionLane(null);
markRootEntangled(root, entangledLanes);
}
gesture.count--;
if (gesture.count === 0) {
let shouldCommit: boolean;
const finalOffset = getCurrentGestureOffset(gesture.provider);
const rangeStart = gesture.rangeStart;
const rangeEnd = gesture.rangeEnd;
if (rangeStart < rangeEnd) {
shouldCommit = finalOffset > rangeStart + (rangeEnd - rangeStart) / 2;
} else {
shouldCommit = finalOffset < rangeEnd + (rangeStart - rangeEnd) / 2;
}
const runningTransition = gesture.running;
if (runningTransition !== null && shouldCommit) {
gesture.committing = true;
if (root.pendingGestures === gesture) {
const commitCallback = gesture.commit;
if (commitCallback !== null) {
gesture.commit = null;
commitCallback();
} else {
pingGestureRoot(root);
}
}
} else {
if (gesture.prev === null) {
if (root.pendingGestures === gesture) {
root.pendingGestures = gesture.next;
let remainingLanes = root.pendingLanes;
if (root.pendingGestures === null) {
remainingLanes &= ~GestureLane;
}
markRootFinished(
root,
GestureLane,
remainingLanes,
NoLane,
NoLane,
NoLanes,
);
restartGestureRoot(root);
}
gesture.running = null;
if (runningTransition !== null) {
stopViewTransition(runningTransition);
}
} else {
gesture.prev.next = gesture.next;
if (gesture.next !== null) {
gesture.next.prev = gesture.prev;
}
gesture.prev = null;
gesture.next = null;
}
}
}
}
export function stopCommittedGesture(root: FiberRoot) {
const committedGesture = root.pendingGestures;
if (committedGesture !== null) {
committedGesture.committing = false;
const nextGesture = committedGesture.next;
if (nextGesture === null) {
root.pendingLanes &= ~GestureLane;
} else {
nextGesture.prev = null;
}
root.pendingGestures = nextGesture;
const runningTransition = committedGesture.running;
if (runningTransition !== null) {
committedGesture.running = null;
stopViewTransition(runningTransition);
}
}
}
export function scheduleGestureCommit(
gesture: ScheduledGesture,
callback: () => void,
): () => void {
gesture.commit = callback;
return function () {
gesture.commit = null;
};
}