Implement getBoundingClientRect in React Native

Summary:
We already have `getBoundingClientRect` implemented in Fabric, so we can expose this as a proper method in `ReactNativeElement` (behind a feature flag).

Changelog: [internal]

bypass-github-export-checks

Reviewed By: javache

Differential Revision: D44065187

fbshipit-source-id: bd87e72f78d135079f5440b0b0bd4c572b05ba2a
This commit is contained in:
Rubén Norte
2023-04-13 09:19:00 -07:00
committed by Facebook GitHub Bot
parent 3838a1894c
commit be7f2ab4f6
5 changed files with 72 additions and 11 deletions
@@ -12,8 +12,11 @@
import type HTMLCollection from '../OldStyleCollections/HTMLCollection';
import {getFabricUIManager} from '../../ReactNative/FabricUIManager';
import DOMRect from '../Geometry/DOMRect';
import {createHTMLCollection} from '../OldStyleCollections/HTMLCollection';
import ReadOnlyNode, {getChildNodes} from './ReadOnlyNode';
import ReadOnlyNode, {getChildNodes, getShadowNode} from './ReadOnlyNode';
import nullthrows from 'nullthrows';
export default class ReadOnlyElement extends ReadOnlyNode {
get childElementCount(): number {
@@ -124,6 +127,23 @@ export default class ReadOnlyElement extends ReadOnlyNode {
throw new TypeError('Unimplemented');
}
getBoundingClientRect(): DOMRect {
const shadowNode = getShadowNode(this);
if (shadowNode != null) {
const rect = nullthrows(getFabricUIManager()).getBoundingClientRect(
shadowNode,
);
if (rect) {
return new DOMRect(rect[0], rect[1], rect[2], rect[3]);
}
}
// Empty rect if any of the above failed
return new DOMRect(0, 0, 0, 0);
}
getClientRects(): DOMRectList {
throw new TypeError('Unimplemented');
}
+1 -1
View File
@@ -76,7 +76,7 @@ export default class ReadOnlyNode {
*/
get nodeName(): string {
throw new TypeError(
'Unexpected access to `nodeName` in abstract class `ReadOnlyNode`',
'`nodeName` is abstract and must be implemented in a subclass of `ReadOnlyNode`',
);
}
@@ -58,7 +58,7 @@ export type Spec = {|
+findShadowNodeByTag_DEPRECATED: (reactTag: number) => ?Node,
+getBoundingClientRect: (
node: Node,
) => [
) => ?[
/* x:*/ number,
/* y:*/ number,
/* width:*/ number,
@@ -113,6 +113,17 @@ function getAncestors(root: Node, node: Node): ?$ReadOnlyArray<[Node, number]> {
return null;
}
function getNodeInCurrentTree(node: Node): ?Node {
const ancestors = getAncestorsInCurrentTree(node);
if (ancestors == null) {
return null;
}
const [parent, position] = ancestors[ancestors.length - 1];
const nodeInCurrentTree = fromNode(parent).children[position];
return nodeInCurrentTree;
}
const FabricUIManagerMock: FabricUIManager = {
createNode: jest.fn(
(
@@ -215,7 +226,7 @@ const FabricUIManagerMock: FabricUIManager = {
getBoundingClientRect: jest.fn(
(
node: Node,
): [
): ?[
/* x:*/ number,
/* y:*/ number,
/* width:*/ number,
@@ -223,7 +234,28 @@ const FabricUIManagerMock: FabricUIManager = {
] => {
ensureHostNode(node);
return [10, 10, 100, 100];
const nodeInCurrentTree = getNodeInCurrentTree(node);
const currentProps =
nodeInCurrentTree != null ? fromNode(nodeInCurrentTree).props : null;
if (currentProps == null) {
return null;
}
const boundingClientRectForTests: ?{
x: number,
y: number,
width: number,
height: number,
} =
// $FlowExpectedError[prop-missing]
currentProps.__boundingClientRectForTests;
if (boundingClientRectForTests == null) {
return null;
}
const {x, y, width, height} = boundingClientRectForTests;
return [x, y, width, height];
},
),
setNativeProps: jest.fn((node: Node, newProps: NodeProps): void => {}),
@@ -242,13 +274,12 @@ const FabricUIManagerMock: FabricUIManager = {
}),
getChildNodes: jest.fn(
(node: Node): $ReadOnlyArray<InternalInstanceHandle> => {
const ancestors = getAncestorsInCurrentTree(node);
if (ancestors == null) {
const nodeInCurrentTree = getNodeInCurrentTree(node);
if (nodeInCurrentTree == null) {
return [];
}
const [parent, position] = ancestors[ancestors.length - 1];
const nodeInCurrentTree = fromNode(parent).children[position];
return fromNode(nodeInCurrentTree).children.map(
child => fromNode(child).instanceHandle,
);
@@ -728,10 +728,20 @@ jsi::Value UIManagerBinding::get(
*/
if (methodName == "getBoundingClientRect") {
// This is a React Native implementation of
// `Element.prototype.getBoundingClientRect` (see
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
// This is similar to `measureInWindow`, except it's explicitly synchronous
// (returns the result instead of passing it to a callback).
// The behavior is similar to `Element.prototype.getBoundingClientRect` from
// Web.
// getBoundingClientRect(shadowNode: ShadowNode):
// [
// /* x: */ number,
// /* y: */ number,
// /* width: */ number,
// /* height: */ number
// ]
return jsi::Function::createFromHostFunction(
runtime,
name,