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.
Files changed (154) hide show
  1. package/.gitlab-ci.yml +1 -1
  2. package/CHANGELOG.md +24 -1
  3. package/README.md +139 -33
  4. package/babel.config.js +25 -0
  5. package/babel.js +5 -0
  6. package/bundle/index-bypass.html +1 -1
  7. package/bundle/index-dom.html +1 -1
  8. package/bundle/index-grid.html +1 -2
  9. package/bundle/index-scrollableContainer.html +1 -1
  10. package/bundle/index-tbody-scrollableContainer.html +2 -0
  11. package/bundle/index-tbody.html +2 -0
  12. package/bundle/virtual-scroller-dom.js +1 -1
  13. package/bundle/virtual-scroller-dom.js.map +1 -1
  14. package/bundle/virtual-scroller-react.js +1 -1
  15. package/bundle/virtual-scroller-react.js.map +1 -1
  16. package/bundle/virtual-scroller.js +1 -1
  17. package/bundle/virtual-scroller.js.map +1 -1
  18. package/commonjs/BeforeResize.js +319 -0
  19. package/commonjs/BeforeResize.js.map +1 -0
  20. package/commonjs/DOM/Engine.js +46 -0
  21. package/commonjs/DOM/Engine.js.map +1 -0
  22. package/commonjs/DOM/ItemsContainer.js +78 -0
  23. package/commonjs/DOM/ItemsContainer.js.map +1 -0
  24. package/commonjs/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +56 -35
  25. package/commonjs/DOM/ListTopOffsetWatcher.js.map +1 -0
  26. package/commonjs/DOM/ScrollableContainer.js +56 -81
  27. package/commonjs/DOM/ScrollableContainer.js.map +1 -1
  28. package/commonjs/DOM/VirtualScroller.js +20 -15
  29. package/commonjs/DOM/VirtualScroller.js.map +1 -1
  30. package/commonjs/DOM/tbody.js +2 -2
  31. package/commonjs/ItemHeights.js +22 -29
  32. package/commonjs/ItemHeights.js.map +1 -1
  33. package/commonjs/Layout.js +588 -215
  34. package/commonjs/Layout.js.map +1 -1
  35. package/commonjs/Layout.test.js +191 -0
  36. package/commonjs/Layout.test.js.map +1 -0
  37. package/commonjs/ListHeightChangeWatcher.js +126 -0
  38. package/commonjs/ListHeightChangeWatcher.js.map +1 -0
  39. package/commonjs/Resize.js +22 -21
  40. package/commonjs/Resize.js.map +1 -1
  41. package/commonjs/Scroll.js +148 -88
  42. package/commonjs/Scroll.js.map +1 -1
  43. package/commonjs/VirtualScroller.js +1269 -390
  44. package/commonjs/VirtualScroller.js.map +1 -1
  45. package/commonjs/getItemCoordinates.js.map +1 -1
  46. package/commonjs/getItemsDiff.js.map +1 -1
  47. package/commonjs/getVerticalSpacing.js +8 -8
  48. package/commonjs/getVerticalSpacing.js.map +1 -1
  49. package/commonjs/react/VirtualScroller.js +31 -37
  50. package/commonjs/react/VirtualScroller.js.map +1 -1
  51. package/commonjs/utility/debounce.js +26 -4
  52. package/commonjs/utility/debounce.js.map +1 -1
  53. package/commonjs/utility/debug.js +51 -12
  54. package/commonjs/utility/debug.js.map +1 -1
  55. package/commonjs/utility/getStateSnapshot.js +50 -0
  56. package/commonjs/utility/getStateSnapshot.js.map +1 -0
  57. package/commonjs/utility/px.js +1 -1
  58. package/commonjs/utility/px.js.map +1 -1
  59. package/commonjs/utility/px.test.js +14 -0
  60. package/commonjs/utility/px.test.js.map +1 -0
  61. package/commonjs/utility/shallowEqual.js +1 -1
  62. package/commonjs/utility/shallowEqual.js.map +1 -1
  63. package/commonjs/utility/throttle.js.map +1 -1
  64. package/dom/index.d.ts +23 -0
  65. package/index.d.ts +84 -0
  66. package/modules/BeforeResize.js +310 -0
  67. package/modules/BeforeResize.js.map +1 -0
  68. package/modules/DOM/Engine.js +27 -0
  69. package/modules/DOM/Engine.js.map +1 -0
  70. package/modules/DOM/ItemsContainer.js +71 -0
  71. package/modules/DOM/ItemsContainer.js.map +1 -0
  72. package/modules/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +57 -35
  73. package/modules/DOM/ListTopOffsetWatcher.js.map +1 -0
  74. package/modules/DOM/ScrollableContainer.js +55 -80
  75. package/modules/DOM/ScrollableContainer.js.map +1 -1
  76. package/modules/DOM/VirtualScroller.js +15 -14
  77. package/modules/DOM/VirtualScroller.js.map +1 -1
  78. package/modules/ItemHeights.js +17 -28
  79. package/modules/ItemHeights.js.map +1 -1
  80. package/modules/Layout.js +582 -213
  81. package/modules/Layout.js.map +1 -1
  82. package/modules/Layout.test.js +185 -0
  83. package/modules/Layout.test.js.map +1 -0
  84. package/modules/ListHeightChangeWatcher.js +119 -0
  85. package/modules/ListHeightChangeWatcher.js.map +1 -0
  86. package/modules/Resize.js +21 -20
  87. package/modules/Resize.js.map +1 -1
  88. package/modules/Scroll.js +148 -87
  89. package/modules/Scroll.js.map +1 -1
  90. package/modules/VirtualScroller.js +1263 -390
  91. package/modules/VirtualScroller.js.map +1 -1
  92. package/modules/getItemCoordinates.js.map +1 -1
  93. package/modules/getItemsDiff.js.map +1 -1
  94. package/modules/getVerticalSpacing.js +8 -8
  95. package/modules/getVerticalSpacing.js.map +1 -1
  96. package/modules/react/VirtualScroller.js +31 -37
  97. package/modules/react/VirtualScroller.js.map +1 -1
  98. package/modules/utility/debounce.js +26 -4
  99. package/modules/utility/debounce.js.map +1 -1
  100. package/modules/utility/debug.js +47 -10
  101. package/modules/utility/debug.js.map +1 -1
  102. package/modules/utility/getStateSnapshot.js +43 -0
  103. package/modules/utility/getStateSnapshot.js.map +1 -0
  104. package/modules/utility/px.js +1 -1
  105. package/modules/utility/px.js.map +1 -1
  106. package/modules/utility/px.test.js +9 -0
  107. package/modules/utility/px.test.js.map +1 -0
  108. package/modules/utility/shallowEqual.js +1 -1
  109. package/modules/utility/shallowEqual.js.map +1 -1
  110. package/modules/utility/throttle.js.map +1 -1
  111. package/package.json +24 -22
  112. package/react/index.d.ts +27 -0
  113. package/source/BeforeResize.js +317 -0
  114. package/source/DOM/Engine.js +32 -0
  115. package/source/DOM/ItemsContainer.js +48 -0
  116. package/source/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +48 -22
  117. package/source/DOM/ScrollableContainer.js +39 -56
  118. package/source/DOM/VirtualScroller.js +6 -7
  119. package/source/ItemHeights.js +19 -24
  120. package/source/Layout.js +626 -252
  121. package/source/Layout.test.js +171 -0
  122. package/source/ListHeightChangeWatcher.js +94 -0
  123. package/source/Resize.js +23 -15
  124. package/source/Scroll.js +139 -78
  125. package/source/VirtualScroller.js +1243 -286
  126. package/source/getVerticalSpacing.js +7 -7
  127. package/source/react/VirtualScroller.js +2 -18
  128. package/source/utility/debounce.js +20 -3
  129. package/source/utility/debug.js +34 -3
  130. package/source/utility/getStateSnapshot.js +36 -0
  131. package/source/utility/px.js +1 -1
  132. package/source/utility/px.test.js +9 -0
  133. package/website/index-bypass.html +195 -0
  134. package/website/index-grid.html +0 -1
  135. package/website/index-scrollableContainer.html +208 -0
  136. package/website/index-tbody-scrollableContainer.html +68 -0
  137. package/website/index-tbody.html +55 -0
  138. package/commonjs/DOM/RenderingEngine.js +0 -33
  139. package/commonjs/DOM/RenderingEngine.js.map +0 -1
  140. package/commonjs/DOM/Screen.js +0 -87
  141. package/commonjs/DOM/Screen.js.map +0 -1
  142. package/commonjs/DOM/WaitForStylesToLoad.js.map +0 -1
  143. package/commonjs/RestoreScroll.js +0 -118
  144. package/commonjs/RestoreScroll.js.map +0 -1
  145. package/modules/DOM/RenderingEngine.js +0 -19
  146. package/modules/DOM/RenderingEngine.js.map +0 -1
  147. package/modules/DOM/Screen.js +0 -80
  148. package/modules/DOM/Screen.js.map +0 -1
  149. package/modules/DOM/WaitForStylesToLoad.js.map +0 -1
  150. package/modules/RestoreScroll.js +0 -111
  151. package/modules/RestoreScroll.js.map +0 -1
  152. package/source/DOM/RenderingEngine.js +0 -22
  153. package/source/DOM/Screen.js +0 -51
  154. 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
- // `VirtualScroller` calls `this.layout.layOut()` on mount,
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 for that would be that calling `.getListTopOffsetInsideScrollableContainer()`
23
- // on mount returns "incorrect" `top` position because the styles haven't been applied yet.
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
- // This type of a bug doesn't occur in production, but it can appear
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 `.getListTopOffsetInsideScrollableContainer()`
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
- var WaitForStylesToLoad =
44
- /*#__PURE__*/
45
- function () {
46
- function WaitForStylesToLoad(_ref) {
47
- var updateLayout = _ref.updateLayout,
48
- getListTopOffsetInsideScrollableContainer = _ref.getListTopOffsetInsideScrollableContainer;
49
-
50
- _classCallCheck(this, WaitForStylesToLoad);
51
-
52
- this.updateLayout = updateLayout;
53
- this.getListTopOffsetInsideScrollableContainer = getListTopOffsetInsideScrollableContainer;
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(WaitForStylesToLoad, [{
57
- key: "onGotListTopOffset",
58
- value: function onGotListTopOffset(listTopOffset) {
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 `WaitForStylesToLoad.js` file
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
- (0, _requestAnimationFrameTimeout.clearTimeout)(this.watchListTopOffsetTimer);
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.getListTopOffsetInsideScrollableContainer()`
99
- // on an element is about 0.003 milliseconds on a modern desktop CPU,
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.getListTopOffsetInsideScrollableContainer() !== _this.listTopOffsetInsideScrollableContainer) {
102
- _this.updateLayout({
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 WaitForStylesToLoad;
148
+ return ListTopOffsetWatcher;
126
149
  }();
127
150
 
128
- exports["default"] = WaitForStylesToLoad;
129
- var WATCH_LIST_TOP_OFFSET_INTERVAL = 500;
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.ScrollableWindowContainer = exports["default"] = void 0;
8
+ exports["default"] = exports.ScrollableWindowContainer = void 0;
7
9
 
8
- 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); }
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 _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
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 _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
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 _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
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 _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); }
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 _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
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
- this.element.scrollTo(0, scrollY);
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 the height of the content in a scrollable container.
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: "getTopOffset",
109
- value: function getTopOffset(element) {
102
+ key: "getItemsContainerTopOffset",
103
+ value: function getItemsContainerTopOffset() {
110
104
  var scrollableContainerTop = this.element.getBoundingClientRect().top;
111
105
  var scrollableContainerBorderTopWidth = this.element.clientTop;
112
- var top = element.getBoundingClientRect().top;
113
- return top - scrollableContainerTop + this.getScrollY() - scrollableContainerBorderTopWidth;
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: "addScrollListener",
127
- value: function addScrollListener(onScroll) {
120
+ key: "onScroll",
121
+ value: function onScroll(_onScroll) {
128
122
  var _this = this;
129
123
 
130
- this.element.addEventListener('scroll', onScroll);
124
+ this.element.addEventListener('scroll', _onScroll);
131
125
  return function () {
132
- return _this.element.removeEventListener('scroll', onScroll);
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
- * @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.
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, _ref) {
137
+ value: function onResize(_onResize) {
145
138
  var _this2 = this;
146
139
 
147
- var container = _ref.container;
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
- container: container
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
- function ScrollableWindowContainer() {
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 _possibleConstructorReturn(this, _getPrototypeOf(ScrollableWindowContainer).call(this, window));
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 the height of the content in a scrollable container.
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: "getTopOffset",
286
- value: function getTopOffset(element) {
262
+ key: "getItemsContainerTopOffset",
263
+ value: function getItemsContainerTopOffset() {
287
264
  var borderTopWidth = document.clientTop || document.body.clientTop || 0;
288
- return element.getBoundingClientRect().top + this.getScrollY() - borderTopWidth;
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, _ref2) {
300
- var container = _ref2.container;
275
+ value: function onResize(_onResize2) {
301
276
  return addGlobalResizeListener(_onResize2, {
302
- container: container
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.container — The "container".
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, _ref3) {
323
- var container = _ref3.container;
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(container)) {// The element is either the `container`'s ancestor,
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":"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","sourcesContent":["export default class ScrollableContainer {\r\n\t/**\r\n\t * Constructs a new \"scrollable container\" from an element.\r\n\t * @param {Element} scrollableContainer\r\n\t */\r\n\tconstructor(element) {\r\n\t\tthis.element = element\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the current scroll position.\r\n\t * @return {number}\r\n\t */\r\n\tgetScrollY() {\r\n\t\treturn this.element.scrollTop\r\n\t}\r\n\r\n\t/**\r\n\t * Scrolls to a specific position.\r\n\t * @param {number} scrollY\r\n\t */\r\n\tscrollToY(scrollY) {\r\n\t\tthis.element.scrollTo(0, scrollY)\r\n\t}\r\n\r\n\t/**\r\n\t * Returns \"scrollable container\" width,\r\n\t * i.e. the available width for its content.\r\n\t * @return {number}\r\n\t */\r\n\tgetWidth() {\r\n\t\treturn this.element.offsetWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the \"scrollable container\" itself.\r\n\t * Not to be confused with the height of \"scrollable container\"'s content.\r\n\t * @return {number}\r\n\t */\r\n\tgetHeight() {\r\n\t\t// if (!this.element && !precise) {\r\n\t\t// \treturn getScreenHeight()\r\n\t\t// }\r\n\t\treturn this.element.offsetHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the content in a scrollable container.\r\n\t * For example, a scrollable container can have a height of 500px,\r\n\t * but the content in it could have a height of 5000px,\r\n\t * in which case a vertical scrollbar is rendered, and only\r\n\t * one-tenth of all the items are shown at any given moment.\r\n\t * This function is currently only used when using the\r\n\t * `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.\r\n\t * @return {number}\r\n\t */\r\n\tgetContentHeight() {\r\n\t\treturn this.element.scrollHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a \"top offset\" of an element\r\n\t * relative to the \"scrollable container\"'s top edge.\r\n\t * @param {Element} element\r\n\t * @return {number}\r\n\t */\r\n\tgetTopOffset(element) {\r\n\t\tconst scrollableContainerTop = this.element.getBoundingClientRect().top\r\n\t\tconst scrollableContainerBorderTopWidth = this.element.clientTop\r\n\t\tconst top = element.getBoundingClientRect().top\r\n\t\treturn (top - scrollableContainerTop) + this.getScrollY() - scrollableContainerBorderTopWidth\r\n\t}\r\n\r\n\t// isVisible() {\r\n\t// \tconst { top, bottom } = this.element.getBoundingClientRect()\r\n\t// \treturn bottom > 0 && top < getScreenHeight()\r\n\t// }\r\n\r\n\t/**\r\n\t * Adds a \"scroll\" event listener to the \"scrollable container\".\r\n\t * @param {onScroll} Should be called whenever the scroll position inside the \"scrollable container\" (potentially) changes.\r\n\t * @return {function} Returns a function that stops listening.\r\n\t */\r\n\taddScrollListener(onScroll) {\r\n\t\tthis.element.addEventListener('scroll', onScroll)\r\n\t\treturn () => this.element.removeEventListener('scroll', onScroll)\r\n\t}\r\n\r\n\t/**\r\n\t * Adds a \"resize\" event listener to the \"scrollable container\".\r\n\t * @param {onResize} Should be called whenever the \"scrollable container\"'s width or height (potentially) changes.\r\n\t * @param {Element} options.container — The result of the `getContainerElement()` function that was passed in `VirtualScroller` constructor. For example, DOM renderer uses it to filter-out unrelated \"resize\" events.\r\n\t * @return {function} Returns a function that stops listening.\r\n\t */\r\n\tonResize(onResize, { container }) {\r\n\t\t// Could somehow track DOM Element size.\r\n\t\t// For now, `scrollableContainer` is supposed to have constant width and height.\r\n\t\t// (unless window is resized).\r\n\t\t// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver\r\n\t\t// https://web.dev/resize-observer/\r\n\t\tlet unobserve\r\n\t\tif (typeof ResizeObserver !== 'undefined') {\r\n\t\t\tconst resizeObserver = new ResizeObserver((entries) => {\r\n\t\t\t\t// \"one entry per observed element\".\r\n\t\t\t\t// https://web.dev/resize-observer/\r\n\t\t\t\t// `entry.target === this.element`.\r\n\t\t\t\tconst entry = entries[0]\r\n\t\t\t\t// // If `entry.contentBoxSize` property is supported by the web browser.\r\n\t\t\t\t// if (entry.contentBoxSize) {\r\n\t\t\t\t// \t// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentBoxSize\r\n\t\t\t\t// \tconst width = entry.contentBoxSize.inlineSize\r\n\t\t\t\t// \tconst height = entry.contentBoxSize.blockSize\r\n\t\t\t\t// }\r\n\t\t\t\tonResize()\r\n\t\t\t})\r\n\t\t\tresizeObserver.observe(this.element)\r\n\t\t\tunobserve = () => resizeObserver.unobserve(this.element)\r\n\t\t}\r\n\t\t// I guess, if window is resized, `onResize()` will be triggered twice:\r\n\t\t// once for window resize, and once for the scrollable container resize.\r\n\t\t// But `onResize()` also has an internal check: if the container size\r\n\t\t// hasn't changed since the previous time `onResize()` has been called,\r\n\t\t// then `onResize()` doesn't do anything, so, I guess, there shouldn't be\r\n\t\t// any \"performance implications\" of running the listener twice in such case.\r\n\t\tconst unlistenGlobalResize = addGlobalResizeListener(onResize, { container })\r\n\t\treturn () => {\r\n\t\t\tif (unobserve) {\r\n\t\t\t\tunobserve()\r\n\t\t\t}\r\n\t\t\tunlistenGlobalResize()\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport class ScrollableWindowContainer extends ScrollableContainer {\r\n\tconstructor() {\r\n\t\tsuper(window)\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the current scroll position.\r\n\t * @return {number}\r\n\t */\r\n\tgetScrollY() {\r\n\t\t// `window.scrollY` is not supported by Internet Explorer.\r\n\t\treturn window.pageYOffset\r\n\t}\r\n\r\n\t/**\r\n\t * Returns \"scrollable container\" width,\r\n\t * i.e. the available width for its content.\r\n\t * @return {number}\r\n\t */\r\n\tgetWidth() {\r\n\t\t// https://javascript.info/size-and-scroll-window\r\n\t\t// `<!DOCTYPE html>` may be required in order for this to work correctly.\r\n\t\t// Includes scrollbar (if any).\r\n\t\t// Correctly reflects page zoom in iOS Safari.\r\n\t\t// (scales screen width accordingly).\r\n\t\t// But, includes scrollbar (if any).\r\n\t\treturn window.innerWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the \"scrollable container\" itself.\r\n\t * Not to be confused with the height of \"scrollable container\"'s content.\r\n\t * @return {number}\r\n\t */\r\n\tgetHeight() {\r\n\t\t// https://javascript.info/size-and-scroll-window\r\n\t\t// `<!DOCTYPE html>` is required in order for this to work correctly.\r\n\t\t// Without it, the returned height would be the height of the entire document.\r\n\t\t// Includes scrollbar (if any).\r\n\t\t// Supports iOS Safari's dynamically shown/hidden\r\n\t\t// top URL bar and bottom actions bar.\r\n\t\t// https://codesandbox.io/s/elegant-fog-iddrh\r\n\t\t// Tested in IE 11.\r\n\t\t// It also correctly reflects page zoom in iOS Safari.\r\n\t\t// (scales screen height accordingly).\r\n\t\t// But, includes scrollbar (if any).\r\n\t\treturn window.innerHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the content in a scrollable container.\r\n\t * For example, a scrollable container can have a height of 500px,\r\n\t * but the content in it could have a height of 5000px,\r\n\t * in which case a vertical scrollbar is rendered, and only\r\n\t * one-tenth of all the items are shown at any given moment.\r\n\t * This function is currently only used when using the\r\n\t * `preserveScrollPositionOfTheBottomOfTheListOnMount` feature.\r\n\t * @return {number}\r\n\t */\r\n\tgetContentHeight() {\r\n\t\treturn document.documentElement.scrollHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a \"top offset\" of an element\r\n\t * relative to the \"scrollable container\"'s top edge.\r\n\t * @param {Element} element\r\n\t * @return {number}\r\n\t */\r\n\tgetTopOffset(element) {\r\n\t\tconst borderTopWidth = document.clientTop || document.body.clientTop || 0\r\n\t\treturn element.getBoundingClientRect().top + this.getScrollY() - borderTopWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Adds a \"resize\" event listener to the \"scrollable container\".\r\n\t * @param {onScroll} Should be called whenever the \"scrollable container\"'s width or height (potentially) changes.\r\n\t * @param {Element} options.container — The result of the `getContainerElement()` function that was passed in `VirtualScroller` constructor. For example, DOM renderer uses it to filter-out unrelated \"resize\" events.\r\n\t * @return {function} Returns a function that stops listening.\r\n\t */\r\n\tonResize(onResize, { container }) {\r\n\t\treturn addGlobalResizeListener(onResize, { container })\r\n\t}\r\n\r\n\t// isVisible() {\r\n\t// \treturn true\r\n\t// }\r\n}\r\n\r\n/**\r\n * Adds a \"resize\" event listener to the `window`.\r\n * @param {onResize} Should be called whenever the \"container\"'s width or height (potentially) changes.\r\n * @param {Element} options.container — The \"container\".\r\n * @return {function} Returns a function that stops listening.\r\n */\r\nfunction addGlobalResizeListener(onResize, { container }) {\r\n\tconst onResizeListener = () => {\r\n\t\t// By default, `VirtualScroller` always performs a re-layout\r\n\t\t// on window `resize` event. But browsers (Chrome, Firefox)\r\n\t\t// [trigger](https://developer.mozilla.org/en-US/docs/Web/API/Window/fullScreen#Notes)\r\n\t\t// window `resize` event also when a user switches into fullscreen mode:\r\n\t\t// for example, when a user is watching a video and double-clicks on it\r\n\t\t// to maximize it. And also when the user goes out of the fullscreen mode.\r\n\t\t// Each such fullscreen mode entering/exiting will trigger window `resize`\r\n\t\t// event that will it turn trigger a re-layout of `VirtualScroller`,\r\n\t\t// resulting in bad user experience. To prevent that, such cases are filtered out.\r\n\t\t// Some other workaround:\r\n\t\t// https://stackoverflow.com/questions/23770449/embedded-youtube-video-fullscreen-or-causing-resize\r\n\t\tif (document.fullscreenElement) {\r\n\t\t\t// If the fullscreened element doesn't contain the list\r\n\t\t\t// (and is not the list itself), then the layout hasn't been affected,\r\n\t\t\t// so don't perform a re-layout.\r\n\t\t\t//\r\n\t\t\t// For example, suppose there's a list of items, and some item contains a video.\r\n\t\t\t// If, upon clicking such video, it plays inline, and the user enters\r\n\t\t\t// fullscreen mode while playing such inline video, then the layout won't be\r\n\t\t\t// affected, and so such `resize` event should be ignored: when\r\n\t\t\t// `document.fullscreenElement` is in a separate \"branch\" relative to the\r\n\t\t\t// `container`.\r\n\t\t\t//\r\n\t\t\t// Another scenario: suppose that upon click, the video doesn't play inline,\r\n\t\t\t// but instead a \"Slideshow\" component is open, with the video shown at the\r\n\t\t\t// center of the screen in an overlay. If then the user enters fullscreen mode,\r\n\t\t\t// the layout wouldn't be affected too, so such `resize` event should also be\r\n\t\t\t// ignored: when `document.fullscreenElement` is inside the `container`.\r\n\t\t\t//\r\n\t\t\tif (document.fullscreenElement.contains(container)) {\r\n\t\t\t\t// The element is either the `container`'s ancestor,\r\n\t\t\t\t// Or is the `container` itself.\r\n\t\t\t\t// (`a.contains(b)` includes the `a === b` case).\r\n\t\t\t\t// So the `resize` event will affect the `container`'s dimensions.\r\n\t\t\t} else {\r\n\t\t\t\t// The element is either inside the `container`,\r\n\t\t\t\t// Or is in a separate tree.\r\n\t\t\t\t// So the `resize` event won't affect the `container`'s dimensions.\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t\tonResize()\r\n\t}\r\n\twindow.addEventListener('resize', onResizeListener)\r\n\treturn () => window.removeEventListener('resize', onResizeListener)\r\n}"],"file":"ScrollableContainer.js"}
1
+ {"version":3,"sources":["../../source/DOM/ScrollableContainer.js"],"names":["ScrollableContainer","element","getItemsContainerElement","scrollTop","scrollY","scrollTo","offsetWidth","offsetHeight","scrollableContainerTop","getBoundingClientRect","top","scrollableContainerBorderTopWidth","clientTop","itemsContainerTop","getScrollY","onScroll","addEventListener","removeEventListener","onResize","unobserve","ResizeObserver","resizeObserver","entries","entry","observe","unlistenGlobalResize","addGlobalResizeListener","itemsContainerElement","ScrollableWindowContainer","window","pageYOffset","innerWidth","innerHeight","borderTopWidth","document","body","onResizeListener","fullscreenElement","contains"],"mappings":"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","sourcesContent":["export default class ScrollableContainer {\r\n\t/**\r\n\t * Constructs a new \"scrollable container\" from an element.\r\n\t * @param {Element} scrollableContainer\r\n\t * @param {func} getItemsContainerElement — Returns items \"container\" element.\r\n\t */\r\n\tconstructor(element, getItemsContainerElement) {\r\n\t\tthis.element = element\r\n\t\tthis.getItemsContainerElement = getItemsContainerElement\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the current scroll position.\r\n\t * @return {number}\r\n\t */\r\n\tgetScrollY() {\r\n\t\treturn this.element.scrollTop\r\n\t}\r\n\r\n\t/**\r\n\t * Scrolls to a specific position.\r\n\t * @param {number} scrollY\r\n\t */\r\n\tscrollToY(scrollY) {\r\n\t\t// IE 11 doesn't seem to have a `.scrollTo()` method.\r\n\t\t// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/10\r\n\t\t// https://stackoverflow.com/questions/39908825/window-scrollto-is-not-working-in-internet-explorer-11\r\n\t\tif (this.element.scrollTo) {\r\n\t\t\tthis.element.scrollTo(0, scrollY)\r\n\t\t} else {\r\n\t\t\tthis.element.scrollTop = scrollY\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Returns \"scrollable container\" width,\r\n\t * i.e. the available width for its content.\r\n\t * @return {number}\r\n\t */\r\n\tgetWidth() {\r\n\t\treturn this.element.offsetWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the \"scrollable container\" itself.\r\n\t * Not to be confused with the height of \"scrollable container\"'s content.\r\n\t * @return {number}\r\n\t */\r\n\tgetHeight() {\r\n\t\t// if (!this.element && !precise) {\r\n\t\t// \treturn getScreenHeight()\r\n\t\t// }\r\n\t\treturn this.element.offsetHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a \"top offset\" of an items container element\r\n\t * relative to the \"scrollable container\"'s top edge.\r\n\t * @return {number}\r\n\t */\r\n\tgetItemsContainerTopOffset() {\r\n\t\tconst scrollableContainerTop = this.element.getBoundingClientRect().top\r\n\t\tconst scrollableContainerBorderTopWidth = this.element.clientTop\r\n\t\tconst itemsContainerTop = this.getItemsContainerElement().getBoundingClientRect().top\r\n\t\treturn (itemsContainerTop - scrollableContainerTop) + this.getScrollY() - scrollableContainerBorderTopWidth\r\n\t}\r\n\r\n\t// isVisible() {\r\n\t// \tconst { top, bottom } = this.element.getBoundingClientRect()\r\n\t// \treturn bottom > 0 && top < getScreenHeight()\r\n\t// }\r\n\r\n\t/**\r\n\t * Adds a \"scroll\" event listener to the \"scrollable container\".\r\n\t * @param {onScroll} Should be called whenever the scroll position inside the \"scrollable container\" (potentially) changes.\r\n\t * @return {function} Returns a function that stops listening.\r\n\t */\r\n\tonScroll(onScroll) {\r\n\t\tthis.element.addEventListener('scroll', onScroll)\r\n\t\treturn () => this.element.removeEventListener('scroll', onScroll)\r\n\t}\r\n\r\n\t/**\r\n\t * Adds a \"resize\" event listener to the \"scrollable container\".\r\n\t * @param {onResize} Should be called whenever the \"scrollable container\"'s width or height (potentially) changes.\r\n * @return {function} Returns a function that stops listening.\r\n\t */\r\n\tonResize(onResize) {\r\n\t\t// Watches \"scrollable container\"'s dimensions via a `ResizeObserver`.\r\n\t\t// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver\r\n\t\t// https://web.dev/resize-observer/\r\n\t\tlet unobserve\r\n\t\tif (typeof ResizeObserver !== 'undefined') {\r\n\t\t\tconst resizeObserver = new ResizeObserver((entries) => {\r\n\t\t\t\t// \"one entry per observed element\".\r\n\t\t\t\t// https://web.dev/resize-observer/\r\n\t\t\t\t// `entry.target === this.element`.\r\n\t\t\t\tconst entry = entries[0]\r\n\t\t\t\t// // If `entry.contentBoxSize` property is supported by the web browser.\r\n\t\t\t\t// if (entry.contentBoxSize) {\r\n\t\t\t\t// \t// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/contentBoxSize\r\n\t\t\t\t// \tconst width = entry.contentBoxSize.inlineSize\r\n\t\t\t\t// \tconst height = entry.contentBoxSize.blockSize\r\n\t\t\t\t// }\r\n\t\t\t\tonResize()\r\n\t\t\t})\r\n\t\t\tresizeObserver.observe(this.element)\r\n\t\t\tunobserve = () => resizeObserver.unobserve(this.element)\r\n\t\t}\r\n\t\t// I guess, if window is resized, `onResize()` will be triggered twice:\r\n\t\t// once for window resize, and once for the scrollable container resize.\r\n\t\t// But `onResize()` also has an internal check: if the container size\r\n\t\t// hasn't changed since the previous time `onResize()` has been called,\r\n\t\t// then `onResize()` doesn't do anything, so, I guess, there shouldn't be\r\n\t\t// any \"performance implications\" of running the listener twice in such case.\r\n\t\tconst unlistenGlobalResize = addGlobalResizeListener(onResize, {\r\n\t\t\titemsContainerElement: this.getItemsContainerElement()\r\n\t\t})\r\n\t\treturn () => {\r\n\t\t\tif (unobserve) {\r\n\t\t\t\tunobserve()\r\n\t\t\t}\r\n\t\t\tunlistenGlobalResize()\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport class ScrollableWindowContainer extends ScrollableContainer {\r\n\t/**\r\n\t * Constructs a new window \"scrollable container\".\r\n\t * @param {func} getItemsContainerElement — Returns items \"container\" element.\r\n\t */\r\n\tconstructor(getItemsContainerElement) {\r\n\t\tsuper(window, getItemsContainerElement)\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the current scroll position.\r\n\t * @return {number}\r\n\t */\r\n\tgetScrollY() {\r\n\t\t// `window.scrollY` is not supported by Internet Explorer.\r\n\t\treturn window.pageYOffset\r\n\t}\r\n\r\n\t/**\r\n\t * Returns \"scrollable container\" width,\r\n\t * i.e. the available width for its content.\r\n\t * @return {number}\r\n\t */\r\n\tgetWidth() {\r\n\t\t// https://javascript.info/size-and-scroll-window\r\n\t\t// `<!DOCTYPE html>` may be required in order for this to work correctly.\r\n\t\t// Includes scrollbar (if any).\r\n\t\t// Correctly reflects page zoom in iOS Safari.\r\n\t\t// (scales screen width accordingly).\r\n\t\t// But, includes scrollbar (if any).\r\n\t\treturn window.innerWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Returns the height of the \"scrollable container\" itself.\r\n\t * Not to be confused with the height of \"scrollable container\"'s content.\r\n\t * @return {number}\r\n\t */\r\n\tgetHeight() {\r\n\t\t// https://javascript.info/size-and-scroll-window\r\n\t\t// `<!DOCTYPE html>` is required in order for this to work correctly.\r\n\t\t// Without it, the returned height would be the height of the entire document.\r\n\t\t// Includes scrollbar (if any).\r\n\t\t// Supports iOS Safari's dynamically shown/hidden\r\n\t\t// top URL bar and bottom actions bar.\r\n\t\t// https://codesandbox.io/s/elegant-fog-iddrh\r\n\t\t// Tested in IE 11.\r\n\t\t// It also correctly reflects page zoom in iOS Safari.\r\n\t\t// (scales screen height accordingly).\r\n\t\t// But, includes scrollbar (if any).\r\n\t\treturn window.innerHeight\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a \"top offset\" of an items container element\r\n\t * relative to the \"scrollable container\"'s top edge.\r\n\t * @return {number}\r\n\t */\r\n\tgetItemsContainerTopOffset() {\r\n\t\tconst borderTopWidth = document.clientTop || document.body.clientTop || 0\r\n\t\treturn this.getItemsContainerElement().getBoundingClientRect().top + this.getScrollY() - borderTopWidth\r\n\t}\r\n\r\n\t/**\r\n\t * Adds a \"resize\" event listener to the \"scrollable container\".\r\n\t * @param {onScroll} Should be called whenever the \"scrollable container\"'s width or height (potentially) changes.\r\n\t * @return {function} Returns a function that stops listening.\r\n\t */\r\n\tonResize(onResize) {\r\n\t\treturn addGlobalResizeListener(onResize, {\r\n\t\t\titemsContainerElement: this.getItemsContainerElement()\r\n\t\t})\r\n\t}\r\n\r\n\t// isVisible() {\r\n\t// \treturn true\r\n\t// }\r\n}\r\n\r\n/**\r\n * Adds a \"resize\" event listener to the `window`.\r\n * @param {onResize} Should be called whenever the \"scrollable container\"'s width or height (potentially) changes.\r\n * @param {Element} options.itemsContainerElement — The items \"container\" element, which is not the same as the \"scrollable container\" element. For example, \"scrollable container\" could be resized while the list element retaining its size. One such example is a user entering fullscreen mode on an HTML5 `<video/>` element: in that case, a \"resize\" event is triggered on a window, and window dimensions change to the user's screen size, but such \"resize\" event can be ignored because the list isn't visible until the user exits fullscreen mode.\r\n * @return {function} Returns a function that stops listening.\r\n */\r\nfunction addGlobalResizeListener(onResize, { itemsContainerElement }) {\r\n\tconst onResizeListener = () => {\r\n\t\t// By default, `VirtualScroller` always performs a re-layout\r\n\t\t// on window `resize` event. But browsers (Chrome, Firefox)\r\n\t\t// [trigger](https://developer.mozilla.org/en-US/docs/Web/API/Window/fullScreen#Notes)\r\n\t\t// window `resize` event also when a user switches into fullscreen mode:\r\n\t\t// for example, when a user is watching a video and double-clicks on it\r\n\t\t// to maximize it. And also when the user goes out of the fullscreen mode.\r\n\t\t// Each such fullscreen mode entering/exiting will trigger window `resize`\r\n\t\t// event that will it turn trigger a re-layout of `VirtualScroller`,\r\n\t\t// resulting in bad user experience. To prevent that, such cases are filtered out.\r\n\t\t// Some other workaround:\r\n\t\t// https://stackoverflow.com/questions/23770449/embedded-youtube-video-fullscreen-or-causing-resize\r\n\t\tif (document.fullscreenElement) {\r\n\t\t\t// If the fullscreened element doesn't contain the list\r\n\t\t\t// (and is not the list itself), then the layout hasn't been affected,\r\n\t\t\t// so don't perform a re-layout.\r\n\t\t\t//\r\n\t\t\t// For example, suppose there's a list of items, and some item contains a video.\r\n\t\t\t// If, upon clicking such video, it plays inline, and the user enters\r\n\t\t\t// fullscreen mode while playing such inline video, then the layout won't be\r\n\t\t\t// affected, and so such `resize` event should be ignored: when\r\n\t\t\t// `document.fullscreenElement` is in a separate \"branch\" relative to the\r\n\t\t\t// `container`.\r\n\t\t\t//\r\n\t\t\t// Another scenario: suppose that upon click, the video doesn't play inline,\r\n\t\t\t// but instead a \"Slideshow\" component is open, with the video shown at the\r\n\t\t\t// center of the screen in an overlay. If then the user enters fullscreen mode,\r\n\t\t\t// the layout wouldn't be affected too, so such `resize` event should also be\r\n\t\t\t// ignored: when `document.fullscreenElement` is inside the `container`.\r\n\t\t\t//\r\n\t\t\tif (document.fullscreenElement.contains(itemsContainerElement)) {\r\n\t\t\t\t// The element is either the `container`'s ancestor,\r\n\t\t\t\t// Or is the `container` itself.\r\n\t\t\t\t// (`a.contains(b)` includes the `a === b` case).\r\n\t\t\t\t// So the `resize` event will affect the `container`'s dimensions.\r\n\t\t\t} else {\r\n\t\t\t\t// The element is either inside the `container`,\r\n\t\t\t\t// Or is in a separate tree.\r\n\t\t\t\t// So the `resize` event won't affect the `container`'s dimensions.\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t}\r\n\t\tonResize()\r\n\t}\r\n\twindow.addEventListener('resize', onResizeListener)\r\n\treturn () => window.removeEventListener('resize', onResizeListener)\r\n}"],"file":"ScrollableContainer.js"}