Skip to content

Commit

Permalink
Implement clientTop/clientLeft in ReadOnlyElement (facebook#39308)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#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: aec2d2f907339aa45e28271100c89b954f816cef
  • Loading branch information
rubennorte authored and facebook-github-bot committed Sep 6, 2023
1 parent 1330d5e commit f1ae460
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 2 deletions.
22 changes: 20 additions & 2 deletions packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,29 @@ export default class ReadOnlyElement extends ReadOnlyNode {
}

get clientLeft(): number {
throw new TypeError('Unimplemented');
const node = getShadowNode(this);

if (node != null) {
const borderSize = nullthrows(getFabricUIManager()).getBorderSize(node);
if (borderSize != null) {
return borderSize[3];
}
}

return 0;
}

get clientTop(): number {
throw new TypeError('Unimplemented');
const node = getShadowNode(this);

if (node != null) {
const borderSize = nullthrows(getFabricUIManager()).getBorderSize(node);
if (borderSize != null) {
return borderSize[0];
}
}

return 0;
}

get clientWidth(): number {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ export interface Spec {
node: Node,
) => ?[/* scrollLeft: */ number, /* scrollTop: */ number];
+getInnerSize: (node: Node) => ?[/* width: */ number, /* height: */ number];
+getBorderSize: (
node: Node,
) => ?[
/* topWidth: */ number,
/* rightWidth: */ number,
/* bottomWidth: */ number,
/* leftWidth: */ number,
];
+getTagName: (node: Node) => string;

/**
Expand Down Expand Up @@ -134,6 +142,7 @@ const CACHED_PROPERTIES = [
'getOffset',
'getScrollPosition',
'getInnerSize',
'getBorderSize',
'getTagName',
'hasPointerCapture',
'setPointerCapture',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,48 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
},
),

getBorderSize: jest.fn(
(
node: Node,
): ?[
/* topWidth: */ number,
/* rightWidth: */ number,
/* bottomWidth: */ number,
/* leftWidth: */ number,
] => {
ensureHostNode(node);

const nodeInCurrentTree = getNodeInCurrentTree(node);
const currentProps =
nodeInCurrentTree != null ? fromNode(nodeInCurrentTree).props : null;
if (currentProps == null) {
return null;
}

const borderSizeForTests: ?{
topWidth?: number,
rightWidth?: number,
bottomWidth?: number,
leftWidth?: number,
...
} =
// $FlowExpectedError[prop-missing]
currentProps.__borderSizeForTests;

if (borderSizeForTests == null) {
return null;
}

const {
topWidth = 0,
rightWidth = 0,
bottomWidth = 0,
leftWidth = 0,
} = borderSizeForTests;
return [topWidth, rightWidth, bottomWidth, leftWidth];
},
),

getTagName: jest.fn((node: Node): string => {
ensureHostNode(node);
return 'RN:' + fromNode(node).viewName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,60 @@ jsi::Value UIManagerBinding::get(
});
}

if (methodName == "getBorderSize") {
// This is a method to access the border size of a shadow node, to implement
// these methods:
// * `Element.prototype.clientLeft`: see
// https://developer.mozilla.org/en-US/docs/Web/API/Element/clientLeft.
// * `Element.prototype.clientTop`: see
// https://developer.mozilla.org/en-US/docs/Web/API/Element/clientTop.

// It uses the version of the shadow node that is present in the current
// revision of the shadow tree. If the node is not present, it is not
// displayed (because any of its ancestors or itself have 'display: none'),
// or it has an inline display, it returns undefined.
// Otherwise, it returns its border size.

// getBorderSize(shadowNode: ShadowNode):
// ?[
// /* topWidth: */ number,
// /* rightWidth: */ number,
// /* bottomWidth: */ number,
// /* leftWidth: */ number,
// ]
auto paramCount = 1;
return jsi::Function::createFromHostFunction(
runtime,
name,
paramCount,
[uiManager, methodName, paramCount](
jsi::Runtime& runtime,
const jsi::Value& /*thisValue*/,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
validateArgumentCount(runtime, methodName, paramCount, count);

auto shadowNode = shadowNodeFromValue(runtime, arguments[0]);

// If the node is not displayed (itself or any of its ancestors has
// "display: none"), this returns an empty layout metrics object.
auto layoutMetrics = uiManager->getRelativeLayoutMetrics(
*shadowNode, nullptr, {/* .includeTransform = */ true});

if (layoutMetrics == EmptyLayoutMetrics ||
layoutMetrics.displayType == DisplayType::Inline) {
return jsi::Value::undefined();
}

return jsi::Array::createWithElements(
runtime,
jsi::Value{runtime, std::round(layoutMetrics.borderWidth.top)},
jsi::Value{runtime, std::round(layoutMetrics.borderWidth.right)},
jsi::Value{runtime, std::round(layoutMetrics.borderWidth.bottom)},
jsi::Value{runtime, std::round(layoutMetrics.borderWidth.left)});
});
}

if (methodName == "getTagName") {
// This is a method to access the normalized tag name of a shadow node, to
// implement `Element.prototype.tagName` (see
Expand Down

0 comments on commit f1ae460

Please sign in to comment.