@shopify/flash-list 1.8.0 → 2.0.0-alpha.2

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 (170) hide show
  1. package/README.md +147 -26
  2. package/dist/FlashListProps.d.ts +65 -2
  3. package/dist/FlashListProps.d.ts.map +1 -1
  4. package/dist/__tests__/AverageWindow.test.js +35 -0
  5. package/dist/__tests__/AverageWindow.test.js.map +1 -1
  6. package/dist/enableNewCore.d.ts +3 -0
  7. package/dist/enableNewCore.d.ts.map +1 -0
  8. package/dist/enableNewCore.js +25 -0
  9. package/dist/enableNewCore.js.map +1 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +28 -8
  13. package/dist/index.js.map +1 -1
  14. package/dist/recyclerview/RecycleKeyManager.d.ts +82 -0
  15. package/dist/recyclerview/RecycleKeyManager.d.ts.map +1 -0
  16. package/dist/recyclerview/RecycleKeyManager.js +135 -0
  17. package/dist/recyclerview/RecycleKeyManager.js.map +1 -0
  18. package/dist/recyclerview/RecyclerView.d.ts +12 -0
  19. package/dist/recyclerview/RecyclerView.d.ts.map +1 -0
  20. package/dist/recyclerview/RecyclerView.js +283 -0
  21. package/dist/recyclerview/RecyclerView.js.map +1 -0
  22. package/dist/recyclerview/RecyclerViewContextProvider.d.ts +12 -0
  23. package/dist/recyclerview/RecyclerViewContextProvider.d.ts.map +1 -0
  24. package/dist/recyclerview/RecyclerViewContextProvider.js +11 -0
  25. package/dist/recyclerview/RecyclerViewContextProvider.js.map +1 -0
  26. package/dist/recyclerview/RecyclerViewManager.d.ts +52 -0
  27. package/dist/recyclerview/RecyclerViewManager.d.ts.map +1 -0
  28. package/dist/recyclerview/RecyclerViewManager.js +323 -0
  29. package/dist/recyclerview/RecyclerViewManager.js.map +1 -0
  30. package/dist/recyclerview/RecyclerViewProps.d.ts +9 -0
  31. package/dist/recyclerview/RecyclerViewProps.d.ts.map +1 -0
  32. package/dist/recyclerview/RecyclerViewProps.js +3 -0
  33. package/dist/recyclerview/RecyclerViewProps.js.map +1 -0
  34. package/dist/recyclerview/ViewHolder.d.ts +45 -0
  35. package/dist/recyclerview/ViewHolder.d.ts.map +1 -0
  36. package/dist/recyclerview/ViewHolder.js +96 -0
  37. package/dist/recyclerview/ViewHolder.js.map +1 -0
  38. package/dist/recyclerview/ViewHolderCollection.d.ts +57 -0
  39. package/dist/recyclerview/ViewHolderCollection.d.ts.map +1 -0
  40. package/dist/recyclerview/ViewHolderCollection.js +75 -0
  41. package/dist/recyclerview/ViewHolderCollection.js.map +1 -0
  42. package/dist/recyclerview/components/CompatScroller.d.ts +7 -0
  43. package/dist/recyclerview/components/CompatScroller.d.ts.map +1 -0
  44. package/dist/recyclerview/components/CompatScroller.js +8 -0
  45. package/dist/recyclerview/components/CompatScroller.js.map +1 -0
  46. package/dist/recyclerview/components/CompatView.d.ts +7 -0
  47. package/dist/recyclerview/components/CompatView.d.ts.map +1 -0
  48. package/dist/recyclerview/components/CompatView.js +8 -0
  49. package/dist/recyclerview/components/CompatView.js.map +1 -0
  50. package/dist/recyclerview/components/ScrollAnchor.d.ts +28 -0
  51. package/dist/recyclerview/components/ScrollAnchor.d.ts.map +1 -0
  52. package/dist/recyclerview/components/ScrollAnchor.js +35 -0
  53. package/dist/recyclerview/components/ScrollAnchor.js.map +1 -0
  54. package/dist/recyclerview/components/StickyHeaders.d.ts +38 -0
  55. package/dist/recyclerview/components/StickyHeaders.d.ts.map +1 -0
  56. package/dist/recyclerview/components/StickyHeaders.js +119 -0
  57. package/dist/recyclerview/components/StickyHeaders.js.map +1 -0
  58. package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts +51 -0
  59. package/dist/recyclerview/helpers/ConsecutiveNumbers.d.ts.map +1 -0
  60. package/dist/recyclerview/helpers/ConsecutiveNumbers.js +122 -0
  61. package/dist/recyclerview/helpers/ConsecutiveNumbers.js.map +1 -0
  62. package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts +59 -0
  63. package/dist/recyclerview/helpers/EngagedIndicesTracker.d.ts.map +1 -0
  64. package/dist/recyclerview/helpers/EngagedIndicesTracker.js +138 -0
  65. package/dist/recyclerview/helpers/EngagedIndicesTracker.js.map +1 -0
  66. package/dist/recyclerview/hooks/useBoundDetection.d.ts +19 -0
  67. package/dist/recyclerview/hooks/useBoundDetection.d.ts.map +1 -0
  68. package/dist/recyclerview/hooks/useBoundDetection.js +103 -0
  69. package/dist/recyclerview/hooks/useBoundDetection.js.map +1 -0
  70. package/dist/recyclerview/hooks/useLayoutState.d.ts +12 -0
  71. package/dist/recyclerview/hooks/useLayoutState.d.ts.map +1 -0
  72. package/dist/recyclerview/hooks/useLayoutState.js +43 -0
  73. package/dist/recyclerview/hooks/useLayoutState.js.map +1 -0
  74. package/dist/recyclerview/hooks/useOnLoad.d.ts +25 -0
  75. package/dist/recyclerview/hooks/useOnLoad.d.ts.map +1 -0
  76. package/dist/recyclerview/hooks/useOnLoad.js +73 -0
  77. package/dist/recyclerview/hooks/useOnLoad.js.map +1 -0
  78. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts +72 -0
  79. package/dist/recyclerview/hooks/useRecyclerViewController.d.ts.map +1 -0
  80. package/dist/recyclerview/hooks/useRecyclerViewController.js +370 -0
  81. package/dist/recyclerview/hooks/useRecyclerViewController.js.map +1 -0
  82. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts +6 -0
  83. package/dist/recyclerview/hooks/useRecyclerViewManager.d.ts.map +1 -0
  84. package/dist/recyclerview/hooks/useRecyclerViewManager.js +27 -0
  85. package/dist/recyclerview/hooks/useRecyclerViewManager.js.map +1 -0
  86. package/dist/recyclerview/hooks/useRecyclingState.d.ts +16 -0
  87. package/dist/recyclerview/hooks/useRecyclingState.d.ts.map +1 -0
  88. package/dist/recyclerview/hooks/useRecyclingState.js +54 -0
  89. package/dist/recyclerview/hooks/useRecyclingState.js.map +1 -0
  90. package/dist/recyclerview/hooks/useSecondaryProps.d.ts +27 -0
  91. package/dist/recyclerview/hooks/useSecondaryProps.d.ts.map +1 -0
  92. package/dist/recyclerview/hooks/useSecondaryProps.js +93 -0
  93. package/dist/recyclerview/hooks/useSecondaryProps.js.map +1 -0
  94. package/dist/recyclerview/hooks/useUnmountFlag.d.ts +11 -0
  95. package/dist/recyclerview/hooks/useUnmountFlag.d.ts.map +1 -0
  96. package/dist/recyclerview/hooks/useUnmountFlag.js +28 -0
  97. package/dist/recyclerview/hooks/useUnmountFlag.js.map +1 -0
  98. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts +65 -0
  99. package/dist/recyclerview/layout-managers/GridLayoutManager.d.ts.map +1 -0
  100. package/dist/recyclerview/layout-managers/GridLayoutManager.js +204 -0
  101. package/dist/recyclerview/layout-managers/GridLayoutManager.js.map +1 -0
  102. package/dist/recyclerview/layout-managers/LayoutManager.d.ts +281 -0
  103. package/dist/recyclerview/layout-managers/LayoutManager.d.ts.map +1 -0
  104. package/dist/recyclerview/layout-managers/LayoutManager.js +250 -0
  105. package/dist/recyclerview/layout-managers/LayoutManager.js.map +1 -0
  106. package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts +52 -0
  107. package/dist/recyclerview/layout-managers/LinearLayoutManager.d.ts.map +1 -0
  108. package/dist/recyclerview/layout-managers/LinearLayoutManager.js +191 -0
  109. package/dist/recyclerview/layout-managers/LinearLayoutManager.js.map +1 -0
  110. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts +73 -0
  111. package/dist/recyclerview/layout-managers/MasonryLayoutManager.d.ts.map +1 -0
  112. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js +274 -0
  113. package/dist/recyclerview/layout-managers/MasonryLayoutManager.js.map +1 -0
  114. package/dist/recyclerview/utils/adjustOffsetForRTL.d.ts +12 -0
  115. package/dist/recyclerview/utils/adjustOffsetForRTL.d.ts.map +1 -0
  116. package/dist/recyclerview/utils/adjustOffsetForRTL.js +18 -0
  117. package/dist/recyclerview/utils/adjustOffsetForRTL.js.map +1 -0
  118. package/dist/recyclerview/utils/componentUtils.d.ts +19 -0
  119. package/dist/recyclerview/utils/componentUtils.d.ts.map +1 -0
  120. package/dist/recyclerview/utils/componentUtils.js +32 -0
  121. package/dist/recyclerview/utils/componentUtils.js.map +1 -0
  122. package/dist/recyclerview/utils/findVisibleIndex.d.ts +24 -0
  123. package/dist/recyclerview/utils/findVisibleIndex.d.ts.map +1 -0
  124. package/dist/recyclerview/utils/findVisibleIndex.js +82 -0
  125. package/dist/recyclerview/utils/findVisibleIndex.js.map +1 -0
  126. package/dist/recyclerview/utils/measureLayout.d.ts +56 -0
  127. package/dist/recyclerview/utils/measureLayout.d.ts.map +1 -0
  128. package/dist/recyclerview/utils/measureLayout.js +77 -0
  129. package/dist/recyclerview/utils/measureLayout.js.map +1 -0
  130. package/dist/tsconfig.tsbuildinfo +1 -1
  131. package/dist/utils/AverageWindow.d.ts +13 -0
  132. package/dist/utils/AverageWindow.d.ts.map +1 -1
  133. package/dist/utils/AverageWindow.js +30 -1
  134. package/dist/utils/AverageWindow.js.map +1 -1
  135. package/package.json +1 -1
  136. package/src/FlashListProps.ts +73 -2
  137. package/src/__tests__/AverageWindow.test.ts +49 -1
  138. package/src/enableNewCore.ts +22 -0
  139. package/src/index.ts +21 -0
  140. package/src/recyclerview/RecycleKeyManager.ts +185 -0
  141. package/src/recyclerview/RecyclerView.tsx +500 -0
  142. package/src/recyclerview/RecyclerViewContextProvider.ts +19 -0
  143. package/src/recyclerview/RecyclerViewManager.ts +379 -0
  144. package/src/recyclerview/RecyclerViewProps.ts +10 -0
  145. package/src/recyclerview/ViewHolder.tsx +173 -0
  146. package/src/recyclerview/ViewHolderCollection.tsx +164 -0
  147. package/src/recyclerview/components/CompatScroller.ts +9 -0
  148. package/src/recyclerview/components/CompatView.ts +9 -0
  149. package/src/recyclerview/components/ScrollAnchor.tsx +53 -0
  150. package/src/recyclerview/components/StickyHeaders.tsx +210 -0
  151. package/src/recyclerview/helpers/ConsecutiveNumbers.ts +120 -0
  152. package/src/recyclerview/helpers/EngagedIndicesTracker.ts +191 -0
  153. package/src/recyclerview/hooks/useBoundDetection.ts +127 -0
  154. package/src/recyclerview/hooks/useLayoutState.ts +46 -0
  155. package/src/recyclerview/hooks/useOnLoad.ts +78 -0
  156. package/src/recyclerview/hooks/useRecyclerViewController.tsx +487 -0
  157. package/src/recyclerview/hooks/useRecyclerViewManager.ts +30 -0
  158. package/src/recyclerview/hooks/useRecyclingState.ts +63 -0
  159. package/src/recyclerview/hooks/useSecondaryProps.tsx +119 -0
  160. package/src/recyclerview/hooks/useUnmountFlag.ts +26 -0
  161. package/src/recyclerview/layout-managers/GridLayoutManager.ts +215 -0
  162. package/src/recyclerview/layout-managers/LayoutManager.ts +493 -0
  163. package/src/recyclerview/layout-managers/LinearLayoutManager.ts +167 -0
  164. package/src/recyclerview/layout-managers/MasonryLayoutManager.ts +302 -0
  165. package/src/recyclerview/utils/adjustOffsetForRTL.ts +17 -0
  166. package/src/recyclerview/utils/componentUtils.ts +28 -0
  167. package/src/recyclerview/utils/findVisibleIndex.ts +94 -0
  168. package/src/recyclerview/utils/measureLayout.ts +89 -0
  169. package/src/utils/AverageWindow.ts +33 -0
  170. package/src/viewability/ViewToken.ts +1 -1
@@ -0,0 +1,493 @@
1
+ // Interface of layout manager for app's listviews
2
+
3
+ import { MultiTypeAverageWindow } from "../../utils/AverageWindow";
4
+ import { ConsecutiveNumbers } from "../helpers/ConsecutiveNumbers";
5
+ import {
6
+ findFirstVisibleIndex,
7
+ findLastVisibleIndex,
8
+ } from "../utils/findVisibleIndex";
9
+ import { areDimensionsNotEqual } from "../utils/measureLayout";
10
+
11
+ /**
12
+ * Base abstract class for layout managers in the recycler view system.
13
+ * Provides common functionality for managing item layouts and dimensions.
14
+ * Supports both horizontal and vertical layouts with dynamic item sizing.
15
+ */
16
+ export abstract class RVLayoutManager {
17
+ /** Whether the layout is horizontal (true) or vertical (false) */
18
+ protected horizontal: boolean;
19
+ /** Array of layout information for all items */
20
+ protected layouts: RVLayout[];
21
+ /** Dimensions of the visible window/viewport */
22
+ protected windowSize: RVDimension;
23
+ /** Information about item spans and sizes */
24
+ protected spanSizeInfo: SpanSizeInfo = {};
25
+ /** Maximum number of columns in the layout */
26
+ protected maxColumns: number;
27
+ /** Optional callback to override default item layout */
28
+ protected overrideItemLayout?: (index: number, layout: SpanSizeInfo) => void;
29
+
30
+ /** Whether to optimize item placement for better space utilization */
31
+ protected optimizeItemArrangement: boolean;
32
+
33
+ /** Flag indicating if the layout requires repainting */
34
+ public requiresRepaint: boolean = false;
35
+
36
+ /** Optional function to determine item type */
37
+ private _getItemType?: (index: number) => string | number;
38
+ /** Window for tracking average heights by item type */
39
+ private heightAverageWindow: MultiTypeAverageWindow;
40
+ /** Window for tracking average widths by item type */
41
+ private widthAverageWindow: MultiTypeAverageWindow;
42
+ /** Maximum number of items to process in a single layout pass */
43
+ private maxItemsToProcess: number = 250; // TODO: make this dynamic
44
+
45
+ constructor(params: LayoutParams, previousLayoutManager?: RVLayoutManager) {
46
+ this.heightAverageWindow = new MultiTypeAverageWindow(5, 200);
47
+ this.widthAverageWindow = new MultiTypeAverageWindow(5, 200);
48
+ this._getItemType = params.getItemType;
49
+ this.overrideItemLayout = params.overrideItemLayout;
50
+ this.layouts = previousLayoutManager?.layouts ?? [];
51
+ if (previousLayoutManager) {
52
+ this.updateLayoutParams(params);
53
+ } else {
54
+ this.horizontal = Boolean(params.horizontal);
55
+ this.windowSize = params.windowSize;
56
+ this.maxColumns = params.maxColumns ?? 1;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Gets the type of an item at the given index.
62
+ * @param index Index of the item
63
+ * @returns Item type or "default" if not specified
64
+ */
65
+ private getItemType(index: number): string | number {
66
+ return this._getItemType?.(index) ?? "default";
67
+ }
68
+
69
+ /**
70
+ * Gets the estimated width for an item based on its type.
71
+ * @param index Index of the item
72
+ * @returns Estimated width
73
+ */
74
+ protected getEstimatedWidth(index: number): number {
75
+ return this.widthAverageWindow.getCurrentValue(this.getItemType(index));
76
+ }
77
+
78
+ /**
79
+ * Gets the estimated height for an item based on its type.
80
+ * @param index Index of the item
81
+ * @returns Estimated height
82
+ */
83
+ protected getEstimatedHeight(index: number): number {
84
+ return this.heightAverageWindow.getCurrentValue(this.getItemType(index));
85
+ }
86
+
87
+ /**
88
+ * Abstract method to process layout information for items.
89
+ * @param layoutInfo Array of layout information for items
90
+ * @param itemCount Total number of items in the list
91
+ * @returns Index of first modified layout or void
92
+ */
93
+ protected abstract processLayoutInfo(
94
+ layoutInfo: RVLayoutInfo[],
95
+ itemCount: number
96
+ ): number | void;
97
+
98
+ /**
99
+ * Checks if the layout is horizontal.
100
+ * @returns True if horizontal, false if vertical
101
+ */
102
+ isHorizontal(): boolean {
103
+ return this.horizontal;
104
+ }
105
+
106
+ /**
107
+ * Gets the dimensions of the visible window.
108
+ * @returns Window dimensions
109
+ */
110
+ getWindowsSize(): RVDimension {
111
+ return this.windowSize;
112
+ }
113
+
114
+ /**
115
+ * Gets indices of items currently visible in the viewport.
116
+ * Uses binary search for efficient lookup.
117
+ * @param unboundDimensionStart Start position of viewport
118
+ * @param unboundDimensionEnd End position of viewport
119
+ * @returns ConsecutiveNumbers containing visible indices
120
+ */
121
+ getVisibleLayouts(
122
+ unboundDimensionStart: number,
123
+ unboundDimensionEnd: number
124
+ ): ConsecutiveNumbers {
125
+ // Find the first visible index
126
+ const firstVisibleIndex = findFirstVisibleIndex(
127
+ this.layouts,
128
+ unboundDimensionStart,
129
+ this.horizontal
130
+ );
131
+
132
+ // Find the last visible index
133
+ const lastVisibleIndex = findLastVisibleIndex(
134
+ this.layouts,
135
+ unboundDimensionEnd,
136
+ this.horizontal
137
+ );
138
+
139
+ // Collect the indices in the range
140
+ if (firstVisibleIndex !== -1 && lastVisibleIndex !== -1) {
141
+ return new ConsecutiveNumbers(firstVisibleIndex, lastVisibleIndex);
142
+ }
143
+ return ConsecutiveNumbers.EMPTY;
144
+ }
145
+
146
+ /**
147
+ * Removes layout information for specified indices and recomputes layout.
148
+ * @param indices Array of indices to remove
149
+ */
150
+ deleteLayout(indices: number[]): void {
151
+ // Sort indices in descending order
152
+ indices.sort((num1, num2) => num2 - num1);
153
+
154
+ // Remove elements from the array
155
+ for (const index of indices) {
156
+ this.layouts.splice(index, 1);
157
+ }
158
+ const startIndex = Math.min(...indices);
159
+ // Recompute layouts starting from the smallest index in the original indices array
160
+ this.recomputeLayouts(
161
+ this.getMinRecomputeIndex(startIndex),
162
+ this.getMaxRecomputeIndex(startIndex)
163
+ );
164
+ }
165
+
166
+ /**
167
+ * Updates layout information for items and recomputes layout if necessary.
168
+ * @param layoutInfo Array of layout information for items (real measurements)
169
+ * @param totalItemCount Total number of items in the list
170
+ */
171
+ modifyLayout(layoutInfo: RVLayoutInfo[], totalItemCount: number): void {
172
+ let minRecomputeIndex = Number.MAX_VALUE;
173
+
174
+ if (this.layouts.length > totalItemCount) {
175
+ this.layouts.length = totalItemCount;
176
+ minRecomputeIndex = totalItemCount - 1; // <0 gets skipped so it's safe to set to totalItemCount - 1
177
+ }
178
+ // update average windows
179
+ minRecomputeIndex = Math.min(
180
+ minRecomputeIndex,
181
+ this.computeEstimatesAndMinRecomputeIndex(layoutInfo)
182
+ );
183
+
184
+ if (this.layouts.length < totalItemCount && totalItemCount > 0) {
185
+ const startIndex = this.layouts.length;
186
+ this.layouts.length = totalItemCount;
187
+ for (let i = startIndex; i < totalItemCount; i++) {
188
+ this.getLayout(i);
189
+ }
190
+ this.recomputeLayouts(startIndex, totalItemCount - 1);
191
+ }
192
+ minRecomputeIndex = Math.min(
193
+ minRecomputeIndex,
194
+ this.processLayoutInfo(layoutInfo, totalItemCount) ?? minRecomputeIndex
195
+ );
196
+ // compute minRecomputeIndex
197
+ minRecomputeIndex = Math.min(
198
+ minRecomputeIndex,
199
+ this.computeEstimatesAndMinRecomputeIndex(layoutInfo)
200
+ );
201
+ if (minRecomputeIndex >= 0 && minRecomputeIndex < totalItemCount) {
202
+ this.recomputeLayouts(
203
+ this.getMinRecomputeIndex(minRecomputeIndex),
204
+ this.getMaxRecomputeIndex(minRecomputeIndex)
205
+ );
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Gets layout information for an item at the given index.
211
+ * Creates and initializes a new layout if one doesn't exist.
212
+ * @param index Index of the item
213
+ * @returns Layout information for the item
214
+ */
215
+ getLayout(index: number): RVLayout {
216
+ if (index >= this.layouts.length) {
217
+ throw new Error("index out of bounds, not enough layouts");
218
+ }
219
+ let layout = this.layouts[index];
220
+ if (!layout) {
221
+ // Create new layout with estimated dimensions
222
+ layout = {
223
+ x: 0,
224
+ y: 0,
225
+ width: 0,
226
+ height: 0,
227
+ };
228
+ this.layouts[index] = layout;
229
+ }
230
+ if (!layout.isWidthMeasured || !layout.isHeightMeasured) {
231
+ this.estimateLayout(index);
232
+ }
233
+ return layout;
234
+ }
235
+
236
+ /**
237
+ * Updates layout parameters and triggers recomputation if necessary.
238
+ * @param params New layout parameters
239
+ */
240
+ updateLayoutParams(params: LayoutParams) {
241
+ this.windowSize = params.windowSize;
242
+ this.horizontal = params.horizontal ?? this.horizontal;
243
+ this.maxColumns = params.maxColumns ?? this.maxColumns;
244
+ this.optimizeItemArrangement =
245
+ params.optimizeItemArrangement ?? this.optimizeItemArrangement;
246
+ }
247
+
248
+ /**
249
+ * Abstract method to recompute layouts for items in the given range.
250
+ * @param startIndex Starting index of items to recompute
251
+ * @param endIndex Ending index of items to recompute
252
+ */
253
+ abstract recomputeLayouts(startIndex: number, endIndex: number): void;
254
+
255
+ /**
256
+ * Abstract method to get the total size of the layout area.
257
+ * @returns RVDimension containing width and height of the layout
258
+ */
259
+ abstract getLayoutSize(): RVDimension;
260
+
261
+ /**
262
+ * Abstract method to estimate layout dimensions for an item.
263
+ * @param index Index of the item to estimate layout for
264
+ */
265
+ protected abstract estimateLayout(index: number): void;
266
+
267
+ /**
268
+ * Gets span size information for an item, applying any overrides.
269
+ * @param index Index of the item
270
+ * @returns SpanSizeInfo for the item
271
+ */
272
+ protected getSpanSizeInfo(index: number): SpanSizeInfo {
273
+ this.spanSizeInfo.span = undefined;
274
+ this.overrideItemLayout?.(index, this.spanSizeInfo);
275
+ return this.spanSizeInfo;
276
+ }
277
+
278
+ /**
279
+ * Gets the maximum index to process in a single layout pass.
280
+ * @param startIndex Starting index
281
+ * @returns Maximum index to process
282
+ */
283
+ private getMaxRecomputeIndex(startIndex: number): number {
284
+ return Math.min(
285
+ startIndex + this.maxItemsToProcess,
286
+ this.layouts.length - 1
287
+ );
288
+ }
289
+
290
+ /**
291
+ * Gets the minimum index to process in a single layout pass.
292
+ * @param startIndex Starting index
293
+ * @returns Minimum index to process
294
+ */
295
+ private getMinRecomputeIndex(startIndex: number): number {
296
+ return startIndex;
297
+ }
298
+
299
+ /**
300
+ * Computes size estimates and finds the minimum recompute index.
301
+ * @param layoutInfo Array of layout information for items
302
+ * @returns Minimum index that needs recomputation
303
+ */
304
+ private computeEstimatesAndMinRecomputeIndex(
305
+ layoutInfo: RVLayoutInfo[]
306
+ ): number {
307
+ let minRecomputeIndex = Number.MAX_VALUE;
308
+ for (const info of layoutInfo) {
309
+ const { index, dimensions } = info;
310
+ const storedLayout = this.layouts[index];
311
+ if (
312
+ !storedLayout.isHeightMeasured ||
313
+ !storedLayout.isWidthMeasured ||
314
+ areDimensionsNotEqual(storedLayout.height, dimensions.height) ||
315
+ areDimensionsNotEqual(storedLayout.width, dimensions.width)
316
+ ) {
317
+ minRecomputeIndex = Math.min(minRecomputeIndex, index);
318
+ }
319
+ this.heightAverageWindow.addValue(
320
+ dimensions.height,
321
+ this.getItemType(index)
322
+ );
323
+ this.widthAverageWindow.addValue(
324
+ dimensions.width,
325
+ this.getItemType(index)
326
+ );
327
+ }
328
+ return minRecomputeIndex;
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Configuration parameters for the layout manager
334
+ */
335
+ export interface LayoutParams {
336
+ /**
337
+ * The dimensions of the visible window/viewport that displays list items
338
+ * Used to determine which items are visible and need to be rendered
339
+ */
340
+ windowSize: RVDimension;
341
+
342
+ /**
343
+ * Determines if the list scrolls horizontally (true) or vertically (false)
344
+ * Affects how items are positioned and which dimension is used for scrolling
345
+ */
346
+ horizontal?: boolean;
347
+
348
+ /**
349
+ * Maximum number of columns in a grid layout
350
+ * Controls how many items can be placed side by side
351
+ */
352
+ maxColumns?: number;
353
+
354
+ /**
355
+ * When true, attempts to optimize item placement for better space utilization
356
+ * May affect the ordering of items to minimize empty space
357
+ */
358
+ optimizeItemArrangement?: boolean;
359
+
360
+ /**
361
+ * Callback to manually override layout properties for specific items
362
+ * Allows custom control over span and size for individual items
363
+ */
364
+ overrideItemLayout?: (index: number, layout: SpanSizeInfo) => void;
365
+
366
+ /**
367
+ * Function to determine the type of an item at a specific index
368
+ * Used for size estimation and optimization based on item types
369
+ */
370
+ getItemType?: (index: number) => string | number;
371
+ }
372
+
373
+ /**
374
+ * Information about an item's layout including its index and dimensions
375
+ * Used when updating layout information for specific items
376
+ */
377
+ export interface RVLayoutInfo {
378
+ /**
379
+ * The index of the item in the data array
380
+ * Used to identify which item this layout information belongs to
381
+ */
382
+ index: number;
383
+
384
+ /**
385
+ * The width and height dimensions of the item
386
+ * Used to update the layout manager's knowledge of item sizes
387
+ */
388
+ dimensions: RVDimension;
389
+ }
390
+
391
+ /**
392
+ * Information about an item's span and size in a grid layout
393
+ * Used when overriding default layout behavior for specific items
394
+ */
395
+ export interface SpanSizeInfo {
396
+ /**
397
+ * Number of columns/cells this item should span horizontally
398
+ * Used in grid layouts to allow items to take up multiple columns
399
+ */
400
+ span?: number;
401
+
402
+ /**
403
+ * Custom size value for the item
404
+ * Can be used to override the default size calculation
405
+ */
406
+ size?: number;
407
+ }
408
+
409
+ /**
410
+ * Complete layout information for a list item
411
+ * Extends RVDimension with positioning and constraint properties
412
+ * Used to position and size ViewHolder components in the list
413
+ */
414
+ export interface RVLayout extends RVDimension {
415
+ /**
416
+ * X-coordinate (horizontal position) in pixels
417
+ * Used to position the item horizontally with absolute positioning
418
+ */
419
+ x: number;
420
+
421
+ /**
422
+ * Y-coordinate (vertical position) in pixels
423
+ * Used to position the item vertically with absolute positioning
424
+ */
425
+ y: number;
426
+
427
+ /**
428
+ * Indicates if the width has been measured from the actual rendered item
429
+ * When false, width may be an estimated value
430
+ */
431
+ isWidthMeasured?: boolean;
432
+
433
+ /**
434
+ * Indicates if the height has been measured from the actual rendered item
435
+ * When false, height may be an estimated value
436
+ */
437
+ isHeightMeasured?: boolean;
438
+
439
+ /**
440
+ * Minimum height constraint in pixels
441
+ * Applied to the ViewHolder's style to ensure item doesn't shrink below this value
442
+ */
443
+ minHeight?: number;
444
+
445
+ /**
446
+ * Minimum width constraint in pixels
447
+ * Applied to the ViewHolder's style to ensure item doesn't shrink below this value
448
+ */
449
+ minWidth?: number;
450
+
451
+ /**
452
+ * Maximum height constraint in pixels
453
+ * Applied to the ViewHolder's style to limit item's vertical growth
454
+ */
455
+ maxHeight?: number;
456
+
457
+ /**
458
+ * Maximum width constraint in pixels
459
+ * Applied to the ViewHolder's style to limit item's horizontal growth
460
+ */
461
+ maxWidth?: number;
462
+
463
+ /**
464
+ * When true, the width value is strictly enforced on the ViewHolder
465
+ * When false, the width is determined by content
466
+ */
467
+ enforcedWidth?: boolean;
468
+
469
+ /**
470
+ * When true, the height value is strictly enforced on the ViewHolder
471
+ * When false, the height is determined by content
472
+ */
473
+ enforcedHeight?: boolean;
474
+ }
475
+
476
+ /**
477
+ * Basic dimension interface representing width and height
478
+ * Used throughout the recycler view system to track item sizes
479
+ * and viewport dimensions
480
+ */
481
+ export interface RVDimension {
482
+ /**
483
+ * Width in pixels
484
+ * Used for horizontal measurement and positioning
485
+ */
486
+ width: number;
487
+
488
+ /**
489
+ * Height in pixels
490
+ * Used for vertical measurement and positioning
491
+ */
492
+ height: number;
493
+ }
@@ -0,0 +1,167 @@
1
+ import { LayoutParams, RVDimension, RVLayoutInfo } from "./LayoutManager";
2
+ import { RVLayout } from "./LayoutManager";
3
+ import { RVLayoutManager } from "./LayoutManager";
4
+
5
+ /**
6
+ * LinearLayoutManager implementation that arranges items in a single row or column.
7
+ * Supports both horizontal and vertical layouts with dynamic item sizing.
8
+ */
9
+ export class RVLinearLayoutManagerImpl extends RVLayoutManager {
10
+ /** The bounded size (width for vertical, height for horizontal) */
11
+ private boundedSize: number;
12
+ /** Whether the bounded size has been set */
13
+ private hasSize: boolean = false;
14
+
15
+ /** Reference to the tallest item in the layout */
16
+ private tallestItem?: RVLayout;
17
+ /** Height of the tallest item */
18
+ private tallestItemHeight: number = 0;
19
+
20
+ constructor(params: LayoutParams, previousLayoutManager?: RVLayoutManager) {
21
+ super(params, previousLayoutManager);
22
+ this.boundedSize = this.horizontal
23
+ ? params.windowSize.height
24
+ : params.windowSize.width;
25
+ this.hasSize = this.boundedSize > 0;
26
+ }
27
+
28
+ /**
29
+ * Updates layout parameters and triggers recomputation if necessary.
30
+ * @param params New layout parameters
31
+ */
32
+ updateLayoutParams(params: LayoutParams): void {
33
+ const prevHorizontal = this.horizontal;
34
+ super.updateLayoutParams(params);
35
+ const oldBoundedSize = this.boundedSize;
36
+ this.boundedSize = this.horizontal
37
+ ? params.windowSize.height
38
+ : params.windowSize.width;
39
+ if (
40
+ oldBoundedSize !== this.boundedSize ||
41
+ prevHorizontal !== this.horizontal
42
+ ) {
43
+ if (this.layouts.length > 0) {
44
+ //console.log("-----> recomputeLayouts", this.horizontal);
45
+ this.recomputeLayouts(0, this.layouts.length - 1);
46
+ this.requiresRepaint = true;
47
+ }
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Processes layout information for items, updating their dimensions.
53
+ * For horizontal layouts, also normalizes heights of items.
54
+ * @param layoutInfo Array of layout information for items
55
+ * @param itemCount Total number of items in the list
56
+ */
57
+ processLayoutInfo(layoutInfo: RVLayoutInfo[], itemCount: number) {
58
+ // Update layout information
59
+ for (const info of layoutInfo) {
60
+ const { index, dimensions } = info;
61
+ const layout = this.layouts[index];
62
+ layout.width = this.horizontal ? dimensions.width : this.boundedSize;
63
+ layout.isHeightMeasured = true;
64
+ layout.isWidthMeasured = true;
65
+ layout.height = dimensions.height;
66
+ }
67
+
68
+ if (this.horizontal && !this.hasSize) {
69
+ this.normalizeLayoutHeights(layoutInfo);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Estimates layout dimensions for an item at the given index.
75
+ * @param index Index of the item to estimate layout for
76
+ */
77
+ estimateLayout(index: number) {
78
+ const layout = this.layouts[index];
79
+ layout.width = this.horizontal
80
+ ? this.getEstimatedWidth(index)
81
+ : this.boundedSize;
82
+ layout.height = this.getEstimatedHeight(index);
83
+ layout.isWidthMeasured = !this.horizontal;
84
+ layout.enforcedWidth = !this.horizontal;
85
+ }
86
+
87
+ /**
88
+ * Returns the total size of the layout area.
89
+ * @returns RVDimension containing width and height of the layout
90
+ */
91
+ getLayoutSize(): RVDimension {
92
+ if (this.layouts.length === 0) return { width: 0, height: 0 };
93
+ const lastLayout = this.layouts[this.layouts.length - 1];
94
+ return {
95
+ width: this.horizontal
96
+ ? lastLayout.x + lastLayout.width
97
+ : this.boundedSize,
98
+ height: this.horizontal
99
+ ? this.tallestItem?.height ?? 0
100
+ : lastLayout.y + lastLayout.height,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Normalizes heights of items in horizontal layout to match the tallest item.
106
+ * @param layoutInfo Array of layout information for items
107
+ */
108
+ private normalizeLayoutHeights(layoutInfo: RVLayoutInfo[]) {
109
+ let newTallestItem: RVLayout | undefined;
110
+ for (const info of layoutInfo) {
111
+ const { index } = info;
112
+ const layout = this.layouts[index];
113
+ if (
114
+ layout.height > (layout.minHeight ?? 0) &&
115
+ layout.height > (newTallestItem?.height ?? 0)
116
+ ) {
117
+ newTallestItem = layout;
118
+ }
119
+ }
120
+ if (newTallestItem && newTallestItem.height !== this.tallestItemHeight) {
121
+ let targetMinHeight = newTallestItem.height;
122
+ if (newTallestItem.height < this.tallestItemHeight) {
123
+ this.requiresRepaint = true;
124
+ targetMinHeight = 0;
125
+ }
126
+ //set minHeight for all layouts
127
+ for (const layout of this.layouts) {
128
+ if (targetMinHeight > 0) {
129
+ layout.height = newTallestItem.height;
130
+ }
131
+ layout.minHeight = targetMinHeight;
132
+ }
133
+ newTallestItem.minHeight = 0;
134
+ this.tallestItem = newTallestItem;
135
+ this.tallestItemHeight = newTallestItem.height;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Recomputes layouts for items in the given range.
141
+ * Positions items sequentially based on layout direction.
142
+ * @param startIndex Starting index of items to recompute
143
+ * @param endIndex Ending index of items to recompute
144
+ */
145
+ recomputeLayouts(startIndex: number, endIndex: number): void {
146
+ for (let i = startIndex; i <= endIndex; i++) {
147
+ const layout = this.getLayout(i);
148
+
149
+ // Set positions based on whether this is the first item or not
150
+ if (i === 0) {
151
+ layout.x = 0;
152
+ layout.y = 0;
153
+ } else {
154
+ const prevLayout = this.getLayout(i - 1);
155
+ layout.x = this.horizontal ? prevLayout.x + prevLayout.width : 0;
156
+ layout.y = this.horizontal ? 0 : prevLayout.y + prevLayout.height;
157
+ }
158
+
159
+ // Set width for vertical layouts
160
+ if (!this.horizontal) {
161
+ layout.width = this.boundedSize;
162
+ } else if (this.hasSize) {
163
+ layout.minHeight = this.boundedSize;
164
+ }
165
+ }
166
+ }
167
+ }