@candidstartup/react-virtual-scroll 0.4.0 → 0.6.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 +2 -2
- package/dist/index.d.ts +590 -61
- package/dist/index.js +392 -117
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1,34 +1,72 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import React, { useState, createRef, useRef, useEffect, Fragment } from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
4
|
+
const defaultContainerRender = ({ ...rest }, ref) => (jsx("div", { ref: ref, ...rest }));
|
|
5
|
+
/**
|
|
6
|
+
* Wrapper around a div used by other components in {@link @candidstartup/react-virtual-scroll!}. Most props are passed through to the div. Use the
|
|
7
|
+
* {@link VirtualContainerComponentProps.render} prop to override the default behavior.
|
|
8
|
+
*
|
|
9
|
+
* @group Components
|
|
10
|
+
*/
|
|
11
|
+
const VirtualContainer = React.forwardRef(function VirtualContainer({ render = defaultContainerRender, ...rest }, ref) {
|
|
12
|
+
return render(rest, ref);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* HOC that calculates the size available to it and makes the computed size available to children.
|
|
17
|
+
* The size available depends on DOM layout and style applied wherever the AutoSizer finds itself.
|
|
18
|
+
* You will probably want to pass something appropriate via the `className` or `style` props.
|
|
19
|
+
*
|
|
20
|
+
* Accepts props defined by {@link AutoSizerProps}.
|
|
21
|
+
* You must pass a single instance of {@link AutoSizerRender} as a child.
|
|
22
|
+
* @group Components
|
|
23
|
+
*/
|
|
24
|
+
function AutoSizer(props) {
|
|
25
|
+
const { children, className, style } = props;
|
|
26
|
+
// Using separate primitive states rather than one composite so that React
|
|
27
|
+
// can detect duplicates values and bail out of redundant renders.
|
|
28
|
+
const [width, setWidth] = React.useState(0);
|
|
29
|
+
const [height, setHeight] = React.useState(0);
|
|
30
|
+
const ref = React.useRef(null);
|
|
31
|
+
// Make sure resize callback is a stable value so we're not constantly
|
|
32
|
+
// creating and disconnecting resize observers.
|
|
33
|
+
const resizeCallback = React.useCallback((entries) => {
|
|
34
|
+
entries.forEach(entry => {
|
|
35
|
+
// Context box sizes can contain fractional values while clientWidth
|
|
36
|
+
// and clientHeight properties are always rounded to nearest integer.
|
|
37
|
+
// Always use integer values to avoid confusion.
|
|
38
|
+
const newWidth = Math.round(entry.contentBoxSize[0].inlineSize);
|
|
39
|
+
setWidth(newWidth);
|
|
40
|
+
const newHeight = Math.round(entry.borderBoxSize[0].blockSize);
|
|
41
|
+
setHeight(newHeight);
|
|
42
|
+
});
|
|
43
|
+
}, []);
|
|
44
|
+
// Expect effect to run only on initial mount
|
|
45
|
+
React.useLayoutEffect(() => {
|
|
46
|
+
const div = ref.current;
|
|
47
|
+
/* istanbul ignore if*/
|
|
48
|
+
if (!div)
|
|
49
|
+
return;
|
|
50
|
+
// Size on initial mount
|
|
51
|
+
setHeight(div.clientHeight);
|
|
52
|
+
setWidth(div.clientWidth);
|
|
53
|
+
// Updates size on any subsequent resize. Only available in browser
|
|
54
|
+
// environment so avoid crashing out when server side rendering, or
|
|
55
|
+
// running unit test without ResizeObserver being mocked.
|
|
56
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
57
|
+
const resizeObserver = new ResizeObserver(resizeCallback);
|
|
58
|
+
resizeObserver.observe(div);
|
|
59
|
+
return () => { resizeObserver.disconnect(); };
|
|
60
|
+
}
|
|
61
|
+
}, [resizeCallback]);
|
|
62
|
+
// No point rendering children until we've measured size and found a usable area
|
|
63
|
+
const renderChildren = height > 0 && width > 0;
|
|
64
|
+
// Ensure that size is driven only by parent. Wrapping child in a zero sized inner div
|
|
65
|
+
// which it can overflow stops child's size having any impact on size of outer div.
|
|
66
|
+
// Otherwise can end up in infinite loop if child makes itself bigger than the
|
|
67
|
+
// actual width and height we pass to it. That could be because child has
|
|
68
|
+
// padding/borders/margins or because child renders itself bigger than size it's given.
|
|
69
|
+
return (jsx("div", { ref: ref, className: className, style: style, children: jsx("div", { style: { overflow: 'visible', width: 0, height: 0 }, children: renderChildren && children({ height, width }) }) }));
|
|
32
70
|
}
|
|
33
71
|
|
|
34
72
|
// Max size that is safe across all browsers (Firefox is the limiting factor)
|
|
@@ -135,7 +173,7 @@ function useEventListener(eventName, handler, element = window, options = {}) {
|
|
|
135
173
|
const el = isListener(element) ? element : element.current;
|
|
136
174
|
if (!el)
|
|
137
175
|
return;
|
|
138
|
-
const eventListener = (event) => savedHandler.current(event);
|
|
176
|
+
const eventListener = (event) => savedHandler.current?.(event);
|
|
139
177
|
const opts = { capture, passive, once };
|
|
140
178
|
el.addEventListener(eventName, eventListener, opts);
|
|
141
179
|
return () => {
|
|
@@ -187,7 +225,7 @@ function useAnimationTimeout(callback, delay, key) {
|
|
|
187
225
|
requestRef.current = undefined;
|
|
188
226
|
}
|
|
189
227
|
};
|
|
190
|
-
}, [delay, key]);
|
|
228
|
+
}, [start, delay, key]);
|
|
191
229
|
}
|
|
192
230
|
|
|
193
231
|
const DEBOUNCE_INTERVAL = 150;
|
|
@@ -206,30 +244,86 @@ function useIsScrolling(element = window) {
|
|
|
206
244
|
return scrollCount > 0;
|
|
207
245
|
}
|
|
208
246
|
|
|
209
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Returns the offset needed to scroll in one dimension for a specified range
|
|
249
|
+
*
|
|
250
|
+
* Used internally to implement {@link VirtualScrollProxy.scrollToArea}. Can be used directly for
|
|
251
|
+
* advanced customization scenarios.
|
|
252
|
+
*/
|
|
253
|
+
function getOffsetToScrollRange(offset, size, clientExtent, scrollOffset, option) {
|
|
254
|
+
if (offset === undefined)
|
|
255
|
+
return undefined;
|
|
256
|
+
if (option != 'visible')
|
|
257
|
+
return offset;
|
|
258
|
+
// Start of item offscreen before start of viewport?
|
|
259
|
+
if (offset < scrollOffset)
|
|
260
|
+
return offset;
|
|
261
|
+
size = size || 0;
|
|
262
|
+
// Already completely visible?
|
|
263
|
+
const endOffset = offset + size;
|
|
264
|
+
const endViewport = scrollOffset + clientExtent;
|
|
265
|
+
if (endOffset <= endViewport)
|
|
266
|
+
return undefined;
|
|
267
|
+
// Item offscreen past end of viewport
|
|
268
|
+
// Item bigger than viewport? Make sure start is in view
|
|
269
|
+
if (size > clientExtent)
|
|
270
|
+
return offset;
|
|
271
|
+
// Scroll so end of item aligns with end of viewport
|
|
272
|
+
return offset - clientExtent + size;
|
|
273
|
+
}
|
|
274
|
+
|
|
210
275
|
// Using a named function rather than => so that the name shows up in React Developer Tools
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Customizable Virtual Scrolling Component
|
|
278
|
+
*
|
|
279
|
+
* Allows user to scroll over a virtual area `scrollHeight` x `scrollWidth` pixels.
|
|
280
|
+
* Use `onScroll` to track scroll state and `innerRender` to render scroll state specific content into the viewport
|
|
281
|
+
*
|
|
282
|
+
* Accepts props defined by {@link VirtualScrollProps}.
|
|
283
|
+
* Refs are forwarded to {@link VirtualScrollProxy}.
|
|
284
|
+
* @group Components
|
|
285
|
+
*/
|
|
286
|
+
const VirtualScroll = React.forwardRef(function VirtualScroll(props, ref) {
|
|
287
|
+
const { width, height, scrollWidth = 0, scrollHeight = 0, className, innerClassName, children, onScroll: onScrollCallback, useIsScrolling: useIsScrolling$1 = false, innerRender, outerRender } = props;
|
|
216
288
|
const outerRef = React.useRef(null);
|
|
217
|
-
const { scrollOffset: scrollRowOffset, renderOffset: renderRowOffset, renderSize: renderRowSize, onScroll: onScrollRow, doScrollTo: doScrollToRow } = useVirtualScroll(
|
|
218
|
-
const
|
|
219
|
-
const
|
|
289
|
+
const { scrollOffset: scrollRowOffset, renderOffset: renderRowOffset, renderSize: renderRowSize, onScroll: onScrollRow, doScrollTo: doScrollToRow } = useVirtualScroll(scrollHeight, props.maxCssSize, props.minNumPages);
|
|
290
|
+
const currentVerticalOffset = scrollRowOffset + renderRowOffset;
|
|
291
|
+
const { scrollOffset: scrollColOffset, renderOffset: renderColOffset, renderSize: renderColumnSize, onScroll: onScrollColumn, doScrollTo: doScrollToColumn } = useVirtualScroll(scrollWidth, props.maxCssSize, props.minNumPages);
|
|
292
|
+
const currentHorizontalOffset = scrollColOffset + renderColOffset;
|
|
293
|
+
const isActuallyScrolling = useIsScrolling(outerRef);
|
|
220
294
|
React.useImperativeHandle(ref, () => {
|
|
221
295
|
return {
|
|
222
296
|
scrollTo(rowOffset, columnOffset) {
|
|
297
|
+
if (rowOffset === undefined && columnOffset === undefined)
|
|
298
|
+
return;
|
|
223
299
|
const outer = outerRef.current;
|
|
224
300
|
/* istanbul ignore else */
|
|
225
|
-
if (outer)
|
|
226
|
-
|
|
301
|
+
if (outer) {
|
|
302
|
+
const options = {};
|
|
303
|
+
if (rowOffset != undefined)
|
|
304
|
+
options.top = doScrollToRow(rowOffset, outer.clientHeight);
|
|
305
|
+
if (columnOffset != undefined)
|
|
306
|
+
options.left = doScrollToColumn(columnOffset, outer.clientWidth);
|
|
307
|
+
outer.scrollTo(options);
|
|
308
|
+
}
|
|
227
309
|
},
|
|
228
|
-
|
|
229
|
-
|
|
310
|
+
scrollToArea(verticalOffset, verticalSize, horizontalOffset, horizontalSize, option) {
|
|
311
|
+
const outer = outerRef.current;
|
|
312
|
+
/* istanbul ignore if*/
|
|
313
|
+
if (!outer)
|
|
314
|
+
return;
|
|
315
|
+
const rowOffset = getOffsetToScrollRange(verticalOffset, verticalSize, outer.clientHeight, currentVerticalOffset, option);
|
|
316
|
+
const colOffset = getOffsetToScrollRange(horizontalOffset, horizontalSize, outer.clientWidth, currentHorizontalOffset, option);
|
|
317
|
+
this.scrollTo(rowOffset, colOffset);
|
|
318
|
+
},
|
|
319
|
+
get clientWidth() {
|
|
320
|
+
return outerRef.current ? outerRef.current.clientWidth : /* istanbul ignore next */ 0;
|
|
321
|
+
},
|
|
322
|
+
get clientHeight() {
|
|
323
|
+
return outerRef.current ? outerRef.current.clientHeight : /* istanbul ignore next */ 0;
|
|
230
324
|
}
|
|
231
325
|
};
|
|
232
|
-
}, [
|
|
326
|
+
}, [doScrollToRow, doScrollToColumn, currentVerticalOffset, currentHorizontalOffset]);
|
|
233
327
|
function onScroll(event) {
|
|
234
328
|
const { clientWidth, clientHeight, scrollWidth, scrollHeight, scrollLeft, scrollTop } = event.currentTarget;
|
|
235
329
|
const [newScrollTop, newRowScrollState] = onScrollRow(clientHeight, scrollHeight, scrollTop);
|
|
@@ -238,93 +332,263 @@ const VirtualGrid = React.forwardRef(function VirtualGrid(props, ref) {
|
|
|
238
332
|
outerRef.current.scrollTo(newScrollLeft, newScrollTop);
|
|
239
333
|
onScrollCallback?.(newRowScrollState.scrollOffset + newRowScrollState.renderOffset, newColumnScrollState.scrollOffset + newColumnScrollState.renderOffset, newRowScrollState, newColumnScrollState);
|
|
240
334
|
}
|
|
241
|
-
const
|
|
242
|
-
|
|
335
|
+
const isScrolling = useIsScrolling$1 ? isActuallyScrolling : undefined;
|
|
336
|
+
return (jsxs(VirtualContainer, { className: className, render: outerRender, onScroll: onScroll, ref: outerRef, style: { position: "relative", height, width, overflow: "auto", willChange: "transform" }, children: [jsx(VirtualContainer, { className: innerClassName, render: innerRender, style: { zIndex: 1, position: 'sticky', top: 0, left: 0, width: '100%', height: '100%' }, children: children({ isScrolling }) }), jsx("div", { style: { position: 'absolute', top: 0, left: 0,
|
|
337
|
+
height: scrollHeight ? renderRowSize : '100%',
|
|
338
|
+
width: scrollWidth ? renderColumnSize : '100%' } })] }));
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
function getRangeToRender(itemCount, itemOffsetMapping, clientExtent, scrollOffset) {
|
|
342
|
+
if (itemCount == 0) {
|
|
343
|
+
return [0, 0, 0, []];
|
|
344
|
+
}
|
|
345
|
+
// Negative offset equivalent to reducing the size of the window (possibly down to nothing)
|
|
346
|
+
if (scrollOffset < 0) {
|
|
347
|
+
clientExtent += scrollOffset;
|
|
348
|
+
scrollOffset = 0;
|
|
349
|
+
}
|
|
350
|
+
if (clientExtent <= 0) {
|
|
351
|
+
return [0, 0, 0, []];
|
|
352
|
+
}
|
|
353
|
+
const [baseIndex, startOffset] = itemOffsetMapping.offsetToItem(scrollOffset);
|
|
354
|
+
if (baseIndex >= itemCount) {
|
|
355
|
+
return [0, 0, 0, []];
|
|
356
|
+
}
|
|
357
|
+
let itemIndex = Math.max(0, Math.min(itemCount - 1, baseIndex));
|
|
358
|
+
const endOffset = scrollOffset + clientExtent;
|
|
359
|
+
const startIndex = itemIndex;
|
|
360
|
+
let offset = startOffset;
|
|
361
|
+
const sizes = [];
|
|
362
|
+
let totalSize = 0;
|
|
363
|
+
while (offset < endOffset && itemIndex < itemCount) {
|
|
364
|
+
const size = itemOffsetMapping.itemSize(itemIndex);
|
|
365
|
+
sizes.push(size);
|
|
366
|
+
totalSize += size;
|
|
367
|
+
offset += size;
|
|
368
|
+
itemIndex++;
|
|
369
|
+
}
|
|
370
|
+
return [startIndex, startOffset, totalSize, sizes];
|
|
371
|
+
}
|
|
372
|
+
function formatRepeat(repeat, size) {
|
|
373
|
+
return (repeat == 1) ? `${size}px` : `repeat(${repeat},${size}px)`;
|
|
374
|
+
}
|
|
375
|
+
function join(a, s) {
|
|
376
|
+
return a ? a + ' ' + s : s;
|
|
377
|
+
}
|
|
378
|
+
function getGridTemplate(sizes) {
|
|
379
|
+
const count = sizes.length;
|
|
380
|
+
if (count == 0)
|
|
381
|
+
return undefined;
|
|
382
|
+
let ret = undefined;
|
|
383
|
+
let lastSize = sizes[0];
|
|
384
|
+
let repeat = 1;
|
|
385
|
+
for (let i = 1; i < count; i++) {
|
|
386
|
+
const size = sizes[i];
|
|
387
|
+
if (size == lastSize) {
|
|
388
|
+
repeat++;
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
const s = formatRepeat(repeat, lastSize);
|
|
392
|
+
ret = join(ret, s);
|
|
393
|
+
lastSize = size;
|
|
394
|
+
repeat = 1;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const s = formatRepeat(repeat, lastSize);
|
|
398
|
+
return join(ret, s);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const defaultItemKey$1 = (index, _data) => index;
|
|
402
|
+
const boxStyle$1 = { boxSizing: 'border-box' };
|
|
403
|
+
/**
|
|
404
|
+
* Displays a window onto the contents of a virtualized list starting from `offset`.
|
|
405
|
+
*
|
|
406
|
+
* Accepts props defined by {@link DisplayListProps}.
|
|
407
|
+
* You must pass a single instance of {@link DisplayListItem} as a child.
|
|
408
|
+
* @group Components
|
|
409
|
+
*/
|
|
410
|
+
function DisplayList(props) {
|
|
411
|
+
const { width, height, itemCount, itemOffsetMapping, className, innerClassName, offset: renderOffset, children, itemData, itemKey = defaultItemKey$1, layout = 'vertical', outerRender, innerRender, isScrolling } = props;
|
|
412
|
+
const isVertical = layout === 'vertical';
|
|
413
|
+
const [startIndex, startOffset, renderSize, sizes] = getRangeToRender(itemCount, itemOffsetMapping, isVertical ? height : width, renderOffset);
|
|
414
|
+
const template = getGridTemplate(sizes);
|
|
415
|
+
const offset = startOffset - renderOffset;
|
|
416
|
+
// We can decide the JSX child type at runtime as long as we use a variable that uses the same capitalized
|
|
417
|
+
// naming convention as components do.
|
|
418
|
+
const ChildVar = children;
|
|
419
|
+
return (jsx(VirtualContainer, { className: className, render: outerRender, style: { position: "relative", height, width, overflow: "hidden", willChange: "transform" }, children: jsx(VirtualContainer, { className: innerClassName, render: innerRender, style: { position: 'absolute',
|
|
420
|
+
display: 'grid',
|
|
421
|
+
gridTemplateColumns: isVertical ? undefined : template,
|
|
422
|
+
gridTemplateRows: isVertical ? template : undefined,
|
|
423
|
+
top: isVertical ? offset : 0,
|
|
424
|
+
left: isVertical ? 0 : offset,
|
|
425
|
+
height: isVertical ? renderSize : "100%",
|
|
426
|
+
width: isVertical ? "100%" : renderSize }, children: sizes.map((_size, arrayIndex) => (jsx(ChildVar, { data: itemData, isScrolling: isScrolling, index: startIndex + arrayIndex, style: boxStyle$1 }, itemKey(startIndex + arrayIndex, itemData)))) }) }));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const defaultItemKey = (rowIndex, columnIndex, _data) => `${rowIndex}:${columnIndex}`;
|
|
430
|
+
const boxStyle = { boxSizing: 'border-box' };
|
|
431
|
+
/**
|
|
432
|
+
* Displays a window onto the contents of a virtualized grid starting from `rowOffset`, `columnOffset`.
|
|
433
|
+
*
|
|
434
|
+
* Accepts props defined by {@link DisplayGridProps}.
|
|
435
|
+
* You must pass a single instance of {@link DisplayGridItem} as a child.
|
|
436
|
+
* @group Components
|
|
437
|
+
*/
|
|
438
|
+
function DisplayGrid(props) {
|
|
439
|
+
const { width, height, rowCount, rowOffsetMapping, columnCount, columnOffsetMapping, className, innerClassName, rowOffset: rowRenderOffset, columnOffset: colRenderOffset, children, itemData, itemKey = defaultItemKey, outerRender, innerRender, isScrolling } = props;
|
|
440
|
+
const [rowStartIndex, rowStartOffset, rowRenderSize, rowSizes] = getRangeToRender(rowCount, rowOffsetMapping, height, rowRenderOffset);
|
|
441
|
+
const rowTemplate = getGridTemplate(rowSizes);
|
|
442
|
+
const [colStartIndex, colStartOffset, colRenderSize, colSizes] = getRangeToRender(columnCount, columnOffsetMapping, width, colRenderOffset);
|
|
443
|
+
const colTemplate = getGridTemplate(colSizes);
|
|
444
|
+
const rowOffset = rowStartOffset - rowRenderOffset;
|
|
445
|
+
const colOffset = colStartOffset - colRenderOffset;
|
|
446
|
+
// We can decide the JSX child type at runtime as long as we use a variable that uses the same capitalized
|
|
447
|
+
// naming convention as components do.
|
|
448
|
+
const ChildVar = children;
|
|
449
|
+
return (jsx(VirtualContainer, { className: className, render: outerRender, style: { position: "relative", height, width, overflow: "hidden", willChange: "transform" }, children: jsx(VirtualContainer, { className: innerClassName, render: innerRender, style: { position: 'absolute',
|
|
450
|
+
display: 'grid',
|
|
451
|
+
gridTemplateColumns: colTemplate,
|
|
452
|
+
gridTemplateRows: rowTemplate,
|
|
453
|
+
top: rowOffset,
|
|
454
|
+
left: colOffset,
|
|
455
|
+
height: rowRenderSize,
|
|
456
|
+
width: colRenderSize }, children: rowSizes.map((_rowSize, rowIndex) => (jsx(Fragment, { children: colSizes.map((_size, colIndex) => (jsx(ChildVar, { data: itemData, isScrolling: isScrolling, rowIndex: rowStartIndex + rowIndex, columnIndex: colStartIndex + colIndex, style: boxStyle }, itemKey(rowStartIndex + rowIndex, colStartIndex + colIndex, itemData)))) }, itemKey(rowStartIndex + rowIndex, 0, itemData)))) }) }));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Returns the {@link ScrollRange} corresponding to a specified item.
|
|
461
|
+
*
|
|
462
|
+
* Used internally to implement {@link VirtualGridProxy.scrollToItem}. Can be used directly for
|
|
463
|
+
* advanced customization scenarios.
|
|
464
|
+
*/
|
|
465
|
+
function getRangeToScroll(index, mapping) {
|
|
466
|
+
if (index === undefined)
|
|
467
|
+
return [undefined, undefined];
|
|
468
|
+
return [mapping.itemOffset(index), mapping.itemSize(index)];
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Same logic as {@link VirtualGridProxy.scrollToItem} usable with your own {@link VirtualScroll}
|
|
472
|
+
*
|
|
473
|
+
* You're encouraged to put together your own combination of {@link VirtualScroll} and {@link DisplayGrid} for
|
|
474
|
+
* advanced customization scenarios. This function provides `ScrollToItem` functionality for use with your own {@link VirtualScroll}.
|
|
475
|
+
*/
|
|
476
|
+
function virtualGridScrollToItem(scrollRef, rowOffsetMapping, columnOffsetMapping, rowIndex, columnIndex, option) {
|
|
477
|
+
const scroll = scrollRef.current;
|
|
478
|
+
/* istanbul ignore if */
|
|
479
|
+
if (!scroll)
|
|
480
|
+
return;
|
|
481
|
+
const [rowOffset, rowSize] = getRangeToScroll(rowIndex, rowOffsetMapping);
|
|
482
|
+
const [colOffset, colSize] = getRangeToScroll(columnIndex, columnOffsetMapping);
|
|
483
|
+
scroll.scrollToArea(rowOffset, rowSize, colOffset, colSize, option);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Using a named function rather than => so that the name shows up in React Developer Tools
|
|
487
|
+
/**
|
|
488
|
+
* Virtual Scrolling Grid
|
|
489
|
+
*
|
|
490
|
+
* Accepts props defined by {@link VirtualGridProps}.
|
|
491
|
+
* Refs are forwarded to {@link VirtualGridProxy}.
|
|
492
|
+
* You must pass a single instance of {@link DisplayGridItem} as a child.
|
|
493
|
+
* @group Components
|
|
494
|
+
*/
|
|
495
|
+
const VirtualGrid = React.forwardRef(function VirtualGrid(props, ref) {
|
|
496
|
+
const { rowCount, rowOffsetMapping, columnCount, columnOffsetMapping, children, innerClassName, innerRender, itemData, itemKey, onScroll: onScrollCallback, ...scrollProps } = props;
|
|
497
|
+
// Total size is same as offset to item one off the end
|
|
498
|
+
const totalRowSize = rowOffsetMapping.itemOffset(rowCount);
|
|
499
|
+
const totalColumnSize = columnOffsetMapping.itemOffset(columnCount);
|
|
500
|
+
const [state, setState] = React.useState([0, 0]);
|
|
501
|
+
const scrollRef = React.useRef(null);
|
|
502
|
+
React.useImperativeHandle(ref, () => {
|
|
503
|
+
return {
|
|
504
|
+
scrollTo(rowOffset, columnOffset) {
|
|
505
|
+
const scroll = scrollRef.current;
|
|
506
|
+
/* istanbul ignore else */
|
|
507
|
+
if (scroll)
|
|
508
|
+
scroll.scrollTo(rowOffset, columnOffset);
|
|
509
|
+
},
|
|
510
|
+
scrollToItem(rowIndex, columnIndex, option) {
|
|
511
|
+
virtualGridScrollToItem(scrollRef, rowOffsetMapping, columnOffsetMapping, rowIndex, columnIndex, option);
|
|
512
|
+
},
|
|
513
|
+
get clientWidth() {
|
|
514
|
+
return scrollRef.current ? scrollRef.current.clientWidth : /* istanbul ignore next */ 0;
|
|
515
|
+
},
|
|
516
|
+
get clientHeight() {
|
|
517
|
+
return scrollRef.current ? scrollRef.current.clientHeight : /* istanbul ignore next */ 0;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
}, [rowOffsetMapping, columnOffsetMapping]);
|
|
243
521
|
// We can decide the JSX child type at runtime as long as we use a variable that uses the same capitalized
|
|
244
522
|
// naming convention as components do.
|
|
245
523
|
const ChildVar = children;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
let nextRowOffset = startRowOffset - renderRowOffset;
|
|
252
|
-
let rowIndex = 0, rowOffset = 0;
|
|
253
|
-
let nextColumnOffset = 0, columnIndex = 0, columnOffset = 0;
|
|
254
|
-
return (jsx(Outer, { className: className, onScroll: onScroll, ref: outerRef, style: { position: "relative", height, width, overflow: "auto", willChange: "transform" }, children: jsx(Inner, { className: innerClassName, style: { height: renderRowSize, width: renderColumnSize }, children: rowSizes.map((rowSize, rowArrayIndex) => (rowOffset = nextRowOffset,
|
|
255
|
-
nextRowOffset += rowSize,
|
|
256
|
-
rowIndex = startRowIndex + rowArrayIndex,
|
|
257
|
-
nextColumnOffset = startColumnOffset - renderColumnOffset,
|
|
258
|
-
jsx(Fragment, { children: columnSizes.map((columnSize, columnArrayIndex) => (columnOffset = nextColumnOffset,
|
|
259
|
-
nextColumnOffset += columnSize,
|
|
260
|
-
columnIndex = startColumnIndex + columnArrayIndex,
|
|
261
|
-
jsx(ChildVar, { data: itemData, rowIndex: rowIndex, columnIndex: columnIndex, isScrolling: useIsScrolling$1 ? isScrolling : undefined, style: { position: "absolute", top: rowOffset, height: rowSize, left: columnOffset, width: columnSize } }, itemKey(rowIndex, columnIndex, itemData)))) }, itemKey(rowIndex, 0, itemData)))) }) }));
|
|
524
|
+
return (jsx(VirtualScroll, { ref: scrollRef, ...scrollProps, scrollHeight: totalRowSize, scrollWidth: totalColumnSize, onScroll: (verticalOffset, horizontalOffset, verticalScrollState, horizontalScrollState) => {
|
|
525
|
+
setState([verticalOffset, horizontalOffset]);
|
|
526
|
+
if (onScrollCallback)
|
|
527
|
+
onScrollCallback(verticalOffset, horizontalOffset, verticalScrollState, horizontalScrollState);
|
|
528
|
+
}, children: ({ isScrolling }) => (jsx(AutoSizer, { style: { height: '100%', width: '100%' }, children: ({ height, width }) => (jsx(DisplayGrid, { innerClassName: innerClassName, innerRender: innerRender, rowOffset: state[0], columnOffset: state[1], height: height, rowCount: rowCount, columnCount: columnCount, itemData: itemData, itemKey: itemKey, isScrolling: isScrolling, rowOffsetMapping: rowOffsetMapping, columnOffsetMapping: columnOffsetMapping, width: width, children: ChildVar })) })) }));
|
|
262
529
|
});
|
|
263
530
|
|
|
264
|
-
|
|
531
|
+
/**
|
|
532
|
+
* Same logic as {@link VirtualListProxy.scrollToItem} usable with your own {@link VirtualScroll}
|
|
533
|
+
*
|
|
534
|
+
* You're encouraged to put together your own combination of {@link VirtualScroll} and {@link DisplayList} for
|
|
535
|
+
* advanced customization scenarios. This function provides `ScrollToItem` functionality for use with your own {@link VirtualScroll}.
|
|
536
|
+
*/
|
|
537
|
+
function virtualListScrollToItem(scrollRef, itemOffsetMapping, isVertical, index, option) {
|
|
538
|
+
const scroll = scrollRef.current;
|
|
539
|
+
/* istanbul ignore if */
|
|
540
|
+
if (!scroll)
|
|
541
|
+
return;
|
|
542
|
+
const itemOffset = itemOffsetMapping.itemOffset(index);
|
|
543
|
+
const itemSize = itemOffsetMapping.itemSize(index);
|
|
544
|
+
if (isVertical)
|
|
545
|
+
scroll.scrollToArea(itemOffset, itemSize, undefined, undefined, option);
|
|
546
|
+
else
|
|
547
|
+
scroll.scrollToArea(undefined, undefined, itemOffset, itemSize, option);
|
|
548
|
+
}
|
|
549
|
+
|
|
265
550
|
// Using a named function rather than => so that the name shows up in React Developer Tools
|
|
551
|
+
/**
|
|
552
|
+
* Virtual Scrolling List
|
|
553
|
+
*
|
|
554
|
+
* Accepts props defined by {@link VirtualListProps}.
|
|
555
|
+
* Refs are forwarded to {@link VirtualListProxy}.
|
|
556
|
+
* You must pass a single instance of {@link DisplayListItem} as a child.
|
|
557
|
+
* @group Components
|
|
558
|
+
*/
|
|
266
559
|
const VirtualList = React.forwardRef(function VirtualList(props, ref) {
|
|
267
|
-
const {
|
|
560
|
+
const { itemCount, itemOffsetMapping, children, layout = 'vertical', onScroll: onScrollCallback, innerClassName, innerRender, itemData, itemKey, ...scrollProps } = props;
|
|
268
561
|
// Total size is same as offset to item one off the end
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
const isScrolling = useIsScrolling(outerRef);
|
|
562
|
+
const renderSize = itemOffsetMapping.itemOffset(itemCount);
|
|
563
|
+
const [offset, setOffset] = React.useState(0);
|
|
564
|
+
const scrollRef = React.useRef(null);
|
|
273
565
|
const isVertical = layout === 'vertical';
|
|
274
566
|
React.useImperativeHandle(ref, () => {
|
|
275
567
|
return {
|
|
276
568
|
scrollTo(offset) {
|
|
277
|
-
const
|
|
278
|
-
/* istanbul ignore
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
569
|
+
const scroll = scrollRef.current;
|
|
570
|
+
/* istanbul ignore if */
|
|
571
|
+
if (!scroll)
|
|
572
|
+
return;
|
|
573
|
+
if (isVertical)
|
|
574
|
+
scroll.scrollTo(offset, undefined);
|
|
575
|
+
else
|
|
576
|
+
scroll.scrollTo(undefined, offset);
|
|
285
577
|
},
|
|
286
|
-
scrollToItem(index) {
|
|
287
|
-
|
|
578
|
+
scrollToItem(index, option) {
|
|
579
|
+
virtualListScrollToItem(scrollRef, itemOffsetMapping, isVertical, index, option);
|
|
288
580
|
}
|
|
289
581
|
};
|
|
290
|
-
}, [itemOffsetMapping, isVertical
|
|
291
|
-
function onScroll(event) {
|
|
292
|
-
if (isVertical) {
|
|
293
|
-
const { clientHeight, scrollHeight, scrollTop, scrollLeft } = event.currentTarget;
|
|
294
|
-
const [newScrollTop, newScrollState] = onScrollExtent(clientHeight, scrollHeight, scrollTop);
|
|
295
|
-
if (newScrollTop != scrollTop && outerRef.current)
|
|
296
|
-
outerRef.current.scrollTo(scrollLeft, newScrollTop);
|
|
297
|
-
onScrollCallback?.(newScrollState.scrollOffset + newScrollState.renderOffset, newScrollState);
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
const { clientWidth, scrollWidth, scrollTop, scrollLeft } = event.currentTarget;
|
|
301
|
-
const [newScrollLeft, newScrollState] = onScrollExtent(clientWidth, scrollWidth, scrollLeft);
|
|
302
|
-
if (newScrollLeft != scrollLeft && outerRef.current)
|
|
303
|
-
outerRef.current.scrollTo(newScrollLeft, scrollTop);
|
|
304
|
-
onScrollCallback?.(newScrollState.scrollOffset + newScrollState.renderOffset, newScrollState);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const [startIndex, startOffset, sizes] = getRangeToRender(itemCount, itemOffsetMapping, isVertical ? height : width, scrollOffset + renderOffset);
|
|
582
|
+
}, [itemOffsetMapping, isVertical]);
|
|
308
583
|
// We can decide the JSX child type at runtime as long as we use a variable that uses the same capitalized
|
|
309
584
|
// naming convention as components do.
|
|
310
585
|
const ChildVar = children;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
let index, offset;
|
|
318
|
-
return (jsx(Outer, { className: className, onScroll: onScroll, ref: outerRef, style: { position: "relative", height, width, overflow: "auto", willChange: "transform" }, children: jsx(Inner, { className: innerClassName, style: { height: isVertical ? renderSize : "100%", width: isVertical ? "100%" : renderSize }, children: sizes.map((size, arrayIndex) => (offset = nextOffset,
|
|
319
|
-
nextOffset += size,
|
|
320
|
-
index = startIndex + arrayIndex,
|
|
321
|
-
jsx(ChildVar, { data: itemData, index: index, isScrolling: useIsScrolling$1 ? isScrolling : undefined, style: {
|
|
322
|
-
position: "absolute",
|
|
323
|
-
top: isVertical ? offset : undefined,
|
|
324
|
-
left: isVertical ? undefined : offset,
|
|
325
|
-
height: isVertical ? size : "100%",
|
|
326
|
-
width: isVertical ? "100%" : size,
|
|
327
|
-
} }, itemKey(index, itemData)))) }) }));
|
|
586
|
+
return (jsx(VirtualScroll, { ref: scrollRef, ...scrollProps, scrollHeight: isVertical ? renderSize : undefined, scrollWidth: isVertical ? undefined : renderSize, onScroll: (verticalOffset, horizontalOffset, verticalScrollState, horizontalScrollState) => {
|
|
587
|
+
const newOffset = isVertical ? verticalOffset : horizontalOffset;
|
|
588
|
+
setOffset(newOffset);
|
|
589
|
+
if (onScrollCallback)
|
|
590
|
+
onScrollCallback(newOffset, isVertical ? verticalScrollState : horizontalScrollState);
|
|
591
|
+
}, children: ({ isScrolling }) => (jsx(AutoSizer, { style: { height: '100%', width: '100%' }, children: ({ height, width }) => (jsx(DisplayList, { innerClassName: innerClassName, innerRender: innerRender, layout: layout, offset: offset, height: height, itemCount: itemCount, itemData: itemData, itemKey: itemKey, isScrolling: isScrolling, itemOffsetMapping: itemOffsetMapping, width: width, children: ChildVar })) })) }));
|
|
328
592
|
});
|
|
329
593
|
|
|
330
594
|
class FixedSizeItemOffsetMapping {
|
|
@@ -349,6 +613,11 @@ class FixedSizeItemOffsetMapping {
|
|
|
349
613
|
return [itemIndex, startOffset];
|
|
350
614
|
}
|
|
351
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* Returns an instance of {@link ItemOffsetMapping} suitable for use when all items have a fixed size.
|
|
618
|
+
*
|
|
619
|
+
* @param itemSize - Size to use for all items
|
|
620
|
+
*/
|
|
352
621
|
function useFixedSizeItemOffsetMapping(itemSize) {
|
|
353
622
|
return new FixedSizeItemOffsetMapping(itemSize);
|
|
354
623
|
}
|
|
@@ -374,7 +643,7 @@ class VariableSizeItemOffsetMapping {
|
|
|
374
643
|
return (itemIndex < this.sizes.length) ? this.sizes[itemIndex] : this.defaultItemSize;
|
|
375
644
|
}
|
|
376
645
|
itemOffset(itemIndex) {
|
|
377
|
-
|
|
646
|
+
let offset = 0;
|
|
378
647
|
let length = this.sizes.length;
|
|
379
648
|
if (itemIndex > length) {
|
|
380
649
|
const numDefaultSize = itemIndex - length;
|
|
@@ -389,7 +658,7 @@ class VariableSizeItemOffsetMapping {
|
|
|
389
658
|
return offset;
|
|
390
659
|
}
|
|
391
660
|
offsetToItem(offset) {
|
|
392
|
-
|
|
661
|
+
let startOffset = 0;
|
|
393
662
|
const length = this.sizes.length;
|
|
394
663
|
for (let i = 0; i < length; i++) {
|
|
395
664
|
const size = this.sizes[i];
|
|
@@ -403,9 +672,15 @@ class VariableSizeItemOffsetMapping {
|
|
|
403
672
|
return [itemIndex + length, startOffset];
|
|
404
673
|
}
|
|
405
674
|
}
|
|
675
|
+
/**
|
|
676
|
+
* Returns an instance of {@link ItemOffsetMapping} suitable for use when initial items have variable sizes.
|
|
677
|
+
*
|
|
678
|
+
* @param defaultItemSize - Size to use for all other items
|
|
679
|
+
* @param sizes - Array of sizes to use for the initial items, one size per item
|
|
680
|
+
*/
|
|
406
681
|
function useVariableSizeItemOffsetMapping(defaultItemSize, sizes) {
|
|
407
682
|
return new VariableSizeItemOffsetMapping(defaultItemSize, sizes || []);
|
|
408
683
|
}
|
|
409
684
|
|
|
410
|
-
export { VirtualGrid, VirtualList, useFixedSizeItemOffsetMapping, useVariableSizeItemOffsetMapping };
|
|
685
|
+
export { AutoSizer, DisplayGrid, DisplayList, VirtualContainer, VirtualGrid, VirtualList, VirtualScroll, getOffsetToScrollRange, getRangeToScroll, useFixedSizeItemOffsetMapping, useVariableSizeItemOffsetMapping, virtualGridScrollToItem, virtualListScrollToItem };
|
|
411
686
|
//# sourceMappingURL=index.js.map
|