@pdanpdan/virtual-scroll 0.3.0 → 0.5.0
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.
- package/README.md +268 -275
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1497 -192
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2219 -896
- package/dist/index.mjs.map +1 -1
- package/dist/virtual-scroll.css +1 -2
- package/package.json +5 -1
- package/src/components/VirtualScroll.test.ts +1979 -627
- package/src/components/VirtualScroll.vue +951 -349
- package/src/components/VirtualScrollbar.test.ts +174 -0
- package/src/components/VirtualScrollbar.vue +102 -0
- package/src/composables/useVirtualScroll.test.ts +1160 -1521
- package/src/composables/useVirtualScroll.ts +1135 -791
- package/src/composables/useVirtualScrollbar.test.ts +526 -0
- package/src/composables/useVirtualScrollbar.ts +239 -0
- package/src/index.ts +4 -0
- package/src/types.ts +816 -0
- package/src/utils/fenwick-tree.test.ts +39 -39
- package/src/utils/fenwick-tree.ts +38 -18
- package/src/utils/scroll.test.ts +174 -0
- package/src/utils/scroll.ts +50 -13
- package/src/utils/virtual-scroll-logic.test.ts +2850 -0
- package/src/utils/virtual-scroll-logic.ts +901 -0
|
@@ -0,0 +1,901 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ColumnRangeParams,
|
|
3
|
+
ItemPositionParams,
|
|
4
|
+
ItemStyleParams,
|
|
5
|
+
RangeParams,
|
|
6
|
+
ScrollAlignment,
|
|
7
|
+
ScrollAlignmentOptions,
|
|
8
|
+
ScrollTargetParams,
|
|
9
|
+
ScrollTargetResult,
|
|
10
|
+
ScrollToIndexOptions,
|
|
11
|
+
StickyParams,
|
|
12
|
+
TotalSizeParams,
|
|
13
|
+
} from '../types';
|
|
14
|
+
|
|
15
|
+
import { BROWSER_MAX_SIZE, isScrollToIndexOptions } from './scroll';
|
|
16
|
+
|
|
17
|
+
// --- Internal Helper Types ---
|
|
18
|
+
|
|
19
|
+
interface GenericRangeParams {
|
|
20
|
+
scrollPos: number;
|
|
21
|
+
containerSize: number;
|
|
22
|
+
count: number;
|
|
23
|
+
bufferBefore: number;
|
|
24
|
+
bufferAfter: number;
|
|
25
|
+
gap: number;
|
|
26
|
+
fixedSize: number | null;
|
|
27
|
+
findLowerBound: (offset: number) => number;
|
|
28
|
+
query: (index: number) => number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface AxisAlignmentParams {
|
|
32
|
+
align: ScrollAlignment;
|
|
33
|
+
targetPos: number;
|
|
34
|
+
itemSize: number;
|
|
35
|
+
scrollPos: number;
|
|
36
|
+
viewSize: number;
|
|
37
|
+
stickyOffsetStart: number;
|
|
38
|
+
stickyOffsetEnd: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// --- Internal Helpers ---
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generic range calculation for a single axis (row or column).
|
|
45
|
+
*
|
|
46
|
+
* @param params - Range parameters.
|
|
47
|
+
* @param params.scrollPos - Virtual scroll position.
|
|
48
|
+
* @param params.containerSize - Usable viewport size.
|
|
49
|
+
* @param params.count - Total item count.
|
|
50
|
+
* @param params.bufferBefore - Buffer items before.
|
|
51
|
+
* @param params.bufferAfter - Buffer items after.
|
|
52
|
+
* @param params.gap - Item gap.
|
|
53
|
+
* @param params.fixedSize - Fixed item size.
|
|
54
|
+
* @param params.findLowerBound - Binary search for index.
|
|
55
|
+
* @param params.query - Prefix sum for index.
|
|
56
|
+
* @returns Start and end indices.
|
|
57
|
+
*/
|
|
58
|
+
function calculateGenericRange({
|
|
59
|
+
scrollPos,
|
|
60
|
+
containerSize,
|
|
61
|
+
count,
|
|
62
|
+
bufferBefore,
|
|
63
|
+
bufferAfter,
|
|
64
|
+
gap,
|
|
65
|
+
fixedSize,
|
|
66
|
+
findLowerBound,
|
|
67
|
+
query,
|
|
68
|
+
}: GenericRangeParams) {
|
|
69
|
+
let start = 0;
|
|
70
|
+
let end = count;
|
|
71
|
+
const endOffset = scrollPos + containerSize;
|
|
72
|
+
|
|
73
|
+
if (fixedSize !== null) {
|
|
74
|
+
const step = fixedSize + gap;
|
|
75
|
+
start = Math.floor(scrollPos / step);
|
|
76
|
+
end = Math.ceil(endOffset / step);
|
|
77
|
+
} else {
|
|
78
|
+
start = findLowerBound(scrollPos);
|
|
79
|
+
end = findLowerBound(endOffset);
|
|
80
|
+
if (end < count && query(end) < endOffset) {
|
|
81
|
+
end++;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
start: Math.max(0, start - bufferBefore),
|
|
87
|
+
end: Math.min(count, end + bufferAfter),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Binary search for the next sticky index after the current index.
|
|
93
|
+
*
|
|
94
|
+
* @param stickyIndices - Sorted array of sticky indices.
|
|
95
|
+
* @param index - Current index.
|
|
96
|
+
* @returns Next sticky index or undefined.
|
|
97
|
+
*/
|
|
98
|
+
function findNextStickyIndex(stickyIndices: number[], index: number): number | undefined {
|
|
99
|
+
let low = 0;
|
|
100
|
+
let high = stickyIndices.length - 1;
|
|
101
|
+
let nextStickyIdx: number | undefined;
|
|
102
|
+
|
|
103
|
+
while (low <= high) {
|
|
104
|
+
const mid = (low + high) >>> 1;
|
|
105
|
+
if (stickyIndices[ mid ]! > index) {
|
|
106
|
+
nextStickyIdx = stickyIndices[ mid ];
|
|
107
|
+
high = mid - 1;
|
|
108
|
+
} else {
|
|
109
|
+
low = mid + 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return nextStickyIdx;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Binary search for the previous sticky index before the current index.
|
|
117
|
+
*
|
|
118
|
+
* @param stickyIndices - Sorted array of sticky indices.
|
|
119
|
+
* @param index - Current index.
|
|
120
|
+
* @returns Previous sticky index or undefined.
|
|
121
|
+
*/
|
|
122
|
+
export function findPrevStickyIndex(stickyIndices: number[], index: number): number | undefined {
|
|
123
|
+
let low = 0;
|
|
124
|
+
let high = stickyIndices.length - 1;
|
|
125
|
+
let prevStickyIdx: number | undefined;
|
|
126
|
+
|
|
127
|
+
while (low <= high) {
|
|
128
|
+
const mid = (low + high) >>> 1;
|
|
129
|
+
if (stickyIndices[ mid ]! < index) {
|
|
130
|
+
prevStickyIdx = stickyIndices[ mid ];
|
|
131
|
+
low = mid + 1;
|
|
132
|
+
} else {
|
|
133
|
+
high = mid - 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return prevStickyIdx;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generic alignment calculation for a single axis.
|
|
141
|
+
*
|
|
142
|
+
* @param params - Alignment parameters.
|
|
143
|
+
* @param params.align - Desired alignment.
|
|
144
|
+
* @param params.targetPos - Virtual item position.
|
|
145
|
+
* @param params.itemSize - Virtual item size.
|
|
146
|
+
* @param params.scrollPos - Virtual scroll position.
|
|
147
|
+
* @param params.viewSize - Full viewport size.
|
|
148
|
+
* @param params.stickyOffsetStart - Dynamic sticky offset at start.
|
|
149
|
+
* @param params.stickyOffsetEnd - Sticky offset at end.
|
|
150
|
+
* @returns Target scroll position and effective alignment.
|
|
151
|
+
*/
|
|
152
|
+
function calculateAxisAlignment({
|
|
153
|
+
align,
|
|
154
|
+
targetPos,
|
|
155
|
+
itemSize,
|
|
156
|
+
scrollPos,
|
|
157
|
+
viewSize,
|
|
158
|
+
stickyOffsetStart,
|
|
159
|
+
stickyOffsetEnd,
|
|
160
|
+
}: AxisAlignmentParams) {
|
|
161
|
+
const targetStart = targetPos - stickyOffsetStart;
|
|
162
|
+
const targetEnd = targetPos - (viewSize - stickyOffsetEnd - itemSize);
|
|
163
|
+
|
|
164
|
+
if (align === 'start') {
|
|
165
|
+
return { target: targetStart, effectiveAlign: 'start' as const };
|
|
166
|
+
}
|
|
167
|
+
if (align === 'center') {
|
|
168
|
+
return {
|
|
169
|
+
target: targetPos - stickyOffsetStart - (viewSize - stickyOffsetStart - stickyOffsetEnd - itemSize) / 2,
|
|
170
|
+
effectiveAlign: 'center' as const,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (align === 'end') {
|
|
174
|
+
return { target: targetEnd, effectiveAlign: 'end' as const };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (isItemVisible(targetPos, itemSize, scrollPos, viewSize, stickyOffsetStart, stickyOffsetEnd)) {
|
|
178
|
+
return { target: scrollPos, effectiveAlign: 'auto' as const };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const usableSize = viewSize - stickyOffsetStart - stickyOffsetEnd;
|
|
182
|
+
|
|
183
|
+
if (itemSize <= usableSize) {
|
|
184
|
+
return targetPos < scrollPos + stickyOffsetStart
|
|
185
|
+
? {
|
|
186
|
+
target: targetStart,
|
|
187
|
+
effectiveAlign: 'start' as const,
|
|
188
|
+
}
|
|
189
|
+
: {
|
|
190
|
+
target: targetEnd,
|
|
191
|
+
effectiveAlign: 'end' as const,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return Math.abs(targetStart - scrollPos) < Math.abs(targetEnd - scrollPos)
|
|
196
|
+
? {
|
|
197
|
+
target: targetStart,
|
|
198
|
+
effectiveAlign: 'start' as const,
|
|
199
|
+
}
|
|
200
|
+
: {
|
|
201
|
+
target: targetEnd,
|
|
202
|
+
effectiveAlign: 'end' as const,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Helper to calculate total size for a single axis.
|
|
208
|
+
*
|
|
209
|
+
* @param count - Item count.
|
|
210
|
+
* @param fixedSize - Fixed size if any.
|
|
211
|
+
* @param gap - Gap size.
|
|
212
|
+
* @param query - Prefix sum resolver.
|
|
213
|
+
* @returns Total size.
|
|
214
|
+
*/
|
|
215
|
+
function calculateAxisSize(
|
|
216
|
+
count: number,
|
|
217
|
+
fixedSize: number | null,
|
|
218
|
+
gap: number,
|
|
219
|
+
query: (index: number) => number,
|
|
220
|
+
): number {
|
|
221
|
+
if (count <= 0) {
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
if (fixedSize !== null) {
|
|
225
|
+
return Math.max(0, count * (fixedSize + gap) - gap);
|
|
226
|
+
}
|
|
227
|
+
return Math.max(0, query(count) - gap);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Helper to calculate target scroll position for a single axis.
|
|
232
|
+
*
|
|
233
|
+
* @param params - Axis target parameters.
|
|
234
|
+
* @param params.index - Row/column index.
|
|
235
|
+
* @param params.align - Desired alignment.
|
|
236
|
+
* @param params.viewSize - Full viewport size.
|
|
237
|
+
* @param params.scrollPos - Virtual scroll position.
|
|
238
|
+
* @param params.fixedSize - Fixed item size.
|
|
239
|
+
* @param params.gap - Item gap.
|
|
240
|
+
* @param params.query - Prefix sum resolver.
|
|
241
|
+
* @param params.getSize - Item size resolver.
|
|
242
|
+
* @param params.stickyIndices - Sticky indices.
|
|
243
|
+
* @param params.stickyStart - Sticky start element size.
|
|
244
|
+
* @param params.stickyEnd - Sticky end element size.
|
|
245
|
+
* @returns Target position, item size and effective alignment.
|
|
246
|
+
*/
|
|
247
|
+
function calculateAxisTarget({
|
|
248
|
+
index,
|
|
249
|
+
align,
|
|
250
|
+
viewSize,
|
|
251
|
+
scrollPos,
|
|
252
|
+
fixedSize,
|
|
253
|
+
gap,
|
|
254
|
+
query,
|
|
255
|
+
getSize,
|
|
256
|
+
stickyIndices,
|
|
257
|
+
stickyStart,
|
|
258
|
+
stickyEnd = 0,
|
|
259
|
+
}: {
|
|
260
|
+
index: number;
|
|
261
|
+
align: ScrollAlignment;
|
|
262
|
+
viewSize: number;
|
|
263
|
+
scrollPos: number;
|
|
264
|
+
fixedSize: number | null;
|
|
265
|
+
gap: number;
|
|
266
|
+
query: (idx: number) => number;
|
|
267
|
+
getSize: (idx: number) => number;
|
|
268
|
+
stickyIndices?: number[] | undefined;
|
|
269
|
+
stickyStart: number;
|
|
270
|
+
stickyEnd?: number;
|
|
271
|
+
}) {
|
|
272
|
+
let stickyOffsetStart = stickyStart;
|
|
273
|
+
if (stickyIndices && stickyIndices.length > 0) {
|
|
274
|
+
const activeStickyIdx = findPrevStickyIndex(stickyIndices, index);
|
|
275
|
+
if (activeStickyIdx !== undefined) {
|
|
276
|
+
stickyOffsetStart += calculateAxisSize(1, fixedSize, 0, () => getSize(activeStickyIdx));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const itemPos = (fixedSize !== null ? index * (fixedSize + gap) : query(index));
|
|
281
|
+
const itemSize = fixedSize !== null ? fixedSize : getSize(index) - gap;
|
|
282
|
+
|
|
283
|
+
const { target, effectiveAlign } = calculateAxisAlignment({
|
|
284
|
+
align,
|
|
285
|
+
targetPos: itemPos,
|
|
286
|
+
itemSize,
|
|
287
|
+
scrollPos,
|
|
288
|
+
viewSize,
|
|
289
|
+
stickyOffsetStart,
|
|
290
|
+
stickyOffsetEnd: stickyEnd,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return { target, itemSize, effectiveAlign };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// --- Exported Functions ---
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Determines if an item is visible within the usable viewport.
|
|
300
|
+
*
|
|
301
|
+
* @param itemPos - Virtual start position of the item (VU).
|
|
302
|
+
* @param itemSize - Virtual size of the item (VU).
|
|
303
|
+
* @param scrollPos - Virtual scroll position (VU).
|
|
304
|
+
* @param viewSize - Full size of the viewport (VU).
|
|
305
|
+
* @param stickyOffsetStart - Dynamic offset from sticky items at start (VU).
|
|
306
|
+
* @param stickyOffsetEnd - Offset from sticky items at end (VU).
|
|
307
|
+
* @returns True if visible.
|
|
308
|
+
*/
|
|
309
|
+
export function isItemVisible(
|
|
310
|
+
itemPos: number,
|
|
311
|
+
itemSize: number,
|
|
312
|
+
scrollPos: number,
|
|
313
|
+
viewSize: number,
|
|
314
|
+
stickyOffsetStart: number = 0,
|
|
315
|
+
stickyOffsetEnd: number = 0,
|
|
316
|
+
): boolean {
|
|
317
|
+
const usableStart = scrollPos + stickyOffsetStart;
|
|
318
|
+
const usableEnd = scrollPos + viewSize - stickyOffsetEnd;
|
|
319
|
+
const usableSize = viewSize - stickyOffsetStart - stickyOffsetEnd;
|
|
320
|
+
|
|
321
|
+
if (itemSize <= usableSize) {
|
|
322
|
+
return itemPos >= usableStart - 0.5 && (itemPos + itemSize) <= usableEnd + 0.5;
|
|
323
|
+
}
|
|
324
|
+
return itemPos <= usableStart + 0.5 && (itemPos + itemSize) >= usableEnd - 0.5;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Maps a display scroll position to a virtual content position.
|
|
329
|
+
*
|
|
330
|
+
* @param displayPos - Display pixel position (DU).
|
|
331
|
+
* @param hostOffset - Offset of the host element in display pixels (DU).
|
|
332
|
+
* @param scale - Coordinate scaling factor (VU/DU).
|
|
333
|
+
* @returns Virtual content position (VU).
|
|
334
|
+
*/
|
|
335
|
+
export function displayToVirtual(displayPos: number, hostOffset: number, scale: number): number {
|
|
336
|
+
return (displayPos - hostOffset) * scale;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Maps a virtual content position to a display scroll position.
|
|
341
|
+
*
|
|
342
|
+
* @param virtualPos - Virtual content position (VU).
|
|
343
|
+
* @param hostOffset - Offset of the host element in display pixels (DU).
|
|
344
|
+
* @param scale - Coordinate scaling factor (VU/DU).
|
|
345
|
+
* @returns Display pixel position (DU).
|
|
346
|
+
*/
|
|
347
|
+
export function virtualToDisplay(virtualPos: number, hostOffset: number, scale: number): number {
|
|
348
|
+
return virtualPos / scale + hostOffset;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Calculates the target scroll position (relative to content) for a given row/column index and alignment.
|
|
353
|
+
*
|
|
354
|
+
* @param params - Scroll target parameters.
|
|
355
|
+
* @param params.rowIndex - Row index to target.
|
|
356
|
+
* @param params.colIndex - Column index to target.
|
|
357
|
+
* @param params.options - Scroll options including alignment.
|
|
358
|
+
* @param params.direction - Current scroll direction.
|
|
359
|
+
* @param params.viewportWidth - Full viewport width (DU).
|
|
360
|
+
* @param params.viewportHeight - Full viewport height (DU).
|
|
361
|
+
* @param params.totalWidth - Total estimated width (VU).
|
|
362
|
+
* @param params.totalHeight - Total estimated height (VU).
|
|
363
|
+
* @param params.gap - Item gap (VU).
|
|
364
|
+
* @param params.columnGap - Column gap (VU).
|
|
365
|
+
* @param params.fixedSize - Fixed item size (VU).
|
|
366
|
+
* @param params.fixedWidth - Fixed column width (VU).
|
|
367
|
+
* @param params.relativeScrollX - Current relative X scroll (VU).
|
|
368
|
+
* @param params.relativeScrollY - Current relative Y scroll (VU).
|
|
369
|
+
* @param params.getItemSizeY - Resolver for item height (VU).
|
|
370
|
+
* @param params.getItemSizeX - Resolver for item width (VU).
|
|
371
|
+
* @param params.getItemQueryY - Prefix sum resolver for item height (VU).
|
|
372
|
+
* @param params.getItemQueryX - Prefix sum resolver for item width (VU).
|
|
373
|
+
* @param params.getColumnSize - Resolver for column size (VU).
|
|
374
|
+
* @param params.getColumnQuery - Prefix sum resolver for column width (VU).
|
|
375
|
+
* @param params.scaleX - Coordinate scaling factor for X axis.
|
|
376
|
+
* @param params.scaleY - Coordinate scaling factor for Y axis.
|
|
377
|
+
* @param params.hostOffsetX - Display pixels offset of items wrapper on X axis (DU).
|
|
378
|
+
* @param params.hostOffsetY - Display pixels offset of items wrapper on Y axis (DU).
|
|
379
|
+
* @param params.flowPaddingStartX - Display pixels padding at flow start on X axis (DU).
|
|
380
|
+
* @param params.flowPaddingStartY - Display pixels padding at flow start on Y axis (DU).
|
|
381
|
+
* @param params.paddingStartX - Display pixels padding at scroll start on X axis (DU).
|
|
382
|
+
* @param params.paddingStartY - Display pixels padding at scroll start on Y axis (DU).
|
|
383
|
+
* @param params.paddingEndX - Display pixels padding at scroll end on X axis (DU).
|
|
384
|
+
* @param params.paddingEndY - Display pixels padding at scroll end on Y axis (DU).
|
|
385
|
+
* @param params.stickyIndices - List of sticky indices.
|
|
386
|
+
* @param params.stickyStartX - Sticky start offset on X axis (DU).
|
|
387
|
+
* @param params.stickyStartY - Sticky start offset on Y axis (DU).
|
|
388
|
+
* @param params.stickyEndX - Sticky end offset on X axis (DU).
|
|
389
|
+
* @param params.stickyEndY - Sticky end offset on Y axis (DU).
|
|
390
|
+
* @returns The target X and Y positions (VU) and item dimensions (VU).
|
|
391
|
+
* @see ScrollTargetParams
|
|
392
|
+
* @see ScrollTargetResult
|
|
393
|
+
*/
|
|
394
|
+
export function calculateScrollTarget({
|
|
395
|
+
rowIndex,
|
|
396
|
+
colIndex,
|
|
397
|
+
options,
|
|
398
|
+
direction,
|
|
399
|
+
viewportWidth,
|
|
400
|
+
viewportHeight,
|
|
401
|
+
totalWidth,
|
|
402
|
+
totalHeight,
|
|
403
|
+
gap,
|
|
404
|
+
columnGap,
|
|
405
|
+
fixedSize,
|
|
406
|
+
fixedWidth,
|
|
407
|
+
relativeScrollX,
|
|
408
|
+
relativeScrollY,
|
|
409
|
+
getItemSizeY,
|
|
410
|
+
getItemSizeX,
|
|
411
|
+
getItemQueryY,
|
|
412
|
+
getItemQueryX,
|
|
413
|
+
getColumnSize,
|
|
414
|
+
getColumnQuery,
|
|
415
|
+
scaleX,
|
|
416
|
+
scaleY,
|
|
417
|
+
hostOffsetX,
|
|
418
|
+
hostOffsetY,
|
|
419
|
+
stickyIndices,
|
|
420
|
+
stickyStartX = 0,
|
|
421
|
+
stickyStartY = 0,
|
|
422
|
+
stickyEndX = 0,
|
|
423
|
+
stickyEndY = 0,
|
|
424
|
+
flowPaddingStartX = 0,
|
|
425
|
+
flowPaddingStartY = 0,
|
|
426
|
+
paddingStartX = 0,
|
|
427
|
+
paddingStartY = 0,
|
|
428
|
+
paddingEndX = 0,
|
|
429
|
+
paddingEndY = 0,
|
|
430
|
+
}: ScrollTargetParams): ScrollTargetResult {
|
|
431
|
+
let align: ScrollAlignment | ScrollAlignmentOptions | ScrollToIndexOptions | undefined;
|
|
432
|
+
|
|
433
|
+
if (isScrollToIndexOptions(options)) {
|
|
434
|
+
align = options.align;
|
|
435
|
+
} else {
|
|
436
|
+
align = options as ScrollAlignment | ScrollAlignmentOptions;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const alignX = (align && typeof align === 'object' ? align.x : align) || 'auto';
|
|
440
|
+
const alignY = (align && typeof align === 'object' ? align.y : align) || 'auto';
|
|
441
|
+
|
|
442
|
+
let targetX = relativeScrollX;
|
|
443
|
+
let targetY = relativeScrollY;
|
|
444
|
+
let itemWidth = 0;
|
|
445
|
+
let itemHeight = 0;
|
|
446
|
+
let effectiveAlignX: ScrollAlignment = 'auto';
|
|
447
|
+
let effectiveAlignY: ScrollAlignment = 'auto';
|
|
448
|
+
|
|
449
|
+
// Clamp to valid range
|
|
450
|
+
const rWidth = scaleX === 1 ? totalWidth : BROWSER_MAX_SIZE;
|
|
451
|
+
const rHeight = scaleY === 1 ? totalHeight : BROWSER_MAX_SIZE;
|
|
452
|
+
|
|
453
|
+
const maxDisplayX = Math.max(0, hostOffsetX + rWidth - viewportWidth);
|
|
454
|
+
const maxDisplayY = Math.max(0, hostOffsetY + rHeight - viewportHeight);
|
|
455
|
+
|
|
456
|
+
// maxTarget should be in virtual internalScroll coordinates
|
|
457
|
+
const maxTargetX = (maxDisplayX - hostOffsetX) * scaleX;
|
|
458
|
+
const maxTargetY = (maxDisplayY - hostOffsetY) * scaleY;
|
|
459
|
+
|
|
460
|
+
const itemsStartVirtualX = flowPaddingStartX + stickyStartX + paddingStartX;
|
|
461
|
+
const itemsStartVirtualY = flowPaddingStartY + stickyStartY + paddingStartY;
|
|
462
|
+
|
|
463
|
+
// Y calculation
|
|
464
|
+
if (rowIndex != null) {
|
|
465
|
+
const res = calculateAxisTarget({
|
|
466
|
+
index: rowIndex,
|
|
467
|
+
align: alignY as ScrollAlignment,
|
|
468
|
+
viewSize: viewportHeight,
|
|
469
|
+
scrollPos: relativeScrollY,
|
|
470
|
+
fixedSize,
|
|
471
|
+
gap,
|
|
472
|
+
query: getItemQueryY,
|
|
473
|
+
getSize: getItemSizeY,
|
|
474
|
+
stickyIndices,
|
|
475
|
+
stickyStart: stickyStartY + paddingStartY,
|
|
476
|
+
stickyEnd: stickyEndY + paddingEndY,
|
|
477
|
+
});
|
|
478
|
+
targetY = res.target + itemsStartVirtualY;
|
|
479
|
+
itemHeight = res.itemSize;
|
|
480
|
+
effectiveAlignY = res.effectiveAlign;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// X calculation
|
|
484
|
+
if (colIndex != null) {
|
|
485
|
+
const isGrid = direction === 'both';
|
|
486
|
+
const isHorizontal = direction === 'horizontal';
|
|
487
|
+
const res = calculateAxisTarget({
|
|
488
|
+
index: colIndex,
|
|
489
|
+
align: alignX as ScrollAlignment,
|
|
490
|
+
viewSize: viewportWidth,
|
|
491
|
+
scrollPos: relativeScrollX,
|
|
492
|
+
fixedSize: isGrid ? fixedWidth : fixedSize,
|
|
493
|
+
gap: (isGrid || isHorizontal) ? columnGap : gap,
|
|
494
|
+
query: isGrid ? getColumnQuery : getItemQueryX,
|
|
495
|
+
getSize: isGrid ? getColumnSize : getItemSizeX,
|
|
496
|
+
stickyIndices,
|
|
497
|
+
stickyStart: stickyStartX + paddingStartX,
|
|
498
|
+
stickyEnd: stickyEndX + paddingEndX,
|
|
499
|
+
});
|
|
500
|
+
targetX = res.target + itemsStartVirtualX;
|
|
501
|
+
itemWidth = res.itemSize;
|
|
502
|
+
effectiveAlignX = res.effectiveAlign;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
targetX = Math.max(0, Math.min(targetX, maxTargetX));
|
|
506
|
+
targetY = Math.max(0, Math.min(targetY, maxTargetY));
|
|
507
|
+
|
|
508
|
+
return { targetX, targetY, itemWidth, itemHeight, effectiveAlignX, effectiveAlignY };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Calculates the range of items to render based on scroll position and viewport size.
|
|
513
|
+
*
|
|
514
|
+
* @param params - Range parameters.
|
|
515
|
+
* @param params.direction - Scroll direction.
|
|
516
|
+
* @param params.relativeScrollX - Virtual horizontal position (VU).
|
|
517
|
+
* @param params.relativeScrollY - Virtual vertical position (VU).
|
|
518
|
+
* @param params.usableWidth - Usable viewport width (VU).
|
|
519
|
+
* @param params.usableHeight - Usable viewport height (VU).
|
|
520
|
+
* @param params.itemsLength - Total item count.
|
|
521
|
+
* @param params.bufferBefore - Buffer items before.
|
|
522
|
+
* @param params.bufferAfter - Buffer items after.
|
|
523
|
+
* @param params.gap - Item gap (VU).
|
|
524
|
+
* @param params.columnGap - Column gap (VU).
|
|
525
|
+
* @param params.fixedSize - Fixed item size (VU).
|
|
526
|
+
* @param params.findLowerBoundY - Resolver for vertical index.
|
|
527
|
+
* @param params.findLowerBoundX - Resolver for horizontal index.
|
|
528
|
+
* @param params.queryY - Resolver for vertical offset (VU).
|
|
529
|
+
* @param params.queryX - Resolver for horizontal offset (VU).
|
|
530
|
+
* @returns The start and end indices of the items to render.
|
|
531
|
+
* @see RangeParams
|
|
532
|
+
*/
|
|
533
|
+
export function calculateRange({
|
|
534
|
+
direction,
|
|
535
|
+
relativeScrollX,
|
|
536
|
+
relativeScrollY,
|
|
537
|
+
usableWidth,
|
|
538
|
+
usableHeight,
|
|
539
|
+
itemsLength,
|
|
540
|
+
bufferBefore,
|
|
541
|
+
bufferAfter,
|
|
542
|
+
gap,
|
|
543
|
+
columnGap,
|
|
544
|
+
fixedSize,
|
|
545
|
+
findLowerBoundY,
|
|
546
|
+
findLowerBoundX,
|
|
547
|
+
queryY,
|
|
548
|
+
queryX,
|
|
549
|
+
}: RangeParams) {
|
|
550
|
+
const isVertical = direction === 'vertical' || direction === 'both';
|
|
551
|
+
|
|
552
|
+
return calculateGenericRange({
|
|
553
|
+
scrollPos: isVertical ? relativeScrollY : relativeScrollX,
|
|
554
|
+
containerSize: isVertical ? usableHeight : usableWidth,
|
|
555
|
+
count: itemsLength,
|
|
556
|
+
bufferBefore,
|
|
557
|
+
bufferAfter,
|
|
558
|
+
gap: isVertical ? gap : columnGap,
|
|
559
|
+
fixedSize,
|
|
560
|
+
findLowerBound: isVertical ? findLowerBoundY : findLowerBoundX,
|
|
561
|
+
query: isVertical ? queryY : queryX,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Calculates the range of columns to render for bidirectional scroll.
|
|
567
|
+
*
|
|
568
|
+
* @param params - Column range parameters.
|
|
569
|
+
* @param params.columnCount - Total column count.
|
|
570
|
+
* @param params.relativeScrollX - Virtual horizontal position (VU).
|
|
571
|
+
* @param params.usableWidth - Usable viewport width (VU).
|
|
572
|
+
* @param params.colBuffer - Column buffer size.
|
|
573
|
+
* @param params.fixedWidth - Fixed column width (VU).
|
|
574
|
+
* @param params.columnGap - Column gap (VU).
|
|
575
|
+
* @param params.findLowerBound - Resolver for column index.
|
|
576
|
+
* @param params.query - Resolver for column offset (VU).
|
|
577
|
+
* @param params.totalColsQuery - Resolver for total width (VU).
|
|
578
|
+
* @returns The start and end indices and paddings for columns (VU).
|
|
579
|
+
* @see ColumnRangeParams
|
|
580
|
+
* @see ColumnRange
|
|
581
|
+
*/
|
|
582
|
+
export function calculateColumnRange({
|
|
583
|
+
columnCount,
|
|
584
|
+
relativeScrollX,
|
|
585
|
+
usableWidth,
|
|
586
|
+
colBuffer,
|
|
587
|
+
fixedWidth,
|
|
588
|
+
columnGap,
|
|
589
|
+
findLowerBound,
|
|
590
|
+
query,
|
|
591
|
+
totalColsQuery,
|
|
592
|
+
}: ColumnRangeParams) {
|
|
593
|
+
if (!columnCount) {
|
|
594
|
+
return { start: 0, end: 0, padStart: 0, padEnd: 0 };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const { start, end } = calculateGenericRange({
|
|
598
|
+
scrollPos: relativeScrollX,
|
|
599
|
+
containerSize: usableWidth,
|
|
600
|
+
count: columnCount,
|
|
601
|
+
bufferBefore: colBuffer,
|
|
602
|
+
bufferAfter: colBuffer,
|
|
603
|
+
gap: columnGap,
|
|
604
|
+
fixedSize: fixedWidth,
|
|
605
|
+
findLowerBound,
|
|
606
|
+
query,
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const safeStart = start;
|
|
610
|
+
const safeEnd = end;
|
|
611
|
+
|
|
612
|
+
const padStart = fixedWidth !== null ? safeStart * (fixedWidth + columnGap) : query(safeStart);
|
|
613
|
+
const totalWidth = fixedWidth !== null ? columnCount * (fixedWidth + columnGap) - columnGap : Math.max(0, totalColsQuery() - columnGap);
|
|
614
|
+
|
|
615
|
+
const contentEnd = fixedWidth !== null
|
|
616
|
+
? (safeEnd * (fixedWidth + columnGap) - (safeEnd > 0 ? columnGap : 0))
|
|
617
|
+
: (query(safeEnd) - (safeEnd > 0 ? columnGap : 0));
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
start: safeStart,
|
|
621
|
+
end: safeEnd,
|
|
622
|
+
padStart,
|
|
623
|
+
padEnd: Math.max(0, totalWidth - contentEnd),
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Calculates the sticky state and offset for a single item.
|
|
629
|
+
*
|
|
630
|
+
* @param params - Sticky item parameters.
|
|
631
|
+
* @param params.index - Item index.
|
|
632
|
+
* @param params.isSticky - If configured as sticky.
|
|
633
|
+
* @param params.direction - Scroll direction.
|
|
634
|
+
* @param params.relativeScrollX - Virtual horizontal position (VU).
|
|
635
|
+
* @param params.relativeScrollY - Virtual vertical position (VU).
|
|
636
|
+
* @param params.originalX - Virtual original X position (VU).
|
|
637
|
+
* @param params.originalY - Virtual original Y position (VU).
|
|
638
|
+
* @param params.width - Virtual item width (VU).
|
|
639
|
+
* @param params.height - Virtual item height (VU).
|
|
640
|
+
* @param params.stickyIndices - All sticky indices.
|
|
641
|
+
* @param params.fixedSize - Fixed item size (VU).
|
|
642
|
+
* @param params.fixedWidth - Fixed column width (VU).
|
|
643
|
+
* @param params.gap - Item gap (VU).
|
|
644
|
+
* @param params.columnGap - Column gap (VU).
|
|
645
|
+
* @param params.getItemQueryY - Resolver for vertical offset (VU).
|
|
646
|
+
* @param params.getItemQueryX - Resolver for horizontal offset (VU).
|
|
647
|
+
* @returns Sticky state and offset (VU).
|
|
648
|
+
* @see StickyParams
|
|
649
|
+
*/
|
|
650
|
+
export function calculateStickyItem({
|
|
651
|
+
index,
|
|
652
|
+
isSticky,
|
|
653
|
+
direction,
|
|
654
|
+
relativeScrollX,
|
|
655
|
+
relativeScrollY,
|
|
656
|
+
originalX,
|
|
657
|
+
originalY,
|
|
658
|
+
width,
|
|
659
|
+
height,
|
|
660
|
+
stickyIndices,
|
|
661
|
+
fixedSize,
|
|
662
|
+
fixedWidth,
|
|
663
|
+
gap,
|
|
664
|
+
columnGap,
|
|
665
|
+
getItemQueryY,
|
|
666
|
+
getItemQueryX,
|
|
667
|
+
}: StickyParams) {
|
|
668
|
+
let isStickyActive = false;
|
|
669
|
+
const stickyOffset = { x: 0, y: 0 };
|
|
670
|
+
|
|
671
|
+
if (!isSticky) {
|
|
672
|
+
return { isStickyActive, stickyOffset };
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Y Axis (Sticky Rows)
|
|
676
|
+
if (direction === 'vertical' || direction === 'both') {
|
|
677
|
+
if (relativeScrollY > originalY) {
|
|
678
|
+
const nextStickyIdx = findNextStickyIndex(stickyIndices, index);
|
|
679
|
+
|
|
680
|
+
if (nextStickyIdx !== undefined) {
|
|
681
|
+
const nextStickyY = fixedSize !== null ? nextStickyIdx * (fixedSize + gap) : getItemQueryY(nextStickyIdx);
|
|
682
|
+
if (relativeScrollY >= nextStickyY) {
|
|
683
|
+
isStickyActive = false;
|
|
684
|
+
} else {
|
|
685
|
+
isStickyActive = true;
|
|
686
|
+
stickyOffset.y = Math.max(0, Math.min(height, nextStickyY - relativeScrollY)) - height;
|
|
687
|
+
}
|
|
688
|
+
} else {
|
|
689
|
+
isStickyActive = true;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// X Axis (Sticky Columns / Items)
|
|
695
|
+
if (direction === 'horizontal' || (direction === 'both' && !isStickyActive)) {
|
|
696
|
+
if (relativeScrollX > originalX) {
|
|
697
|
+
const nextStickyIdx = findNextStickyIndex(stickyIndices, index);
|
|
698
|
+
|
|
699
|
+
if (nextStickyIdx !== undefined) {
|
|
700
|
+
const nextStickyX = direction === 'horizontal'
|
|
701
|
+
? (fixedSize !== null ? nextStickyIdx * (fixedSize + columnGap) : getItemQueryX(nextStickyIdx))
|
|
702
|
+
: (fixedWidth !== null ? nextStickyIdx * (fixedWidth + columnGap) : getItemQueryX(nextStickyIdx));
|
|
703
|
+
|
|
704
|
+
if (relativeScrollX >= nextStickyX) {
|
|
705
|
+
isStickyActive = false;
|
|
706
|
+
} else {
|
|
707
|
+
isStickyActive = true;
|
|
708
|
+
stickyOffset.x = Math.max(0, Math.min(width, nextStickyX - relativeScrollX)) - width;
|
|
709
|
+
}
|
|
710
|
+
} else {
|
|
711
|
+
isStickyActive = true;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return { isStickyActive, stickyOffset };
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Calculates the position and size of a single item.
|
|
721
|
+
*
|
|
722
|
+
* @param params - Item position parameters.
|
|
723
|
+
* @param params.index - Item index.
|
|
724
|
+
* @param params.direction - Scroll direction.
|
|
725
|
+
* @param params.fixedSize - Fixed item size (VU).
|
|
726
|
+
* @param params.gap - Item gap (VU).
|
|
727
|
+
* @param params.columnGap - Column gap (VU).
|
|
728
|
+
* @param params.usableWidth - Usable viewport width (VU).
|
|
729
|
+
* @param params.usableHeight - Usable viewport height (VU).
|
|
730
|
+
* @param params.totalWidth - Total estimated width (VU).
|
|
731
|
+
* @param params.queryY - Resolver for vertical offset (VU).
|
|
732
|
+
* @param params.queryX - Resolver for horizontal offset (VU).
|
|
733
|
+
* @param params.getSizeY - Resolver for height (VU).
|
|
734
|
+
* @param params.getSizeX - Resolver for width (VU).
|
|
735
|
+
* @param params.columnRange - Current column range (for grid mode).
|
|
736
|
+
* @returns Item position and size (VU).
|
|
737
|
+
* @see ItemPositionParams
|
|
738
|
+
*/
|
|
739
|
+
export function calculateItemPosition({
|
|
740
|
+
index,
|
|
741
|
+
direction,
|
|
742
|
+
fixedSize,
|
|
743
|
+
gap,
|
|
744
|
+
columnGap,
|
|
745
|
+
usableWidth,
|
|
746
|
+
usableHeight,
|
|
747
|
+
totalWidth,
|
|
748
|
+
queryY,
|
|
749
|
+
queryX,
|
|
750
|
+
getSizeY,
|
|
751
|
+
getSizeX,
|
|
752
|
+
columnRange,
|
|
753
|
+
}: ItemPositionParams) {
|
|
754
|
+
let x = 0;
|
|
755
|
+
let y = 0;
|
|
756
|
+
let width = 0;
|
|
757
|
+
let height = 0;
|
|
758
|
+
|
|
759
|
+
if (direction === 'horizontal') {
|
|
760
|
+
x = fixedSize !== null ? index * (fixedSize + columnGap) : queryX(index);
|
|
761
|
+
width = fixedSize !== null ? fixedSize : getSizeX(index) - columnGap;
|
|
762
|
+
height = usableHeight;
|
|
763
|
+
} else if (direction === 'both' && columnRange) {
|
|
764
|
+
y = fixedSize !== null ? index * (fixedSize + gap) : queryY(index);
|
|
765
|
+
height = fixedSize !== null ? fixedSize : getSizeY(index) - gap;
|
|
766
|
+
x = columnRange.padStart;
|
|
767
|
+
width = Math.max(0, totalWidth - columnRange.padStart - columnRange.padEnd);
|
|
768
|
+
} else {
|
|
769
|
+
y = fixedSize !== null ? index * (fixedSize + gap) : queryY(index);
|
|
770
|
+
height = fixedSize !== null ? fixedSize : getSizeY(index) - gap;
|
|
771
|
+
width = direction === 'both' ? totalWidth : usableWidth;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return { height, width, x, y };
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Calculates the style object for a rendered item.
|
|
779
|
+
*
|
|
780
|
+
* @param params - Item style parameters.
|
|
781
|
+
* @param params.item - Rendered item state.
|
|
782
|
+
* @param params.direction - Scroll direction.
|
|
783
|
+
* @param params.itemSize - Virtual item size (VU).
|
|
784
|
+
* @param params.containerTag - Container HTML tag.
|
|
785
|
+
* @param params.paddingStartX - Horizontal virtual padding (DU).
|
|
786
|
+
* @param params.paddingStartY - Vertical virtual padding (DU).
|
|
787
|
+
* @param params.isHydrated - If mounted and hydrated.
|
|
788
|
+
* @param params.isRtl - If in RTL mode.
|
|
789
|
+
* @returns Style object.
|
|
790
|
+
* @see ItemStyleParams
|
|
791
|
+
*/
|
|
792
|
+
export function calculateItemStyle<T = unknown>({
|
|
793
|
+
item,
|
|
794
|
+
direction,
|
|
795
|
+
itemSize,
|
|
796
|
+
containerTag,
|
|
797
|
+
paddingStartX,
|
|
798
|
+
paddingStartY,
|
|
799
|
+
isHydrated,
|
|
800
|
+
isRtl,
|
|
801
|
+
}: ItemStyleParams<T>) {
|
|
802
|
+
const isVertical = direction === 'vertical';
|
|
803
|
+
const isHorizontal = direction === 'horizontal';
|
|
804
|
+
const isBoth = direction === 'both';
|
|
805
|
+
const isDynamic = itemSize === undefined || itemSize === null || itemSize === 0;
|
|
806
|
+
|
|
807
|
+
const style: Record<string, string | number | undefined> = {
|
|
808
|
+
blockSize: isHorizontal ? '100%' : (!isDynamic ? `${ item.size.height }px` : 'auto'),
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
if (isVertical && containerTag === 'table') {
|
|
812
|
+
style.minInlineSize = '100%';
|
|
813
|
+
} else {
|
|
814
|
+
style.inlineSize = isVertical ? '100%' : (!isDynamic ? `${ item.size.width }px` : 'auto');
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
if (isDynamic) {
|
|
818
|
+
if (!isVertical) {
|
|
819
|
+
style.minInlineSize = '1px';
|
|
820
|
+
}
|
|
821
|
+
if (!isHorizontal) {
|
|
822
|
+
style.minBlockSize = '1px';
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (isHydrated) {
|
|
827
|
+
const tx = isRtl
|
|
828
|
+
? -(item.isStickyActive ? item.stickyOffset.x : item.offset.x)
|
|
829
|
+
: (item.isStickyActive ? item.stickyOffset.x : item.offset.x);
|
|
830
|
+
|
|
831
|
+
if (item.isStickyActive) {
|
|
832
|
+
if (isVertical || isBoth) {
|
|
833
|
+
style.insetBlockStart = `${ paddingStartY }px`;
|
|
834
|
+
}
|
|
835
|
+
if (isHorizontal || isBoth) {
|
|
836
|
+
style.insetInlineStart = `${ paddingStartX }px`;
|
|
837
|
+
}
|
|
838
|
+
style.transform = `translate(${ tx }px, ${ item.stickyOffset.y }px)`;
|
|
839
|
+
} else {
|
|
840
|
+
style.transform = `translate(${ tx }px, ${ item.offset.y }px)`;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return style;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Calculates the total width and height of the virtualized content.
|
|
849
|
+
*
|
|
850
|
+
* @param params - Total size parameters.
|
|
851
|
+
* @param params.direction - Scroll direction.
|
|
852
|
+
* @param params.itemsLength - Total item count.
|
|
853
|
+
* @param params.columnCount - Column count.
|
|
854
|
+
* @param params.fixedSize - Fixed item size (VU).
|
|
855
|
+
* @param params.fixedWidth - Fixed column width (VU).
|
|
856
|
+
* @param params.gap - Item gap (VU).
|
|
857
|
+
* @param params.columnGap - Column gap (VU).
|
|
858
|
+
* @param params.usableWidth - Usable viewport width (VU).
|
|
859
|
+
* @param params.usableHeight - Usable viewport height (VU).
|
|
860
|
+
* @param params.queryY - Resolver for vertical offset (VU).
|
|
861
|
+
* @param params.queryX - Resolver for horizontal offset (VU).
|
|
862
|
+
* @param params.queryColumn - Resolver for column offset (VU).
|
|
863
|
+
* @returns Total width and height (VU).
|
|
864
|
+
* @see TotalSizeParams
|
|
865
|
+
*/
|
|
866
|
+
export function calculateTotalSize({
|
|
867
|
+
direction,
|
|
868
|
+
itemsLength,
|
|
869
|
+
columnCount,
|
|
870
|
+
fixedSize,
|
|
871
|
+
fixedWidth,
|
|
872
|
+
gap,
|
|
873
|
+
columnGap,
|
|
874
|
+
usableWidth,
|
|
875
|
+
usableHeight,
|
|
876
|
+
queryY,
|
|
877
|
+
queryX,
|
|
878
|
+
queryColumn,
|
|
879
|
+
}: TotalSizeParams) {
|
|
880
|
+
const isBoth = direction === 'both';
|
|
881
|
+
const isHorizontal = direction === 'horizontal';
|
|
882
|
+
|
|
883
|
+
let width = 0;
|
|
884
|
+
let height = 0;
|
|
885
|
+
|
|
886
|
+
if (isBoth) {
|
|
887
|
+
width = calculateAxisSize(columnCount, fixedWidth, columnGap, queryColumn);
|
|
888
|
+
height = calculateAxisSize(itemsLength, fixedSize, gap, queryY);
|
|
889
|
+
} else if (isHorizontal) {
|
|
890
|
+
width = calculateAxisSize(itemsLength, fixedSize, columnGap, queryX);
|
|
891
|
+
height = usableHeight;
|
|
892
|
+
} else {
|
|
893
|
+
width = usableWidth;
|
|
894
|
+
height = calculateAxisSize(itemsLength, fixedSize, gap, queryY);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return {
|
|
898
|
+
width: isBoth ? Math.max(width, usableWidth) : width,
|
|
899
|
+
height: isBoth ? Math.max(height, usableHeight) : height,
|
|
900
|
+
};
|
|
901
|
+
}
|