import type {Fiber, FiberRoot} from './ReactInternalTypes';
import type {Instance, TextInstance} from './ReactFiberConfig';
import type {OffscreenState} from './ReactFiberActivityComponent';
import {
cloneMutableInstance,
cloneMutableTextInstance,
cloneRootViewTransitionContainer,
removeRootViewTransitionClone,
cancelRootViewTransitionName,
restoreRootViewTransitionName,
appendChild,
commitUpdate,
commitTextUpdate,
resetTextContent,
supportsResources,
supportsSingletons,
unhideInstance,
unhideTextInstance,
} from './ReactFiberConfig';
import {
popMutationContext,
pushMutationContext,
viewTransitionMutationContext,
} from './ReactFiberMutationTracking';
import {
MutationMask,
Update,
ContentReset,
NoFlags,
Visibility,
} from './ReactFiberFlags';
import {
HostComponent,
HostHoistable,
HostSingleton,
HostText,
HostPortal,
OffscreenComponent,
ViewTransitionComponent,
} from './ReactWorkTags';
let didWarnForRootClone = false;
function detectMutationOrInsertClones(finishedWork: Fiber): boolean {
return true;
}
let unhideHostChildren = false;
function recursivelyInsertClonesFromExistingTree(
parentFiber: Fiber,
hostParentClone: Instance,
): void {
let child = parentFiber.child;
while (child !== null) {
switch (child.tag) {
case HostComponent: {
const instance: Instance = child.stateNode;
const clone: Instance = cloneMutableInstance(instance, true);
appendChild(hostParentClone, clone);
if (unhideHostChildren) {
unhideInstance(clone, child.memoizedProps);
}
break;
}
case HostText: {
const textInstance: TextInstance = child.stateNode;
if (textInstance === null) {
throw new Error(
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}
const clone = cloneMutableTextInstance(textInstance);
appendChild(hostParentClone, clone);
if (unhideHostChildren) {
unhideTextInstance(clone, child.memoizedProps);
}
break;
}
case HostPortal: {
break;
}
case OffscreenComponent: {
const newState: OffscreenState | null = child.memoizedState;
const isHidden = newState !== null;
if (!isHidden) {
recursivelyInsertClonesFromExistingTree(child, hostParentClone);
}
break;
}
case ViewTransitionComponent:
const prevMutationContext = pushMutationContext();
recursivelyInsertClonesFromExistingTree(child, hostParentClone);
popMutationContext(prevMutationContext);
break;
default: {
recursivelyInsertClonesFromExistingTree(child, hostParentClone);
break;
}
}
child = child.sibling;
}
}
function recursivelyInsertClones(
parentFiber: Fiber,
hostParentClone: Instance,
) {
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
}
}
if (
parentFiber.alternate === null ||
(parentFiber.subtreeFlags & MutationMask) !== NoFlags
) {
let child = parentFiber.child;
while (child !== null) {
insertDestinationClonesOfFiber(child, hostParentClone);
child = child.sibling;
}
} else {
recursivelyInsertClonesFromExistingTree(parentFiber, hostParentClone);
}
}
function insertDestinationClonesOfFiber(
finishedWork: Fiber,
hostParentClone: Instance,
) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case HostHoistable: {
if (supportsResources) {
recursivelyInsertClones(finishedWork, hostParentClone);
break;
}
}
case HostSingleton: {
if (supportsSingletons) {
recursivelyInsertClones(finishedWork, hostParentClone);
if (__DEV__) {
if (flags & Update) {
if (current === null) {
console.error(
'useSwipeTransition() caused something to render a new <%s>. ' +
'This is not possible in the current implementation. ' +
"Make sure that the swipe doesn't mount any new <%s> elements.",
finishedWork.type,
finishedWork.type,
);
} else {
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
const instance = finishedWork.stateNode;
const type = finishedWork.type;
const prev = pushMutationContext();
try {
commitUpdate(instance, type, oldProps, newProps, finishedWork);
if (viewTransitionMutationContext) {
console.error(
'useSwipeTransition() caused something to mutate <%s>. ' +
'This is not possible in the current implementation. ' +
"Make sure that the swipe doesn't update any state which " +
'causes <%s> to change.',
finishedWork.type,
finishedWork.type,
);
}
commitUpdate(instance, type, newProps, oldProps, finishedWork);
} finally {
popMutationContext(prev);
}
}
}
}
break;
}
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
if (current === null) {
appendChild(hostParentClone, instance);
} else {
let clone: Instance;
if (finishedWork.child === null) {
clone = cloneMutableInstance(instance, true);
if (finishedWork.flags & ContentReset) {
resetTextContent(clone);
}
} else {
clone = cloneMutableInstance(instance, false);
}
if (flags & Update) {
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
const type = finishedWork.type;
commitUpdate(clone, type, oldProps, newProps, finishedWork);
}
if (unhideHostChildren) {
unhideHostChildren = false;
recursivelyInsertClones(finishedWork, clone);
appendChild(hostParentClone, clone);
unhideHostChildren = true;
unhideInstance(clone, finishedWork.memoizedProps);
} else {
recursivelyInsertClones(finishedWork, clone);
appendChild(hostParentClone, clone);
}
}
break;
}
case HostText: {
const textInstance: TextInstance = finishedWork.stateNode;
if (textInstance === null) {
throw new Error(
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}
if (current === null) {
appendChild(hostParentClone, textInstance);
} else {
const clone = cloneMutableTextInstance(textInstance);
if (flags & Update) {
const newText: string = finishedWork.memoizedProps;
const oldText: string = current.memoizedProps;
commitTextUpdate(clone, newText, oldText);
}
appendChild(hostParentClone, clone);
if (unhideHostChildren) {
unhideTextInstance(clone, finishedWork.memoizedProps);
}
}
break;
}
case HostPortal: {
break;
}
case OffscreenComponent: {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
if (!isHidden) {
const prevUnhide = unhideHostChildren;
unhideHostChildren = prevUnhide || (flags & Visibility) !== NoFlags;
recursivelyInsertClones(finishedWork, hostParentClone);
unhideHostChildren = prevUnhide;
}
break;
}
case ViewTransitionComponent:
const prevMutationContext = pushMutationContext();
recursivelyInsertClones(finishedWork, hostParentClone);
if (viewTransitionMutationContext) {
finishedWork.flags |= Update;
}
popMutationContext(prevMutationContext);
break;
default: {
recursivelyInsertClones(finishedWork, hostParentClone);
break;
}
}
}
export function insertDestinationClones(
root: FiberRoot,
finishedWork: Fiber,
): void {
unhideHostChildren = false;
const needsClone = detectMutationOrInsertClones(finishedWork);
if (needsClone) {
if (__DEV__) {
if (!didWarnForRootClone) {
didWarnForRootClone = true;
console.warn(
'useSwipeTransition() caused something to mutate or relayout the root. ' +
'This currently requires a clone of the whole document. Make sure to ' +
'add a <ViewTransition> directly around an absolutely positioned DOM node ' +
'to minimize the impact of any changes caused by the Swipe Transition.',
);
}
}
const rootClone = cloneRootViewTransitionContainer(root.containerInfo);
root.gestureClone = rootClone;
recursivelyInsertClones(finishedWork, rootClone);
} else {
root.gestureClone = null;
cancelRootViewTransitionName(root.containerInfo);
}
}
export function applyDepartureTransitions(
root: FiberRoot,
finishedWork: Fiber,
): void {
const rootClone = root.gestureClone;
if (rootClone !== null) {
root.gestureClone = null;
removeRootViewTransitionClone(root.containerInfo, rootClone);
}
}
export function startGestureAnimations(
root: FiberRoot,
finishedWork: Fiber,
): void {
restoreRootViewTransitionName(root.containerInfo);
}