@candidstartup/react-virtual-scroll 0.5.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/dist/index.d.ts +428 -141
- package/dist/index.js +350 -140
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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)
|
|
@@ -206,40 +244,58 @@ function useIsScrolling(element = window) {
|
|
|
206
244
|
return scrollCount > 0;
|
|
207
245
|
}
|
|
208
246
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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;
|
|
221
273
|
}
|
|
274
|
+
|
|
222
275
|
// Using a named function rather than => so that the name shows up in React Developer Tools
|
|
223
276
|
/**
|
|
224
|
-
* Virtual Scrolling
|
|
277
|
+
* Customizable Virtual Scrolling Component
|
|
225
278
|
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
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}.
|
|
229
284
|
* @group Components
|
|
230
285
|
*/
|
|
231
|
-
const
|
|
232
|
-
const { width, height,
|
|
233
|
-
// Total size is same as offset to item one off the end
|
|
234
|
-
const totalRowSize = rowOffsetMapping.itemOffset(rowCount);
|
|
235
|
-
const totalColumnSize = columnOffsetMapping.itemOffset(columnCount);
|
|
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;
|
|
236
288
|
const outerRef = React.useRef(null);
|
|
237
|
-
const { scrollOffset: scrollRowOffset, renderOffset: renderRowOffset, renderSize: renderRowSize, onScroll: onScrollRow, doScrollTo: doScrollToRow } = useVirtualScroll(
|
|
238
|
-
const
|
|
239
|
-
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);
|
|
240
294
|
React.useImperativeHandle(ref, () => {
|
|
241
295
|
return {
|
|
242
296
|
scrollTo(rowOffset, columnOffset) {
|
|
297
|
+
if (rowOffset === undefined && columnOffset === undefined)
|
|
298
|
+
return;
|
|
243
299
|
const outer = outerRef.current;
|
|
244
300
|
/* istanbul ignore else */
|
|
245
301
|
if (outer) {
|
|
@@ -251,10 +307,14 @@ const VirtualGrid = React.forwardRef(function VirtualGrid(props, ref) {
|
|
|
251
307
|
outer.scrollTo(options);
|
|
252
308
|
}
|
|
253
309
|
},
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
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);
|
|
258
318
|
},
|
|
259
319
|
get clientWidth() {
|
|
260
320
|
return outerRef.current ? outerRef.current.clientWidth : /* istanbul ignore next */ 0;
|
|
@@ -263,7 +323,7 @@ const VirtualGrid = React.forwardRef(function VirtualGrid(props, ref) {
|
|
|
263
323
|
return outerRef.current ? outerRef.current.clientHeight : /* istanbul ignore next */ 0;
|
|
264
324
|
}
|
|
265
325
|
};
|
|
266
|
-
}, [
|
|
326
|
+
}, [doScrollToRow, doScrollToColumn, currentVerticalOffset, currentHorizontalOffset]);
|
|
267
327
|
function onScroll(event) {
|
|
268
328
|
const { clientWidth, clientHeight, scrollWidth, scrollHeight, scrollLeft, scrollTop } = event.currentTarget;
|
|
269
329
|
const [newScrollTop, newRowScrollState] = onScrollRow(clientHeight, scrollHeight, scrollTop);
|
|
@@ -272,113 +332,263 @@ const VirtualGrid = React.forwardRef(function VirtualGrid(props, ref) {
|
|
|
272
332
|
outerRef.current.scrollTo(newScrollLeft, newScrollTop);
|
|
273
333
|
onScrollCallback?.(newRowScrollState.scrollOffset + newRowScrollState.renderOffset, newColumnScrollState.scrollOffset + newColumnScrollState.renderOffset, newRowScrollState, newColumnScrollState);
|
|
274
334
|
}
|
|
275
|
-
const
|
|
276
|
-
|
|
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;
|
|
277
416
|
// We can decide the JSX child type at runtime as long as we use a variable that uses the same capitalized
|
|
278
|
-
// naming convention as components do.
|
|
417
|
+
// naming convention as components do.
|
|
279
418
|
const ChildVar = children;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
nextRowOffset += rowSize,
|
|
290
|
-
rowIndex = startRowIndex + rowArrayIndex,
|
|
291
|
-
nextColumnOffset = startColumnOffset - renderColumnOffset,
|
|
292
|
-
jsx(Fragment, { children: columnSizes.map((columnSize, columnArrayIndex) => (columnOffset = nextColumnOffset,
|
|
293
|
-
nextColumnOffset += columnSize,
|
|
294
|
-
columnIndex = startColumnIndex + columnArrayIndex,
|
|
295
|
-
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)))) }) }));
|
|
296
|
-
});
|
|
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
|
+
}
|
|
297
428
|
|
|
298
|
-
const defaultItemKey = (
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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)))) }) }));
|
|
304
457
|
}
|
|
305
|
-
|
|
306
|
-
|
|
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]);
|
|
521
|
+
// We can decide the JSX child type at runtime as long as we use a variable that uses the same capitalized
|
|
522
|
+
// naming convention as components do.
|
|
523
|
+
const ChildVar = children;
|
|
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 })) })) }));
|
|
307
529
|
});
|
|
308
|
-
|
|
309
|
-
|
|
530
|
+
|
|
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);
|
|
310
548
|
}
|
|
549
|
+
|
|
311
550
|
// Using a named function rather than => so that the name shows up in React Developer Tools
|
|
312
551
|
/**
|
|
313
552
|
* Virtual Scrolling List
|
|
314
553
|
*
|
|
315
554
|
* Accepts props defined by {@link VirtualListProps}.
|
|
316
555
|
* Refs are forwarded to {@link VirtualListProxy}.
|
|
317
|
-
* You must pass a single instance of {@link
|
|
556
|
+
* You must pass a single instance of {@link DisplayListItem} as a child.
|
|
318
557
|
* @group Components
|
|
319
558
|
*/
|
|
320
559
|
const VirtualList = React.forwardRef(function VirtualList(props, ref) {
|
|
321
|
-
const {
|
|
560
|
+
const { itemCount, itemOffsetMapping, children, layout = 'vertical', onScroll: onScrollCallback, innerClassName, innerRender, itemData, itemKey, ...scrollProps } = props;
|
|
322
561
|
// Total size is same as offset to item one off the end
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
const isScrolling = useIsScrolling(outerRef);
|
|
562
|
+
const renderSize = itemOffsetMapping.itemOffset(itemCount);
|
|
563
|
+
const [offset, setOffset] = React.useState(0);
|
|
564
|
+
const scrollRef = React.useRef(null);
|
|
327
565
|
const isVertical = layout === 'vertical';
|
|
328
566
|
React.useImperativeHandle(ref, () => {
|
|
329
567
|
return {
|
|
330
568
|
scrollTo(offset) {
|
|
331
|
-
const
|
|
332
|
-
/* istanbul ignore
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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);
|
|
339
577
|
},
|
|
340
|
-
scrollToItem(index) {
|
|
341
|
-
|
|
578
|
+
scrollToItem(index, option) {
|
|
579
|
+
virtualListScrollToItem(scrollRef, itemOffsetMapping, isVertical, index, option);
|
|
342
580
|
}
|
|
343
581
|
};
|
|
344
|
-
}, [itemOffsetMapping, isVertical
|
|
345
|
-
function onScroll(event) {
|
|
346
|
-
if (isVertical) {
|
|
347
|
-
const { clientHeight, scrollHeight, scrollTop, scrollLeft } = event.currentTarget;
|
|
348
|
-
const [newScrollTop, newScrollState] = onScrollExtent(clientHeight, scrollHeight, scrollTop);
|
|
349
|
-
if (newScrollTop != scrollTop && outerRef.current)
|
|
350
|
-
outerRef.current.scrollTo(scrollLeft, newScrollTop);
|
|
351
|
-
onScrollCallback?.(newScrollState.scrollOffset + newScrollState.renderOffset, newScrollState);
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
const { clientWidth, scrollWidth, scrollTop, scrollLeft } = event.currentTarget;
|
|
355
|
-
const [newScrollLeft, newScrollState] = onScrollExtent(clientWidth, scrollWidth, scrollLeft);
|
|
356
|
-
if (newScrollLeft != scrollLeft && outerRef.current)
|
|
357
|
-
outerRef.current.scrollTo(newScrollLeft, scrollTop);
|
|
358
|
-
onScrollCallback?.(newScrollState.scrollOffset + newScrollState.renderOffset, newScrollState);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const [startIndex, startOffset, sizes] = getRangeToRender(itemCount, itemOffsetMapping, isVertical ? height : width, scrollOffset + renderOffset);
|
|
582
|
+
}, [itemOffsetMapping, isVertical]);
|
|
362
583
|
// We can decide the JSX child type at runtime as long as we use a variable that uses the same capitalized
|
|
363
584
|
// naming convention as components do.
|
|
364
585
|
const ChildVar = children;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
let index, offset;
|
|
372
|
-
return (jsx(Outer, { className: className, render: outerRender, onScroll: onScroll, ref: outerRef, style: { position: "relative", height, width, overflow: "auto", willChange: "transform" }, children: jsx(Inner, { className: innerClassName, render: innerRender, style: { height: isVertical ? renderSize : "100%", width: isVertical ? "100%" : renderSize }, children: sizes.map((size, arrayIndex) => (offset = nextOffset,
|
|
373
|
-
nextOffset += size,
|
|
374
|
-
index = startIndex + arrayIndex,
|
|
375
|
-
jsx(ChildVar, { data: itemData, index: index, isScrolling: useIsScrolling$1 ? isScrolling : undefined, style: {
|
|
376
|
-
position: "absolute",
|
|
377
|
-
top: isVertical ? offset : undefined,
|
|
378
|
-
left: isVertical ? undefined : offset,
|
|
379
|
-
height: isVertical ? size : "100%",
|
|
380
|
-
width: isVertical ? "100%" : size,
|
|
381
|
-
} }, 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 })) })) }));
|
|
382
592
|
});
|
|
383
593
|
|
|
384
594
|
class FixedSizeItemOffsetMapping {
|
|
@@ -472,5 +682,5 @@ function useVariableSizeItemOffsetMapping(defaultItemSize, sizes) {
|
|
|
472
682
|
return new VariableSizeItemOffsetMapping(defaultItemSize, sizes || []);
|
|
473
683
|
}
|
|
474
684
|
|
|
475
|
-
export { VirtualGrid, VirtualList, useFixedSizeItemOffsetMapping, useVariableSizeItemOffsetMapping };
|
|
685
|
+
export { AutoSizer, DisplayGrid, DisplayList, VirtualContainer, VirtualGrid, VirtualList, VirtualScroll, getOffsetToScrollRange, getRangeToScroll, useFixedSizeItemOffsetMapping, useVariableSizeItemOffsetMapping, virtualGridScrollToItem, virtualListScrollToItem };
|
|
476
686
|
//# sourceMappingURL=index.js.map
|