mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Support root offsets for Fantom and getBoundingClientRect (#51817)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51817 Changelog: [General][Fixed] - Support viewport offsets for Fantom root and fix `getBoundingClientRect` to respect viewport offsets Reviewed By: rubennorte Differential Revision: D75632540 fbshipit-source-id: a3989f1470156dd6be83ad7b03de8cb4de1a56ac
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ac8d677cb4
commit
b5c62f52d1
@@ -25,6 +25,8 @@ import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/React
|
||||
function getActualViewportDimensions(root: Root): {
|
||||
viewportWidth: number,
|
||||
viewportHeight: number,
|
||||
viewportOffsetX: number,
|
||||
viewportOffsetY: number,
|
||||
} {
|
||||
Fantom.runTask(() => {
|
||||
root.render(<View />);
|
||||
@@ -34,6 +36,8 @@ function getActualViewportDimensions(root: Root): {
|
||||
return {
|
||||
viewportWidth: rect.width,
|
||||
viewportHeight: rect.height,
|
||||
viewportOffsetX: rect.x,
|
||||
viewportOffsetY: rect.y,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -301,17 +305,23 @@ describe('Fantom', () => {
|
||||
expect(getActualViewportDimensions(rootWithDefaults)).toEqual({
|
||||
viewportWidth: 390,
|
||||
viewportHeight: 844,
|
||||
viewportOffsetX: 0,
|
||||
viewportOffsetY: 0,
|
||||
});
|
||||
|
||||
const rootWithCustomWidthAndHeight = Fantom.createRoot({
|
||||
viewportWidth: 200,
|
||||
viewportHeight: 600,
|
||||
viewportOffsetX: 20,
|
||||
viewportOffsetY: 102,
|
||||
});
|
||||
|
||||
expect(getActualViewportDimensions(rootWithCustomWidthAndHeight)).toEqual(
|
||||
{
|
||||
viewportWidth: 200,
|
||||
viewportHeight: 600,
|
||||
viewportOffsetX: 20,
|
||||
viewportOffsetY: 102,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
+8
@@ -37,6 +37,8 @@ export type RootConfig = {
|
||||
viewportWidth?: number,
|
||||
viewportHeight?: number,
|
||||
devicePixelRatio?: number,
|
||||
viewportOffsetX?: number,
|
||||
viewportOffsetY?: number,
|
||||
};
|
||||
|
||||
export {getConstants} from './Constants';
|
||||
@@ -50,6 +52,8 @@ class Root {
|
||||
#surfaceId: number;
|
||||
#viewportWidth: number;
|
||||
#viewportHeight: number;
|
||||
#viewportOffsetX: number;
|
||||
#viewportOffsetY: number;
|
||||
#devicePixelRatio: number;
|
||||
#document: ?ReactNativeDocument;
|
||||
|
||||
@@ -62,6 +66,8 @@ class Root {
|
||||
this.#devicePixelRatio =
|
||||
config?.devicePixelRatio ?? DEFAULT_DEVICE_PIXEL_RATIO;
|
||||
globalSurfaceIdCounter += 10;
|
||||
this.#viewportOffsetX = config?.viewportOffsetX ?? 0;
|
||||
this.#viewportOffsetY = config?.viewportOffsetY ?? 0;
|
||||
}
|
||||
|
||||
// $FlowExpectedError[unsafe-getters-setters]
|
||||
@@ -88,6 +94,8 @@ class Root {
|
||||
this.#viewportWidth,
|
||||
this.#viewportHeight,
|
||||
this.#devicePixelRatio,
|
||||
this.#viewportOffsetX,
|
||||
this.#viewportOffsetY,
|
||||
);
|
||||
this.#hasRendered = true;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,28 @@ LayoutableShadowNode::LayoutableShadowNode(
|
||||
layoutMetrics_(static_cast<const LayoutableShadowNode&>(sourceShadowNode)
|
||||
.layoutMetrics_) {}
|
||||
|
||||
LayoutMetrics LayoutableShadowNode::computeLayoutMetricsFromRoot(
|
||||
const ShadowNodeFamily& descendantNodeFamily,
|
||||
const LayoutableShadowNode& rootNode,
|
||||
LayoutInspectingPolicy policy) {
|
||||
// Prelude.
|
||||
|
||||
if (&descendantNodeFamily == &rootNode.getFamily()) {
|
||||
// If calculating layout for root node
|
||||
auto layoutMetrics = rootNode.getLayoutMetrics();
|
||||
if (layoutMetrics.displayType == DisplayType::None) {
|
||||
return EmptyLayoutMetrics;
|
||||
}
|
||||
if (policy.includeTransform) {
|
||||
layoutMetrics.frame = layoutMetrics.frame * rootNode.getTransform();
|
||||
}
|
||||
return layoutMetrics;
|
||||
}
|
||||
|
||||
auto ancestors = descendantNodeFamily.getAncestors(rootNode);
|
||||
return computeRelativeLayoutMetrics(ancestors, policy);
|
||||
}
|
||||
|
||||
LayoutMetrics LayoutableShadowNode::computeRelativeLayoutMetrics(
|
||||
const ShadowNodeFamily& descendantNodeFamily,
|
||||
const LayoutableShadowNode& ancestorNode,
|
||||
|
||||
@@ -47,6 +47,16 @@ class LayoutableShadowNode : public ShadowNode {
|
||||
|
||||
using UnsharedList = std::vector<LayoutableShadowNode*>;
|
||||
|
||||
/*
|
||||
* Returns layout metrics of a node represented as `descendantNodeFamily`
|
||||
* from `rootNode` like `computeRelativeLayoutMetrics` but returns absolute
|
||||
* transform for node if used as root.
|
||||
*/
|
||||
static LayoutMetrics computeLayoutMetricsFromRoot(
|
||||
const ShadowNodeFamily& descendantNodeFamily,
|
||||
const LayoutableShadowNode& rootNode,
|
||||
LayoutInspectingPolicy policy);
|
||||
|
||||
/*
|
||||
* Returns layout metrics of a node represented as `descendantNodeFamily`
|
||||
* computed relatively to given `ancestorNode`. Returns `EmptyLayoutMetrics`
|
||||
|
||||
@@ -112,7 +112,7 @@ void getTextContentInShadowNode(
|
||||
}
|
||||
}
|
||||
|
||||
LayoutMetrics getRelativeLayoutMetrics(
|
||||
LayoutMetrics getLayoutMetricsFromRoot(
|
||||
const ShadowNode& ancestorNode,
|
||||
const ShadowNode& shadowNode,
|
||||
LayoutableShadowNode::LayoutInspectingPolicy policy) {
|
||||
@@ -123,7 +123,7 @@ LayoutMetrics getRelativeLayoutMetrics(
|
||||
return EmptyLayoutMetrics;
|
||||
}
|
||||
|
||||
return LayoutableShadowNode::computeRelativeLayoutMetrics(
|
||||
return LayoutableShadowNode::computeLayoutMetricsFromRoot(
|
||||
shadowNode.getFamily(), *layoutableAncestorShadowNode, policy);
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ DOMRect getBoundingClientRect(
|
||||
return DOMRect{};
|
||||
}
|
||||
|
||||
auto layoutMetrics = getRelativeLayoutMetrics(
|
||||
auto layoutMetrics = getLayoutMetricsFromRoot(
|
||||
*currentRevision,
|
||||
shadowNode,
|
||||
{.includeTransform = includeTransform, .includeViewportOffset = true});
|
||||
@@ -295,13 +295,13 @@ DOMOffset getOffset(
|
||||
|
||||
// If the node is not displayed (itself or any of its ancestors has
|
||||
// "display: none"), this returns an empty layout metrics object.
|
||||
auto shadowNodeLayoutMetricsRelativeToRoot = getRelativeLayoutMetrics(
|
||||
auto shadowNodeLayoutMetricsRelativeToRoot = getLayoutMetricsFromRoot(
|
||||
*currentRevision, shadowNode, {.includeTransform = false});
|
||||
if (shadowNodeLayoutMetricsRelativeToRoot == EmptyLayoutMetrics) {
|
||||
return DOMOffset{};
|
||||
}
|
||||
|
||||
auto positionedAncestorLayoutMetricsRelativeToRoot = getRelativeLayoutMetrics(
|
||||
auto positionedAncestorLayoutMetricsRelativeToRoot = getLayoutMetricsFromRoot(
|
||||
*currentRevision,
|
||||
*positionedAncestorOfShadowNodeInCurrentRevision,
|
||||
{.includeTransform = false});
|
||||
@@ -340,7 +340,7 @@ DOMPoint getScrollPosition(
|
||||
|
||||
// If the node is not displayed (itself or any of its ancestors has
|
||||
// "display: none"), this returns an empty layout metrics object.
|
||||
auto layoutMetrics = getRelativeLayoutMetrics(
|
||||
auto layoutMetrics = getLayoutMetricsFromRoot(
|
||||
*currentRevision,
|
||||
*shadowNodeInCurrentRevision,
|
||||
{.includeTransform = true});
|
||||
@@ -374,7 +374,7 @@ DOMSizeRounded getScrollSize(
|
||||
|
||||
// If the node is not displayed (itself or any of its ancestors has
|
||||
// "display: none"), this returns an empty layout metrics object.
|
||||
auto layoutMetrics = getRelativeLayoutMetrics(
|
||||
auto layoutMetrics = getLayoutMetricsFromRoot(
|
||||
*currentRevision,
|
||||
*shadowNodeInCurrentRevision,
|
||||
{.includeTransform = false});
|
||||
@@ -410,7 +410,7 @@ DOMSizeRounded getInnerSize(
|
||||
|
||||
// If the node is not displayed (itself or any of its ancestors has
|
||||
// "display: none"), this returns an empty layout metrics object.
|
||||
auto layoutMetrics = getRelativeLayoutMetrics(
|
||||
auto layoutMetrics = getLayoutMetricsFromRoot(
|
||||
*currentRevision,
|
||||
*shadowNodeInCurrentRevision,
|
||||
{.includeTransform = false});
|
||||
@@ -437,7 +437,7 @@ DOMBorderWidthRounded getBorderWidth(
|
||||
|
||||
// If the node is not displayed (itself or any of its ancestors has
|
||||
// "display: none"), this returns an empty layout metrics object.
|
||||
auto layoutMetrics = getRelativeLayoutMetrics(
|
||||
auto layoutMetrics = getLayoutMetricsFromRoot(
|
||||
*currentRevision,
|
||||
*shadowNodeInCurrentRevision,
|
||||
{.includeTransform = false});
|
||||
@@ -479,7 +479,7 @@ RNMeasureRect measure(
|
||||
return RNMeasureRect{};
|
||||
}
|
||||
|
||||
auto layoutMetrics = getRelativeLayoutMetrics(
|
||||
auto layoutMetrics = getLayoutMetricsFromRoot(
|
||||
*currentRevision,
|
||||
*shadowNodeInCurrentRevision,
|
||||
{.includeTransform = true, .includeViewportOffset = false});
|
||||
@@ -514,7 +514,7 @@ DOMRect measureInWindow(
|
||||
return DOMRect{};
|
||||
}
|
||||
|
||||
auto layoutMetrics = getRelativeLayoutMetrics(
|
||||
auto layoutMetrics = getLayoutMetricsFromRoot(
|
||||
*currentRevision,
|
||||
*shadowNodeInCurrentRevision,
|
||||
{.includeTransform = true, .includeViewportOffset = true});
|
||||
@@ -548,7 +548,7 @@ std::optional<DOMRect> measureLayout(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto layoutMetrics = getRelativeLayoutMetrics(
|
||||
auto layoutMetrics = getLayoutMetricsFromRoot(
|
||||
*relativeToShadowNodeInCurrentRevision,
|
||||
*shadowNodeInCurrentRevision,
|
||||
{.includeTransform = false});
|
||||
|
||||
@@ -69,6 +69,8 @@ interface Spec extends TurboModule {
|
||||
viewportWidth: number,
|
||||
viewportHeight: number,
|
||||
devicePixelRatio: number,
|
||||
viewportOffsetX?: number,
|
||||
viewportOffsetY?: number,
|
||||
) => void;
|
||||
stopSurface: (surfaceId: number) => void;
|
||||
enqueueNativeEvent: (
|
||||
|
||||
+8
-3
@@ -107,7 +107,12 @@ describe('ReactNativeDocument', () => {
|
||||
it('provides a documentElement node that behaves like a regular element', () => {
|
||||
const nodeRef = createRef<HostInstance>();
|
||||
|
||||
const root = Fantom.createRoot({viewportWidth: 200, viewportHeight: 100});
|
||||
const root = Fantom.createRoot({
|
||||
viewportWidth: 200,
|
||||
viewportHeight: 100,
|
||||
viewportOffsetX: 111,
|
||||
viewportOffsetY: 222,
|
||||
});
|
||||
Fantom.runTask(() => {
|
||||
root.render(<View ref={nodeRef} />);
|
||||
});
|
||||
@@ -118,8 +123,8 @@ describe('ReactNativeDocument', () => {
|
||||
const {x, y, width, height} =
|
||||
document.documentElement.getBoundingClientRect();
|
||||
|
||||
expect(x).toBe(0);
|
||||
expect(y).toBe(0);
|
||||
expect(x).toBe(111);
|
||||
expect(y).toBe(222);
|
||||
expect(width).toBe(200);
|
||||
expect(height).toBe(100);
|
||||
|
||||
|
||||
+89
@@ -833,6 +833,40 @@ describe('ReactNativeElement', () => {
|
||||
expect(boundingClientRectAfterUnmount.width).toBe(0);
|
||||
expect(boundingClientRectAfterUnmount.height).toBe(0);
|
||||
});
|
||||
|
||||
it('returns a DOMRect with its size and position respecting viewport offsets', () => {
|
||||
const elementRef = createRef<HostInstance>();
|
||||
|
||||
const root = Fantom.createRoot({
|
||||
viewportOffsetX: 111,
|
||||
viewportOffsetY: 222,
|
||||
});
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
key="parent"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: 5.1,
|
||||
top: 10.2,
|
||||
width: 50.3,
|
||||
height: 100.4,
|
||||
}}
|
||||
ref={elementRef}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const element = ensureReactNativeElement(elementRef.current);
|
||||
|
||||
const boundingClientRect = element.getBoundingClientRect();
|
||||
expect(boundingClientRect).toBeInstanceOf(DOMRect);
|
||||
expect(boundingClientRect.x).toBe(116);
|
||||
expect(boundingClientRect.y).toBeCloseTo(232.33);
|
||||
expect(boundingClientRect.width).toBeCloseTo(50.33);
|
||||
expect(boundingClientRect.height).toBeCloseTo(100.33);
|
||||
});
|
||||
});
|
||||
|
||||
describe('scrollLeft / scrollTop', () => {
|
||||
@@ -1111,6 +1145,61 @@ describe('ReactNativeElement', () => {
|
||||
expect(element.offsetLeft).toBe(0);
|
||||
expect(element.offsetParent).toBe(null);
|
||||
});
|
||||
|
||||
it('return the rounded offset values from the parent with view offsets', () => {
|
||||
const parentRef = createRef<HostInstance>();
|
||||
const elementRef = createRef<HostInstance>();
|
||||
|
||||
const root = Fantom.createRoot({
|
||||
viewportOffsetX: 111,
|
||||
viewportOffsetY: 222,
|
||||
});
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<View key="parent" ref={parentRef}>
|
||||
<View
|
||||
key="child"
|
||||
style={{marginTop: 10.6, marginLeft: 5.1}}
|
||||
ref={elementRef}
|
||||
/>
|
||||
</View>,
|
||||
);
|
||||
});
|
||||
|
||||
const parentElement = ensureReactNativeElement(parentRef.current);
|
||||
const element = ensureReactNativeElement(elementRef.current);
|
||||
|
||||
expect(element.offsetTop).toBe(11);
|
||||
expect(element.offsetLeft).toBe(5);
|
||||
expect(element.offsetParent).toBe(parentElement);
|
||||
|
||||
expect(parentElement.offsetParent).toBe(root.document.documentElement);
|
||||
});
|
||||
|
||||
it('return the rounded offset values from the root with view offsets', () => {
|
||||
const elementRef = createRef<HostInstance>();
|
||||
|
||||
const root = Fantom.createRoot({
|
||||
viewportOffsetX: 111,
|
||||
viewportOffsetY: 222,
|
||||
});
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<View
|
||||
key="child"
|
||||
style={{marginTop: 10.6, marginLeft: 5.1}}
|
||||
ref={elementRef}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const element = ensureReactNativeElement(elementRef.current);
|
||||
|
||||
expect(element.offsetTop).toBe(11);
|
||||
expect(element.offsetLeft).toBe(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user