virtual-scroller 1.7.7 → 1.8.1

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