@shopify/flash-list 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/LICENSE.md +7 -0
  3. package/README.md +65 -0
  4. package/RNFlashList.podspec +26 -0
  5. package/android/build.gradle +59 -0
  6. package/android/src/main/AndroidManifest.xml +3 -0
  7. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutShadow.kt +94 -0
  8. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutView.kt +79 -0
  9. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/AutoLayoutViewManager.kt +69 -0
  10. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainer.java +16 -0
  11. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainerImpl.kt +16 -0
  12. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/CellContainerManager.kt +27 -0
  13. package/android/src/main/kotlin/com/shopify/reactnative/flash_list/FlashListPackage.kt +19 -0
  14. package/android/src/test/java/com/shopify/reactnative/flash_list/AutoLayoutShadowTest.kt +146 -0
  15. package/android/src/test/java/com/shopify/reactnative/flash_list/models/Rect.kt +59 -0
  16. package/android/src/test/java/com/shopify/reactnative/flash_list/models/TestCollection.kt +6 -0
  17. package/android/src/test/java/com/shopify/reactnative/flash_list/models/TestDataModel.kt +8 -0
  18. package/android/src/test/resources/LayoutTestData.json +708 -0
  19. package/dist/AnimatedFlashList.d.ts +6 -0
  20. package/dist/AnimatedFlashList.d.ts.map +1 -0
  21. package/dist/AnimatedFlashList.js +8 -0
  22. package/dist/AnimatedFlashList.js.map +1 -0
  23. package/dist/FlashList.d.ts +121 -0
  24. package/dist/FlashList.d.ts.map +1 -0
  25. package/dist/FlashList.js +502 -0
  26. package/dist/FlashList.js.map +1 -0
  27. package/dist/FlashListProps.d.ts +251 -0
  28. package/dist/FlashListProps.d.ts.map +1 -0
  29. package/dist/FlashListProps.js +3 -0
  30. package/dist/FlashListProps.js.map +1 -0
  31. package/dist/GridLayoutProviderWithProps.d.ts +30 -0
  32. package/dist/GridLayoutProviderWithProps.d.ts.map +1 -0
  33. package/dist/GridLayoutProviderWithProps.js +80 -0
  34. package/dist/GridLayoutProviderWithProps.js.map +1 -0
  35. package/dist/PureComponentWrapper.d.ts +22 -0
  36. package/dist/PureComponentWrapper.d.ts.map +1 -0
  37. package/dist/PureComponentWrapper.js +37 -0
  38. package/dist/PureComponentWrapper.js.map +1 -0
  39. package/dist/__tests__/AverageWindow.test.d.ts +2 -0
  40. package/dist/__tests__/AverageWindow.test.d.ts.map +1 -0
  41. package/dist/__tests__/AverageWindow.test.js +69 -0
  42. package/dist/__tests__/AverageWindow.test.js.map +1 -0
  43. package/dist/__tests__/FlashList.test.d.ts +2 -0
  44. package/dist/__tests__/FlashList.test.d.ts.map +1 -0
  45. package/dist/__tests__/FlashList.test.js +656 -0
  46. package/dist/__tests__/FlashList.test.js.map +1 -0
  47. package/dist/__tests__/GridLayoutProviderWithProps.test.d.ts +2 -0
  48. package/dist/__tests__/GridLayoutProviderWithProps.test.d.ts.map +1 -0
  49. package/dist/__tests__/GridLayoutProviderWithProps.test.js +133 -0
  50. package/dist/__tests__/GridLayoutProviderWithProps.test.js.map +1 -0
  51. package/dist/__tests__/PlatformHelper.web.test.d.ts +2 -0
  52. package/dist/__tests__/PlatformHelper.web.test.d.ts.map +1 -0
  53. package/dist/__tests__/PlatformHelper.web.test.js +25 -0
  54. package/dist/__tests__/PlatformHelper.web.test.js.map +1 -0
  55. package/dist/__tests__/ViewabilityHelper.test.d.ts +2 -0
  56. package/dist/__tests__/ViewabilityHelper.test.d.ts.map +1 -0
  57. package/dist/__tests__/ViewabilityHelper.test.js +187 -0
  58. package/dist/__tests__/ViewabilityHelper.test.js.map +1 -0
  59. package/dist/__tests__/helpers/mountFlashList.d.ts +20 -0
  60. package/dist/__tests__/helpers/mountFlashList.d.ts.map +1 -0
  61. package/dist/__tests__/helpers/mountFlashList.js +44 -0
  62. package/dist/__tests__/helpers/mountFlashList.js.map +1 -0
  63. package/dist/__tests__/useBlankAreaTracker.test.d.ts +2 -0
  64. package/dist/__tests__/useBlankAreaTracker.test.d.ts.map +1 -0
  65. package/dist/__tests__/useBlankAreaTracker.test.js +179 -0
  66. package/dist/__tests__/useBlankAreaTracker.test.js.map +1 -0
  67. package/dist/benchmark/AutoScrollHelper.d.ts +18 -0
  68. package/dist/benchmark/AutoScrollHelper.d.ts.map +1 -0
  69. package/dist/benchmark/AutoScrollHelper.js +68 -0
  70. package/dist/benchmark/AutoScrollHelper.js.map +1 -0
  71. package/dist/benchmark/JSFPSMonitor.d.ts +23 -0
  72. package/dist/benchmark/JSFPSMonitor.d.ts.map +1 -0
  73. package/dist/benchmark/JSFPSMonitor.js +65 -0
  74. package/dist/benchmark/JSFPSMonitor.js.map +1 -0
  75. package/dist/benchmark/roundToDecimalPlaces.d.ts +2 -0
  76. package/dist/benchmark/roundToDecimalPlaces.d.ts.map +1 -0
  77. package/dist/benchmark/roundToDecimalPlaces.js +9 -0
  78. package/dist/benchmark/roundToDecimalPlaces.js.map +1 -0
  79. package/dist/benchmark/useBenchmark.d.ts +35 -0
  80. package/dist/benchmark/useBenchmark.d.ts.map +1 -0
  81. package/dist/benchmark/useBenchmark.js +167 -0
  82. package/dist/benchmark/useBenchmark.js.map +1 -0
  83. package/dist/benchmark/useBlankAreaTracker.d.ts +34 -0
  84. package/dist/benchmark/useBlankAreaTracker.d.ts.map +1 -0
  85. package/dist/benchmark/useBlankAreaTracker.js +67 -0
  86. package/dist/benchmark/useBlankAreaTracker.js.map +1 -0
  87. package/dist/benchmark/useDataMultiplier.d.ts +9 -0
  88. package/dist/benchmark/useDataMultiplier.d.ts.map +1 -0
  89. package/dist/benchmark/useDataMultiplier.js +25 -0
  90. package/dist/benchmark/useDataMultiplier.js.map +1 -0
  91. package/dist/benchmark/useFlatListBenchmark.d.ts +13 -0
  92. package/dist/benchmark/useFlatListBenchmark.d.ts.map +1 -0
  93. package/dist/benchmark/useFlatListBenchmark.js +100 -0
  94. package/dist/benchmark/useFlatListBenchmark.js.map +1 -0
  95. package/dist/errors/CustomError.d.ts +8 -0
  96. package/dist/errors/CustomError.d.ts.map +1 -0
  97. package/dist/errors/CustomError.js +14 -0
  98. package/dist/errors/CustomError.js.map +1 -0
  99. package/dist/errors/ExceptionList.d.ts +20 -0
  100. package/dist/errors/ExceptionList.d.ts.map +1 -0
  101. package/dist/errors/ExceptionList.js +22 -0
  102. package/dist/errors/ExceptionList.js.map +1 -0
  103. package/dist/errors/Warnings.d.ts +10 -0
  104. package/dist/errors/Warnings.d.ts.map +1 -0
  105. package/dist/errors/Warnings.js +15 -0
  106. package/dist/errors/Warnings.js.map +1 -0
  107. package/dist/index.d.ts +13 -0
  108. package/dist/index.d.ts.map +1 -0
  109. package/dist/index.js +28 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/native/auto-layout/AutoLayoutView.d.ts +21 -0
  112. package/dist/native/auto-layout/AutoLayoutView.d.ts.map +1 -0
  113. package/dist/native/auto-layout/AutoLayoutView.js +48 -0
  114. package/dist/native/auto-layout/AutoLayoutView.js.map +1 -0
  115. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts +4 -0
  116. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.d.ts.map +1 -0
  117. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.js +6 -0
  118. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.js.map +1 -0
  119. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.d.ts +5 -0
  120. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.d.ts.map +1 -0
  121. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.js +6 -0
  122. package/dist/native/auto-layout/AutoLayoutViewNativeComponent.web.js.map +1 -0
  123. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts +14 -0
  124. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.d.ts.map +1 -0
  125. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.js +3 -0
  126. package/dist/native/auto-layout/AutoLayoutViewNativeComponentProps.js.map +1 -0
  127. package/dist/native/cell-container/CellContainer.d.ts +6 -0
  128. package/dist/native/cell-container/CellContainer.d.ts.map +1 -0
  129. package/dist/native/cell-container/CellContainer.js +9 -0
  130. package/dist/native/cell-container/CellContainer.js.map +1 -0
  131. package/dist/native/cell-container/CellContainer.web.d.ts +7 -0
  132. package/dist/native/cell-container/CellContainer.web.d.ts.map +1 -0
  133. package/dist/native/cell-container/CellContainer.web.js +13 -0
  134. package/dist/native/cell-container/CellContainer.web.js.map +1 -0
  135. package/dist/tsconfig.tsbuildinfo +1 -0
  136. package/dist/utils/AverageWindow.d.ts +21 -0
  137. package/dist/utils/AverageWindow.d.ts.map +1 -0
  138. package/dist/utils/AverageWindow.js +49 -0
  139. package/dist/utils/AverageWindow.js.map +1 -0
  140. package/dist/utils/PlatformHelper.d.ts +14 -0
  141. package/dist/utils/PlatformHelper.d.ts.map +1 -0
  142. package/dist/utils/PlatformHelper.js +16 -0
  143. package/dist/utils/PlatformHelper.js.map +1 -0
  144. package/dist/utils/PlatformHelper.web.d.ts +14 -0
  145. package/dist/utils/PlatformHelper.web.d.ts.map +1 -0
  146. package/dist/utils/PlatformHelper.web.js +18 -0
  147. package/dist/utils/PlatformHelper.web.js.map +1 -0
  148. package/dist/viewability/ViewToken.d.ts +8 -0
  149. package/dist/viewability/ViewToken.d.ts.map +1 -0
  150. package/dist/viewability/ViewToken.js +3 -0
  151. package/dist/viewability/ViewToken.js.map +1 -0
  152. package/dist/viewability/ViewabilityHelper.d.ts +25 -0
  153. package/dist/viewability/ViewabilityHelper.d.ts.map +1 -0
  154. package/dist/viewability/ViewabilityHelper.js +104 -0
  155. package/dist/viewability/ViewabilityHelper.js.map +1 -0
  156. package/dist/viewability/ViewabilityManager.d.ts +24 -0
  157. package/dist/viewability/ViewabilityManager.d.ts.map +1 -0
  158. package/dist/viewability/ViewabilityManager.js +94 -0
  159. package/dist/viewability/ViewabilityManager.js.map +1 -0
  160. package/ios/RNFlashList.xcodeproj/project.pbxproj +3 -0
  161. package/ios/RNFlashList.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
  162. package/ios/Sources/AutoLayoutView.swift +218 -0
  163. package/ios/Sources/AutoLayoutViewManager.m +14 -0
  164. package/ios/Sources/AutoLayoutViewManager.swift +12 -0
  165. package/ios/Sources/CellContainer.swift +9 -0
  166. package/ios/Sources/CellContainerManager.m +8 -0
  167. package/ios/Sources/CellContainerManager.swift +12 -0
  168. package/ios/Sources/FlatListPro-Bridging-Header.h +8 -0
  169. package/ios/Tests/AutoLayoutViewTests.swift +113 -0
  170. package/jestSetup.js +15 -0
  171. package/package.json +75 -0
  172. package/src/AnimatedFlashList.ts +11 -0
  173. package/src/FlashList.tsx +801 -0
  174. package/src/FlashListProps.ts +312 -0
  175. package/src/GridLayoutProviderWithProps.ts +137 -0
  176. package/src/PureComponentWrapper.tsx +42 -0
  177. package/src/__tests__/AverageWindow.test.ts +80 -0
  178. package/src/__tests__/FlashList.test.tsx +738 -0
  179. package/src/__tests__/GridLayoutProviderWithProps.test.ts +150 -0
  180. package/src/__tests__/PlatformHelper.web.test.ts +29 -0
  181. package/src/__tests__/ViewabilityHelper.test.ts +283 -0
  182. package/src/__tests__/helpers/mountFlashList.tsx +62 -0
  183. package/src/__tests__/useBlankAreaTracker.test.tsx +206 -0
  184. package/src/benchmark/AutoScrollHelper.ts +70 -0
  185. package/src/benchmark/JSFPSMonitor.ts +74 -0
  186. package/src/benchmark/roundToDecimalPlaces.ts +4 -0
  187. package/src/benchmark/useBenchmark.ts +240 -0
  188. package/src/benchmark/useBlankAreaTracker.ts +117 -0
  189. package/src/benchmark/useDataMultiplier.ts +19 -0
  190. package/src/benchmark/useFlatListBenchmark.ts +107 -0
  191. package/src/errors/CustomError.ts +10 -0
  192. package/src/errors/ExceptionList.ts +23 -0
  193. package/src/errors/Warnings.ts +18 -0
  194. package/src/index.ts +32 -0
  195. package/src/native/auto-layout/AutoLayoutView.tsx +72 -0
  196. package/src/native/auto-layout/AutoLayoutViewNativeComponent.ts +7 -0
  197. package/src/native/auto-layout/AutoLayoutViewNativeComponent.web.ts +8 -0
  198. package/src/native/auto-layout/AutoLayoutViewNativeComponentProps.ts +14 -0
  199. package/src/native/cell-container/CellContainer.ts +7 -0
  200. package/src/native/cell-container/CellContainer.web.tsx +9 -0
  201. package/src/utils/AverageWindow.ts +49 -0
  202. package/src/utils/PlatformHelper.ts +16 -0
  203. package/src/utils/PlatformHelper.web.ts +20 -0
  204. package/src/viewability/ViewToken.ts +7 -0
  205. package/src/viewability/ViewabilityHelper.ts +162 -0
  206. package/src/viewability/ViewabilityManager.ts +133 -0
@@ -0,0 +1,162 @@
1
+ import { ViewabilityConfig } from "react-native";
2
+ import { Dimension, Layout } from "recyclerlistview";
3
+
4
+ import CustomError from "../errors/CustomError";
5
+ import ExceptionList from "../errors/ExceptionList";
6
+
7
+ /**
8
+ * Helper class for computing viewable items based on the passed `viewabilityConfig`.
9
+ * Note methods in this class will be invoked on every scroll and should be optimized for performance.
10
+ */
11
+ class ViewabilityHelper {
12
+ /**
13
+ * Viewable indices regardless of the viewability config
14
+ */
15
+ possiblyViewableIndices: number[] = [];
16
+
17
+ hasInteracted: boolean = false;
18
+
19
+ private viewableIndices: number[] = [];
20
+ private lastReportedViewableIndices: number[] = [];
21
+
22
+ private viewabilityConfig: ViewabilityConfig | null | undefined;
23
+ private viewableIndicesChanged: (
24
+ indices: number[],
25
+ newlyVisibleIndicies: number[],
26
+ newlyNonvisibleIndices: number[]
27
+ ) => void;
28
+
29
+ private timers: Set<NodeJS.Timeout> = new Set();
30
+
31
+ constructor(
32
+ viewabilityConfig: ViewabilityConfig | null | undefined,
33
+ viewableIndicesChanged: (
34
+ indices: number[],
35
+ newlyVisibleIndicies: number[],
36
+ newlyNonvisibleIndices: number[]
37
+ ) => void
38
+ ) {
39
+ this.viewabilityConfig = viewabilityConfig;
40
+ this.viewableIndicesChanged = viewableIndicesChanged;
41
+ }
42
+
43
+ public dispose() {
44
+ // Clean up on dismount
45
+ this.timers.forEach(clearTimeout);
46
+ }
47
+
48
+ public updateViewableItems(
49
+ horizontal: boolean,
50
+ scrollOffset: number,
51
+ listSize: Dimension,
52
+ getLayout: (index: number) => Layout | undefined,
53
+ viewableIndices?: number[]
54
+ ) {
55
+ if (viewableIndices !== undefined) {
56
+ this.possiblyViewableIndices = viewableIndices;
57
+ }
58
+ if (
59
+ this.viewabilityConfig?.itemVisiblePercentThreshold !== null &&
60
+ this.viewabilityConfig?.itemVisiblePercentThreshold !== undefined &&
61
+ this.viewabilityConfig?.viewAreaCoveragePercentThreshold !== null &&
62
+ this.viewabilityConfig?.viewAreaCoveragePercentThreshold !== undefined
63
+ ) {
64
+ throw new CustomError(
65
+ ExceptionList.multipleViewabilityThresholdTypesNotSupported
66
+ );
67
+ }
68
+ if (
69
+ (this.viewabilityConfig?.waitForInteraction ?? false) &&
70
+ !this.hasInteracted
71
+ ) {
72
+ return;
73
+ }
74
+ const newViewableIndices = this.possiblyViewableIndices.filter((index) =>
75
+ this.isItemViewable(
76
+ index,
77
+ horizontal,
78
+ scrollOffset,
79
+ listSize,
80
+ this.viewabilityConfig?.viewAreaCoveragePercentThreshold,
81
+ this.viewabilityConfig?.itemVisiblePercentThreshold,
82
+ getLayout
83
+ )
84
+ );
85
+ this.viewableIndices = newViewableIndices;
86
+ const minimumViewTime = this.viewabilityConfig?.minimumViewTime ?? 250;
87
+ // Setting default to 250. Default of 0 can impact performance when user scrolls fast.
88
+ if (minimumViewTime > 0) {
89
+ const timeoutId = setTimeout(() => {
90
+ this.timers.delete(timeoutId);
91
+ this.checkViewableIndicesChanges(newViewableIndices);
92
+ this.timers.add(timeoutId);
93
+ }, minimumViewTime);
94
+ } else {
95
+ this.checkViewableIndicesChanges(newViewableIndices);
96
+ }
97
+ }
98
+
99
+ public checkViewableIndicesChanges(newViewableIndices: number[]) {
100
+ // Check if all viewable indices are still available (applicable if minimumViewTime > 0)
101
+ const currentlyNewViewableIndices = newViewableIndices.filter((index) =>
102
+ this.viewableIndices.includes(index)
103
+ );
104
+ const newlyVisibleItems = currentlyNewViewableIndices.filter(
105
+ (index) => !this.lastReportedViewableIndices.includes(index)
106
+ );
107
+ const newlyNonvisibleItems = this.lastReportedViewableIndices.filter(
108
+ (index) => !currentlyNewViewableIndices.includes(index)
109
+ );
110
+
111
+ if (newlyVisibleItems.length > 0 || newlyNonvisibleItems.length > 0) {
112
+ this.lastReportedViewableIndices = currentlyNewViewableIndices;
113
+ this.viewableIndicesChanged(
114
+ currentlyNewViewableIndices,
115
+ newlyVisibleItems,
116
+ newlyNonvisibleItems
117
+ );
118
+ }
119
+ }
120
+
121
+ private isItemViewable(
122
+ index: number,
123
+ horizontal: boolean,
124
+ scrollOffset: number,
125
+ listSize: Dimension,
126
+ viewAreaCoveragePercentThreshold: number | null | undefined,
127
+ itemVisiblePercentThreshold: number | null | undefined,
128
+ getLayout: (index: number) => Layout | undefined
129
+ ) {
130
+ const itemLayout = getLayout(index);
131
+ if (itemLayout === undefined) {
132
+ return false;
133
+ }
134
+ const itemTop = (horizontal ? itemLayout.x : itemLayout.y) - scrollOffset;
135
+ const itemSize = horizontal ? itemLayout.width : itemLayout.height;
136
+ const listMainSize = horizontal ? listSize.width : listSize.height;
137
+ const pixelsVisible =
138
+ Math.min(itemTop + itemSize, listMainSize) - Math.max(itemTop, 0);
139
+
140
+ // Always consider item fully viewable if it is fully visible, regardless of the `viewAreaCoveragePercentThreshold`
141
+ if (pixelsVisible === itemSize) {
142
+ return true;
143
+ }
144
+ // Skip checking item if it's not visible at all
145
+ if (pixelsVisible === 0) {
146
+ return false;
147
+ }
148
+ const viewAreaMode =
149
+ viewAreaCoveragePercentThreshold !== null &&
150
+ viewAreaCoveragePercentThreshold !== undefined;
151
+ const percent = viewAreaMode
152
+ ? pixelsVisible / listMainSize
153
+ : pixelsVisible / itemSize;
154
+ const viewableAreaPercentThreshold = viewAreaMode
155
+ ? viewAreaCoveragePercentThreshold * 0.01
156
+ : (itemVisiblePercentThreshold ?? 0) * 0.01;
157
+
158
+ return percent >= viewableAreaPercentThreshold;
159
+ }
160
+ }
161
+
162
+ export default ViewabilityHelper;
@@ -0,0 +1,133 @@
1
+ import { ViewabilityConfig } from "react-native";
2
+
3
+ import FlashList from "../FlashList";
4
+
5
+ import ViewabilityHelper from "./ViewabilityHelper";
6
+ import ViewToken from "./ViewToken";
7
+
8
+ /**
9
+ * Manager for viewability tracking. It holds multiple viewability callback pairs and keeps them updated.
10
+ */
11
+ export default class ViewabilityManager<T> {
12
+ private flashListRef: FlashList<T>;
13
+ private viewabilityHelpers: ViewabilityHelper[] = [];
14
+ private hasInteracted = false;
15
+
16
+ constructor(flashListRef: FlashList<T>) {
17
+ this.flashListRef = flashListRef;
18
+ if (
19
+ flashListRef.props.onViewableItemsChanged !== null &&
20
+ flashListRef.props.onViewableItemsChanged !== undefined
21
+ ) {
22
+ this.viewabilityHelpers.push(
23
+ this.createViewabilityHelper(
24
+ flashListRef.props.viewabilityConfig,
25
+ flashListRef.props.onViewableItemsChanged
26
+ )
27
+ );
28
+ }
29
+ (flashListRef.props.viewabilityConfigCallbackPairs ?? []).forEach(
30
+ (pair) => {
31
+ this.viewabilityHelpers.push(
32
+ this.createViewabilityHelper(
33
+ pair.viewabilityConfig,
34
+ pair.onViewableItemsChanged
35
+ )
36
+ );
37
+ }
38
+ );
39
+ }
40
+
41
+ /**
42
+ * @returns true if the viewability manager has any viewability callback pairs registered.
43
+ */
44
+ public get shouldListenToVisibleIndices() {
45
+ return this.viewabilityHelpers.length > 0;
46
+ }
47
+
48
+ public dispose = () => {
49
+ this.viewabilityHelpers.forEach((viewabilityHelper) =>
50
+ viewabilityHelper.dispose()
51
+ );
52
+ };
53
+
54
+ public onVisibleIndicesChanged = (all: number[]) => {
55
+ this.updateViewableItems(all);
56
+ };
57
+
58
+ public recordInteraction = () => {
59
+ if (this.hasInteracted) {
60
+ return;
61
+ }
62
+ this.hasInteracted = true;
63
+ this.viewabilityHelpers.forEach((viewabilityHelper) => {
64
+ viewabilityHelper.hasInteracted = true;
65
+ });
66
+ this.updateViewableItems();
67
+ };
68
+
69
+ public updateViewableItems = (newViewableIndices?: number[]) => {
70
+ const listSize =
71
+ this.flashListRef.recyclerlistview_unsafe?.getRenderedSize();
72
+ if (listSize === undefined || !this.shouldListenToVisibleIndices) {
73
+ return;
74
+ }
75
+ const scrollOffset =
76
+ (this.flashListRef.recyclerlistview_unsafe?.getCurrentScrollOffset() ??
77
+ 0) - this.flashListRef.firstItemOffset;
78
+ this.viewabilityHelpers.forEach((viewabilityHelper) => {
79
+ viewabilityHelper.updateViewableItems(
80
+ this.flashListRef.props.horizontal ?? false,
81
+ scrollOffset,
82
+ listSize,
83
+ (index: number) =>
84
+ this.flashListRef.recyclerlistview_unsafe?.getLayout(index),
85
+ newViewableIndices
86
+ );
87
+ });
88
+ };
89
+
90
+ /**
91
+ * Creates a new `ViewabilityHelper` instance with `onViewableItemsChanged` callback and `ViewabilityConfig`
92
+ * @returns `ViewabilityHelper` instance
93
+ */
94
+ private createViewabilityHelper = (
95
+ viewabilityConfig: ViewabilityConfig | null | undefined,
96
+ onViewableItemsChanged:
97
+ | ((info: { viewableItems: ViewToken[]; changed: ViewToken[] }) => void)
98
+ | null
99
+ | undefined
100
+ ) => {
101
+ const mapViewToken: (index: number, isViewable: boolean) => ViewToken = (
102
+ index: number,
103
+ isViewable: boolean
104
+ ) => {
105
+ const item = this.flashListRef.props.data?.[index];
106
+ const key =
107
+ item === undefined || this.flashListRef.props.keyExtractor === undefined
108
+ ? index.toString()
109
+ : this.flashListRef.props.keyExtractor(item, index);
110
+ return {
111
+ index,
112
+ isViewable,
113
+ item,
114
+ key,
115
+ timestamp: Date.now(),
116
+ };
117
+ };
118
+ return new ViewabilityHelper(
119
+ viewabilityConfig,
120
+ (indices, newlyVisibleIndices, newlyNonvisibleIndices) => {
121
+ onViewableItemsChanged?.({
122
+ viewableItems: indices.map((index) => mapViewToken(index, true)),
123
+ changed: [
124
+ ...newlyVisibleIndices.map((index) => mapViewToken(index, true)),
125
+ ...newlyNonvisibleIndices.map((index) =>
126
+ mapViewToken(index, false)
127
+ ),
128
+ ],
129
+ });
130
+ }
131
+ );
132
+ };
133
+ }