mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
[DOM] Scroll to text siblings of empty Fragments instead of the parent (#37060)
When a Fragment has no children, React would consider scrolling to siblings first and then to parents. However, React only considered `HostComponent` for the siblings. Since we already have a heuristic for scrolling to `HostText`, we can reuse that same heuristic. --------- Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
Hanzo Dev
parent
25a2a2926e
commit
fe4075812c
+124
-60
@@ -62,13 +62,13 @@ import {
|
||||
isOwnedInstance,
|
||||
} from './ReactDOMComponentTree';
|
||||
import {
|
||||
traverseFragmentInstance,
|
||||
getFragmentParentHostFiber,
|
||||
traverseFragmentInstancesAndTextInstances,
|
||||
getFragmentParentInstanceOrContainerFiber,
|
||||
getInstanceFromHostFiber,
|
||||
isFiberFollowing,
|
||||
isFiberPreceding,
|
||||
getFragmentInstanceSiblings,
|
||||
traverseFragmentInstanceDeeply,
|
||||
getFragmentInstanceOrTextInstanceSiblings,
|
||||
traverseFragmentInstancesAndTextInstancesDeeply,
|
||||
fiberIsPortaledIntoHost,
|
||||
isFiberContainedByFragment,
|
||||
isFragmentContainedByFiber,
|
||||
@@ -223,6 +223,9 @@ export type TextInstance = Text;
|
||||
type InstanceWithFragmentHandles = Instance & {
|
||||
reactFragments?: Set<FragmentInstanceType>,
|
||||
};
|
||||
type HostNodeWithFragmentHandles = (Instance | TextInstance) & {
|
||||
reactFragments?: Set<FragmentInstanceType>,
|
||||
};
|
||||
|
||||
declare class ActivityInterface extends Comment {}
|
||||
declare class SuspenseInterface extends Comment {
|
||||
@@ -3036,7 +3039,7 @@ FragmentInstance.prototype.addEventListener = function (
|
||||
indexOfEventListener(listeners, type, listener, optionsOrUseCapture) === -1;
|
||||
if (isNewEventListener) {
|
||||
listeners.push({type, listener, optionsOrUseCapture});
|
||||
traverseFragmentInstance(
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
addEventListenerToChild,
|
||||
type,
|
||||
@@ -3052,7 +3055,7 @@ function addEventListenerToChild(
|
||||
listener: EventListener,
|
||||
optionsOrUseCapture?: EventListenerOptionsOrUseCapture,
|
||||
): boolean {
|
||||
const instance = getInstanceFromHostFiber<Instance>(child);
|
||||
const instance = getInstanceFromHostFiber<Instance | TextInstance>(child);
|
||||
instance.addEventListener(type, listener, optionsOrUseCapture);
|
||||
return false;
|
||||
}
|
||||
@@ -3068,7 +3071,7 @@ FragmentInstance.prototype.removeEventListener = function (
|
||||
return;
|
||||
}
|
||||
if (typeof listeners !== 'undefined' && listeners.length > 0) {
|
||||
traverseFragmentInstance(
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
removeEventListenerFromChild,
|
||||
type,
|
||||
@@ -3092,7 +3095,7 @@ function removeEventListenerFromChild(
|
||||
listener: EventListener,
|
||||
optionsOrUseCapture?: EventListenerOptionsOrUseCapture,
|
||||
): boolean {
|
||||
const instance = getInstanceFromHostFiber<Instance>(child);
|
||||
const instance = getInstanceFromHostFiber<Instance | TextInstance>(child);
|
||||
instance.removeEventListener(type, listener, optionsOrUseCapture);
|
||||
return false;
|
||||
}
|
||||
@@ -3136,12 +3139,15 @@ FragmentInstance.prototype.dispatchEvent = function (
|
||||
this: FragmentInstanceType,
|
||||
event: Event,
|
||||
): boolean {
|
||||
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
|
||||
const parentHostFiber = getFragmentParentInstanceOrContainerFiber(
|
||||
this._fragmentFiber,
|
||||
);
|
||||
if (parentHostFiber === null) {
|
||||
return true;
|
||||
}
|
||||
const parentHostInstance =
|
||||
getInstanceFromHostFiber<Instance>(parentHostFiber);
|
||||
const parentHostInstance = getInstanceFromHostFiber<Instance | Container>(
|
||||
parentHostFiber,
|
||||
);
|
||||
const eventListeners = this._eventListeners;
|
||||
if (
|
||||
(eventListeners !== null && eventListeners.length > 0) ||
|
||||
@@ -3173,7 +3179,7 @@ FragmentInstance.prototype.focus = function (
|
||||
this: FragmentInstanceType,
|
||||
focusOptions?: FocusOptions,
|
||||
): void {
|
||||
traverseFragmentInstanceDeeply(
|
||||
traverseFragmentInstancesAndTextInstancesDeeply(
|
||||
this._fragmentFiber,
|
||||
setFocusOnFiberIfFocusable,
|
||||
focusOptions,
|
||||
@@ -3198,7 +3204,7 @@ FragmentInstance.prototype.focusLast = function (
|
||||
focusOptions?: FocusOptions,
|
||||
): void {
|
||||
const children: Array<Fiber> = [];
|
||||
traverseFragmentInstanceDeeply(
|
||||
traverseFragmentInstancesAndTextInstancesDeeply(
|
||||
this._fragmentFiber,
|
||||
collectChildren,
|
||||
children,
|
||||
@@ -3217,18 +3223,25 @@ function collectChildren(child: Fiber, collection: Array<Fiber>): boolean {
|
||||
// $FlowFixMe[prop-missing]
|
||||
FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
|
||||
// Early exit if activeElement is not within the fragment's parent
|
||||
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
|
||||
const parentHostFiber = getFragmentParentInstanceOrContainerFiber(
|
||||
this._fragmentFiber,
|
||||
);
|
||||
if (parentHostFiber === null) {
|
||||
return;
|
||||
}
|
||||
const parentHostInstance =
|
||||
getInstanceFromHostFiber<Instance>(parentHostFiber);
|
||||
const activeElement = parentHostInstance.ownerDocument.activeElement;
|
||||
if (activeElement === null || !parentHostInstance.contains(activeElement)) {
|
||||
const parentInstanceOrContainer = getInstanceFromHostFiber<
|
||||
Instance | Container,
|
||||
>(parentHostFiber);
|
||||
// TODO: Handle parentInstanceOrContainer being a document
|
||||
const activeElement = parentInstanceOrContainer.ownerDocument.activeElement;
|
||||
if (
|
||||
activeElement === null ||
|
||||
!parentInstanceOrContainer.contains(activeElement)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
traverseFragmentInstance(
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
blurActiveElementWithinFragment,
|
||||
activeElement,
|
||||
@@ -3259,16 +3272,19 @@ FragmentInstance.prototype.observeUsing = function (
|
||||
if (enableFragmentRefsTextNodes) {
|
||||
let hasText = false;
|
||||
let hasElement = false;
|
||||
traverseFragmentInstance(this._fragmentFiber, (child: Fiber) => {
|
||||
if (child.tag === HostText) {
|
||||
hasText = true;
|
||||
} else {
|
||||
// Stop traversal, found element
|
||||
hasElement = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
(child: Fiber) => {
|
||||
if (child.tag === HostText) {
|
||||
hasText = true;
|
||||
} else {
|
||||
// Stop traversal, found element
|
||||
hasElement = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
if (hasText && !hasElement) {
|
||||
console.error(
|
||||
'observeUsing() was called on a FragmentInstance with only text children. ' +
|
||||
@@ -3281,7 +3297,11 @@ FragmentInstance.prototype.observeUsing = function (
|
||||
this._observers = new Set();
|
||||
}
|
||||
this._observers.add(observer);
|
||||
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
observeChild,
|
||||
observer,
|
||||
);
|
||||
};
|
||||
function observeChild(
|
||||
child: Fiber,
|
||||
@@ -3312,7 +3332,11 @@ FragmentInstance.prototype.unobserveUsing = function (
|
||||
}
|
||||
} else {
|
||||
observers.delete(observer);
|
||||
traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer);
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
unobserveChild,
|
||||
observer,
|
||||
);
|
||||
}
|
||||
};
|
||||
function unobserveChild(
|
||||
@@ -3334,7 +3358,11 @@ FragmentInstance.prototype.getClientRects = function (
|
||||
this: FragmentInstanceType,
|
||||
): Array<DOMRect> {
|
||||
const rects: Array<DOMRect> = [];
|
||||
traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects);
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
collectClientRects,
|
||||
rects,
|
||||
);
|
||||
return rects;
|
||||
};
|
||||
function collectClientRects(child: Fiber, rects: Array<DOMRect>): boolean {
|
||||
@@ -3356,12 +3384,15 @@ FragmentInstance.prototype.getRootNode = function (
|
||||
this: FragmentInstanceType,
|
||||
getRootNodeOptions?: {composed: boolean},
|
||||
): Document | ShadowRoot | FragmentInstanceType {
|
||||
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
|
||||
const parentHostFiber = getFragmentParentInstanceOrContainerFiber(
|
||||
this._fragmentFiber,
|
||||
);
|
||||
if (parentHostFiber === null) {
|
||||
return this;
|
||||
}
|
||||
const parentHostInstance =
|
||||
getInstanceFromHostFiber<Instance>(parentHostFiber);
|
||||
const parentHostInstance = getInstanceFromHostFiber<Instance | Container>(
|
||||
parentHostFiber,
|
||||
);
|
||||
const rootNode =
|
||||
// $FlowFixMe[incompatible-type] Flow expects Node
|
||||
parentHostInstance.getRootNode(getRootNodeOptions) as Document | ShadowRoot;
|
||||
@@ -3372,14 +3403,21 @@ FragmentInstance.prototype.compareDocumentPosition = function (
|
||||
this: FragmentInstanceType,
|
||||
otherNode: Instance,
|
||||
): number {
|
||||
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
|
||||
const parentHostFiber = getFragmentParentInstanceOrContainerFiber(
|
||||
this._fragmentFiber,
|
||||
);
|
||||
if (parentHostFiber === null) {
|
||||
return Node.DOCUMENT_POSITION_DISCONNECTED;
|
||||
}
|
||||
const children: Array<Fiber> = [];
|
||||
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
|
||||
const parentHostInstance =
|
||||
getInstanceFromHostFiber<Instance>(parentHostFiber);
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
collectChildren,
|
||||
children,
|
||||
);
|
||||
const parentHostInstance = getInstanceFromHostFiber<Instance | Container>(
|
||||
parentHostFiber,
|
||||
);
|
||||
|
||||
if (children.length === 0) {
|
||||
return compareDocumentPositionForEmptyFragment(
|
||||
@@ -3390,8 +3428,10 @@ FragmentInstance.prototype.compareDocumentPosition = function (
|
||||
);
|
||||
}
|
||||
|
||||
const firstNode = getInstanceFromHostFiber<Instance>(children[0]);
|
||||
const lastNode = getInstanceFromHostFiber<Instance>(
|
||||
const firstNode = getInstanceFromHostFiber<Instance | TextInstance>(
|
||||
children[0],
|
||||
);
|
||||
const lastNode = getInstanceFromHostFiber<Instance | TextInstance>(
|
||||
children[children.length - 1],
|
||||
);
|
||||
|
||||
@@ -3510,6 +3550,19 @@ function validateDocumentPositionWithFiberTree(
|
||||
return false;
|
||||
}
|
||||
|
||||
function scrollTextNodeIntoView(
|
||||
textNode: TextInstance,
|
||||
resolvedAlignToTop: boolean,
|
||||
): void {
|
||||
const range = textNode.ownerDocument.createRange();
|
||||
range.selectNodeContents(textNode);
|
||||
const rect = range.getBoundingClientRect();
|
||||
const scrollY = resolvedAlignToTop
|
||||
? window.scrollY + rect.top
|
||||
: window.scrollY + rect.bottom - window.innerHeight;
|
||||
window.scrollTo(window.scrollX + rect.left, scrollY);
|
||||
}
|
||||
|
||||
if (enableFragmentRefsScrollIntoView) {
|
||||
// $FlowFixMe[prop-missing]
|
||||
FragmentInstance.prototype.scrollIntoView = function (
|
||||
@@ -3524,17 +3577,23 @@ if (enableFragmentRefsScrollIntoView) {
|
||||
}
|
||||
// First, get the children nodes
|
||||
const children: Array<Fiber> = [];
|
||||
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
collectChildren,
|
||||
children,
|
||||
);
|
||||
|
||||
const resolvedAlignToTop = alignToTop !== false;
|
||||
|
||||
// If there are no children, we can use the parent and siblings to determine a position
|
||||
if (children.length === 0) {
|
||||
const hostSiblings = getFragmentInstanceSiblings(this._fragmentFiber);
|
||||
const hostSiblings = getFragmentInstanceOrTextInstanceSiblings(
|
||||
this._fragmentFiber,
|
||||
);
|
||||
const targetFiber = resolvedAlignToTop
|
||||
? hostSiblings[1] ||
|
||||
hostSiblings[0] ||
|
||||
getFragmentParentHostFiber(this._fragmentFiber)
|
||||
getFragmentParentInstanceOrContainerFiber(this._fragmentFiber)
|
||||
: hostSiblings[0] || hostSiblings[1];
|
||||
|
||||
if (targetFiber === null) {
|
||||
@@ -3546,7 +3605,19 @@ if (enableFragmentRefsScrollIntoView) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const target = getInstanceFromHostFiber<Instance>(targetFiber);
|
||||
// For text node siblings, use Range API to scroll to their position
|
||||
if (enableFragmentRefsTextNodes && targetFiber.tag === HostText) {
|
||||
const textNode = getInstanceFromHostFiber<TextInstance>(targetFiber);
|
||||
scrollTextNodeIntoView(textNode, resolvedAlignToTop);
|
||||
return;
|
||||
}
|
||||
const target = getInstanceFromHostFiber<Instance | Container>(
|
||||
targetFiber,
|
||||
);
|
||||
// TODO: If the parent host fiber is a HostRoot, the target is a
|
||||
// Container which can be a Document or DocumentFragment. Those have no
|
||||
// scrollIntoView method, so this crashes at runtime.
|
||||
// $FlowFixMe[prop-missing]
|
||||
target.scrollIntoView(alignToTop);
|
||||
return;
|
||||
}
|
||||
@@ -3556,14 +3627,8 @@ if (enableFragmentRefsScrollIntoView) {
|
||||
const child = children[i];
|
||||
// For text nodes, use Range API to scroll to their position
|
||||
if (enableFragmentRefsTextNodes && child.tag === HostText) {
|
||||
const textNode: Text = child.stateNode;
|
||||
const range = textNode.ownerDocument.createRange();
|
||||
range.selectNodeContents(textNode);
|
||||
const rect = range.getBoundingClientRect();
|
||||
const scrollY = resolvedAlignToTop
|
||||
? window.scrollY + rect.top
|
||||
: window.scrollY + rect.bottom - window.innerHeight;
|
||||
window.scrollTo(window.scrollX + rect.left, scrollY);
|
||||
const textNode = getInstanceFromHostFiber<TextInstance>(child);
|
||||
scrollTextNodeIntoView(textNode, resolvedAlignToTop);
|
||||
i += resolvedAlignToTop ? -1 : 1;
|
||||
continue;
|
||||
}
|
||||
@@ -3579,17 +3644,16 @@ function addFragmentHandleToFiber(
|
||||
fragmentInstance: FragmentInstanceType,
|
||||
): boolean {
|
||||
if (enableFragmentRefsInstanceHandles) {
|
||||
const instance =
|
||||
getInstanceFromHostFiber<InstanceWithFragmentHandles>(child);
|
||||
if (instance != null) {
|
||||
addFragmentHandleToInstance(instance, fragmentInstance);
|
||||
}
|
||||
const instance = getInstanceFromHostFiber<Instance | TextInstance>(
|
||||
child,
|
||||
) as any as HostNodeWithFragmentHandles;
|
||||
addFragmentHandleToInstance(instance, fragmentInstance);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function addFragmentHandleToInstance(
|
||||
instance: InstanceWithFragmentHandles,
|
||||
instance: HostNodeWithFragmentHandles,
|
||||
fragmentInstance: FragmentInstanceType,
|
||||
): void {
|
||||
if (enableFragmentRefsInstanceHandles) {
|
||||
@@ -3605,7 +3669,7 @@ export function createFragmentInstance(
|
||||
): FragmentInstanceType {
|
||||
const fragmentInstance = new (FragmentInstance as any)(fragmentFiber);
|
||||
if (enableFragmentRefsInstanceHandles) {
|
||||
traverseFragmentInstance(
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
fragmentFiber,
|
||||
addFragmentHandleToFiber,
|
||||
fragmentInstance,
|
||||
|
||||
@@ -2755,6 +2755,48 @@ describe('FragmentRefs', () => {
|
||||
restoreRange();
|
||||
});
|
||||
|
||||
// @gate enableFragmentRefs && enableFragmentRefsTextNodes && enableFragmentRefsScrollIntoView
|
||||
it('scrollIntoView scrolls to text siblings of an empty fragment using the Range API', async () => {
|
||||
const restoreRange = mockRangeClientRects([
|
||||
{x: 100, y: 200, width: 80, height: 16},
|
||||
]);
|
||||
const fragmentRef = React.createRef();
|
||||
const parentRef = React.createRef();
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() =>
|
||||
root.render(
|
||||
<div ref={parentRef}>
|
||||
Text before
|
||||
<Fragment ref={fragmentRef} />
|
||||
Text after
|
||||
</div>,
|
||||
),
|
||||
);
|
||||
|
||||
const parentScrollMock = jest.fn();
|
||||
parentRef.current.scrollIntoView = parentScrollMock;
|
||||
// Mock window.scrollTo to verify Range-based text scrolling
|
||||
const originalScrollTo = window.scrollTo;
|
||||
const scrollToMock = jest.fn();
|
||||
window.scrollTo = scrollToMock;
|
||||
|
||||
// Default call scrolls to the following text sibling
|
||||
fragmentRef.current.scrollIntoView();
|
||||
expect(scrollToMock).toHaveBeenCalledTimes(1);
|
||||
expect(parentScrollMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
scrollToMock.mockClear();
|
||||
|
||||
// alignToTop=false scrolls to the preceding text sibling
|
||||
fragmentRef.current.scrollIntoView(false);
|
||||
expect(scrollToMock).toHaveBeenCalledTimes(1);
|
||||
expect(parentScrollMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
window.scrollTo = originalScrollTo;
|
||||
restoreRange();
|
||||
});
|
||||
|
||||
// @gate enableFragmentRefs
|
||||
it('treats passive:true and passive:false as same listener per DOM spec', async () => {
|
||||
const fragmentRef = React.createRef();
|
||||
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||
import {HostText} from 'react-reconciler/src/ReactWorkTags';
|
||||
import {
|
||||
getFragmentParentHostFiber,
|
||||
traverseFragmentInstance,
|
||||
getFragmentParentInstanceOrContainerFiber,
|
||||
traverseFragmentInstancesAndTextInstances,
|
||||
} from 'react-reconciler/src/ReactFiberTreeReflection';
|
||||
|
||||
// Modules provided by RN:
|
||||
@@ -704,7 +704,11 @@ FragmentInstance.prototype.observeUsing = function (
|
||||
this._observers = new Set();
|
||||
}
|
||||
this._observers.add(observer);
|
||||
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
observeChild,
|
||||
observer,
|
||||
);
|
||||
};
|
||||
function observeChild(child: Fiber, observer: IntersectionObserver) {
|
||||
// $FlowFixMe[incompatible-type]
|
||||
@@ -728,7 +732,11 @@ FragmentInstance.prototype.unobserveUsing = function (
|
||||
}
|
||||
} else {
|
||||
observers.delete(observer);
|
||||
traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer);
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
unobserveChild,
|
||||
observer,
|
||||
);
|
||||
}
|
||||
};
|
||||
function unobserveChild(child: Fiber, observer: IntersectionObserver) {
|
||||
@@ -744,12 +752,18 @@ FragmentInstance.prototype.compareDocumentPosition = function (
|
||||
this: FragmentInstanceType,
|
||||
otherNode: PublicInstance,
|
||||
): number {
|
||||
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
|
||||
const parentHostFiber = getFragmentParentInstanceOrContainerFiber(
|
||||
this._fragmentFiber,
|
||||
);
|
||||
if (parentHostFiber === null) {
|
||||
return Node.DOCUMENT_POSITION_DISCONNECTED;
|
||||
}
|
||||
const children: Array<Fiber> = [];
|
||||
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
collectChildren,
|
||||
children,
|
||||
);
|
||||
if (children.length === 0) {
|
||||
const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber);
|
||||
return compareDocumentPositionForEmptyFragment<PublicInstance>(
|
||||
@@ -804,7 +818,9 @@ FragmentInstance.prototype.getRootNode = function (
|
||||
this: FragmentInstanceType,
|
||||
getRootNodeOptions?: {composed: boolean},
|
||||
): Node | FragmentInstanceType {
|
||||
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
|
||||
const parentHostFiber = getFragmentParentInstanceOrContainerFiber(
|
||||
this._fragmentFiber,
|
||||
);
|
||||
if (parentHostFiber === null) {
|
||||
return this;
|
||||
}
|
||||
@@ -819,7 +835,11 @@ FragmentInstance.prototype.getClientRects = function (
|
||||
this: FragmentInstanceType,
|
||||
): Array<DOMRect> {
|
||||
const rects: Array<DOMRect> = [];
|
||||
traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects);
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
this._fragmentFiber,
|
||||
collectClientRects,
|
||||
rects,
|
||||
);
|
||||
return rects;
|
||||
};
|
||||
function collectClientRects(child: Fiber, rects: Array<DOMRect>): boolean {
|
||||
@@ -867,7 +887,7 @@ export function createFragmentInstance(
|
||||
): FragmentInstanceType {
|
||||
const fragmentInstance = new (FragmentInstance as any)(fragmentFiber);
|
||||
if (enableFragmentRefsInstanceHandles) {
|
||||
traverseFragmentInstance(
|
||||
traverseFragmentInstancesAndTextInstances(
|
||||
fragmentFiber,
|
||||
addFragmentHandleToFiber,
|
||||
fragmentInstance,
|
||||
|
||||
+69
-19
@@ -10,6 +10,8 @@
|
||||
import type {Fiber} from './ReactInternalTypes';
|
||||
import type {
|
||||
Container,
|
||||
Instance,
|
||||
TextInstance,
|
||||
ActivityInstance,
|
||||
SuspenseInstance,
|
||||
} from './ReactFiberConfig';
|
||||
@@ -345,27 +347,41 @@ export function doesFiberContain(
|
||||
return false;
|
||||
}
|
||||
|
||||
export function traverseFragmentInstance<A, B, C>(
|
||||
export function traverseFragmentInstancesAndTextInstances<A, B, C>(
|
||||
fragmentFiber: Fiber,
|
||||
fn: (Fiber, A, B, C) => boolean,
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
): void {
|
||||
traverseVisibleHostChildren(fragmentFiber.child, false, fn, a, b, c);
|
||||
traverseVisibleInstancesAndTextInstances(
|
||||
fragmentFiber.child,
|
||||
false,
|
||||
fn,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
);
|
||||
}
|
||||
|
||||
export function traverseFragmentInstanceDeeply<A, B, C>(
|
||||
export function traverseFragmentInstancesAndTextInstancesDeeply<A, B, C>(
|
||||
fragmentFiber: Fiber,
|
||||
fn: (Fiber, A, B, C) => boolean,
|
||||
a: A,
|
||||
b: B,
|
||||
c: C,
|
||||
): void {
|
||||
traverseVisibleHostChildren(fragmentFiber.child, true, fn, a, b, c);
|
||||
traverseVisibleInstancesAndTextInstances(
|
||||
fragmentFiber.child,
|
||||
true,
|
||||
fn,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
);
|
||||
}
|
||||
|
||||
function traverseVisibleHostChildren<A, B, C>(
|
||||
function traverseVisibleInstancesAndTextInstances<A, B, C>(
|
||||
child: Fiber | null,
|
||||
searchWithinHosts: boolean,
|
||||
fn: (Fiber, A, B, C) => boolean,
|
||||
@@ -387,7 +403,14 @@ function traverseVisibleHostChildren<A, B, C>(
|
||||
} else {
|
||||
if (
|
||||
(searchWithinHosts || child.tag !== HostComponent) &&
|
||||
traverseVisibleHostChildren(child.child, searchWithinHosts, fn, a, b, c)
|
||||
traverseVisibleInstancesAndTextInstances(
|
||||
child.child,
|
||||
searchWithinHosts,
|
||||
fn,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -397,7 +420,9 @@ function traverseVisibleHostChildren<A, B, C>(
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getFragmentParentHostFiber(fiber: Fiber): null | Fiber {
|
||||
export function getFragmentParentInstanceOrContainerFiber(
|
||||
fiber: Fiber,
|
||||
): null | Fiber {
|
||||
let parent = fiber.return;
|
||||
while (parent !== null) {
|
||||
if (parent.tag === HostRoot || parent.tag === HostComponent) {
|
||||
@@ -424,20 +449,27 @@ export function fiberIsPortaledIntoHost(fiber: Fiber): boolean {
|
||||
return foundPortalParent;
|
||||
}
|
||||
|
||||
export function getFragmentInstanceSiblings(
|
||||
export function getFragmentInstanceOrTextInstanceSiblings(
|
||||
fiber: Fiber,
|
||||
): [Fiber | null, Fiber | null] {
|
||||
const result: [Fiber | null, Fiber | null] = [null, null];
|
||||
const parentHostFiber = getFragmentParentHostFiber(fiber);
|
||||
const parentHostFiber = getFragmentParentInstanceOrContainerFiber(fiber);
|
||||
if (parentHostFiber === null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
findFragmentInstanceSiblings(result, fiber, parentHostFiber.child);
|
||||
findFragmentInstanceOrTextInstanceSiblings(
|
||||
result,
|
||||
fiber,
|
||||
parentHostFiber.child,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
function findFragmentInstanceSiblings(
|
||||
/**
|
||||
* Only collects HostText with enableFragmentRefsTextNodes enabled. Otherwise, only collects HostComponent.
|
||||
*/
|
||||
function findFragmentInstanceOrTextInstanceSiblings(
|
||||
result: [Fiber | null, Fiber | null],
|
||||
self: Fiber,
|
||||
child: null | Fiber,
|
||||
@@ -452,7 +484,10 @@ function findFragmentInstanceSiblings(
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (child.tag === HostComponent) {
|
||||
if (
|
||||
child.tag === HostComponent ||
|
||||
(enableFragmentRefsTextNodes && child.tag === HostText)
|
||||
) {
|
||||
if (foundSelf) {
|
||||
result[1] = child;
|
||||
return true;
|
||||
@@ -465,7 +500,14 @@ function findFragmentInstanceSiblings(
|
||||
) {
|
||||
// Skip hidden subtrees
|
||||
} else {
|
||||
if (findFragmentInstanceSiblings(result, self, child.child, foundSelf)) {
|
||||
if (
|
||||
findFragmentInstanceOrTextInstanceSiblings(
|
||||
result,
|
||||
self,
|
||||
child.child,
|
||||
foundSelf,
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -474,7 +516,9 @@ function findFragmentInstanceSiblings(
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getInstanceFromHostFiber<I>(fiber: Fiber): I {
|
||||
export function getInstanceFromHostFiber<
|
||||
I: Instance | TextInstance | Container,
|
||||
>(fiber: Fiber): I {
|
||||
switch (fiber.tag) {
|
||||
case HostComponent:
|
||||
case HostText:
|
||||
@@ -501,8 +545,14 @@ function popSearchBoundary(): null | Fiber {
|
||||
return searchBoundary;
|
||||
}
|
||||
|
||||
export function getNextSiblingHostFiber(fiber: Fiber): null | Fiber {
|
||||
traverseVisibleHostChildren(fiber.sibling, false, findNextSibling);
|
||||
export function getNextSiblingInstanceOrTextInstanceFiber(
|
||||
fiber: Fiber,
|
||||
): null | Fiber {
|
||||
traverseVisibleInstancesAndTextInstances(
|
||||
fiber.sibling,
|
||||
false,
|
||||
findNextSibling,
|
||||
);
|
||||
const sibling = popSearchTarget();
|
||||
pushSearchTarget(null);
|
||||
return sibling;
|
||||
@@ -536,7 +586,7 @@ export function isFragmentContainedByFiber(
|
||||
): boolean {
|
||||
let current: Fiber | null = fragmentFiber;
|
||||
const fiberHostParent: Fiber | null =
|
||||
getFragmentParentHostFiber(fragmentFiber);
|
||||
getFragmentParentInstanceOrContainerFiber(fragmentFiber);
|
||||
while (current !== null) {
|
||||
if (
|
||||
(current.tag === HostComponent || current.tag === HostRoot) &&
|
||||
@@ -558,7 +608,7 @@ export function isFiberPreceding(fiber: Fiber, otherFiber: Fiber): boolean {
|
||||
if (commonAncestor === null) {
|
||||
return false;
|
||||
}
|
||||
traverseVisibleHostChildren(
|
||||
traverseVisibleInstancesAndTextInstances(
|
||||
commonAncestor,
|
||||
true,
|
||||
isFiberPrecedingCheck,
|
||||
@@ -594,7 +644,7 @@ export function isFiberFollowing(fiber: Fiber, otherFiber: Fiber): boolean {
|
||||
if (commonAncestor === null) {
|
||||
return false;
|
||||
}
|
||||
traverseVisibleHostChildren(
|
||||
traverseVisibleInstancesAndTextInstances(
|
||||
commonAncestor,
|
||||
true,
|
||||
isFiberFollowingCheck,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||
|
||||
import {getNextSiblingHostFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
|
||||
import {getNextSiblingInstanceOrTextInstanceFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
|
||||
|
||||
export function compareDocumentPositionForEmptyFragment<TPublicInstance>(
|
||||
fragmentFiber: Fiber,
|
||||
@@ -32,7 +32,8 @@ export function compareDocumentPositionForEmptyFragment<TPublicInstance>(
|
||||
if (parentResult & Node.DOCUMENT_POSITION_CONTAINED_BY) {
|
||||
// otherNode is one of the fragment's siblings. Use the next
|
||||
// sibling to determine if its preceding or following.
|
||||
const nextSiblingFiber = getNextSiblingHostFiber(fragmentFiber);
|
||||
const nextSiblingFiber =
|
||||
getNextSiblingInstanceOrTextInstanceFiber(fragmentFiber);
|
||||
if (nextSiblingFiber === null) {
|
||||
result = Node.DOCUMENT_POSITION_PRECEDING;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user