Skip to content

Commit

Permalink
Implemenet clientWidth/clientHeight
Browse files Browse the repository at this point in the history
Differential Revision: D49008698

fbshipit-source-id: 6759f583fdecbac10193b4d9cd036c2d2d63d59c
  • Loading branch information
rubennorte authored and facebook-github-bot committed Sep 6, 2023
1 parent 88a6798 commit 1330d5e
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 6 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 @@ -33,7 +33,16 @@ export default class ReadOnlyElement extends ReadOnlyNode {
}

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

if (node != null) {
const innerSize = nullthrows(getFabricUIManager()).getInnerSize(node);
if (innerSize != null) {
return innerSize[1];
}
}

return 0;
}

get clientLeft(): number {
Expand All @@ -45,7 +54,16 @@ export default class ReadOnlyElement extends ReadOnlyNode {
}

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

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

return 0;
}

get firstElementChild(): ReadOnlyElement | null {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export interface Spec {
+getScrollPosition: (
node: Node,
) => ?[/* scrollLeft: */ number, /* scrollTop: */ number];
+getInnerSize: (node: Node) => ?[/* width: */ number, /* height: */ number];
+getTagName: (node: Node) => string;

/**
Expand Down Expand Up @@ -132,6 +133,7 @@ const CACHED_PROPERTIES = [
'getBoundingClientRect',
'getOffset',
'getScrollPosition',
'getInnerSize',
'getTagName',
'hasPointerCapture',
'setPointerCapture',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,15 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
});
},
),

cloneNode: jest.fn((node: Node): Node => {
return toNode({...fromNode(node)});
}),

cloneNodeWithNewChildren: jest.fn((node: Node): Node => {
return toNode({...fromNode(node), children: []});
}),

cloneNodeWithNewProps: jest.fn((node: Node, newProps: NodeProps): Node => {
return toNode({
...fromNode(node),
Expand All @@ -214,6 +217,7 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
},
});
}),

cloneNodeWithNewChildrenAndProps: jest.fn(
(node: Node, newProps: NodeProps): Node => {
return toNode({
Expand All @@ -226,35 +230,42 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
});
},
),

createChildSet: jest.fn((rootTag: RootTag): NodeSet => {
return [];
}),

appendChild: jest.fn((parentNode: Node, child: Node): Node => {
// Although the signature returns a Node, React expects this to be mutating.
fromNode(parentNode).children.push(child);
return parentNode;
}),

appendChildToSet: jest.fn((childSet: NodeSet, child: Node): void => {
childSet.push(child);
}),

completeRoot: jest.fn((rootTag: RootTag, childSet: NodeSet): void => {
commitHooks.forEach(hook =>
hook.shadowTreeWillCommit(rootTag, roots.get(rootTag), childSet),
);
roots.set(rootTag, childSet);
}),

measure: jest.fn((node: Node, callback: MeasureOnSuccessCallback): void => {
ensureHostNode(node);

callback(10, 10, 100, 100, 0, 0);
}),

measureInWindow: jest.fn(
(node: Node, callback: MeasureInWindowOnSuccessCallback): void => {
ensureHostNode(node);

callback(10, 10, 100, 100);
},
),

measureLayout: jest.fn(
(
node: Node,
Expand All @@ -268,15 +279,19 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
onSuccess(1, 1, 100, 100);
},
),

configureNextLayoutAnimation: jest.fn(
(
config: LayoutAnimationConfig,
callback: () => void, // check what is returned here
errorCallback: () => void,
): void => {},
),

sendAccessibilityEvent: jest.fn((node: Node, eventType: string): void => {}),

findShadowNodeByTag_DEPRECATED: jest.fn((reactTag: number): ?Node => {}),

getBoundingClientRect: jest.fn(
(
node: Node,
Expand Down Expand Up @@ -312,13 +327,19 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
return [x, y, width, height];
},
),

hasPointerCapture: jest.fn((node: Node, pointerId: number): boolean => false),

setPointerCapture: jest.fn((node: Node, pointerId: number): void => {}),

releasePointerCapture: jest.fn((node: Node, pointerId: number): void => {}),

setNativeProps: jest.fn((node: Node, newProps: NodeProps): void => {}),

dispatchCommand: jest.fn(
(node: Node, commandName: string, args: Array<mixed>): void => {},
),

getParentNode: jest.fn((node: Node): ?InternalInstanceHandle => {
const ancestors = getAncestorsInCurrentTree(node);
if (ancestors == null || ancestors.length - 2 < 0) {
Expand All @@ -329,6 +350,7 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
const parentInCurrentTree = fromNode(parentOfParent).children[position];
return fromNode(parentInCurrentTree).instanceHandle;
}),

getChildNodes: jest.fn(
(node: Node): $ReadOnlyArray<InternalInstanceHandle> => {
const nodeInCurrentTree = getNodeInCurrentTree(node);
Expand All @@ -342,9 +364,11 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
);
},
),

isConnected: jest.fn((node: Node): boolean => {
return getNodeInCurrentTree(node) != null;
}),

getTextContent: jest.fn((node: Node): string => {
const nodeInCurrentTree = getNodeInCurrentTree(node);

Expand All @@ -366,6 +390,7 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
}
return result;
}),

compareDocumentPosition: jest.fn((node: Node, otherNode: Node): number => {
/* eslint-disable no-bitwise */
const ReadOnlyNode = require('../../DOM/Nodes/ReadOnlyNode').default;
Expand Down Expand Up @@ -419,6 +444,7 @@ const FabricUIManagerMock: IFabricUIManagerMock = {

return ReadOnlyNode.DOCUMENT_POSITION_FOLLOWING;
}),

getOffset: jest.fn(
(
node: Node,
Expand Down Expand Up @@ -469,6 +495,7 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
];
},
),

getScrollPosition: jest.fn(
(node: Node): ?[/* scrollLeft: */ number, /* scrollTop: */ number] => {
ensureHostNode(node);
Expand Down Expand Up @@ -497,6 +524,34 @@ const FabricUIManagerMock: IFabricUIManagerMock = {
},
),

getInnerSize: jest.fn(
(node: Node): ?[/* width: */ number, /* height: */ number] => {
ensureHostNode(node);

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

const innerSizeForTests: ?{
width: number,
height: number,
...
} =
// $FlowExpectedError[prop-missing]
currentProps.__innerSizeForTests;

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

const {width, height} = innerSizeForTests;
return [width, height];
},
),

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 @@ -1051,7 +1051,7 @@ jsi::Value UIManagerBinding::get(
}

if (methodName == "getOffset") {
// This is a method to access offset information for React Native nodes, to
// This is a method to access the offset information for a shadow node, to
// implement these methods:
// * `HTMLElement.prototype.offsetParent`: see
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent.
Expand Down Expand Up @@ -1099,9 +1099,10 @@ jsi::Value UIManagerBinding::get(
}

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

if (layoutMetrics == EmptyLayoutMetrics) {
return jsi::Value::undefined();
}
Expand Down Expand Up @@ -1140,7 +1141,7 @@ jsi::Value UIManagerBinding::get(
}

if (methodName == "getScrollPosition") {
// This is a method to access scroll information for React Native nodes, to
// This is a method to access scroll information for a shadow node, to
// implement these methods:
// * `Element.prototype.scrollLeft`: see
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft.
Expand Down Expand Up @@ -1180,9 +1181,10 @@ jsi::Value UIManagerBinding::get(
}

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

if (layoutMetrics == EmptyLayoutMetrics) {
return jsi::Value::undefined();
}
Expand All @@ -1207,6 +1209,58 @@ jsi::Value UIManagerBinding::get(
});
}

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

// 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 inner size.

// getInnerSize(shadowNode: ShadowNode):
// ?[
// /* width: */ number,
// /* height: */ 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();
}

auto innerFrame = getInnerFrame(layoutMetrics);

return jsi::Array::createWithElements(
runtime,
jsi::Value{runtime, std::round(innerFrame.size.width)},
jsi::Value{runtime, std::round(innerFrame.size.height)});
});
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
#include <react/debug/react_native_assert.h>
#include <react/renderer/components/text/RawTextShadowNode.h>
#include <react/renderer/core/EventHandler.h>
#include <react/renderer/core/LayoutMetrics.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/core/TraitCast.h>
#include <react/renderer/graphics/Rect.h>
#include <react/utils/CoreFeatures.h>

namespace facebook::react {
Expand Down Expand Up @@ -219,4 +221,16 @@ inline static void getTextContentInShadowNode(
getTextContentInShadowNode(*childNode.get(), result);
}
}

// Origin: the outer border of the node.
// Size: includes content and padding (but no borders).
inline static Rect getInnerFrame(LayoutMetrics layoutMetrics) {
return Rect{
Point{layoutMetrics.borderWidth.left, layoutMetrics.borderWidth.top},
Size{
layoutMetrics.frame.size.width - layoutMetrics.borderWidth.left -
layoutMetrics.borderWidth.right,
layoutMetrics.frame.size.height - layoutMetrics.borderWidth.top -
layoutMetrics.borderWidth.bottom}};
}
} // namespace facebook::react

0 comments on commit 1330d5e

Please sign in to comment.