virtual-scroller 1.14.0 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +403 -254
- package/bundle/index-dom-bypass.html +197 -0
- package/bundle/index-dom-grid.html +203 -0
- package/bundle/index-dom-scrollableContainer.html +214 -0
- package/bundle/index-dom-tbody-scrollableContainer.html +81 -0
- package/bundle/index-dom-tbody.html +65 -0
- package/bundle/index-dom.html +114 -84
- package/bundle/index-react-bypass.html +194 -0
- package/bundle/{index-bypass.html → index-react-grid.html} +122 -120
- package/bundle/index-react-hook.html +209 -0
- package/bundle/index-react-scrollableContainer.html +207 -0
- package/bundle/index-react-strictMode.html +193 -0
- package/bundle/index-react-tbody-scrollableContainer.html +94 -0
- package/bundle/index-react-tbody.html +78 -0
- package/bundle/{messages.js → items.js} +1 -1
- package/bundle/lib/babel.min.js +25 -0
- package/bundle/lib/prop-types.min.js +1 -0
- package/bundle/lib/react-dom.development.js +29924 -0
- package/bundle/lib/react-dom.production.min.js +267 -0
- package/bundle/lib/react.development.js +3343 -0
- package/bundle/lib/react.production.min.js +31 -0
- package/bundle/style.base.css +33 -0
- package/{website/style.css → bundle/style.list.css} +10 -43
- package/bundle/style.list.grid.css +23 -0
- package/bundle/virtual-scroller-dom.js +1 -1
- package/bundle/virtual-scroller-dom.js.map +1 -1
- package/bundle/virtual-scroller-react.js +1 -1
- package/bundle/virtual-scroller-react.js.map +1 -1
- package/bundle/virtual-scroller.js +1 -1
- package/bundle/virtual-scroller.js.map +1 -1
- package/commonjs/BeforeResize.js +1 -2
- package/commonjs/BeforeResize.js.map +1 -1
- package/commonjs/DOM/VirtualScroller.js +7 -13
- package/commonjs/DOM/VirtualScroller.js.map +1 -1
- package/commonjs/DOM/tbody.js +6 -6
- package/commonjs/DOM/tbody.js.map +1 -1
- package/commonjs/ItemHeights.js +10 -13
- package/commonjs/ItemHeights.js.map +1 -1
- package/commonjs/Layout.defaults.js +21 -0
- package/commonjs/Layout.defaults.js.map +1 -0
- package/commonjs/Layout.js +75 -64
- package/commonjs/Layout.js.map +1 -1
- package/commonjs/Layout.test.js +8 -4
- package/commonjs/Layout.test.js.map +1 -1
- package/commonjs/VirtualScroller.constructor.js +38 -4
- package/commonjs/VirtualScroller.constructor.js.map +1 -1
- package/commonjs/VirtualScroller.items.js +50 -4
- package/commonjs/VirtualScroller.items.js.map +1 -1
- package/commonjs/VirtualScroller.js +23 -14
- package/commonjs/VirtualScroller.js.map +1 -1
- package/commonjs/VirtualScroller.layout.js +40 -29
- package/commonjs/VirtualScroller.layout.js.map +1 -1
- package/commonjs/VirtualScroller.onContainerResize.js +1 -2
- package/commonjs/VirtualScroller.onContainerResize.js.map +1 -1
- package/commonjs/VirtualScroller.state.js +10 -9
- package/commonjs/VirtualScroller.state.js.map +1 -1
- package/commonjs/VirtualScroller.verticalSpacing.js +39 -6
- package/commonjs/VirtualScroller.verticalSpacing.js.map +1 -1
- package/commonjs/react/VirtualScroller.js +124 -131
- package/commonjs/react/VirtualScroller.js.map +1 -1
- package/commonjs/react/useClassName.js +2 -2
- package/commonjs/react/useClassName.js.map +1 -1
- package/commonjs/react/useCreateVirtualScroller.js +64 -0
- package/commonjs/react/useCreateVirtualScroller.js.map +1 -0
- package/commonjs/react/useInstanceMethods.js +4 -4
- package/commonjs/react/useInstanceMethods.js.map +1 -1
- package/commonjs/react/useItemKeys.js +28 -5
- package/commonjs/react/useItemKeys.js.map +1 -1
- package/commonjs/react/useMergeRefs.js +52 -0
- package/commonjs/react/useMergeRefs.js.map +1 -0
- package/commonjs/react/useOnItemHeightDidChange.js +28 -12
- package/commonjs/react/useOnItemHeightDidChange.js.map +1 -1
- package/commonjs/react/useSetItemState.js +31 -12
- package/commonjs/react/useSetItemState.js.map +1 -1
- package/commonjs/react/{useVirtualScrollerStartStop.js → useStartStopVirtualScroller.js} +1 -1
- package/commonjs/react/{useVirtualScrollerStartStop.js.map → useStartStopVirtualScroller.js.map} +1 -1
- package/commonjs/react/useState.js +9 -9
- package/commonjs/react/useState.js.map +1 -1
- package/commonjs/react/{useStateNoStaleBug.js → useStateWithRepeatableRead.js} +3 -3
- package/commonjs/react/useStateWithRepeatableRead.js.map +1 -0
- package/commonjs/react/useStyle.js +28 -4
- package/commonjs/react/useStyle.js.map +1 -1
- package/commonjs/react/useValidateTableBodyItemsContainer.js +24 -0
- package/commonjs/react/useValidateTableBodyItemsContainer.js.map +1 -0
- package/commonjs/react/useVirtualScroller.js +142 -42
- package/commonjs/react/useVirtualScroller.js.map +1 -1
- package/commonjs/test/ItemsContainer.js +10 -10
- package/commonjs/test/ItemsContainer.js.map +1 -1
- package/commonjs/test/VirtualScroller.js +25 -10
- package/commonjs/test/VirtualScroller.js.map +1 -1
- package/dom/index.d.ts +6 -5
- package/index.d.ts +19 -8
- package/modules/BeforeResize.js +1 -2
- package/modules/BeforeResize.js.map +1 -1
- package/modules/DOM/VirtualScroller.js +7 -13
- package/modules/DOM/VirtualScroller.js.map +1 -1
- package/modules/DOM/tbody.js +4 -4
- package/modules/DOM/tbody.js.map +1 -1
- package/modules/ItemHeights.js +11 -14
- package/modules/ItemHeights.js.map +1 -1
- package/modules/Layout.defaults.js +11 -0
- package/modules/Layout.defaults.js.map +1 -0
- package/modules/Layout.js +74 -64
- package/modules/Layout.js.map +1 -1
- package/modules/Layout.test.js +8 -4
- package/modules/Layout.test.js.map +1 -1
- package/modules/VirtualScroller.constructor.js +37 -4
- package/modules/VirtualScroller.constructor.js.map +1 -1
- package/modules/VirtualScroller.items.js +51 -5
- package/modules/VirtualScroller.items.js.map +1 -1
- package/modules/VirtualScroller.js +23 -14
- package/modules/VirtualScroller.js.map +1 -1
- package/modules/VirtualScroller.layout.js +40 -29
- package/modules/VirtualScroller.layout.js.map +1 -1
- package/modules/VirtualScroller.onContainerResize.js +1 -2
- package/modules/VirtualScroller.onContainerResize.js.map +1 -1
- package/modules/VirtualScroller.state.js +10 -9
- package/modules/VirtualScroller.state.js.map +1 -1
- package/modules/VirtualScroller.verticalSpacing.js +38 -6
- package/modules/VirtualScroller.verticalSpacing.js.map +1 -1
- package/modules/react/VirtualScroller.js +122 -124
- package/modules/react/VirtualScroller.js.map +1 -1
- package/modules/react/useClassName.js +3 -3
- package/modules/react/useClassName.js.map +1 -1
- package/modules/react/useCreateVirtualScroller.js +53 -0
- package/modules/react/useCreateVirtualScroller.js.map +1 -0
- package/modules/react/useInstanceMethods.js +4 -4
- package/modules/react/useInstanceMethods.js.map +1 -1
- package/modules/react/useItemKeys.js +28 -5
- package/modules/react/useItemKeys.js.map +1 -1
- package/modules/react/useMergeRefs.js +44 -0
- package/modules/react/useMergeRefs.js.map +1 -0
- package/modules/react/useOnItemHeightDidChange.js +28 -12
- package/modules/react/useOnItemHeightDidChange.js.map +1 -1
- package/modules/react/useSetItemState.js +31 -12
- package/modules/react/useSetItemState.js.map +1 -1
- package/modules/react/{useVirtualScrollerStartStop.js → useStartStopVirtualScroller.js} +1 -1
- package/modules/react/{useVirtualScrollerStartStop.js.map → useStartStopVirtualScroller.js.map} +1 -1
- package/modules/react/useState.js +9 -9
- package/modules/react/useState.js.map +1 -1
- package/modules/react/{useStateNoStaleBug.js → useStateWithRepeatableRead.js} +2 -2
- package/modules/react/useStateWithRepeatableRead.js.map +1 -0
- package/modules/react/useStyle.js +27 -4
- package/modules/react/useStyle.js.map +1 -1
- package/modules/react/useValidateTableBodyItemsContainer.js +16 -0
- package/modules/react/useValidateTableBodyItemsContainer.js.map +1 -0
- package/modules/react/useVirtualScroller.js +136 -42
- package/modules/react/useVirtualScroller.js.map +1 -1
- package/modules/test/ItemsContainer.js +10 -10
- package/modules/test/ItemsContainer.js.map +1 -1
- package/modules/test/VirtualScroller.js +25 -10
- package/modules/test/VirtualScroller.js.map +1 -1
- package/package.json +4 -1
- package/react/as.d.ts +42 -0
- package/react/index.cjs +2 -1
- package/react/index.d.ts +248 -63
- package/react/index.js +1 -0
- package/rollup.config.mjs +15 -1
- package/source/BeforeResize.js +1 -2
- package/source/DOM/VirtualScroller.js +7 -13
- package/source/DOM/tbody.js +5 -5
- package/source/ItemHeights.js +11 -12
- package/source/Layout.defaults.js +8 -0
- package/source/Layout.js +66 -53
- package/source/Layout.test.js +7 -2
- package/source/VirtualScroller.constructor.js +27 -4
- package/source/VirtualScroller.items.js +47 -2
- package/source/VirtualScroller.js +23 -14
- package/source/VirtualScroller.layout.js +41 -28
- package/source/VirtualScroller.onContainerResize.js +1 -2
- package/source/VirtualScroller.state.js +10 -11
- package/source/VirtualScroller.verticalSpacing.js +32 -6
- package/source/react/VirtualScroller.js +135 -133
- package/source/react/useClassName.js +3 -3
- package/source/react/useCreateVirtualScroller.js +65 -0
- package/source/react/useInstanceMethods.js +12 -4
- package/source/react/useItemKeys.js +22 -4
- package/source/react/useMergeRefs.js +45 -0
- package/source/react/useOnItemHeightDidChange.js +29 -10
- package/source/react/useSetItemState.js +32 -10
- package/source/react/useState.js +6 -6
- package/source/react/{useStateNoStaleBug.js → useStateWithRepeatableRead.js} +1 -1
- package/source/react/useStyle.js +18 -2
- package/source/react/useValidateTableBodyItemsContainer.js +16 -0
- package/source/react/useVirtualScroller.js +155 -47
- package/source/test/ItemsContainer.js +10 -10
- package/source/test/VirtualScroller.js +16 -10
- package/website/index-dom-bypass.html +197 -0
- package/website/index-dom-grid.html +203 -0
- package/website/index-dom-scrollableContainer.html +214 -0
- package/website/index-dom-tbody-scrollableContainer.html +81 -0
- package/website/index-dom-tbody.html +65 -0
- package/website/index-dom.html +116 -84
- package/website/index-react-bypass.html +194 -0
- package/website/index-react-grid.html +197 -0
- package/website/index-react-hook.html +209 -0
- package/website/index-react-scrollableContainer.html +207 -0
- package/website/index-react-strictMode.html +193 -0
- package/website/index-react-tbody-scrollableContainer.html +94 -0
- package/website/index-react-tbody.html +78 -0
- package/website/index-react.html +193 -0
- package/website/index.html +120 -111
- package/website/{messages.js → items.js} +1 -1
- package/website/lib/babel.min.js +25 -0
- package/website/lib/prop-types.min.js +1 -0
- package/website/lib/react-dom.development.js +29924 -0
- package/website/lib/react-dom.production.min.js +267 -0
- package/website/lib/react.development.js +3343 -0
- package/website/lib/react.production.min.js +31 -0
- package/website/style.base.css +33 -0
- package/website/style.list.css +92 -0
- package/website/style.list.grid.css +23 -0
- package/bundle/index-grid.html +0 -216
- package/bundle/index-scrollableContainer.html +0 -208
- package/bundle/index-tbody-scrollableContainer.html +0 -70
- package/bundle/index-tbody.html +0 -57
- package/bundle/on-scroll-to-dom.js +0 -2
- package/bundle/on-scroll-to-dom.js.map +0 -1
- package/bundle/on-scroll-to-react.js +0 -2
- package/bundle/on-scroll-to-react.js.map +0 -1
- package/bundle/on-scroll-to.js +0 -2
- package/bundle/on-scroll-to.js.map +0 -1
- package/commonjs/react/useStateNoStaleBug.js.map +0 -1
- package/modules/react/useStateNoStaleBug.js.map +0 -1
- package/website/index-bypass.html +0 -185
- package/website/index-grid.html +0 -216
- package/website/index-scrollableContainer.html +0 -208
- package/website/index-tbody-scrollableContainer.html +0 -70
- package/website/index-tbody.html +0 -57
- package/website/lib/on-scroll-to-dom.js +0 -2
- package/website/lib/on-scroll-to-dom.js.map +0 -1
- package/website/lib/on-scroll-to-react.js +0 -2
- package/website/lib/on-scroll-to-react.js.map +0 -1
- /package/source/react/{useVirtualScrollerStartStop.js → useStartStopVirtualScroller.js} +0 -0
package/source/ItemHeights.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import log, { warn
|
|
1
|
+
import log, { warn } from './utility/debug.js'
|
|
2
2
|
|
|
3
3
|
export default class ItemHeights {
|
|
4
4
|
constructor({
|
|
5
5
|
container,
|
|
6
|
-
itemHeights,
|
|
7
6
|
getItemHeight,
|
|
8
7
|
setItemHeight
|
|
9
8
|
}) {
|
|
10
9
|
this.container = container
|
|
11
10
|
this._get = getItemHeight
|
|
12
11
|
this._set = setItemHeight
|
|
12
|
+
|
|
13
13
|
this.reset()
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -20,9 +20,9 @@ export default class ItemHeights {
|
|
|
20
20
|
// is called, some items might get prepended, in which case
|
|
21
21
|
// `this.lastMeasuredItemIndex` is updated. If there was no
|
|
22
22
|
// `this.firstMeasuredItemIndex`, then the average item height
|
|
23
|
-
// calculated in `.
|
|
23
|
+
// calculated in `.getAverageItemHeight()` would be incorrect in the timeframe
|
|
24
24
|
// between `.setItems()` is called and those changes have been rendered.
|
|
25
|
-
// And in that timeframe, `.
|
|
25
|
+
// And in that timeframe, `.getAverageItemHeight()` is used to calculate the "layout":
|
|
26
26
|
// stuff like "before/after items height" and "estimated items count on screen".
|
|
27
27
|
this.firstMeasuredItemIndex = undefined
|
|
28
28
|
this.lastMeasuredItemIndex = undefined
|
|
@@ -64,7 +64,7 @@ export default class ItemHeights {
|
|
|
64
64
|
// this._set(i, itemHeight)
|
|
65
65
|
// return itemHeight
|
|
66
66
|
// }
|
|
67
|
-
// return this.
|
|
67
|
+
// return this.getAverageItemHeight()
|
|
68
68
|
// }
|
|
69
69
|
|
|
70
70
|
_measureItemHeight(i, firstShownItemIndex) {
|
|
@@ -169,7 +169,7 @@ export default class ItemHeights {
|
|
|
169
169
|
const previousHeight = this._get(i)
|
|
170
170
|
const height = this._measureItemHeight(i, firstShownItemIndex)
|
|
171
171
|
if (previousHeight !== height) {
|
|
172
|
-
warn('Item index', i, 'height changed unexpectedly: it was', previousHeight, 'before, but now it is', height, '. Whenever an item\'s height changes for whatever reason, a developer must call `onItemHeightDidChange(
|
|
172
|
+
warn('Item index', i, 'height changed unexpectedly: it was', previousHeight, 'before, but now it is', height, '. Whenever an item\'s height changes for whatever reason, a developer must call `onItemHeightDidChange(item)` right after that change. If you are calling `onItemHeightDidChange(item)` correctly, then there\'re several other possible causes. For example, perhaps you forgot to persist the item\'s "state" by calling `setItemState(item, newState)` when that "state" did change, and so the item\'s "state" got lost when the item element was unmounted, which resulted in a different item height when the item was shown again with no previous "state". Or perhaps you\'re running your application in "devleopment" mode and `VirtualScroller` has initially rendered the list before your CSS styles or custom fonts have loaded, resulting in different item height measurements "before" and "after" the page has fully loaded.')
|
|
173
173
|
// Update the item's height as an attempt to fix things.
|
|
174
174
|
this._set(i, height)
|
|
175
175
|
}
|
|
@@ -231,7 +231,7 @@ export default class ItemHeights {
|
|
|
231
231
|
// * Public API: is called by `VirtualScroller`.
|
|
232
232
|
// * @return {number}
|
|
233
233
|
// */
|
|
234
|
-
//
|
|
234
|
+
// getAvergetAverageItemHeightage() {
|
|
235
235
|
// // Previously measured average item height might still be
|
|
236
236
|
// // more precise if it contains more measured items ("samples").
|
|
237
237
|
// if (this.previousAverageItemHeight) {
|
|
@@ -244,13 +244,12 @@ export default class ItemHeights {
|
|
|
244
244
|
|
|
245
245
|
/**
|
|
246
246
|
* Public API: is called by `VirtualScroller`.
|
|
247
|
-
* @return {number}
|
|
247
|
+
* @return {number} [averageItemHeight] Returns `undefined` until at least one item has been rendered.
|
|
248
248
|
*/
|
|
249
|
-
|
|
250
|
-
if (this.lastMeasuredItemIndex
|
|
251
|
-
return
|
|
249
|
+
getAverageItemHeight() {
|
|
250
|
+
if (this.lastMeasuredItemIndex !== undefined) {
|
|
251
|
+
return this.measuredItemsHeight / (this.lastMeasuredItemIndex - this.firstMeasuredItemIndex + 1)
|
|
252
252
|
}
|
|
253
|
-
return this.measuredItemsHeight / (this.lastMeasuredItemIndex - this.firstMeasuredItemIndex + 1)
|
|
254
253
|
}
|
|
255
254
|
|
|
256
255
|
onPrepend(count) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// These values are used by default in server-side render,
|
|
2
|
+
// unless the developer explicitly specifies:
|
|
3
|
+
// * `getEstimatedItemHeight()`
|
|
4
|
+
// * `getEstimatedVisibleItemRowsCount()`
|
|
5
|
+
// * `getEstimatedInterItemVerticalSpacing()`
|
|
6
|
+
export const DEFAULT_VISIBLE_ITEM_ROWS_COUNT = 1 // determines the count of items to render.
|
|
7
|
+
export const DEFAULT_ITEM_HEIGHT = 0 // determines the length of the scrollbar, i.e. how much the user can scroll.
|
|
8
|
+
export const DEFAULT_INTER_ITEM_VERTICAL_SPACING = 0 // determines the length of the scrollbar, i.e. how much the user can scroll.
|
package/source/Layout.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import log, { warn } from './utility/debug.js'
|
|
2
2
|
import ScrollableContainerNotReadyError from './ScrollableContainerNotReadyError.js'
|
|
3
|
+
import { DEFAULT_VISIBLE_ITEM_ROWS_COUNT } from './Layout.defaults.js'
|
|
3
4
|
|
|
4
5
|
export default class Layout {
|
|
5
6
|
constructor({
|
|
6
7
|
isInBypassMode,
|
|
7
|
-
|
|
8
|
-
getInitialEstimatedVisibleItemRowsCount,
|
|
8
|
+
getEstimatedVisibleItemRowsCountForInitialRender,
|
|
9
9
|
measureItemsBatchSize,
|
|
10
10
|
getPrerenderMargin,
|
|
11
|
+
getPrerenderMarginRatio,
|
|
11
12
|
getVerticalSpacing,
|
|
12
13
|
getVerticalSpacingBeforeResize,
|
|
13
14
|
getColumnsCount,
|
|
@@ -20,10 +21,10 @@ export default class Layout {
|
|
|
20
21
|
getPreviouslyCalculatedLayout
|
|
21
22
|
}) {
|
|
22
23
|
this.isInBypassMode = isInBypassMode
|
|
23
|
-
this.
|
|
24
|
-
this.getInitialEstimatedVisibleItemRowsCount = getInitialEstimatedVisibleItemRowsCount
|
|
24
|
+
this.getEstimatedVisibleItemRowsCountForInitialRender = getEstimatedVisibleItemRowsCountForInitialRender
|
|
25
25
|
this.measureItemsBatchSize = measureItemsBatchSize
|
|
26
26
|
this.getPrerenderMargin = getPrerenderMargin
|
|
27
|
+
this.getPrerenderMarginRatio = getPrerenderMarginRatio
|
|
27
28
|
this.getVerticalSpacing = getVerticalSpacing
|
|
28
29
|
this.getVerticalSpacingBeforeResize = getVerticalSpacingBeforeResize
|
|
29
30
|
this.getColumnsCount = getColumnsCount
|
|
@@ -69,6 +70,10 @@ export default class Layout {
|
|
|
69
70
|
}) {
|
|
70
71
|
let firstShownItemIndex
|
|
71
72
|
let lastShownItemIndex
|
|
73
|
+
|
|
74
|
+
let beforeItemsHeight = 0
|
|
75
|
+
let afterItemsHeight = 0
|
|
76
|
+
|
|
72
77
|
// If there're no items then `firstShownItemIndex` stays `undefined`.
|
|
73
78
|
if (itemsCount > 0) {
|
|
74
79
|
const getLastShownItemIndex = () => {
|
|
@@ -78,18 +83,30 @@ export default class Layout {
|
|
|
78
83
|
firstShownItemIndex
|
|
79
84
|
})
|
|
80
85
|
}
|
|
86
|
+
|
|
81
87
|
firstShownItemIndex = 0
|
|
88
|
+
|
|
82
89
|
lastShownItemIndex = beforeStart
|
|
83
90
|
? this.getInitialLayoutValueWithFallback(
|
|
84
91
|
'lastShownItemIndex',
|
|
85
92
|
getLastShownItemIndex,
|
|
86
|
-
|
|
93
|
+
firstShownItemIndex
|
|
87
94
|
)
|
|
88
95
|
: getLastShownItemIndex()
|
|
96
|
+
|
|
97
|
+
const averageItemHeight = this.getAverageItemHeight()
|
|
98
|
+
const verticalSpacing = this.getVerticalSpacing()
|
|
99
|
+
|
|
100
|
+
const beforeItemsCount = firstShownItemIndex
|
|
101
|
+
const afterItemsCount = itemsCount - (lastShownItemIndex + 1)
|
|
102
|
+
|
|
103
|
+
beforeItemsHeight = Math.ceil(beforeItemsCount / columnsCount) * (verticalSpacing + averageItemHeight)
|
|
104
|
+
afterItemsHeight = Math.ceil(afterItemsCount / columnsCount) * (verticalSpacing + averageItemHeight)
|
|
89
105
|
}
|
|
106
|
+
|
|
90
107
|
return {
|
|
91
|
-
beforeItemsHeight
|
|
92
|
-
afterItemsHeight
|
|
108
|
+
beforeItemsHeight,
|
|
109
|
+
afterItemsHeight,
|
|
93
110
|
firstShownItemIndex,
|
|
94
111
|
lastShownItemIndex
|
|
95
112
|
}
|
|
@@ -103,54 +120,47 @@ export default class Layout {
|
|
|
103
120
|
if (this.isInBypassMode()) {
|
|
104
121
|
return itemsCount - 1
|
|
105
122
|
}
|
|
106
|
-
// On server side, at initialization time,
|
|
107
|
-
// `scrollableContainer` is `undefined`,
|
|
108
|
-
// so default to `1` estimated rows count.
|
|
109
|
-
let estimatedRowsCount = 1
|
|
110
|
-
if (this.getMaxVisibleAreaHeight()) {
|
|
111
|
-
estimatedRowsCount = this.getEstimatedRowsCountForHeight(this.getMaxVisibleAreaHeight() + this.getPrerenderMargin())
|
|
112
|
-
} else if (this.getInitialEstimatedVisibleItemRowsCount) {
|
|
113
|
-
estimatedRowsCount = this.getInitialEstimatedVisibleItemRowsCount()
|
|
114
|
-
if (isNaN(estimatedRowsCount)) {
|
|
115
|
-
throw new Error('[virtual-scroller] `getEstimatedVisibleItemRowsCount()` must return a number')
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
123
|
return Math.min(
|
|
119
|
-
firstShownItemIndex + (
|
|
124
|
+
firstShownItemIndex + (this.getInitialRenderedRowsCount() * columnsCount - 1),
|
|
120
125
|
itemsCount - 1
|
|
121
126
|
)
|
|
122
127
|
}
|
|
123
128
|
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return Math.ceil((height + verticalSpacing) / (estimatedItemHeight + verticalSpacing))
|
|
129
|
-
} else {
|
|
130
|
-
// If no items have been rendered yet, and no `estimatedItemHeight` option
|
|
131
|
-
// has been passed, then default to `1` estimated rows count in any `height`.
|
|
132
|
-
return 1
|
|
129
|
+
getInitialRenderedRowsCount() {
|
|
130
|
+
const estimatedVisibleItemRowsCount = this.getEstimatedVisibleItemRowsCount()
|
|
131
|
+
if (typeof estimatedVisibleItemRowsCount === 'number') {
|
|
132
|
+
return Math.ceil(estimatedVisibleItemRowsCount * (1 + this.getPrerenderMarginRatio()))
|
|
133
133
|
}
|
|
134
|
+
// `DEFAULT_VISIBLE_ITEM_ROWS_COUNT` will be used in server-side render
|
|
135
|
+
// unless `getEstimatedVisibleItemRowsCount()` parameter is specified.
|
|
136
|
+
return DEFAULT_VISIBLE_ITEM_ROWS_COUNT
|
|
134
137
|
}
|
|
135
138
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (averageItemHeight) {
|
|
144
|
-
return averageItemHeight
|
|
139
|
+
getEstimatedVisibleItemRowsCount() {
|
|
140
|
+
const maxVisibleAreaHeight = this.getMaxVisibleAreaHeight()
|
|
141
|
+
if (typeof maxVisibleAreaHeight === 'number') {
|
|
142
|
+
const estimatedRowsCount = this.getEstimatedRowsCountForHeight(maxVisibleAreaHeight)
|
|
143
|
+
if (typeof estimatedRowsCount === 'number') {
|
|
144
|
+
return estimatedRowsCount
|
|
145
|
+
}
|
|
145
146
|
}
|
|
146
|
-
if (this.
|
|
147
|
-
const
|
|
148
|
-
if (
|
|
149
|
-
|
|
147
|
+
if (this.getEstimatedVisibleItemRowsCountForInitialRender) {
|
|
148
|
+
const estimatedRowsCount = this.getEstimatedVisibleItemRowsCountForInitialRender()
|
|
149
|
+
if (typeof estimatedRowsCount === 'number') {
|
|
150
|
+
return estimatedRowsCount
|
|
150
151
|
}
|
|
151
|
-
return
|
|
152
|
+
throw new Error('[virtual-scroller] `getEstimatedVisibleItemRowsCount()` must return a number')
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getEstimatedRowsCountForHeight(height) {
|
|
157
|
+
const averageItemHeight = this.getAverageItemHeight()
|
|
158
|
+
const verticalSpacing = this.getVerticalSpacing()
|
|
159
|
+
// `estimatedItemHeight` will most likely be `0` if it hasn't been specified explicitly.
|
|
160
|
+
// In that case, it can't divide by `0`.
|
|
161
|
+
if (averageItemHeight + verticalSpacing > 0) {
|
|
162
|
+
return Math.ceil((height + verticalSpacing) / (averageItemHeight + verticalSpacing))
|
|
152
163
|
}
|
|
153
|
-
return 0
|
|
154
164
|
}
|
|
155
165
|
|
|
156
166
|
getLayoutUpdateForItemsDiff({
|
|
@@ -328,8 +338,17 @@ export default class Layout {
|
|
|
328
338
|
|
|
329
339
|
const columnsCount = this.getColumnsCount()
|
|
330
340
|
|
|
341
|
+
const getNonMeasuredItemRowsCount = () => {
|
|
342
|
+
const estimatedRowsCount = this.getEstimatedRowsCountForHeight(nonMeasuredAreaHeight)
|
|
343
|
+
if (typeof estimatedRowsCount === 'number') {
|
|
344
|
+
return estimatedRowsCount
|
|
345
|
+
}
|
|
346
|
+
// Render all available item rows as a sensible fallback behavior.
|
|
347
|
+
return Math.ceil(itemsCount / columnsCount)
|
|
348
|
+
}
|
|
349
|
+
|
|
331
350
|
const itemsCountToRenderForMeasurement = Math.min(
|
|
332
|
-
|
|
351
|
+
getNonMeasuredItemRowsCount() * columnsCount,
|
|
333
352
|
this.measureItemsBatchSize || Infinity,
|
|
334
353
|
)
|
|
335
354
|
|
|
@@ -434,15 +453,12 @@ export default class Layout {
|
|
|
434
453
|
// then `shownItemsHeight` would also have to be returned from this function:
|
|
435
454
|
// the total height of all shown items including vertical spacing between them.
|
|
436
455
|
//
|
|
437
|
-
// If "previously calculated layout" would be used then it would first
|
|
438
|
-
// `firstShownItemIndex` and then
|
|
456
|
+
// If "previously calculated layout" would be used then it would first calculate
|
|
457
|
+
// `firstShownItemIndex` and then calculate `lastShownItemIndex` as part of two
|
|
439
458
|
// separate calls of this function, each with or without `backwards` flag,
|
|
440
459
|
// depending on whether `visibleAreaInsideTheList.top` and `visibleAreaInsideTheList.top`
|
|
441
460
|
// have shifted up or down.
|
|
442
461
|
|
|
443
|
-
let firstShownItemIndex
|
|
444
|
-
let lastShownItemIndex
|
|
445
|
-
|
|
446
462
|
// It's not always required to pass `beforeItemsHeight` parameter:
|
|
447
463
|
// when `fromIndex` is `0`, it's also assumed to be `0`.
|
|
448
464
|
if (fromIndex === 0) {
|
|
@@ -678,8 +694,6 @@ export default class Layout {
|
|
|
678
694
|
const verticalSpacing = beforeResize ? this.getVerticalSpacingBeforeResize() : this.getVerticalSpacing()
|
|
679
695
|
|
|
680
696
|
while (i < beforeItemsCount) {
|
|
681
|
-
const currentRowFirstItemIndex = i
|
|
682
|
-
|
|
683
697
|
let rowHeight = 0
|
|
684
698
|
let columnIndex = 0
|
|
685
699
|
// Not checking for `itemsCount` overflow here because `i = beforeItemsCount`
|
|
@@ -734,7 +748,6 @@ export default class Layout {
|
|
|
734
748
|
// Which becomes negligible in my project's use case (a couple thousands items max).
|
|
735
749
|
|
|
736
750
|
const columnsCount = this.getColumnsCount()
|
|
737
|
-
const lastShownRowIndex = Math.floor(lastShownItemIndex / columnsCount)
|
|
738
751
|
|
|
739
752
|
let afterItemsHeight = 0
|
|
740
753
|
|
package/source/Layout.test.js
CHANGED
|
@@ -158,9 +158,11 @@ describe('Layout', function() {
|
|
|
158
158
|
|
|
159
159
|
let shouldResetGridLayout
|
|
160
160
|
|
|
161
|
+
// Don't `throw` `VirtualScroller` errors but rather collect them in an array.
|
|
161
162
|
const errors = []
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
global.VirtualScrollerCatchError = (error) => {
|
|
164
|
+
errors.push(error)
|
|
165
|
+
}
|
|
164
166
|
|
|
165
167
|
layout.getLayoutUpdateForItemsDiff(
|
|
166
168
|
{
|
|
@@ -185,7 +187,10 @@ describe('Layout', function() {
|
|
|
185
187
|
afterItemsHeight: 5 * (ITEM_HEIGHT + VERTICAL_SPACING)
|
|
186
188
|
})
|
|
187
189
|
|
|
190
|
+
// Stop collecting `VirtualScroller` errors in the `errors` array.
|
|
191
|
+
// Use the default behavior of just `throw`-ing such errors.
|
|
188
192
|
global.VirtualScrollerCatchError = undefined
|
|
193
|
+
// Verify the errors that have been `throw`-n.
|
|
189
194
|
errors.length.should.equal(2)
|
|
190
195
|
errors[0].message.should.equal('[virtual-scroller] ~ Prepended items count 5 is not divisible by Columns Count 4 ~')
|
|
191
196
|
errors[1].message.should.equal('[virtual-scroller] Layout reset required')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import DOMEngine from './DOM/Engine.js'
|
|
2
2
|
|
|
3
3
|
import Layout, { LAYOUT_REASON } from './Layout.js'
|
|
4
|
+
import { DEFAULT_ITEM_HEIGHT } from './Layout.defaults.js'
|
|
4
5
|
import ScrollableContainerResizeHandler from './ScrollableContainerResizeHandler.js'
|
|
5
6
|
import BeforeResize from './BeforeResize.js'
|
|
6
7
|
import Scroll from './Scroll.js'
|
|
@@ -45,6 +46,7 @@ export default function VirtualScrollerConstructor(
|
|
|
45
46
|
// `estimatedItemHeight` is deprecated, use `getEstimatedItemHeight()` instead.
|
|
46
47
|
estimatedItemHeight,
|
|
47
48
|
getEstimatedVisibleItemRowsCount,
|
|
49
|
+
getEstimatedInterItemVerticalSpacing,
|
|
48
50
|
onItemInitialRender,
|
|
49
51
|
// `onItemFirstRender(i)` is deprecated, use `onItemInitialRender(item)` instead.
|
|
50
52
|
onItemFirstRender,
|
|
@@ -167,7 +169,7 @@ export default function VirtualScrollerConstructor(
|
|
|
167
169
|
|
|
168
170
|
createStateHelpers.call(this, { state, getInitialItemState, onStateChange, render, items })
|
|
169
171
|
|
|
170
|
-
createVerticalSpacingHelpers.call(this)
|
|
172
|
+
createVerticalSpacingHelpers.call(this, { getEstimatedInterItemVerticalSpacing })
|
|
171
173
|
createColumnsHelpers.call(this, { getColumnsCount })
|
|
172
174
|
|
|
173
175
|
createLayoutHelpers.call(this)
|
|
@@ -236,12 +238,33 @@ function createHelpers({
|
|
|
236
238
|
setItemHeight: (i, height) => this.getState().itemHeights[i] = height
|
|
237
239
|
})
|
|
238
240
|
|
|
241
|
+
this.getAverageItemHeight = () => {
|
|
242
|
+
const averageItemHeight = this.itemHeights.getAverageItemHeight()
|
|
243
|
+
if (typeof averageItemHeight === 'number') {
|
|
244
|
+
return averageItemHeight
|
|
245
|
+
}
|
|
246
|
+
return this.getEstimatedItemHeight()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.getEstimatedItemHeight = () => {
|
|
250
|
+
if (getEstimatedItemHeight) {
|
|
251
|
+
const estimatedItemHeight = getEstimatedItemHeight()
|
|
252
|
+
if (typeof estimatedItemHeight === 'number') {
|
|
253
|
+
return estimatedItemHeight
|
|
254
|
+
}
|
|
255
|
+
throw new Error('[virtual-scroller] `getEstimatedItemHeight()` must return a number')
|
|
256
|
+
}
|
|
257
|
+
// `DEFAULT_ITEM_HEIGHT` will be used in server-side render
|
|
258
|
+
// unless `getEstimatedItemHeight()` parameter is specified.
|
|
259
|
+
return DEFAULT_ITEM_HEIGHT
|
|
260
|
+
}
|
|
261
|
+
|
|
239
262
|
this.layout = new Layout({
|
|
240
263
|
isInBypassMode: this.isInBypassMode,
|
|
241
|
-
|
|
242
|
-
getInitialEstimatedVisibleItemRowsCount: getEstimatedVisibleItemRowsCount,
|
|
264
|
+
getEstimatedVisibleItemRowsCountForInitialRender: getEstimatedVisibleItemRowsCount,
|
|
243
265
|
measureItemsBatchSize,
|
|
244
266
|
getPrerenderMargin: () => this.getPrerenderMargin(),
|
|
267
|
+
getPrerenderMarginRatio: () => this.getPrerenderMarginRatio(),
|
|
245
268
|
getVerticalSpacing: () => this.getVerticalSpacing(),
|
|
246
269
|
getVerticalSpacingBeforeResize: () => this.getVerticalSpacingBeforeResize(),
|
|
247
270
|
getColumnsCount: () => this.getColumnsCount(),
|
|
@@ -249,7 +272,7 @@ function createHelpers({
|
|
|
249
272
|
getItemHeight: (i) => this.getState().itemHeights[i],
|
|
250
273
|
getItemHeightBeforeResize: (i) => this.getState().beforeResize && this.getState().beforeResize.itemHeights[i],
|
|
251
274
|
getBeforeResizeItemsCount: () => this.getState().beforeResize ? this.getState().beforeResize.itemHeights.length : 0,
|
|
252
|
-
getAverageItemHeight: () => this.
|
|
275
|
+
getAverageItemHeight: () => this.getAverageItemHeight(),
|
|
253
276
|
// `this.scrollableContainer` is gonna be `undefined` during server-side rendering.
|
|
254
277
|
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/30
|
|
255
278
|
getMaxVisibleAreaHeight: () => this.scrollableContainer && this.scrollableContainer.getHeight(),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import log, {
|
|
1
|
+
import log, { reportError } from './utility/debug.js'
|
|
2
2
|
import getItemsDiff from './getItemsDiff.js'
|
|
3
3
|
import fillArray from './utility/fillArray.js'
|
|
4
4
|
|
|
@@ -7,6 +7,49 @@ export default function() {
|
|
|
7
7
|
return this.getState().items.length
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
this._getItemIndexByItemOrIndex = (itemOrIndex) => {
|
|
11
|
+
const item = itemOrIndex
|
|
12
|
+
const { items } = this.getState()
|
|
13
|
+
// Find the `item`'s index in the `items` array.
|
|
14
|
+
//
|
|
15
|
+
// Performance notes:
|
|
16
|
+
// Doing an `.indexOf()` operation on a very large array could be inefficient
|
|
17
|
+
// because it would have to check every element of the array.
|
|
18
|
+
// In case of any hypothetical performance-related issues,
|
|
19
|
+
// the code could use a `new Map()` as an "index" for finding individual items.
|
|
20
|
+
const i = items.indexOf(item)
|
|
21
|
+
// validate that the `item` exists in the `items` array.
|
|
22
|
+
if (i >= 0) {
|
|
23
|
+
return i
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Legacy behavior: some functions used to accept an `i: number` item index as an argument.
|
|
27
|
+
// Later the argument was changed to `item: Item`.
|
|
28
|
+
//
|
|
29
|
+
// The old way of passing an `i: number` argument would only result in weird application behavior
|
|
30
|
+
// when all of the below circumstances are true, which is assumed extremely unlikely:
|
|
31
|
+
//
|
|
32
|
+
// BOTH use a previous version of `virtual-scroller` (ones before December 2025, with downloads count that is not very high)
|
|
33
|
+
// AND to be used with an `items` array of type `number[]`
|
|
34
|
+
// AND to use any of the "advanced" functions:
|
|
35
|
+
// * `setItemState(i, newState)`
|
|
36
|
+
// * `onItemHeightDidChange(i)`
|
|
37
|
+
// * `getItemScrollPosition(i)`
|
|
38
|
+
//
|
|
39
|
+
// The code below handles the legacy compatibility aspect of the old type of argument
|
|
40
|
+
// except maybe for those "extremely unlikely" cases in which the app is still unlikely to crash.
|
|
41
|
+
//
|
|
42
|
+
if (typeof itemOrIndex === 'number') {
|
|
43
|
+
const i = itemOrIndex
|
|
44
|
+
// Validate the item index argument.
|
|
45
|
+
if (i >= 0 && i < items.length) {
|
|
46
|
+
return i
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
reportError(`Item not found: ${JSON.stringify(item)}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
10
53
|
/**
|
|
11
54
|
* Updates `items`. For example, can prepend or append new items to the list.
|
|
12
55
|
* @param {any[]} newItems
|
|
@@ -81,7 +124,9 @@ export default function() {
|
|
|
81
124
|
if (prependedItemsCount > 0) {
|
|
82
125
|
log('Prepend', prependedItemsCount, 'items')
|
|
83
126
|
|
|
84
|
-
itemHeights = new Array(prependedItemsCount)
|
|
127
|
+
itemHeights = new Array(prependedItemsCount)
|
|
128
|
+
.concat(itemHeights)
|
|
129
|
+
|
|
85
130
|
itemStates = fillArray(
|
|
86
131
|
new Array(prependedItemsCount),
|
|
87
132
|
(i) => this.getInitialItemState(newItems[i])
|
|
@@ -206,10 +206,19 @@ export default class VirtualScroller {
|
|
|
206
206
|
|
|
207
207
|
/**
|
|
208
208
|
* Returns the items's top offset relative to the scrollable container's top edge.
|
|
209
|
-
* @param {
|
|
209
|
+
* @param {any} item — Item
|
|
210
210
|
* @return {[number]} Returns the item's scroll Y position. Returns `undefined` if any of the previous items haven't been rendered yet.
|
|
211
211
|
*/
|
|
212
|
-
getItemScrollPosition(
|
|
212
|
+
getItemScrollPosition(itemOrIndex) {
|
|
213
|
+
// Item index.
|
|
214
|
+
const i = this._getItemIndexByItemOrIndex(itemOrIndex)
|
|
215
|
+
|
|
216
|
+
// If the item wasn't found, the error was already reported,
|
|
217
|
+
// so just return some "sensible" default value.
|
|
218
|
+
if (i === undefined) {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
|
|
213
222
|
const itemTopOffsetInList = this.layout.getItemTopOffset(i)
|
|
214
223
|
if (itemTopOffsetInList === undefined) {
|
|
215
224
|
return
|
|
@@ -221,28 +230,28 @@ export default class VirtualScroller {
|
|
|
221
230
|
* @deprecated
|
|
222
231
|
* `.onItemHeightChange()` has been renamed to `.onItemHeightDidChange()`.
|
|
223
232
|
*/
|
|
224
|
-
onItemHeightChange(
|
|
225
|
-
warn('`.onItemHeightChange(
|
|
226
|
-
this.onItemHeightDidChange(
|
|
233
|
+
onItemHeightChange(item) {
|
|
234
|
+
warn('`.onItemHeightChange(item)` method was renamed to `.onItemHeightDidChange(item)`')
|
|
235
|
+
this.onItemHeightDidChange(item)
|
|
227
236
|
}
|
|
228
237
|
|
|
229
238
|
/**
|
|
230
239
|
* Forces a re-measure of an item's height.
|
|
231
|
-
* @param
|
|
240
|
+
* @param {any} item — Item. Legacy argument variant: Item index.
|
|
232
241
|
*/
|
|
233
|
-
onItemHeightDidChange(
|
|
242
|
+
onItemHeightDidChange(itemOrIndex) {
|
|
234
243
|
// See the comments in the `setItemState()` function below for the rationale
|
|
235
244
|
// on why the `hasToBeStarted()` check was commented out.
|
|
236
245
|
// this.hasToBeStarted()
|
|
237
|
-
this._onItemHeightDidChange(
|
|
246
|
+
this._onItemHeightDidChange(itemOrIndex)
|
|
238
247
|
}
|
|
239
248
|
|
|
240
249
|
/**
|
|
241
250
|
* Updates an item's state in `state.itemStates[]`.
|
|
242
|
-
* @param {
|
|
243
|
-
* @param {any}
|
|
251
|
+
* @param {any} item — Item. Legacy argument variant: Item index.
|
|
252
|
+
* @param {any} newItemState — Item's new state
|
|
244
253
|
*/
|
|
245
|
-
setItemState(
|
|
254
|
+
setItemState(itemOrIndex, newItemState) {
|
|
246
255
|
// There is an issue in React 18.2.0 when `useInsertionEffect()` doesn't run twice
|
|
247
256
|
// on mount unlike `useLayoutEffect()` in "strict" mode. That causes a bug in a React
|
|
248
257
|
// implementation of the `virtual-scroller`.
|
|
@@ -285,13 +294,13 @@ export default class VirtualScroller {
|
|
|
285
294
|
// Commenting it out wouldn't result in any potential bugs because the code would work correctly
|
|
286
295
|
// in both cases.
|
|
287
296
|
// this.hasToBeStarted()
|
|
288
|
-
this._setItemState(
|
|
297
|
+
this._setItemState(itemOrIndex, newItemState)
|
|
289
298
|
}
|
|
290
299
|
|
|
291
300
|
// (deprecated)
|
|
292
301
|
// Use `.setItemState()` method name instead.
|
|
293
|
-
onItemStateChange(
|
|
294
|
-
this.setItemState(
|
|
302
|
+
onItemStateChange(item, newItemState) {
|
|
303
|
+
this.setItemState(item, newItemState)
|
|
295
304
|
}
|
|
296
305
|
|
|
297
306
|
/**
|