mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Add compareDocumentPosition to Fabric FragmentInstance (#34103)
Stacked on https://github.com/facebook/react/pull/34069 Same basic semantics as the react-dom for determining document position of a Fragment compared to a given node. It's simpler here because we don't have to deal with inserted nodes or portals. So we can skip a bunch of the validation logic. The logic for handling empty fragments is the same so I've split out `compareDocumentPositionForEmptyFragment` into a shared module. There doesn't seem to be a great place to put shared DOM logic between Fabric and DOM configs at the moment. There may be more of this coming as we add more and more DOM APIs to RN. For testing I've written Fantom tests internally which pass the basic cases on this build. The renderer we have configured for Fabric tests in the repo doesn't support the Element APIs we need like `compareDocumentPosition`.
This commit is contained in:
parent
8dba9311e5
commit
45a6532a08
|
|
@ -44,7 +44,6 @@ import {
|
||||||
isFragmentContainedByFiber,
|
isFragmentContainedByFiber,
|
||||||
traverseFragmentInstance,
|
traverseFragmentInstance,
|
||||||
getFragmentParentHostFiber,
|
getFragmentParentHostFiber,
|
||||||
getNextSiblingHostFiber,
|
|
||||||
getInstanceFromHostFiber,
|
getInstanceFromHostFiber,
|
||||||
traverseFragmentInstanceDeeply,
|
traverseFragmentInstanceDeeply,
|
||||||
fiberIsPortaledIntoHost,
|
fiberIsPortaledIntoHost,
|
||||||
|
|
@ -70,6 +69,7 @@ import {
|
||||||
markNodeAsHoistable,
|
markNodeAsHoistable,
|
||||||
isOwnedInstance,
|
isOwnedInstance,
|
||||||
} from './ReactDOMComponentTree';
|
} from './ReactDOMComponentTree';
|
||||||
|
import {compareDocumentPositionForEmptyFragment} from 'shared/ReactDOMFragmentRefShared';
|
||||||
|
|
||||||
export {detachDeletedInstance};
|
export {detachDeletedInstance};
|
||||||
import {hasRole} from './DOMAccessibilityRoles';
|
import {hasRole} from './DOMAccessibilityRoles';
|
||||||
|
|
@ -3055,40 +3055,13 @@ FragmentInstance.prototype.compareDocumentPosition = function (
|
||||||
const parentHostInstance =
|
const parentHostInstance =
|
||||||
getInstanceFromHostFiber<Instance>(parentHostFiber);
|
getInstanceFromHostFiber<Instance>(parentHostFiber);
|
||||||
|
|
||||||
let result = Node.DOCUMENT_POSITION_DISCONNECTED;
|
|
||||||
if (children.length === 0) {
|
if (children.length === 0) {
|
||||||
// If the fragment has no children, we can use the parent and
|
return compareDocumentPositionForEmptyFragment(
|
||||||
// siblings to determine a position.
|
this._fragmentFiber,
|
||||||
const parentResult = parentHostInstance.compareDocumentPosition(otherNode);
|
parentHostInstance,
|
||||||
result = parentResult;
|
otherNode,
|
||||||
if (parentHostInstance === otherNode) {
|
getInstanceFromHostFiber,
|
||||||
result = Node.DOCUMENT_POSITION_CONTAINS;
|
);
|
||||||
} else {
|
|
||||||
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(this._fragmentFiber);
|
|
||||||
if (nextSiblingFiber === null) {
|
|
||||||
result = Node.DOCUMENT_POSITION_PRECEDING;
|
|
||||||
} else {
|
|
||||||
const nextSiblingInstance =
|
|
||||||
getInstanceFromHostFiber<Instance>(nextSiblingFiber);
|
|
||||||
const nextSiblingResult =
|
|
||||||
nextSiblingInstance.compareDocumentPosition(otherNode);
|
|
||||||
if (
|
|
||||||
nextSiblingResult === 0 ||
|
|
||||||
nextSiblingResult & Node.DOCUMENT_POSITION_FOLLOWING
|
|
||||||
) {
|
|
||||||
result = Node.DOCUMENT_POSITION_FOLLOWING;
|
|
||||||
} else {
|
|
||||||
result = Node.DOCUMENT_POSITION_PRECEDING;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result |= Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstElement = getInstanceFromHostFiber<Instance>(children[0]);
|
const firstElement = getInstanceFromHostFiber<Instance>(children[0]);
|
||||||
|
|
@ -3099,8 +3072,9 @@ FragmentInstance.prototype.compareDocumentPosition = function (
|
||||||
// If the fragment has been portaled into another host instance, we need to
|
// If the fragment has been portaled into another host instance, we need to
|
||||||
// our best guess is to use the parent of the child instance, rather than
|
// our best guess is to use the parent of the child instance, rather than
|
||||||
// the fiber tree host parent.
|
// the fiber tree host parent.
|
||||||
|
const firstInstance = getInstanceFromHostFiber<Instance>(children[0]);
|
||||||
const parentHostInstanceFromDOM = fiberIsPortaledIntoHost(this._fragmentFiber)
|
const parentHostInstanceFromDOM = fiberIsPortaledIntoHost(this._fragmentFiber)
|
||||||
? (getInstanceFromHostFiber<Instance>(children[0]).parentElement: ?Instance)
|
? (firstInstance.parentElement: ?Instance)
|
||||||
: parentHostInstance;
|
: parentHostInstance;
|
||||||
|
|
||||||
if (parentHostInstanceFromDOM == null) {
|
if (parentHostInstanceFromDOM == null) {
|
||||||
|
|
@ -3133,6 +3107,7 @@ FragmentInstance.prototype.compareDocumentPosition = function (
|
||||||
firstResult & Node.DOCUMENT_POSITION_FOLLOWING &&
|
firstResult & Node.DOCUMENT_POSITION_FOLLOWING &&
|
||||||
lastResult & Node.DOCUMENT_POSITION_PRECEDING;
|
lastResult & Node.DOCUMENT_POSITION_PRECEDING;
|
||||||
|
|
||||||
|
let result = Node.DOCUMENT_POSITION_DISCONNECTED;
|
||||||
if (
|
if (
|
||||||
otherNodeIsFirstOrLastChild ||
|
otherNodeIsFirstOrLastChild ||
|
||||||
otherNodeIsWithinFirstOrLastChild ||
|
otherNodeIsWithinFirstOrLastChild ||
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import {
|
||||||
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||||
import {HostText} from 'react-reconciler/src/ReactWorkTags';
|
import {HostText} from 'react-reconciler/src/ReactWorkTags';
|
||||||
import {
|
import {
|
||||||
|
getFragmentParentHostFiber,
|
||||||
getInstanceFromHostFiber,
|
getInstanceFromHostFiber,
|
||||||
traverseFragmentInstance,
|
traverseFragmentInstance,
|
||||||
} from 'react-reconciler/src/ReactFiberTreeReflection';
|
} from 'react-reconciler/src/ReactFiberTreeReflection';
|
||||||
|
|
@ -59,6 +60,7 @@ const {
|
||||||
} = nativeFabricUIManager;
|
} = nativeFabricUIManager;
|
||||||
|
|
||||||
import {getClosestInstanceFromNode} from './ReactFabricComponentTree';
|
import {getClosestInstanceFromNode} from './ReactFabricComponentTree';
|
||||||
|
import {compareDocumentPositionForEmptyFragment} from 'shared/ReactDOMFragmentRefShared';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getInspectorDataForViewTag,
|
getInspectorDataForViewTag,
|
||||||
|
|
@ -87,7 +89,7 @@ const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
|
||||||
let nextReactTag = 2;
|
let nextReactTag = 2;
|
||||||
|
|
||||||
type InternalInstanceHandle = Object;
|
type InternalInstanceHandle = Object;
|
||||||
type Node = Object;
|
|
||||||
export type Type = string;
|
export type Type = string;
|
||||||
export type Props = Object;
|
export type Props = Object;
|
||||||
export type Instance = {
|
export type Instance = {
|
||||||
|
|
@ -344,6 +346,15 @@ export function getPublicInstanceFromInternalInstanceHandle(
|
||||||
return getPublicInstance(elementInstance);
|
return getPublicInstance(elementInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPublicInstanceFromHostFiber(fiber: Fiber): PublicInstance {
|
||||||
|
const instance = getInstanceFromHostFiber<Instance>(fiber);
|
||||||
|
const publicInstance = getPublicInstance(instance);
|
||||||
|
if (publicInstance == null) {
|
||||||
|
throw new Error('Expected to find a host node. This is a bug in React.');
|
||||||
|
}
|
||||||
|
return publicInstance;
|
||||||
|
}
|
||||||
|
|
||||||
export function prepareForCommit(containerInfo: Container): null | Object {
|
export function prepareForCommit(containerInfo: Container): null | Object {
|
||||||
// Noop
|
// Noop
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -610,6 +621,7 @@ export type FragmentInstanceType = {
|
||||||
_observers: null | Set<IntersectionObserver>,
|
_observers: null | Set<IntersectionObserver>,
|
||||||
observeUsing: (observer: IntersectionObserver) => void,
|
observeUsing: (observer: IntersectionObserver) => void,
|
||||||
unobserveUsing: (observer: IntersectionObserver) => void,
|
unobserveUsing: (observer: IntersectionObserver) => void,
|
||||||
|
compareDocumentPosition: (otherNode: PublicInstance) => number,
|
||||||
};
|
};
|
||||||
|
|
||||||
function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) {
|
function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) {
|
||||||
|
|
@ -629,12 +641,8 @@ FragmentInstance.prototype.observeUsing = function (
|
||||||
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
|
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
|
||||||
};
|
};
|
||||||
function observeChild(child: Fiber, observer: IntersectionObserver) {
|
function observeChild(child: Fiber, observer: IntersectionObserver) {
|
||||||
const instance = getInstanceFromHostFiber<Instance>(child);
|
const publicInstance = getPublicInstanceFromHostFiber(child);
|
||||||
const publicInstance = getPublicInstance(instance);
|
// $FlowFixMe[incompatible-call] DOM types expect Element
|
||||||
if (publicInstance == null) {
|
|
||||||
throw new Error('Expected to find a host node. This is a bug in React.');
|
|
||||||
}
|
|
||||||
// $FlowFixMe[incompatible-call] Element types are behind a flag in RN
|
|
||||||
observer.observe(publicInstance);
|
observer.observe(publicInstance);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -656,16 +664,72 @@ FragmentInstance.prototype.unobserveUsing = function (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
function unobserveChild(child: Fiber, observer: IntersectionObserver) {
|
function unobserveChild(child: Fiber, observer: IntersectionObserver) {
|
||||||
const instance = getInstanceFromHostFiber<Instance>(child);
|
const publicInstance = getPublicInstanceFromHostFiber(child);
|
||||||
const publicInstance = getPublicInstance(instance);
|
// $FlowFixMe[incompatible-call] DOM types expect Element
|
||||||
if (publicInstance == null) {
|
|
||||||
throw new Error('Expected to find a host node. This is a bug in React.');
|
|
||||||
}
|
|
||||||
// $FlowFixMe[incompatible-call] Element types are behind a flag in RN
|
|
||||||
observer.unobserve(publicInstance);
|
observer.unobserve(publicInstance);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $FlowFixMe[prop-missing]
|
||||||
|
FragmentInstance.prototype.compareDocumentPosition = function (
|
||||||
|
this: FragmentInstanceType,
|
||||||
|
otherNode: PublicInstance,
|
||||||
|
): number {
|
||||||
|
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
|
||||||
|
if (parentHostFiber === null) {
|
||||||
|
return Node.DOCUMENT_POSITION_DISCONNECTED;
|
||||||
|
}
|
||||||
|
const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber);
|
||||||
|
const children: Array<Fiber> = [];
|
||||||
|
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
|
||||||
|
if (children.length === 0) {
|
||||||
|
return compareDocumentPositionForEmptyFragment(
|
||||||
|
this._fragmentFiber,
|
||||||
|
parentHostInstance,
|
||||||
|
otherNode,
|
||||||
|
getPublicInstanceFromHostFiber,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstInstance = getPublicInstanceFromHostFiber(children[0]);
|
||||||
|
const lastInstance = getPublicInstanceFromHostFiber(
|
||||||
|
children[children.length - 1],
|
||||||
|
);
|
||||||
|
|
||||||
|
// $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
|
||||||
|
// $FlowFixMe[prop-missing]
|
||||||
|
const firstResult = firstInstance.compareDocumentPosition(otherNode);
|
||||||
|
// $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
|
||||||
|
// $FlowFixMe[prop-missing]
|
||||||
|
const lastResult = lastInstance.compareDocumentPosition(otherNode);
|
||||||
|
|
||||||
|
const otherNodeIsFirstOrLastChild =
|
||||||
|
firstInstance === otherNode || lastInstance === otherNode;
|
||||||
|
const otherNodeIsWithinFirstOrLastChild =
|
||||||
|
firstResult & Node.DOCUMENT_POSITION_CONTAINED_BY ||
|
||||||
|
lastResult & Node.DOCUMENT_POSITION_CONTAINED_BY;
|
||||||
|
const otherNodeIsBetweenFirstAndLastChildren =
|
||||||
|
firstResult & Node.DOCUMENT_POSITION_FOLLOWING &&
|
||||||
|
lastResult & Node.DOCUMENT_POSITION_PRECEDING;
|
||||||
|
let result;
|
||||||
|
if (
|
||||||
|
otherNodeIsFirstOrLastChild ||
|
||||||
|
otherNodeIsWithinFirstOrLastChild ||
|
||||||
|
otherNodeIsBetweenFirstAndLastChildren
|
||||||
|
) {
|
||||||
|
result = Node.DOCUMENT_POSITION_CONTAINED_BY;
|
||||||
|
} else {
|
||||||
|
result = firstResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
function collectChildren(child: Fiber, collection: Array<Fiber>): boolean {
|
||||||
|
collection.push(child);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function createFragmentInstance(
|
export function createFragmentInstance(
|
||||||
fragmentFiber: Fiber,
|
fragmentFiber: Fiber,
|
||||||
): FragmentInstanceType {
|
): FragmentInstanceType {
|
||||||
|
|
|
||||||
58
packages/shared/ReactDOMFragmentRefShared.js
Normal file
58
packages/shared/ReactDOMFragmentRefShared.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*
|
||||||
|
* Shared logic for Fragment Ref operations for DOM and Fabric configs
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
|
||||||
|
|
||||||
|
import {getNextSiblingHostFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
|
||||||
|
|
||||||
|
export function compareDocumentPositionForEmptyFragment<TPublicInstance>(
|
||||||
|
fragmentFiber: Fiber,
|
||||||
|
parentHostInstance: TPublicInstance,
|
||||||
|
otherNode: TPublicInstance,
|
||||||
|
getPublicInstance: (fiber: Fiber) => TPublicInstance,
|
||||||
|
): number {
|
||||||
|
let result;
|
||||||
|
// If the fragment has no children, we can use the parent and
|
||||||
|
// siblings to determine a position.
|
||||||
|
// $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
|
||||||
|
// $FlowFixMe[prop-missing]
|
||||||
|
const parentResult = parentHostInstance.compareDocumentPosition(otherNode);
|
||||||
|
result = parentResult;
|
||||||
|
if (parentHostInstance === otherNode) {
|
||||||
|
result = Node.DOCUMENT_POSITION_CONTAINS;
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
if (nextSiblingFiber === null) {
|
||||||
|
result = Node.DOCUMENT_POSITION_PRECEDING;
|
||||||
|
} else {
|
||||||
|
const nextSiblingInstance = getPublicInstance(nextSiblingFiber);
|
||||||
|
const nextSiblingResult =
|
||||||
|
// $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
|
||||||
|
// $FlowFixMe[prop-missing]
|
||||||
|
nextSiblingInstance.compareDocumentPosition(otherNode);
|
||||||
|
if (
|
||||||
|
nextSiblingResult === 0 ||
|
||||||
|
nextSiblingResult & Node.DOCUMENT_POSITION_FOLLOWING
|
||||||
|
) {
|
||||||
|
result = Node.DOCUMENT_POSITION_FOLLOWING;
|
||||||
|
} else {
|
||||||
|
result = Node.DOCUMENT_POSITION_PRECEDING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result |= Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user