Skip to content

Commit eb5e7b2

Browse files
rubennortefacebook-github-bot
authored andcommitted
Implement clientTop/clientLeft in ReadOnlyElement (#39308)
Summary: Pull Request resolved: #39308 This adds a new method in Fabric to get the border size for an element, and uses it to implement the following methods as defined in react-native-community/discussions-and-proposals#607 : * `clientLeft`: left border width of the element. * `clientTop`: top border width of the element. If the element isn't displayed or it has display: inline, it return 0 in both cases. These APIs provide rounded integers. Changelog: [internal] Reviewed By: mdvacca Differential Revision: D49009140 fbshipit-source-id: e667059702ca22e2b8e8721209e9c5c2553aa7ac
1 parent f1e9a30 commit eb5e7b2

File tree

4 files changed

+125
-2
lines changed

4 files changed

+125
-2
lines changed

packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,29 @@ export default class ReadOnlyElement extends ReadOnlyNode {
4646
}
4747

4848
get clientLeft(): number {
49-
throw new TypeError('Unimplemented');
49+
const node = getShadowNode(this);
50+
51+
if (node != null) {
52+
const borderSize = nullthrows(getFabricUIManager()).getBorderSize(node);
53+
if (borderSize != null) {
54+
return borderSize[3];
55+
}
56+
}
57+
58+
return 0;
5059
}
5160

5261
get clientTop(): number {
53-
throw new TypeError('Unimplemented');
62+
const node = getShadowNode(this);
63+
64+
if (node != null) {
65+
const borderSize = nullthrows(getFabricUIManager()).getBorderSize(node);
66+
if (borderSize != null) {
67+
return borderSize[0];
68+
}
69+
}
70+
71+
return 0;
5472
}
5573

5674
get clientWidth(): number {

packages/react-native/Libraries/ReactNative/FabricUIManager.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ export interface Spec {
9292
node: Node,
9393
) => ?[/* scrollLeft: */ number, /* scrollTop: */ number];
9494
+getInnerSize: (node: Node) => ?[/* width: */ number, /* height: */ number];
95+
+getBorderSize: (
96+
node: Node,
97+
) => ?[
98+
/* topWidth: */ number,
99+
/* rightWidth: */ number,
100+
/* bottomWidth: */ number,
101+
/* leftWidth: */ number,
102+
];
95103
+getTagName: (node: Node) => string;
96104

97105
/**
@@ -134,6 +142,7 @@ const CACHED_PROPERTIES = [
134142
'getOffset',
135143
'getScrollPosition',
136144
'getInnerSize',
145+
'getBorderSize',
137146
'getTagName',
138147
'hasPointerCapture',
139148
'setPointerCapture',

packages/react-native/Libraries/ReactNative/__mocks__/FabricUIManager.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,48 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
552552
},
553553
),
554554

555+
getBorderSize: jest.fn(
556+
(
557+
node: Node,
558+
): ?[
559+
/* topWidth: */ number,
560+
/* rightWidth: */ number,
561+
/* bottomWidth: */ number,
562+
/* leftWidth: */ number,
563+
] => {
564+
ensureHostNode(node);
565+
566+
const nodeInCurrentTree = getNodeInCurrentTree(node);
567+
const currentProps =
568+
nodeInCurrentTree != null ? fromNode(nodeInCurrentTree).props : null;
569+
if (currentProps == null) {
570+
return null;
571+
}
572+
573+
const borderSizeForTests: ?{
574+
topWidth?: number,
575+
rightWidth?: number,
576+
bottomWidth?: number,
577+
leftWidth?: number,
578+
...
579+
} =
580+
// $FlowExpectedError[prop-missing]
581+
currentProps.__borderSizeForTests;
582+
583+
if (borderSizeForTests == null) {
584+
return null;
585+
}
586+
587+
const {
588+
topWidth = 0,
589+
rightWidth = 0,
590+
bottomWidth = 0,
591+
leftWidth = 0,
592+
} = borderSizeForTests;
593+
return [topWidth, rightWidth, bottomWidth, leftWidth];
594+
},
595+
),
596+
555597
getTagName: jest.fn((node: Node): string => {
556598
ensureHostNode(node);
557599
return 'RN:' + fromNode(node).viewName;

packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,60 @@ jsi::Value UIManagerBinding::get(
12611261
});
12621262
}
12631263

1264+
if (methodName == "getBorderSize") {
1265+
// This is a method to access the border size of a shadow node, to implement
1266+
// these methods:
1267+
// * `Element.prototype.clientLeft`: see
1268+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/clientLeft.
1269+
// * `Element.prototype.clientTop`: see
1270+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/clientTop.
1271+
1272+
// It uses the version of the shadow node that is present in the current
1273+
// revision of the shadow tree. If the node is not present, it is not
1274+
// displayed (because any of its ancestors or itself have 'display: none'),
1275+
// or it has an inline display, it returns undefined.
1276+
// Otherwise, it returns its border size.
1277+
1278+
// getBorderSize(shadowNode: ShadowNode):
1279+
// ?[
1280+
// /* topWidth: */ number,
1281+
// /* rightWidth: */ number,
1282+
// /* bottomWidth: */ number,
1283+
// /* leftWidth: */ number,
1284+
// ]
1285+
auto paramCount = 1;
1286+
return jsi::Function::createFromHostFunction(
1287+
runtime,
1288+
name,
1289+
paramCount,
1290+
[uiManager, methodName, paramCount](
1291+
jsi::Runtime& runtime,
1292+
const jsi::Value& /*thisValue*/,
1293+
const jsi::Value* arguments,
1294+
size_t count) -> jsi::Value {
1295+
validateArgumentCount(runtime, methodName, paramCount, count);
1296+
1297+
auto shadowNode = shadowNodeFromValue(runtime, arguments[0]);
1298+
1299+
// If the node is not displayed (itself or any of its ancestors has
1300+
// "display: none"), this returns an empty layout metrics object.
1301+
auto layoutMetrics = uiManager->getRelativeLayoutMetrics(
1302+
*shadowNode, nullptr, {/* .includeTransform = */ true});
1303+
1304+
if (layoutMetrics == EmptyLayoutMetrics ||
1305+
layoutMetrics.displayType == DisplayType::Inline) {
1306+
return jsi::Value::undefined();
1307+
}
1308+
1309+
return jsi::Array::createWithElements(
1310+
runtime,
1311+
jsi::Value{runtime, std::round(layoutMetrics.borderWidth.top)},
1312+
jsi::Value{runtime, std::round(layoutMetrics.borderWidth.right)},
1313+
jsi::Value{runtime, std::round(layoutMetrics.borderWidth.bottom)},
1314+
jsi::Value{runtime, std::round(layoutMetrics.borderWidth.left)});
1315+
});
1316+
}
1317+
12641318
if (methodName == "getTagName") {
12651319
// This is a method to access the normalized tag name of a shadow node, to
12661320
// implement `Element.prototype.tagName` (see

0 commit comments

Comments
 (0)