mtrl-addons 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/build.js +139 -86
  2. package/package.json +13 -4
  3. package/scripts/debug/vlist-selection.ts +121 -0
  4. package/src/components/index.ts +5 -41
  5. package/src/components/{list → vlist}/config.ts +66 -95
  6. package/src/components/vlist/constants.ts +23 -0
  7. package/src/components/vlist/features/api.ts +322 -0
  8. package/src/components/vlist/features/index.ts +10 -0
  9. package/src/components/vlist/features/selection.ts +444 -0
  10. package/src/components/vlist/features/viewport.ts +65 -0
  11. package/src/components/vlist/index.ts +16 -0
  12. package/src/components/{list → vlist}/types.ts +104 -26
  13. package/src/components/vlist/vlist.ts +92 -0
  14. package/src/core/compose/features/gestures/index.ts +227 -0
  15. package/src/core/compose/features/gestures/longpress.ts +383 -0
  16. package/src/core/compose/features/gestures/pan.ts +424 -0
  17. package/src/core/compose/features/gestures/pinch.ts +475 -0
  18. package/src/core/compose/features/gestures/rotate.ts +485 -0
  19. package/src/core/compose/features/gestures/swipe.ts +492 -0
  20. package/src/core/compose/features/gestures/tap.ts +334 -0
  21. package/src/core/compose/features/index.ts +2 -38
  22. package/src/core/compose/index.ts +13 -29
  23. package/src/core/gestures/index.ts +31 -0
  24. package/src/core/gestures/longpress.ts +68 -0
  25. package/src/core/gestures/manager.ts +418 -0
  26. package/src/core/gestures/pan.ts +48 -0
  27. package/src/core/gestures/pinch.ts +58 -0
  28. package/src/core/gestures/rotate.ts +58 -0
  29. package/src/core/gestures/swipe.ts +66 -0
  30. package/src/core/gestures/tap.ts +45 -0
  31. package/src/core/gestures/types.ts +387 -0
  32. package/src/core/gestures/utils.ts +128 -0
  33. package/src/core/index.ts +27 -151
  34. package/src/core/layout/schema.ts +73 -35
  35. package/src/core/layout/types.ts +5 -2
  36. package/src/core/viewport/constants.ts +140 -0
  37. package/src/core/viewport/features/base.ts +73 -0
  38. package/src/core/viewport/features/collection.ts +882 -0
  39. package/src/core/viewport/features/events.ts +130 -0
  40. package/src/core/viewport/features/index.ts +20 -0
  41. package/src/core/{list-manager/features/viewport → viewport/features}/item-size.ts +27 -30
  42. package/src/core/{list-manager/features/viewport → viewport/features}/loading.ts +4 -4
  43. package/src/core/viewport/features/momentum.ts +260 -0
  44. package/src/core/viewport/features/placeholders.ts +335 -0
  45. package/src/core/viewport/features/rendering.ts +568 -0
  46. package/src/core/viewport/features/scrollbar.ts +434 -0
  47. package/src/core/viewport/features/scrolling.ts +618 -0
  48. package/src/core/viewport/features/utils.ts +88 -0
  49. package/src/core/viewport/features/virtual.ts +384 -0
  50. package/src/core/viewport/index.ts +31 -0
  51. package/src/core/viewport/types.ts +133 -0
  52. package/src/core/viewport/utils/speed-tracker.ts +79 -0
  53. package/src/core/viewport/viewport.ts +246 -0
  54. package/src/index.ts +0 -7
  55. package/src/styles/components/_vlist.scss +331 -0
  56. package/src/styles/index.scss +1 -1
  57. package/test/components/vlist-selection.test.ts +240 -0
  58. package/test/components/vlist.test.ts +63 -0
  59. package/test/core/collection/adapter.test.ts +161 -0
  60. package/bun.lock +0 -792
  61. package/src/components/list/api.ts +0 -314
  62. package/src/components/list/constants.ts +0 -56
  63. package/src/components/list/features/api.ts +0 -428
  64. package/src/components/list/features/index.ts +0 -31
  65. package/src/components/list/features/list-manager.ts +0 -502
  66. package/src/components/list/index.ts +0 -39
  67. package/src/components/list/list.ts +0 -234
  68. package/src/core/collection/base-collection.ts +0 -100
  69. package/src/core/collection/collection-composer.ts +0 -178
  70. package/src/core/collection/collection.ts +0 -745
  71. package/src/core/collection/constants.ts +0 -172
  72. package/src/core/collection/events.ts +0 -428
  73. package/src/core/collection/features/api/loading.ts +0 -279
  74. package/src/core/collection/features/operations/data-operations.ts +0 -147
  75. package/src/core/collection/index.ts +0 -104
  76. package/src/core/collection/state.ts +0 -497
  77. package/src/core/collection/types.ts +0 -404
  78. package/src/core/compose/features/collection.ts +0 -119
  79. package/src/core/compose/features/selection.ts +0 -213
  80. package/src/core/compose/features/styling.ts +0 -108
  81. package/src/core/list-manager/api.ts +0 -599
  82. package/src/core/list-manager/config.ts +0 -593
  83. package/src/core/list-manager/constants.ts +0 -268
  84. package/src/core/list-manager/features/api.ts +0 -58
  85. package/src/core/list-manager/features/collection/collection.ts +0 -705
  86. package/src/core/list-manager/features/collection/index.ts +0 -17
  87. package/src/core/list-manager/features/viewport/constants.ts +0 -42
  88. package/src/core/list-manager/features/viewport/index.ts +0 -16
  89. package/src/core/list-manager/features/viewport/placeholders.ts +0 -281
  90. package/src/core/list-manager/features/viewport/rendering.ts +0 -575
  91. package/src/core/list-manager/features/viewport/scrollbar.ts +0 -495
  92. package/src/core/list-manager/features/viewport/scrolling.ts +0 -795
  93. package/src/core/list-manager/features/viewport/template.ts +0 -220
  94. package/src/core/list-manager/features/viewport/viewport.ts +0 -654
  95. package/src/core/list-manager/features/viewport/virtual.ts +0 -309
  96. package/src/core/list-manager/index.ts +0 -279
  97. package/src/core/list-manager/list-manager.ts +0 -206
  98. package/src/core/list-manager/types.ts +0 -439
  99. package/src/core/list-manager/utils/calculations.ts +0 -290
  100. package/src/core/list-manager/utils/range-calculator.ts +0 -349
  101. package/src/core/list-manager/utils/speed-tracker.ts +0 -273
  102. package/src/styles/components/_list.scss +0 -244
  103. package/src/types/mtrl.d.ts +0 -6
  104. package/test/components/list.test.ts +0 -256
  105. package/test/core/collection/failed-ranges.test.ts +0 -270
  106. package/test/core/compose/features.test.ts +0 -183
  107. package/test/core/list-manager/features/collection.test.ts +0 -704
  108. package/test/core/list-manager/features/viewport.test.ts +0 -698
  109. package/test/core/list-manager/list-manager.test.ts +0 -593
  110. package/test/core/list-manager/utils/calculations.test.ts +0 -433
  111. package/test/core/list-manager/utils/range-calculator.test.ts +0 -569
  112. package/test/core/list-manager/utils/speed-tracker.test.ts +0 -530
  113. package/tsconfig.build.json +0 -23
  114. /package/src/components/{list → vlist}/features.ts +0 -0
  115. /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
- }