virtual-scroller 1.7.9 → 1.8.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/CHANGELOG.md +14 -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 +48 -80
- 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 +13 -20
- 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 +47 -79
- 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 +8 -19
- 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 +31 -55
- package/source/DOM/VirtualScroller.js +6 -7
- package/source/ItemHeights.js +10 -15
- 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 +1240 -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.
|
|
@@ -82,35 +86,18 @@ function () {
|
|
|
82
86
|
return this.element.offsetHeight;
|
|
83
87
|
}
|
|
84
88
|
/**
|
|
85
|
-
* Returns
|
|
86
|
-
* For example, a scrollable container can have a height of 500px,
|
|
87
|
-
* but the content in it could have a height of 5000px,
|
|
88
|
-
* in which case a vertical scrollbar is rendered, and only
|
|
89
|
-
* one-tenth of all the items are shown at any given moment.
|
|
90
|
-
* This function is currently only used when using the
|
|
91
|
-
* `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.
|
|
92
|
-
* @return {number}
|
|
93
|
-
*/
|
|
94
|
-
|
|
95
|
-
}, {
|
|
96
|
-
key: "getContentHeight",
|
|
97
|
-
value: function getContentHeight() {
|
|
98
|
-
return this.element.scrollHeight;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Returns a "top offset" of an element
|
|
89
|
+
* Returns a "top offset" of an items container element
|
|
102
90
|
* relative to the "scrollable container"'s top edge.
|
|
103
|
-
* @param {Element} element
|
|
104
91
|
* @return {number}
|
|
105
92
|
*/
|
|
106
93
|
|
|
107
94
|
}, {
|
|
108
|
-
key: "
|
|
109
|
-
value: function
|
|
95
|
+
key: "getItemsContainerTopOffset",
|
|
96
|
+
value: function getItemsContainerTopOffset() {
|
|
110
97
|
var scrollableContainerTop = this.element.getBoundingClientRect().top;
|
|
111
98
|
var scrollableContainerBorderTopWidth = this.element.clientTop;
|
|
112
|
-
var
|
|
113
|
-
return
|
|
99
|
+
var itemsContainerTop = this.getItemsContainerElement().getBoundingClientRect().top;
|
|
100
|
+
return itemsContainerTop - scrollableContainerTop + this.getScrollY() - scrollableContainerBorderTopWidth;
|
|
114
101
|
} // isVisible() {
|
|
115
102
|
// const { top, bottom } = this.element.getBoundingClientRect()
|
|
116
103
|
// return bottom > 0 && top < getScreenHeight()
|
|
@@ -123,31 +110,27 @@ function () {
|
|
|
123
110
|
*/
|
|
124
111
|
|
|
125
112
|
}, {
|
|
126
|
-
key: "
|
|
127
|
-
value: function
|
|
113
|
+
key: "onScroll",
|
|
114
|
+
value: function onScroll(_onScroll) {
|
|
128
115
|
var _this = this;
|
|
129
116
|
|
|
130
|
-
this.element.addEventListener('scroll',
|
|
117
|
+
this.element.addEventListener('scroll', _onScroll);
|
|
131
118
|
return function () {
|
|
132
|
-
return _this.element.removeEventListener('scroll',
|
|
119
|
+
return _this.element.removeEventListener('scroll', _onScroll);
|
|
133
120
|
};
|
|
134
121
|
}
|
|
135
122
|
/**
|
|
136
123
|
* Adds a "resize" event listener to the "scrollable container".
|
|
137
124
|
* @param {onResize} Should be called whenever the "scrollable container"'s width or height (potentially) changes.
|
|
138
|
-
|
|
139
|
-
* @return {function} Returns a function that stops listening.
|
|
125
|
+
* @return {function} Returns a function that stops listening.
|
|
140
126
|
*/
|
|
141
127
|
|
|
142
128
|
}, {
|
|
143
129
|
key: "onResize",
|
|
144
|
-
value: function onResize(_onResize
|
|
130
|
+
value: function onResize(_onResize) {
|
|
145
131
|
var _this2 = this;
|
|
146
132
|
|
|
147
|
-
|
|
148
|
-
// Could somehow track DOM Element size.
|
|
149
|
-
// For now, `scrollableContainer` is supposed to have constant width and height.
|
|
150
|
-
// (unless window is resized).
|
|
133
|
+
// Watches "scrollable container"'s dimensions via a `ResizeObserver`.
|
|
151
134
|
// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
|
|
152
135
|
// https://web.dev/resize-observer/
|
|
153
136
|
var unobserve;
|
|
@@ -180,7 +163,7 @@ function () {
|
|
|
180
163
|
|
|
181
164
|
|
|
182
165
|
var unlistenGlobalResize = addGlobalResizeListener(_onResize, {
|
|
183
|
-
|
|
166
|
+
itemsContainerElement: this.getItemsContainerElement()
|
|
184
167
|
});
|
|
185
168
|
return function () {
|
|
186
169
|
if (unobserve) {
|
|
@@ -196,15 +179,19 @@ function () {
|
|
|
196
179
|
}();
|
|
197
180
|
|
|
198
181
|
export { ScrollableContainer as default };
|
|
199
|
-
export var ScrollableWindowContainer =
|
|
200
|
-
/*#__PURE__*/
|
|
201
|
-
function (_ScrollableContainer) {
|
|
182
|
+
export var ScrollableWindowContainer = /*#__PURE__*/function (_ScrollableContainer) {
|
|
202
183
|
_inherits(ScrollableWindowContainer, _ScrollableContainer);
|
|
203
184
|
|
|
204
|
-
|
|
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) {
|
|
205
192
|
_classCallCheck(this, ScrollableWindowContainer);
|
|
206
193
|
|
|
207
|
-
return
|
|
194
|
+
return _super.call(this, window, getItemsContainerElement);
|
|
208
195
|
}
|
|
209
196
|
/**
|
|
210
197
|
* Returns the current scroll position.
|
|
@@ -258,47 +245,28 @@ function (_ScrollableContainer) {
|
|
|
258
245
|
return window.innerHeight;
|
|
259
246
|
}
|
|
260
247
|
/**
|
|
261
|
-
* Returns
|
|
262
|
-
* For example, a scrollable container can have a height of 500px,
|
|
263
|
-
* but the content in it could have a height of 5000px,
|
|
264
|
-
* in which case a vertical scrollbar is rendered, and only
|
|
265
|
-
* one-tenth of all the items are shown at any given moment.
|
|
266
|
-
* This function is currently only used when using the
|
|
267
|
-
* `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.
|
|
268
|
-
* @return {number}
|
|
269
|
-
*/
|
|
270
|
-
|
|
271
|
-
}, {
|
|
272
|
-
key: "getContentHeight",
|
|
273
|
-
value: function getContentHeight() {
|
|
274
|
-
return document.documentElement.scrollHeight;
|
|
275
|
-
}
|
|
276
|
-
/**
|
|
277
|
-
* Returns a "top offset" of an element
|
|
248
|
+
* Returns a "top offset" of an items container element
|
|
278
249
|
* relative to the "scrollable container"'s top edge.
|
|
279
|
-
* @param {Element} element
|
|
280
250
|
* @return {number}
|
|
281
251
|
*/
|
|
282
252
|
|
|
283
253
|
}, {
|
|
284
|
-
key: "
|
|
285
|
-
value: function
|
|
254
|
+
key: "getItemsContainerTopOffset",
|
|
255
|
+
value: function getItemsContainerTopOffset() {
|
|
286
256
|
var borderTopWidth = document.clientTop || document.body.clientTop || 0;
|
|
287
|
-
return
|
|
257
|
+
return this.getItemsContainerElement().getBoundingClientRect().top + this.getScrollY() - borderTopWidth;
|
|
288
258
|
}
|
|
289
259
|
/**
|
|
290
260
|
* Adds a "resize" event listener to the "scrollable container".
|
|
291
261
|
* @param {onScroll} Should be called whenever the "scrollable container"'s width or height (potentially) changes.
|
|
292
|
-
* @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.
|
|
293
262
|
* @return {function} Returns a function that stops listening.
|
|
294
263
|
*/
|
|
295
264
|
|
|
296
265
|
}, {
|
|
297
266
|
key: "onResize",
|
|
298
|
-
value: function onResize(_onResize2
|
|
299
|
-
var container = _ref2.container;
|
|
267
|
+
value: function onResize(_onResize2) {
|
|
300
268
|
return addGlobalResizeListener(_onResize2, {
|
|
301
|
-
|
|
269
|
+
itemsContainerElement: this.getItemsContainerElement()
|
|
302
270
|
});
|
|
303
271
|
} // isVisible() {
|
|
304
272
|
// return true
|
|
@@ -310,13 +278,13 @@ function (_ScrollableContainer) {
|
|
|
310
278
|
}(ScrollableContainer);
|
|
311
279
|
/**
|
|
312
280
|
* Adds a "resize" event listener to the `window`.
|
|
313
|
-
* @param {onResize} Should be called whenever the "container"'s width or height (potentially) changes.
|
|
314
|
-
* @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.
|
|
315
283
|
* @return {function} Returns a function that stops listening.
|
|
316
284
|
*/
|
|
317
285
|
|
|
318
|
-
function addGlobalResizeListener(onResize,
|
|
319
|
-
var
|
|
286
|
+
function addGlobalResizeListener(onResize, _ref) {
|
|
287
|
+
var itemsContainerElement = _ref.itemsContainerElement;
|
|
320
288
|
|
|
321
289
|
var onResizeListener = function onResizeListener() {
|
|
322
290
|
// By default, `VirtualScroller` always performs a re-layout
|
|
@@ -348,7 +316,7 @@ function addGlobalResizeListener(onResize, _ref3) {
|
|
|
348
316
|
// the layout wouldn't be affected too, so such `resize` event should also be
|
|
349
317
|
// ignored: when `document.fullscreenElement` is inside the `container`.
|
|
350
318
|
//
|
|
351
|
-
if (document.fullscreenElement.contains(
|
|
319
|
+
if (document.fullscreenElement.contains(itemsContainerElement)) {// The element is either the `container`'s ancestor,
|
|
352
320
|
// Or is the `container` itself.
|
|
353
321
|
// (`a.contains(b)` includes the `a === b` case).
|
|
354
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\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 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"}
|