virtual-scroller 1.7.7 → 1.8.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/.gitlab-ci.yml +1 -1
- package/CHANGELOG.md +24 -1
- package/README.md +139 -33
- package/babel.config.js +25 -0
- package/babel.js +5 -0
- 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 +319 -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} +56 -35
- package/commonjs/DOM/ListTopOffsetWatcher.js.map +1 -0
- package/commonjs/DOM/ScrollableContainer.js +56 -81
- package/commonjs/DOM/ScrollableContainer.js.map +1 -1
- package/commonjs/DOM/VirtualScroller.js +20 -15
- package/commonjs/DOM/VirtualScroller.js.map +1 -1
- package/commonjs/DOM/tbody.js +2 -2
- package/commonjs/ItemHeights.js +22 -29
- package/commonjs/ItemHeights.js.map +1 -1
- package/commonjs/Layout.js +588 -215
- package/commonjs/Layout.js.map +1 -1
- package/commonjs/Layout.test.js +191 -0
- package/commonjs/Layout.test.js.map +1 -0
- package/commonjs/ListHeightChangeWatcher.js +126 -0
- package/commonjs/ListHeightChangeWatcher.js.map +1 -0
- package/commonjs/Resize.js +22 -21
- package/commonjs/Resize.js.map +1 -1
- package/commonjs/Scroll.js +148 -88
- package/commonjs/Scroll.js.map +1 -1
- package/commonjs/VirtualScroller.js +1269 -390
- package/commonjs/VirtualScroller.js.map +1 -1
- 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/react/VirtualScroller.js +31 -37
- package/commonjs/react/VirtualScroller.js.map +1 -1
- package/commonjs/utility/debounce.js +26 -4
- 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.d.ts +23 -0
- package/index.d.ts +84 -0
- package/modules/BeforeResize.js +310 -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} +57 -35
- package/modules/DOM/ListTopOffsetWatcher.js.map +1 -0
- package/modules/DOM/ScrollableContainer.js +55 -80
- package/modules/DOM/ScrollableContainer.js.map +1 -1
- package/modules/DOM/VirtualScroller.js +15 -14
- package/modules/DOM/VirtualScroller.js.map +1 -1
- package/modules/ItemHeights.js +17 -28
- package/modules/ItemHeights.js.map +1 -1
- package/modules/Layout.js +582 -213
- package/modules/Layout.js.map +1 -1
- package/modules/Layout.test.js +185 -0
- package/modules/Layout.test.js.map +1 -0
- package/modules/ListHeightChangeWatcher.js +119 -0
- package/modules/ListHeightChangeWatcher.js.map +1 -0
- package/modules/Resize.js +21 -20
- package/modules/Resize.js.map +1 -1
- package/modules/Scroll.js +148 -87
- package/modules/Scroll.js.map +1 -1
- package/modules/VirtualScroller.js +1263 -390
- package/modules/VirtualScroller.js.map +1 -1
- 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 +31 -37
- package/modules/react/VirtualScroller.js.map +1 -1
- package/modules/utility/debounce.js +26 -4
- 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 +24 -22
- package/react/index.d.ts +27 -0
- package/source/BeforeResize.js +317 -0
- package/source/DOM/Engine.js +32 -0
- package/source/DOM/ItemsContainer.js +48 -0
- package/source/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +48 -22
- package/source/DOM/ScrollableContainer.js +39 -56
- package/source/DOM/VirtualScroller.js +6 -7
- package/source/ItemHeights.js +19 -24
- package/source/Layout.js +626 -252
- package/source/Layout.test.js +171 -0
- package/source/ListHeightChangeWatcher.js +94 -0
- package/source/Resize.js +23 -15
- package/source/Scroll.js +139 -78
- package/source/VirtualScroller.js +1243 -286
- package/source/getVerticalSpacing.js +7 -7
- package/source/react/VirtualScroller.js +2 -18
- package/source/utility/debounce.js +20 -3
- 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/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/source/DOM/RenderingEngine.js +0 -22
- package/source/DOM/Screen.js +0 -51
- package/source/RestoreScroll.js +0 -86
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
2
|
+
|
|
3
|
+
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
|
4
|
+
|
|
5
|
+
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
|
|
6
|
+
|
|
7
|
+
var ItemsContainer = /*#__PURE__*/function () {
|
|
8
|
+
/**
|
|
9
|
+
* Constructs a new "container" from an element.
|
|
10
|
+
* @param {function} getElement
|
|
11
|
+
*/
|
|
12
|
+
function ItemsContainer(getElement) {
|
|
13
|
+
_classCallCheck(this, ItemsContainer);
|
|
14
|
+
|
|
15
|
+
this.getElement = getElement;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns an item element's "top offset", relative to the items `container`'s top edge.
|
|
19
|
+
* @param {number} renderedElementIndex — An index of an item relative to the "first shown item index". For example, if the list is showing items from index 8 to index 12 then `renderedElementIndex = 0` would mean the item at index `8`.
|
|
20
|
+
* @return {number}
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_createClass(ItemsContainer, [{
|
|
25
|
+
key: "getNthRenderedItemTopOffset",
|
|
26
|
+
value: function getNthRenderedItemTopOffset(renderedElementIndex) {
|
|
27
|
+
return this.getElement().childNodes[renderedElementIndex].getBoundingClientRect().top - this.getElement().getBoundingClientRect().top;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns an item element's height.
|
|
31
|
+
* @param {number} renderedElementIndex — An index of an item relative to the "first shown item index". For example, if the list is showing items from index 8 to index 12 then `renderedElementIndex = 0` would mean the item at index `8`.
|
|
32
|
+
* @return {number}
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
}, {
|
|
36
|
+
key: "getNthRenderedItemHeight",
|
|
37
|
+
value: function getNthRenderedItemHeight(renderedElementIndex) {
|
|
38
|
+
// `offsetHeight` is not precise enough (doesn't return fractional pixels).
|
|
39
|
+
// return this.getElement().childNodes[renderedElementIndex].offsetHeight
|
|
40
|
+
return this.getElement().childNodes[renderedElementIndex].getBoundingClientRect().height;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns items container height.
|
|
44
|
+
* @return {number}
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
}, {
|
|
48
|
+
key: "getHeight",
|
|
49
|
+
value: function getHeight() {
|
|
50
|
+
// `offsetHeight` is not precise enough (doesn't return fractional pixels).
|
|
51
|
+
// return this.getElement().offsetHeight
|
|
52
|
+
return this.getElement().getBoundingClientRect().height;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Removes all item elements of an items container.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
}, {
|
|
59
|
+
key: "clear",
|
|
60
|
+
value: function clear() {
|
|
61
|
+
while (this.getElement().firstChild) {
|
|
62
|
+
this.getElement().removeChild(this.getElement().firstChild);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}]);
|
|
66
|
+
|
|
67
|
+
return ItemsContainer;
|
|
68
|
+
}();
|
|
69
|
+
|
|
70
|
+
export { ItemsContainer as default };
|
|
71
|
+
//# sourceMappingURL=ItemsContainer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../source/DOM/ItemsContainer.js"],"names":["ItemsContainer","getElement","renderedElementIndex","childNodes","getBoundingClientRect","top","height","firstChild","removeChild"],"mappings":";;;;;;IAAqBA,c;AACpB;AACD;AACA;AACA;AACC,0BAAYC,UAAZ,EAAwB;AAAA;;AACvB,SAAKA,UAAL,GAAkBA,UAAlB;AACA;AAED;AACD;AACA;AACA;AACA;;;;;WACC,qCAA4BC,oBAA5B,EAAkD;AACjD,aAAO,KAAKD,UAAL,GAAkBE,UAAlB,CAA6BD,oBAA7B,EAAmDE,qBAAnD,GAA2EC,GAA3E,GAAiF,KAAKJ,UAAL,GAAkBG,qBAAlB,GAA0CC,GAAlI;AACA;AAED;AACD;AACA;AACA;AACA;;;;WACC,kCAAyBH,oBAAzB,EAA+C;AAC9C;AACA;AACA,aAAO,KAAKD,UAAL,GAAkBE,UAAlB,CAA6BD,oBAA7B,EAAmDE,qBAAnD,GAA2EE,MAAlF;AACA;AAED;AACD;AACA;AACA;;;;WACC,qBAAY;AACX;AACA;AACA,aAAO,KAAKL,UAAL,GAAkBG,qBAAlB,GAA0CE,MAAjD;AACA;AAED;AACD;AACA;;;;WACC,iBAAQ;AACP,aAAO,KAAKL,UAAL,GAAkBM,UAAzB,EAAqC;AACpC,aAAKN,UAAL,GAAkBO,WAAlB,CAA8B,KAAKP,UAAL,GAAkBM,UAAhD;AACA;AACD;;;;;;SA9CmBP,c","sourcesContent":["export default class ItemsContainer {\r\n\t/**\r\n\t * Constructs a new \"container\" from an element.\r\n\t * @param {function} getElement\r\n\t */\r\n\tconstructor(getElement) {\r\n\t\tthis.getElement = getElement\r\n\t}\r\n\r\n\t/**\r\n\t * Returns an item element's \"top offset\", relative to the items `container`'s top edge.\r\n\t * @param {number} renderedElementIndex — An index of an item relative to the \"first shown item index\". For example, if the list is showing items from index 8 to index 12 then `renderedElementIndex = 0` would mean the item at index `8`.\r\n\t * @return {number}\r\n\t */\r\n\tgetNthRenderedItemTopOffset(renderedElementIndex) {\r\n\t\treturn this.getElement().childNodes[renderedElementIndex].getBoundingClientRect().top - this.getElement().getBoundingClientRect().top\r\n\t}\r\n\r\n\t/**\r\n\t * Returns an item element's height.\r\n\t * @param {number} renderedElementIndex — An index of an item relative to the \"first shown item index\". For example, if the list is showing items from index 8 to index 12 then `renderedElementIndex = 0` would mean the item at index `8`.\r\n\t * @return {number}\r\n\t */\r\n\tgetNthRenderedItemHeight(renderedElementIndex) {\r\n\t\t// `offsetHeight` is not precise enough (doesn't return fractional pixels).\r\n\t\t// return this.getElement().childNodes[renderedElementIndex].offsetHeight\r\n\t\treturn this.getElement().childNodes[renderedElementIndex].getBoundingClientRect().height\r\n\t}\r\n\r\n\t/**\r\n\t * Returns items container height.\r\n\t * @return {number}\r\n\t */\r\n\tgetHeight() {\r\n\t\t// `offsetHeight` is not precise enough (doesn't return fractional pixels).\r\n\t\t// return this.getElement().offsetHeight\r\n\t\treturn this.getElement().getBoundingClientRect().height\r\n\t}\r\n\r\n\t/**\r\n\t * Removes all item elements of an items container.\r\n\t */\r\n\tclear() {\r\n\t\twhile (this.getElement().firstChild) {\r\n\t\t\tthis.getElement().removeChild(this.getElement().firstChild)\r\n\t\t}\r\n\t}\r\n}"],"file":"ItemsContainer.js"}
|
|
@@ -8,53 +8,75 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
|
|
|
8
8
|
// Turns out, Chrome developers have deprecated `setTimeout()` API entirely without asking anyone.
|
|
9
9
|
// Replacing `setTimeout()` with `requestAnimationFrame()` can work around that Chrome bug.
|
|
10
10
|
// https://github.com/bvaughn/react-virtualized/issues/722
|
|
11
|
-
import { setTimeout, clearTimeout } from 'request-animation-frame-timeout';
|
|
12
|
-
|
|
11
|
+
import { setTimeout, clearTimeout } from 'request-animation-frame-timeout'; // Refreshing two times every seconds seems reasonable.
|
|
12
|
+
|
|
13
|
+
var WATCH_LIST_TOP_OFFSET_INTERVAL = 500; // Refreshing for 3 seconds after the initial page load seems reasonable.
|
|
14
|
+
|
|
15
|
+
var WATCH_LIST_TOP_OFFSET_MAX_DURATION = 3000; // `VirtualScroller` calls `this.layout.layOut()` on mount,
|
|
13
16
|
// but if the page styles are applied after `VirtualScroller` mounts
|
|
14
17
|
// (for example, if styles are applied via javascript, like Webpack does)
|
|
15
|
-
// then the list might not render correctly and will only show the first item.
|
|
16
|
-
// The reason
|
|
17
|
-
//
|
|
18
|
+
// then the list might not render correctly and it will only show the first item.
|
|
19
|
+
// The reason is that in that case calling `.getListTopOffset()` on mount
|
|
20
|
+
// returns "incorrect" `top` position because the styles haven't been applied yet.
|
|
21
|
+
//
|
|
18
22
|
// For example, consider a page:
|
|
19
23
|
// <div class="page">
|
|
20
24
|
// <nav class="sidebar">...</nav>
|
|
21
25
|
// <main>...</main>
|
|
22
26
|
// </div>
|
|
27
|
+
//
|
|
23
28
|
// The sidebar is styled as `position: fixed`, but until
|
|
24
29
|
// the page styles have been applied it's gonna be a regular `<div/>`
|
|
25
30
|
// meaning that `<main/>` will be rendered below the sidebar
|
|
26
31
|
// and will appear offscreen and so it will only render the first item.
|
|
32
|
+
//
|
|
27
33
|
// Then, the page styles are loaded and applied and the sidebar
|
|
28
34
|
// is now `position: fixed` so `<main/>` is now rendered at the top of the page
|
|
29
35
|
// but `VirtualScroller`'s `.render()` has already been called
|
|
30
36
|
// and it won't re-render until the user scrolls or the window is resized.
|
|
31
|
-
//
|
|
37
|
+
//
|
|
38
|
+
// This type of a bug doesn't seem to occur in production, but it can appear
|
|
32
39
|
// in development mode when using Webpack. The workaround `VirtualScroller`
|
|
33
|
-
// implements for such cases is calling `.
|
|
40
|
+
// implements for such cases is calling `.getListTopOffset()`
|
|
34
41
|
// on the list container DOM element periodically (every second) to check
|
|
35
42
|
// if the `top` coordinate has changed as a result of CSS being applied:
|
|
36
43
|
// if it has then it recalculates the shown item indexes.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
//
|
|
45
|
+
// Maybe this bug could occur in production when using Webpack chunks.
|
|
46
|
+
// That depends on how a style of a chunk is added to the page:
|
|
47
|
+
// if it's added via `javascript` after the page has been rendered
|
|
48
|
+
// then this workaround will also work for that case.
|
|
49
|
+
//
|
|
50
|
+
// Another example would be a page having a really tall expanded "accordion"
|
|
51
|
+
// section, below which a `VirtualScroller` list resides. If the user un-expands
|
|
52
|
+
// such expanded "accordion" section, the list would become visible but
|
|
53
|
+
// it wouldn't get re-rendered because no `scroll` event has occured,
|
|
54
|
+
// and the list only re-renders automatically on `scroll` events.
|
|
55
|
+
// To work around such cases, call `virtualScroller.updateLayout()` method manually.
|
|
56
|
+
// The workaround below could be extended to refresh the list's top coordinate
|
|
57
|
+
// indefinitely and at higher intervals, but why waste CPU time on that.
|
|
58
|
+
// There doesn't seem to be any DOM API for tracking an element's top position.
|
|
59
|
+
// There is `IntersectionObserver` API but it doesn't exactly do that.
|
|
60
|
+
//
|
|
61
|
+
|
|
62
|
+
var ListTopOffsetWatcher = /*#__PURE__*/function () {
|
|
63
|
+
function ListTopOffsetWatcher(_ref) {
|
|
64
|
+
var getListTopOffset = _ref.getListTopOffset,
|
|
65
|
+
onListTopOffsetChange = _ref.onListTopOffsetChange;
|
|
66
|
+
|
|
67
|
+
_classCallCheck(this, ListTopOffsetWatcher);
|
|
68
|
+
|
|
69
|
+
this.getListTopOffset = getListTopOffset;
|
|
70
|
+
this.onListTopOffsetChange = onListTopOffsetChange;
|
|
49
71
|
}
|
|
50
72
|
|
|
51
|
-
_createClass(
|
|
52
|
-
key: "
|
|
53
|
-
value: function
|
|
73
|
+
_createClass(ListTopOffsetWatcher, [{
|
|
74
|
+
key: "onListTopOffset",
|
|
75
|
+
value: function onListTopOffset(listTopOffset) {
|
|
54
76
|
if (this.listTopOffsetInsideScrollableContainer === undefined) {
|
|
55
77
|
// Start periodical checks of the list's top offset
|
|
56
78
|
// in order to perform a re-layout in case it changes.
|
|
57
|
-
// See the comments in `
|
|
79
|
+
// See the comments in `ListTopOffsetWatcher.js` file
|
|
58
80
|
// on why can the list's top offset change, and in which circumstances.
|
|
59
81
|
this.start();
|
|
60
82
|
}
|
|
@@ -71,7 +93,11 @@ function () {
|
|
|
71
93
|
key: "stop",
|
|
72
94
|
value: function stop() {
|
|
73
95
|
this.isRendered = false;
|
|
74
|
-
|
|
96
|
+
|
|
97
|
+
if (this.watchListTopOffsetTimer) {
|
|
98
|
+
clearTimeout(this.watchListTopOffsetTimer);
|
|
99
|
+
this.watchListTopOffsetTimer = undefined;
|
|
100
|
+
}
|
|
75
101
|
}
|
|
76
102
|
}, {
|
|
77
103
|
key: "watchListTopOffset",
|
|
@@ -90,13 +116,11 @@ function () {
|
|
|
90
116
|
|
|
91
117
|
|
|
92
118
|
if (_this.listTopOffsetInsideScrollableContainer !== undefined) {
|
|
93
|
-
// Calling `this.
|
|
94
|
-
//
|
|
119
|
+
// Calling `this.getListTopOffset()` on an element
|
|
120
|
+
// runs about 0.003 milliseconds on a modern desktop CPU,
|
|
95
121
|
// so I guess it's fine calling it twice a second.
|
|
96
|
-
if (_this.
|
|
97
|
-
_this.
|
|
98
|
-
reason: LAYOUT_REASON.TOP_OFFSET_CHANGED
|
|
99
|
-
});
|
|
122
|
+
if (_this.getListTopOffset() !== _this.listTopOffsetInsideScrollableContainer) {
|
|
123
|
+
_this.onListTopOffsetChange();
|
|
100
124
|
}
|
|
101
125
|
} // Compare `top` coordinate of the list twice a second
|
|
102
126
|
// to find out if it has changed as a result of loading CSS styles.
|
|
@@ -117,10 +141,8 @@ function () {
|
|
|
117
141
|
}
|
|
118
142
|
}]);
|
|
119
143
|
|
|
120
|
-
return
|
|
144
|
+
return ListTopOffsetWatcher;
|
|
121
145
|
}();
|
|
122
146
|
|
|
123
|
-
export {
|
|
124
|
-
|
|
125
|
-
var WATCH_LIST_TOP_OFFSET_MAX_DURATION = 3000;
|
|
126
|
-
//# sourceMappingURL=WaitForStylesToLoad.js.map
|
|
147
|
+
export { ListTopOffsetWatcher as default };
|
|
148
|
+
//# sourceMappingURL=ListTopOffsetWatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../source/DOM/ListTopOffsetWatcher.js"],"names":["setTimeout","clearTimeout","WATCH_LIST_TOP_OFFSET_INTERVAL","WATCH_LIST_TOP_OFFSET_MAX_DURATION","ListTopOffsetWatcher","getListTopOffset","onListTopOffsetChange","listTopOffset","listTopOffsetInsideScrollableContainer","undefined","start","isRendered","watchListTopOffset","watchListTopOffsetTimer","startedAt","Date","now","check"],"mappings":";;;;;;AAAA;AACA;AACA;AACA;AACA,SAASA,UAAT,EAAqBC,YAArB,QAAyC,iCAAzC,C,CAEA;;AACA,IAAMC,8BAA8B,GAAG,GAAvC,C,CAEA;;AACA,IAAMC,kCAAkC,GAAG,IAA3C,C,CAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;IACqBC,oB;AACpB,sCAGG;AAAA,QAFFC,gBAEE,QAFFA,gBAEE;AAAA,QADFC,qBACE,QADFA,qBACE;;AAAA;;AACF,SAAKD,gBAAL,GAAwBA,gBAAxB;AACA,SAAKC,qBAAL,GAA6BA,qBAA7B;AACA;;;;WAED,yBAAgBC,aAAhB,EAA+B;AAC9B,UAAI,KAAKC,sCAAL,KAAgDC,SAApD,EAA+D;AAC9D;AACA;AACA;AACA;AACA,aAAKC,KAAL;AACA;;AACD,WAAKF,sCAAL,GAA8CD,aAA9C;AACA;;;WAED,iBAAQ;AACP,WAAKI,UAAL,GAAkB,IAAlB;AACA,WAAKC,kBAAL;AACA;;;WAED,gBAAO;AACN,WAAKD,UAAL,GAAkB,KAAlB;;AAEA,UAAI,KAAKE,uBAAT,EAAkC;AACjCZ,QAAAA,YAAY,CAAC,KAAKY,uBAAN,CAAZ;AACA,aAAKA,uBAAL,GAA+BJ,SAA/B;AACA;AACD;;;WAED,8BAAqB;AAAA;;AACpB,UAAMK,SAAS,GAAGC,IAAI,CAACC,GAAL,EAAlB;;AACA,UAAMC,KAAK,GAAG,SAARA,KAAQ,GAAM;AACnB;AACA;AACA,YAAI,CAAC,KAAI,CAACN,UAAV,EAAsB;AACrB;AACA,SALkB,CAMnB;AACA;;;AACA,YAAI,KAAI,CAACH,sCAAL,KAAgDC,SAApD,EAA+D;AAC9D;AACA;AACA;AACA,cAAI,KAAI,CAACJ,gBAAL,OAA4B,KAAI,CAACG,sCAArC,EAA6E;AAC5E,YAAA,KAAI,CAACF,qBAAL;AACA;AACD,SAfkB,CAgBnB;AACA;AACA;AACA;AACA;AACA;AACA;;;AACA,YAAIS,IAAI,CAACC,GAAL,KAAaF,SAAb,GAAyBX,kCAA7B,EAAiE;AAChE,UAAA,KAAI,CAACU,uBAAL,GAA+Bb,UAAU,CAACiB,KAAD,EAAQf,8BAAR,CAAzC;AACA;AACD,OA1BD,CAFoB,CA6BpB;;;AACAe,MAAAA,KAAK;AACL;;;;;;SAjEmBb,oB","sourcesContent":["// For some weird reason, in Chrome, `setTimeout()` would lag up to a second (or more) behind.\r\n// Turns out, Chrome developers have deprecated `setTimeout()` API entirely without asking anyone.\r\n// Replacing `setTimeout()` with `requestAnimationFrame()` can work around that Chrome bug.\r\n// https://github.com/bvaughn/react-virtualized/issues/722\r\nimport { setTimeout, clearTimeout } from 'request-animation-frame-timeout'\r\n\r\n// Refreshing two times every seconds seems reasonable.\r\nconst WATCH_LIST_TOP_OFFSET_INTERVAL = 500\r\n\r\n// Refreshing for 3 seconds after the initial page load seems reasonable.\r\nconst WATCH_LIST_TOP_OFFSET_MAX_DURATION = 3000\r\n\r\n// `VirtualScroller` calls `this.layout.layOut()` on mount,\r\n// but if the page styles are applied after `VirtualScroller` mounts\r\n// (for example, if styles are applied via javascript, like Webpack does)\r\n// then the list might not render correctly and it will only show the first item.\r\n// The reason is that in that case calling `.getListTopOffset()` on mount\r\n// returns \"incorrect\" `top` position because the styles haven't been applied yet.\r\n//\r\n// For example, consider a page:\r\n// <div class=\"page\">\r\n// <nav class=\"sidebar\">...</nav>\r\n// <main>...</main>\r\n// </div>\r\n//\r\n// The sidebar is styled as `position: fixed`, but until\r\n// the page styles have been applied it's gonna be a regular `<div/>`\r\n// meaning that `<main/>` will be rendered below the sidebar\r\n// and will appear offscreen and so it will only render the first item.\r\n//\r\n// Then, the page styles are loaded and applied and the sidebar\r\n// is now `position: fixed` so `<main/>` is now rendered at the top of the page\r\n// but `VirtualScroller`'s `.render()` has already been called\r\n// and it won't re-render until the user scrolls or the window is resized.\r\n//\r\n// This type of a bug doesn't seem to occur in production, but it can appear\r\n// in development mode when using Webpack. The workaround `VirtualScroller`\r\n// implements for such cases is calling `.getListTopOffset()`\r\n// on the list container DOM element periodically (every second) to check\r\n// if the `top` coordinate has changed as a result of CSS being applied:\r\n// if it has then it recalculates the shown item indexes.\r\n//\r\n// Maybe this bug could occur in production when using Webpack chunks.\r\n// That depends on how a style of a chunk is added to the page:\r\n// if it's added via `javascript` after the page has been rendered\r\n// then this workaround will also work for that case.\r\n//\r\n// Another example would be a page having a really tall expanded \"accordion\"\r\n// section, below which a `VirtualScroller` list resides. If the user un-expands\r\n// such expanded \"accordion\" section, the list would become visible but\r\n// it wouldn't get re-rendered because no `scroll` event has occured,\r\n// and the list only re-renders automatically on `scroll` events.\r\n// To work around such cases, call `virtualScroller.updateLayout()` method manually.\r\n// The workaround below could be extended to refresh the list's top coordinate\r\n// indefinitely and at higher intervals, but why waste CPU time on that.\r\n// There doesn't seem to be any DOM API for tracking an element's top position.\r\n// There is `IntersectionObserver` API but it doesn't exactly do that.\r\n//\r\nexport default class ListTopOffsetWatcher {\r\n\tconstructor({\r\n\t\tgetListTopOffset,\r\n\t\tonListTopOffsetChange\r\n\t}) {\r\n\t\tthis.getListTopOffset = getListTopOffset\r\n\t\tthis.onListTopOffsetChange = onListTopOffsetChange\r\n\t}\r\n\r\n\tonListTopOffset(listTopOffset) {\r\n\t\tif (this.listTopOffsetInsideScrollableContainer === undefined) {\r\n\t\t\t// Start periodical checks of the list's top offset\r\n\t\t\t// in order to perform a re-layout in case it changes.\r\n\t\t\t// See the comments in `ListTopOffsetWatcher.js` file\r\n\t\t\t// on why can the list's top offset change, and in which circumstances.\r\n\t\t\tthis.start()\r\n\t\t}\r\n\t\tthis.listTopOffsetInsideScrollableContainer = listTopOffset\r\n\t}\r\n\r\n\tstart() {\r\n\t\tthis.isRendered = true\r\n\t\tthis.watchListTopOffset()\r\n\t}\r\n\r\n\tstop() {\r\n\t\tthis.isRendered = false\r\n\r\n\t\tif (this.watchListTopOffsetTimer) {\r\n\t\t\tclearTimeout(this.watchListTopOffsetTimer)\r\n\t\t\tthis.watchListTopOffsetTimer = undefined\r\n\t\t}\r\n\t}\r\n\r\n\twatchListTopOffset() {\r\n\t\tconst startedAt = Date.now()\r\n\t\tconst check = () => {\r\n\t\t\t// If `VirtualScroller` has been unmounted\r\n\t\t\t// while `setTimeout()` was waiting, then exit.\r\n\t\t\tif (!this.isRendered) {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\t// Skip comparing `top` coordinate of the list\r\n\t\t\t// when this function is called for the first time.\r\n\t\t\tif (this.listTopOffsetInsideScrollableContainer !== undefined) {\r\n\t\t\t\t// Calling `this.getListTopOffset()` on an element\r\n\t\t\t\t// runs about 0.003 milliseconds on a modern desktop CPU,\r\n\t\t\t\t// so I guess it's fine calling it twice a second.\r\n\t\t\t\tif (this.getListTopOffset() !== this.listTopOffsetInsideScrollableContainer) {\r\n\t\t\t\t\tthis.onListTopOffsetChange()\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// Compare `top` coordinate of the list twice a second\r\n\t\t\t// to find out if it has changed as a result of loading CSS styles.\r\n\t\t\t// The total duration of 3 seconds would be enough for any styles to load, I guess.\r\n\t\t\t// There could be other cases changing the `top` coordinate\r\n\t\t\t// of the list (like collapsing an \"accordeon\" panel above the list\r\n\t\t\t// without scrolling the page), but those cases should be handled\r\n\t\t\t// by manually calling `.updateLayout()` instance method on `VirtualScroller` instance.\r\n\t\t\tif (Date.now() - startedAt < WATCH_LIST_TOP_OFFSET_MAX_DURATION) {\r\n\t\t\t\tthis.watchListTopOffsetTimer = setTimeout(check, WATCH_LIST_TOP_OFFSET_INTERVAL)\r\n\t\t\t}\r\n\t\t}\r\n\t\t// Run the cycle.\r\n\t\tcheck()\r\n\t}\r\n}"],"file":"ListTopOffsetWatcher.js"}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
|
|
1
|
+
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
|
|
2
2
|
|
|
3
|
-
function
|
|
3
|
+
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
|
|
4
4
|
|
|
5
|
-
function
|
|
5
|
+
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
|
6
6
|
|
|
7
|
-
function
|
|
7
|
+
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
|
8
8
|
|
|
9
|
-
function
|
|
9
|
+
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
|
|
10
10
|
|
|
11
|
-
function
|
|
11
|
+
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
|
12
|
+
|
|
13
|
+
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
|
|
14
|
+
|
|
15
|
+
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
|
12
16
|
|
|
13
17
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
14
18
|
|
|
@@ -16,17 +20,17 @@ function _defineProperties(target, props) { for (var i = 0; i < props.length; i+
|
|
|
16
20
|
|
|
17
21
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
|
|
18
22
|
|
|
19
|
-
var ScrollableContainer =
|
|
20
|
-
/*#__PURE__*/
|
|
21
|
-
function () {
|
|
23
|
+
var ScrollableContainer = /*#__PURE__*/function () {
|
|
22
24
|
/**
|
|
23
25
|
* Constructs a new "scrollable container" from an element.
|
|
24
26
|
* @param {Element} scrollableContainer
|
|
27
|
+
* @param {func} getItemsContainerElement — Returns items "container" element.
|
|
25
28
|
*/
|
|
26
|
-
function ScrollableContainer(element) {
|
|
29
|
+
function ScrollableContainer(element, getItemsContainerElement) {
|
|
27
30
|
_classCallCheck(this, ScrollableContainer);
|
|
28
31
|
|
|
29
32
|
this.element = element;
|
|
33
|
+
this.getItemsContainerElement = getItemsContainerElement;
|
|
30
34
|
}
|
|
31
35
|
/**
|
|
32
36
|
* Returns the current scroll position.
|
|
@@ -47,7 +51,14 @@ function () {
|
|
|
47
51
|
}, {
|
|
48
52
|
key: "scrollToY",
|
|
49
53
|
value: function scrollToY(scrollY) {
|
|
50
|
-
|
|
54
|
+
// IE 11 doesn't seem to have a `.scrollTo()` method.
|
|
55
|
+
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/10
|
|
56
|
+
// https://stackoverflow.com/questions/39908825/window-scrollto-is-not-working-in-internet-explorer-11
|
|
57
|
+
if (this.element.scrollTo) {
|
|
58
|
+
this.element.scrollTo(0, scrollY);
|
|
59
|
+
} else {
|
|
60
|
+
this.element.scrollTop = scrollY;
|
|
61
|
+
}
|
|
51
62
|
}
|
|
52
63
|
/**
|
|
53
64
|
* Returns "scrollable container" width,
|
|
@@ -75,35 +86,18 @@ function () {
|
|
|
75
86
|
return this.element.offsetHeight;
|
|
76
87
|
}
|
|
77
88
|
/**
|
|
78
|
-
* Returns
|
|
79
|
-
* For example, a scrollable container can have a height of 500px,
|
|
80
|
-
* but the content in it could have a height of 5000px,
|
|
81
|
-
* in which case a vertical scrollbar is rendered, and only
|
|
82
|
-
* one-tenth of all the items are shown at any given moment.
|
|
83
|
-
* This function is currently only used when using the
|
|
84
|
-
* `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.
|
|
85
|
-
* @return {number}
|
|
86
|
-
*/
|
|
87
|
-
|
|
88
|
-
}, {
|
|
89
|
-
key: "getContentHeight",
|
|
90
|
-
value: function getContentHeight() {
|
|
91
|
-
return this.element.scrollHeight;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Returns a "top offset" of an element
|
|
89
|
+
* Returns a "top offset" of an items container element
|
|
95
90
|
* relative to the "scrollable container"'s top edge.
|
|
96
|
-
* @param {Element} element
|
|
97
91
|
* @return {number}
|
|
98
92
|
*/
|
|
99
93
|
|
|
100
94
|
}, {
|
|
101
|
-
key: "
|
|
102
|
-
value: function
|
|
95
|
+
key: "getItemsContainerTopOffset",
|
|
96
|
+
value: function getItemsContainerTopOffset() {
|
|
103
97
|
var scrollableContainerTop = this.element.getBoundingClientRect().top;
|
|
104
98
|
var scrollableContainerBorderTopWidth = this.element.clientTop;
|
|
105
|
-
var
|
|
106
|
-
return
|
|
99
|
+
var itemsContainerTop = this.getItemsContainerElement().getBoundingClientRect().top;
|
|
100
|
+
return itemsContainerTop - scrollableContainerTop + this.getScrollY() - scrollableContainerBorderTopWidth;
|
|
107
101
|
} // isVisible() {
|
|
108
102
|
// const { top, bottom } = this.element.getBoundingClientRect()
|
|
109
103
|
// return bottom > 0 && top < getScreenHeight()
|
|
@@ -116,31 +110,27 @@ function () {
|
|
|
116
110
|
*/
|
|
117
111
|
|
|
118
112
|
}, {
|
|
119
|
-
key: "
|
|
120
|
-
value: function
|
|
113
|
+
key: "onScroll",
|
|
114
|
+
value: function onScroll(_onScroll) {
|
|
121
115
|
var _this = this;
|
|
122
116
|
|
|
123
|
-
this.element.addEventListener('scroll',
|
|
117
|
+
this.element.addEventListener('scroll', _onScroll);
|
|
124
118
|
return function () {
|
|
125
|
-
return _this.element.removeEventListener('scroll',
|
|
119
|
+
return _this.element.removeEventListener('scroll', _onScroll);
|
|
126
120
|
};
|
|
127
121
|
}
|
|
128
122
|
/**
|
|
129
123
|
* Adds a "resize" event listener to the "scrollable container".
|
|
130
124
|
* @param {onResize} Should be called whenever the "scrollable container"'s width or height (potentially) changes.
|
|
131
|
-
|
|
132
|
-
* @return {function} Returns a function that stops listening.
|
|
125
|
+
* @return {function} Returns a function that stops listening.
|
|
133
126
|
*/
|
|
134
127
|
|
|
135
128
|
}, {
|
|
136
129
|
key: "onResize",
|
|
137
|
-
value: function onResize(_onResize
|
|
130
|
+
value: function onResize(_onResize) {
|
|
138
131
|
var _this2 = this;
|
|
139
132
|
|
|
140
|
-
|
|
141
|
-
// Could somehow track DOM Element size.
|
|
142
|
-
// For now, `scrollableContainer` is supposed to have constant width and height.
|
|
143
|
-
// (unless window is resized).
|
|
133
|
+
// Watches "scrollable container"'s dimensions via a `ResizeObserver`.
|
|
144
134
|
// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
|
|
145
135
|
// https://web.dev/resize-observer/
|
|
146
136
|
var unobserve;
|
|
@@ -173,7 +163,7 @@ function () {
|
|
|
173
163
|
|
|
174
164
|
|
|
175
165
|
var unlistenGlobalResize = addGlobalResizeListener(_onResize, {
|
|
176
|
-
|
|
166
|
+
itemsContainerElement: this.getItemsContainerElement()
|
|
177
167
|
});
|
|
178
168
|
return function () {
|
|
179
169
|
if (unobserve) {
|
|
@@ -189,15 +179,19 @@ function () {
|
|
|
189
179
|
}();
|
|
190
180
|
|
|
191
181
|
export { ScrollableContainer as default };
|
|
192
|
-
export var ScrollableWindowContainer =
|
|
193
|
-
/*#__PURE__*/
|
|
194
|
-
function (_ScrollableContainer) {
|
|
182
|
+
export var ScrollableWindowContainer = /*#__PURE__*/function (_ScrollableContainer) {
|
|
195
183
|
_inherits(ScrollableWindowContainer, _ScrollableContainer);
|
|
196
184
|
|
|
197
|
-
|
|
185
|
+
var _super = _createSuper(ScrollableWindowContainer);
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Constructs a new window "scrollable container".
|
|
189
|
+
* @param {func} getItemsContainerElement — Returns items "container" element.
|
|
190
|
+
*/
|
|
191
|
+
function ScrollableWindowContainer(getItemsContainerElement) {
|
|
198
192
|
_classCallCheck(this, ScrollableWindowContainer);
|
|
199
193
|
|
|
200
|
-
return
|
|
194
|
+
return _super.call(this, window, getItemsContainerElement);
|
|
201
195
|
}
|
|
202
196
|
/**
|
|
203
197
|
* Returns the current scroll position.
|
|
@@ -251,47 +245,28 @@ function (_ScrollableContainer) {
|
|
|
251
245
|
return window.innerHeight;
|
|
252
246
|
}
|
|
253
247
|
/**
|
|
254
|
-
* Returns
|
|
255
|
-
* For example, a scrollable container can have a height of 500px,
|
|
256
|
-
* but the content in it could have a height of 5000px,
|
|
257
|
-
* in which case a vertical scrollbar is rendered, and only
|
|
258
|
-
* one-tenth of all the items are shown at any given moment.
|
|
259
|
-
* This function is currently only used when using the
|
|
260
|
-
* `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.
|
|
261
|
-
* @return {number}
|
|
262
|
-
*/
|
|
263
|
-
|
|
264
|
-
}, {
|
|
265
|
-
key: "getContentHeight",
|
|
266
|
-
value: function getContentHeight() {
|
|
267
|
-
return document.documentElement.scrollHeight;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Returns a "top offset" of an element
|
|
248
|
+
* Returns a "top offset" of an items container element
|
|
271
249
|
* relative to the "scrollable container"'s top edge.
|
|
272
|
-
* @param {Element} element
|
|
273
250
|
* @return {number}
|
|
274
251
|
*/
|
|
275
252
|
|
|
276
253
|
}, {
|
|
277
|
-
key: "
|
|
278
|
-
value: function
|
|
254
|
+
key: "getItemsContainerTopOffset",
|
|
255
|
+
value: function getItemsContainerTopOffset() {
|
|
279
256
|
var borderTopWidth = document.clientTop || document.body.clientTop || 0;
|
|
280
|
-
return
|
|
257
|
+
return this.getItemsContainerElement().getBoundingClientRect().top + this.getScrollY() - borderTopWidth;
|
|
281
258
|
}
|
|
282
259
|
/**
|
|
283
260
|
* Adds a "resize" event listener to the "scrollable container".
|
|
284
261
|
* @param {onScroll} Should be called whenever the "scrollable container"'s width or height (potentially) changes.
|
|
285
|
-
* @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.
|
|
286
262
|
* @return {function} Returns a function that stops listening.
|
|
287
263
|
*/
|
|
288
264
|
|
|
289
265
|
}, {
|
|
290
266
|
key: "onResize",
|
|
291
|
-
value: function onResize(_onResize2
|
|
292
|
-
var container = _ref2.container;
|
|
267
|
+
value: function onResize(_onResize2) {
|
|
293
268
|
return addGlobalResizeListener(_onResize2, {
|
|
294
|
-
|
|
269
|
+
itemsContainerElement: this.getItemsContainerElement()
|
|
295
270
|
});
|
|
296
271
|
} // isVisible() {
|
|
297
272
|
// return true
|
|
@@ -303,13 +278,13 @@ function (_ScrollableContainer) {
|
|
|
303
278
|
}(ScrollableContainer);
|
|
304
279
|
/**
|
|
305
280
|
* Adds a "resize" event listener to the `window`.
|
|
306
|
-
* @param {onResize} Should be called whenever the "container"'s width or height (potentially) changes.
|
|
307
|
-
* @param {Element} options.
|
|
281
|
+
* @param {onResize} Should be called whenever the "scrollable container"'s width or height (potentially) changes.
|
|
282
|
+
* @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.
|
|
308
283
|
* @return {function} Returns a function that stops listening.
|
|
309
284
|
*/
|
|
310
285
|
|
|
311
|
-
function addGlobalResizeListener(onResize,
|
|
312
|
-
var
|
|
286
|
+
function addGlobalResizeListener(onResize, _ref) {
|
|
287
|
+
var itemsContainerElement = _ref.itemsContainerElement;
|
|
313
288
|
|
|
314
289
|
var onResizeListener = function onResizeListener() {
|
|
315
290
|
// By default, `VirtualScroller` always performs a re-layout
|
|
@@ -341,7 +316,7 @@ function addGlobalResizeListener(onResize, _ref3) {
|
|
|
341
316
|
// the layout wouldn't be affected too, so such `resize` event should also be
|
|
342
317
|
// ignored: when `document.fullscreenElement` is inside the `container`.
|
|
343
318
|
//
|
|
344
|
-
if (document.fullscreenElement.contains(
|
|
319
|
+
if (document.fullscreenElement.contains(itemsContainerElement)) {// The element is either the `container`'s ancestor,
|
|
345
320
|
// Or is the `container` itself.
|
|
346
321
|
// (`a.contains(b)` includes the `a === b` case).
|
|
347
322
|
// So the `resize` event will affect the `container`'s dimensions.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../source/DOM/ScrollableContainer.js"],"names":["ScrollableContainer","element","scrollTop","scrollY","scrollTo","offsetWidth","offsetHeight","scrollHeight","scrollableContainerTop","getBoundingClientRect","top","scrollableContainerBorderTopWidth","clientTop","getScrollY","onScroll","addEventListener","removeEventListener","onResize","container","unobserve","ResizeObserver","resizeObserver","entries","entry","observe","unlistenGlobalResize","addGlobalResizeListener","ScrollableWindowContainer","window","pageYOffset","innerWidth","innerHeight","document","documentElement","borderTopWidth","body","onResizeListener","fullscreenElement","contains"],"mappings":"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","sourcesContent":["export default class ScrollableContainer {\r\n\t/**\r\n\t * Constructs a new \"scrollable container\" from an element.\r\n\t * @param {Element} scrollableContainer\r\n\t */\r\n\tconstructor(element) {\r\n\t\tthis.element = element\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the current scroll position.\r\n\t * @return {number}\r\n\t */\r\n\tgetScrollY() {\r\n\t\treturn this.element.scrollTop\r\n\t}\r\n\r\n\t/**\r\n\t * Scrolls to a specific position.\r\n\t * @param {number} scrollY\r\n\t */\r\n\tscrollToY(scrollY) {\r\n\t\tthis.element.scrollTo(0, scrollY)\r\n\t}\r\n\r\n\t/**\r\n\t * Returns \"scrollable container\" width,\r\n\t * i.e. the available width for its content.\r\n\t * @return {number}\r\n\t */\r\n\tgetWidth() {\r\n\t\treturn this.element.offsetWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the \"scrollable container\" itself.\r\n\t * Not to be confused with the height of \"scrollable container\"'s content.\r\n\t * @return {number}\r\n\t */\r\n\tgetHeight() {\r\n\t\t// if (!this.element && !precise) {\r\n\t\t// \treturn getScreenHeight()\r\n\t\t// }\r\n\t\treturn this.element.offsetHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the content in a scrollable container.\r\n\t * For example, a scrollable container can have a height of 500px,\r\n\t * but the content in it could have a height of 5000px,\r\n\t * in which case a vertical scrollbar is rendered, and only\r\n\t * one-tenth of all the items are shown at any given moment.\r\n\t * This function is currently only used when using the\r\n\t * `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.\r\n\t * @return {number}\r\n\t */\r\n\tgetContentHeight() {\r\n\t\treturn this.element.scrollHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a \"top offset\" of an element\r\n\t * relative to the \"scrollable container\"'s top edge.\r\n\t * @param {Element} element\r\n\t * @return {number}\r\n\t */\r\n\tgetTopOffset(element) {\r\n\t\tconst scrollableContainerTop = this.element.getBoundingClientRect().top\r\n\t\tconst scrollableContainerBorderTopWidth = this.element.clientTop\r\n\t\tconst top = element.getBoundingClientRect().top\r\n\t\treturn (top - scrollableContainerTop) + this.getScrollY() - scrollableContainerBorderTopWidth\r\n\t}\r\n\r\n\t// isVisible() {\r\n\t// \tconst { top, bottom } = this.element.getBoundingClientRect()\r\n\t// \treturn bottom > 0 && top < getScreenHeight()\r\n\t// }\r\n\r\n\t/**\r\n\t * Adds a \"scroll\" event listener to the \"scrollable container\".\r\n\t * @param {onScroll} Should be called whenever the scroll position inside the \"scrollable container\" (potentially) changes.\r\n\t * @return {function} Returns a function that stops listening.\r\n\t */\r\n\taddScrollListener(onScroll) {\r\n\t\tthis.element.addEventListener('scroll', onScroll)\r\n\t\treturn () => this.element.removeEventListener('scroll', onScroll)\r\n\t}\r\n\r\n\t/**\r\n\t * Adds a \"resize\" event listener to the \"scrollable container\".\r\n\t * @param {onResize} Should be called whenever the \"scrollable container\"'s width or height (potentially) changes.\r\n\t * @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.\r\n\t * @return {function} Returns a function that stops listening.\r\n\t */\r\n\tonResize(onResize, { container }) {\r\n\t\t// Could somehow track DOM Element size.\r\n\t\t// For now, `scrollableContainer` is supposed to have constant width and height.\r\n\t\t// (unless window is resized).\r\n\t\t// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver\r\n\t\t// https://web.dev/resize-observer/\r\n\t\tlet unobserve\r\n\t\tif (typeof ResizeObserver !== 'undefined') {\r\n\t\t\tconst resizeObserver = new ResizeObserver((entries) => {\r\n\t\t\t\t// \"one entry per observed element\".\r\n\t\t\t\t// https://web.dev/resize-observer/\r\n\t\t\t\t// `entry.target === this.element`.\r\n\t\t\t\tconst entry = entries[0]\r\n\t\t\t\t// // If `entry.contentBoxSize` property is supported by the web browser.\r\n\t\t\t\t// if (entry.contentBoxSize) {\r\n\t\t\t\t// \t// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentBoxSize\r\n\t\t\t\t// \tconst width = entry.contentBoxSize.inlineSize\r\n\t\t\t\t// \tconst height = entry.contentBoxSize.blockSize\r\n\t\t\t\t// }\r\n\t\t\t\tonResize()\r\n\t\t\t})\r\n\t\t\tresizeObserver.observe(this.element)\r\n\t\t\tunobserve = () => resizeObserver.unobserve(this.element)\r\n\t\t}\r\n\t\t// I guess, if window is resized, `onResize()` will be triggered twice:\r\n\t\t// once for window resize, and once for the scrollable container resize.\r\n\t\t// But `onResize()` also has an internal check: if the container size\r\n\t\t// hasn't changed since the previous time `onResize()` has been called,\r\n\t\t// then `onResize()` doesn't do anything, so, I guess, there shouldn't be\r\n\t\t// any \"performance implications\" of running the listener twice in such case.\r\n\t\tconst unlistenGlobalResize = addGlobalResizeListener(onResize, { container })\r\n\t\treturn () => {\r\n\t\t\tif (unobserve) {\r\n\t\t\t\tunobserve()\r\n\t\t\t}\r\n\t\t\tunlistenGlobalResize()\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport class ScrollableWindowContainer extends ScrollableContainer {\r\n\tconstructor() {\r\n\t\tsuper(window)\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the current scroll position.\r\n\t * @return {number}\r\n\t */\r\n\tgetScrollY() {\r\n\t\t// `window.scrollY` is not supported by Internet Explorer.\r\n\t\treturn window.pageYOffset\r\n\t}\r\n\r\n\t/**\r\n\t * Returns \"scrollable container\" width,\r\n\t * i.e. the available width for its content.\r\n\t * @return {number}\r\n\t */\r\n\tgetWidth() {\r\n\t\t// https://javascript.info/size-and-scroll-window\r\n\t\t// `<!DOCTYPE html>` may be required in order for this to work correctly.\r\n\t\t// Includes scrollbar (if any).\r\n\t\t// Correctly reflects page zoom in iOS Safari.\r\n\t\t// (scales screen width accordingly).\r\n\t\t// But, includes scrollbar (if any).\r\n\t\treturn window.innerWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the \"scrollable container\" itself.\r\n\t * Not to be confused with the height of \"scrollable container\"'s content.\r\n\t * @return {number}\r\n\t */\r\n\tgetHeight() {\r\n\t\t// https://javascript.info/size-and-scroll-window\r\n\t\t// `<!DOCTYPE html>` is required in order for this to work correctly.\r\n\t\t// Without it, the returned height would be the height of the entire document.\r\n\t\t// Includes scrollbar (if any).\r\n\t\t// Supports iOS Safari's dynamically shown/hidden\r\n\t\t// top URL bar and bottom actions bar.\r\n\t\t// https://codesandbox.io/s/elegant-fog-iddrh\r\n\t\t// Tested in IE 11.\r\n\t\t// It also correctly reflects page zoom in iOS Safari.\r\n\t\t// (scales screen height accordingly).\r\n\t\t// But, includes scrollbar (if any).\r\n\t\treturn window.innerHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the content in a scrollable container.\r\n\t * For example, a scrollable container can have a height of 500px,\r\n\t * but the content in it could have a height of 5000px,\r\n\t * in which case a vertical scrollbar is rendered, and only\r\n\t * one-tenth of all the items are shown at any given moment.\r\n\t * This function is currently only used when using the\r\n\t * `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.\r\n\t * @return {number}\r\n\t */\r\n\tgetContentHeight() {\r\n\t\treturn document.documentElement.scrollHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a \"top offset\" of an element\r\n\t * relative to the \"scrollable container\"'s top edge.\r\n\t * @param {Element} element\r\n\t * @return {number}\r\n\t */\r\n\tgetTopOffset(element) {\r\n\t\tconst borderTopWidth = document.clientTop || document.body.clientTop || 0\r\n\t\treturn element.getBoundingClientRect().top + this.getScrollY() - borderTopWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Adds a \"resize\" event listener to the \"scrollable container\".\r\n\t * @param {onScroll} Should be called whenever the \"scrollable container\"'s width or height (potentially) changes.\r\n\t * @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.\r\n\t * @return {function} Returns a function that stops listening.\r\n\t */\r\n\tonResize(onResize, { container }) {\r\n\t\treturn addGlobalResizeListener(onResize, { container })\r\n\t}\r\n\r\n\t// isVisible() {\r\n\t// \treturn true\r\n\t// }\r\n}\r\n\r\n/**\r\n * Adds a \"resize\" event listener to the `window`.\r\n * @param {onResize} Should be called whenever the \"container\"'s width or height (potentially) changes.\r\n * @param {Element} options.container — The \"container\".\r\n * @return {function} Returns a function that stops listening.\r\n */\r\nfunction addGlobalResizeListener(onResize, { container }) {\r\n\tconst onResizeListener = () => {\r\n\t\t// By default, `VirtualScroller` always performs a re-layout\r\n\t\t// on window `resize` event. But browsers (Chrome, Firefox)\r\n\t\t// [trigger](https://developer.mozilla.org/en-US/docs/Web/API/Window/fullScreen#Notes)\r\n\t\t// window `resize` event also when a user switches into fullscreen mode:\r\n\t\t// for example, when a user is watching a video and double-clicks on it\r\n\t\t// to maximize it. And also when the user goes out of the fullscreen mode.\r\n\t\t// Each such fullscreen mode entering/exiting will trigger window `resize`\r\n\t\t// event that will it turn trigger a re-layout of `VirtualScroller`,\r\n\t\t// resulting in bad user experience. To prevent that, such cases are filtered out.\r\n\t\t// Some other workaround:\r\n\t\t// https://stackoverflow.com/questions/23770449/embedded-youtube-video-fullscreen-or-causing-resize\r\n\t\tif (document.fullscreenElement) {\r\n\t\t\t// If the fullscreened element doesn't contain the list\r\n\t\t\t// (and is not the list itself), then the layout hasn't been affected,\r\n\t\t\t// so don't perform a re-layout.\r\n\t\t\t//\r\n\t\t\t// For example, suppose there's a list of items, and some item contains a video.\r\n\t\t\t// If, upon clicking such video, it plays inline, and the user enters\r\n\t\t\t// fullscreen mode while playing such inline video, then the layout won't be\r\n\t\t\t// affected, and so such `resize` event should be ignored: when\r\n\t\t\t// `document.fullscreenElement` is in a separate \"branch\" relative to the\r\n\t\t\t// `container`.\r\n\t\t\t//\r\n\t\t\t// Another scenario: suppose that upon click, the video doesn't play inline,\r\n\t\t\t// but instead a \"Slideshow\" component is open, with the video shown at the\r\n\t\t\t// center of the screen in an overlay. If then the user enters fullscreen mode,\r\n\t\t\t// the layout wouldn't be affected too, so such `resize` event should also be\r\n\t\t\t// ignored: when `document.fullscreenElement` is inside the `container`.\r\n\t\t\t//\r\n\t\t\tif (document.fullscreenElement.contains(container)) {\r\n\t\t\t\t// The element is either the `container`'s ancestor,\r\n\t\t\t\t// Or is the `container` itself.\r\n\t\t\t\t// (`a.contains(b)` includes the `a === b` case).\r\n\t\t\t\t// So the `resize` event will affect the `container`'s dimensions.\r\n\t\t\t} else {\r\n\t\t\t\t// The element is either inside the `container`,\r\n\t\t\t\t// Or is in a separate tree.\r\n\t\t\t\t// So the `resize` event won't affect the `container`'s dimensions.\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t\tonResize()\r\n\t}\r\n\twindow.addEventListener('resize', onResizeListener)\r\n\treturn () => window.removeEventListener('resize', onResizeListener)\r\n}"],"file":"ScrollableContainer.js"}
|
|
1
|
+
{"version":3,"sources":["../../source/DOM/ScrollableContainer.js"],"names":["ScrollableContainer","element","getItemsContainerElement","scrollTop","scrollY","scrollTo","offsetWidth","offsetHeight","scrollableContainerTop","getBoundingClientRect","top","scrollableContainerBorderTopWidth","clientTop","itemsContainerTop","getScrollY","onScroll","addEventListener","removeEventListener","onResize","unobserve","ResizeObserver","resizeObserver","entries","entry","observe","unlistenGlobalResize","addGlobalResizeListener","itemsContainerElement","ScrollableWindowContainer","window","pageYOffset","innerWidth","innerHeight","borderTopWidth","document","body","onResizeListener","fullscreenElement","contains"],"mappings":"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","sourcesContent":["export default class ScrollableContainer {\r\n\t/**\r\n\t * Constructs a new \"scrollable container\" from an element.\r\n\t * @param {Element} scrollableContainer\r\n\t * @param {func} getItemsContainerElement — Returns items \"container\" element.\r\n\t */\r\n\tconstructor(element, getItemsContainerElement) {\r\n\t\tthis.element = element\r\n\t\tthis.getItemsContainerElement = getItemsContainerElement\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the current scroll position.\r\n\t * @return {number}\r\n\t */\r\n\tgetScrollY() {\r\n\t\treturn this.element.scrollTop\r\n\t}\r\n\r\n\t/**\r\n\t * Scrolls to a specific position.\r\n\t * @param {number} scrollY\r\n\t */\r\n\tscrollToY(scrollY) {\r\n\t\t// IE 11 doesn't seem to have a `.scrollTo()` method.\r\n\t\t// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/10\r\n\t\t// https://stackoverflow.com/questions/39908825/window-scrollto-is-not-working-in-internet-explorer-11\r\n\t\tif (this.element.scrollTo) {\r\n\t\t\tthis.element.scrollTo(0, scrollY)\r\n\t\t} else {\r\n\t\t\tthis.element.scrollTop = scrollY\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Returns \"scrollable container\" width,\r\n\t * i.e. the available width for its content.\r\n\t * @return {number}\r\n\t */\r\n\tgetWidth() {\r\n\t\treturn this.element.offsetWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the \"scrollable container\" itself.\r\n\t * Not to be confused with the height of \"scrollable container\"'s content.\r\n\t * @return {number}\r\n\t */\r\n\tgetHeight() {\r\n\t\t// if (!this.element && !precise) {\r\n\t\t// \treturn getScreenHeight()\r\n\t\t// }\r\n\t\treturn this.element.offsetHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a \"top offset\" of an items container element\r\n\t * relative to the \"scrollable container\"'s top edge.\r\n\t * @return {number}\r\n\t */\r\n\tgetItemsContainerTopOffset() {\r\n\t\tconst scrollableContainerTop = this.element.getBoundingClientRect().top\r\n\t\tconst scrollableContainerBorderTopWidth = this.element.clientTop\r\n\t\tconst itemsContainerTop = this.getItemsContainerElement().getBoundingClientRect().top\r\n\t\treturn (itemsContainerTop - scrollableContainerTop) + this.getScrollY() - scrollableContainerBorderTopWidth\r\n\t}\r\n\r\n\t// isVisible() {\r\n\t// \tconst { top, bottom } = this.element.getBoundingClientRect()\r\n\t// \treturn bottom > 0 && top < getScreenHeight()\r\n\t// }\r\n\r\n\t/**\r\n\t * Adds a \"scroll\" event listener to the \"scrollable container\".\r\n\t * @param {onScroll} Should be called whenever the scroll position inside the \"scrollable container\" (potentially) changes.\r\n\t * @return {function} Returns a function that stops listening.\r\n\t */\r\n\tonScroll(onScroll) {\r\n\t\tthis.element.addEventListener('scroll', onScroll)\r\n\t\treturn () => this.element.removeEventListener('scroll', onScroll)\r\n\t}\r\n\r\n\t/**\r\n\t * Adds a \"resize\" event listener to the \"scrollable container\".\r\n\t * @param {onResize} Should be called whenever the \"scrollable container\"'s width or height (potentially) changes.\r\n * @return {function} Returns a function that stops listening.\r\n\t */\r\n\tonResize(onResize) {\r\n\t\t// Watches \"scrollable container\"'s dimensions via a `ResizeObserver`.\r\n\t\t// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver\r\n\t\t// https://web.dev/resize-observer/\r\n\t\tlet unobserve\r\n\t\tif (typeof ResizeObserver !== 'undefined') {\r\n\t\t\tconst resizeObserver = new ResizeObserver((entries) => {\r\n\t\t\t\t// \"one entry per observed element\".\r\n\t\t\t\t// https://web.dev/resize-observer/\r\n\t\t\t\t// `entry.target === this.element`.\r\n\t\t\t\tconst entry = entries[0]\r\n\t\t\t\t// // If `entry.contentBoxSize` property is supported by the web browser.\r\n\t\t\t\t// if (entry.contentBoxSize) {\r\n\t\t\t\t// \t// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentBoxSize\r\n\t\t\t\t// \tconst width = entry.contentBoxSize.inlineSize\r\n\t\t\t\t// \tconst height = entry.contentBoxSize.blockSize\r\n\t\t\t\t// }\r\n\t\t\t\tonResize()\r\n\t\t\t})\r\n\t\t\tresizeObserver.observe(this.element)\r\n\t\t\tunobserve = () => resizeObserver.unobserve(this.element)\r\n\t\t}\r\n\t\t// I guess, if window is resized, `onResize()` will be triggered twice:\r\n\t\t// once for window resize, and once for the scrollable container resize.\r\n\t\t// But `onResize()` also has an internal check: if the container size\r\n\t\t// hasn't changed since the previous time `onResize()` has been called,\r\n\t\t// then `onResize()` doesn't do anything, so, I guess, there shouldn't be\r\n\t\t// any \"performance implications\" of running the listener twice in such case.\r\n\t\tconst unlistenGlobalResize = addGlobalResizeListener(onResize, {\r\n\t\t\titemsContainerElement: this.getItemsContainerElement()\r\n\t\t})\r\n\t\treturn () => {\r\n\t\t\tif (unobserve) {\r\n\t\t\t\tunobserve()\r\n\t\t\t}\r\n\t\t\tunlistenGlobalResize()\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport class ScrollableWindowContainer extends ScrollableContainer {\r\n\t/**\r\n\t * Constructs a new window \"scrollable container\".\r\n\t * @param {func} getItemsContainerElement — Returns items \"container\" element.\r\n\t */\r\n\tconstructor(getItemsContainerElement) {\r\n\t\tsuper(window, getItemsContainerElement)\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the current scroll position.\r\n\t * @return {number}\r\n\t */\r\n\tgetScrollY() {\r\n\t\t// `window.scrollY` is not supported by Internet Explorer.\r\n\t\treturn window.pageYOffset\r\n\t}\r\n\r\n\t/**\r\n\t * Returns \"scrollable container\" width,\r\n\t * i.e. the available width for its content.\r\n\t * @return {number}\r\n\t */\r\n\tgetWidth() {\r\n\t\t// https://javascript.info/size-and-scroll-window\r\n\t\t// `<!DOCTYPE html>` may be required in order for this to work correctly.\r\n\t\t// Includes scrollbar (if any).\r\n\t\t// Correctly reflects page zoom in iOS Safari.\r\n\t\t// (scales screen width accordingly).\r\n\t\t// But, includes scrollbar (if any).\r\n\t\treturn window.innerWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the \"scrollable container\" itself.\r\n\t * Not to be confused with the height of \"scrollable container\"'s content.\r\n\t * @return {number}\r\n\t */\r\n\tgetHeight() {\r\n\t\t// https://javascript.info/size-and-scroll-window\r\n\t\t// `<!DOCTYPE html>` is required in order for this to work correctly.\r\n\t\t// Without it, the returned height would be the height of the entire document.\r\n\t\t// Includes scrollbar (if any).\r\n\t\t// Supports iOS Safari's dynamically shown/hidden\r\n\t\t// top URL bar and bottom actions bar.\r\n\t\t// https://codesandbox.io/s/elegant-fog-iddrh\r\n\t\t// Tested in IE 11.\r\n\t\t// It also correctly reflects page zoom in iOS Safari.\r\n\t\t// (scales screen height accordingly).\r\n\t\t// But, includes scrollbar (if any).\r\n\t\treturn window.innerHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a \"top offset\" of an items container element\r\n\t * relative to the \"scrollable container\"'s top edge.\r\n\t * @return {number}\r\n\t */\r\n\tgetItemsContainerTopOffset() {\r\n\t\tconst borderTopWidth = document.clientTop || document.body.clientTop || 0\r\n\t\treturn this.getItemsContainerElement().getBoundingClientRect().top + this.getScrollY() - borderTopWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Adds a \"resize\" event listener to the \"scrollable container\".\r\n\t * @param {onScroll} Should be called whenever the \"scrollable container\"'s width or height (potentially) changes.\r\n\t * @return {function} Returns a function that stops listening.\r\n\t */\r\n\tonResize(onResize) {\r\n\t\treturn addGlobalResizeListener(onResize, {\r\n\t\t\titemsContainerElement: this.getItemsContainerElement()\r\n\t\t})\r\n\t}\r\n\r\n\t// isVisible() {\r\n\t// \treturn true\r\n\t// }\r\n}\r\n\r\n/**\r\n * Adds a \"resize\" event listener to the `window`.\r\n * @param {onResize} Should be called whenever the \"scrollable container\"'s width or height (potentially) changes.\r\n * @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.\r\n * @return {function} Returns a function that stops listening.\r\n */\r\nfunction addGlobalResizeListener(onResize, { itemsContainerElement }) {\r\n\tconst onResizeListener = () => {\r\n\t\t// By default, `VirtualScroller` always performs a re-layout\r\n\t\t// on window `resize` event. But browsers (Chrome, Firefox)\r\n\t\t// [trigger](https://developer.mozilla.org/en-US/docs/Web/API/Window/fullScreen#Notes)\r\n\t\t// window `resize` event also when a user switches into fullscreen mode:\r\n\t\t// for example, when a user is watching a video and double-clicks on it\r\n\t\t// to maximize it. And also when the user goes out of the fullscreen mode.\r\n\t\t// Each such fullscreen mode entering/exiting will trigger window `resize`\r\n\t\t// event that will it turn trigger a re-layout of `VirtualScroller`,\r\n\t\t// resulting in bad user experience. To prevent that, such cases are filtered out.\r\n\t\t// Some other workaround:\r\n\t\t// https://stackoverflow.com/questions/23770449/embedded-youtube-video-fullscreen-or-causing-resize\r\n\t\tif (document.fullscreenElement) {\r\n\t\t\t// If the fullscreened element doesn't contain the list\r\n\t\t\t// (and is not the list itself), then the layout hasn't been affected,\r\n\t\t\t// so don't perform a re-layout.\r\n\t\t\t//\r\n\t\t\t// For example, suppose there's a list of items, and some item contains a video.\r\n\t\t\t// If, upon clicking such video, it plays inline, and the user enters\r\n\t\t\t// fullscreen mode while playing such inline video, then the layout won't be\r\n\t\t\t// affected, and so such `resize` event should be ignored: when\r\n\t\t\t// `document.fullscreenElement` is in a separate \"branch\" relative to the\r\n\t\t\t// `container`.\r\n\t\t\t//\r\n\t\t\t// Another scenario: suppose that upon click, the video doesn't play inline,\r\n\t\t\t// but instead a \"Slideshow\" component is open, with the video shown at the\r\n\t\t\t// center of the screen in an overlay. If then the user enters fullscreen mode,\r\n\t\t\t// the layout wouldn't be affected too, so such `resize` event should also be\r\n\t\t\t// ignored: when `document.fullscreenElement` is inside the `container`.\r\n\t\t\t//\r\n\t\t\tif (document.fullscreenElement.contains(itemsContainerElement)) {\r\n\t\t\t\t// The element is either the `container`'s ancestor,\r\n\t\t\t\t// Or is the `container` itself.\r\n\t\t\t\t// (`a.contains(b)` includes the `a === b` case).\r\n\t\t\t\t// So the `resize` event will affect the `container`'s dimensions.\r\n\t\t\t} else {\r\n\t\t\t\t// The element is either inside the `container`,\r\n\t\t\t\t// Or is in a separate tree.\r\n\t\t\t\t// So the `resize` event won't affect the `container`'s dimensions.\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t\tonResize()\r\n\t}\r\n\twindow.addEventListener('resize', onResizeListener)\r\n\treturn () => window.removeEventListener('resize', onResizeListener)\r\n}"],"file":"ScrollableContainer.js"}
|