Skip to content

Commit 8e7d87d

Browse files
committed
Add compareDocumentPosition to FragmentInstance on Fabric
1 parent b5d1bae commit 8e7d87d

File tree

3 files changed

+145
-48
lines changed

3 files changed

+145
-48
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import {
4444
isFragmentContainedByFiber,
4545
traverseFragmentInstance,
4646
getFragmentParentHostFiber,
47-
getNextSiblingHostFiber,
4847
getInstanceFromHostFiber,
4948
traverseFragmentInstanceDeeply,
5049
fiberIsPortaledIntoHost,
@@ -70,6 +69,7 @@ import {
7069
markNodeAsHoistable,
7170
isOwnedInstance,
7271
} from './ReactDOMComponentTree';
72+
import {compareDocumentPositionForEmptyFragment} from 'shared/ReactDOMFragmentRefShared';
7373

7474
export {detachDeletedInstance};
7575
import {hasRole} from './DOMAccessibilityRoles';
@@ -3055,40 +3055,13 @@ FragmentInstance.prototype.compareDocumentPosition = function (
30553055
const parentHostInstance =
30563056
getInstanceFromHostFiber<Instance>(parentHostFiber);
30573057

3058-
let result = Node.DOCUMENT_POSITION_DISCONNECTED;
30593058
if (children.length === 0) {
3060-
// If the fragment has no children, we can use the parent and
3061-
// siblings to determine a position.
3062-
const parentResult = parentHostInstance.compareDocumentPosition(otherNode);
3063-
result = parentResult;
3064-
if (parentHostInstance === otherNode) {
3065-
result = Node.DOCUMENT_POSITION_CONTAINS;
3066-
} else {
3067-
if (parentResult & Node.DOCUMENT_POSITION_CONTAINED_BY) {
3068-
// otherNode is one of the fragment's siblings. Use the next
3069-
// sibling to determine if its preceding or following.
3070-
const nextSiblingFiber = getNextSiblingHostFiber(this._fragmentFiber);
3071-
if (nextSiblingFiber === null) {
3072-
result = Node.DOCUMENT_POSITION_PRECEDING;
3073-
} else {
3074-
const nextSiblingInstance =
3075-
getInstanceFromHostFiber<Instance>(nextSiblingFiber);
3076-
const nextSiblingResult =
3077-
nextSiblingInstance.compareDocumentPosition(otherNode);
3078-
if (
3079-
nextSiblingResult === 0 ||
3080-
nextSiblingResult & Node.DOCUMENT_POSITION_FOLLOWING
3081-
) {
3082-
result = Node.DOCUMENT_POSITION_FOLLOWING;
3083-
} else {
3084-
result = Node.DOCUMENT_POSITION_PRECEDING;
3085-
}
3086-
}
3087-
}
3088-
}
3089-
3090-
result |= Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
3091-
return result;
3059+
return compareDocumentPositionForEmptyFragment(
3060+
this._fragmentFiber,
3061+
parentHostInstance,
3062+
otherNode,
3063+
getInstanceFromHostFiber,
3064+
);
30923065
}
30933066

30943067
const firstElement = getInstanceFromHostFiber<Instance>(children[0]);
@@ -3099,8 +3072,9 @@ FragmentInstance.prototype.compareDocumentPosition = function (
30993072
// If the fragment has been portaled into another host instance, we need to
31003073
// our best guess is to use the parent of the child instance, rather than
31013074
// the fiber tree host parent.
3075+
const firstInstance = getInstanceFromHostFiber<Instance>(children[0]);
31023076
const parentHostInstanceFromDOM = fiberIsPortaledIntoHost(this._fragmentFiber)
3103-
? (getInstanceFromHostFiber<Instance>(children[0]).parentElement: ?Instance)
3077+
? (firstInstance.parentElement: ?Instance)
31043078
: parentHostInstance;
31053079

31063080
if (parentHostInstanceFromDOM == null) {
@@ -3133,6 +3107,7 @@ FragmentInstance.prototype.compareDocumentPosition = function (
31333107
firstResult & Node.DOCUMENT_POSITION_FOLLOWING &&
31343108
lastResult & Node.DOCUMENT_POSITION_PRECEDING;
31353109

3110+
let result = Node.DOCUMENT_POSITION_DISCONNECTED;
31363111
if (
31373112
otherNodeIsFirstOrLastChild ||
31383113
otherNodeIsWithinFirstOrLastChild ||

packages/react-native-renderer/src/ReactFiberConfigFabric.js

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
2525
import {HostText} from 'react-reconciler/src/ReactWorkTags';
2626
import {
27+
getFragmentParentHostFiber,
2728
getInstanceFromHostFiber,
2829
traverseFragmentInstance,
2930
} from 'react-reconciler/src/ReactFiberTreeReflection';
@@ -59,6 +60,7 @@ const {
5960
} = nativeFabricUIManager;
6061

6162
import {getClosestInstanceFromNode} from './ReactFabricComponentTree';
63+
import {compareDocumentPositionForEmptyFragment} from 'shared/ReactDOMFragmentRefShared';
6264

6365
import {
6466
getInspectorDataForViewTag,
@@ -87,7 +89,7 @@ const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
8789
let nextReactTag = 2;
8890

8991
type InternalInstanceHandle = Object;
90-
type Node = Object;
92+
9193
export type Type = string;
9294
export type Props = Object;
9395
export type Instance = {
@@ -344,6 +346,15 @@ export function getPublicInstanceFromInternalInstanceHandle(
344346
return getPublicInstance(elementInstance);
345347
}
346348

349+
function getPublicInstanceFromHostFiber(fiber: Fiber): PublicInstance {
350+
const instance = getInstanceFromHostFiber<Instance>(fiber);
351+
const publicInstance = getPublicInstance(instance);
352+
if (publicInstance == null) {
353+
throw new Error('Expected to find a host node. This is a bug in React.');
354+
}
355+
return publicInstance;
356+
}
357+
347358
export function prepareForCommit(containerInfo: Container): null | Object {
348359
// Noop
349360
return null;
@@ -610,6 +621,7 @@ export type FragmentInstanceType = {
610621
_observers: null | Set<IntersectionObserver>,
611622
observeUsing: (observer: IntersectionObserver) => void,
612623
unobserveUsing: (observer: IntersectionObserver) => void,
624+
compareDocumentPosition: (otherNode: PublicInstance) => number,
613625
};
614626

615627
function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) {
@@ -629,12 +641,8 @@ FragmentInstance.prototype.observeUsing = function (
629641
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
630642
};
631643
function observeChild(child: Fiber, observer: IntersectionObserver) {
632-
const instance = getInstanceFromHostFiber<Instance>(child);
633-
const publicInstance = getPublicInstance(instance);
634-
if (publicInstance == null) {
635-
throw new Error('Expected to find a host node. This is a bug in React.');
636-
}
637-
// $FlowFixMe[incompatible-call] Element types are behind a flag in RN
644+
const publicInstance = getPublicInstanceFromHostFiber(child);
645+
// $FlowFixMe[incompatible-call] DOM types expect Element
638646
observer.observe(publicInstance);
639647
return false;
640648
}
@@ -656,16 +664,72 @@ FragmentInstance.prototype.unobserveUsing = function (
656664
}
657665
};
658666
function unobserveChild(child: Fiber, observer: IntersectionObserver) {
659-
const instance = getInstanceFromHostFiber<Instance>(child);
660-
const publicInstance = getPublicInstance(instance);
661-
if (publicInstance == null) {
662-
throw new Error('Expected to find a host node. This is a bug in React.');
663-
}
664-
// $FlowFixMe[incompatible-call] Element types are behind a flag in RN
667+
const publicInstance = getPublicInstanceFromHostFiber(child);
668+
// $FlowFixMe[incompatible-call] DOM types expect Element
665669
observer.unobserve(publicInstance);
666670
return false;
667671
}
668672

673+
// $FlowFixMe[prop-missing]
674+
FragmentInstance.prototype.compareDocumentPosition = function (
675+
this: FragmentInstanceType,
676+
otherNode: PublicInstance,
677+
): number {
678+
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
679+
if (parentHostFiber === null) {
680+
return Node.DOCUMENT_POSITION_DISCONNECTED;
681+
}
682+
const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber);
683+
const children: Array<Fiber> = [];
684+
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
685+
if (children.length === 0) {
686+
return compareDocumentPositionForEmptyFragment(
687+
this._fragmentFiber,
688+
parentHostInstance,
689+
otherNode,
690+
getPublicInstanceFromHostFiber,
691+
);
692+
}
693+
694+
const firstInstance = getPublicInstanceFromHostFiber(children[0]);
695+
const lastInstance = getPublicInstanceFromHostFiber(
696+
children[children.length - 1],
697+
);
698+
699+
// $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
700+
// $FlowFixMe[prop-missing]
701+
const firstResult = firstInstance.compareDocumentPosition(otherNode);
702+
// $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
703+
// $FlowFixMe[prop-missing]
704+
const lastResult = lastInstance.compareDocumentPosition(otherNode);
705+
706+
const otherNodeIsFirstOrLastChild =
707+
firstInstance === otherNode || lastInstance === otherNode;
708+
const otherNodeIsWithinFirstOrLastChild =
709+
firstResult & Node.DOCUMENT_POSITION_CONTAINED_BY ||
710+
lastResult & Node.DOCUMENT_POSITION_CONTAINED_BY;
711+
const otherNodeIsBetweenFirstAndLastChildren =
712+
firstResult & Node.DOCUMENT_POSITION_FOLLOWING &&
713+
lastResult & Node.DOCUMENT_POSITION_PRECEDING;
714+
let result;
715+
if (
716+
otherNodeIsFirstOrLastChild ||
717+
otherNodeIsWithinFirstOrLastChild ||
718+
otherNodeIsBetweenFirstAndLastChildren
719+
) {
720+
result = Node.DOCUMENT_POSITION_CONTAINED_BY;
721+
} else {
722+
result = firstResult;
723+
}
724+
725+
return result;
726+
};
727+
728+
function collectChildren(child: Fiber, collection: Array<Fiber>): boolean {
729+
collection.push(child);
730+
return false;
731+
}
732+
669733
export function createFragmentInstance(
670734
fragmentFiber: Fiber,
671735
): FragmentInstanceType {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* Shared logic for Fragment Ref operations for DOM and Fabric configs
8+
*
9+
* @flow
10+
*/
11+
12+
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
13+
14+
import {getNextSiblingHostFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
15+
16+
export function compareDocumentPositionForEmptyFragment<TPublicInstance>(
17+
fragmentFiber: Fiber,
18+
parentHostInstance: TPublicInstance,
19+
otherNode: TPublicInstance,
20+
getPublicInstance: (fiber: Fiber) => TPublicInstance,
21+
): number {
22+
let result;
23+
// If the fragment has no children, we can use the parent and
24+
// siblings to determine a position.
25+
// $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
26+
// $FlowFixMe[prop-missing]
27+
const parentResult = parentHostInstance.compareDocumentPosition(otherNode);
28+
result = parentResult;
29+
if (parentHostInstance === otherNode) {
30+
result = Node.DOCUMENT_POSITION_CONTAINS;
31+
} else {
32+
if (parentResult & Node.DOCUMENT_POSITION_CONTAINED_BY) {
33+
// otherNode is one of the fragment's siblings. Use the next
34+
// sibling to determine if its preceding or following.
35+
const nextSiblingFiber = getNextSiblingHostFiber(fragmentFiber);
36+
if (nextSiblingFiber === null) {
37+
result = Node.DOCUMENT_POSITION_PRECEDING;
38+
} else {
39+
const nextSiblingInstance = getPublicInstance(nextSiblingFiber);
40+
const nextSiblingResult =
41+
// $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
42+
// $FlowFixMe[prop-missing]
43+
nextSiblingInstance.compareDocumentPosition(otherNode);
44+
if (
45+
nextSiblingResult === 0 ||
46+
nextSiblingResult & Node.DOCUMENT_POSITION_FOLLOWING
47+
) {
48+
result = Node.DOCUMENT_POSITION_FOLLOWING;
49+
} else {
50+
result = Node.DOCUMENT_POSITION_PRECEDING;
51+
}
52+
}
53+
}
54+
}
55+
56+
result |= Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
57+
return result;
58+
}

0 commit comments

Comments
 (0)