virtual-scroller 1.7.6 → 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 +29 -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 +56 -81
  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 +55 -80
  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 +39 -56
  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,13 +1,23 @@
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.LAYOUT_REASON = exports["default"] = void 0;
8
+ exports["default"] = exports.LAYOUT_REASON = void 0;
9
+
10
+ var _debug = _interopRequireWildcard(require("./utility/debug"));
11
+
12
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
13
+
14
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
15
+
16
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
7
17
 
8
- var _debug = _interopRequireDefault(require("./utility/debug"));
18
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
9
19
 
10
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
20
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
11
21
 
12
22
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
13
23
 
@@ -15,44 +25,63 @@ function _defineProperties(target, props) { for (var i = 0; i < props.length; i+
15
25
 
16
26
  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
17
27
 
18
- var Layout =
19
- /*#__PURE__*/
20
- function () {
28
+ var Layout = /*#__PURE__*/function () {
21
29
  function Layout(_ref) {
22
30
  var bypass = _ref.bypass,
23
31
  estimatedItemHeight = _ref.estimatedItemHeight,
24
32
  measureItemsBatchSize = _ref.measureItemsBatchSize,
33
+ getPrerenderMargin = _ref.getPrerenderMargin,
25
34
  getVerticalSpacing = _ref.getVerticalSpacing,
35
+ getVerticalSpacingBeforeResize = _ref.getVerticalSpacingBeforeResize,
26
36
  getColumnsCount = _ref.getColumnsCount,
37
+ getColumnsCountBeforeResize = _ref.getColumnsCountBeforeResize,
27
38
  getItemHeight = _ref.getItemHeight,
28
- getAverageItemHeight = _ref.getAverageItemHeight;
39
+ getItemHeightBeforeResize = _ref.getItemHeightBeforeResize,
40
+ getBeforeResizeItemsCount = _ref.getBeforeResizeItemsCount,
41
+ getAverageItemHeight = _ref.getAverageItemHeight,
42
+ getMaxVisibleAreaHeight = _ref.getMaxVisibleAreaHeight,
43
+ getPreviouslyCalculatedLayout = _ref.getPreviouslyCalculatedLayout;
29
44
 
30
45
  _classCallCheck(this, Layout);
31
46
 
32
47
  this.bypass = bypass;
33
48
  this.estimatedItemHeight = estimatedItemHeight;
34
49
  this.measureItemsBatchSize = measureItemsBatchSize;
50
+ this.getPrerenderMargin = getPrerenderMargin;
35
51
  this.getVerticalSpacing = getVerticalSpacing;
52
+ this.getVerticalSpacingBeforeResize = getVerticalSpacingBeforeResize;
36
53
  this.getColumnsCount = getColumnsCount;
54
+ this.getColumnsCountBeforeResize = getColumnsCountBeforeResize;
37
55
  this.getItemHeight = getItemHeight;
56
+ this.getItemHeightBeforeResize = getItemHeightBeforeResize;
57
+ this.getBeforeResizeItemsCount = getBeforeResizeItemsCount;
38
58
  this.getAverageItemHeight = getAverageItemHeight;
59
+ this.getMaxVisibleAreaHeight = getMaxVisibleAreaHeight; //
60
+ // The "previously calculated layout" feature is not currently used.
61
+ //
62
+ // The current layout snapshot could be stored as a "previously calculated layout" variable
63
+ // so that it could theoretically be used when calculating new layout incrementally
64
+ // rather than from scratch, which would be an optimization.
65
+ //
66
+
67
+ this.getPreviouslyCalculatedLayout = getPreviouslyCalculatedLayout;
39
68
  }
40
69
 
41
70
  _createClass(Layout, [{
42
71
  key: "getInitialLayoutValues",
43
72
  value: function getInitialLayoutValues(_ref2) {
44
- var bypass = _ref2.bypass,
45
- itemsCount = _ref2.itemsCount,
46
- visibleAreaHeightIncludingMargins = _ref2.visibleAreaHeightIncludingMargins;
47
- // On server side, at initialization time, there's no "visible area height",
48
- // so default to `1` estimated rows count.
49
- var estimatedRowsCount = visibleAreaHeightIncludingMargins ? this.getEstimatedRowsCountForHeight(visibleAreaHeightIncludingMargins) : 1;
73
+ var itemsCount = _ref2.itemsCount,
74
+ columnsCount = _ref2.columnsCount;
50
75
  var firstShownItemIndex;
51
76
  var lastShownItemIndex; // If there're no items then `firstShownItemIndex` stays `undefined`.
52
77
 
53
78
  if (itemsCount > 0) {
54
79
  firstShownItemIndex = 0;
55
- lastShownItemIndex = this.getLastShownItemIndex(firstShownItemIndex, itemsCount, estimatedRowsCount, bypass);
80
+ lastShownItemIndex = this.getInitialLastShownItemIndex({
81
+ itemsCount: itemsCount,
82
+ columnsCount: columnsCount,
83
+ firstShownItemIndex: firstShownItemIndex
84
+ });
56
85
  }
57
86
 
58
87
  return {
@@ -63,21 +92,35 @@ function () {
63
92
  };
64
93
  }
65
94
  }, {
66
- key: "getLastShownItemIndex",
67
- value: function getLastShownItemIndex(firstShownItemIndex, itemsCount, estimatedRowsCount, bypass) {
68
- if (this.bypass || bypass) {
95
+ key: "getInitialLastShownItemIndex",
96
+ value: function getInitialLastShownItemIndex(_ref3) {
97
+ var itemsCount = _ref3.itemsCount,
98
+ columnsCount = _ref3.columnsCount,
99
+ firstShownItemIndex = _ref3.firstShownItemIndex;
100
+
101
+ if (this.bypass) {
69
102
  return itemsCount - 1;
103
+ } // On server side, at initialization time,
104
+ // `scrollableContainer` is `undefined`,
105
+ // so default to `1` estimated rows count.
106
+
107
+
108
+ var estimatedRowsCount = 1;
109
+
110
+ if (this.getMaxVisibleAreaHeight()) {
111
+ estimatedRowsCount = this.getEstimatedRowsCountForHeight(this.getMaxVisibleAreaHeight() + this.getPrerenderMargin());
70
112
  }
71
113
 
72
- return Math.min(firstShownItemIndex + (estimatedRowsCount * this.getColumnsCount() - 1), itemsCount - 1);
114
+ return Math.min(firstShownItemIndex + (estimatedRowsCount * columnsCount - 1), itemsCount - 1);
73
115
  }
74
116
  }, {
75
117
  key: "getEstimatedRowsCountForHeight",
76
118
  value: function getEstimatedRowsCountForHeight(height) {
77
119
  var estimatedItemHeight = this.getEstimatedItemHeight();
120
+ var verticalSpacing = this.getVerticalSpacing();
78
121
 
79
122
  if (estimatedItemHeight) {
80
- return Math.ceil((height + this.getVerticalSpacing()) / (estimatedItemHeight + this.getVerticalSpacing()));
123
+ return Math.ceil((height + verticalSpacing) / (estimatedItemHeight + verticalSpacing));
81
124
  } else {
82
125
  // If no items have been rendered yet, and no `estimatedItemHeight` option
83
126
  // has been passed, then default to `1` estimated rows count in any `height`.
@@ -96,286 +139,614 @@ function () {
96
139
  return this.getAverageItemHeight() || this.estimatedItemHeight || 0;
97
140
  }
98
141
  }, {
99
- key: "updateLayoutForItemsDiff",
100
- value: function updateLayoutForItemsDiff(layout, _ref3, _ref4) {
101
- var prependedItemsCount = _ref3.prependedItemsCount,
102
- appendedItemsCount = _ref3.appendedItemsCount;
103
- var itemsCount = _ref4.itemsCount;
104
- layout.firstShownItemIndex += prependedItemsCount;
105
- layout.lastShownItemIndex += prependedItemsCount;
142
+ key: "getLayoutUpdateForItemsDiff",
143
+ value: function getLayoutUpdateForItemsDiff(_ref4, _ref5, _ref6) {
144
+ var firstShownItemIndex = _ref4.firstShownItemIndex,
145
+ lastShownItemIndex = _ref4.lastShownItemIndex,
146
+ beforeItemsHeight = _ref4.beforeItemsHeight,
147
+ afterItemsHeight = _ref4.afterItemsHeight;
148
+ var prependedItemsCount = _ref5.prependedItemsCount,
149
+ appendedItemsCount = _ref5.appendedItemsCount;
150
+ var itemsCount = _ref6.itemsCount,
151
+ columnsCount = _ref6.columnsCount,
152
+ shouldRestoreScrollPosition = _ref6.shouldRestoreScrollPosition;
153
+ // const layoutUpdate = {}
154
+ // If the layout stays the same, then simply increase
155
+ // the top and bottom margins proportionally to the amount
156
+ // of the items added.
157
+ var averageItemHeight = this.getAverageItemHeight();
158
+ var verticalSpacing = this.getVerticalSpacing();
159
+
160
+ if (appendedItemsCount > 0) {
161
+ var appendedRowsCount = Math.ceil(appendedItemsCount / columnsCount);
162
+ var addedHeightAfter = appendedRowsCount * (verticalSpacing + averageItemHeight);
163
+ afterItemsHeight += addedHeightAfter; // layoutUpdate = {
164
+ // ...layoutUpdate,
165
+ // afterItemsHeight
166
+ // }
167
+ }
168
+
169
+ if (prependedItemsCount > 0) {
170
+ var prependedRowsCount = Math.ceil(prependedItemsCount / columnsCount);
171
+ var addedHeightBefore = prependedRowsCount * (averageItemHeight + verticalSpacing);
172
+ firstShownItemIndex += prependedItemsCount;
173
+ lastShownItemIndex += prependedItemsCount;
174
+ beforeItemsHeight += addedHeightBefore; // If the currently shown items position on screen should be preserved
175
+ // when prepending new items, then it means that:
176
+ // * The current scroll position should be snapshotted.
177
+ // * The current list height should be snapshotted.
178
+ // * All prepended items should be shown so that their height could be
179
+ // measured after they're rendered. Based on the prepended items' height,
180
+ // the scroll position will be restored so that there's no "jump of content".
181
+
182
+ if (shouldRestoreScrollPosition) {
183
+ firstShownItemIndex = 0;
184
+ beforeItemsHeight = 0;
185
+ }
186
+
187
+ if (prependedItemsCount % columnsCount > 0) {
188
+ // Rows will be rebalanced as a result of prepending new items,
189
+ // and row heights can change as a result, so re-layout items
190
+ // after they've been measured (after the upcoming re-render).
191
+ //
192
+ // For example, consider a web page where item rows are `display: flex`.
193
+ // Suppose there're 3 columns and it shows items from 4 to 6.
194
+ //
195
+ // ------------------------------------------
196
+ // | Apples are | Bananas | Cranberries |
197
+ // | green | | |
198
+ // ------------------------------------------
199
+ // | Dates | Elderberry | Figs are |
200
+ // | | | tasty |
201
+ // ------------------------------------------
202
+ //
203
+ // Now, 1 item gets prepended. As a result, all existing rows will have
204
+ // a different set of items, which means that the row heights will change.
205
+ //
206
+ // ------------------------------------------
207
+ // | Zucchini | Apples are | Bananas |
208
+ // | | green | |
209
+ // ------------------------------------------
210
+ // | Cranberries | Dates | Elderberry |
211
+ // ------------------------------------------
212
+ // | Figs |
213
+ // | are tasty |
214
+ // ---------------
215
+ //
216
+ // As it can be seen above, the second row's height has changed from 2 to 1.
217
+ // Not only that, but `itemHeights` have changed as well, so if you thought
218
+ // that the library could easily recalculate row heights using `Math.max()` — 
219
+ // turns out it's not always the case.
220
+ //
221
+ // There could be an explicit opt-in option for automatically recalculating
222
+ // row heights, but I don't want to write code for such an extremely rare
223
+ // use case. Instead, use the `getColumnsCount()` parameter function when
224
+ // fetching previous items.
225
+ (0, _debug.warn)('~ Prepended items count', prependedItemsCount, 'is not divisible by Columns Count', columnsCount, '~');
226
+ (0, _debug.warn)('Reset Layout');
227
+ var shownItemsCountBeforeItemsUpdate = lastShownItemIndex - firstShownItemIndex + 1;
228
+ firstShownItemIndex = 0;
229
+ beforeItemsHeight = 0;
230
+
231
+ if (!shouldRestoreScrollPosition) {
232
+ // Limit shown items count if too many items have been prepended.
233
+ if (prependedItemsCount > shownItemsCountBeforeItemsUpdate) {
234
+ lastShownItemIndex = this.getInitialLastShownItemIndex({
235
+ itemsCount: itemsCount,
236
+ columnsCount: columnsCount,
237
+ firstShownItemIndex: firstShownItemIndex
238
+ }); // Approximate `afterItemsHeight` calculation.
239
+
240
+ var afterItemsCount = itemsCount - (lastShownItemIndex + 1);
241
+ afterItemsHeight = Math.ceil(afterItemsCount / columnsCount) * (verticalSpacing + averageItemHeight); // layoutUpdate = {
242
+ // ...layoutUpdate,
243
+ // afterItemsHeight
244
+ // }
245
+ }
246
+ }
247
+ } // layoutUpdate = {
248
+ // ...layoutUpdate,
249
+ // beforeItemsHeight,
250
+ // firstShownItemIndex,
251
+ // lastShownItemIndex
252
+ // }
253
+
254
+ } // return layoutUpdate
255
+ // Overwrite all four props in all scenarios.
256
+ // The reason is that only this way subsequent `setItems()` calls
257
+ // will be truly "stateless" when a chain of `setItems()` calls
258
+ // could be replaced with just the last one in a scenario when
259
+ // `setState()` calls are "asynchronous" (delayed execution).
260
+ //
261
+ // So, for example, the user calls `setItems()` with one set of items.
262
+ // A `setState()` call has been dispatched but the `state` hasn't been updated yet.
263
+ // Then the user calls `setItems()` with another set of items.
264
+ // If this function only returned a minimal set of properties that actually change,
265
+ // the other layout properties of the second `setItems()` call wouldn't overwrite the ones
266
+ // scheduled for update during the first `setItems()` call, resulting in an inconsistent `state`.
267
+ //
268
+ // For example, the first `setItems()` call does a `setState()` call where it updates
269
+ // `afterItemsHeight`, and then the second `setItems()` call only updates `beforeItemsHeight`
270
+ // and `firstShownItemIndex` and `lastShownItemIndex`. If the second `setItems()` call was to
271
+ // overwrite any effects of the pending-but-not-yet-applied first `setItems()` call, it would
272
+ // have to call `setState()` with an `afterItemsHeight` property too, even though it hasn't change.
273
+ // That would be just to revert the change to `afterItemsHeight` state property already scheduled
274
+ // by the first `setItems()` call.
275
+ //
276
+
277
+
278
+ return {
279
+ beforeItemsHeight: beforeItemsHeight,
280
+ afterItemsHeight: afterItemsHeight,
281
+ firstShownItemIndex: firstShownItemIndex,
282
+ lastShownItemIndex: lastShownItemIndex
283
+ };
284
+ } // If an item that hasn't been shown (and measured) yet is encountered
285
+ // then show such item and then retry after it has been measured.
286
+
287
+ }, {
288
+ key: "getItemNotMeasuredIndexes",
289
+ value: function getItemNotMeasuredIndexes(i, _ref7) {
290
+ var itemsCount = _ref7.itemsCount,
291
+ firstShownItemIndex = _ref7.firstShownItemIndex,
292
+ nonMeasuredAreaHeight = _ref7.nonMeasuredAreaHeight,
293
+ indexOfTheFirstItemInTheRow = _ref7.indexOfTheFirstItemInTheRow;
294
+ (0, _debug["default"])('Item index', i, 'height is required for calculations but hasn\'t been measured yet. Mark the item as "shown", rerender the list, measure the item\'s height and redo the layout.');
106
295
  var columnsCount = this.getColumnsCount();
296
+ var itemsCountToRenderForMeasurement = Math.min(this.getEstimatedRowsCountForHeight(nonMeasuredAreaHeight) * columnsCount, this.measureItemsBatchSize || Infinity);
107
297
 
108
- if (prependedItemsCount % columnsCount === 0) {
109
- // If the layout stays the same, then simply increase
110
- // the top and bottom margins proportionally to the amount
111
- // of the items added.
112
- var prependedRowsCount = prependedItemsCount / columnsCount;
113
- var appendedRowsCount = Math.ceil(appendedItemsCount / columnsCount);
114
- var averageItemHeight = this.getAverageItemHeight();
115
- var verticalSpacing = this.getVerticalSpacing();
116
- layout.beforeItemsHeight += prependedRowsCount * (averageItemHeight + verticalSpacing);
117
- layout.afterItemsHeight += appendedRowsCount * (verticalSpacing + averageItemHeight);
118
- } else {
119
- // Rows will be rebalanced as a result of prepending the items,
120
- // and the row heights can change as a result, so recalculate
121
- // `beforeItemsHeight` and `afterItemsHeight` from scratch.
122
- // `this.itemHeights[]` and `firstShownItemIndex`/`lastShownItemIndex`
123
- // have already been updated at this point.
124
- layout.beforeItemsHeight = this.getBeforeItemsHeight(firstShownItemIndex, lastShownItemIndex);
125
- layout.afterItemsHeight = this.getAfterItemsHeight(firstShownItemIndex, lastShownItemIndex, itemsCount);
298
+ if (firstShownItemIndex === undefined) {
299
+ firstShownItemIndex = indexOfTheFirstItemInTheRow;
126
300
  }
301
+
302
+ var lastShownItemIndex = Math.min(indexOfTheFirstItemInTheRow + itemsCountToRenderForMeasurement - 1, // Guard against index overflow.
303
+ itemsCount - 1);
304
+ return {
305
+ firstNonMeasuredItemIndex: i,
306
+ firstShownItemIndex: firstShownItemIndex,
307
+ lastShownItemIndex: lastShownItemIndex
308
+ };
127
309
  }
310
+ /**
311
+ * Finds the indexes of the currently visible items.
312
+ * @return {object} `{ firstShownItemIndex: number, lastShownItemIndex: number, firstNonMeasuredItemIndex: number? }`
313
+ */
314
+
128
315
  }, {
129
- key: "_getVisibleItemIndexes",
130
- value: function _getVisibleItemIndexes(visibleAreaTop, visibleAreaBottom, listTopOffset, itemsCount) {
131
- var columnsCount = this.getColumnsCount();
132
- var firstShownItemIndex;
133
- var lastShownItemIndex;
134
- var previousRowsHeight = 0;
135
- var rowsCount = Math.ceil(itemsCount / columnsCount);
136
- var rowIndex = 0;
316
+ key: "getShownItemIndexes",
317
+ value: function getShownItemIndexes(_ref8) {
318
+ var itemsCount = _ref8.itemsCount,
319
+ visibleAreaTop = _ref8.visibleAreaTop,
320
+ visibleAreaBottom = _ref8.visibleAreaBottom;
321
+
322
+ var indexes = this._getShownItemIndex({
323
+ itemsCount: itemsCount,
324
+ fromIndex: 0,
325
+ visibleAreaTop: visibleAreaTop,
326
+ visibleAreaBottom: visibleAreaBottom,
327
+ findFirstShownItemIndex: true
328
+ });
329
+
330
+ if (indexes === null) {
331
+ return this.getNonVisibleListShownItemIndexes();
332
+ }
137
333
 
138
- while (rowIndex < rowsCount) {
139
- var hasMoreRows = itemsCount > (rowIndex + 1) * columnsCount;
140
- var verticalSpaceAfterCurrentRow = hasMoreRows ? this.getVerticalSpacing() : 0;
141
- var currentRowHeight = 0;
142
- var columnIndex = 0;
143
- var i = void 0;
334
+ if (indexes.firstNonMeasuredItemIndex !== undefined) {
335
+ return indexes;
336
+ }
144
337
 
145
- while (columnIndex < columnsCount && (i = rowIndex * columnsCount + columnIndex) < itemsCount) {
146
- var itemHeight = this.getItemHeight(i); // If an item that hasn't been shown (and measured) yet is encountered
147
- // then show such item and then retry after it has been measured.
338
+ var _indexes = indexes,
339
+ firstShownItemIndex = _indexes.firstShownItemIndex,
340
+ beforeItemsHeight = _indexes.beforeItemsHeight;
341
+ indexes = this._getShownItemIndex({
342
+ itemsCount: itemsCount,
343
+ fromIndex: firstShownItemIndex,
344
+ beforeItemsHeight: beforeItemsHeight,
345
+ visibleAreaTop: visibleAreaTop,
346
+ visibleAreaBottom: visibleAreaBottom,
347
+ findLastShownItemIndex: true
348
+ });
349
+
350
+ if (indexes === null) {
351
+ return this.getNonVisibleListShownItemIndexes();
352
+ }
148
353
 
149
- if (itemHeight === undefined) {
150
- (0, _debug["default"])("Item index ".concat(i, " lies within the visible area or its \"margins\", but its height hasn't been measured yet. Mark the item as \"shown\", render the list, measure the item's height and redo the layout."));
354
+ if (indexes.firstNonMeasuredItemIndex !== undefined) {
355
+ return indexes;
356
+ }
151
357
 
152
- if (firstShownItemIndex === undefined) {
153
- firstShownItemIndex = rowIndex * columnsCount;
154
- }
358
+ var _indexes2 = indexes,
359
+ lastShownItemIndex = _indexes2.lastShownItemIndex;
360
+ return {
361
+ firstShownItemIndex: firstShownItemIndex,
362
+ lastShownItemIndex: lastShownItemIndex
363
+ };
364
+ }
365
+ }, {
366
+ key: "_getShownItemIndex",
367
+ value: function _getShownItemIndex(parameters) {
368
+ var beforeResize = parameters.beforeResize,
369
+ itemsCount = parameters.itemsCount,
370
+ visibleAreaTop = parameters.visibleAreaTop,
371
+ visibleAreaBottom = parameters.visibleAreaBottom,
372
+ findFirstShownItemIndex = parameters.findFirstShownItemIndex,
373
+ findLastShownItemIndex = parameters.findLastShownItemIndex;
374
+ var fromIndex = parameters.fromIndex,
375
+ beforeItemsHeight = parameters.beforeItemsHeight; // This function could potentially also use `this.getPreviouslyCalculatedLayout()`
376
+ // in order to skip calculating visible item indexes from scratch
377
+ // and instead just calculate the difference from a "previously calculated layout".
378
+ //
379
+ // I did a simple test in a web browser and found out that running the following
380
+ // piece of code is less than 10 milliseconds:
381
+ //
382
+ // var startedAt = Date.now()
383
+ // var i = 0
384
+ // while (i < 1000000) {
385
+ // i++
386
+ // }
387
+ // console.log(Date.now() - startedAt)
388
+ //
389
+ // Which becomes negligible in my project's use case (a couple thousands items max).
390
+ //
391
+ // If someone would attempt to use a "previously calculated layout" here
392
+ // then `shownItemsHeight` would also have to be returned from this function:
393
+ // the total height of all shown items including vertical spacing between them.
394
+ //
395
+ // If "previously calculated layout" would be used then it would first find
396
+ // `firstShownItemIndex` and then find `lastShownItemIndex` as part of two
397
+ // separate calls of this function, each with or without `backwards` flag,
398
+ // depending on whether `visibleAreaTop` and `visibleAreBottom` have shifted up or down.
399
+
400
+ var firstShownItemIndex;
401
+ var lastShownItemIndex; // It's not always required to pass `beforeItemsHeight` parameter:
402
+ // when `fromIndex` is `0`, it's also assumed to be `0`.
403
+
404
+ if (fromIndex === 0) {
405
+ beforeItemsHeight = 0;
406
+ }
407
+
408
+ if (beforeItemsHeight === undefined) {
409
+ throw new Error('[virtual-scroller] `beforeItemsHeight` not passed to `Layout.getShownItemIndexes()` when starting from index ' + fromIndex);
410
+ } // const backwards = false
411
+ // while (backwards ? i >= 0 : i < itemsCount) {}
412
+
413
+
414
+ if (!beforeResize) {
415
+ var beforeResizeItemsCount = this.getBeforeResizeItemsCount();
416
+
417
+ if (beforeResizeItemsCount > fromIndex) {
418
+ // First search for the item in "before resize" items.
419
+ var _this$_getShownItemIn = this._getShownItemIndex(_objectSpread(_objectSpread({}, parameters), {}, {
420
+ beforeResize: true,
421
+ itemsCount: beforeResizeItemsCount
422
+ })),
423
+ notFound = _this$_getShownItemIn.notFound,
424
+ beforeResizeItemsHeight = _this$_getShownItemIn.beforeItemsHeight,
425
+ _firstShownItemIndex = _this$_getShownItemIn.firstShownItemIndex,
426
+ _lastShownItemIndex = _this$_getShownItemIn.lastShownItemIndex; // If the item was not found in "before resize" items
427
+ // then search in regular items skipping "before resize" ones.
428
+
429
+
430
+ if (notFound) {
431
+ beforeItemsHeight = beforeResizeItemsHeight;
432
+ fromIndex += beforeResizeItemsCount;
433
+ } else {
434
+ // If the item was found in "before resize" items
435
+ // then return the result.
436
+ // Rebalance first / last shown item indexes based on
437
+ // the current columns count, if required.
438
+ var _columnsCount = this.getColumnsCount();
155
439
 
156
- var heightLeft = visibleAreaBottom - (listTopOffset + previousRowsHeight);
157
- lastShownItemIndex = Math.min((rowIndex + this.getEstimatedRowsCountForHeight(heightLeft)) * columnsCount - 1, // Guard against index overflow.
158
- itemsCount - 1);
159
440
  return {
160
- firstNonMeasuredItemIndex: i,
161
- firstShownItemIndex: firstShownItemIndex,
162
- lastShownItemIndex: lastShownItemIndex
441
+ firstShownItemIndex: _firstShownItemIndex === undefined ? undefined : Math.floor(_firstShownItemIndex / _columnsCount) * _columnsCount,
442
+ lastShownItemIndex: _lastShownItemIndex === undefined ? undefined : Math.floor(_lastShownItemIndex / _columnsCount) * _columnsCount,
443
+ beforeItemsHeight: beforeResizeItemsHeight
163
444
  };
164
445
  }
446
+ }
447
+ }
165
448
 
166
- currentRowHeight = Math.max(currentRowHeight, itemHeight); // If this is the first item visible
167
- // then start showing items from this row.
168
-
169
- if (firstShownItemIndex === undefined) {
170
- if (listTopOffset + previousRowsHeight + currentRowHeight > visibleAreaTop) {
171
- (0, _debug["default"])('First shown row index', rowIndex);
172
- firstShownItemIndex = rowIndex * columnsCount;
173
- }
174
- } // If this item is the last one visible in the viewport then exit.
449
+ var columnsCount = beforeResize ? this.getColumnsCountBeforeResize() : this.getColumnsCount();
450
+ var verticalSpacing = beforeResize ? this.getVerticalSpacingBeforeResize() : this.getVerticalSpacing();
451
+ var i = fromIndex;
175
452
 
453
+ while (i < itemsCount) {
454
+ var currentRowFirstItemIndex = i;
455
+ var hasMoreRows = itemsCount > currentRowFirstItemIndex + columnsCount;
456
+ var verticalSpacingAfterCurrentRow = hasMoreRows ? verticalSpacing : 0;
457
+ var currentRowHeight = 0; // Calculate current row height.
176
458
 
177
- if (listTopOffset + previousRowsHeight + currentRowHeight + verticalSpaceAfterCurrentRow > visibleAreaBottom) {
178
- (0, _debug["default"])('Last shown row index', rowIndex); // The list height is estimated until all items have been seen,
179
- // so it's possible that even when the list DOM element happens
180
- // to be in the viewport in reality the list isn't visible
181
- // in which case `firstShownItemIndex` will be `undefined`.
459
+ var columnIndex = 0;
182
460
 
183
- if (firstShownItemIndex !== undefined) {
184
- lastShownItemIndex = Math.min( // The index of the last item in the current row.
185
- (rowIndex + 1) * columnsCount - 1, // Guards against index overflow.
186
- itemsCount - 1);
187
- }
461
+ while (columnIndex < columnsCount && i < itemsCount) {
462
+ var itemHeight = beforeResize ? this.getItemHeightBeforeResize(i) : this.getItemHeight(i); // If this item hasn't been measured yet (or re-measured after a resize)
463
+ // then mark it as the first non-measured one.
464
+ //
465
+ // Can't happen by definition when `beforeResize` parameter is `true`.
466
+ //
188
467
 
189
- return {
190
- firstShownItemIndex: firstShownItemIndex,
191
- lastShownItemIndex: lastShownItemIndex
192
- };
468
+ if (itemHeight === undefined) {
469
+ return this.getItemNotMeasuredIndexes(i, {
470
+ itemsCount: itemsCount,
471
+ firstShownItemIndex: findLastShownItemIndex ? fromIndex : undefined,
472
+ indexOfTheFirstItemInTheRow: currentRowFirstItemIndex,
473
+ nonMeasuredAreaHeight: visibleAreaBottom + this.getPrerenderMargin() - beforeItemsHeight
474
+ });
193
475
  }
194
476
 
477
+ currentRowHeight = Math.max(currentRowHeight, itemHeight);
195
478
  columnIndex++;
479
+ i++;
196
480
  }
197
481
 
198
- previousRowsHeight += currentRowHeight; // If there're more rows below the current row, then add vertical spacing.
199
-
200
- previousRowsHeight += verticalSpaceAfterCurrentRow;
201
- rowIndex++;
202
- } // If there're no more items then the last item is the last one to show.
203
-
482
+ var itemsHeightFromFirstRowToThisRow = beforeItemsHeight + currentRowHeight;
483
+ var rowStepsIntoVisibleAreaTop = itemsHeightFromFirstRowToThisRow > visibleAreaTop - this.getPrerenderMargin();
484
+ var rowStepsOutOfVisibleAreaBottomOrIsAtTheBorder = itemsHeightFromFirstRowToThisRow + verticalSpacingAfterCurrentRow >= visibleAreaBottom + this.getPrerenderMargin(); // if (backwards) {
485
+ // if (findFirstShownItemIndex) {
486
+ // if (rowStepsOutOfVisibleAreaTop) {
487
+ // return {
488
+ // firstShownItemIndex: currentRowFirstItemIndex + columnsCount
489
+ // }
490
+ // }
491
+ // } else if (findLastShownItemIndex) {
492
+ // if (rowStepsIntoVisibleAreaBottom) {
493
+ // return {
494
+ // lastShownItemIndex: currentRowFirstItemIndex + columnsCount - 1
495
+ // }
496
+ // }
497
+ // }
498
+ // }
499
+
500
+ if (findFirstShownItemIndex) {
501
+ if (rowStepsIntoVisibleAreaTop) {
502
+ // If item is the first one visible in the viewport
503
+ // then start showing items from this row.
504
+ return {
505
+ firstShownItemIndex: currentRowFirstItemIndex,
506
+ beforeItemsHeight: beforeItemsHeight
507
+ };
508
+ }
509
+ } else if (findLastShownItemIndex) {
510
+ if (rowStepsOutOfVisibleAreaBottomOrIsAtTheBorder) {
511
+ return {
512
+ lastShownItemIndex: Math.min( // The index of the last item in the current row.
513
+ currentRowFirstItemIndex + columnsCount - 1, // Guards against index overflow.
514
+ itemsCount - 1)
515
+ };
516
+ }
517
+ }
204
518
 
205
- if (firstShownItemIndex !== undefined && lastShownItemIndex === undefined) {
206
- lastShownItemIndex = itemsCount - 1;
207
- (0, _debug["default"])('Last item index (is fully visible)', lastShownItemIndex);
208
- }
519
+ beforeItemsHeight += currentRowHeight + verticalSpacingAfterCurrentRow; // if (backwards) {
520
+ // // Set `i` to be the first item of the current row.
521
+ // i -= columnsCount
522
+ // const prevoiusRowIsBeforeResize = i - 1 < this.getBeforeResizeItemsCount()
523
+ // const previousRowColumnsCount = prevoiusRowIsBeforeResize ? this.getColumnsCountBeforeResize() : this.getColumnsCount()
524
+ // // Set `i` to be the first item of the previous row.
525
+ // i -= previousRowColumnsCount
526
+ // }
527
+ } // if (backwards) {
528
+ // if (findFirstShownItemIndex) {
529
+ // warn('The list is supposed to be visible but no visible item has been found (while traversing backwards)')
530
+ // return null
531
+ // } else if (findLastShownItemIndex) {
532
+ // return {
533
+ // firstShownItemIndex: 0
534
+ // }
535
+ // }
536
+ // }
537
+
538
+
539
+ if (beforeResize) {
540
+ return {
541
+ notFound: true,
542
+ beforeItemsHeight: beforeItemsHeight
543
+ };
544
+ } // This case isn't supposed to happen but it could hypothetically happen
545
+ // because the list height is measured from the user's screen and
546
+ // not necessarily can be trusted.
209
547
 
210
- return {
211
- firstShownItemIndex: firstShownItemIndex,
212
- lastShownItemIndex: lastShownItemIndex
213
- };
214
- } // Finds the items which are displayed in the viewport.
215
548
 
216
- }, {
217
- key: "getVisibleItemIndexes",
218
- value: function getVisibleItemIndexes(visibleAreaTop, visibleAreaBottom, listTopOffset, itemsCount) {
219
- var _this$_getVisibleItem = this._getVisibleItemIndexes(visibleAreaTop, visibleAreaBottom, listTopOffset, itemsCount),
220
- firstNonMeasuredItemIndex = _this$_getVisibleItem.firstNonMeasuredItemIndex,
221
- firstShownItemIndex = _this$_getVisibleItem.firstShownItemIndex,
222
- lastShownItemIndex = _this$_getVisibleItem.lastShownItemIndex;
223
-
224
- var redoLayoutAfterMeasuringItemHeights = firstNonMeasuredItemIndex !== undefined; // If some items will be rendered in order to measure their height,
225
- // and it's not a `preserveScrollPositionOnPrependItems` case,
226
- // then limit the amount of such items being measured in a single pass.
227
-
228
- if (redoLayoutAfterMeasuringItemHeights && this.measureItemsBatchSize) {
229
- var maxAllowedLastShownItemIndex = firstNonMeasuredItemIndex + this.measureItemsBatchSize - 1;
230
- var columnsCount = this.getColumnsCount();
231
- lastShownItemIndex = Math.min( // Also guards against index overflow.
232
- lastShownItemIndex, // The index of the last item in the row.
233
- Math.ceil(maxAllowedLastShownItemIndex / columnsCount) * columnsCount - 1);
549
+ if (findFirstShownItemIndex) {
550
+ (0, _debug.warn)('The list is supposed to be visible but no visible item has been found');
551
+ return null;
552
+ } else if (findLastShownItemIndex) {
553
+ return {
554
+ lastShownItemIndex: itemsCount - 1
555
+ };
234
556
  }
235
-
236
- return {
237
- firstShownItemIndex: firstShownItemIndex,
238
- lastShownItemIndex: lastShownItemIndex,
239
- redoLayoutAfterMeasuringItemHeights: redoLayoutAfterMeasuringItemHeights
240
- };
241
557
  }
242
558
  }, {
243
559
  key: "getNonVisibleListShownItemIndexes",
244
560
  value: function getNonVisibleListShownItemIndexes() {
245
- return {
561
+ var layout = {
246
562
  firstShownItemIndex: 0,
247
- lastShownItemIndex: 0,
248
- redoLayoutAfterMeasuringItemHeights: this.getItemHeight(0) === undefined
563
+ lastShownItemIndex: 0
249
564
  };
250
- }
251
- }, {
252
- key: "getItemIndexes",
253
- value: function getItemIndexes(visibleAreaTop, visibleAreaBottom, listTopOffset, listHeight, itemsCount) {
254
- var isVisible = listTopOffset + listHeight > visibleAreaTop && listTopOffset < visibleAreaBottom;
255
-
256
- if (!isVisible) {
257
- (0, _debug["default"])('The entire list is off-screen. No items are visible.');
258
- return;
259
- } // Find the items which are displayed in the viewport.
260
-
261
-
262
- var indexes = this.getVisibleItemIndexes(visibleAreaTop, visibleAreaBottom, listTopOffset, itemsCount); // The list height is estimated until all items have been seen,
263
- // so it's possible that even when the list DOM element happens
264
- // to be in the viewport, in reality the list isn't visible
265
- // in which case `firstShownItemIndex` will be `undefined`.
266
565
 
267
- if (indexes.firstShownItemIndex === undefined) {
268
- (0, _debug["default"])('The entire list is off-screen. No items are visible.');
269
- return;
566
+ if (this.getItemHeight(0) === undefined) {
567
+ layout.firstNonMeasuredItemIndex = 0;
270
568
  }
271
569
 
272
- return indexes;
570
+ return layout;
273
571
  }
274
572
  /**
275
573
  * Measures "before" items height.
276
- * @param {number} firstShownItemIndexNew first shown item index.
277
- * @param {number} lastShownItemIndex — New last shown item index.
574
+ * @param {number} beforeItemsCountBasically, first shown item index.
278
575
  * @return {number}
279
576
  */
280
577
 
281
578
  }, {
282
579
  key: "getBeforeItemsHeight",
283
- value: function getBeforeItemsHeight(firstShownItemIndex, lastShownItemIndex) {
284
- var columnsCount = this.getColumnsCount();
285
- var firstShownRowIndex = Math.floor(firstShownItemIndex / columnsCount);
286
- var beforeItemsHeight = 0; // Add all "before" items height.
580
+ value: function getBeforeItemsHeight(beforeItemsCount) {
581
+ var _ref9 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
582
+ beforeResize = _ref9.beforeResize;
583
+
584
+ // This function could potentially also use `this.getPreviouslyCalculatedLayout()`
585
+ // in order to skip calculating visible item indexes from scratch
586
+ // and instead just calculate the difference from a "previously calculated layout".
587
+ //
588
+ // I did a simple test in a web browser and found out that running the following
589
+ // piece of code is less than 10 milliseconds:
590
+ //
591
+ // var startedAt = Date.now()
592
+ // var i = 0
593
+ // while (i < 1000000) {
594
+ // i++
595
+ // }
596
+ // console.log(Date.now() - startedAt)
597
+ //
598
+ // Which becomes negligible in my project's use case (a couple thousands items max).
599
+ var beforeItemsHeight = 0;
600
+ var i = 0;
601
+
602
+ if (!beforeResize) {
603
+ var beforeResizeItemsCount = this.getBeforeResizeItemsCount();
604
+
605
+ if (beforeResizeItemsCount > 0) {
606
+ // First add all "before resize" item heights.
607
+ beforeItemsHeight = this.getBeforeItemsHeight( // `firstShownItemIndex` (called `beforeItemsCount`) could be greater than
608
+ // `beforeResizeItemsCount` when the user scrolls down.
609
+ // `firstShownItemIndex` (called `beforeItemsCount`) could be less than
610
+ // `beforeResizeItemsCount` when the user scrolls up.
611
+ Math.min(beforeItemsCount, beforeResizeItemsCount), {
612
+ beforeResize: true
613
+ });
614
+ i = beforeResizeItemsCount;
615
+ }
616
+ }
287
617
 
288
- var rowIndex = 0;
618
+ var columnsCount = beforeResize ? this.getColumnsCountBeforeResize() : this.getColumnsCount();
619
+ var verticalSpacing = beforeResize ? this.getVerticalSpacingBeforeResize() : this.getVerticalSpacing();
289
620
 
290
- while (rowIndex < firstShownRowIndex) {
621
+ while (i < beforeItemsCount) {
622
+ var currentRowFirstItemIndex = i;
291
623
  var rowHeight = 0;
292
- var columnIndex = 0;
624
+ var columnIndex = 0; // Not checking for `itemsCount` overflow here because `i = beforeItemsCount`
625
+ // can only start at the start of a row, meaning that when calculating
626
+ // "before items height" it's not supposed to add item heights from the
627
+ // last row of items because in that case it would have to iterate from
628
+ // `i === beforeItemsCount` and that condition is already checked above.
629
+ // while (i < itemsCount) {
293
630
 
294
631
  while (columnIndex < columnsCount) {
295
- rowHeight = Math.max(rowHeight, this.getItemHeight(rowIndex * columnsCount + columnIndex) || this.getAverageItemHeight());
632
+ var itemHeight = beforeResize ? this.getItemHeightBeforeResize(i) : this.getItemHeight(i);
633
+
634
+ if (itemHeight === undefined) {
635
+ // `itemHeight` can only be `undefined` when not `beforeResize`.
636
+ // Use the current "average item height" as a substitute.
637
+ itemHeight = this.getAverageItemHeight();
638
+ }
639
+
640
+ rowHeight = Math.max(rowHeight, itemHeight);
641
+ i++;
296
642
  columnIndex++;
297
643
  }
298
644
 
299
645
  beforeItemsHeight += rowHeight;
300
- beforeItemsHeight += this.getVerticalSpacing();
301
- rowIndex++;
646
+ beforeItemsHeight += verticalSpacing;
302
647
  }
303
648
 
304
649
  return beforeItemsHeight;
305
650
  }
306
651
  /**
307
652
  * Measures "after" items height.
308
- * @param {number} firstShownItemIndexNew first shown item index.
309
- * @param {number} lastShownItemIndex — New last shown item index.
310
- * @param {number} averageItemHeight — Average item height.
311
- * @param {number} verticalSpacing — Item vertical spacing.
653
+ * @param {number} lastShownItemIndexLast shown item index.
312
654
  * @param {number} itemsCount — Items count.
313
655
  * @return {number}
314
656
  */
315
657
 
316
658
  }, {
317
659
  key: "getAfterItemsHeight",
318
- value: function getAfterItemsHeight(firstShownItemIndex, lastShownItemIndex, itemsCount) {
660
+ value: function getAfterItemsHeight(lastShownItemIndex, itemsCount) {
661
+ // This function could potentially also use `this.getPreviouslyCalculatedLayout()`
662
+ // in order to skip calculating visible item indexes from scratch
663
+ // and instead just calculate the difference from a "previously calculated layout".
664
+ //
665
+ // I did a simple test in a web browser and found out that running the following
666
+ // piece of code is less than 10 milliseconds:
667
+ //
668
+ // var startedAt = Date.now()
669
+ // var i = 0
670
+ // while (i < 1000000) {
671
+ // i++
672
+ // }
673
+ // console.log(Date.now() - startedAt)
674
+ //
675
+ // Which becomes negligible in my project's use case (a couple thousands items max).
319
676
  var columnsCount = this.getColumnsCount();
320
- var rowsCount = Math.ceil(itemsCount / columnsCount);
321
677
  var lastShownRowIndex = Math.floor(lastShownItemIndex / columnsCount);
322
678
  var afterItemsHeight = 0;
323
- var rowIndex = lastShownRowIndex + 1;
679
+ var i = lastShownItemIndex + 1;
324
680
 
325
- while (rowIndex < rowsCount) {
681
+ while (i < itemsCount) {
326
682
  var rowHeight = 0;
327
683
  var columnIndex = 0;
328
- var i = void 0;
329
684
 
330
- while (columnIndex < columnsCount && (i = rowIndex * columnsCount + columnIndex) < itemsCount) {
331
- rowHeight = Math.max(rowHeight, this.getItemHeight(i) || this.getAverageItemHeight());
685
+ while (columnIndex < columnsCount && i < itemsCount) {
686
+ var itemHeight = this.getItemHeight(i);
687
+
688
+ if (itemHeight === undefined) {
689
+ itemHeight = this.getAverageItemHeight();
690
+ }
691
+
692
+ rowHeight = Math.max(rowHeight, itemHeight);
693
+ i++;
332
694
  columnIndex++;
333
695
  } // Add all "after" items height.
334
696
 
335
697
 
336
698
  afterItemsHeight += this.getVerticalSpacing();
337
699
  afterItemsHeight += rowHeight;
338
- rowIndex++;
339
700
  }
340
701
 
341
702
  return afterItemsHeight;
342
703
  }
343
704
  /**
344
- * Finds the indexes of the currently visible items.
345
- * @return {object} `{ firstShownItemIndex: number, lastShownItemIndex: number, redoLayoutAfterMeasuringItemHeights: boolean }`
705
+ * Returns the items's top offset relative to the top edge of the first item.
706
+ * @param {number} i Item index
707
+ * @return {[number]} Returns `undefined` if any of the previous items haven't been rendered yet.
346
708
  */
347
709
 
348
710
  }, {
349
- key: "getShownItemIndexes",
350
- value: function getShownItemIndexes(_ref5) {
351
- var listHeight = _ref5.listHeight,
352
- itemsCount = _ref5.itemsCount,
353
- visibleAreaIncludingMargins = _ref5.visibleAreaIncludingMargins,
354
- listTopOffsetInsideScrollableContainer = _ref5.listTopOffsetInsideScrollableContainer;
711
+ key: "getItemTopOffset",
712
+ value: function getItemTopOffset(i) {
713
+ var topOffsetInsideScrollableContainer = 0;
714
+ var beforeResizeItemsCount = this.getBeforeResizeItemsCount();
715
+ var beforeResizeRowsCount = beforeResizeItemsCount === 0 ? 0 : Math.ceil(beforeResizeItemsCount / this.getColumnsCountBeforeResize());
716
+ var maxBeforeResizeRowsCount = i < beforeResizeItemsCount ? Math.floor(i / this.getColumnsCountBeforeResize()) : beforeResizeRowsCount;
717
+ var beforeResizeRowIndex = 0;
718
+
719
+ while (beforeResizeRowIndex < maxBeforeResizeRowsCount) {
720
+ var rowHeight = this.getItemHeightBeforeResize(beforeResizeRowIndex * this.getColumnsCountBeforeResize());
721
+ topOffsetInsideScrollableContainer += rowHeight;
722
+ topOffsetInsideScrollableContainer += this.getVerticalSpacingBeforeResize();
723
+ beforeResizeRowIndex++;
724
+ }
355
725
 
356
- if (this.bypass) {
357
- return {
358
- firstShownItemIndex: 0,
359
- lastShownItemIndex: itemsCount - 1
360
- };
361
- } // Finds the indexes of the items that are currently visible
362
- // (or close to being visible) in the scrollable container.
363
- // For scrollable containers other than the main screen, it could also
364
- // check the visibility of such scrollable container itself, because it
365
- // might be not visible.
366
- // If such kind of an optimization would hypothetically be implemented,
367
- // then it would also require listening for "scroll" events on the screen.
368
- // Overall, I suppose that such "actual visibility" feature would be
369
- // a very minor optimization and not something I'd deal with.
370
-
371
-
372
- return this.getItemIndexes(visibleAreaIncludingMargins.top, visibleAreaIncludingMargins.bottom, listTopOffsetInsideScrollableContainer, listHeight, itemsCount) || this.getNonVisibleListShownItemIndexes();
373
- }
374
- }, {
375
- key: "showItemsFromTheStart",
376
- value: function showItemsFromTheStart(layout) {
377
- layout.firstShownItemIndex = 0;
378
- layout.beforeItemsHeight = 0;
726
+ var itemRowIndex = Math.floor((i - beforeResizeItemsCount) / this.getColumnsCount());
727
+ var rowIndex = 0;
728
+
729
+ while (rowIndex < itemRowIndex) {
730
+ var _rowHeight = 0;
731
+ var columnIndex = 0;
732
+
733
+ while (columnIndex < this.getColumnsCount()) {
734
+ var itemHeight = this.getItemHeight(beforeResizeItemsCount + rowIndex * this.getColumnsCount() + columnIndex);
735
+
736
+ if (itemHeight === undefined) {
737
+ return;
738
+ }
739
+
740
+ _rowHeight = Math.max(_rowHeight, itemHeight);
741
+ columnIndex++;
742
+ }
743
+
744
+ topOffsetInsideScrollableContainer += _rowHeight;
745
+ topOffsetInsideScrollableContainer += this.getVerticalSpacing();
746
+ rowIndex++;
747
+ }
748
+
749
+ return topOffsetInsideScrollableContainer;
379
750
  }
380
751
  }]);
381
752
 
@@ -387,9 +758,11 @@ var LAYOUT_REASON = {
387
758
  SCROLL: 'scroll',
388
759
  STOPPED_SCROLLING: 'stopped scrolling',
389
760
  MANUAL: 'manual',
390
- MOUNT: 'mount',
391
- ITEM_HEIGHT_NOT_MEASURED: 'some item height wasn\'t measured',
392
- RESIZE: 'resize',
761
+ MOUNTED: 'mounted',
762
+ ACTUAL_ITEM_HEIGHTS_HAVE_BEEN_MEASURED: 'actual item heights have been measured',
763
+ VIEWPORT_WIDTH_CHANGED: 'viewport width changed',
764
+ VIEWPORT_HEIGHT_CHANGED: 'viewport height changed',
765
+ VIEWPORT_SIZE_UNCHANGED: 'viewport size unchanged',
393
766
  ITEM_HEIGHT_CHANGED: 'item height changed',
394
767
  ITEMS_CHANGED: 'items changed',
395
768
  TOP_OFFSET_CHANGED: 'list top offset changed'