diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index f2f5b1f6d93..19d6b952860 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -9607,7 +9607,6 @@ exports[`public API should not change unintentionally src/private/webapis/inters observer: IntersectionObserver ) => mixed; export interface IntersectionObserverInit { - root?: ?ReactNativeElement; threshold?: number | $ReadOnlyArray; rnRootThreshold?: number | $ReadOnlyArray; } diff --git a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp index 628553cfd09..9b54c3a5abd 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp @@ -70,20 +70,12 @@ jsi::Object NativeIntersectionObserver::observeV2( auto shadowNode = shadowNodeFromValue(runtime, std::move(options.targetShadowNode)); auto shadowNodeFamily = shadowNode->getFamilyShared(); - - std::optional observationRootShadowNodeFamily; - if (options.rootShadowNode.isObject()) { - observationRootShadowNodeFamily = - shadowNodeFromValue(runtime, options.rootShadowNode)->getFamilyShared(); - } - auto thresholds = options.thresholds; auto rootThresholds = options.rootThresholds; auto& uiManager = getUIManagerFromRuntime(runtime); intersectionObserverManager_.observe( intersectionObserverId, - observationRootShadowNodeFamily, shadowNodeFamily, thresholds, rootThresholds, diff --git a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h index 0d932e996d5..9ea6487b9f7 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h @@ -22,8 +22,6 @@ using NativeIntersectionObserverObserveOptions = NativeIntersectionObserverNativeIntersectionObserverObserveOptions< // intersectionObserverId NativeIntersectionObserverIntersectionObserverId, - // rootShadowNode - jsi::Value, // targetShadowNode jsi::Object, // thresholds diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp index d2e70b1d061..bc8d0c875a5 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp @@ -16,41 +16,17 @@ namespace facebook::react { IntersectionObserver::IntersectionObserver( IntersectionObserverObserverId intersectionObserverId, - std::optional observationRootShadowNodeFamily, ShadowNodeFamily::Shared targetShadowNodeFamily, std::vector thresholds, std::optional> rootThresholds) : intersectionObserverId_(intersectionObserverId), - observationRootShadowNodeFamily_( - std::move(observationRootShadowNodeFamily)), targetShadowNodeFamily_(std::move(targetShadowNodeFamily)), thresholds_(std::move(thresholds)), rootThresholds_(std::move(rootThresholds)) {} -static std::shared_ptr getShadowNode( - const ShadowNodeFamily::AncestorList& ancestors) { - if (ancestors.empty()) { - return nullptr; - } - - const auto& lastAncestor = ancestors.back(); - const ShadowNode& parentNode = lastAncestor.first.get(); - int childIndex = lastAncestor.second; - - const std::shared_ptr& childNode = - parentNode.getChildren().at(childIndex); - return childNode; -} - -static Rect getRootNodeBoundingRect(const RootShadowNode& rootShadowNode) { - const auto layoutableRootShadowNode = - dynamic_cast(&rootShadowNode); - - react_native_assert( - layoutableRootShadowNode != nullptr && - "RootShadowNode instances must always inherit from LayoutableShadowNode."); - - auto layoutMetrics = layoutableRootShadowNode->getLayoutMetrics(); +static Rect getRootBoundingRect( + const LayoutableShadowNode& layoutableRootShadowNode) { + auto layoutMetrics = layoutableRootShadowNode.getLayoutMetrics(); if (layoutMetrics == EmptyLayoutMetrics || layoutMetrics.displayType == DisplayType::None) { @@ -59,12 +35,13 @@ static Rect getRootNodeBoundingRect(const RootShadowNode& rootShadowNode) { // Apply the transform to translate the root view to its location in the // viewport. - return layoutMetrics.frame * layoutableRootShadowNode->getTransform(); + return layoutMetrics.frame * layoutableRootShadowNode.getTransform(); } -static Rect getBoundingRect(const ShadowNodeFamily::AncestorList& ancestors) { +static Rect getTargetBoundingRect( + const ShadowNodeFamily::AncestorList& targetAncestors) { auto layoutMetrics = LayoutableShadowNode::computeRelativeLayoutMetrics( - ancestors, + targetAncestors, {/* .includeTransform = */ true, /* .includeViewportOffset = */ true}); return layoutMetrics == EmptyLayoutMetrics ? Rect{} : layoutMetrics.frame; @@ -86,7 +63,7 @@ static Rect getClippedTargetBoundingRect( static Rect computeIntersection( const Rect& rootBoundingRect, const Rect& targetBoundingRect, - const ShadowNodeFamily::AncestorList& targetToRootAncestors) { + const ShadowNodeFamily::AncestorList& targetAncestors) { auto absoluteIntersectionRect = Rect::intersect(rootBoundingRect, targetBoundingRect); @@ -102,15 +79,10 @@ static Rect computeIntersection( return {}; } - // Coordinates of the target after clipping the parts hidden by a parent, - // until till the root (e.g.: in scroll views, or in views with a parent with - // overflow: hidden) - auto clippedTargetFromRoot = - getClippedTargetBoundingRect(targetToRootAncestors); - - auto clippedTargetBoundingRect = Rect{ - rootBoundingRect.origin + clippedTargetFromRoot.origin, - clippedTargetFromRoot.size}; + // Coordinates of the target after clipping the parts hidden by a parent + // (e.g.: in scroll views, or in views with a parent with overflow: hidden) + auto clippedTargetBoundingRect = + getClippedTargetBoundingRect(targetAncestors); return Rect::intersect(rootBoundingRect, clippedTargetBoundingRect); } @@ -133,34 +105,23 @@ std::optional IntersectionObserver::updateIntersectionObservation( const RootShadowNode& rootShadowNode, double time) { - bool hasCustomRoot = observationRootShadowNodeFamily_.has_value(); + const auto layoutableRootShadowNode = + dynamic_cast(&rootShadowNode); - auto rootAncestors = hasCustomRoot - ? observationRootShadowNodeFamily_.value()->getAncestors(rootShadowNode) - : ShadowNodeFamily::AncestorList{}; - - // Absolute coordinates of the root - auto rootBoundingRect = hasCustomRoot - ? getBoundingRect(rootAncestors) - : getRootNodeBoundingRect(rootShadowNode); + react_native_assert( + layoutableRootShadowNode != nullptr && + "RootShadowNode instances must always inherit from LayoutableShadowNode."); auto targetAncestors = targetShadowNodeFamily_->getAncestors(rootShadowNode); + // Absolute coordinates of the root + auto rootBoundingRect = getRootBoundingRect(*layoutableRootShadowNode); + // Absolute coordinates of the target - auto targetBoundingRect = getBoundingRect(targetAncestors); - - if ((hasCustomRoot && rootAncestors.empty()) || targetAncestors.empty()) { - // If observation root or target is not a descendant of `rootShadowNode` - return setNotIntersectingState( - rootBoundingRect, targetBoundingRect, {}, time); - } - - auto targetToRootAncestors = hasCustomRoot - ? targetShadowNodeFamily_->getAncestors(*getShadowNode(rootAncestors)) - : targetAncestors; + auto targetBoundingRect = getTargetBoundingRect(targetAncestors); auto intersectionRect = computeIntersection( - rootBoundingRect, targetBoundingRect, targetToRootAncestors); + rootBoundingRect, targetBoundingRect, targetAncestors); Float targetBoundingRectArea = targetBoundingRect.size.width * targetBoundingRect.size.height; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h index 89483ec3b53..d5dd24174af 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h @@ -40,7 +40,6 @@ class IntersectionObserver { public: IntersectionObserver( IntersectionObserverObserverId intersectionObserverId, - std::optional observationRootShadowNodeFamily, ShadowNodeFamily::Shared targetShadowNodeFamily, std::vector thresholds, std::optional> rootThresholds = std::nullopt); @@ -82,7 +81,6 @@ class IntersectionObserver { double time); IntersectionObserverObserverId intersectionObserverId_; - std::optional observationRootShadowNodeFamily_; ShadowNodeFamily::Shared targetShadowNodeFamily_; std::vector thresholds_; std::optional> rootThresholds_; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp index a8eb27afeaf..4725628b898 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp @@ -39,8 +39,6 @@ IntersectionObserverManager::IntersectionObserverManager() = default; void IntersectionObserverManager::observe( IntersectionObserverObserverId intersectionObserverId, - const std::optional& - observationRootShadowNodeFamily, const ShadowNodeFamily::Shared& shadowNodeFamily, std::vector thresholds, std::optional> rootThresholds, @@ -60,7 +58,6 @@ void IntersectionObserverManager::observe( auto& observers = observersBySurfaceId_[surfaceId]; observers.emplace_back(std::make_unique( intersectionObserverId, - observationRootShadowNodeFamily, shadowNodeFamily, std::move(thresholds), std::move(rootThresholds))); diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h index 8faa627b40a..9331bcfef67 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h @@ -27,7 +27,6 @@ class IntersectionObserverManager final void observe( IntersectionObserverObserverId intersectionObserverId, - const std::optional& observationRootShadowNode, const ShadowNodeFamily::Shared& shadowNode, std::vector thresholds, std::optional> rootThresholds, diff --git a/packages/react-native/src/private/webapis/intersectionobserver/IntersectionObserver.js b/packages/react-native/src/private/webapis/intersectionobserver/IntersectionObserver.js index f9b4c7b61b3..7dfa3b6e60f 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/IntersectionObserver.js +++ b/packages/react-native/src/private/webapis/intersectionobserver/IntersectionObserver.js @@ -23,7 +23,7 @@ export type IntersectionObserverCallback = ( ) => mixed; export interface IntersectionObserverInit { - root?: ?ReactNativeElement; + // root?: ReactNativeElement, // This option exists on the Web but it's not currently supported in React Native. // rootMargin?: string, // This option exists on the Web but it's not currently supported in React Native. threshold?: number | $ReadOnlyArray; @@ -67,7 +67,6 @@ export default class IntersectionObserver { _observationTargets: Set = new Set(); _intersectionObserverId: ?IntersectionObserverId; _rootThresholds: $ReadOnlyArray | null; - _root: ReactNativeElement | null; constructor( callback: IntersectionObserverCallback, @@ -86,18 +85,16 @@ export default class IntersectionObserver { } // $FlowExpectedError[prop-missing] it's not typed in React Native but exists on Web. - if (options?.rootMargin != null) { + if (options?.root != null) { throw new TypeError( - "Failed to construct 'IntersectionObserver': rootMargin is not supported", + "Failed to construct 'IntersectionObserver': root is not supported", ); } - if ( - options?.root != null && - !(options?.root instanceof ReactNativeElement) - ) { + // $FlowExpectedError[prop-missing] it's not typed in React Native but exists on Web. + if (options?.rootMargin != null) { throw new TypeError( - "Failed to construct 'IntersectionObserver': Failed to read the 'root' property from 'IntersectionObserverInit': The provided value is not of type '(null or ReactNativeElement)", + "Failed to construct 'IntersectionObserver': rootMargin is not supported", ); } @@ -108,7 +105,6 @@ export default class IntersectionObserver { options?.threshold, this._rootThresholds != null, // only provide default if no rootThreshold ); - this._root = options?.root ?? null; } /** @@ -120,7 +116,7 @@ export default class IntersectionObserver { * NOTE: This cannot currently be configured and `root` is always `null`. */ get root(): ReactNativeElement | null { - return this._root; + return null; } /** @@ -187,7 +183,6 @@ export default class IntersectionObserver { const didStartObserving = IntersectionObserverManager.observe({ intersectionObserverId: this._getOrCreateIntersectionObserverId(), - root: this._root, target, }); diff --git a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-benchmark-itest.js b/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-benchmark-itest.js index a6fb620e444..1eb1797d4ac 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-benchmark-itest.js +++ b/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-benchmark-itest.js @@ -30,8 +30,7 @@ setUpIntersectionObserver(); const nodeRef = createRef(); let node: ReactNativeElement; -const rootRef = createRef(); -let rootNode: ReactNativeElement; + const scrollViewRef = createRef(); let scrollViewNode: ReactNativeElement; let observer: IntersectionObserverType; @@ -97,27 +96,6 @@ Fantom.unstable_benchmark }, }, ) - .test( - 'Create IntersectionObserver with custom root', - () => { - Fantom.runTask(() => { - observer = new IntersectionObserver(mockCallback, {root: rootNode}); - }); - }, - { - beforeEach: () => { - mockCallback = jest.fn(); - Fantom.runTask(() => { - root.render(); - }); - rootNode = ensureInstance(rootRef.current, ReactNativeElement); - }, - afterEach: () => { - expect(mockCallback).not.toHaveBeenCalled(); - cleanup(root, observer); - }, - }, - ) .test( 'Observe a mounted view', () => { @@ -144,39 +122,6 @@ Fantom.unstable_benchmark }, }, ) - .test( - 'Observe a mounted view with custom root', - () => { - Fantom.runTask(() => { - observer.observe(node); - }); - }, - { - beforeEach: () => { - mockCallback = jest.fn(); - Fantom.runTask(() => { - root.render( - - - , - ); - }); - node = ensureInstance(nodeRef.current, ReactNativeElement); - rootNode = ensureInstance(rootRef.current, ReactNativeElement); - Fantom.runTask(() => { - observer = new IntersectionObserver(mockCallback, {root: rootNode}); - }); - }, - afterEach: () => { - expect(mockCallback).toHaveBeenCalledTimes(1); - const [entries] = mockCallback.mock.lastCall; - expect(entries.length).toBe(1); - expect(entries[0].isIntersecting).toBe(true); - - cleanup(root, observer); - }, - }, - ) .test( 'ScrollView no intersection, no observation', () => { @@ -271,48 +216,6 @@ Fantom.unstable_benchmark }, }, ) - .test( - 'ScrollView no intersection, observation with custom root', - () => { - scrollBy1(scrollViewNode, VIEWPORT_HEIGHT); - }, - { - beforeEach: () => { - mockCallback = jest.fn(); - - Fantom.runTask(() => { - root.render( - - {renderElementAtYScrollPosition( - VIEWPORT_HEIGHT + 50, - , - )} - , - ); - }); - scrollViewNode = ensureInstance( - scrollViewRef.current, - ReactNativeElement, - ); - node = ensureInstance(nodeRef.current, ReactNativeElement); - Fantom.runTask(() => { - observer = new IntersectionObserver(mockCallback, { - root: scrollViewNode, - }); - observer.observe(node); - }); - }, - afterEach: () => { - expect(mockCallback).toHaveBeenCalledTimes(1); - - const [entries] = mockCallback.mock.lastCall; - expect(entries.length).toBe(1); - expect(entries[0].isIntersecting).toBe(false); - - cleanup(root, observer); - }, - }, - ) .test( 'ScrollView intersection, observation', () => { @@ -357,57 +260,6 @@ Fantom.unstable_benchmark expect(entries3.length).toBe(1); expect(entries3[0].isIntersecting).toBe(false); - cleanup(root, observer); - }, - }, - ) - .test( - 'ScrollView intersection, observation, with custom root', - () => { - scrollBy1(scrollViewNode, VIEWPORT_HEIGHT); - }, - { - beforeEach: () => { - mockCallback = jest.fn(); - - Fantom.runTask(() => { - root.render( - - {renderElementAtYScrollPosition( - -5, - , - )} - , - ); - }); - scrollViewNode = ensureInstance( - scrollViewRef.current, - ReactNativeElement, - ); - node = ensureInstance(nodeRef.current, ReactNativeElement); - Fantom.runTask(() => { - observer = new IntersectionObserver(mockCallback, { - threshold: 1, - root: scrollViewNode, - }); - observer.observe(node); - }); - }, - afterEach: () => { - expect(mockCallback).toHaveBeenCalledTimes(3); - - const [entries1] = mockCallback.mock.calls[0]; - expect(entries1.length).toBe(1); - expect(entries1[0].isIntersecting).toBe(false); - - const [entries2] = mockCallback.mock.calls[1]; - expect(entries2.length).toBe(1); - expect(entries2[0].isIntersecting).toBe(true); - - const [entries3] = mockCallback.mock.calls[2]; - expect(entries3.length).toBe(1); - expect(entries3[0].isIntersecting).toBe(false); - cleanup(root, observer); }, }, diff --git a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js b/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js index bec03d1ed5d..964d772b51d 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js +++ b/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js @@ -90,6 +90,24 @@ describe('IntersectionObserver', () => { ); }); + it('should throw if `root` is provided', () => { + const nodeRef = createRef(); + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + const node = ensureReactNativeElement(nodeRef.current); + + expect(() => { + // $FlowExpectedError[prop-missing] root is not even defined in Flow. + return new IntersectionObserver(() => {}, {root: node}); + }).toThrow( + "Failed to construct 'IntersectionObserver': root is not supported", + ); + }); + it('should throw if `rootMargin` is provided', () => { expect(() => { // $FlowExpectedError[prop-missing] rootMargin is not even defined in Flow. @@ -122,32 +140,6 @@ describe('IntersectionObserver', () => { ); }); - it('should throw if `root` is not a `ReactNativeElement`', () => { - expect(() => { - // $FlowExpectedError[incompatible-call] - observer = new IntersectionObserver(() => {}, {root: 'something'}); - }).toThrow( - "Failed to construct 'IntersectionObserver': Failed to read the 'root' property from 'IntersectionObserverInit': The provided value is not of type '(null or ReactNativeElement)", - ); - }); - - it('should provide access to custom `root`', () => { - const rootRef = React.createRef(); - - const root = Fantom.createRoot(); - Fantom.runTask(() => { - root.render(); - }); - - const rootNode = ensureReactNativeElement(rootRef.current); - - Fantom.runTask(() => { - observer = new IntersectionObserver(() => {}, {root: rootNode}); - }); - - expect(observer?.root).toBe(rootNode); - }); - it('should provide access to `root`, `rootMargin` and `thresholds`', () => { observer = new IntersectionObserver(() => {}); @@ -854,352 +846,6 @@ describe('IntersectionObserver', () => { }); }); - describe('with custom root', () => { - it('should report partial non-intersecting initial state correctly', () => { - const nodeRef = createRef(); - const scrollNodeRef = createRef(); - - const root = Fantom.createRoot({ - viewportWidth: 1000, - viewportHeight: 1000, - }); - - Fantom.runTask(() => { - root.render( - - - , - ); - }); - - const scrollNode = ensureReactNativeElement(scrollNodeRef.current); - const node = ensureReactNativeElement(nodeRef.current); - - // Scroll such that View is partially intersecting - Fantom.scrollTo(scrollNode, {x: 0, y: 25}); - - const intersectionObserverCallback = jest.fn(); - - Fantom.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - threshold: [1], - root: scrollNode, - }); - - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - - expect(reportedObserver).toBe(observer); - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(0.5); - // intersectionRectArea / rootboundsArea - expect(entries[0].rnRootIntersectionRatio).toBe(0.0125); - expect(entries[0].isIntersecting).toBe(false); - expect(entries[0].target).toBe(node); - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 50, - height: 25, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: -25, - width: 50, - height: 50, - }); - // Expect this to be width of the viewport - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 100, - height: 1000, - }); - }); - - it('should report partial intersecting initial state correctly', () => { - const nodeRef = createRef(); - const scrollNodeRef = createRef(); - - const root = Fantom.createRoot({ - viewportWidth: 1000, - viewportHeight: 1000, - }); - Fantom.runTask(() => { - root.render( - - - , - ); - }); - const scrollNode = ensureReactNativeElement(scrollNodeRef.current); - const node = ensureReactNativeElement(nodeRef.current); - - // Scroll such that View is partially intersecting - Fantom.scrollTo(scrollNode, {x: 0, y: 25}); - - const intersectionObserverCallback = jest.fn(); - - Fantom.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - threshold: [], - root: scrollNode, - }); - - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - - expect(reportedObserver).toBe(observer); - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(0.5); - expect(entries[0].rnRootIntersectionRatio).toBe(0.0125); - expect(entries[0].isIntersecting).toBe(true); - expect(entries[0].target).toBe(node); - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 50, - height: 25, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: -25, - width: 50, - height: 50, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 100, - height: 1000, - }); - }); - - it('should report subsequent updates correctly', () => { - const nodeRef = createRef(); - const scrollNodeRef = createRef(); - - const root = Fantom.createRoot({ - viewportWidth: 1000, - viewportHeight: 1000, - }); - - Fantom.runTask(() => { - root.render( - - - , - ); - }); - - const node = ensureReactNativeElement(nodeRef.current); - const scrollNode = ensureReactNativeElement(scrollNodeRef.current); - - expect(node.isConnected).toBe(true); - - const intersectionObserverCallback = jest.fn(); - - Fantom.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - root: scrollNode, - }); - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(1); - expect(entries[0].rnRootIntersectionRatio).toBe(0.1); - expect(entries[0].isIntersecting).toBe(true); - - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 0, - width: 100, - height: 100, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: 0, - width: 100, - height: 100, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 100, - height: 1000, - }); - - expect(reportedObserver).toBe(observer); - - // Move the view out of the viewport - Fantom.scrollTo(scrollNode, {x: 0, y: 200}); - - expect(node.isConnected).toBe(true); - expect(intersectionObserverCallback).toHaveBeenCalledTimes(2); - const [entries2, reportedObserver2] = - intersectionObserverCallback.mock.lastCall; - expect(entries2.length).toBe(1); - expect(entries2[0].isIntersecting).toBe(false); - expect(entries2[0].target).toBe(node); - - expectRectEquals(entries2[0].intersectionRect, { - x: 0, - y: 0, - width: 0, - height: 0, - }); - expectRectEquals(entries2[0].boundingClientRect, { - x: 0, - y: -200, - width: 100, - height: 100, - }); - expectRectEquals(entries2[0].rootBounds, { - x: 0, - y: 0, - width: 100, - height: 1000, - }); - expect(reportedObserver2).toBe(observer); - }); - - it('should report updates to the right observers', () => { - let maybeNode1; - let maybeNode2; - const scrollNodeRef = createRef(); - let observer1: IntersectionObserver; - let observer2: IntersectionObserver; - - const root = Fantom.createRoot({ - viewportWidth: 1000, - viewportHeight: 1000, - }); - - Fantom.runTask(() => { - root.render( - - { - maybeNode1 = receivedNode; - }} - /> - { - maybeNode2 = receivedNode; - }} - /> - , - ); - }); - const node1 = ensureReactNativeElement(maybeNode1); - const node2 = ensureReactNativeElement(maybeNode2); - const scrollNode = ensureReactNativeElement(scrollNodeRef.current); - - // Scroll such that node1 is not intersecting and node 2 is intersecting - Fantom.scrollTo(scrollNode, {x: 0, y: 100}); - - const intersectionObserverCallback1 = jest.fn(); - const intersectionObserverCallback2 = jest.fn(); - - Fantom.runTask(() => { - observer1 = new IntersectionObserver(intersectionObserverCallback1, { - threshold: [0], - }); - - observer1.observe(node1); - observer1.observe(node2); - - observer2 = new IntersectionObserver(intersectionObserverCallback2, { - threshold: [1], - }); - observer2.observe(node2); - }); - - // Verify observer1 is reporting right thing - expect(intersectionObserverCallback1).toHaveBeenCalledTimes(1); - const [entries1, reportedObserver1] = - intersectionObserverCallback1.mock.lastCall; - - expect(reportedObserver1).toBe(observer1); - expect(entries1.length).toBe(2); - - expect(entries1[0].isIntersecting).toBe(false); - expect(entries1[0].intersectionRatio).toBe(0); - expect(entries1[0].target).toBe(node1); - - expectRectEquals(entries1[0].intersectionRect, { - x: 0, - y: 0, - width: 0, - height: 0, - }); - expectRectEquals(entries1[0].boundingClientRect, { - x: 0, - y: -100, - width: 50, - height: 50, - }); - expectRectEquals(entries1[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - - expect(entries1[1].isIntersecting).toBe(true); - expect(entries1[1].intersectionRatio).toBe(0.75); - expect(entries1[1].target).toBe(node2); - - // Verify observer2 is reporting no intersection because the threshold is 1 - expect(intersectionObserverCallback2).toHaveBeenCalledTimes(1); - const [entries2, reportedObserver2] = - intersectionObserverCallback2.mock.lastCall; - - expect(reportedObserver2).toBe(observer2); - expect(entries2.length).toBe(1); - - expect(entries2[0].isIntersecting).toBe(false); - expect(entries2[0].intersectionRatio).toBe(0.75); - expect(entries2[0].target).toBe(node2); - - expectRectEquals(entries2[0].intersectionRect, { - x: 0, - y: 0, - width: 200, - height: 150, - }); - expectRectEquals(entries2[0].boundingClientRect, { - x: 0, - y: -50, - width: 200, - height: 200, - }); - expectRectEquals(entries2[0].rootBounds, { - x: 0, - y: 0, - width: 1000, - height: 1000, - }); - }); - }); - it('should not retain initial children of observed targets', () => { const root = Fantom.createRoot(); observer = new IntersectionObserver(() => {}); @@ -1576,139 +1222,6 @@ describe('IntersectionObserver', () => { }); }); - describe('clipping behavior', () => { - it('should report intersection for clipping ancestor', () => { - const nodeRef = React.createRef(); - const rootRef = React.createRef(); - - const root = Fantom.createRoot({ - viewportWidth: 1000, - viewportHeight: 1000, - }); - Fantom.runTask(() => { - root.render( - - - - - , - , - ); - }); - const node = ensureReactNativeElement(nodeRef.current); - const rootNode = ensureReactNativeElement(rootRef.current); - - const intersectionObserverCallback = jest.fn(); - - Fantom.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - root: rootNode, - }); - - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - - expect(reportedObserver).toBe(observer); - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(1); - // This is the ratio of intersection area / (custom) root area - expect(entries[0].rnRootIntersectionRatio).toBe(0.25); - expect(entries[0].isIntersecting).toBe(true); - expect(entries[0].target).toBe(node); - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 100, - width: 100, - height: 100, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: 100, - width: 100, - height: 100, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 200, - height: 200, - }); - }); - - it('should report intersection for clipping root', () => { - const nodeRef = React.createRef(); - const rootRef = React.createRef(); - - const root = Fantom.createRoot({ - viewportWidth: 1000, - viewportHeight: 1000, - }); - Fantom.runTask(() => { - root.render( - - - , - ); - }); - const node = ensureReactNativeElement(nodeRef.current); - const rootNode = ensureReactNativeElement(rootRef.current); - - const intersectionObserverCallback = jest.fn(); - - Fantom.runTask(() => { - observer = new IntersectionObserver(intersectionObserverCallback, { - root: rootNode, - }); - - observer.observe(node); - }); - - expect(intersectionObserverCallback).toHaveBeenCalledTimes(1); - const [entries, reportedObserver] = - intersectionObserverCallback.mock.lastCall; - - expect(reportedObserver).toBe(observer); - expect(entries.length).toBe(1); - expect(entries[0]).toBeInstanceOf(IntersectionObserverEntry); - expect(entries[0].intersectionRatio).toBe(0.5); - // This is the ratio of intersection area / (custom) root area - expect(entries[0].rnRootIntersectionRatio).toBe(0.5); - expect(entries[0].isIntersecting).toBe(true); - expect(entries[0].target).toBe(node); - expectRectEquals(entries[0].intersectionRect, { - x: 0, - y: 50, - width: 100, - height: 50, - }); - expectRectEquals(entries[0].boundingClientRect, { - x: 0, - y: 50, - width: 100, - height: 100, - }); - expectRectEquals(entries[0].rootBounds, { - x: 0, - y: 0, - width: 100, - height: 100, - }); - }); - }); - describe('unobserve(target)', () => { it('should throw if `target` is not a `ReactNativeElement`', () => { observer = new IntersectionObserver(() => {}); diff --git a/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js b/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js index bda5af95ea5..d49a08add18 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js +++ b/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js @@ -137,11 +137,9 @@ export function unregisterObserver( */ export function observe({ intersectionObserverId, - root, target, }: { intersectionObserverId: IntersectionObserverId, - root: ?ReactNativeElement, target: ReactNativeElement, }): boolean { if (NativeIntersectionObserver == null) { @@ -173,15 +171,6 @@ export function observe({ return false; } - const rootNativeNodeReference = - root != null ? getNativeNodeReference(root) : null; - if (root != null && rootNativeNodeReference == null) { - console.error( - 'IntersectionObserverManager: could not find shadow node for observation root', - ); - return false; - } - // Store the mapping between the instance handle and the target so we can // access it even after the instance handle has been unmounted. setTargetForInstanceHandle(instanceHandle, target); @@ -199,7 +188,6 @@ export function observe({ if (modernNativeIntersectionObserver == null) { NativeIntersectionObserver.observe({ intersectionObserverId, - rootShadowNode: rootNativeNodeReference, targetShadowNode: targetNativeNodeReference, thresholds: registeredObserver.observer.thresholds, rootThresholds: registeredObserver.observer.rnRootThresholds, @@ -207,7 +195,6 @@ export function observe({ } else { const token = modernNativeIntersectionObserver.observe({ intersectionObserverId, - rootShadowNode: rootNativeNodeReference, targetShadowNode: targetNativeNodeReference, thresholds: registeredObserver.observer.thresholds, rootThresholds: registeredObserver.observer.rnRootThresholds, diff --git a/packages/react-native/src/private/webapis/intersectionobserver/specs/NativeIntersectionObserver.js b/packages/react-native/src/private/webapis/intersectionobserver/specs/NativeIntersectionObserver.js index f9e83084bf8..88b1a0a1529 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/specs/NativeIntersectionObserver.js +++ b/packages/react-native/src/private/webapis/intersectionobserver/specs/NativeIntersectionObserver.js @@ -25,7 +25,6 @@ export type NativeIntersectionObserverEntry = { export type NativeIntersectionObserverObserveOptions = { intersectionObserverId: number, - rootShadowNode?: ?mixed, targetShadowNode: mixed, thresholds: $ReadOnlyArray, rootThresholds?: ?$ReadOnlyArray,