@tamagui/react-native-web-lite 1.116.2 → 1.116.3

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 (202) hide show
  1. package/dist/cjs/Batchinator/index.cjs +68 -0
  2. package/dist/cjs/Batchinator/index.js +57 -0
  3. package/dist/cjs/Batchinator/index.js.map +6 -0
  4. package/dist/cjs/Batchinator/index.native.js +92 -0
  5. package/dist/cjs/Batchinator/index.native.js.map +6 -0
  6. package/dist/cjs/FlatList.cjs +196 -0
  7. package/dist/cjs/FlatList.js +193 -0
  8. package/dist/cjs/FlatList.js.map +6 -0
  9. package/dist/cjs/FlatList.native.js +256 -0
  10. package/dist/cjs/FlatList.native.js.map +6 -0
  11. package/dist/cjs/InteractionManager/TaskQueue.cjs +88 -0
  12. package/dist/cjs/InteractionManager/TaskQueue.js +76 -0
  13. package/dist/cjs/InteractionManager/TaskQueue.js.map +6 -0
  14. package/dist/cjs/InteractionManager/TaskQueue.native.js +134 -0
  15. package/dist/cjs/InteractionManager/TaskQueue.native.js.map +6 -0
  16. package/dist/cjs/InteractionManager/index.cjs +114 -0
  17. package/dist/cjs/InteractionManager/index.js +95 -0
  18. package/dist/cjs/InteractionManager/index.js.map +6 -0
  19. package/dist/cjs/InteractionManager/index.native.js +99 -0
  20. package/dist/cjs/InteractionManager/index.native.js.map +6 -0
  21. package/dist/cjs/index.cjs +7 -7
  22. package/dist/cjs/index.js +6 -6
  23. package/dist/cjs/index.js.map +1 -1
  24. package/dist/cjs/index.native.js +6 -6
  25. package/dist/cjs/index.native.js.map +1 -1
  26. package/dist/cjs/vendor/react-native/FillRateHelper/index.cjs +138 -0
  27. package/dist/cjs/vendor/react-native/FillRateHelper/index.js +124 -0
  28. package/dist/cjs/vendor/react-native/FillRateHelper/index.js.map +6 -0
  29. package/dist/cjs/vendor/react-native/FillRateHelper/index.native.js +158 -0
  30. package/dist/cjs/vendor/react-native/FillRateHelper/index.native.js.map +6 -0
  31. package/dist/cjs/vendor/react-native/Utilities/clamp.cjs +29 -0
  32. package/dist/cjs/vendor/react-native/Utilities/clamp.js +24 -0
  33. package/dist/cjs/vendor/react-native/Utilities/clamp.js.map +6 -0
  34. package/dist/cjs/vendor/react-native/Utilities/clamp.native.js +25 -0
  35. package/dist/cjs/vendor/react-native/Utilities/clamp.native.js.map +6 -0
  36. package/dist/cjs/vendor/react-native/ViewabilityHelper.cjs +133 -0
  37. package/dist/cjs/vendor/react-native/ViewabilityHelper.js +144 -0
  38. package/dist/cjs/vendor/react-native/ViewabilityHelper.js.map +6 -0
  39. package/dist/cjs/vendor/react-native/ViewabilityHelper.native.js +199 -0
  40. package/dist/cjs/vendor/react-native/ViewabilityHelper.native.js.map +6 -0
  41. package/dist/cjs/vendor/react-native/VirtualizeUtils/index.cjs +106 -0
  42. package/dist/cjs/vendor/react-native/VirtualizeUtils/index.js +86 -0
  43. package/dist/cjs/vendor/react-native/VirtualizeUtils/index.js.map +6 -0
  44. package/dist/cjs/vendor/react-native/VirtualizeUtils/index.native.js +95 -0
  45. package/dist/cjs/vendor/react-native/VirtualizeUtils/index.native.js.map +6 -0
  46. package/dist/cjs/vendor/react-native/VirtualizedList/CellRenderMask.cjs +81 -0
  47. package/dist/cjs/vendor/react-native/VirtualizedList/CellRenderMask.js +84 -0
  48. package/dist/cjs/vendor/react-native/VirtualizedList/CellRenderMask.js.map +6 -0
  49. package/dist/cjs/vendor/react-native/VirtualizedList/CellRenderMask.native.js +125 -0
  50. package/dist/cjs/vendor/react-native/VirtualizedList/CellRenderMask.native.js.map +6 -0
  51. package/dist/cjs/vendor/react-native/VirtualizedList/ChildListCollection.cjs +56 -0
  52. package/dist/cjs/vendor/react-native/VirtualizedList/ChildListCollection.js +59 -0
  53. package/dist/cjs/vendor/react-native/VirtualizedList/ChildListCollection.js.map +6 -0
  54. package/dist/cjs/vendor/react-native/VirtualizedList/ChildListCollection.native.js +153 -0
  55. package/dist/cjs/vendor/react-native/VirtualizedList/ChildListCollection.native.js.map +6 -0
  56. package/dist/cjs/vendor/react-native/VirtualizedList/StateSafePureComponent.cjs +80 -0
  57. package/dist/cjs/vendor/react-native/VirtualizedList/StateSafePureComponent.js +74 -0
  58. package/dist/cjs/vendor/react-native/VirtualizedList/StateSafePureComponent.js.map +6 -0
  59. package/dist/cjs/vendor/react-native/VirtualizedList/StateSafePureComponent.native.js +163 -0
  60. package/dist/cjs/vendor/react-native/VirtualizedList/StateSafePureComponent.native.js.map +6 -0
  61. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.cjs +164 -0
  62. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.js +149 -0
  63. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.js.map +6 -0
  64. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.native.js +245 -0
  65. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.native.js.map +6 -0
  66. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListContext.cjs +84 -0
  67. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListContext.js +72 -0
  68. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListContext.js.map +6 -0
  69. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListContext.native.js +85 -0
  70. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListContext.native.js.map +6 -0
  71. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListProps.cjs +16 -0
  72. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListProps.js +14 -0
  73. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListProps.js.map +6 -0
  74. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListProps.native.js +15 -0
  75. package/dist/cjs/vendor/react-native/VirtualizedList/VirtualizedListProps.native.js.map +6 -0
  76. package/dist/cjs/vendor/react-native/VirtualizedList/index.cjs +1003 -0
  77. package/dist/cjs/vendor/react-native/VirtualizedList/index.js +1021 -0
  78. package/dist/cjs/vendor/react-native/VirtualizedList/index.js.map +6 -0
  79. package/dist/cjs/vendor/react-native/VirtualizedList/index.native.js +1075 -0
  80. package/dist/cjs/vendor/react-native/VirtualizedList/index.native.js.map +6 -0
  81. package/dist/cjs/vendor/react-native/deepDiffer/index.cjs +41 -0
  82. package/dist/cjs/vendor/react-native/deepDiffer/index.js +47 -0
  83. package/dist/cjs/vendor/react-native/deepDiffer/index.js.map +6 -0
  84. package/dist/cjs/vendor/react-native/deepDiffer/index.native.js +48 -0
  85. package/dist/cjs/vendor/react-native/deepDiffer/index.native.js.map +6 -0
  86. package/dist/esm/Batchinator/index.js +33 -0
  87. package/dist/esm/Batchinator/index.js.map +6 -0
  88. package/dist/esm/Batchinator/index.mjs +34 -0
  89. package/dist/esm/Batchinator/index.mjs.map +1 -0
  90. package/dist/esm/Batchinator/index.native.js +67 -0
  91. package/dist/esm/Batchinator/index.native.js.map +6 -0
  92. package/dist/esm/FlatList.js +176 -0
  93. package/dist/esm/FlatList.js.map +6 -0
  94. package/dist/esm/FlatList.mjs +162 -0
  95. package/dist/esm/FlatList.mjs.map +1 -0
  96. package/dist/esm/FlatList.native.js +238 -0
  97. package/dist/esm/FlatList.native.js.map +6 -0
  98. package/dist/esm/InteractionManager/TaskQueue.js +60 -0
  99. package/dist/esm/InteractionManager/TaskQueue.js.map +6 -0
  100. package/dist/esm/InteractionManager/TaskQueue.mjs +65 -0
  101. package/dist/esm/InteractionManager/TaskQueue.mjs.map +1 -0
  102. package/dist/esm/InteractionManager/TaskQueue.native.js +117 -0
  103. package/dist/esm/InteractionManager/TaskQueue.native.js.map +6 -0
  104. package/dist/esm/InteractionManager/index.js +73 -0
  105. package/dist/esm/InteractionManager/index.js.map +6 -0
  106. package/dist/esm/InteractionManager/index.mjs +80 -0
  107. package/dist/esm/InteractionManager/index.mjs.map +1 -0
  108. package/dist/esm/InteractionManager/index.native.js +77 -0
  109. package/dist/esm/InteractionManager/index.native.js.map +6 -0
  110. package/dist/esm/index.js +2 -2
  111. package/dist/esm/index.mjs +2 -2
  112. package/dist/esm/index.native.js +2 -2
  113. package/dist/esm/vendor/react-native/FillRateHelper/index.js +108 -0
  114. package/dist/esm/vendor/react-native/FillRateHelper/index.js.map +6 -0
  115. package/dist/esm/vendor/react-native/FillRateHelper/index.mjs +115 -0
  116. package/dist/esm/vendor/react-native/FillRateHelper/index.mjs.map +1 -0
  117. package/dist/esm/vendor/react-native/FillRateHelper/index.native.js +141 -0
  118. package/dist/esm/vendor/react-native/FillRateHelper/index.native.js.map +6 -0
  119. package/dist/esm/vendor/react-native/Utilities/clamp.js +8 -0
  120. package/dist/esm/vendor/react-native/Utilities/clamp.js.map +6 -0
  121. package/dist/esm/vendor/react-native/Utilities/clamp.mjs +6 -0
  122. package/dist/esm/vendor/react-native/Utilities/clamp.mjs.map +1 -0
  123. package/dist/esm/vendor/react-native/Utilities/clamp.native.js +8 -0
  124. package/dist/esm/vendor/react-native/Utilities/clamp.native.js.map +6 -0
  125. package/dist/esm/vendor/react-native/ViewabilityHelper.js +128 -0
  126. package/dist/esm/vendor/react-native/ViewabilityHelper.js.map +6 -0
  127. package/dist/esm/vendor/react-native/ViewabilityHelper.mjs +110 -0
  128. package/dist/esm/vendor/react-native/ViewabilityHelper.mjs.map +1 -0
  129. package/dist/esm/vendor/react-native/ViewabilityHelper.native.js +182 -0
  130. package/dist/esm/vendor/react-native/ViewabilityHelper.native.js.map +6 -0
  131. package/dist/esm/vendor/react-native/VirtualizeUtils/index.js +70 -0
  132. package/dist/esm/vendor/react-native/VirtualizeUtils/index.js.map +6 -0
  133. package/dist/esm/vendor/react-native/VirtualizeUtils/index.mjs +80 -0
  134. package/dist/esm/vendor/react-native/VirtualizeUtils/index.mjs.map +1 -0
  135. package/dist/esm/vendor/react-native/VirtualizeUtils/index.native.js +71 -0
  136. package/dist/esm/vendor/react-native/VirtualizeUtils/index.native.js.map +6 -0
  137. package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.js +68 -0
  138. package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.js.map +6 -0
  139. package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.mjs +58 -0
  140. package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.mjs.map +1 -0
  141. package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.native.js +104 -0
  142. package/dist/esm/vendor/react-native/VirtualizedList/CellRenderMask.native.js.map +6 -0
  143. package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.js +43 -0
  144. package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.js.map +6 -0
  145. package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.mjs +33 -0
  146. package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.mjs.map +1 -0
  147. package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.native.js +136 -0
  148. package/dist/esm/vendor/react-native/VirtualizedList/ChildListCollection.native.js.map +6 -0
  149. package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.js +51 -0
  150. package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.js.map +6 -0
  151. package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.mjs +46 -0
  152. package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.mjs.map +1 -0
  153. package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.native.js +139 -0
  154. package/dist/esm/vendor/react-native/VirtualizedList/StateSafePureComponent.native.js.map +6 -0
  155. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.js +132 -0
  156. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.js.map +6 -0
  157. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.mjs +130 -0
  158. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.mjs.map +1 -0
  159. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.native.js +224 -0
  160. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.native.js.map +6 -0
  161. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.js +50 -0
  162. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.js.map +6 -0
  163. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.mjs +47 -0
  164. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.mjs.map +1 -0
  165. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.native.js +56 -0
  166. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListContext.native.js.map +6 -0
  167. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.js +1 -0
  168. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.js.map +6 -0
  169. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.mjs +2 -0
  170. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.mjs.map +1 -0
  171. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.native.js +1 -0
  172. package/dist/esm/vendor/react-native/VirtualizedList/VirtualizedListProps.native.js.map +6 -0
  173. package/dist/esm/vendor/react-native/VirtualizedList/index.js +1016 -0
  174. package/dist/esm/vendor/react-native/VirtualizedList/index.js.map +6 -0
  175. package/dist/esm/vendor/react-native/VirtualizedList/index.mjs +969 -0
  176. package/dist/esm/vendor/react-native/VirtualizedList/index.mjs.map +1 -0
  177. package/dist/esm/vendor/react-native/VirtualizedList/index.native.js +1065 -0
  178. package/dist/esm/vendor/react-native/VirtualizedList/index.native.js.map +6 -0
  179. package/dist/esm/vendor/react-native/deepDiffer/index.js +31 -0
  180. package/dist/esm/vendor/react-native/deepDiffer/index.js.map +6 -0
  181. package/dist/esm/vendor/react-native/deepDiffer/index.mjs +18 -0
  182. package/dist/esm/vendor/react-native/deepDiffer/index.mjs.map +1 -0
  183. package/dist/esm/vendor/react-native/deepDiffer/index.native.js +31 -0
  184. package/dist/esm/vendor/react-native/deepDiffer/index.native.js.map +6 -0
  185. package/package.json +6 -6
  186. package/src/Batchinator/index.tsx +78 -0
  187. package/src/FlatList.tsx +330 -0
  188. package/src/InteractionManager/TaskQueue.tsx +114 -0
  189. package/src/InteractionManager/index.tsx +139 -0
  190. package/src/index.tsx +2 -2
  191. package/src/vendor/react-native/FillRateHelper/index.tsx +218 -0
  192. package/src/vendor/react-native/Utilities/clamp.ts +21 -0
  193. package/src/vendor/react-native/ViewabilityHelper.ts +291 -0
  194. package/src/vendor/react-native/VirtualizeUtils/index.tsx +212 -0
  195. package/src/vendor/react-native/VirtualizedList/CellRenderMask.ts +147 -0
  196. package/src/vendor/react-native/VirtualizedList/ChildListCollection.tsx +73 -0
  197. package/src/vendor/react-native/VirtualizedList/StateSafePureComponent.tsx +79 -0
  198. package/src/vendor/react-native/VirtualizedList/VirtualizedListCellRenderer.tsx +210 -0
  199. package/src/vendor/react-native/VirtualizedList/VirtualizedListContext.tsx +116 -0
  200. package/src/vendor/react-native/VirtualizedList/VirtualizedListProps.ts +130 -0
  201. package/src/vendor/react-native/VirtualizedList/index.tsx +1797 -0
  202. package/src/vendor/react-native/deepDiffer/index.tsx +56 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import { invariant } from '@tamagui/react-native-web-internals'
9
+ import type { FrameMetricProps } from './VirtualizedList/VirtualizedListProps'
10
+
11
+ export type ViewToken = {
12
+ item: any
13
+ key: string
14
+ index?: number | null
15
+ isViewable: boolean
16
+ section?: any
17
+ }
18
+
19
+ export type ViewabilityConfigCallbackPair = {
20
+ viewabilityConfig: ViewabilityConfig
21
+ onViewableItemsChanged: (info: {
22
+ viewableItems: ViewToken[]
23
+ changed: ViewToken[]
24
+ }) => void
25
+ }
26
+
27
+ export type ViewabilityConfig = {
28
+ /**
29
+ * Minimum amount of time (in milliseconds) that an item must be physically viewable before the
30
+ * viewability callback will be fired. A high number means that scrolling through content without
31
+ * stopping will not mark the content as viewable.
32
+ */
33
+ minimumViewTime?: number
34
+
35
+ /**
36
+ * Percent of viewport that must be covered for a partially occluded item to count as
37
+ * "viewable", 0-100. Fully visible items are always considered viewable. A value of 0 means
38
+ * that a single pixel in the viewport makes the item viewable, and a value of 100 means that
39
+ * an item must be either entirely visible or cover the entire viewport to count as viewable.
40
+ */
41
+ viewAreaCoveragePercentThreshold?: number
42
+
43
+ /**
44
+ * Similar to `viewAreaPercentThreshold`, but considers the percent of the item that is visible,
45
+ * rather than the fraction of the viewable area it covers.
46
+ */
47
+ itemVisiblePercentThreshold?: number
48
+
49
+ /**
50
+ * Nothing is considered viewable until the user scrolls or `recordInteraction` is called after
51
+ * render.
52
+ */
53
+ waitForInteraction?: boolean
54
+ }
55
+
56
+ /**
57
+ * A Utility class for calculating viewable items based on current metrics like scroll position and
58
+ * layout.
59
+ */
60
+ class ViewabilityHelper {
61
+ _config: ViewabilityConfig
62
+ _hasInteracted: boolean = false
63
+ _timers: Set<number> = new Set()
64
+ _viewableIndices: number[] = []
65
+ _viewableItems: Map<string, ViewToken> = new Map()
66
+
67
+ constructor(config: ViewabilityConfig = { viewAreaCoveragePercentThreshold: 0 }) {
68
+ this._config = config
69
+ }
70
+
71
+ /**
72
+ * Cleanup, e.g. on unmount. Clears any pending timers.
73
+ */
74
+ dispose() {
75
+ this._timers.forEach(clearTimeout)
76
+ }
77
+
78
+ /**
79
+ * Determines which items are viewable based on the current metrics and config.
80
+ */
81
+ computeViewableItems(
82
+ props: FrameMetricProps,
83
+ scrollOffset: number,
84
+ viewportHeight: number,
85
+ getFrameMetrics: (
86
+ index: number,
87
+ props: FrameMetricProps
88
+ ) => { length: number; offset: number } | null,
89
+ renderRange?: { first: number; last: number }
90
+ ): number[] {
91
+ const itemCount = props.getItemCount(props.data)
92
+ const { itemVisiblePercentThreshold, viewAreaCoveragePercentThreshold } = this._config
93
+ const viewAreaMode = viewAreaCoveragePercentThreshold != null
94
+ const viewablePercentThreshold = viewAreaMode
95
+ ? viewAreaCoveragePercentThreshold
96
+ : itemVisiblePercentThreshold
97
+ invariant(
98
+ viewablePercentThreshold != null &&
99
+ (itemVisiblePercentThreshold != null) !==
100
+ (viewAreaCoveragePercentThreshold != null),
101
+ 'Must set exactly one of itemVisiblePercentThreshold or viewAreaCoveragePercentThreshold'
102
+ )
103
+ const viewableIndices: number[] = []
104
+ if (itemCount === 0) {
105
+ return viewableIndices
106
+ }
107
+ let firstVisible = -1
108
+ const { first, last } = renderRange || { first: 0, last: itemCount - 1 }
109
+ if (last >= itemCount) {
110
+ console.warn(
111
+ 'Invalid render range computing viewability ' +
112
+ JSON.stringify({ renderRange, itemCount })
113
+ )
114
+ return []
115
+ }
116
+ for (let idx = first; idx <= last; idx++) {
117
+ const metrics = getFrameMetrics(idx, props)
118
+ if (!metrics) {
119
+ continue
120
+ }
121
+ const top = metrics.offset - scrollOffset
122
+ const bottom = top + metrics.length
123
+ if (top < viewportHeight && bottom > 0) {
124
+ firstVisible = idx
125
+ if (
126
+ _isViewable(
127
+ viewAreaMode,
128
+ viewablePercentThreshold!,
129
+ top,
130
+ bottom,
131
+ viewportHeight,
132
+ metrics.length
133
+ )
134
+ ) {
135
+ viewableIndices.push(idx)
136
+ }
137
+ } else if (firstVisible >= 0) {
138
+ break
139
+ }
140
+ }
141
+ return viewableIndices
142
+ }
143
+
144
+ /**
145
+ * Figures out which items are viewable and how that has changed from before and calls
146
+ * `onViewableItemsChanged` as appropriate.
147
+ */
148
+ onUpdate(
149
+ props: FrameMetricProps,
150
+ scrollOffset: number,
151
+ viewportHeight: number,
152
+ getFrameMetrics: (
153
+ index: number,
154
+ props: FrameMetricProps
155
+ ) => { length: number; offset: number } | null,
156
+ createViewToken: (
157
+ index: number,
158
+ isViewable: boolean,
159
+ props: FrameMetricProps
160
+ ) => ViewToken,
161
+ onViewableItemsChanged: (info: {
162
+ viewableItems: ViewToken[]
163
+ changed: ViewToken[]
164
+ }) => void,
165
+ renderRange?: { first: number; last: number }
166
+ ): void {
167
+ const itemCount = props.getItemCount(props.data)
168
+ if (
169
+ (this._config.waitForInteraction && !this._hasInteracted) ||
170
+ itemCount === 0 ||
171
+ !getFrameMetrics(0, props)
172
+ ) {
173
+ return
174
+ }
175
+ let viewableIndices: number[] = []
176
+ if (itemCount) {
177
+ viewableIndices = this.computeViewableItems(
178
+ props,
179
+ scrollOffset,
180
+ viewportHeight,
181
+ getFrameMetrics,
182
+ renderRange
183
+ )
184
+ }
185
+ if (
186
+ this._viewableIndices.length === viewableIndices.length &&
187
+ this._viewableIndices.every((v, ii) => v === viewableIndices[ii])
188
+ ) {
189
+ return
190
+ }
191
+ this._viewableIndices = viewableIndices
192
+ if (this._config.minimumViewTime) {
193
+ const handle = setTimeout(() => {
194
+ this._timers.delete(handle as any)
195
+ this._onUpdateSync(
196
+ props,
197
+ viewableIndices,
198
+ onViewableItemsChanged,
199
+ createViewToken
200
+ )
201
+ }, this._config.minimumViewTime)
202
+ this._timers.add(handle as any)
203
+ } else {
204
+ this._onUpdateSync(props, viewableIndices, onViewableItemsChanged, createViewToken)
205
+ }
206
+ }
207
+
208
+ resetViewableIndices() {
209
+ this._viewableIndices = []
210
+ }
211
+
212
+ recordInteraction() {
213
+ this._hasInteracted = true
214
+ }
215
+
216
+ _onUpdateSync(
217
+ props: FrameMetricProps,
218
+ viewableIndicesToCheck: number[],
219
+ onViewableItemsChanged: (info: {
220
+ changed: ViewToken[]
221
+ viewableItems: ViewToken[]
222
+ }) => void,
223
+ createViewToken: (
224
+ index: number,
225
+ isViewable: boolean,
226
+ props: FrameMetricProps
227
+ ) => ViewToken
228
+ ) {
229
+ viewableIndicesToCheck = viewableIndicesToCheck.filter((ii) =>
230
+ this._viewableIndices.includes(ii)
231
+ )
232
+ const prevItems = this._viewableItems
233
+ const nextItems = new Map(
234
+ viewableIndicesToCheck.map((ii) => {
235
+ const viewable = createViewToken(ii, true, props)
236
+ return [viewable.key, viewable]
237
+ })
238
+ )
239
+
240
+ const changed: ViewToken[] = []
241
+ for (const [key, viewable] of nextItems) {
242
+ if (!prevItems.has(key)) {
243
+ changed.push(viewable)
244
+ }
245
+ }
246
+ for (const [key, viewable] of prevItems) {
247
+ if (!nextItems.has(key)) {
248
+ changed.push({ ...viewable, isViewable: false })
249
+ }
250
+ }
251
+ if (changed.length > 0) {
252
+ this._viewableItems = nextItems
253
+ onViewableItemsChanged({
254
+ viewableItems: Array.from(nextItems.values()),
255
+ changed,
256
+ })
257
+ }
258
+ }
259
+ }
260
+
261
+ function _isViewable(
262
+ viewAreaMode: boolean,
263
+ viewablePercentThreshold: number,
264
+ top: number,
265
+ bottom: number,
266
+ viewportHeight: number,
267
+ itemLength: number
268
+ ): boolean {
269
+ if (_isEntirelyVisible(top, bottom, viewportHeight)) {
270
+ return true
271
+ } else {
272
+ const pixels = _getPixelsVisible(top, bottom, viewportHeight)
273
+ const percent = 100 * (viewAreaMode ? pixels / viewportHeight : pixels / itemLength)
274
+ return percent >= viewablePercentThreshold
275
+ }
276
+ }
277
+
278
+ function _getPixelsVisible(top: number, bottom: number, viewportHeight: number): number {
279
+ const visibleHeight = Math.min(bottom, viewportHeight) - Math.max(top, 0)
280
+ return Math.max(0, visibleHeight)
281
+ }
282
+
283
+ function _isEntirelyVisible(
284
+ top: number,
285
+ bottom: number,
286
+ viewportHeight: number
287
+ ): boolean {
288
+ return top >= 0 && bottom <= viewportHeight && bottom > top
289
+ }
290
+
291
+ export default ViewabilityHelper
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ interface FrameMetric {
9
+ length: number
10
+ offset: number
11
+ }
12
+
13
+ interface ScrollMetrics {
14
+ dt: number
15
+ offset: number
16
+ velocity: number
17
+ visibleLength: number
18
+ zoomScale?: number
19
+ }
20
+
21
+ interface Range {
22
+ first: number
23
+ last: number
24
+ }
25
+
26
+ interface FrameMetricProps {
27
+ data: any
28
+ getItemCount: (data: any) => number
29
+ }
30
+
31
+ /**
32
+ * Used to find the indices of the frames that overlap the given offsets. Useful for finding the
33
+ * items that bound different windows of content, such as the visible area or the buffered overscan
34
+ * area.
35
+ */
36
+ export function elementsThatOverlapOffsets(
37
+ offsets: number[],
38
+ props: FrameMetricProps,
39
+ getFrameMetrics: (index: number, props: FrameMetricProps) => FrameMetric,
40
+ zoomScale = 1
41
+ ): number[] {
42
+ const itemCount = props.getItemCount(props.data)
43
+ const result: number[] = []
44
+
45
+ for (let offsetIndex = 0; offsetIndex < offsets.length; offsetIndex++) {
46
+ const currentOffset = offsets[offsetIndex]
47
+ let left = 0
48
+ let right = itemCount - 1
49
+
50
+ while (left <= right) {
51
+ const mid = left + ((right - left) >>> 1)
52
+ const frame = getFrameMetrics(mid, props)
53
+ const scaledOffsetStart = frame.offset * zoomScale
54
+ const scaledOffsetEnd = (frame.offset + frame.length) * zoomScale
55
+
56
+ if (
57
+ (mid === 0 && currentOffset < scaledOffsetStart) ||
58
+ (mid !== 0 && currentOffset <= scaledOffsetStart)
59
+ ) {
60
+ right = mid - 1
61
+ } else if (currentOffset > scaledOffsetEnd) {
62
+ left = mid + 1
63
+ } else {
64
+ result[offsetIndex] = mid
65
+ break
66
+ }
67
+ }
68
+ }
69
+
70
+ return result
71
+ }
72
+
73
+ /**
74
+ * Computes the number of elements in the `next` range that are new compared to the `prev` range.
75
+ * Handy for calculating how many new items will be rendered when the render window changes so we
76
+ * can restrict the number of new items render at once so that content can appear on the screen
77
+ * faster.
78
+ */
79
+ export function newRangeCount(prev: Range, next: Range): number {
80
+ return (
81
+ next.last -
82
+ next.first +
83
+ 1 -
84
+ Math.max(0, 1 + Math.min(next.last, prev.last) - Math.max(next.first, prev.first))
85
+ )
86
+ }
87
+
88
+ /**
89
+ * Custom logic for determining which items should be rendered given the current frame and scroll
90
+ * metrics, as well as the previous render state.
91
+ */
92
+ export function computeWindowedRenderLimits(
93
+ props: FrameMetricProps,
94
+ maxToRenderPerBatch: number,
95
+ windowSize: number,
96
+ prev: Range,
97
+ getFrameMetricsApprox: (index: number, props: FrameMetricProps) => FrameMetric,
98
+ scrollMetrics: ScrollMetrics
99
+ ): Range {
100
+ const itemCount = props.getItemCount(props.data)
101
+ if (itemCount === 0) {
102
+ return { first: 0, last: -1 }
103
+ }
104
+
105
+ const { offset, velocity, visibleLength, zoomScale = 1 } = scrollMetrics
106
+
107
+ const visibleBegin = Math.max(0, offset)
108
+ const visibleEnd = visibleBegin + visibleLength
109
+ const overscanLength = (windowSize - 1) * visibleLength
110
+
111
+ const leadFactor = 0.5
112
+ const fillPreference = velocity > 1 ? 'after' : velocity < -1 ? 'before' : 'none'
113
+
114
+ const overscanBegin = Math.max(0, visibleBegin - (1 - leadFactor) * overscanLength)
115
+ const overscanEnd = Math.max(0, visibleEnd + leadFactor * overscanLength)
116
+
117
+ const lastItemOffset = getFrameMetricsApprox(itemCount - 1, props).offset * zoomScale
118
+ if (lastItemOffset < overscanBegin) {
119
+ return {
120
+ first: Math.max(0, itemCount - 1 - maxToRenderPerBatch),
121
+ last: itemCount - 1,
122
+ }
123
+ }
124
+
125
+ let [overscanFirst, first, last, overscanLast] = elementsThatOverlapOffsets(
126
+ [overscanBegin, visibleBegin, visibleEnd, overscanEnd],
127
+ props,
128
+ getFrameMetricsApprox,
129
+ zoomScale
130
+ )
131
+
132
+ overscanFirst = overscanFirst == null ? 0 : overscanFirst
133
+ first = first == null ? Math.max(0, overscanFirst) : first
134
+ overscanLast = overscanLast == null ? itemCount - 1 : overscanLast
135
+ last = last == null ? Math.min(overscanLast, first + maxToRenderPerBatch - 1) : last
136
+ const visible = { first, last }
137
+
138
+ let newCellCount = newRangeCount(prev, visible)
139
+
140
+ while (true) {
141
+ if (first <= overscanFirst && last >= overscanLast) {
142
+ break
143
+ }
144
+
145
+ const maxNewCells = newCellCount >= maxToRenderPerBatch
146
+ const firstWillAddMore = first <= prev.first || first > prev.last
147
+ const firstShouldIncrement =
148
+ first > overscanFirst && (!maxNewCells || !firstWillAddMore)
149
+ const lastWillAddMore = last >= prev.last || last < prev.first
150
+ const lastShouldIncrement = last < overscanLast && (!maxNewCells || !lastWillAddMore)
151
+
152
+ if (maxNewCells && !firstShouldIncrement && !lastShouldIncrement) {
153
+ break
154
+ }
155
+
156
+ if (
157
+ firstShouldIncrement &&
158
+ !(fillPreference === 'after' && lastShouldIncrement && lastWillAddMore)
159
+ ) {
160
+ if (firstWillAddMore) {
161
+ newCellCount++
162
+ }
163
+ first--
164
+ }
165
+
166
+ if (
167
+ lastShouldIncrement &&
168
+ !(fillPreference === 'before' && firstShouldIncrement && firstWillAddMore)
169
+ ) {
170
+ if (lastWillAddMore) {
171
+ newCellCount++
172
+ }
173
+ last++
174
+ }
175
+ }
176
+
177
+ if (
178
+ !(
179
+ last >= first &&
180
+ first >= 0 &&
181
+ last < itemCount &&
182
+ first >= overscanFirst &&
183
+ last <= overscanLast &&
184
+ first <= visible.first &&
185
+ last >= visible.last
186
+ )
187
+ ) {
188
+ throw new Error(
189
+ 'Bad window calculation ' +
190
+ JSON.stringify({
191
+ first,
192
+ last,
193
+ itemCount,
194
+ overscanFirst,
195
+ overscanLast,
196
+ visible,
197
+ })
198
+ )
199
+ }
200
+
201
+ return { first, last }
202
+ }
203
+
204
+ export function keyExtractor(item: any, index: number): string {
205
+ if (typeof item === 'object' && item?.key != null) {
206
+ return item.key
207
+ }
208
+ if (typeof item === 'object' && item?.id != null) {
209
+ return item.id
210
+ }
211
+ return String(index)
212
+ }
@@ -0,0 +1,147 @@
1
+ // @ts-nocheck
2
+
3
+ /**
4
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+ import { invariant } from '@tamagui/react-native-web-internals'
11
+
12
+ export type CellRegion = {
13
+ first: number
14
+ last: number
15
+ isSpacer: boolean
16
+ }
17
+
18
+ export class CellRenderMask {
19
+ private _numCells: number
20
+ private _regions: Array<CellRegion>
21
+
22
+ constructor(numCells: number) {
23
+ invariant(numCells >= 0, 'CellRenderMask must contain a non-negative number of cells')
24
+
25
+ this._numCells = numCells
26
+
27
+ if (numCells === 0) {
28
+ this._regions = []
29
+ } else {
30
+ this._regions = [
31
+ {
32
+ first: 0,
33
+ last: numCells - 1,
34
+ isSpacer: true,
35
+ },
36
+ ]
37
+ }
38
+ }
39
+
40
+ enumerateRegions(): ReadonlyArray<CellRegion> {
41
+ return this._regions
42
+ }
43
+
44
+ addCells(cells: { first: number; last: number }): void {
45
+ invariant(
46
+ cells.first >= 0 &&
47
+ cells.first < this._numCells &&
48
+ cells.last >= -1 &&
49
+ cells.last < this._numCells &&
50
+ cells.last >= cells.first - 1,
51
+ 'CellRenderMask.addCells called with invalid cell range'
52
+ )
53
+
54
+ // VirtualizedList uses inclusive ranges, where zero-count states are
55
+ // possible. E.g. [0, -1] for no cells, starting at 0.
56
+ if (cells.last < cells.first) {
57
+ return
58
+ }
59
+
60
+ const [firstIntersect, firstIntersectIdx] = this._findRegion(cells.first)
61
+ const [lastIntersect, lastIntersectIdx] = this._findRegion(cells.last)
62
+
63
+ // Fast-path if the cells to add are already all present in the mask. We
64
+ // will otherwise need to do some mutation.
65
+ if (firstIntersectIdx === lastIntersectIdx && !firstIntersect.isSpacer) {
66
+ return
67
+ }
68
+
69
+ // We need to replace the existing covered regions with 1-3 new regions
70
+ // depending whether we need to split spacers out of overlapping regions.
71
+ const newLeadRegion: Array<CellRegion> = []
72
+ const newTailRegion: Array<CellRegion> = []
73
+ const newMainRegion: CellRegion = {
74
+ ...cells,
75
+ isSpacer: false,
76
+ }
77
+
78
+ if (firstIntersect.first < newMainRegion.first) {
79
+ if (firstIntersect.isSpacer) {
80
+ newLeadRegion.push({
81
+ first: firstIntersect.first,
82
+ last: newMainRegion.first - 1,
83
+ isSpacer: true,
84
+ })
85
+ } else {
86
+ newMainRegion.first = firstIntersect.first
87
+ }
88
+ }
89
+
90
+ if (lastIntersect.last > newMainRegion.last) {
91
+ if (lastIntersect.isSpacer) {
92
+ newTailRegion.push({
93
+ first: newMainRegion.last + 1,
94
+ last: lastIntersect.last,
95
+ isSpacer: true,
96
+ })
97
+ } else {
98
+ newMainRegion.last = lastIntersect.last
99
+ }
100
+ }
101
+
102
+ const replacementRegions: Array<CellRegion> = [
103
+ ...newLeadRegion,
104
+ newMainRegion,
105
+ ...newTailRegion,
106
+ ]
107
+ const numRegionsToDelete = lastIntersectIdx - firstIntersectIdx + 1
108
+ this._regions.splice(firstIntersectIdx, numRegionsToDelete, ...replacementRegions)
109
+ }
110
+
111
+ numCells(): number {
112
+ return this._numCells
113
+ }
114
+
115
+ equals(other: CellRenderMask): boolean {
116
+ return (
117
+ this._numCells === other._numCells &&
118
+ this._regions.length === other._regions.length &&
119
+ this._regions.every(
120
+ (region, i) =>
121
+ region.first === other._regions[i].first &&
122
+ region.last === other._regions[i].last &&
123
+ region.isSpacer === other._regions[i].isSpacer
124
+ )
125
+ )
126
+ }
127
+
128
+ private _findRegion(cellIdx: number): [CellRegion, number] {
129
+ let firstIdx = 0
130
+ let lastIdx = this._regions.length - 1
131
+
132
+ while (firstIdx <= lastIdx) {
133
+ const middleIdx = Math.floor((firstIdx + lastIdx) / 2)
134
+ const middleRegion = this._regions[middleIdx]
135
+
136
+ if (cellIdx >= middleRegion.first && cellIdx <= middleRegion.last) {
137
+ return [middleRegion, middleIdx]
138
+ } else if (cellIdx < middleRegion.first) {
139
+ lastIdx = middleIdx - 1
140
+ } else if (cellIdx > middleRegion.last) {
141
+ firstIdx = middleIdx + 1
142
+ }
143
+ }
144
+
145
+ invariant(false, `A region was not found containing cellIdx ${cellIdx}`)
146
+ }
147
+ }