virtual-scroller 1.7.9 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CHANGELOG.md +14 -1
  2. package/README.md +139 -33
  3. package/babel.config.js +25 -0
  4. package/babel.js +5 -0
  5. package/bundle/index-bypass.html +1 -1
  6. package/bundle/index-dom.html +1 -1
  7. package/bundle/index-grid.html +1 -2
  8. package/bundle/index-scrollableContainer.html +1 -1
  9. package/bundle/index-tbody-scrollableContainer.html +2 -0
  10. package/bundle/index-tbody.html +2 -0
  11. package/bundle/virtual-scroller-dom.js +1 -1
  12. package/bundle/virtual-scroller-dom.js.map +1 -1
  13. package/bundle/virtual-scroller-react.js +1 -1
  14. package/bundle/virtual-scroller-react.js.map +1 -1
  15. package/bundle/virtual-scroller.js +1 -1
  16. package/bundle/virtual-scroller.js.map +1 -1
  17. package/commonjs/BeforeResize.js +319 -0
  18. package/commonjs/BeforeResize.js.map +1 -0
  19. package/commonjs/DOM/Engine.js +46 -0
  20. package/commonjs/DOM/Engine.js.map +1 -0
  21. package/commonjs/DOM/ItemsContainer.js +78 -0
  22. package/commonjs/DOM/ItemsContainer.js.map +1 -0
  23. package/commonjs/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +56 -35
  24. package/commonjs/DOM/ListTopOffsetWatcher.js.map +1 -0
  25. package/commonjs/DOM/ScrollableContainer.js +48 -80
  26. package/commonjs/DOM/ScrollableContainer.js.map +1 -1
  27. package/commonjs/DOM/VirtualScroller.js +20 -15
  28. package/commonjs/DOM/VirtualScroller.js.map +1 -1
  29. package/commonjs/DOM/tbody.js +2 -2
  30. package/commonjs/ItemHeights.js +13 -20
  31. package/commonjs/ItemHeights.js.map +1 -1
  32. package/commonjs/Layout.js +588 -215
  33. package/commonjs/Layout.js.map +1 -1
  34. package/commonjs/Layout.test.js +191 -0
  35. package/commonjs/Layout.test.js.map +1 -0
  36. package/commonjs/ListHeightChangeWatcher.js +126 -0
  37. package/commonjs/ListHeightChangeWatcher.js.map +1 -0
  38. package/commonjs/Resize.js +22 -21
  39. package/commonjs/Resize.js.map +1 -1
  40. package/commonjs/Scroll.js +148 -88
  41. package/commonjs/Scroll.js.map +1 -1
  42. package/commonjs/VirtualScroller.js +1269 -390
  43. package/commonjs/VirtualScroller.js.map +1 -1
  44. package/commonjs/getItemCoordinates.js.map +1 -1
  45. package/commonjs/getItemsDiff.js.map +1 -1
  46. package/commonjs/getVerticalSpacing.js +8 -8
  47. package/commonjs/getVerticalSpacing.js.map +1 -1
  48. package/commonjs/react/VirtualScroller.js +31 -37
  49. package/commonjs/react/VirtualScroller.js.map +1 -1
  50. package/commonjs/utility/debounce.js +26 -4
  51. package/commonjs/utility/debounce.js.map +1 -1
  52. package/commonjs/utility/debug.js +51 -12
  53. package/commonjs/utility/debug.js.map +1 -1
  54. package/commonjs/utility/getStateSnapshot.js +50 -0
  55. package/commonjs/utility/getStateSnapshot.js.map +1 -0
  56. package/commonjs/utility/px.js +1 -1
  57. package/commonjs/utility/px.js.map +1 -1
  58. package/commonjs/utility/px.test.js +14 -0
  59. package/commonjs/utility/px.test.js.map +1 -0
  60. package/commonjs/utility/shallowEqual.js +1 -1
  61. package/commonjs/utility/shallowEqual.js.map +1 -1
  62. package/commonjs/utility/throttle.js.map +1 -1
  63. package/dom/index.d.ts +23 -0
  64. package/index.d.ts +84 -0
  65. package/modules/BeforeResize.js +310 -0
  66. package/modules/BeforeResize.js.map +1 -0
  67. package/modules/DOM/Engine.js +27 -0
  68. package/modules/DOM/Engine.js.map +1 -0
  69. package/modules/DOM/ItemsContainer.js +71 -0
  70. package/modules/DOM/ItemsContainer.js.map +1 -0
  71. package/modules/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +57 -35
  72. package/modules/DOM/ListTopOffsetWatcher.js.map +1 -0
  73. package/modules/DOM/ScrollableContainer.js +47 -79
  74. package/modules/DOM/ScrollableContainer.js.map +1 -1
  75. package/modules/DOM/VirtualScroller.js +15 -14
  76. package/modules/DOM/VirtualScroller.js.map +1 -1
  77. package/modules/ItemHeights.js +8 -19
  78. package/modules/ItemHeights.js.map +1 -1
  79. package/modules/Layout.js +582 -213
  80. package/modules/Layout.js.map +1 -1
  81. package/modules/Layout.test.js +185 -0
  82. package/modules/Layout.test.js.map +1 -0
  83. package/modules/ListHeightChangeWatcher.js +119 -0
  84. package/modules/ListHeightChangeWatcher.js.map +1 -0
  85. package/modules/Resize.js +21 -20
  86. package/modules/Resize.js.map +1 -1
  87. package/modules/Scroll.js +148 -87
  88. package/modules/Scroll.js.map +1 -1
  89. package/modules/VirtualScroller.js +1263 -390
  90. package/modules/VirtualScroller.js.map +1 -1
  91. package/modules/getItemCoordinates.js.map +1 -1
  92. package/modules/getItemsDiff.js.map +1 -1
  93. package/modules/getVerticalSpacing.js +8 -8
  94. package/modules/getVerticalSpacing.js.map +1 -1
  95. package/modules/react/VirtualScroller.js +31 -37
  96. package/modules/react/VirtualScroller.js.map +1 -1
  97. package/modules/utility/debounce.js +26 -4
  98. package/modules/utility/debounce.js.map +1 -1
  99. package/modules/utility/debug.js +47 -10
  100. package/modules/utility/debug.js.map +1 -1
  101. package/modules/utility/getStateSnapshot.js +43 -0
  102. package/modules/utility/getStateSnapshot.js.map +1 -0
  103. package/modules/utility/px.js +1 -1
  104. package/modules/utility/px.js.map +1 -1
  105. package/modules/utility/px.test.js +9 -0
  106. package/modules/utility/px.test.js.map +1 -0
  107. package/modules/utility/shallowEqual.js +1 -1
  108. package/modules/utility/shallowEqual.js.map +1 -1
  109. package/modules/utility/throttle.js.map +1 -1
  110. package/package.json +24 -22
  111. package/react/index.d.ts +27 -0
  112. package/source/BeforeResize.js +317 -0
  113. package/source/DOM/Engine.js +32 -0
  114. package/source/DOM/ItemsContainer.js +48 -0
  115. package/source/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +48 -22
  116. package/source/DOM/ScrollableContainer.js +31 -55
  117. package/source/DOM/VirtualScroller.js +6 -7
  118. package/source/ItemHeights.js +10 -15
  119. package/source/Layout.js +626 -252
  120. package/source/Layout.test.js +171 -0
  121. package/source/ListHeightChangeWatcher.js +94 -0
  122. package/source/Resize.js +23 -15
  123. package/source/Scroll.js +139 -78
  124. package/source/VirtualScroller.js +1240 -286
  125. package/source/getVerticalSpacing.js +7 -7
  126. package/source/react/VirtualScroller.js +2 -18
  127. package/source/utility/debounce.js +20 -3
  128. package/source/utility/debug.js +34 -3
  129. package/source/utility/getStateSnapshot.js +36 -0
  130. package/source/utility/px.js +1 -1
  131. package/source/utility/px.test.js +9 -0
  132. package/website/index-bypass.html +195 -0
  133. package/website/index-grid.html +0 -1
  134. package/website/index-scrollableContainer.html +208 -0
  135. package/website/index-tbody-scrollableContainer.html +68 -0
  136. package/website/index-tbody.html +55 -0
  137. package/commonjs/DOM/RenderingEngine.js +0 -33
  138. package/commonjs/DOM/RenderingEngine.js.map +0 -1
  139. package/commonjs/DOM/Screen.js +0 -87
  140. package/commonjs/DOM/Screen.js.map +0 -1
  141. package/commonjs/DOM/WaitForStylesToLoad.js.map +0 -1
  142. package/commonjs/RestoreScroll.js +0 -118
  143. package/commonjs/RestoreScroll.js.map +0 -1
  144. package/modules/DOM/RenderingEngine.js +0 -19
  145. package/modules/DOM/RenderingEngine.js.map +0 -1
  146. package/modules/DOM/Screen.js +0 -80
  147. package/modules/DOM/Screen.js.map +0 -1
  148. package/modules/DOM/WaitForStylesToLoad.js.map +0 -1
  149. package/modules/RestoreScroll.js +0 -111
  150. package/modules/RestoreScroll.js.map +0 -1
  151. package/source/DOM/RenderingEngine.js +0 -22
  152. package/source/DOM/Screen.js +0 -51
  153. package/source/RestoreScroll.js +0 -86
@@ -1 +1 @@
1
- {"version":3,"sources":["../../source/utility/px.js"],"names":["px","number","toFixed"],"mappings":"AAAA;;;;;;;AAOA,eAAe,SAASA,EAAT,CAAYC,MAAZ,EAAoB;AAClC;AACC,SAAOA,MAAM,CAACC,OAAP,CAAe,CAAf,IAAoB,IAA3B;AACD","sourcesContent":["/**\r\n * Rounds coordinates upto 4th decimal place (after dot) and appends \"px\".\r\n * Small numbers could be printed as `\"1.2345e-50\"` unless rounded:\r\n * that would be invalid \"px\" value in CSS.\r\n * @param {number}\r\n * @return {string}\r\n */\r\nexport default function px(number) {\r\n\t// Fractional pixels are used on \"retina\" screens.\r\n return number.toFixed(2) + 'px'\r\n}"],"file":"px.js"}
1
+ {"version":3,"sources":["../../source/utility/px.js"],"names":["px","number","toFixed"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,SAASA,EAAT,CAAYC,MAAZ,EAAoB;AAClC;AACC,SAAO,CAACA,MAAM,GAAG,CAAT,KAAe,CAAf,GAAmBA,MAAnB,GAA4BA,MAAM,CAACC,OAAP,CAAe,CAAf,CAA7B,IAAkD,IAAzD;AACD","sourcesContent":["/**\r\n * Rounds coordinates upto 4th decimal place (after dot) and appends \"px\".\r\n * Small numbers could be printed as `\"1.2345e-50\"` unless rounded:\r\n * that would be invalid \"px\" value in CSS.\r\n * @param {number}\r\n * @return {string}\r\n */\r\nexport default function px(number) {\r\n\t// Fractional pixels are used on \"retina\" screens.\r\n return (number % 1 === 0 ? number : number.toFixed(2)) + 'px'\r\n}"],"file":"px.js"}
@@ -0,0 +1,9 @@
1
+ import px from './px';
2
+ describe('utility/px', function () {
3
+ it('should truncate px values', function () {
4
+ px(0).should.equal('0px');
5
+ px(1).should.equal('1px');
6
+ px(1.2345).should.equal('1.23px');
7
+ });
8
+ });
9
+ //# sourceMappingURL=px.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../source/utility/px.test.js"],"names":["px","describe","it","should","equal"],"mappings":"AAAA,OAAOA,EAAP,MAAe,MAAf;AAEAC,QAAQ,CAAC,YAAD,EAAe,YAAW;AACjCC,EAAAA,EAAE,CAAC,2BAAD,EAA8B,YAAW;AAC1CF,IAAAA,EAAE,CAAC,CAAD,CAAF,CAAMG,MAAN,CAAaC,KAAb,CAAmB,KAAnB;AACAJ,IAAAA,EAAE,CAAC,CAAD,CAAF,CAAMG,MAAN,CAAaC,KAAb,CAAmB,KAAnB;AACAJ,IAAAA,EAAE,CAAC,MAAD,CAAF,CAAWG,MAAX,CAAkBC,KAAlB,CAAwB,QAAxB;AACA,GAJC,CAAF;AAKA,CANO,CAAR","sourcesContent":["import px from './px'\r\n\r\ndescribe('utility/px', function() {\r\n\tit('should truncate px values', function() {\r\n\t\tpx(0).should.equal('0px')\r\n\t\tpx(1).should.equal('1px')\r\n\t\tpx(1.2345).should.equal('1.23px')\r\n\t})\r\n})"],"file":"px.test.js"}
@@ -15,7 +15,7 @@
15
15
  /*eslint-disable no-self-compare */
16
16
  'use strict';
17
17
 
18
- 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); }
18
+ 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); }
19
19
 
20
20
  var hasOwnProperty = Object.prototype.hasOwnProperty;
21
21
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../source/utility/shallowEqual.js"],"names":["hasOwnProperty","Object","prototype","is","x","y","shallowEqual","objA","objB","keysA","keys","keysB","length","i","call"],"mappings":"AAAA;AACA;;AAEA;;;;;;;;;;;AAWA;AAEA;;;;AAEA,IAAMA,cAAc,GAAGC,MAAM,CAACC,SAAP,CAAiBF,cAAxC;AAEA;;;;;AAIA,SAASG,EAAT,CAAYC,CAAZ,EAAeC,CAAf,EAAkB;AAChB;AACA,MAAID,CAAC,KAAKC,CAAV,EAAa;AAAE;AACb;AACA;AACA,WAAOD,CAAC,KAAK,CAAN,IAAWC,CAAC,KAAK,CAAjB,IAAsB,IAAID,CAAJ,KAAU,IAAIC,CAA3C;AACD,GAJD,MAIO;AACL;AACA,WAAOD,CAAC,KAAKA,CAAN,IAAWC,CAAC,KAAKA,CAAxB;AACD;AACF;AAED;;;;;;;AAKA,eAAe,SAASC,YAAT,CAAsBC,IAAtB,EAA4BC,IAA5B,EAAkC;AAC/C,MAAIL,EAAE,CAACI,IAAD,EAAOC,IAAP,CAAN,EAAoB;AAClB,WAAO,IAAP;AACD;;AAED,MAAI,QAAOD,IAAP,MAAgB,QAAhB,IAA4BA,IAAI,KAAK,IAArC,IACA,QAAOC,IAAP,MAAgB,QADhB,IAC4BA,IAAI,KAAK,IADzC,EAC+C;AAC7C,WAAO,KAAP;AACD;;AAED,MAAMC,KAAK,GAAGR,MAAM,CAACS,IAAP,CAAYH,IAAZ,CAAd;AACA,MAAMI,KAAK,GAAGV,MAAM,CAACS,IAAP,CAAYF,IAAZ,CAAd;;AAEA,MAAIC,KAAK,CAACG,MAAN,KAAiBD,KAAK,CAACC,MAA3B,EAAmC;AACjC,WAAO,KAAP;AACD,GAf8C,CAiB/C;;;AACA,OAAK,IAAIC,CAAC,GAAG,CAAb,EAAgBA,CAAC,GAAGJ,KAAK,CAACG,MAA1B,EAAkCC,CAAC,EAAnC,EAAuC;AACrC,QACE,CAACb,cAAc,CAACc,IAAf,CAAoBN,IAApB,EAA0BC,KAAK,CAACI,CAAD,CAA/B,CAAD,IACA,CAACV,EAAE,CAACI,IAAI,CAACE,KAAK,CAACI,CAAD,CAAN,CAAL,EAAiBL,IAAI,CAACC,KAAK,CAACI,CAAD,CAAN,CAArB,CAFL,EAGE;AACA,aAAO,KAAP;AACD;AACF;;AAED,SAAO,IAAP;AACD","sourcesContent":["// https://github.com/lodash/lodash/issues/2340\r\n// https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js\r\n\r\n/**\r\n * Copyright (c) 2013-present, Facebook, Inc.\r\n *\r\n * This source code is licensed under the MIT license found in the\r\n * LICENSE file in the root directory of this source tree.\r\n *\r\n * @providesModule shallowEqual\r\n * @typechecks\r\n * @flow\r\n */\r\n\r\n/*eslint-disable no-self-compare */\r\n\r\n'use strict';\r\n\r\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\r\n\r\n/**\r\n * inlined Object.is polyfill to avoid requiring consumers ship their own\r\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is\r\n */\r\nfunction is(x, y) {\r\n // SameValue algorithm\r\n if (x === y) { // Steps 1-5, 7-10\r\n // Steps 6.b-6.e: +0 != -0\r\n // Added the nonzero y check to make Flow happy, but it is redundant\r\n return x !== 0 || y !== 0 || 1 / x === 1 / y;\r\n } else {\r\n // Step 6.a: NaN == NaN\r\n return x !== x && y !== y;\r\n }\r\n}\r\n\r\n/**\r\n * Performs equality by iterating through keys on an object and returning false\r\n * when any key has values which are not strictly equal between the arguments.\r\n * Returns true when the values of all keys are strictly equal.\r\n */\r\nexport default function shallowEqual(objA, objB) {\r\n if (is(objA, objB)) {\r\n return true;\r\n }\r\n\r\n if (typeof objA !== 'object' || objA === null ||\r\n typeof objB !== 'object' || objB === null) {\r\n return false;\r\n }\r\n\r\n const keysA = Object.keys(objA);\r\n const keysB = Object.keys(objB);\r\n\r\n if (keysA.length !== keysB.length) {\r\n return false;\r\n }\r\n\r\n // Test for A's keys different from B.\r\n for (let i = 0; i < keysA.length; i++) {\r\n if (\r\n !hasOwnProperty.call(objB, keysA[i]) ||\r\n !is(objA[keysA[i]], objB[keysA[i]])\r\n ) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}"],"file":"shallowEqual.js"}
1
+ {"version":3,"sources":["../../source/utility/shallowEqual.js"],"names":["hasOwnProperty","Object","prototype","is","x","y","shallowEqual","objA","objB","keysA","keys","keysB","length","i","call"],"mappings":"AAAA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AAEA;;;;AAEA,IAAMA,cAAc,GAAGC,MAAM,CAACC,SAAP,CAAiBF,cAAxC;AAEA;AACA;AACA;AACA;;AACA,SAASG,EAAT,CAAYC,CAAZ,EAAeC,CAAf,EAAkB;AAChB;AACA,MAAID,CAAC,KAAKC,CAAV,EAAa;AAAE;AACb;AACA;AACA,WAAOD,CAAC,KAAK,CAAN,IAAWC,CAAC,KAAK,CAAjB,IAAsB,IAAID,CAAJ,KAAU,IAAIC,CAA3C;AACD,GAJD,MAIO;AACL;AACA,WAAOD,CAAC,KAAKA,CAAN,IAAWC,CAAC,KAAKA,CAAxB;AACD;AACF;AAED;AACA;AACA;AACA;AACA;;;AACA,eAAe,SAASC,YAAT,CAAsBC,IAAtB,EAA4BC,IAA5B,EAAkC;AAC/C,MAAIL,EAAE,CAACI,IAAD,EAAOC,IAAP,CAAN,EAAoB;AAClB,WAAO,IAAP;AACD;;AAED,MAAI,QAAOD,IAAP,MAAgB,QAAhB,IAA4BA,IAAI,KAAK,IAArC,IACA,QAAOC,IAAP,MAAgB,QADhB,IAC4BA,IAAI,KAAK,IADzC,EAC+C;AAC7C,WAAO,KAAP;AACD;;AAED,MAAMC,KAAK,GAAGR,MAAM,CAACS,IAAP,CAAYH,IAAZ,CAAd;AACA,MAAMI,KAAK,GAAGV,MAAM,CAACS,IAAP,CAAYF,IAAZ,CAAd;;AAEA,MAAIC,KAAK,CAACG,MAAN,KAAiBD,KAAK,CAACC,MAA3B,EAAmC;AACjC,WAAO,KAAP;AACD,GAf8C,CAiB/C;;;AACA,OAAK,IAAIC,CAAC,GAAG,CAAb,EAAgBA,CAAC,GAAGJ,KAAK,CAACG,MAA1B,EAAkCC,CAAC,EAAnC,EAAuC;AACrC,QACE,CAACb,cAAc,CAACc,IAAf,CAAoBN,IAApB,EAA0BC,KAAK,CAACI,CAAD,CAA/B,CAAD,IACA,CAACV,EAAE,CAACI,IAAI,CAACE,KAAK,CAACI,CAAD,CAAN,CAAL,EAAiBL,IAAI,CAACC,KAAK,CAACI,CAAD,CAAN,CAArB,CAFL,EAGE;AACA,aAAO,KAAP;AACD;AACF;;AAED,SAAO,IAAP;AACD","sourcesContent":["// https://github.com/lodash/lodash/issues/2340\r\n// https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js\r\n\r\n/**\r\n * Copyright (c) 2013-present, Facebook, Inc.\r\n *\r\n * This source code is licensed under the MIT license found in the\r\n * LICENSE file in the root directory of this source tree.\r\n *\r\n * @providesModule shallowEqual\r\n * @typechecks\r\n * @flow\r\n */\r\n\r\n/*eslint-disable no-self-compare */\r\n\r\n'use strict';\r\n\r\nconst hasOwnProperty = Object.prototype.hasOwnProperty;\r\n\r\n/**\r\n * inlined Object.is polyfill to avoid requiring consumers ship their own\r\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is\r\n */\r\nfunction is(x, y) {\r\n // SameValue algorithm\r\n if (x === y) { // Steps 1-5, 7-10\r\n // Steps 6.b-6.e: +0 != -0\r\n // Added the nonzero y check to make Flow happy, but it is redundant\r\n return x !== 0 || y !== 0 || 1 / x === 1 / y;\r\n } else {\r\n // Step 6.a: NaN == NaN\r\n return x !== x && y !== y;\r\n }\r\n}\r\n\r\n/**\r\n * Performs equality by iterating through keys on an object and returning false\r\n * when any key has values which are not strictly equal between the arguments.\r\n * Returns true when the values of all keys are strictly equal.\r\n */\r\nexport default function shallowEqual(objA, objB) {\r\n if (is(objA, objB)) {\r\n return true;\r\n }\r\n\r\n if (typeof objA !== 'object' || objA === null ||\r\n typeof objB !== 'object' || objB === null) {\r\n return false;\r\n }\r\n\r\n const keysA = Object.keys(objA);\r\n const keysB = Object.keys(objB);\r\n\r\n if (keysA.length !== keysB.length) {\r\n return false;\r\n }\r\n\r\n // Test for A's keys different from B.\r\n for (let i = 0; i < keysA.length; i++) {\r\n if (\r\n !hasOwnProperty.call(objB, keysA[i]) ||\r\n !is(objA[keysA[i]], objB[keysA[i]])\r\n ) {\r\n return false;\r\n }\r\n }\r\n\r\n return true;\r\n}"],"file":"shallowEqual.js"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../source/utility/throttle.js"],"names":["setTimeout","clearTimeout","throttle","func","interval","timeout","executedAt","scheduled","undefined","Date","now","remaining"],"mappings":"AAAA;AACA;AACA;AACA;AACA,SAASA,UAAT,EAAqBC,YAArB,QAAyC,iCAAzC;AAEA;;;;;;;AAMA,eAAe,SAASC,QAAT,CAAkBC,IAAlB,EAAwBC,QAAxB,EAAkC;AAChD,MAAIC,OAAJ;AACA,MAAIC,UAAU,GAAG,CAAjB;;AACA,MAAIC,SAAS,GAAG,SAAZA,SAAY,GAAW;AAC1BF,IAAAA,OAAO,GAAGG,SAAV;AACAF,IAAAA,UAAU,GAAGG,IAAI,CAACC,GAAL,EAAb;AACAP,IAAAA,IAAI;AACJ,GAJD;;AAKA,SAAO,YAAW;AACjB,QAAMO,GAAG,GAAGD,IAAI,CAACC,GAAL,EAAZ;AACA,QAAMC,SAAS,GAAGP,QAAQ,IAAIM,GAAG,GAAGJ,UAAV,CAA1B;;AACA,QAAIK,SAAS,IAAI,CAAjB,EAAoB;AACnB,UAAIN,OAAJ,EAAa;AACZJ,QAAAA,YAAY,CAACI,OAAD,CAAZ;AACAA,QAAAA,OAAO,GAAGG,SAAV;AACA;;AACDF,MAAAA,UAAU,GAAGI,GAAb;AACAP,MAAAA,IAAI;AACJ,KAPD,MAOO,IAAI,CAACE,OAAL,EAAc;AACpBA,MAAAA,OAAO,GAAGL,UAAU,CAACO,SAAD,EAAYI,SAAZ,CAApB;AACA;AACD,GAbD;AAcA","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/**\r\n * Same as `lodash`'s `throttle()` for functions with no arguments.\r\n * @param {function} func\r\n * @param {number} interval\r\n * @return {function}\r\n */\r\nexport default function throttle(func, interval) {\r\n\tlet timeout\r\n\tlet executedAt = 0\r\n\tlet scheduled = function() {\r\n\t\ttimeout = undefined\r\n\t\texecutedAt = Date.now()\r\n\t\tfunc()\r\n\t}\r\n\treturn function() {\r\n\t\tconst now = Date.now()\r\n\t\tconst remaining = interval - (now - executedAt)\r\n\t\tif (remaining <= 0) {\r\n\t\t\tif (timeout) {\r\n\t\t\t\tclearTimeout(timeout)\r\n\t\t\t\ttimeout = undefined\r\n\t\t\t}\r\n\t\t\texecutedAt = now\r\n\t\t\tfunc()\r\n\t\t} else if (!timeout) {\r\n\t\t\ttimeout = setTimeout(scheduled, remaining)\r\n\t\t}\r\n\t}\r\n}"],"file":"throttle.js"}
1
+ {"version":3,"sources":["../../source/utility/throttle.js"],"names":["setTimeout","clearTimeout","throttle","func","interval","timeout","executedAt","scheduled","undefined","Date","now","remaining"],"mappings":"AAAA;AACA;AACA;AACA;AACA,SAASA,UAAT,EAAqBC,YAArB,QAAyC,iCAAzC;AAEA;AACA;AACA;AACA;AACA;AACA;;AACA,eAAe,SAASC,QAAT,CAAkBC,IAAlB,EAAwBC,QAAxB,EAAkC;AAChD,MAAIC,OAAJ;AACA,MAAIC,UAAU,GAAG,CAAjB;;AACA,MAAIC,SAAS,GAAG,SAAZA,SAAY,GAAW;AAC1BF,IAAAA,OAAO,GAAGG,SAAV;AACAF,IAAAA,UAAU,GAAGG,IAAI,CAACC,GAAL,EAAb;AACAP,IAAAA,IAAI;AACJ,GAJD;;AAKA,SAAO,YAAW;AACjB,QAAMO,GAAG,GAAGD,IAAI,CAACC,GAAL,EAAZ;AACA,QAAMC,SAAS,GAAGP,QAAQ,IAAIM,GAAG,GAAGJ,UAAV,CAA1B;;AACA,QAAIK,SAAS,IAAI,CAAjB,EAAoB;AACnB,UAAIN,OAAJ,EAAa;AACZJ,QAAAA,YAAY,CAACI,OAAD,CAAZ;AACAA,QAAAA,OAAO,GAAGG,SAAV;AACA;;AACDF,MAAAA,UAAU,GAAGI,GAAb;AACAP,MAAAA,IAAI;AACJ,KAPD,MAOO,IAAI,CAACE,OAAL,EAAc;AACpBA,MAAAA,OAAO,GAAGL,UAAU,CAACO,SAAD,EAAYI,SAAZ,CAApB;AACA;AACD,GAbD;AAcA","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/**\r\n * Same as `lodash`'s `throttle()` for functions with no arguments.\r\n * @param {function} func\r\n * @param {number} interval\r\n * @return {function}\r\n */\r\nexport default function throttle(func, interval) {\r\n\tlet timeout\r\n\tlet executedAt = 0\r\n\tlet scheduled = function() {\r\n\t\ttimeout = undefined\r\n\t\texecutedAt = Date.now()\r\n\t\tfunc()\r\n\t}\r\n\treturn function() {\r\n\t\tconst now = Date.now()\r\n\t\tconst remaining = interval - (now - executedAt)\r\n\t\tif (remaining <= 0) {\r\n\t\t\tif (timeout) {\r\n\t\t\t\tclearTimeout(timeout)\r\n\t\t\t\ttimeout = undefined\r\n\t\t\t}\r\n\t\t\texecutedAt = now\r\n\t\t\tfunc()\r\n\t\t} else if (!timeout) {\r\n\t\t\ttimeout = setTimeout(scheduled, remaining)\r\n\t\t}\r\n\t}\r\n}"],"file":"throttle.js"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "virtual-scroller",
3
- "version": "1.7.9",
3
+ "version": "1.8.0",
4
4
  "description": "A component for efficiently rendering large lists of variable height items",
5
5
  "main": "index.commonjs.js",
6
6
  "module": "index.js",
@@ -11,39 +11,41 @@
11
11
  "request-animation-frame-timeout": "^1.0.0"
12
12
  },
13
13
  "devDependencies": {
14
- "@babel/cli": "^7.2.3",
15
- "@babel/core": "^7.0.0",
16
- "@babel/node": "^7.2.2",
17
- "@babel/plugin-proposal-class-properties": "^7.0.0",
18
- "@babel/plugin-proposal-object-rest-spread": "^7.2.0",
19
- "@babel/plugin-transform-destructuring": "^7.1.2",
14
+ "@babel/cli": "^7.16.0",
15
+ "@babel/core": "^7.16.0",
16
+ "@babel/node": "^7.16.0",
17
+ "@babel/plugin-proposal-class-properties": "^7.16.0",
18
+ "@babel/plugin-proposal-object-rest-spread": "^7.16.0",
19
+ "@babel/plugin-transform-destructuring": "^7.16.0",
20
20
  "@babel/polyfill": "^7.0.0",
21
- "@babel/preset-env": "^7.0.0",
22
- "@babel/preset-react": "^7.0.0",
23
- "@babel/register": "^7.0.0",
24
- "autoprefixer": "^9.5.0",
21
+ "@babel/preset-env": "^7.16.4",
22
+ "@babel/preset-react": "^7.16.0",
23
+ "@babel/register": "^7.16.0",
25
24
  "babel-eslint": "^9.0.0",
26
- "babel-loader": "^8.0.1",
27
- "babel-plugin-istanbul": "^5.1.1",
28
- "cross-env": "^5.1.4",
29
- "npm-run-all": "^1.4.0",
30
- "postcss": "^7.0.14",
31
- "postcss-custom-properties": "^8.0.10",
32
- "rimraf": "^2.5.0",
33
- "rollup": "^1.9.0",
25
+ "babel-loader": "^8.2.3",
26
+ "babel-plugin-istanbul": "^6.1.1",
27
+ "chai": "^4.3.4",
28
+ "cross-env": "^7.0.3",
29
+ "mocha": "^9.1.3",
30
+ "npm-run-all": "^4.1.5",
31
+ "nyc": "^15.1.0",
32
+ "regenerator-runtime": "^0.13.9",
33
+ "rimraf": "^3.0.2",
34
+ "rollup": "^2.60.1",
34
35
  "rollup-plugin-commonjs": "^9.3.4",
35
36
  "rollup-plugin-node-resolve": "^4.2.3",
36
- "rollup-plugin-terser": "^4.0.4"
37
+ "rollup-plugin-terser": "^7.0.2"
37
38
  },
38
39
  "scripts": {
39
- "test": "echo \"Error: no test specified\"",
40
+ "test": "mocha --require ./babel.js --colors --bail --reporter spec --require ./test/setup.js \"source/**/*.test.js\" \"test/**/*.test.js\" --recursive",
41
+ "test-coverage": "cross-env NODE_ENV=test nyc mocha --require ./babel.js --bail --require ./test/setup.js \"source/**/*.test.js\" \"test/**/*.test.js\"",
40
42
  "coveralls": "nyc report --reporter=text-lcov | coveralls",
41
43
  "build:browser": "rollup --config rollup.config.js",
42
44
  "build:clean": "rimraf ./commonjs/**/* ./modules/**/*",
43
45
  "build:commonjs": "cross-env babel ./source --out-dir ./commonjs --source-maps --ignore *.test.js",
44
46
  "build:es6": "cross-env BABEL_ENV=es6 babel ./source --out-dir ./modules --source-maps --ignore *.test.js",
45
47
  "build": "npm-run-all build:clean build:commonjs build:es6 build:browser",
46
- "prepublish": "npm-run-all build test"
48
+ "prepublishOnly": "npm-run-all build test"
47
49
  },
48
50
  "repository": {
49
51
  "type": "git",
@@ -0,0 +1,27 @@
1
+ import { State, VirtualScrollerCommonOptionsWithoutInitialState } from '../index.d.ts';
2
+
3
+ export { State, ItemState } from '../index.d.ts';
4
+
5
+ import * as React from 'react';
6
+
7
+ interface Props<Item> extends VirtualScrollerCommonOptionsWithoutInitialState<HTMLElement, Item> {
8
+ as?: React.ReactType;
9
+ items: Item[];
10
+ itemComponent: React.ReactType;
11
+ itemComponentProps?: object;
12
+ className?: string;
13
+ initialCustomState?: object;
14
+ initialState?: State<Item>;
15
+ preserveScrollPositionOnPrependItems?: boolean;
16
+ onMount?: () => void;
17
+ }
18
+
19
+ type ReactVirtualScroller<Item> = React.ComponentClass<Props<Item>, State<Item>>
20
+
21
+ // TypeScript doesn't know how to receive `<Item>` "generic":
22
+ // declare const VirtualScroller<Item>: ReactVirtualScroller<Item>;
23
+ //
24
+ // Uses `<any>` instead:
25
+ declare const VirtualScroller: ReactVirtualScroller<any>;
26
+
27
+ export default VirtualScroller;
@@ -0,0 +1,317 @@
1
+ import log from './utility/debug'
2
+
3
+ export default class BeforeResize {
4
+ constructor({
5
+ getState,
6
+ getVerticalSpacing,
7
+ getColumnsCount
8
+ }) {
9
+ this.getState = getState
10
+ this.getVerticalSpacing = getVerticalSpacing
11
+ this.getColumnsCount = getColumnsCount
12
+ }
13
+
14
+ // Possibly clean up "before resize" property in state.
15
+ // "Before resize" state property is cleaned up when all "before resize" item heights
16
+ // have been re-measured in an asynchronous `this.setState({ beforeResize: undefined })` call.
17
+ // If `VirtualScroller` state was snapshotted externally before that `this.setState()` call
18
+ // has been applied, then "before resize" property might have not been cleaned up properly.
19
+ onInitialState(state) {
20
+ if (state) {
21
+ if (state.beforeResize) {
22
+ if (state.beforeResize.itemHeights.length === 0) {
23
+ state.beforeResize = undefined
24
+ }
25
+ }
26
+ if (state.beforeResize) {
27
+ this._includesBeforeResizeInState = true
28
+ }
29
+ }
30
+ }
31
+
32
+ // Cleans up "before resize" item heights and adjusts the scroll position accordingly.
33
+ //
34
+ // Hypothetically, it could also wait for the user to stop scrolling and only then
35
+ // adjust the scroll position. The rationale is that if `window.scrollTo()` is called
36
+ // while the user is scrolling, the user would occasionally experience "lost" mouse wheel
37
+ // events when scrolling with a mouse wheel.
38
+ //
39
+ // Seems like Twitter's website waits for the user to stop scrolling before applying
40
+ // the scroll position correction after a window resize. This library could do that too,
41
+ // but that would require rewriting "before items height" top padding calculation
42
+ // so that it doesn't re-calculate it on every re-render and instead does so incrementally,
43
+ // and then, when the user stops, it re-calculates it from scratch removing the error
44
+ // and adjusting the scroll position accordingly so that there's no "jump of content".
45
+ //
46
+ // But, seems like it works fine as it is and there's no need to rewrite anything.
47
+ //
48
+ cleanUpBeforeResizeItemHeights(prevState) {
49
+ const {
50
+ firstShownItemIndex,
51
+ lastShownItemIndex,
52
+ itemHeights,
53
+ beforeResize
54
+ } = this.getState()
55
+
56
+ // If there're "before resize" properties in `state`
57
+ // then it means that the corresponding items are waiting to be
58
+ // re-measured after container resize. Since the resize,
59
+ // some of those non-re-measured items might have just been measured,
60
+ // so see if that's true, and if it is, remove those now-obsolete
61
+ // "before resize" item heights and ajust the scroll position
62
+ // so that there's no "content jumping".
63
+
64
+ if (beforeResize) {
65
+ // If the user has scrolled up to reveal a previously hidden item
66
+ // that has not yet been re-measured after a previous resize.
67
+ if (firstShownItemIndex < beforeResize.itemHeights.length) {
68
+ log('~ Clean up "before resize" item heights and correct scroll position ~')
69
+
70
+ // Some of the "before" items have been un-hidden and re-measured.
71
+ // Un-hiding those items would result in a "jump of content"
72
+ // because "before resize" heights of those un-hidden items
73
+ // could (and most likely will) be different from the current ones,
74
+ // or because "before resize" columns count is different from
75
+ // the current one.
76
+ // To prevent a "jump of content", calculate the scroll position
77
+ // difference and adjust the scroll position.
78
+
79
+ // The height of the item rows that have transitioned
80
+ // from hidden to shown.
81
+ let newlyShownItemRowsHeight = 0
82
+
83
+ // Some of the `itemHeights` between the current `firstShownItemIndex` and
84
+ // the previous `firstShownItemIndex` could stay `undefined` if the user
85
+ // scrolled "abruptly": for example, by using a `window.scrollTo()` call.
86
+ // In that case, the items below the visible ones won't be rendered and measured.
87
+ // In such case, limit the items being iterated over to the current `lastShownItemIndex`
88
+ // rather than the previous `firstShownItemIndex`.
89
+ const prevFirstReMeasuredItemsRowIndex = Math.floor(beforeResize.itemHeights.length / this.getColumnsCount())
90
+ const newlyShownItemsToIndex = Math.min(
91
+ prevFirstReMeasuredItemsRowIndex * this.getColumnsCount() - 1,
92
+ lastShownItemIndex
93
+ )
94
+
95
+ let i = firstShownItemIndex
96
+ while (i <= newlyShownItemsToIndex) {
97
+ // Calculate newly shown row height.
98
+ let rowHeight = 0
99
+ let columnIndex = 0
100
+ while (columnIndex < this.getColumnsCount() && i <= newlyShownItemsToIndex) {
101
+ let itemHeight = itemHeights[i]
102
+ if (itemHeight === undefined) {
103
+ // `itemHeight` can only be `undefined` when not `beforeResize`.
104
+ // Use the current "average item height" as a substitute.
105
+ itemHeight = this.getAverageItemHeight()
106
+ }
107
+ rowHeight = Math.max(rowHeight, itemHeight)
108
+ i++
109
+ columnIndex++
110
+ }
111
+ // Append to the total "newly shown item rows height".
112
+ newlyShownItemRowsHeight += rowHeight
113
+ newlyShownItemRowsHeight += this.getVerticalSpacing()
114
+ }
115
+
116
+ // The height of the "before resize" item rows
117
+ // that will be "cleaned up" in this function call.
118
+ let cleanedUpBeforeResizeItemRowsHeight = 0
119
+
120
+ // Some of the `beforeResize` item rows might have been skipped if the user
121
+ // scrolled up "abruptly": for example, by using a `window.scrollTo()` call.
122
+ // In that case, the "before resize" items below the bottom border of the screen
123
+ // shouldn't be accounted for when calculating the scrollbar adjustment shift
124
+ // because items after `lastShownItemIndex` aren't participating in the calculation
125
+ // of `newlyShownItemRowsHeight`.
126
+ const maxParticipatingBeforeResizeItemsCount = Math.min(beforeResize.itemHeights.length, lastShownItemIndex + 1)
127
+ const participatingBeforeResizeItemRowsCount = Math.ceil(maxParticipatingBeforeResizeItemsCount / beforeResize.columnsCount)
128
+
129
+ const firstCleanedUpBeforeResizeItemsRowIndex = firstShownItemIndex === 0
130
+ ? 0
131
+ : Math.floor((firstShownItemIndex - 1) / beforeResize.columnsCount) + 1
132
+
133
+ let k = firstCleanedUpBeforeResizeItemsRowIndex
134
+ while (k < participatingBeforeResizeItemRowsCount) {
135
+ const rowHeight = beforeResize.itemHeights[k * beforeResize.columnsCount]
136
+ cleanedUpBeforeResizeItemRowsHeight += rowHeight
137
+ cleanedUpBeforeResizeItemRowsHeight += beforeResize.verticalSpacing
138
+ k++
139
+ }
140
+
141
+ // Schedule an asynchronous `this.setState()` call that will update
142
+ // `beforeResize` property of `state`. Ideally, it should be updated
143
+ // immediately, but since `this.setState()` calls are asynchronous,
144
+ // the code updates just the underlying `beforeResize.itemHeights`
145
+ // array immediately instead, which is still a hack but still a lesser one.
146
+ if (firstShownItemIndex === 0) {
147
+ log('Drop all "before resize" item heights')
148
+ } else {
149
+ const firstDroppedBeforeResizeItemIndex = firstShownItemIndex
150
+ const lastDroppedBeforeResizeItemIndex = beforeResize.itemHeights.length - 1
151
+ if (firstDroppedBeforeResizeItemIndex === lastDroppedBeforeResizeItemIndex) {
152
+ log('For item index', firstDroppedBeforeResizeItemIndex, '— drop "before resize" height', beforeResize.itemHeights[firstDroppedBeforeResizeItemIndex], )
153
+ } else {
154
+ log('For item indexes from', firstDroppedBeforeResizeItemIndex, 'to', lastDroppedBeforeResizeItemIndex, '— drop "before resize" heights', beforeResize.itemHeights.slice(firstDroppedBeforeResizeItemIndex))
155
+ }
156
+ }
157
+
158
+ // Immediately update `beforeResize.itemHeights`
159
+ // so that the component isn't left in an inconsistent state
160
+ // before a `this.setState()` call below is applied.
161
+ beforeResize.itemHeights.splice(
162
+ firstShownItemIndex,
163
+ beforeResize.itemHeights.length - firstShownItemIndex
164
+ )
165
+
166
+ // Return the "scroll by" amount that would correct the scroll position.
167
+ // Also return a state update.
168
+ return {
169
+ scrollBy: newlyShownItemRowsHeight - cleanedUpBeforeResizeItemRowsHeight,
170
+ beforeResize: firstShownItemIndex === 0 ? undefined : {
171
+ // Simply change the "reference" to `beforeResize` while leaving
172
+ // its contents unchanged. That simply indicates that it has been updated:
173
+ // `beforeResize.itemHeights` array length has been changed "directly".
174
+ ...beforeResize
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ // Snapshots "before resize" values in order to preserve the currently
182
+ // shown items' vertical position on screen so that there's no "content jumping".
183
+ //
184
+ // `newFirstShownItemIndex` is `> 0`.
185
+ //
186
+ snapshotBeforeResizeItemHeights({
187
+ firstShownItemIndex,
188
+ newFirstShownItemIndex,
189
+ newColumnsCount
190
+ }) {
191
+ const columnsCount = this.getColumnsCount()
192
+ const verticalSpacing = this.getVerticalSpacing()
193
+
194
+ this._includesBeforeResizeInState = true
195
+
196
+ const {
197
+ beforeResize: prevBeforeResize,
198
+ itemHeights
199
+ } = this.getState()
200
+
201
+ const prevBeforeResizeItemsCount = prevBeforeResize
202
+ ? prevBeforeResize.itemHeights.length
203
+ : 0
204
+
205
+ // If there already are "before resize" values in `state`
206
+ // then it means that those should be merged with the new ones.
207
+ //
208
+ // `beforeResize.itemHeights` could be empty in an edge case
209
+ // when there's a pending state update that sets `beforeResize`
210
+ // to `undefined`, and in that case empty `beforeResize.itemHeights`
211
+ // signals about that type of a situation.
212
+ //
213
+ if (prevBeforeResizeItemsCount > 0) {
214
+ // Because the "previous" before resize values might have been captured
215
+ // for a window width corresponding to a layout with a different columns count
216
+ // and different vertical spacing, re-calculate those item heights as if
217
+ // they corresponded to the current columns count and current vertical spacing,
218
+ // since "previous" and "new" before resize item heights are gonna be merged.
219
+ if (
220
+ prevBeforeResize.columnsCount !== columnsCount ||
221
+ prevBeforeResize.verticalSpacing !== verticalSpacing
222
+ ) {
223
+ let prevBeforeResizeBeforeItemsHeight = 0
224
+
225
+ const prevBeforeResizeItemRowsCount = Math.ceil(prevBeforeResizeItemsCount / prevBeforeResize.columnsCount)
226
+ let rowIndex = 0
227
+ while (rowIndex < prevBeforeResizeItemRowsCount) {
228
+ // Since all "before resize" item heights are equal within a row,
229
+ // the height of the first "before resize" item in a row is that row's height.
230
+ const rowHeight = prevBeforeResize.itemHeights[rowIndex * prevBeforeResize.columnsCount]
231
+ prevBeforeResizeBeforeItemsHeight += rowHeight
232
+ prevBeforeResizeBeforeItemsHeight += prevBeforeResize.verticalSpacing
233
+ rowIndex++
234
+ }
235
+
236
+ let newBeforeResizeAdditionalBeforeItemsHeight = 0
237
+ let i = firstShownItemIndex
238
+ while (i < newFirstShownItemIndex) {
239
+ let rowHeight = 0
240
+ let k = 0
241
+ while (k < columnsCount && i < newFirstShownItemIndex) {
242
+ rowHeight = Math.max(rowHeight, itemHeights[i])
243
+ k++
244
+ i++
245
+ }
246
+ newBeforeResizeAdditionalBeforeItemsHeight += rowHeight
247
+ newBeforeResizeAdditionalBeforeItemsHeight += verticalSpacing
248
+ }
249
+
250
+ const newBeforeResizeBeforeItemsHeight = prevBeforeResizeBeforeItemsHeight + newBeforeResizeAdditionalBeforeItemsHeight
251
+ const newBeforeResizeBeforeItemRowsCount = Math.ceil(newFirstShownItemIndex / columnsCount)
252
+
253
+ return new Array(newFirstShownItemIndex).fill(
254
+ // Re-calculate "before resize" item heights so that "previous" and "new" ones
255
+ // correspond to the same (new) columns count.
256
+ // Also don't occasionally set item heights to `< 0`.
257
+ Math.max(0, newBeforeResizeBeforeItemsHeight / newBeforeResizeBeforeItemRowsCount - verticalSpacing)
258
+ )
259
+ } else {
260
+ // Add new item heights to the previously snapshotted ones.
261
+ return prevBeforeResize.itemHeights.concat(
262
+ equalizeItemHeights(
263
+ itemHeights,
264
+ newFirstShownItemIndex,
265
+ columnsCount
266
+ ).slice(prevBeforeResize.itemHeights.length)
267
+ )
268
+ }
269
+ } else {
270
+ return equalizeItemHeights(
271
+ itemHeights,
272
+ newFirstShownItemIndex,
273
+ columnsCount
274
+ )
275
+ }
276
+ }
277
+
278
+ shouldIncludeBeforeResizeValuesInState() {
279
+ return this._includesBeforeResizeInState
280
+ }
281
+ }
282
+
283
+ // Equalizes all item heights within a given row, for each row.
284
+ //
285
+ // The reason is that `beforeResize.itemHeights` is not necessarily divisible by
286
+ // `beforeResize.columnsCount`, which would result in varying last row height
287
+ // as items get removed from `beforeResize.itemHeights` as the user scrolls up.
288
+ //
289
+ // By equalizing all item heights within a given row, for each row, such "jumping"
290
+ // last "before resize" row height is prevented when the user scrolls up.
291
+ //
292
+ function equalizeItemHeights(itemHeights, maxItemsCount, columnsCount) {
293
+ itemHeights = itemHeights.slice(0, Math.ceil(maxItemsCount / columnsCount) * columnsCount)
294
+
295
+ let rowIndex = 0
296
+ while (rowIndex * columnsCount < maxItemsCount) {
297
+ // Calculate row height.
298
+ let rowHeight = 0
299
+ let k = 0
300
+ while (k < columnsCount) {
301
+ rowHeight = Math.max(rowHeight, itemHeights[rowIndex * columnsCount + k])
302
+ k++
303
+ }
304
+
305
+ // Equalize all item heights within the row.
306
+ k = 0
307
+ while (k < columnsCount) {
308
+ itemHeights[rowIndex * columnsCount + k] = rowHeight
309
+ k++
310
+ }
311
+
312
+ // Proceed with the next row.
313
+ rowIndex++
314
+ }
315
+
316
+ return itemHeights.slice(0, maxItemsCount)
317
+ }
@@ -0,0 +1,32 @@
1
+ import ItemsContainer from './ItemsContainer'
2
+
3
+ import ScrollableContainer, {
4
+ ScrollableWindowContainer
5
+ } from './ScrollableContainer'
6
+
7
+ import ListTopOffsetWatcher from './ListTopOffsetWatcher'
8
+
9
+ export default {
10
+ createItemsContainer(getItemsContainerElement) {
11
+ return new ItemsContainer(getItemsContainerElement)
12
+ },
13
+ // Creates a `scrollableContainer`.
14
+ // On client side, `scrollableContainer` is always created.
15
+ // On server side, `scrollableContainer` is not created (and not used).
16
+ createScrollableContainer(scrollableContainer, getItemsContainerElement) {
17
+ if (scrollableContainer) {
18
+ return new ScrollableContainer(scrollableContainer, getItemsContainerElement)
19
+ } else if (typeof window !== 'undefined') {
20
+ return new ScrollableWindowContainer(getItemsContainerElement)
21
+ }
22
+ },
23
+ watchListTopOffset({
24
+ getListTopOffset,
25
+ onListTopOffsetChange
26
+ }) {
27
+ return new ListTopOffsetWatcher({
28
+ getListTopOffset,
29
+ onListTopOffsetChange
30
+ })
31
+ }
32
+ }
@@ -0,0 +1,48 @@
1
+ export default class ItemsContainer {
2
+ /**
3
+ * Constructs a new "container" from an element.
4
+ * @param {function} getElement
5
+ */
6
+ constructor(getElement) {
7
+ this.getElement = getElement
8
+ }
9
+
10
+ /**
11
+ * Returns an item element's "top offset", relative to the items `container`'s top edge.
12
+ * @param {number} renderedElementIndex — An index of an item relative to the "first shown item index". For example, if the list is showing items from index 8 to index 12 then `renderedElementIndex = 0` would mean the item at index `8`.
13
+ * @return {number}
14
+ */
15
+ getNthRenderedItemTopOffset(renderedElementIndex) {
16
+ return this.getElement().childNodes[renderedElementIndex].getBoundingClientRect().top - this.getElement().getBoundingClientRect().top
17
+ }
18
+
19
+ /**
20
+ * Returns an item element's height.
21
+ * @param {number} renderedElementIndex — An index of an item relative to the "first shown item index". For example, if the list is showing items from index 8 to index 12 then `renderedElementIndex = 0` would mean the item at index `8`.
22
+ * @return {number}
23
+ */
24
+ getNthRenderedItemHeight(renderedElementIndex) {
25
+ // `offsetHeight` is not precise enough (doesn't return fractional pixels).
26
+ // return this.getElement().childNodes[renderedElementIndex].offsetHeight
27
+ return this.getElement().childNodes[renderedElementIndex].getBoundingClientRect().height
28
+ }
29
+
30
+ /**
31
+ * Returns items container height.
32
+ * @return {number}
33
+ */
34
+ getHeight() {
35
+ // `offsetHeight` is not precise enough (doesn't return fractional pixels).
36
+ // return this.getElement().offsetHeight
37
+ return this.getElement().getBoundingClientRect().height
38
+ }
39
+
40
+ /**
41
+ * Removes all item elements of an items container.
42
+ */
43
+ clear() {
44
+ while (this.getElement().firstChild) {
45
+ this.getElement().removeChild(this.getElement().firstChild)
46
+ }
47
+ }
48
+ }