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
|
@@ -7,59 +7,80 @@ exports["default"] = void 0;
|
|
|
7
7
|
|
|
8
8
|
var _requestAnimationFrameTimeout = require("request-animation-frame-timeout");
|
|
9
9
|
|
|
10
|
-
var _Layout = require("../Layout");
|
|
11
|
-
|
|
12
10
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
13
11
|
|
|
14
12
|
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); } }
|
|
15
13
|
|
|
16
14
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
|
|
17
15
|
|
|
18
|
-
//
|
|
16
|
+
// Refreshing two times every seconds seems reasonable.
|
|
17
|
+
var WATCH_LIST_TOP_OFFSET_INTERVAL = 500; // Refreshing for 3 seconds after the initial page load seems reasonable.
|
|
18
|
+
|
|
19
|
+
var WATCH_LIST_TOP_OFFSET_MAX_DURATION = 3000; // `VirtualScroller` calls `this.layout.layOut()` on mount,
|
|
19
20
|
// but if the page styles are applied after `VirtualScroller` mounts
|
|
20
21
|
// (for example, if styles are applied via javascript, like Webpack does)
|
|
21
|
-
// then the list might not render correctly and will only show the first item.
|
|
22
|
-
// The reason
|
|
23
|
-
//
|
|
22
|
+
// then the list might not render correctly and it will only show the first item.
|
|
23
|
+
// The reason is that in that case calling `.getListTopOffset()` on mount
|
|
24
|
+
// returns "incorrect" `top` position because the styles haven't been applied yet.
|
|
25
|
+
//
|
|
24
26
|
// For example, consider a page:
|
|
25
27
|
// <div class="page">
|
|
26
28
|
// <nav class="sidebar">...</nav>
|
|
27
29
|
// <main>...</main>
|
|
28
30
|
// </div>
|
|
31
|
+
//
|
|
29
32
|
// The sidebar is styled as `position: fixed`, but until
|
|
30
33
|
// the page styles have been applied it's gonna be a regular `<div/>`
|
|
31
34
|
// meaning that `<main/>` will be rendered below the sidebar
|
|
32
35
|
// and will appear offscreen and so it will only render the first item.
|
|
36
|
+
//
|
|
33
37
|
// Then, the page styles are loaded and applied and the sidebar
|
|
34
38
|
// is now `position: fixed` so `<main/>` is now rendered at the top of the page
|
|
35
39
|
// but `VirtualScroller`'s `.render()` has already been called
|
|
36
40
|
// and it won't re-render until the user scrolls or the window is resized.
|
|
37
|
-
//
|
|
41
|
+
//
|
|
42
|
+
// This type of a bug doesn't seem to occur in production, but it can appear
|
|
38
43
|
// in development mode when using Webpack. The workaround `VirtualScroller`
|
|
39
|
-
// implements for such cases is calling `.
|
|
44
|
+
// implements for such cases is calling `.getListTopOffset()`
|
|
40
45
|
// on the list container DOM element periodically (every second) to check
|
|
41
46
|
// if the `top` coordinate has changed as a result of CSS being applied:
|
|
42
47
|
// if it has then it recalculates the shown item indexes.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
//
|
|
49
|
+
// Maybe this bug could occur in production when using Webpack chunks.
|
|
50
|
+
// That depends on how a style of a chunk is added to the page:
|
|
51
|
+
// if it's added via `javascript` after the page has been rendered
|
|
52
|
+
// then this workaround will also work for that case.
|
|
53
|
+
//
|
|
54
|
+
// Another example would be a page having a really tall expanded "accordion"
|
|
55
|
+
// section, below which a `VirtualScroller` list resides. If the user un-expands
|
|
56
|
+
// such expanded "accordion" section, the list would become visible but
|
|
57
|
+
// it wouldn't get re-rendered because no `scroll` event has occured,
|
|
58
|
+
// and the list only re-renders automatically on `scroll` events.
|
|
59
|
+
// To work around such cases, call `virtualScroller.updateLayout()` method manually.
|
|
60
|
+
// The workaround below could be extended to refresh the list's top coordinate
|
|
61
|
+
// indefinitely and at higher intervals, but why waste CPU time on that.
|
|
62
|
+
// There doesn't seem to be any DOM API for tracking an element's top position.
|
|
63
|
+
// There is `IntersectionObserver` API but it doesn't exactly do that.
|
|
64
|
+
//
|
|
65
|
+
|
|
66
|
+
var ListTopOffsetWatcher = /*#__PURE__*/function () {
|
|
67
|
+
function ListTopOffsetWatcher(_ref) {
|
|
68
|
+
var getListTopOffset = _ref.getListTopOffset,
|
|
69
|
+
onListTopOffsetChange = _ref.onListTopOffsetChange;
|
|
70
|
+
|
|
71
|
+
_classCallCheck(this, ListTopOffsetWatcher);
|
|
72
|
+
|
|
73
|
+
this.getListTopOffset = getListTopOffset;
|
|
74
|
+
this.onListTopOffsetChange = onListTopOffsetChange;
|
|
54
75
|
}
|
|
55
76
|
|
|
56
|
-
_createClass(
|
|
57
|
-
key: "
|
|
58
|
-
value: function
|
|
77
|
+
_createClass(ListTopOffsetWatcher, [{
|
|
78
|
+
key: "onListTopOffset",
|
|
79
|
+
value: function onListTopOffset(listTopOffset) {
|
|
59
80
|
if (this.listTopOffsetInsideScrollableContainer === undefined) {
|
|
60
81
|
// Start periodical checks of the list's top offset
|
|
61
82
|
// in order to perform a re-layout in case it changes.
|
|
62
|
-
// See the comments in `
|
|
83
|
+
// See the comments in `ListTopOffsetWatcher.js` file
|
|
63
84
|
// on why can the list's top offset change, and in which circumstances.
|
|
64
85
|
this.start();
|
|
65
86
|
}
|
|
@@ -76,7 +97,11 @@ function () {
|
|
|
76
97
|
key: "stop",
|
|
77
98
|
value: function stop() {
|
|
78
99
|
this.isRendered = false;
|
|
79
|
-
|
|
100
|
+
|
|
101
|
+
if (this.watchListTopOffsetTimer) {
|
|
102
|
+
(0, _requestAnimationFrameTimeout.clearTimeout)(this.watchListTopOffsetTimer);
|
|
103
|
+
this.watchListTopOffsetTimer = undefined;
|
|
104
|
+
}
|
|
80
105
|
}
|
|
81
106
|
}, {
|
|
82
107
|
key: "watchListTopOffset",
|
|
@@ -95,13 +120,11 @@ function () {
|
|
|
95
120
|
|
|
96
121
|
|
|
97
122
|
if (_this.listTopOffsetInsideScrollableContainer !== undefined) {
|
|
98
|
-
// Calling `this.
|
|
99
|
-
//
|
|
123
|
+
// Calling `this.getListTopOffset()` on an element
|
|
124
|
+
// runs about 0.003 milliseconds on a modern desktop CPU,
|
|
100
125
|
// so I guess it's fine calling it twice a second.
|
|
101
|
-
if (_this.
|
|
102
|
-
_this.
|
|
103
|
-
reason: _Layout.LAYOUT_REASON.TOP_OFFSET_CHANGED
|
|
104
|
-
});
|
|
126
|
+
if (_this.getListTopOffset() !== _this.listTopOffsetInsideScrollableContainer) {
|
|
127
|
+
_this.onListTopOffsetChange();
|
|
105
128
|
}
|
|
106
129
|
} // Compare `top` coordinate of the list twice a second
|
|
107
130
|
// to find out if it has changed as a result of loading CSS styles.
|
|
@@ -122,10 +145,8 @@ function () {
|
|
|
122
145
|
}
|
|
123
146
|
}]);
|
|
124
147
|
|
|
125
|
-
return
|
|
148
|
+
return ListTopOffsetWatcher;
|
|
126
149
|
}();
|
|
127
150
|
|
|
128
|
-
exports["default"] =
|
|
129
|
-
|
|
130
|
-
var WATCH_LIST_TOP_OFFSET_MAX_DURATION = 3000;
|
|
131
|
-
//# sourceMappingURL=WaitForStylesToLoad.js.map
|
|
151
|
+
exports["default"] = ListTopOffsetWatcher;
|
|
152
|
+
//# sourceMappingURL=ListTopOffsetWatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../source/DOM/ListTopOffsetWatcher.js"],"names":["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":";;;;;;;AAIA;;;;;;;;AAEA;AACA,IAAMA,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;AACjC,wDAAa,KAAKA,uBAAlB;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+B,8CAAWI,KAAX,EAAkBf,8BAAlB,CAA/B;AACA;AACD,OA1BD,CAFoB,CA6BpB;;;AACAe,MAAAA,KAAK;AACL","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,21 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
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); }
|
|
4
|
+
|
|
3
5
|
Object.defineProperty(exports, "__esModule", {
|
|
4
6
|
value: true
|
|
5
7
|
});
|
|
6
|
-
exports
|
|
8
|
+
exports["default"] = exports.ScrollableWindowContainer = void 0;
|
|
7
9
|
|
|
8
|
-
function
|
|
10
|
+
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); }
|
|
9
11
|
|
|
10
|
-
function
|
|
12
|
+
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
|
11
13
|
|
|
12
|
-
function
|
|
14
|
+
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); }; }
|
|
13
15
|
|
|
14
|
-
function
|
|
16
|
+
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); }
|
|
15
17
|
|
|
16
|
-
function
|
|
18
|
+
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
|
17
19
|
|
|
18
|
-
function
|
|
20
|
+
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; } }
|
|
21
|
+
|
|
22
|
+
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
|
19
23
|
|
|
20
24
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
21
25
|
|
|
@@ -23,17 +27,17 @@ function _defineProperties(target, props) { for (var i = 0; i < props.length; i+
|
|
|
23
27
|
|
|
24
28
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
|
|
25
29
|
|
|
26
|
-
var ScrollableContainer =
|
|
27
|
-
/*#__PURE__*/
|
|
28
|
-
function () {
|
|
30
|
+
var ScrollableContainer = /*#__PURE__*/function () {
|
|
29
31
|
/**
|
|
30
32
|
* Constructs a new "scrollable container" from an element.
|
|
31
33
|
* @param {Element} scrollableContainer
|
|
34
|
+
* @param {func} getItemsContainerElement — Returns items "container" element.
|
|
32
35
|
*/
|
|
33
|
-
function ScrollableContainer(element) {
|
|
36
|
+
function ScrollableContainer(element, getItemsContainerElement) {
|
|
34
37
|
_classCallCheck(this, ScrollableContainer);
|
|
35
38
|
|
|
36
39
|
this.element = element;
|
|
40
|
+
this.getItemsContainerElement = getItemsContainerElement;
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
43
|
* Returns the current scroll position.
|
|
@@ -54,7 +58,14 @@ function () {
|
|
|
54
58
|
}, {
|
|
55
59
|
key: "scrollToY",
|
|
56
60
|
value: function scrollToY(scrollY) {
|
|
57
|
-
|
|
61
|
+
// IE 11 doesn't seem to have a `.scrollTo()` method.
|
|
62
|
+
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/10
|
|
63
|
+
// https://stackoverflow.com/questions/39908825/window-scrollto-is-not-working-in-internet-explorer-11
|
|
64
|
+
if (this.element.scrollTo) {
|
|
65
|
+
this.element.scrollTo(0, scrollY);
|
|
66
|
+
} else {
|
|
67
|
+
this.element.scrollTop = scrollY;
|
|
68
|
+
}
|
|
58
69
|
}
|
|
59
70
|
/**
|
|
60
71
|
* Returns "scrollable container" width,
|
|
@@ -82,35 +93,18 @@ function () {
|
|
|
82
93
|
return this.element.offsetHeight;
|
|
83
94
|
}
|
|
84
95
|
/**
|
|
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
|
|
96
|
+
* Returns a "top offset" of an items container element
|
|
102
97
|
* relative to the "scrollable container"'s top edge.
|
|
103
|
-
* @param {Element} element
|
|
104
98
|
* @return {number}
|
|
105
99
|
*/
|
|
106
100
|
|
|
107
101
|
}, {
|
|
108
|
-
key: "
|
|
109
|
-
value: function
|
|
102
|
+
key: "getItemsContainerTopOffset",
|
|
103
|
+
value: function getItemsContainerTopOffset() {
|
|
110
104
|
var scrollableContainerTop = this.element.getBoundingClientRect().top;
|
|
111
105
|
var scrollableContainerBorderTopWidth = this.element.clientTop;
|
|
112
|
-
var
|
|
113
|
-
return
|
|
106
|
+
var itemsContainerTop = this.getItemsContainerElement().getBoundingClientRect().top;
|
|
107
|
+
return itemsContainerTop - scrollableContainerTop + this.getScrollY() - scrollableContainerBorderTopWidth;
|
|
114
108
|
} // isVisible() {
|
|
115
109
|
// const { top, bottom } = this.element.getBoundingClientRect()
|
|
116
110
|
// return bottom > 0 && top < getScreenHeight()
|
|
@@ -123,31 +117,27 @@ function () {
|
|
|
123
117
|
*/
|
|
124
118
|
|
|
125
119
|
}, {
|
|
126
|
-
key: "
|
|
127
|
-
value: function
|
|
120
|
+
key: "onScroll",
|
|
121
|
+
value: function onScroll(_onScroll) {
|
|
128
122
|
var _this = this;
|
|
129
123
|
|
|
130
|
-
this.element.addEventListener('scroll',
|
|
124
|
+
this.element.addEventListener('scroll', _onScroll);
|
|
131
125
|
return function () {
|
|
132
|
-
return _this.element.removeEventListener('scroll',
|
|
126
|
+
return _this.element.removeEventListener('scroll', _onScroll);
|
|
133
127
|
};
|
|
134
128
|
}
|
|
135
129
|
/**
|
|
136
130
|
* Adds a "resize" event listener to the "scrollable container".
|
|
137
131
|
* @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.
|
|
132
|
+
* @return {function} Returns a function that stops listening.
|
|
140
133
|
*/
|
|
141
134
|
|
|
142
135
|
}, {
|
|
143
136
|
key: "onResize",
|
|
144
|
-
value: function onResize(_onResize
|
|
137
|
+
value: function onResize(_onResize) {
|
|
145
138
|
var _this2 = this;
|
|
146
139
|
|
|
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).
|
|
140
|
+
// Watches "scrollable container"'s dimensions via a `ResizeObserver`.
|
|
151
141
|
// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
|
|
152
142
|
// https://web.dev/resize-observer/
|
|
153
143
|
var unobserve;
|
|
@@ -180,7 +170,7 @@ function () {
|
|
|
180
170
|
|
|
181
171
|
|
|
182
172
|
var unlistenGlobalResize = addGlobalResizeListener(_onResize, {
|
|
183
|
-
|
|
173
|
+
itemsContainerElement: this.getItemsContainerElement()
|
|
184
174
|
});
|
|
185
175
|
return function () {
|
|
186
176
|
if (unobserve) {
|
|
@@ -197,15 +187,19 @@ function () {
|
|
|
197
187
|
|
|
198
188
|
exports["default"] = ScrollableContainer;
|
|
199
189
|
|
|
200
|
-
var ScrollableWindowContainer =
|
|
201
|
-
/*#__PURE__*/
|
|
202
|
-
function (_ScrollableContainer) {
|
|
190
|
+
var ScrollableWindowContainer = /*#__PURE__*/function (_ScrollableContainer) {
|
|
203
191
|
_inherits(ScrollableWindowContainer, _ScrollableContainer);
|
|
204
192
|
|
|
205
|
-
|
|
193
|
+
var _super = _createSuper(ScrollableWindowContainer);
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Constructs a new window "scrollable container".
|
|
197
|
+
* @param {func} getItemsContainerElement — Returns items "container" element.
|
|
198
|
+
*/
|
|
199
|
+
function ScrollableWindowContainer(getItemsContainerElement) {
|
|
206
200
|
_classCallCheck(this, ScrollableWindowContainer);
|
|
207
201
|
|
|
208
|
-
return
|
|
202
|
+
return _super.call(this, window, getItemsContainerElement);
|
|
209
203
|
}
|
|
210
204
|
/**
|
|
211
205
|
* Returns the current scroll position.
|
|
@@ -259,47 +253,28 @@ function (_ScrollableContainer) {
|
|
|
259
253
|
return window.innerHeight;
|
|
260
254
|
}
|
|
261
255
|
/**
|
|
262
|
-
* Returns
|
|
263
|
-
* For example, a scrollable container can have a height of 500px,
|
|
264
|
-
* but the content in it could have a height of 5000px,
|
|
265
|
-
* in which case a vertical scrollbar is rendered, and only
|
|
266
|
-
* one-tenth of all the items are shown at any given moment.
|
|
267
|
-
* This function is currently only used when using the
|
|
268
|
-
* `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.
|
|
269
|
-
* @return {number}
|
|
270
|
-
*/
|
|
271
|
-
|
|
272
|
-
}, {
|
|
273
|
-
key: "getContentHeight",
|
|
274
|
-
value: function getContentHeight() {
|
|
275
|
-
return document.documentElement.scrollHeight;
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Returns a "top offset" of an element
|
|
256
|
+
* Returns a "top offset" of an items container element
|
|
279
257
|
* relative to the "scrollable container"'s top edge.
|
|
280
|
-
* @param {Element} element
|
|
281
258
|
* @return {number}
|
|
282
259
|
*/
|
|
283
260
|
|
|
284
261
|
}, {
|
|
285
|
-
key: "
|
|
286
|
-
value: function
|
|
262
|
+
key: "getItemsContainerTopOffset",
|
|
263
|
+
value: function getItemsContainerTopOffset() {
|
|
287
264
|
var borderTopWidth = document.clientTop || document.body.clientTop || 0;
|
|
288
|
-
return
|
|
265
|
+
return this.getItemsContainerElement().getBoundingClientRect().top + this.getScrollY() - borderTopWidth;
|
|
289
266
|
}
|
|
290
267
|
/**
|
|
291
268
|
* Adds a "resize" event listener to the "scrollable container".
|
|
292
269
|
* @param {onScroll} Should be called whenever the "scrollable container"'s width or height (potentially) changes.
|
|
293
|
-
* @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.
|
|
294
270
|
* @return {function} Returns a function that stops listening.
|
|
295
271
|
*/
|
|
296
272
|
|
|
297
273
|
}, {
|
|
298
274
|
key: "onResize",
|
|
299
|
-
value: function onResize(_onResize2
|
|
300
|
-
var container = _ref2.container;
|
|
275
|
+
value: function onResize(_onResize2) {
|
|
301
276
|
return addGlobalResizeListener(_onResize2, {
|
|
302
|
-
|
|
277
|
+
itemsContainerElement: this.getItemsContainerElement()
|
|
303
278
|
});
|
|
304
279
|
} // isVisible() {
|
|
305
280
|
// return true
|
|
@@ -311,16 +286,16 @@ function (_ScrollableContainer) {
|
|
|
311
286
|
}(ScrollableContainer);
|
|
312
287
|
/**
|
|
313
288
|
* Adds a "resize" event listener to the `window`.
|
|
314
|
-
* @param {onResize} Should be called whenever the "container"'s width or height (potentially) changes.
|
|
315
|
-
* @param {Element} options.
|
|
289
|
+
* @param {onResize} Should be called whenever the "scrollable container"'s width or height (potentially) changes.
|
|
290
|
+
* @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.
|
|
316
291
|
* @return {function} Returns a function that stops listening.
|
|
317
292
|
*/
|
|
318
293
|
|
|
319
294
|
|
|
320
295
|
exports.ScrollableWindowContainer = ScrollableWindowContainer;
|
|
321
296
|
|
|
322
|
-
function addGlobalResizeListener(onResize,
|
|
323
|
-
var
|
|
297
|
+
function addGlobalResizeListener(onResize, _ref) {
|
|
298
|
+
var itemsContainerElement = _ref.itemsContainerElement;
|
|
324
299
|
|
|
325
300
|
var onResizeListener = function onResizeListener() {
|
|
326
301
|
// By default, `VirtualScroller` always performs a re-layout
|
|
@@ -352,7 +327,7 @@ function addGlobalResizeListener(onResize, _ref3) {
|
|
|
352
327
|
// the layout wouldn't be affected too, so such `resize` event should also be
|
|
353
328
|
// ignored: when `document.fullscreenElement` is inside the `container`.
|
|
354
329
|
//
|
|
355
|
-
if (document.fullscreenElement.contains(
|
|
330
|
+
if (document.fullscreenElement.contains(itemsContainerElement)) {// The element is either the `container`'s ancestor,
|
|
356
331
|
// Or is the `container` itself.
|
|
357
332
|
// (`a.contains(b)` includes the `a === b` case).
|
|
358
333
|
// 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":";;;;;;;;;;;;;;;;;;;;;;;;;IAAqBA,mB;;;AACpB;;;;AAIA,+BAAYC,OAAZ,EAAqB;AAAA;;AACpB,SAAKA,OAAL,GAAeA,OAAf;AACA;AAED;;;;;;;;iCAIa;AACZ,aAAO,KAAKA,OAAL,CAAaC,SAApB;AACA;AAED;;;;;;;8BAIUC,O,EAAS;AAClB,WAAKF,OAAL,CAAaG,QAAb,CAAsB,CAAtB,EAAyBD,OAAzB;AACA;AAED;;;;;;;;+BAKW;AACV,aAAO,KAAKF,OAAL,CAAaI,WAApB;AACA;AAED;;;;;;;;gCAKY;AACX;AACA;AACA;AACA,aAAO,KAAKJ,OAAL,CAAaK,YAApB;AACA;AAED;;;;;;;;;;;;;uCAUmB;AAClB,aAAO,KAAKL,OAAL,CAAaM,YAApB;AACA;AAED;;;;;;;;;iCAMaN,O,EAAS;AACrB,UAAMO,sBAAsB,GAAG,KAAKP,OAAL,CAAaQ,qBAAb,GAAqCC,GAApE;AACA,UAAMC,iCAAiC,GAAG,KAAKV,OAAL,CAAaW,SAAvD;AACA,UAAMF,GAAG,GAAGT,OAAO,CAACQ,qBAAR,GAAgCC,GAA5C;AACA,aAAQA,GAAG,GAAGF,sBAAP,GAAiC,KAAKK,UAAL,EAAjC,GAAqDF,iCAA5D;AACA,K,CAED;AACA;AACA;AACA;;AAEA;;;;;;;;sCAKkBG,Q,EAAU;AAAA;;AAC3B,WAAKb,OAAL,CAAac,gBAAb,CAA8B,QAA9B,EAAwCD,QAAxC;AACA,aAAO;AAAA,eAAM,KAAI,CAACb,OAAL,CAAae,mBAAb,CAAiC,QAAjC,EAA2CF,QAA3C,CAAN;AAAA,OAAP;AACA;AAED;;;;;;;;;6BAMSG,S,QAAyB;AAAA;;AAAA,UAAbC,SAAa,QAAbA,SAAa;AACjC;AACA;AACA;AACA;AACA;AACA,UAAIC,SAAJ;;AACA,UAAI,OAAOC,cAAP,KAA0B,WAA9B,EAA2C;AAC1C,YAAMC,cAAc,GAAG,IAAID,cAAJ,CAAmB,UAACE,OAAD,EAAa;AACtD;AACA;AACA;AACA,cAAMC,KAAK,GAAGD,OAAO,CAAC,CAAD,CAArB,CAJsD,CAKtD;AACA;AACA;AACA;AACA;AACA;;AACAL,UAAAA,SAAQ;AACR,SAZsB,CAAvB;AAaAI,QAAAA,cAAc,CAACG,OAAf,CAAuB,KAAKvB,OAA5B;;AACAkB,QAAAA,SAAS,GAAG;AAAA,iBAAME,cAAc,CAACF,SAAf,CAAyB,MAAI,CAAClB,OAA9B,CAAN;AAAA,SAAZ;AACA,OAvBgC,CAwBjC;AACA;AACA;AACA;AACA;AACA;;;AACA,UAAMwB,oBAAoB,GAAGC,uBAAuB,CAACT,SAAD,EAAW;AAAEC,QAAAA,SAAS,EAATA;AAAF,OAAX,CAApD;AACA,aAAO,YAAM;AACZ,YAAIC,SAAJ,EAAe;AACdA,UAAAA,SAAS;AACT;;AACDM,QAAAA,oBAAoB;AACpB,OALD;AAMA;;;;;;;;IAGWE,yB;;;;;AACZ,uCAAc;AAAA;;AAAA,kGACPC,MADO;AAEb;AAED;;;;;;;;iCAIa;AACZ;AACA,aAAOA,MAAM,CAACC,WAAd;AACA;AAED;;;;;;;;+BAKW;AACV;AACA;AACA;AACA;AACA;AACA;AACA,aAAOD,MAAM,CAACE,UAAd;AACA;AAED;;;;;;;;gCAKY;AACX;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAOF,MAAM,CAACG,WAAd;AACA;AAED;;;;;;;;;;;;;uCAUmB;AAClB,aAAOC,QAAQ,CAACC,eAAT,CAAyB1B,YAAhC;AACA;AAED;;;;;;;;;iCAMaN,O,EAAS;AACrB,UAAMiC,cAAc,GAAGF,QAAQ,CAACpB,SAAT,IAAsBoB,QAAQ,CAACG,IAAT,CAAcvB,SAApC,IAAiD,CAAxE;AACA,aAAOX,OAAO,CAACQ,qBAAR,GAAgCC,GAAhC,GAAsC,KAAKG,UAAL,EAAtC,GAA0DqB,cAAjE;AACA;AAED;;;;;;;;;6BAMSjB,U,SAAyB;AAAA,UAAbC,SAAa,SAAbA,SAAa;AACjC,aAAOQ,uBAAuB,CAACT,UAAD,EAAW;AAAEC,QAAAA,SAAS,EAATA;AAAF,OAAX,CAA9B;AACA,K,CAED;AACA;AACA;;;;;EAtF8ClB,mB;AAyF/C;;;;;;;;;;AAMA,SAAS0B,uBAAT,CAAiCT,QAAjC,SAA0D;AAAA,MAAbC,SAAa,SAAbA,SAAa;;AACzD,MAAMkB,gBAAgB,GAAG,SAAnBA,gBAAmB,GAAM;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAIJ,QAAQ,CAACK,iBAAb,EAAgC;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAIL,QAAQ,CAACK,iBAAT,CAA2BC,QAA3B,CAAoCpB,SAApC,CAAJ,EAAoD,CACnD;AACA;AACA;AACA;AACA,OALD,MAKO;AACN;AACA;AACA;AACA;AACA;AACD;;AACDD,IAAAA,QAAQ;AACR,GA3CD;;AA4CAW,EAAAA,MAAM,CAACb,gBAAP,CAAwB,QAAxB,EAAkCqB,gBAAlC;AACA,SAAO;AAAA,WAAMR,MAAM,CAACZ,mBAAP,CAA2B,QAA3B,EAAqCoB,gBAArC,CAAN;AAAA,GAAP;AACA","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"}
|