virtual-scroller 1.7.9 → 1.9.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/.gitlab-ci.yml +1 -1
- package/CHANGELOG.md +71 -1
- package/README.md +434 -151
- package/bundle/index-bypass.html +1 -1
- package/bundle/index-dom.html +1 -1
- package/bundle/index-grid.html +1 -2
- package/bundle/index-scrollableContainer.html +1 -1
- package/bundle/index-tbody-scrollableContainer.html +2 -0
- package/bundle/index-tbody.html +2 -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 +315 -0
- package/commonjs/BeforeResize.js.map +1 -0
- package/commonjs/DOM/Engine.js +46 -0
- package/commonjs/DOM/Engine.js.map +1 -0
- package/commonjs/DOM/ItemsContainer.js +78 -0
- package/commonjs/DOM/ItemsContainer.js.map +1 -0
- package/commonjs/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +71 -44
- package/commonjs/DOM/ListTopOffsetWatcher.js.map +1 -0
- package/commonjs/DOM/ScrollableContainer.js +69 -101
- package/commonjs/DOM/ScrollableContainer.js.map +1 -1
- package/commonjs/DOM/VirtualScroller.js +37 -29
- package/commonjs/DOM/VirtualScroller.js.map +1 -1
- package/commonjs/DOM/tbody.js +17 -11
- package/commonjs/DOM/tbody.js.map +1 -1
- package/commonjs/ItemHeights.js +33 -34
- package/commonjs/ItemHeights.js.map +1 -1
- package/commonjs/Layout.js +591 -216
- package/commonjs/Layout.js.map +1 -1
- package/commonjs/Layout.test.js +196 -0
- package/commonjs/Layout.test.js.map +1 -0
- package/commonjs/ListHeightMeasurement.js +124 -0
- package/commonjs/ListHeightMeasurement.js.map +1 -0
- package/commonjs/Resize.js +50 -39
- package/commonjs/Resize.js.map +1 -1
- package/commonjs/Scroll.js +139 -95
- package/commonjs/Scroll.js.map +1 -1
- package/commonjs/VirtualScroller.columns.js +43 -0
- package/commonjs/VirtualScroller.columns.js.map +1 -0
- package/commonjs/VirtualScroller.constructor.js +408 -0
- package/commonjs/VirtualScroller.constructor.js.map +1 -0
- package/commonjs/VirtualScroller.items.js +305 -0
- package/commonjs/VirtualScroller.items.js.map +1 -0
- package/commonjs/VirtualScroller.js +160 -1021
- package/commonjs/VirtualScroller.js.map +1 -1
- package/commonjs/VirtualScroller.layout.js +562 -0
- package/commonjs/VirtualScroller.layout.js.map +1 -0
- package/commonjs/VirtualScroller.onRender.js +357 -0
- package/commonjs/VirtualScroller.onRender.js.map +1 -0
- package/commonjs/VirtualScroller.resize.js +186 -0
- package/commonjs/VirtualScroller.resize.js.map +1 -0
- package/commonjs/VirtualScroller.state.js +301 -0
- package/commonjs/VirtualScroller.state.js.map +1 -0
- package/commonjs/VirtualScroller.verticalSpacing.js +65 -0
- package/commonjs/VirtualScroller.verticalSpacing.js.map +1 -0
- package/commonjs/getItemCoordinates.js.map +1 -1
- package/commonjs/getItemsDiff.js.map +1 -1
- package/commonjs/getVerticalSpacing.js +8 -8
- package/commonjs/getVerticalSpacing.js.map +1 -1
- package/commonjs/package.json +5 -0
- package/commonjs/react/VirtualScroller.js +182 -628
- package/commonjs/react/VirtualScroller.js.map +1 -1
- package/commonjs/react/useClassName.js +26 -0
- package/commonjs/react/useClassName.js.map +1 -0
- package/commonjs/react/useHandleItemsChange.js +116 -0
- package/commonjs/react/useHandleItemsChange.js.map +1 -0
- package/commonjs/react/useInstanceMethods.js +37 -0
- package/commonjs/react/useInstanceMethods.js.map +1 -0
- package/commonjs/react/useItemKeys.js +60 -0
- package/commonjs/react/useItemKeys.js.map +1 -0
- package/commonjs/react/useOnItemHeightChange.js +32 -0
- package/commonjs/react/useOnItemHeightChange.js.map +1 -0
- package/commonjs/react/useOnItemStateChange.js +32 -0
- package/commonjs/react/useOnItemStateChange.js.map +1 -0
- package/commonjs/react/useState.js +140 -0
- package/commonjs/react/useState.js.map +1 -0
- package/commonjs/react/useStyle.js +29 -0
- package/commonjs/react/useStyle.js.map +1 -0
- package/commonjs/react/useVirtualScroller.js +62 -0
- package/commonjs/react/useVirtualScroller.js.map +1 -0
- package/commonjs/react/useVirtualScrollerStartStop.js +20 -0
- package/commonjs/react/useVirtualScrollerStartStop.js.map +1 -0
- package/commonjs/test/Engine.js +23 -0
- package/commonjs/test/Engine.js.map +1 -0
- package/commonjs/test/ItemsContainer.js +127 -0
- package/commonjs/test/ItemsContainer.js.map +1 -0
- package/commonjs/test/ScrollableContainer.js +130 -0
- package/commonjs/test/ScrollableContainer.js.map +1 -0
- package/commonjs/test/VirtualScroller.js +281 -0
- package/commonjs/test/VirtualScroller.js.map +1 -0
- package/commonjs/utility/debounce.js +28 -6
- package/commonjs/utility/debounce.js.map +1 -1
- package/commonjs/utility/debug.js +51 -12
- package/commonjs/utility/debug.js.map +1 -1
- package/commonjs/utility/getStateSnapshot.js +50 -0
- package/commonjs/utility/getStateSnapshot.js.map +1 -0
- package/commonjs/utility/px.js +1 -1
- package/commonjs/utility/px.js.map +1 -1
- package/commonjs/utility/px.test.js +14 -0
- package/commonjs/utility/px.test.js.map +1 -0
- package/commonjs/utility/shallowEqual.js +1 -1
- package/commonjs/utility/shallowEqual.js.map +1 -1
- package/commonjs/utility/throttle.js.map +1 -1
- package/dom/index.cjs +4 -0
- package/dom/index.cjs.js +9 -0
- package/dom/index.d.ts +25 -0
- package/dom/index.js +1 -1
- package/dom/package.json +10 -4
- package/index.cjs +4 -0
- package/index.cjs.js +9 -0
- package/index.d.ts +99 -0
- package/index.js +1 -1
- package/modules/BeforeResize.js +305 -0
- package/modules/BeforeResize.js.map +1 -0
- package/modules/DOM/Engine.js +27 -0
- package/modules/DOM/Engine.js.map +1 -0
- package/modules/DOM/ItemsContainer.js +71 -0
- package/modules/DOM/ItemsContainer.js.map +1 -0
- package/modules/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +72 -44
- package/modules/DOM/ListTopOffsetWatcher.js.map +1 -0
- package/modules/DOM/ScrollableContainer.js +68 -100
- package/modules/DOM/ScrollableContainer.js.map +1 -1
- package/modules/DOM/VirtualScroller.js +32 -28
- package/modules/DOM/VirtualScroller.js.map +1 -1
- package/modules/DOM/tbody.js +11 -9
- package/modules/DOM/tbody.js.map +1 -1
- package/modules/ItemHeights.js +28 -33
- package/modules/ItemHeights.js.map +1 -1
- package/modules/Layout.js +585 -214
- package/modules/Layout.js.map +1 -1
- package/modules/Layout.test.js +190 -0
- package/modules/Layout.test.js.map +1 -0
- package/modules/ListHeightMeasurement.js +117 -0
- package/modules/ListHeightMeasurement.js.map +1 -0
- package/modules/Resize.js +50 -39
- package/modules/Resize.js.map +1 -1
- package/modules/Scroll.js +139 -94
- package/modules/Scroll.js.map +1 -1
- package/modules/VirtualScroller.columns.js +36 -0
- package/modules/VirtualScroller.columns.js.map +1 -0
- package/modules/VirtualScroller.constructor.js +371 -0
- package/modules/VirtualScroller.constructor.js.map +1 -0
- package/modules/VirtualScroller.items.js +288 -0
- package/modules/VirtualScroller.items.js.map +1 -0
- package/modules/VirtualScroller.js +159 -1014
- package/modules/VirtualScroller.js.map +1 -1
- package/modules/VirtualScroller.layout.js +549 -0
- package/modules/VirtualScroller.layout.js.map +1 -0
- package/modules/VirtualScroller.onRender.js +337 -0
- package/modules/VirtualScroller.onRender.js.map +1 -0
- package/modules/VirtualScroller.resize.js +176 -0
- package/modules/VirtualScroller.resize.js.map +1 -0
- package/modules/VirtualScroller.state.js +283 -0
- package/modules/VirtualScroller.state.js.map +1 -0
- package/modules/VirtualScroller.verticalSpacing.js +54 -0
- package/modules/VirtualScroller.verticalSpacing.js.map +1 -0
- package/modules/getItemCoordinates.js.map +1 -1
- package/modules/getItemsDiff.js.map +1 -1
- package/modules/getVerticalSpacing.js +8 -8
- package/modules/getVerticalSpacing.js.map +1 -1
- package/modules/react/VirtualScroller.js +179 -634
- package/modules/react/VirtualScroller.js.map +1 -1
- package/modules/react/useClassName.js +18 -0
- package/modules/react/useClassName.js.map +1 -0
- package/modules/react/useHandleItemsChange.js +108 -0
- package/modules/react/useHandleItemsChange.js.map +1 -0
- package/modules/react/useInstanceMethods.js +28 -0
- package/modules/react/useInstanceMethods.js.map +1 -0
- package/modules/react/useItemKeys.js +52 -0
- package/modules/react/useItemKeys.js.map +1 -0
- package/modules/react/useOnItemHeightChange.js +24 -0
- package/modules/react/useOnItemHeightChange.js.map +1 -0
- package/modules/react/useOnItemStateChange.js +24 -0
- package/modules/react/useOnItemStateChange.js.map +1 -0
- package/modules/react/useState.js +132 -0
- package/modules/react/useState.js.map +1 -0
- package/modules/react/useStyle.js +19 -0
- package/modules/react/useStyle.js.map +1 -0
- package/modules/react/useVirtualScroller.js +51 -0
- package/modules/react/useVirtualScroller.js.map +1 -0
- package/modules/react/useVirtualScrollerStartStop.js +12 -0
- package/modules/react/useVirtualScrollerStartStop.js.map +1 -0
- package/modules/test/Engine.js +11 -0
- package/modules/test/Engine.js.map +1 -0
- package/modules/test/ItemsContainer.js +120 -0
- package/modules/test/ItemsContainer.js.map +1 -0
- package/modules/test/ScrollableContainer.js +123 -0
- package/modules/test/ScrollableContainer.js.map +1 -0
- package/modules/test/VirtualScroller.js +270 -0
- package/modules/test/VirtualScroller.js.map +1 -0
- package/modules/utility/debounce.js +28 -6
- package/modules/utility/debounce.js.map +1 -1
- package/modules/utility/debug.js +47 -10
- package/modules/utility/debug.js.map +1 -1
- package/modules/utility/getStateSnapshot.js +43 -0
- package/modules/utility/getStateSnapshot.js.map +1 -0
- package/modules/utility/px.js +1 -1
- package/modules/utility/px.js.map +1 -1
- package/modules/utility/px.test.js +9 -0
- package/modules/utility/px.test.js.map +1 -0
- package/modules/utility/shallowEqual.js +1 -1
- package/modules/utility/shallowEqual.js.map +1 -1
- package/modules/utility/throttle.js.map +1 -1
- package/package.json +54 -29
- package/react/index.cjs +4 -0
- package/react/index.cjs.js +9 -0
- package/react/index.d.ts +28 -0
- package/react/index.js +1 -1
- package/react/package.json +10 -4
- package/rollup.config.mjs +62 -0
- package/runnable/create-commonjs-package-json.js +11 -0
- package/source/BeforeResize.js +312 -0
- package/source/DOM/Engine.js +30 -0
- package/source/DOM/ItemsContainer.js +48 -0
- package/source/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +61 -30
- package/source/DOM/ScrollableContainer.js +51 -73
- package/source/DOM/VirtualScroller.js +33 -18
- package/source/DOM/tbody.js +30 -21
- package/source/ItemHeights.js +27 -27
- package/source/Layout.js +629 -252
- package/source/Layout.test.js +176 -0
- package/source/ListHeightMeasurement.js +95 -0
- package/source/Resize.js +56 -32
- package/source/Scroll.js +135 -82
- package/source/VirtualScroller.columns.js +26 -0
- package/source/VirtualScroller.constructor.js +336 -0
- package/source/VirtualScroller.items.js +302 -0
- package/source/VirtualScroller.js +162 -936
- package/source/VirtualScroller.layout.js +539 -0
- package/source/VirtualScroller.onRender.js +345 -0
- package/source/VirtualScroller.resize.js +189 -0
- package/source/VirtualScroller.state.js +284 -0
- package/source/VirtualScroller.verticalSpacing.js +51 -0
- package/source/getVerticalSpacing.js +7 -7
- package/source/react/VirtualScroller.js +243 -603
- package/source/react/useClassName.js +14 -0
- package/source/react/useHandleItemsChange.js +115 -0
- package/source/react/useInstanceMethods.js +25 -0
- package/source/react/useItemKeys.js +59 -0
- package/source/react/useOnItemHeightChange.js +28 -0
- package/source/react/useOnItemStateChange.js +28 -0
- package/source/react/useState.js +114 -0
- package/source/react/useStyle.js +20 -0
- package/source/react/useVirtualScroller.js +59 -0
- package/source/react/useVirtualScrollerStartStop.js +12 -0
- package/source/test/Engine.js +11 -0
- package/source/test/ItemsContainer.js +87 -0
- package/source/test/ScrollableContainer.js +88 -0
- package/source/test/VirtualScroller.js +232 -0
- package/source/utility/debounce.js +22 -5
- package/source/utility/debug.js +34 -3
- package/source/utility/getStateSnapshot.js +36 -0
- package/source/utility/px.js +1 -1
- package/source/utility/px.test.js +9 -0
- package/website/index-bypass.html +195 -0
- package/website/index-grid.html +0 -1
- package/website/index-scrollableContainer.html +208 -0
- package/website/index-tbody-scrollableContainer.html +68 -0
- package/website/index-tbody.html +55 -0
- package/commonjs/DOM/RenderingEngine.js +0 -33
- package/commonjs/DOM/RenderingEngine.js.map +0 -1
- package/commonjs/DOM/Screen.js +0 -87
- package/commonjs/DOM/Screen.js.map +0 -1
- package/commonjs/DOM/WaitForStylesToLoad.js.map +0 -1
- package/commonjs/RestoreScroll.js +0 -118
- package/commonjs/RestoreScroll.js.map +0 -1
- package/dom/index.commonjs.js +0 -4
- package/index.commonjs.js +0 -4
- package/modules/DOM/RenderingEngine.js +0 -19
- package/modules/DOM/RenderingEngine.js.map +0 -1
- package/modules/DOM/Screen.js +0 -80
- package/modules/DOM/Screen.js.map +0 -1
- package/modules/DOM/WaitForStylesToLoad.js.map +0 -1
- package/modules/RestoreScroll.js +0 -111
- package/modules/RestoreScroll.js.map +0 -1
- package/react/index.commonjs.js +0 -4
- package/source/DOM/RenderingEngine.js +0 -22
- package/source/DOM/Screen.js +0 -51
- package/source/RestoreScroll.js +0 -86
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export default class ScrollableContainer {
|
|
2
2
|
/**
|
|
3
3
|
* Constructs a new "scrollable container" from an element.
|
|
4
|
-
* @param {
|
|
4
|
+
* @param {func} getElement — Returns the scrollable container element.
|
|
5
|
+
* @param {func} getItemsContainerElement — Returns items "container" element.
|
|
5
6
|
*/
|
|
6
|
-
constructor(
|
|
7
|
-
this.
|
|
7
|
+
constructor(getElement, getItemsContainerElement) {
|
|
8
|
+
this.getElement = getElement
|
|
9
|
+
this.getItemsContainerElement = getItemsContainerElement
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
/**
|
|
@@ -12,7 +14,7 @@ export default class ScrollableContainer {
|
|
|
12
14
|
* @return {number}
|
|
13
15
|
*/
|
|
14
16
|
getScrollY() {
|
|
15
|
-
return this.
|
|
17
|
+
return this.getElement().scrollTop
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
@@ -23,10 +25,10 @@ export default class ScrollableContainer {
|
|
|
23
25
|
// IE 11 doesn't seem to have a `.scrollTo()` method.
|
|
24
26
|
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/10
|
|
25
27
|
// https://stackoverflow.com/questions/39908825/window-scrollto-is-not-working-in-internet-explorer-11
|
|
26
|
-
if (this.
|
|
27
|
-
this.
|
|
28
|
+
if (this.getElement().scrollTo) {
|
|
29
|
+
this.getElement().scrollTo(0, scrollY)
|
|
28
30
|
} else {
|
|
29
|
-
this.
|
|
31
|
+
this.getElement().scrollTop = scrollY
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -36,7 +38,7 @@ export default class ScrollableContainer {
|
|
|
36
38
|
* @return {number}
|
|
37
39
|
*/
|
|
38
40
|
getWidth() {
|
|
39
|
-
return this.
|
|
41
|
+
return this.getElement().offsetWidth
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -45,64 +47,47 @@ export default class ScrollableContainer {
|
|
|
45
47
|
* @return {number}
|
|
46
48
|
*/
|
|
47
49
|
getHeight() {
|
|
48
|
-
// if (!this.
|
|
50
|
+
// if (!this.getElement() && !precise) {
|
|
49
51
|
// return getScreenHeight()
|
|
50
52
|
// }
|
|
51
|
-
return this.
|
|
53
|
+
return this.getElement().offsetHeight
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
|
-
* Returns
|
|
56
|
-
* For example, a scrollable container can have a height of 500px,
|
|
57
|
-
* but the content in it could have a height of 5000px,
|
|
58
|
-
* in which case a vertical scrollbar is rendered, and only
|
|
59
|
-
* one-tenth of all the items are shown at any given moment.
|
|
60
|
-
* This function is currently only used when using the
|
|
61
|
-
* `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.
|
|
62
|
-
* @return {number}
|
|
63
|
-
*/
|
|
64
|
-
getContentHeight() {
|
|
65
|
-
return this.element.scrollHeight
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Returns a "top offset" of an element
|
|
57
|
+
* Returns a "top offset" of an items container element
|
|
70
58
|
* relative to the "scrollable container"'s top edge.
|
|
71
|
-
* @param {Element} element
|
|
72
59
|
* @return {number}
|
|
73
60
|
*/
|
|
74
|
-
|
|
75
|
-
const scrollableContainerTop = this.
|
|
76
|
-
const scrollableContainerBorderTopWidth = this.
|
|
77
|
-
const
|
|
78
|
-
return (
|
|
61
|
+
getItemsContainerTopOffset() {
|
|
62
|
+
const scrollableContainerTop = this.getElement().getBoundingClientRect().top
|
|
63
|
+
const scrollableContainerBorderTopWidth = this.getElement().clientTop
|
|
64
|
+
const itemsContainerTop = this.getItemsContainerElement().getBoundingClientRect().top
|
|
65
|
+
return (itemsContainerTop - scrollableContainerTop) + this.getScrollY() - scrollableContainerBorderTopWidth
|
|
79
66
|
}
|
|
80
67
|
|
|
81
68
|
// isVisible() {
|
|
82
|
-
// const { top, bottom } = this.
|
|
69
|
+
// const { top, bottom } = this.getElement().getBoundingClientRect()
|
|
83
70
|
// return bottom > 0 && top < getScreenHeight()
|
|
84
71
|
// }
|
|
85
72
|
|
|
86
73
|
/**
|
|
87
74
|
* Adds a "scroll" event listener to the "scrollable container".
|
|
88
|
-
* @param {
|
|
75
|
+
* @param {onScrollListener} Should be called whenever the scroll position inside the "scrollable container" (potentially) changes.
|
|
89
76
|
* @return {function} Returns a function that stops listening.
|
|
90
77
|
*/
|
|
91
|
-
|
|
92
|
-
this.
|
|
93
|
-
|
|
78
|
+
onScroll(onScrollListener) {
|
|
79
|
+
const element = this.getElement()
|
|
80
|
+
element.addEventListener('scroll', onScrollListener)
|
|
81
|
+
return () => element.removeEventListener('scroll', onScrollListener)
|
|
94
82
|
}
|
|
95
83
|
|
|
96
84
|
/**
|
|
97
85
|
* Adds a "resize" event listener to the "scrollable container".
|
|
98
86
|
* @param {onResize} Should be called whenever the "scrollable container"'s width or height (potentially) changes.
|
|
99
|
-
|
|
100
|
-
* @return {function} Returns a function that stops listening.
|
|
87
|
+
* @return {function} Returns a function that stops listening.
|
|
101
88
|
*/
|
|
102
|
-
onResize(onResize
|
|
103
|
-
//
|
|
104
|
-
// For now, `scrollableContainer` is supposed to have constant width and height.
|
|
105
|
-
// (unless window is resized).
|
|
89
|
+
onResize(onResize) {
|
|
90
|
+
// Watches "scrollable container"'s dimensions via a `ResizeObserver`.
|
|
106
91
|
// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
|
|
107
92
|
// https://web.dev/resize-observer/
|
|
108
93
|
let unobserve
|
|
@@ -110,7 +95,7 @@ export default class ScrollableContainer {
|
|
|
110
95
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
111
96
|
// "one entry per observed element".
|
|
112
97
|
// https://web.dev/resize-observer/
|
|
113
|
-
// `entry.target === this.
|
|
98
|
+
// `entry.target === this.getElement()`.
|
|
114
99
|
const entry = entries[0]
|
|
115
100
|
// // If `entry.contentBoxSize` property is supported by the web browser.
|
|
116
101
|
// if (entry.contentBoxSize) {
|
|
@@ -120,8 +105,9 @@ export default class ScrollableContainer {
|
|
|
120
105
|
// }
|
|
121
106
|
onResize()
|
|
122
107
|
})
|
|
123
|
-
|
|
124
|
-
|
|
108
|
+
const element = this.getElement()
|
|
109
|
+
resizeObserver.observe(element)
|
|
110
|
+
unobserve = () => resizeObserver.unobserve(element)
|
|
125
111
|
}
|
|
126
112
|
// I guess, if window is resized, `onResize()` will be triggered twice:
|
|
127
113
|
// once for window resize, and once for the scrollable container resize.
|
|
@@ -129,7 +115,9 @@ export default class ScrollableContainer {
|
|
|
129
115
|
// hasn't changed since the previous time `onResize()` has been called,
|
|
130
116
|
// then `onResize()` doesn't do anything, so, I guess, there shouldn't be
|
|
131
117
|
// any "performance implications" of running the listener twice in such case.
|
|
132
|
-
const unlistenGlobalResize = addGlobalResizeListener(onResize, {
|
|
118
|
+
const unlistenGlobalResize = addGlobalResizeListener(onResize, {
|
|
119
|
+
itemsContainerElement: this.getItemsContainerElement()
|
|
120
|
+
})
|
|
133
121
|
return () => {
|
|
134
122
|
if (unobserve) {
|
|
135
123
|
unobserve()
|
|
@@ -140,8 +128,12 @@ export default class ScrollableContainer {
|
|
|
140
128
|
}
|
|
141
129
|
|
|
142
130
|
export class ScrollableWindowContainer extends ScrollableContainer {
|
|
143
|
-
|
|
144
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Constructs a new window "scrollable container".
|
|
133
|
+
* @param {func} getItemsContainerElement — Returns items "container" element.
|
|
134
|
+
*/
|
|
135
|
+
constructor(getItemsContainerElement) {
|
|
136
|
+
super(() => window, getItemsContainerElement)
|
|
145
137
|
}
|
|
146
138
|
|
|
147
139
|
/**
|
|
@@ -189,38 +181,24 @@ export class ScrollableWindowContainer extends ScrollableContainer {
|
|
|
189
181
|
}
|
|
190
182
|
|
|
191
183
|
/**
|
|
192
|
-
* Returns
|
|
193
|
-
* For example, a scrollable container can have a height of 500px,
|
|
194
|
-
* but the content in it could have a height of 5000px,
|
|
195
|
-
* in which case a vertical scrollbar is rendered, and only
|
|
196
|
-
* one-tenth of all the items are shown at any given moment.
|
|
197
|
-
* This function is currently only used when using the
|
|
198
|
-
* `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.
|
|
199
|
-
* @return {number}
|
|
200
|
-
*/
|
|
201
|
-
getContentHeight() {
|
|
202
|
-
return document.documentElement.scrollHeight
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Returns a "top offset" of an element
|
|
184
|
+
* Returns a "top offset" of an items container element
|
|
207
185
|
* relative to the "scrollable container"'s top edge.
|
|
208
|
-
* @param {Element} element
|
|
209
186
|
* @return {number}
|
|
210
187
|
*/
|
|
211
|
-
|
|
188
|
+
getItemsContainerTopOffset() {
|
|
212
189
|
const borderTopWidth = document.clientTop || document.body.clientTop || 0
|
|
213
|
-
return
|
|
190
|
+
return this.getItemsContainerElement().getBoundingClientRect().top + this.getScrollY() - borderTopWidth
|
|
214
191
|
}
|
|
215
192
|
|
|
216
193
|
/**
|
|
217
194
|
* Adds a "resize" event listener to the "scrollable container".
|
|
218
195
|
* @param {onScroll} Should be called whenever the "scrollable container"'s width or height (potentially) changes.
|
|
219
|
-
* @param {Element} options.container — The result of the `getContainerElement()` function that was passed in `VirtualScroller` constructor. For example, DOM renderer uses it to filter-out unrelated "resize" events.
|
|
220
196
|
* @return {function} Returns a function that stops listening.
|
|
221
197
|
*/
|
|
222
|
-
onResize(onResize
|
|
223
|
-
return addGlobalResizeListener(onResize, {
|
|
198
|
+
onResize(onResize) {
|
|
199
|
+
return addGlobalResizeListener(onResize, {
|
|
200
|
+
itemsContainerElement: this.getItemsContainerElement()
|
|
201
|
+
})
|
|
224
202
|
}
|
|
225
203
|
|
|
226
204
|
// isVisible() {
|
|
@@ -230,11 +208,11 @@ export class ScrollableWindowContainer extends ScrollableContainer {
|
|
|
230
208
|
|
|
231
209
|
/**
|
|
232
210
|
* Adds a "resize" event listener to the `window`.
|
|
233
|
-
* @param {onResize} Should be called whenever the "container"'s width or height (potentially) changes.
|
|
234
|
-
* @param {Element} options.
|
|
211
|
+
* @param {onResize} Should be called whenever the "scrollable container"'s width or height (potentially) changes.
|
|
212
|
+
* @param {Element} options.itemsContainerElement — The items "container" element, which is not the same as the "scrollable container" element. For example, "scrollable container" could be resized while the list element retaining its size. One such example is a user entering fullscreen mode on an HTML5 `<video/>` element: in that case, a "resize" event is triggered on a window, and window dimensions change to the user's screen size, but such "resize" event can be ignored because the list isn't visible until the user exits fullscreen mode.
|
|
235
213
|
* @return {function} Returns a function that stops listening.
|
|
236
214
|
*/
|
|
237
|
-
function addGlobalResizeListener(onResize, {
|
|
215
|
+
function addGlobalResizeListener(onResize, { itemsContainerElement }) {
|
|
238
216
|
const onResizeListener = () => {
|
|
239
217
|
// By default, `VirtualScroller` always performs a re-layout
|
|
240
218
|
// on window `resize` event. But browsers (Chrome, Firefox)
|
|
@@ -265,7 +243,7 @@ function addGlobalResizeListener(onResize, { container }) {
|
|
|
265
243
|
// the layout wouldn't be affected too, so such `resize` event should also be
|
|
266
244
|
// ignored: when `document.fullscreenElement` is inside the `container`.
|
|
267
245
|
//
|
|
268
|
-
if (document.fullscreenElement.contains(
|
|
246
|
+
if (document.fullscreenElement.contains(itemsContainerElement)) {
|
|
269
247
|
// The element is either the `container`'s ancestor,
|
|
270
248
|
// Or is the `container` itself.
|
|
271
249
|
// (`a.contains(b)` includes the `a === b` case).
|
|
@@ -1,37 +1,42 @@
|
|
|
1
|
-
import VirtualScrollerCore from '../VirtualScroller'
|
|
1
|
+
import VirtualScrollerCore from '../VirtualScroller.js'
|
|
2
2
|
|
|
3
|
-
import log, { warn } from '../utility/debug'
|
|
4
|
-
import px from '../utility/px'
|
|
3
|
+
import log, { warn } from '../utility/debug.js'
|
|
4
|
+
import px from '../utility/px.js'
|
|
5
5
|
|
|
6
6
|
export default class VirtualScroller {
|
|
7
|
-
constructor(
|
|
8
|
-
this.container =
|
|
7
|
+
constructor(itemsContainerElement, items, renderItem, options = {}) {
|
|
8
|
+
this.container = itemsContainerElement
|
|
9
9
|
this.renderItem = renderItem
|
|
10
|
+
|
|
10
11
|
const {
|
|
11
12
|
onMount,
|
|
12
13
|
onItemUnmount,
|
|
13
14
|
...restOptions
|
|
14
15
|
} = options
|
|
16
|
+
|
|
15
17
|
this.onItemUnmount = onItemUnmount
|
|
16
18
|
this.tbody = this.container.tagName === 'TBODY'
|
|
19
|
+
|
|
17
20
|
this.virtualScroller = new VirtualScrollerCore(
|
|
18
21
|
() => this.container,
|
|
19
22
|
items,
|
|
20
23
|
{
|
|
21
24
|
...restOptions,
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
render: this.render,
|
|
26
|
+
tbody: this.tbody
|
|
24
27
|
}
|
|
25
28
|
)
|
|
29
|
+
|
|
30
|
+
this.start()
|
|
31
|
+
|
|
26
32
|
// `onMount()` option is deprecated due to no longer being used.
|
|
27
33
|
// If someone thinks there's a valid use case for it, create an issue.
|
|
28
34
|
if (onMount) {
|
|
29
35
|
onMount()
|
|
30
36
|
}
|
|
31
|
-
this.virtualScroller.listen()
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
render = (state, prevState) => {
|
|
35
40
|
const {
|
|
36
41
|
items,
|
|
37
42
|
firstShownItemIndex,
|
|
@@ -39,9 +44,11 @@ export default class VirtualScroller {
|
|
|
39
44
|
beforeItemsHeight,
|
|
40
45
|
afterItemsHeight
|
|
41
46
|
} = state
|
|
42
|
-
|
|
43
|
-
log('
|
|
44
|
-
log('
|
|
47
|
+
|
|
48
|
+
// log('~ On state change ~')
|
|
49
|
+
// log('Previous state', prevState)
|
|
50
|
+
// log('New state', state)
|
|
51
|
+
|
|
45
52
|
// Set container padding top and bottom.
|
|
46
53
|
// Work around `<tbody/>` not being able to have `padding`.
|
|
47
54
|
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/1
|
|
@@ -51,11 +58,11 @@ export default class VirtualScroller {
|
|
|
51
58
|
this.container.style.paddingTop = px(beforeItemsHeight)
|
|
52
59
|
this.container.style.paddingBottom = px(afterItemsHeight)
|
|
53
60
|
}
|
|
61
|
+
|
|
54
62
|
// Perform an intelligent "diff" re-render if the `items` are the same.
|
|
55
63
|
const diffRender = prevState && items === prevState.items && items.length > 0
|
|
56
64
|
// Remove no longer visible items from the DOM.
|
|
57
65
|
if (diffRender) {
|
|
58
|
-
log('Incremental rerender')
|
|
59
66
|
// Decrement instead of increment here because
|
|
60
67
|
// `this.container.removeChild()` changes indexes.
|
|
61
68
|
let i = prevState.lastShownItemIndex
|
|
@@ -63,18 +70,19 @@ export default class VirtualScroller {
|
|
|
63
70
|
if (i >= firstShownItemIndex && i <= lastShownItemIndex) {
|
|
64
71
|
// The item is still visible.
|
|
65
72
|
} else {
|
|
66
|
-
log('Remove item index', i)
|
|
73
|
+
log('DOM: Remove element for item index', i)
|
|
67
74
|
// The item is no longer visible. Remove it.
|
|
68
75
|
this.unmountItem(this.container.childNodes[i - prevState.firstShownItemIndex])
|
|
69
76
|
}
|
|
70
77
|
i--
|
|
71
78
|
}
|
|
72
79
|
} else {
|
|
73
|
-
log('Rerender from scratch')
|
|
80
|
+
log('DOM: Rerender the list from scratch')
|
|
74
81
|
while (this.container.firstChild) {
|
|
75
82
|
this.unmountItem(this.container.firstChild)
|
|
76
83
|
}
|
|
77
84
|
}
|
|
85
|
+
|
|
78
86
|
// Add newly visible items to the DOM.
|
|
79
87
|
let shouldPrependItems = diffRender
|
|
80
88
|
const prependBeforeItemElement = shouldPrependItems && this.container.firstChild
|
|
@@ -89,11 +97,11 @@ export default class VirtualScroller {
|
|
|
89
97
|
} else {
|
|
90
98
|
const item = this.renderItem(items[i])
|
|
91
99
|
if (shouldPrependItems) {
|
|
92
|
-
log('Prepend item index', i)
|
|
100
|
+
log('DOM: Prepend element for item index', i)
|
|
93
101
|
// Append `item` to `this.container` before the retained items.
|
|
94
102
|
this.container.insertBefore(item, prependBeforeItemElement)
|
|
95
103
|
} else {
|
|
96
|
-
log('Append item index', i)
|
|
104
|
+
log('DOM: Append element for item index', i)
|
|
97
105
|
// Append `item` to `this.container`.
|
|
98
106
|
this.container.appendChild(item)
|
|
99
107
|
}
|
|
@@ -114,11 +122,18 @@ export default class VirtualScroller {
|
|
|
114
122
|
this.stop()
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
// Public API.
|
|
125
|
+
// Public API.
|
|
126
|
+
// Should be "bound" to `this`.
|
|
118
127
|
stop = () => {
|
|
119
128
|
this.virtualScroller.stop()
|
|
120
129
|
}
|
|
121
130
|
|
|
131
|
+
// Potentially public API in some hypothetical scenario.
|
|
132
|
+
// Should be "bound" to `this`.
|
|
133
|
+
start = () => {
|
|
134
|
+
this.virtualScroller.start()
|
|
135
|
+
}
|
|
136
|
+
|
|
122
137
|
unmountItem(itemElement) {
|
|
123
138
|
this.container.removeChild(itemElement)
|
|
124
139
|
if (this.onItemUnmount) {
|
package/source/DOM/tbody.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// A workaround for `<tbody/>` not being able to have `padding`.
|
|
2
2
|
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/1
|
|
3
3
|
|
|
4
|
-
import px from '../utility/px'
|
|
4
|
+
import px from '../utility/px.js'
|
|
5
5
|
|
|
6
6
|
export const BROWSER_NOT_SUPPORTED_ERROR = 'It looks like you\'re using Internet Explorer which doesn\'t support CSS variables required for a <tbody/> container. VirtualScroller has been switched into "bypass" mode (render all items). See: https://gitlab.com/catamphetamine/virtual-scroller/-/issues/1'
|
|
7
7
|
|
|
@@ -18,28 +18,37 @@ export function supportsTbody() {
|
|
|
18
18
|
return true
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export const TBODY_CLASS_NAME = 'VirtualScroller'
|
|
22
|
+
const STYLE_ELEMENT_ID = 'VirtualScrollerStyle'
|
|
23
|
+
|
|
24
|
+
export function hasTbodyStyles(tbody) {
|
|
25
|
+
return tbody.classList.contains(TBODY_CLASS_NAME) &&
|
|
26
|
+
Boolean(document.getElementById(STYLE_ELEMENT_ID))
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
export function addTbodyStyles(tbody) {
|
|
22
30
|
// `classList.add` is supported in Internet Explorer 10+.
|
|
23
|
-
tbody.classList.add(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
tbody.classList.add(TBODY_CLASS_NAME)
|
|
32
|
+
|
|
33
|
+
// Create a `<style/>` element.
|
|
34
|
+
const style = document.createElement('style')
|
|
35
|
+
style.id = STYLE_ELEMENT_ID
|
|
36
|
+
|
|
37
|
+
// CSS variables aren't supported in Internet Explorer.
|
|
38
|
+
style.innerText = `
|
|
39
|
+
tbody.${TBODY_CLASS_NAME}:before {
|
|
40
|
+
content: '';
|
|
41
|
+
display: table-row;
|
|
42
|
+
height: var(--VirtualScroller-paddingTop);
|
|
43
|
+
}
|
|
44
|
+
tbody.${TBODY_CLASS_NAME}:after {
|
|
45
|
+
content: '';
|
|
46
|
+
display: table-row;
|
|
47
|
+
height: var(--VirtualScroller-paddingBottom);
|
|
48
|
+
}
|
|
49
|
+
`.replace(/[\n\t]/g, '')
|
|
50
|
+
|
|
51
|
+
document.head.appendChild(style)
|
|
43
52
|
}
|
|
44
53
|
|
|
45
54
|
export function setTbodyPadding(tbody, beforeItemsHeight, afterItemsHeight) {
|
package/source/ItemHeights.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import log, { warn, isDebug, reportError } from './utility/debug'
|
|
1
|
+
import log, { warn, isDebug, reportError } from './utility/debug.js'
|
|
2
2
|
|
|
3
3
|
export default class ItemHeights {
|
|
4
|
-
constructor(
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
constructor({
|
|
5
|
+
container,
|
|
6
|
+
itemHeights,
|
|
7
|
+
getItemHeight,
|
|
8
|
+
setItemHeight
|
|
9
|
+
}) {
|
|
10
|
+
this.container = container
|
|
7
11
|
this._get = getItemHeight
|
|
8
12
|
this._set = setItemHeight
|
|
9
13
|
this.reset()
|
|
@@ -25,14 +29,14 @@ export default class ItemHeights {
|
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
|
-
*
|
|
32
|
+
* Can only be called after a `.reset()` (including new instance creation).
|
|
29
33
|
* Initializes `this.measuredItemsHeight`, `this.firstMeasuredItemIndex`
|
|
30
34
|
* and `this.lastMeasuredItemIndex` instance variables from `VirtualScroller` `state`.
|
|
31
35
|
* These instance variables are used when calculating "average" item height:
|
|
32
36
|
* the "average" item height is simply `this.measuredItemsHeight` divided by
|
|
33
37
|
* `this.lastMeasuredItemIndex` minus `this.firstMeasuredItemIndex` plus 1.
|
|
34
38
|
*/
|
|
35
|
-
|
|
39
|
+
readItemHeightsFromState({ itemHeights }) {
|
|
36
40
|
let i = 0
|
|
37
41
|
while (i < itemHeights.length) {
|
|
38
42
|
if (itemHeights[i] === undefined) {
|
|
@@ -64,13 +68,7 @@ export default class ItemHeights {
|
|
|
64
68
|
// }
|
|
65
69
|
|
|
66
70
|
_measureItemHeight(i, firstShownItemIndex) {
|
|
67
|
-
|
|
68
|
-
if (container) {
|
|
69
|
-
const elementIndex = i - firstShownItemIndex
|
|
70
|
-
if (elementIndex >= 0 && elementIndex < this.screen.getChildElementsCount(container)) {
|
|
71
|
-
return this.screen.getChildElementHeight(container, elementIndex)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
71
|
+
return this.container.getNthRenderedItemHeight(i - firstShownItemIndex)
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
/**
|
|
@@ -92,6 +90,7 @@ export default class ItemHeights {
|
|
|
92
90
|
* @return {number[]} The indexes of the items that have not previously been measured and have been measured now.
|
|
93
91
|
*/
|
|
94
92
|
measureItemHeights(firstShownItemIndex, lastShownItemIndex) {
|
|
93
|
+
log('~ Measure item heights ~')
|
|
95
94
|
// If no items are rendered, don't measure anything.
|
|
96
95
|
if (firstShownItemIndex === undefined) {
|
|
97
96
|
return
|
|
@@ -102,8 +101,10 @@ export default class ItemHeights {
|
|
|
102
101
|
// then reset `this.measuredItemsHeight` and "first measured"/"last measured" item indexes.
|
|
103
102
|
// For example, this could happen when `.setItems()` prepends a lot of new items.
|
|
104
103
|
if (this.firstMeasuredItemIndex !== undefined) {
|
|
105
|
-
if (
|
|
106
|
-
|
|
104
|
+
if (
|
|
105
|
+
firstShownItemIndex > this.lastMeasuredItemIndex + 1 ||
|
|
106
|
+
lastShownItemIndex < this.firstMeasuredItemIndex - 1
|
|
107
|
+
) {
|
|
107
108
|
// Reset.
|
|
108
109
|
log('Non-measured items gap detected. Reset first and last measured item indexes.')
|
|
109
110
|
this.reset()
|
|
@@ -115,20 +116,16 @@ export default class ItemHeights {
|
|
|
115
116
|
let firstMeasuredItemIndexHasBeenUpdated = false
|
|
116
117
|
let i = firstShownItemIndex
|
|
117
118
|
while (i <= lastShownItemIndex) {
|
|
119
|
+
// Measure item heights that haven't been measured previously.
|
|
118
120
|
// Don't re-measure item heights that have been measured previously.
|
|
119
121
|
// The rationale is that developers are supposed to manually call
|
|
120
122
|
// `.onItemHeightChange()` every time an item's height changes.
|
|
121
|
-
// If developers
|
|
123
|
+
// If developers don't neglect that rule, item heights won't
|
|
122
124
|
// change unexpectedly.
|
|
123
|
-
// // Re-measure all shown items' heights, because an item's height
|
|
124
|
-
// // might have changed since it has been measured initially.
|
|
125
|
-
// // For example, if an item is a long comment with a "Show more" button,
|
|
126
|
-
// // then the user might have clicked that "Show more" button.
|
|
127
125
|
if (this._get(i) === undefined) {
|
|
128
126
|
nonPreviouslyMeasuredItemIndexes.push(i)
|
|
129
|
-
log('Item', i, 'hasn\'t been previously measured')
|
|
130
127
|
const height = this._measureItemHeight(i, firstShownItemIndex)
|
|
131
|
-
log('
|
|
128
|
+
log('Item index', i, 'height', height)
|
|
132
129
|
this._set(i, height)
|
|
133
130
|
// Update average item height calculation variables
|
|
134
131
|
// related to the previously measured items
|
|
@@ -164,14 +161,17 @@ export default class ItemHeights {
|
|
|
164
161
|
this.lastMeasuredItemIndex = i
|
|
165
162
|
}
|
|
166
163
|
} else {
|
|
167
|
-
// Validate the item's height
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
164
|
+
// Validate that the item's height didn't change since it was last measured.
|
|
165
|
+
// If it did, then display a warning and update the item's height
|
|
166
|
+
// as an attempt to fix things.
|
|
167
|
+
// If an item's height changes unexpectedly then it means that there'll
|
|
168
|
+
// likely be "content jumping".
|
|
171
169
|
const previousHeight = this._get(i)
|
|
172
170
|
const height = this._measureItemHeight(i, firstShownItemIndex)
|
|
173
171
|
if (previousHeight !== height) {
|
|
174
|
-
warn('Item', i, 'height was', previousHeight, 'before it
|
|
172
|
+
warn('Item index', i, 'height changed unexpectedly: it was', previousHeight, 'before, but now it is', height, '. An item\'s height is allowed to change only in two cases: when the item\'s "state" changes and the developer calls `onItemStateChange(i, newState)`, or when the item\'s height changes for some other reason and the developer calls `onItemHeightChange(i)`. Perhaps you forgot to persist the item\'s "state" by calling `onItemStateChange(i, newState)` when it changed, and that "state" got lost when the item element was unmounted, which resulted in a different height when the item was shown again having its "state" reset.')
|
|
173
|
+
// Update the item's height as an attempt to fix things.
|
|
174
|
+
this._set(i, height)
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
i++
|