@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/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
- function getRangeToRender(itemCount, itemOffsetMapping, clientExtent, scrollOffset) {
5
- if (itemCount == 0) {
6
- return [0, 0, []];
7
- }
8
- var [itemIndex, startOffset] = itemOffsetMapping.offsetToItem(scrollOffset);
9
- itemIndex = Math.max(0, Math.min(itemCount - 1, itemIndex));
10
- var endOffset = scrollOffset + clientExtent;
11
- const overscanBackward = 1;
12
- const overscanForward = 1;
13
- for (let step = 0; step < overscanBackward && itemIndex > 0; step++) {
14
- itemIndex--;
15
- startOffset -= itemOffsetMapping.itemSize(itemIndex);
16
- }
17
- const startIndex = itemIndex;
18
- var offset = startOffset;
19
- const sizes = [];
20
- while (offset < endOffset && itemIndex < itemCount) {
21
- const size = itemOffsetMapping.itemSize(itemIndex);
22
- sizes.push(size);
23
- offset += size;
24
- itemIndex++;
25
- }
26
- for (let step = 0; step < overscanForward && itemIndex < itemCount; step++) {
27
- const size = itemOffsetMapping.itemSize(itemIndex);
28
- sizes.push(size);
29
- itemIndex++;
30
- }
31
- return [startIndex, startOffset, sizes];
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
- const defaultItemKey$1 = (rowIndex, columnIndex, _data) => `${rowIndex}:${columnIndex}`;
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
- const VirtualGrid = React.forwardRef(function VirtualGrid(props, ref) {
212
- const { width, height, rowCount, rowOffsetMapping, columnCount, columnOffsetMapping, children, className, innerClassName, itemData = undefined, itemKey = defaultItemKey$1, onScroll: onScrollCallback, useIsScrolling: useIsScrolling$1 = false } = props;
213
- // Total size is same as offset to item one off the end
214
- const totalRowSize = rowOffsetMapping.itemOffset(rowCount);
215
- const totalColumnSize = columnOffsetMapping.itemOffset(columnCount);
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(totalRowSize, props.maxCssSize, props.minNumPages);
218
- const { scrollOffset: scrollColumnOffset, renderOffset: renderColumnOffset, renderSize: renderColumnSize, onScroll: onScrollColumn, doScrollTo: doScrollToColumn } = useVirtualScroll(totalColumnSize, props.maxCssSize, props.minNumPages);
219
- const isScrolling = useIsScrolling(outerRef);
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
- outer.scrollTo(doScrollToColumn(columnOffset, outer.clientWidth), doScrollToRow(rowOffset, outer.clientHeight));
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
- scrollToItem(rowIndex, columnIndex) {
229
- this.scrollTo(rowOffsetMapping.itemOffset(rowIndex), columnOffsetMapping.itemOffset(columnIndex));
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
- }, [rowOffsetMapping, columnOffsetMapping, doScrollToRow, doScrollToColumn]);
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 [startRowIndex, startRowOffset, rowSizes] = getRangeToRender(rowCount, rowOffsetMapping, height, scrollRowOffset + renderRowOffset);
242
- const [startColumnIndex, startColumnOffset, columnSizes] = getRangeToRender(columnCount, columnOffsetMapping, width, scrollColumnOffset + renderColumnOffset);
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
- const Outer = props.outerComponent || 'div';
247
- const Inner = props.innerComponent || 'div';
248
- // Being far too clever. Implementing a complex iteration in JSX in a map expression by abusing the comma operator.
249
- // You can't declare local variables in an expression so they need to be hoisted out of the JSX. The comma operator
250
- // returns the result of the final statement which makes the iteration a little clumsier.
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
- const defaultItemKey = (index, _data) => index;
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 { width, height, itemCount, itemOffsetMapping, children, className, innerClassName, itemData = undefined, itemKey = defaultItemKey, layout = 'vertical', onScroll: onScrollCallback, useIsScrolling: useIsScrolling$1 = false } = props;
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 totalSize = itemOffsetMapping.itemOffset(itemCount);
270
- const outerRef = React.useRef(null);
271
- const { scrollOffset, renderOffset, renderSize, onScroll: onScrollExtent, doScrollTo } = useVirtualScroll(totalSize, props.maxCssSize, props.minNumPages);
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 outer = outerRef.current;
278
- /* istanbul ignore else */
279
- if (outer) {
280
- if (isVertical)
281
- outer.scrollTo(0, doScrollTo(offset, outer.clientHeight));
282
- else
283
- outer.scrollTo(doScrollTo(offset, outer.clientWidth), 0);
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
- this.scrollTo(itemOffsetMapping.itemOffset(index));
578
+ scrollToItem(index, option) {
579
+ virtualListScrollToItem(scrollRef, itemOffsetMapping, isVertical, index, option);
288
580
  }
289
581
  };
290
- }, [itemOffsetMapping, isVertical, doScrollTo]);
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
- const Outer = props.outerComponent || 'div';
312
- const Inner = props.innerComponent || 'div';
313
- // Being far too clever. Implementing a complex iteration in JSX in a map expression by abusing the comma operator.
314
- // You can't declare local variables in an expression so they need to be hoisted out of the JSX. The comma operator
315
- // returns the result of the final statement which makes the iteration a little clumsier.
316
- let nextOffset = startOffset - renderOffset;
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
- var offset = 0;
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
- var startOffset = 0;
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