Skip to content

Commit

Permalink
Implement getBoundingClientRect in React Native
Browse files Browse the repository at this point in the history
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
  • Loading branch information
rubennorte authored and facebook-github-bot committed Apr 13, 2023
1 parent 3838a18 commit be7f2ab
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 11 deletions.
22 changes: 21 additions & 1 deletion packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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');
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/Libraries/DOM/Nodes/ReadOnlyNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`',
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export type Spec = {|
+findShadowNodeByTag_DEPRECATED: (reactTag: number) => ?Node,
+getBoundingClientRect: (
node: Node,
) => [
) => ?[
/* x:*/ number,
/* y:*/ number,
/* width:*/ number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
(
Expand Down Expand Up @@ -215,15 +226,36 @@ const FabricUIManagerMock: FabricUIManager = {
getBoundingClientRect: jest.fn(
(
node: Node,
): [
): ?[
/* x:*/ number,
/* y:*/ number,
/* width:*/ number,
/* height:*/ number,
] => {
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 => {}),
Expand All @@ -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,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit be7f2ab

Please sign in to comment.