diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js index 7f98be1c9f37b..cbd6e4937e6d3 100644 --- a/packages/react-native-renderer/src/ReactFabricHostConfig.js +++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js @@ -56,6 +56,7 @@ const { unstable_DiscreteEventPriority: FabricDiscretePriority, unstable_getCurrentEventPriority: fabricGetCurrentEventPriority, setNativeProps, + getBoundingClientRect: fabricGetBoundingClientRect, } = nativeFabricUIManager; const {get: getViewConfigForType} = ReactNativeViewConfigRegistry; @@ -210,6 +211,20 @@ class ReactFabricHostComponent { } } + unstable_getBoundingClientRect(): DOMRect { + const {stateNode} = this._internalInstanceHandle; + if (stateNode != null) { + const rect = fabricGetBoundingClientRect(stateNode.node); + + 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); + } + setNativeProps(nativeProps: Object) { if (__DEV__) { warnForStyleProps(nativeProps, this.viewConfig.validAttributes); diff --git a/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager.js b/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager.js index 72f9fa822f493..33dccc1e8fb56 100644 --- a/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager.js +++ b/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/InitializeNativeFabricUIManager.js @@ -149,6 +149,19 @@ const RCTFabricUIManager = { callback(10, 10, 100, 100); }), + getBoundingClientRect: jest.fn(function getBoundingClientRect(node) { + if (typeof node !== 'object') { + throw new Error( + `Expected node to be an object, was passed "${typeof node}"`, + ); + } + + if (typeof node.viewName !== 'string') { + throw new Error('Expected node to be a host node.'); + } + + return [10, 10, 100, 100]; + }), measureLayout: jest.fn(function measureLayout( node, relativeNode, @@ -181,3 +194,19 @@ const RCTFabricUIManager = { }; global.nativeFabricUIManager = RCTFabricUIManager; + +// DOMRect isn't provided by jsdom, but it's used by `ReactFabricHostComponent`. +// This is a basic implementation for testing. +global.DOMRect = class DOMRect { + constructor(x, y, width, height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + toJSON() { + const {x, y, width, height} = this; + return {x, y, width, height}; + } +}; diff --git a/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js index dcbd07db69108..cace77f902482 100644 --- a/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactFabricHostComponent-test.internal.js @@ -200,6 +200,33 @@ describe('measureLayout', () => { }); }); +describe('unstable_getBoundingClientRect', () => { + test('component.unstable_getBoundingClientRect() returns DOMRect', () => { + const [[fooRef]] = mockRenderKeys([['foo']]); + + const rect = fooRef.unstable_getBoundingClientRect(); + + expect(nativeFabricUIManager.getBoundingClientRect).toHaveBeenCalledTimes( + 1, + ); + expect(rect.toJSON()).toMatchObject({ + x: 10, + y: 10, + width: 100, + height: 100, + }); + }); + + test('unmounted.unstable_getBoundingClientRect() returns empty DOMRect', () => { + const [[fooRef]] = mockRenderKeys([['foo'], null]); + + const rect = fooRef.unstable_getBoundingClientRect(); + + expect(nativeFabricUIManager.getBoundingClientRect).not.toHaveBeenCalled(); + expect(rect.toJSON()).toMatchObject({x: 0, y: 0, width: 0, height: 0}); + }); +}); + describe('setNativeProps', () => { test('setNativeProps(...) invokes setNativeProps on Fabric UIManager', () => { const { diff --git a/scripts/flow/react-native-host-hooks.js b/scripts/flow/react-native-host-hooks.js index e3c98114935fc..5fcee3b57da7c 100644 --- a/scripts/flow/react-native-host-hooks.js +++ b/scripts/flow/react-native-host-hooks.js @@ -201,6 +201,14 @@ declare var nativeFabricUIManager: { onFail: () => void, onSuccess: __MeasureLayoutOnSuccessCallback, ) => void, + getBoundingClientRect: ( + node: Node, + ) => [ + /* x:*/ number, + /* y:*/ number, + /* width:*/ number, + /* height:*/ number, + ], findNodeAtPoint: ( node: Node, locationX: number,