From 70fab8b24ede0c57ea675453bf388b060ac14d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?avery=20=E2=9C=BF?= Date: Thu, 4 Apr 2024 22:49:25 -0700 Subject: [PATCH] add topViewabilityInset --- src/FlashListProps.ts | 1 + src/__tests__/ViewabilityHelper.test.ts | 3 +++ src/viewability/ViewabilityHelper.ts | 22 ++++++++++++++-------- src/viewability/ViewabilityManager.ts | 3 +++ 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/FlashListProps.ts b/src/FlashListProps.ts index 9f03ee7dc..ce8b00701 100644 --- a/src/FlashListProps.ts +++ b/src/FlashListProps.ts @@ -344,4 +344,5 @@ export interface FlashListProps extends ScrollViewProps { * can consider the visible area of the bottom sheet in its calculations. */ bottomViewabilityInsetRef?: React.MutableRefObject; + topViewabilityInsetRef?: React.MutableRefObject; } diff --git a/src/__tests__/ViewabilityHelper.test.ts b/src/__tests__/ViewabilityHelper.test.ts index 52ac04a29..64c1da0ff 100644 --- a/src/__tests__/ViewabilityHelper.test.ts +++ b/src/__tests__/ViewabilityHelper.test.ts @@ -257,6 +257,7 @@ describe("ViewabilityHelper", () => { horizontal, scrollOffset, bottomViewabilityInset, + topViewabilityInset, listSize, getLayout, runAllTimers, @@ -265,6 +266,7 @@ describe("ViewabilityHelper", () => { horizontal?: boolean; scrollOffset?: number; bottomViewabilityInset?: number; + topViewabilityInset?: number; listSize?: Dimension; getLayout?: (index: number) => Layout | undefined; runAllTimers?: boolean; @@ -273,6 +275,7 @@ describe("ViewabilityHelper", () => { horizontal ?? false, scrollOffset ?? 0, bottomViewabilityInset ?? 0, + topViewabilityInset ?? 0, listSize ?? { height: 300, width: 300 }, getLayout ?? ((index) => { diff --git a/src/viewability/ViewabilityHelper.ts b/src/viewability/ViewabilityHelper.ts index d432b7900..6e704da1e 100644 --- a/src/viewability/ViewabilityHelper.ts +++ b/src/viewability/ViewabilityHelper.ts @@ -49,6 +49,7 @@ class ViewabilityHelper { horizontal: boolean, scrollOffset: number, bottomViewabilityInset: number, + topViewabilityInset: number, listSize: Dimension, getLayout: (index: number) => Layout | undefined, viewableIndices?: number[] @@ -78,6 +79,7 @@ class ViewabilityHelper { horizontal, scrollOffset, bottomViewabilityInset, + topViewabilityInset, listSize, this.viewabilityConfig?.viewAreaCoveragePercentThreshold, this.viewabilityConfig?.itemVisiblePercentThreshold, @@ -127,6 +129,7 @@ class ViewabilityHelper { horizontal: boolean, scrollOffset: number, bottomViewabilityInset: number, + topViewabilityInset: number, listSize: Dimension, viewAreaCoveragePercentThreshold: number | null | undefined, itemVisiblePercentThreshold: number | null | undefined, @@ -137,27 +140,30 @@ class ViewabilityHelper { return false; } const itemTop = (horizontal ? itemLayout.x : itemLayout.y) - scrollOffset; - const itemSize = horizontal ? itemLayout.width : itemLayout.height; - const listMainSize = horizontal - ? listSize.width - : listSize.height - bottomViewabilityInset; - const pixelsVisible = - Math.min(itemTop + itemSize, listMainSize) - Math.max(itemTop, 0); + const itemEnd = + itemTop + (horizontal ? itemLayout.width : itemLayout.height); + const listStart = topViewabilityInset; + const listEnd = + (horizontal ? listSize.width : listSize.height) - bottomViewabilityInset; + const visibleStart = Math.max(itemTop, listStart); + const visibleEnd = Math.min(itemEnd, listEnd); + const pixelsVisible = Math.max(0, visibleEnd - visibleStart); // Always consider item fully viewable if it is fully visible, regardless of the `viewAreaCoveragePercentThreshold` // Account for floating point imprecision. + const itemSize = horizontal ? itemLayout.width : itemLayout.height; if (Math.abs(pixelsVisible - itemSize) <= 0.001) { return true; } // Skip checking item if it's not visible at all - if (pixelsVisible === 0) { + if (pixelsVisible <= 0) { return false; } const viewAreaMode = viewAreaCoveragePercentThreshold !== null && viewAreaCoveragePercentThreshold !== undefined; const percent = viewAreaMode - ? pixelsVisible / listMainSize + ? pixelsVisible / (listEnd - listStart) : pixelsVisible / itemSize; const viewableAreaPercentThreshold = viewAreaMode ? viewAreaCoveragePercentThreshold * 0.01 diff --git a/src/viewability/ViewabilityManager.ts b/src/viewability/ViewabilityManager.ts index cc23bfe9b..b03a0e9b0 100644 --- a/src/viewability/ViewabilityManager.ts +++ b/src/viewability/ViewabilityManager.ts @@ -79,12 +79,15 @@ export default class ViewabilityManager { const bottomViewabilityInset = this.flashListRef.props.bottomViewabilityInsetRef?.current ?? 0; + const topViewabilityInset = + this.flashListRef.props.topViewabilityInsetRef?.current ?? 0; this.viewabilityHelpers.forEach((viewabilityHelper) => { viewabilityHelper.updateViewableItems( this.flashListRef.props.horizontal ?? false, scrollOffset, bottomViewabilityInset, + topViewabilityInset, listSize, (index: number) => this.flashListRef.recyclerlistview_unsafe?.getLayout(index),