mtrl-addons 0.1.2 → 0.2.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 (117) hide show
  1. package/AI.md +28 -230
  2. package/CLAUDE.md +882 -0
  3. package/build.js +253 -24
  4. package/package.json +14 -4
  5. package/scripts/debug/vlist-selection.ts +121 -0
  6. package/src/components/index.ts +5 -41
  7. package/src/components/{list → vlist}/config.ts +66 -95
  8. package/src/components/vlist/constants.ts +23 -0
  9. package/src/components/vlist/features/api.ts +626 -0
  10. package/src/components/vlist/features/index.ts +10 -0
  11. package/src/components/vlist/features/selection.ts +436 -0
  12. package/src/components/vlist/features/viewport.ts +59 -0
  13. package/src/components/vlist/index.ts +17 -0
  14. package/src/components/{list → vlist}/types.ts +242 -32
  15. package/src/components/vlist/vlist.ts +92 -0
  16. package/src/core/compose/features/gestures/index.ts +227 -0
  17. package/src/core/compose/features/gestures/longpress.ts +383 -0
  18. package/src/core/compose/features/gestures/pan.ts +424 -0
  19. package/src/core/compose/features/gestures/pinch.ts +475 -0
  20. package/src/core/compose/features/gestures/rotate.ts +485 -0
  21. package/src/core/compose/features/gestures/swipe.ts +492 -0
  22. package/src/core/compose/features/gestures/tap.ts +334 -0
  23. package/src/core/compose/features/index.ts +2 -38
  24. package/src/core/compose/index.ts +13 -29
  25. package/src/core/gestures/index.ts +31 -0
  26. package/src/core/gestures/longpress.ts +68 -0
  27. package/src/core/gestures/manager.ts +418 -0
  28. package/src/core/gestures/pan.ts +48 -0
  29. package/src/core/gestures/pinch.ts +58 -0
  30. package/src/core/gestures/rotate.ts +58 -0
  31. package/src/core/gestures/swipe.ts +66 -0
  32. package/src/core/gestures/tap.ts +45 -0
  33. package/src/core/gestures/types.ts +387 -0
  34. package/src/core/gestures/utils.ts +128 -0
  35. package/src/core/index.ts +27 -151
  36. package/src/core/layout/schema.ts +153 -72
  37. package/src/core/layout/types.ts +5 -2
  38. package/src/core/viewport/constants.ts +145 -0
  39. package/src/core/viewport/features/base.ts +73 -0
  40. package/src/core/viewport/features/collection.ts +1182 -0
  41. package/src/core/viewport/features/events.ts +130 -0
  42. package/src/core/viewport/features/index.ts +20 -0
  43. package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +31 -34
  44. package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
  45. package/src/core/viewport/features/momentum.ts +269 -0
  46. package/src/core/viewport/features/placeholders.ts +335 -0
  47. package/src/core/viewport/features/rendering.ts +962 -0
  48. package/src/core/viewport/features/scrollbar.ts +434 -0
  49. package/src/core/viewport/features/scrolling.ts +634 -0
  50. package/src/core/viewport/features/utils.ts +94 -0
  51. package/src/core/viewport/features/virtual.ts +525 -0
  52. package/src/core/viewport/index.ts +31 -0
  53. package/src/core/viewport/types.ts +133 -0
  54. package/src/core/viewport/utils/speed-tracker.ts +79 -0
  55. package/src/core/viewport/viewport.ts +265 -0
  56. package/src/index.ts +0 -7
  57. package/src/styles/components/_vlist.scss +352 -0
  58. package/src/styles/index.scss +1 -1
  59. package/test/components/vlist-selection.test.ts +240 -0
  60. package/test/components/vlist.test.ts +63 -0
  61. package/test/core/collection/adapter.test.ts +161 -0
  62. package/bun.lock +0 -792
  63. package/src/components/list/api.ts +0 -314
  64. package/src/components/list/constants.ts +0 -56
  65. package/src/components/list/features/api.ts +0 -428
  66. package/src/components/list/features/index.ts +0 -31
  67. package/src/components/list/features/list-manager.ts +0 -502
  68. package/src/components/list/index.ts +0 -39
  69. package/src/components/list/list.ts +0 -234
  70. package/src/core/collection/base-collection.ts +0 -100
  71. package/src/core/collection/collection-composer.ts +0 -178
  72. package/src/core/collection/collection.ts +0 -745
  73. package/src/core/collection/constants.ts +0 -172
  74. package/src/core/collection/events.ts +0 -428
  75. package/src/core/collection/features/api/loading.ts +0 -279
  76. package/src/core/collection/features/operations/data-operations.ts +0 -147
  77. package/src/core/collection/index.ts +0 -104
  78. package/src/core/collection/state.ts +0 -497
  79. package/src/core/collection/types.ts +0 -404
  80. package/src/core/compose/features/collection.ts +0 -119
  81. package/src/core/compose/features/selection.ts +0 -213
  82. package/src/core/compose/features/styling.ts +0 -108
  83. package/src/core/list-manager/api.ts +0 -599
  84. package/src/core/list-manager/config.ts +0 -593
  85. package/src/core/list-manager/constants.ts +0 -268
  86. package/src/core/list-manager/features/api.ts +0 -58
  87. package/src/core/list-manager/features/collection/collection.ts +0 -705
  88. package/src/core/list-manager/features/collection/index.ts +0 -17
  89. package/src/core/list-manager/features/viewport/constants.ts +0 -42
  90. package/src/core/list-manager/features/viewport/index.ts +0 -16
  91. package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
  92. package/src/core/list-manager/features/viewport/rendering.ts +0 -575
  93. package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
  94. package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
  95. package/src/core/list-manager/features/viewport/template.ts +0 -220
  96. package/src/core/list-manager/features/viewport/viewport.ts +0 -654
  97. package/src/core/list-manager/features/viewport/virtual.ts +0 -309
  98. package/src/core/list-manager/index.ts +0 -279
  99. package/src/core/list-manager/list-manager.ts +0 -206
  100. package/src/core/list-manager/types.ts +0 -439
  101. package/src/core/list-manager/utils/calculations.ts +0 -290
  102. package/src/core/list-manager/utils/range-calculator.ts +0 -349
  103. package/src/core/list-manager/utils/speed-tracker.ts +0 -273
  104. package/src/styles/components/_list.scss +0 -244
  105. package/src/types/mtrl.d.ts +0 -6
  106. package/test/components/list.test.ts +0 -256
  107. package/test/core/collection/failed-ranges.test.ts +0 -270
  108. package/test/core/compose/features.test.ts +0 -183
  109. package/test/core/list-manager/features/collection.test.ts +0 -704
  110. package/test/core/list-manager/features/viewport.test.ts +0 -698
  111. package/test/core/list-manager/list-manager.test.ts +0 -593
  112. package/test/core/list-manager/utils/calculations.test.ts +0 -433
  113. package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
  114. package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
  115. package/tsconfig.build.json +0 -23
  116. /package/src/components/{list → vlist}/features.ts +0 -0
  117. /package/src/core/{compose → viewport}/features/performance.ts +0 -0
@@ -1,290 +0,0 @@
1
- /**
2
- * Viewport and range calculation utilities
3
- * Orientation-agnostic math functions for virtual scrolling
4
- */
5
-
6
- import type { ItemRange, ViewportInfo, ListManagerConfig } from "../types";
7
- import { LIST_MANAGER_CONSTANTS } from "../constants";
8
-
9
- // Removed calculateVisibleRange - using unified index-based approach in virtual.ts
10
-
11
- /**
12
- * Calculate total virtual size based on items and estimated size
13
- */
14
- export function calculateTotalVirtualSize(
15
- totalItems: number,
16
- estimatedItemSize: number,
17
- measuredSizes?: Map<number, number>
18
- ): number {
19
- if (!measuredSizes || measuredSizes.size === 0) {
20
- return totalItems * estimatedItemSize;
21
- }
22
-
23
- // Use measured sizes where available, estimated for the rest
24
- let totalSize = 0;
25
- for (let i = 0; i < totalItems; i++) {
26
- totalSize += measuredSizes.get(i) || estimatedItemSize;
27
- }
28
-
29
- return totalSize;
30
- }
31
-
32
- /**
33
- * Calculate container position for virtual scrolling
34
- * Returns the transform value for the container
35
- */
36
- export function calculateContainerPosition(
37
- virtualScrollPosition: number,
38
- visibleRange: ItemRange,
39
- measuredSizes?: Map<number, number>,
40
- estimatedItemSize: number = LIST_MANAGER_CONSTANTS.VIRTUAL_SCROLL
41
- .DEFAULT_ITEM_SIZE
42
- ): number {
43
- let offset = 0;
44
-
45
- // Calculate offset based on measured sizes if available
46
- if (measuredSizes && measuredSizes.size > 0) {
47
- for (let i = 0; i < visibleRange.start; i++) {
48
- offset += measuredSizes.get(i) || estimatedItemSize;
49
- }
50
- } else {
51
- // Use estimated size for all items before visible range
52
- offset = visibleRange.start * estimatedItemSize;
53
- }
54
-
55
- return offset;
56
- }
57
-
58
- /**
59
- * Calculate scroll position for specific index
60
- */
61
- export function calculateScrollPositionForIndex(
62
- targetIndex: number,
63
- estimatedItemSize: number,
64
- measuredSizes?: Map<number, number>,
65
- alignment: "start" | "center" | "end" = "start",
66
- containerSize: number = 0
67
- ): number {
68
- let scrollPosition = 0;
69
-
70
- // Calculate position based on measured sizes if available
71
- if (measuredSizes && measuredSizes.size > 0) {
72
- for (let i = 0; i < targetIndex; i++) {
73
- scrollPosition += measuredSizes.get(i) || estimatedItemSize;
74
- }
75
- } else {
76
- scrollPosition = targetIndex * estimatedItemSize;
77
- }
78
-
79
- // Apply alignment
80
- if (alignment === "center") {
81
- const itemSize = measuredSizes?.get(targetIndex) || estimatedItemSize;
82
- scrollPosition -= (containerSize - itemSize) / 2;
83
- } else if (alignment === "end") {
84
- const itemSize = measuredSizes?.get(targetIndex) || estimatedItemSize;
85
- scrollPosition -= containerSize - itemSize;
86
- }
87
-
88
- return Math.max(0, scrollPosition);
89
- }
90
-
91
- /**
92
- * Calculate page-based scroll position
93
- */
94
- export function calculateScrollPositionForPage(
95
- targetPage: number,
96
- pageSize: number,
97
- estimatedItemSize: number,
98
- measuredSizes?: Map<number, number>,
99
- alignment: "start" | "center" | "end" = "start",
100
- containerSize: number = 0
101
- ): number {
102
- const targetIndex = (targetPage - 1) * pageSize;
103
- return calculateScrollPositionForIndex(
104
- targetIndex,
105
- estimatedItemSize,
106
- measuredSizes,
107
- alignment,
108
- containerSize
109
- );
110
- }
111
-
112
- /**
113
- * Calculate scrollbar thumb position and size
114
- */
115
- export function calculateScrollbarMetrics(
116
- virtualScrollPosition: number,
117
- totalVirtualSize: number,
118
- containerSize: number,
119
- scrollbarTrackSize: number
120
- ): { thumbPosition: number; thumbSize: number } {
121
- // Calculate thumb size based on container to total ratio
122
- const thumbSize = Math.max(
123
- LIST_MANAGER_CONSTANTS.SCROLLBAR.THUMB_MIN_SIZE,
124
- (containerSize / totalVirtualSize) * scrollbarTrackSize
125
- );
126
-
127
- // Calculate thumb position based on scroll position
128
- const scrollRatio =
129
- virtualScrollPosition / (totalVirtualSize - containerSize);
130
- const thumbPosition = scrollRatio * (scrollbarTrackSize - thumbSize);
131
-
132
- return {
133
- thumbPosition: Math.max(0, thumbPosition),
134
- thumbSize: Math.min(thumbSize, scrollbarTrackSize),
135
- };
136
- }
137
-
138
- // Removed calculateViewportInfo - viewport.ts now uses virtualManager directly
139
-
140
- /**
141
- * Calculate optimal overscan based on viewport capacity
142
- * Uses a percentage-based approach that scales with viewport size
143
- */
144
- export function calculateOptimalOverscan(
145
- containerSize: number,
146
- estimatedItemSize: number,
147
- scrollSpeed?: number
148
- ): number {
149
- // Calculate how many items fit in the viewport
150
- const viewportCapacity = Math.ceil(containerSize / estimatedItemSize);
151
-
152
- // Base overscan is 15% of viewport capacity
153
- let overscan = Math.ceil(viewportCapacity * 0.15);
154
-
155
- // Adjust based on scroll speed if provided
156
- if (scrollSpeed !== undefined) {
157
- if (scrollSpeed > 1000) {
158
- // Fast scrolling: increase overscan to 25% of viewport
159
- overscan = Math.ceil(viewportCapacity * 0.25);
160
- } else if (scrollSpeed < 100) {
161
- // Slow scrolling: reduce overscan to 10% of viewport
162
- overscan = Math.ceil(viewportCapacity * 0.1);
163
- }
164
- }
165
-
166
- // Apply min/max constraints
167
- const MIN_OVERSCAN = 1;
168
- const MAX_OVERSCAN = Math.min(10, Math.ceil(viewportCapacity * 0.3)); // Max 30% of viewport
169
-
170
- return Math.max(MIN_OVERSCAN, Math.min(MAX_OVERSCAN, overscan));
171
- }
172
-
173
- /**
174
- * Calculate range size based on viewport capacity
175
- */
176
- export function calculateInitialRangeSize(
177
- containerSize: number,
178
- estimatedItemSize: number,
179
- viewportMultiplier: number = LIST_MANAGER_CONSTANTS.INITIAL_LOAD
180
- .VIEWPORT_MULTIPLIER
181
- ): number {
182
- const viewportCapacity = Math.ceil(containerSize / estimatedItemSize);
183
- const rangeSize = Math.ceil(viewportCapacity * viewportMultiplier);
184
-
185
- return Math.max(
186
- LIST_MANAGER_CONSTANTS.INITIAL_LOAD.MIN_ITEMS,
187
- Math.min(rangeSize, LIST_MANAGER_CONSTANTS.INITIAL_LOAD.MAX_ITEMS)
188
- );
189
- }
190
-
191
- /**
192
- * Calculate missing ranges that need to be loaded
193
- */
194
- export function calculateMissingRanges(
195
- visibleRange: ItemRange,
196
- loadedRanges: Set<number>,
197
- rangeSize: number
198
- ): ItemRange[] {
199
- const missingRanges: ItemRange[] = [];
200
-
201
- const startRange = Math.floor(visibleRange.start / rangeSize);
202
- const endRange = Math.floor(visibleRange.end / rangeSize);
203
-
204
- for (let range = startRange; range <= endRange; range++) {
205
- if (!loadedRanges.has(range)) {
206
- const rangeStart = range * rangeSize;
207
- const rangeEnd = Math.min(rangeStart + rangeSize - 1, visibleRange.end);
208
-
209
- missingRanges.push({
210
- start: rangeStart,
211
- end: rangeEnd,
212
- });
213
- }
214
- }
215
-
216
- return missingRanges;
217
- }
218
-
219
- /**
220
- * Calculate buffer ranges for preloading
221
- */
222
- export function calculateBufferRanges(
223
- visibleRange: ItemRange,
224
- loadedRanges: Set<number>,
225
- rangeSize: number,
226
- prefetchRanges: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.PREFETCH_RANGES
227
- ): ItemRange[] {
228
- const bufferRanges: ItemRange[] = [];
229
-
230
- const startRange = Math.floor(visibleRange.start / rangeSize);
231
- const endRange = Math.floor(visibleRange.end / rangeSize);
232
-
233
- // Add ranges before visible range
234
- for (let i = 1; i <= prefetchRanges; i++) {
235
- const range = startRange - i;
236
- if (range >= 0 && !loadedRanges.has(range)) {
237
- const rangeStart = range * rangeSize;
238
- const rangeEnd = rangeStart + rangeSize - 1;
239
-
240
- bufferRanges.push({
241
- start: rangeStart,
242
- end: rangeEnd,
243
- });
244
- }
245
- }
246
-
247
- // Add ranges after visible range
248
- for (let i = 1; i <= prefetchRanges; i++) {
249
- const range = endRange + i;
250
- if (!loadedRanges.has(range)) {
251
- const rangeStart = range * rangeSize;
252
- const rangeEnd = rangeStart + rangeSize - 1;
253
-
254
- bufferRanges.push({
255
- start: rangeStart,
256
- end: rangeEnd,
257
- });
258
- }
259
- }
260
-
261
- return bufferRanges;
262
- }
263
-
264
- /**
265
- * Clamp value within boundaries
266
- */
267
- export function clamp(value: number, min: number, max: number): number {
268
- return Math.max(min, Math.min(max, value));
269
- }
270
-
271
- /**
272
- * Apply boundary resistance to scroll position
273
- */
274
- export function applyBoundaryResistance(
275
- scrollPosition: number,
276
- totalVirtualSize: number,
277
- containerSize: number,
278
- resistance: number = LIST_MANAGER_CONSTANTS.BOUNDARIES.BOUNDARY_RESISTANCE
279
- ): number {
280
- const maxScroll = Math.max(0, totalVirtualSize - containerSize);
281
-
282
- // Apply resistance at boundaries
283
- if (scrollPosition < 0) {
284
- return scrollPosition * resistance;
285
- } else if (scrollPosition > maxScroll) {
286
- return maxScroll + (scrollPosition - maxScroll) * resistance;
287
- }
288
-
289
- return scrollPosition;
290
- }
@@ -1,349 +0,0 @@
1
- /**
2
- * Range calculation utility for pagination and data loading
3
- * Handles range-based pagination strategies and loading logic
4
- */
5
-
6
- import type { ItemRange, RangeCalculationResult } from "../types";
7
- import { LIST_MANAGER_CONSTANTS } from "../constants";
8
-
9
- /**
10
- * Calculate range index from item index
11
- */
12
- export function calculateRangeIndex(
13
- itemIndex: number,
14
- rangeSize: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.DEFAULT_RANGE_SIZE
15
- ): number {
16
- return Math.floor(itemIndex / rangeSize);
17
- }
18
-
19
- /**
20
- * Calculate range bounds from range index
21
- */
22
- export function calculateRangeBounds(
23
- rangeIndex: number,
24
- rangeSize: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.DEFAULT_RANGE_SIZE
25
- ): ItemRange {
26
- const start = rangeIndex * rangeSize;
27
- const end = start + rangeSize - 1;
28
-
29
- return { start, end };
30
- }
31
-
32
- /**
33
- * Calculate all required ranges for a visible range
34
- */
35
- export function calculateRequiredRanges(
36
- visibleRange: ItemRange,
37
- rangeSize: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.DEFAULT_RANGE_SIZE
38
- ): number[] {
39
- const ranges: number[] = [];
40
-
41
- const startRange = calculateRangeIndex(visibleRange.start, rangeSize);
42
- const endRange = calculateRangeIndex(visibleRange.end, rangeSize);
43
-
44
- for (let range = startRange; range <= endRange; range++) {
45
- ranges.push(range);
46
- }
47
-
48
- return ranges;
49
- }
50
-
51
- /**
52
- * Calculate missing ranges that need to be loaded
53
- */
54
- export function calculateMissingRanges(
55
- visibleRange: ItemRange,
56
- loadedRanges: Set<number>,
57
- rangeSize: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.DEFAULT_RANGE_SIZE
58
- ): ItemRange[] {
59
- const requiredRanges = calculateRequiredRanges(visibleRange, rangeSize);
60
- const missingRanges: ItemRange[] = [];
61
-
62
- for (const rangeIndex of requiredRanges) {
63
- if (!loadedRanges.has(rangeIndex)) {
64
- missingRanges.push(calculateRangeBounds(rangeIndex, rangeSize));
65
- }
66
- }
67
-
68
- return missingRanges;
69
- }
70
-
71
- /**
72
- * Calculate prefetch ranges based on scroll direction and speed
73
- */
74
- export function calculatePrefetchRanges(
75
- visibleRange: ItemRange,
76
- loadedRanges: Set<number>,
77
- direction: "forward" | "backward",
78
- prefetchCount: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.PREFETCH_RANGES,
79
- rangeSize: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.DEFAULT_RANGE_SIZE
80
- ): ItemRange[] {
81
- const prefetchRanges: ItemRange[] = [];
82
-
83
- const startRange = calculateRangeIndex(visibleRange.start, rangeSize);
84
- const endRange = calculateRangeIndex(visibleRange.end, rangeSize);
85
-
86
- if (direction === "forward") {
87
- // Prefetch ranges after visible range
88
- for (let i = 1; i <= prefetchCount; i++) {
89
- const rangeIndex = endRange + i;
90
- if (!loadedRanges.has(rangeIndex)) {
91
- prefetchRanges.push(calculateRangeBounds(rangeIndex, rangeSize));
92
- }
93
- }
94
- } else {
95
- // Prefetch ranges before visible range
96
- for (let i = 1; i <= prefetchCount; i++) {
97
- const rangeIndex = startRange - i;
98
- if (rangeIndex >= 0 && !loadedRanges.has(rangeIndex)) {
99
- prefetchRanges.push(calculateRangeBounds(rangeIndex, rangeSize));
100
- }
101
- }
102
- }
103
-
104
- return prefetchRanges;
105
- }
106
-
107
- /**
108
- * Calculate comprehensive range result
109
- */
110
- export function calculateRangeResult(
111
- visibleRange: ItemRange,
112
- loadedRanges: Set<number>,
113
- direction: "forward" | "backward",
114
- prefetchCount: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.PREFETCH_RANGES,
115
- rangeSize: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.DEFAULT_RANGE_SIZE
116
- ): RangeCalculationResult {
117
- const missingRanges = calculateMissingRanges(
118
- visibleRange,
119
- loadedRanges,
120
- rangeSize
121
- );
122
- const bufferRanges = calculatePrefetchRanges(
123
- visibleRange,
124
- loadedRanges,
125
- direction,
126
- prefetchCount,
127
- rangeSize
128
- );
129
-
130
- return {
131
- visibleRange,
132
- loadedRanges,
133
- missingRanges,
134
- bufferRanges,
135
- };
136
- }
137
-
138
- /**
139
- * Convert range to pagination parameters (page strategy)
140
- */
141
- export function rangeToPaginationParams(
142
- range: ItemRange,
143
- strategy: "page" | "offset" | "cursor" = "page"
144
- ): { page?: number; offset?: number; limit: number } {
145
- const limit = range.end - range.start + 1;
146
-
147
- switch (strategy) {
148
- case "page":
149
- return {
150
- page: Math.floor(range.start / limit) + 1,
151
- limit,
152
- };
153
-
154
- case "offset":
155
- return {
156
- offset: range.start,
157
- limit,
158
- };
159
-
160
- case "cursor":
161
- return {
162
- limit,
163
- // cursor would be handled separately based on last item
164
- };
165
-
166
- default:
167
- return { page: 1, limit };
168
- }
169
- }
170
-
171
- /**
172
- * Convert multiple ranges to batch pagination parameters
173
- */
174
- export function rangesToBatchParams(
175
- ranges: ItemRange[],
176
- strategy: "page" | "offset" | "cursor" = "page"
177
- ): Array<{ page?: number; offset?: number; limit: number }> {
178
- return ranges.map((range) => rangeToPaginationParams(range, strategy));
179
- }
180
-
181
- /**
182
- * Calculate optimal range size based on viewport
183
- */
184
- export function calculateOptimalRangeSize(
185
- containerSize: number,
186
- estimatedItemSize: number,
187
- targetRanges: number = 3
188
- ): number {
189
- const viewportCapacity = Math.ceil(containerSize / estimatedItemSize);
190
- const optimalRangeSize = Math.ceil(viewportCapacity / targetRanges);
191
-
192
- return Math.max(
193
- LIST_MANAGER_CONSTANTS.RANGE_LOADING.DEFAULT_RANGE_SIZE,
194
- optimalRangeSize
195
- );
196
- }
197
-
198
- /**
199
- * Check if range is within viewport bounds
200
- */
201
- export function isRangeInViewport(
202
- range: ItemRange,
203
- visibleRange: ItemRange,
204
- buffer: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.BUFFER_SIZE
205
- ): boolean {
206
- const bufferedStart = visibleRange.start - buffer;
207
- const bufferedEnd = visibleRange.end + buffer;
208
-
209
- return !(range.end < bufferedStart || range.start > bufferedEnd);
210
- }
211
-
212
- /**
213
- * Calculate range priority based on distance from visible range
214
- */
215
- export function calculateRangePriority(
216
- range: ItemRange,
217
- visibleRange: ItemRange
218
- ): number {
219
- const rangeCenter = (range.start + range.end) / 2;
220
- const visibleCenter = (visibleRange.start + visibleRange.end) / 2;
221
-
222
- // Priority decreases with distance (lower number = higher priority)
223
- return Math.abs(rangeCenter - visibleCenter);
224
- }
225
-
226
- /**
227
- * Sort ranges by priority (closest to visible range first)
228
- */
229
- export function sortRangesByPriority(
230
- ranges: ItemRange[],
231
- visibleRange: ItemRange
232
- ): ItemRange[] {
233
- return ranges.sort((a, b) => {
234
- const priorityA = calculateRangePriority(a, visibleRange);
235
- const priorityB = calculateRangePriority(b, visibleRange);
236
- return priorityA - priorityB;
237
- });
238
- }
239
-
240
- /**
241
- * Merge adjacent ranges to reduce request count
242
- */
243
- export function mergeAdjacentRanges(
244
- ranges: ItemRange[],
245
- maxGap: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.DEFAULT_RANGE_SIZE
246
- ): ItemRange[] {
247
- if (ranges.length <= 1) return ranges;
248
-
249
- // Sort ranges by start position
250
- const sortedRanges = [...ranges].sort((a, b) => a.start - b.start);
251
- const mergedRanges: ItemRange[] = [];
252
-
253
- let currentRange = sortedRanges[0];
254
-
255
- for (let i = 1; i < sortedRanges.length; i++) {
256
- const nextRange = sortedRanges[i];
257
-
258
- // Check if ranges are adjacent or close enough to merge
259
- if (nextRange.start - currentRange.end <= maxGap) {
260
- // Merge ranges
261
- currentRange = {
262
- start: currentRange.start,
263
- end: Math.max(currentRange.end, nextRange.end),
264
- };
265
- } else {
266
- // Ranges are too far apart, add current and start new
267
- mergedRanges.push(currentRange);
268
- currentRange = nextRange;
269
- }
270
- }
271
-
272
- // Add the last range
273
- mergedRanges.push(currentRange);
274
-
275
- return mergedRanges;
276
- }
277
-
278
- /**
279
- * Calculate range cleanup candidates (far from visible range)
280
- */
281
- export function calculateRangeCleanupCandidates(
282
- loadedRanges: Set<number>,
283
- visibleRange: ItemRange,
284
- maxDistance: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING
285
- .DEFAULT_RANGE_SIZE * 5,
286
- rangeSize: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.DEFAULT_RANGE_SIZE
287
- ): number[] {
288
- const cleanupCandidates: number[] = [];
289
- const visibleCenter = (visibleRange.start + visibleRange.end) / 2;
290
-
291
- for (const rangeIndex of loadedRanges) {
292
- const range = calculateRangeBounds(rangeIndex, rangeSize);
293
- const rangeCenter = (range.start + range.end) / 2;
294
- const distance = Math.abs(rangeCenter - visibleCenter);
295
-
296
- if (distance > maxDistance) {
297
- cleanupCandidates.push(rangeIndex);
298
- }
299
- }
300
-
301
- return cleanupCandidates;
302
- }
303
-
304
- /**
305
- * Calculate range loading order with priority
306
- */
307
- export function calculateRangeLoadingOrder(
308
- missingRanges: ItemRange[],
309
- prefetchRanges: ItemRange[],
310
- visibleRange: ItemRange
311
- ): { priority: "high" | "medium" | "low"; ranges: ItemRange[] }[] {
312
- const highPriority = sortRangesByPriority(missingRanges, visibleRange);
313
- const lowPriority = sortRangesByPriority(prefetchRanges, visibleRange);
314
-
315
- return [
316
- { priority: "high", ranges: highPriority },
317
- { priority: "low", ranges: lowPriority },
318
- ];
319
- }
320
-
321
- /**
322
- * Get range debug information
323
- */
324
- export function getRangeDebugInfo(
325
- visibleRange: ItemRange,
326
- loadedRanges: Set<number>,
327
- rangeSize: number = LIST_MANAGER_CONSTANTS.RANGE_LOADING.DEFAULT_RANGE_SIZE
328
- ): {
329
- visibleRange: string;
330
- requiredRanges: number[];
331
- loadedRanges: number[];
332
- missingRanges: ItemRange[];
333
- rangeSize: number;
334
- } {
335
- const requiredRanges = calculateRequiredRanges(visibleRange, rangeSize);
336
- const missingRanges = calculateMissingRanges(
337
- visibleRange,
338
- loadedRanges,
339
- rangeSize
340
- );
341
-
342
- return {
343
- visibleRange: `${visibleRange.start}-${visibleRange.end}`,
344
- requiredRanges,
345
- loadedRanges: Array.from(loadedRanges).sort((a, b) => a - b),
346
- missingRanges,
347
- rangeSize,
348
- };
349
- }