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.
- package/CHANGELOG.md +29 -1
- package/README.md +139 -33
- package/babel.config.js +25 -0
- package/babel.js +5 -0
- package/bundle/index-bypass.html +1 -1
- package/bundle/index-dom.html +1 -1
- package/bundle/index-grid.html +1 -2
- package/bundle/index-scrollableContainer.html +1 -1
- package/bundle/index-tbody-scrollableContainer.html +2 -0
- package/bundle/index-tbody.html +2 -0
- package/bundle/virtual-scroller-dom.js +1 -1
- package/bundle/virtual-scroller-dom.js.map +1 -1
- package/bundle/virtual-scroller-react.js +1 -1
- package/bundle/virtual-scroller-react.js.map +1 -1
- package/bundle/virtual-scroller.js +1 -1
- package/bundle/virtual-scroller.js.map +1 -1
- package/commonjs/BeforeResize.js +319 -0
- package/commonjs/BeforeResize.js.map +1 -0
- package/commonjs/DOM/Engine.js +46 -0
- package/commonjs/DOM/Engine.js.map +1 -0
- package/commonjs/DOM/ItemsContainer.js +78 -0
- package/commonjs/DOM/ItemsContainer.js.map +1 -0
- package/commonjs/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +56 -35
- package/commonjs/DOM/ListTopOffsetWatcher.js.map +1 -0
- package/commonjs/DOM/ScrollableContainer.js +56 -81
- package/commonjs/DOM/ScrollableContainer.js.map +1 -1
- package/commonjs/DOM/VirtualScroller.js +20 -15
- package/commonjs/DOM/VirtualScroller.js.map +1 -1
- package/commonjs/DOM/tbody.js +2 -2
- package/commonjs/ItemHeights.js +13 -20
- package/commonjs/ItemHeights.js.map +1 -1
- package/commonjs/Layout.js +588 -215
- package/commonjs/Layout.js.map +1 -1
- package/commonjs/Layout.test.js +191 -0
- package/commonjs/Layout.test.js.map +1 -0
- package/commonjs/ListHeightChangeWatcher.js +126 -0
- package/commonjs/ListHeightChangeWatcher.js.map +1 -0
- package/commonjs/Resize.js +22 -21
- package/commonjs/Resize.js.map +1 -1
- package/commonjs/Scroll.js +148 -88
- package/commonjs/Scroll.js.map +1 -1
- package/commonjs/VirtualScroller.js +1269 -390
- package/commonjs/VirtualScroller.js.map +1 -1
- package/commonjs/getItemCoordinates.js.map +1 -1
- package/commonjs/getItemsDiff.js.map +1 -1
- package/commonjs/getVerticalSpacing.js +8 -8
- package/commonjs/getVerticalSpacing.js.map +1 -1
- package/commonjs/react/VirtualScroller.js +31 -37
- package/commonjs/react/VirtualScroller.js.map +1 -1
- package/commonjs/utility/debounce.js +26 -4
- package/commonjs/utility/debounce.js.map +1 -1
- package/commonjs/utility/debug.js +51 -12
- package/commonjs/utility/debug.js.map +1 -1
- package/commonjs/utility/getStateSnapshot.js +50 -0
- package/commonjs/utility/getStateSnapshot.js.map +1 -0
- package/commonjs/utility/px.js +1 -1
- package/commonjs/utility/px.js.map +1 -1
- package/commonjs/utility/px.test.js +14 -0
- package/commonjs/utility/px.test.js.map +1 -0
- package/commonjs/utility/shallowEqual.js +1 -1
- package/commonjs/utility/shallowEqual.js.map +1 -1
- package/commonjs/utility/throttle.js.map +1 -1
- package/dom/index.d.ts +23 -0
- package/index.d.ts +84 -0
- package/modules/BeforeResize.js +310 -0
- package/modules/BeforeResize.js.map +1 -0
- package/modules/DOM/Engine.js +27 -0
- package/modules/DOM/Engine.js.map +1 -0
- package/modules/DOM/ItemsContainer.js +71 -0
- package/modules/DOM/ItemsContainer.js.map +1 -0
- package/modules/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +57 -35
- package/modules/DOM/ListTopOffsetWatcher.js.map +1 -0
- package/modules/DOM/ScrollableContainer.js +55 -80
- package/modules/DOM/ScrollableContainer.js.map +1 -1
- package/modules/DOM/VirtualScroller.js +15 -14
- package/modules/DOM/VirtualScroller.js.map +1 -1
- package/modules/ItemHeights.js +8 -19
- package/modules/ItemHeights.js.map +1 -1
- package/modules/Layout.js +582 -213
- package/modules/Layout.js.map +1 -1
- package/modules/Layout.test.js +185 -0
- package/modules/Layout.test.js.map +1 -0
- package/modules/ListHeightChangeWatcher.js +119 -0
- package/modules/ListHeightChangeWatcher.js.map +1 -0
- package/modules/Resize.js +21 -20
- package/modules/Resize.js.map +1 -1
- package/modules/Scroll.js +148 -87
- package/modules/Scroll.js.map +1 -1
- package/modules/VirtualScroller.js +1263 -390
- package/modules/VirtualScroller.js.map +1 -1
- package/modules/getItemCoordinates.js.map +1 -1
- package/modules/getItemsDiff.js.map +1 -1
- package/modules/getVerticalSpacing.js +8 -8
- package/modules/getVerticalSpacing.js.map +1 -1
- package/modules/react/VirtualScroller.js +31 -37
- package/modules/react/VirtualScroller.js.map +1 -1
- package/modules/utility/debounce.js +26 -4
- package/modules/utility/debounce.js.map +1 -1
- package/modules/utility/debug.js +47 -10
- package/modules/utility/debug.js.map +1 -1
- package/modules/utility/getStateSnapshot.js +43 -0
- package/modules/utility/getStateSnapshot.js.map +1 -0
- package/modules/utility/px.js +1 -1
- package/modules/utility/px.js.map +1 -1
- package/modules/utility/px.test.js +9 -0
- package/modules/utility/px.test.js.map +1 -0
- package/modules/utility/shallowEqual.js +1 -1
- package/modules/utility/shallowEqual.js.map +1 -1
- package/modules/utility/throttle.js.map +1 -1
- package/package.json +24 -22
- package/react/index.d.ts +27 -0
- package/source/BeforeResize.js +317 -0
- package/source/DOM/Engine.js +32 -0
- package/source/DOM/ItemsContainer.js +48 -0
- package/source/DOM/{WaitForStylesToLoad.js → ListTopOffsetWatcher.js} +48 -22
- package/source/DOM/ScrollableContainer.js +39 -56
- package/source/DOM/VirtualScroller.js +6 -7
- package/source/ItemHeights.js +10 -15
- package/source/Layout.js +626 -252
- package/source/Layout.test.js +171 -0
- package/source/ListHeightChangeWatcher.js +94 -0
- package/source/Resize.js +23 -15
- package/source/Scroll.js +139 -78
- package/source/VirtualScroller.js +1240 -286
- package/source/getVerticalSpacing.js +7 -7
- package/source/react/VirtualScroller.js +2 -18
- package/source/utility/debounce.js +20 -3
- package/source/utility/debug.js +34 -3
- package/source/utility/getStateSnapshot.js +36 -0
- package/source/utility/px.js +1 -1
- package/source/utility/px.test.js +9 -0
- package/website/index-bypass.html +195 -0
- package/website/index-grid.html +0 -1
- package/website/index-scrollableContainer.html +208 -0
- package/website/index-tbody-scrollableContainer.html +68 -0
- package/website/index-tbody.html +55 -0
- package/commonjs/DOM/RenderingEngine.js +0 -33
- package/commonjs/DOM/RenderingEngine.js.map +0 -1
- package/commonjs/DOM/Screen.js +0 -87
- package/commonjs/DOM/Screen.js.map +0 -1
- package/commonjs/DOM/WaitForStylesToLoad.js.map +0 -1
- package/commonjs/RestoreScroll.js +0 -118
- package/commonjs/RestoreScroll.js.map +0 -1
- package/modules/DOM/RenderingEngine.js +0 -19
- package/modules/DOM/RenderingEngine.js.map +0 -1
- package/modules/DOM/Screen.js +0 -80
- package/modules/DOM/Screen.js.map +0 -1
- package/modules/DOM/WaitForStylesToLoad.js.map +0 -1
- package/modules/RestoreScroll.js +0 -111
- package/modules/RestoreScroll.js.map +0 -1
- package/source/DOM/RenderingEngine.js +0 -22
- package/source/DOM/Screen.js +0 -51
- package/source/RestoreScroll.js +0 -86
|
@@ -1,5 +1,7 @@
|
|
|
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
|
});
|
|
@@ -9,17 +11,17 @@ var _requestAnimationFrameTimeout = require("request-animation-frame-timeout");
|
|
|
9
11
|
|
|
10
12
|
var _tbody = require("./DOM/tbody");
|
|
11
13
|
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
var _WaitForStylesToLoad = _interopRequireDefault(require("./DOM/WaitForStylesToLoad"));
|
|
14
|
+
var _Engine = _interopRequireDefault(require("./DOM/Engine"));
|
|
15
15
|
|
|
16
16
|
var _Layout = _interopRequireWildcard(require("./Layout"));
|
|
17
17
|
|
|
18
18
|
var _Resize = _interopRequireDefault(require("./Resize"));
|
|
19
19
|
|
|
20
|
+
var _BeforeResize = _interopRequireDefault(require("./BeforeResize"));
|
|
21
|
+
|
|
20
22
|
var _Scroll = _interopRequireDefault(require("./Scroll"));
|
|
21
23
|
|
|
22
|
-
var
|
|
24
|
+
var _ListHeightChangeWatcher = _interopRequireDefault(require("./ListHeightChangeWatcher"));
|
|
23
25
|
|
|
24
26
|
var _ItemHeights = _interopRequireDefault(require("./ItemHeights"));
|
|
25
27
|
|
|
@@ -31,11 +33,17 @@ var _debug = _interopRequireWildcard(require("./utility/debug"));
|
|
|
31
33
|
|
|
32
34
|
var _shallowEqual = _interopRequireDefault(require("./utility/shallowEqual"));
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
var _getStateSnapshot = _interopRequireDefault(require("./utility/getStateSnapshot"));
|
|
37
|
+
|
|
38
|
+
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); }
|
|
39
|
+
|
|
40
|
+
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; }
|
|
35
41
|
|
|
36
42
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
37
43
|
|
|
38
|
-
function
|
|
44
|
+
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; }
|
|
45
|
+
|
|
46
|
+
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; }
|
|
39
47
|
|
|
40
48
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
41
49
|
|
|
@@ -45,30 +53,52 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
|
|
|
45
53
|
|
|
46
54
|
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; }
|
|
47
55
|
|
|
48
|
-
var VirtualScroller =
|
|
49
|
-
/*#__PURE__*/
|
|
50
|
-
function () {
|
|
56
|
+
var VirtualScroller = /*#__PURE__*/function () {
|
|
51
57
|
/**
|
|
52
|
-
* @param {function}
|
|
58
|
+
* @param {function} getItemsContainerElement — Returns the container DOM `Element`.
|
|
53
59
|
* @param {any[]} items — The list of items.
|
|
54
60
|
* @param {Object} [options] — See README.md.
|
|
55
61
|
* @return {VirtualScroller}
|
|
56
62
|
*/
|
|
57
|
-
function VirtualScroller(
|
|
63
|
+
function VirtualScroller(getItemsContainerElement, items) {
|
|
58
64
|
var _this = this;
|
|
59
65
|
|
|
60
66
|
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
61
67
|
|
|
62
68
|
_classCallCheck(this, VirtualScroller);
|
|
63
69
|
|
|
70
|
+
_defineProperty(this, "getActualColumnsCountForState", function () {
|
|
71
|
+
return _this._getColumnsCount ? _this._getColumnsCount(_this.scrollableContainer) : undefined;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
_defineProperty(this, "getVerticalSpacing", function () {
|
|
75
|
+
return _this.verticalSpacing || 0;
|
|
76
|
+
});
|
|
77
|
+
|
|
64
78
|
_defineProperty(this, "getListTopOffsetInsideScrollableContainer", function () {
|
|
65
|
-
var listTopOffset = _this.scrollableContainer.
|
|
79
|
+
var listTopOffset = _this.scrollableContainer.getItemsContainerTopOffset();
|
|
66
80
|
|
|
67
|
-
_this.
|
|
81
|
+
if (_this.listTopOffsetWatcher) {
|
|
82
|
+
_this.listTopOffsetWatcher.onListTopOffset(listTopOffset);
|
|
83
|
+
}
|
|
68
84
|
|
|
69
85
|
return listTopOffset;
|
|
70
86
|
});
|
|
71
87
|
|
|
88
|
+
_defineProperty(this, "stop", function () {
|
|
89
|
+
_this.isRendered = false;
|
|
90
|
+
|
|
91
|
+
_this.resize.stop();
|
|
92
|
+
|
|
93
|
+
_this.scroll.stop();
|
|
94
|
+
|
|
95
|
+
if (_this.listTopOffsetWatcher) {
|
|
96
|
+
_this.listTopOffsetWatcher.stop();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_this.cancelLayoutTimer({});
|
|
100
|
+
});
|
|
101
|
+
|
|
72
102
|
_defineProperty(this, "willUpdateState", function (newState, prevState) {
|
|
73
103
|
// Ignore setting initial state.
|
|
74
104
|
if (!prevState) {
|
|
@@ -98,100 +128,165 @@ function () {
|
|
|
98
128
|
}
|
|
99
129
|
|
|
100
130
|
(0, _debug["default"])('~ Rendered ~');
|
|
101
|
-
_this.newItemsPending = undefined;
|
|
102
|
-
_this.layoutResetPending = undefined;
|
|
103
|
-
var redoLayoutReason = _this.redoLayoutReason;
|
|
104
|
-
_this.redoLayoutReason = undefined;
|
|
105
|
-
var previousItems = prevState.items;
|
|
106
|
-
var newItems = newState.items;
|
|
107
131
|
|
|
108
|
-
if (
|
|
109
|
-
|
|
132
|
+
if ((0, _debug.isDebug)()) {
|
|
133
|
+
(0, _debug["default"])('State', (0, _getStateSnapshot["default"])(newState));
|
|
134
|
+
}
|
|
110
135
|
|
|
111
|
-
|
|
136
|
+
var layoutUpdateReason;
|
|
112
137
|
|
|
138
|
+
if (_this.firstNonMeasuredItemIndex !== undefined) {
|
|
139
|
+
layoutUpdateReason = _Layout.LAYOUT_REASON.ACTUAL_ITEM_HEIGHTS_HAVE_BEEN_MEASURED;
|
|
140
|
+
}
|
|
113
141
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
142
|
+
if (_this.resetLayoutAfterResize) {
|
|
143
|
+
layoutUpdateReason = _Layout.LAYOUT_REASON.VIEWPORT_WIDTH_CHANGED;
|
|
144
|
+
} // If `this.resetLayoutAfterResize` flag was reset after calling
|
|
145
|
+
// `this.measureItemHeightsAndSpacingAndUpdateTablePadding()`
|
|
146
|
+
// then there would be a bug because
|
|
147
|
+
// `this.measureItemHeightsAndSpacingAndUpdateTablePadding()`
|
|
148
|
+
// calls `this.setState({ verticalSpacing })` which calls
|
|
149
|
+
// `this.didUpdateState()` immediately, so `this.resetLayoutAfterResize`
|
|
150
|
+
// flag wouldn't be reset by that time and would trigger things
|
|
151
|
+
// like `this.itemHeights.reset()` a second time.
|
|
152
|
+
//
|
|
153
|
+
// So, instead read the value of `this.resetLayoutAfterResize` flag
|
|
154
|
+
// and reset it right away to prevent any such potential bugs.
|
|
155
|
+
//
|
|
117
156
|
|
|
118
|
-
if (prependedItemsCount > 0) {
|
|
119
|
-
// The call to `.onPrepend()` must precede
|
|
120
|
-
// the call to `.measureItemHeights()`
|
|
121
|
-
// which is called in `.onRendered()`.
|
|
122
|
-
_this.itemHeights.onPrepend(prependedItemsCount);
|
|
123
157
|
|
|
124
|
-
|
|
125
|
-
layoutNeedsReCalculating = false;
|
|
126
|
-
(0, _debug["default"])('~ Restore Scroll Position ~');
|
|
158
|
+
var resetLayoutAfterResize = _this.resetLayoutAfterResize; // Reset `this.firstNonMeasuredItemIndex`.
|
|
127
159
|
|
|
128
|
-
|
|
160
|
+
_this.firstNonMeasuredItemIndex = undefined; // Reset `this.resetLayoutAfterResize` flag.
|
|
129
161
|
|
|
130
|
-
|
|
131
|
-
(0, _debug["default"])('Scroll down by', scrollByY);
|
|
162
|
+
_this.resetLayoutAfterResize = undefined; // Reset `this.newItemsWillBeRendered` flag.
|
|
132
163
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
164
|
+
_this.newItemsWillBeRendered = undefined; // Reset `this.itemHeightsThatChangedWhileNewItemsWereBeingRendered`.
|
|
165
|
+
|
|
166
|
+
_this.itemHeightsThatChangedWhileNewItemsWereBeingRendered = undefined; // Reset `this.itemStatesThatChangedWhileNewItemsWereBeingRendered`.
|
|
167
|
+
|
|
168
|
+
_this.itemStatesThatChangedWhileNewItemsWereBeingRendered = undefined;
|
|
169
|
+
|
|
170
|
+
if (resetLayoutAfterResize) {
|
|
171
|
+
// Reset measured item heights on viewport width change.
|
|
172
|
+
_this.itemHeights.reset(); // Reset `verticalSpacing` (will be re-measured).
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
_this.verticalSpacing = undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
var previousItems = prevState.items;
|
|
179
|
+
var newItems = newState.items; // Even if `this.newItemsWillBeRendered` flag is `true`,
|
|
180
|
+
// `newItems` could still be equal to `previousItems`.
|
|
181
|
+
// For example, when `setState()` calls don't update `state` immediately
|
|
182
|
+
// and a developer first calls `setItems(newItems)` and then calls `setItems(oldItems)`:
|
|
183
|
+
// in that case, `this.newItemsWillBeRendered` flag will be `true` but the actual `items`
|
|
184
|
+
// in state wouldn't have changed due to the first `setState()` call being overwritten
|
|
185
|
+
// by the second `setState()` call (that's called "batching state updates" in React).
|
|
186
|
+
|
|
187
|
+
if (newItems !== previousItems) {
|
|
188
|
+
var itemsDiff = _this.getItemsDiff(previousItems, newItems);
|
|
189
|
+
|
|
190
|
+
if (itemsDiff) {
|
|
191
|
+
// The call to `.onPrepend()` must precede the call to `.measureItemHeights()`
|
|
192
|
+
// which is called in `.onRendered()`.
|
|
193
|
+
// `this.itemHeights.onPrepend()` updates `firstMeasuredItemIndex`
|
|
194
|
+
// and `lastMeasuredItemIndex` of `this.itemHeights`.
|
|
195
|
+
var prependedItemsCount = itemsDiff.prependedItemsCount;
|
|
196
|
+
|
|
197
|
+
_this.itemHeights.onPrepend(prependedItemsCount);
|
|
139
198
|
} else {
|
|
140
|
-
_this.itemHeights.reset();
|
|
199
|
+
_this.itemHeights.reset(); // `newState.itemHeights` is an array of `undefined`s.
|
|
141
200
|
|
|
142
|
-
|
|
201
|
+
|
|
202
|
+
_this.itemHeights.initialize(newState.itemHeights);
|
|
143
203
|
}
|
|
144
204
|
|
|
145
|
-
if (
|
|
146
|
-
|
|
205
|
+
if (!resetLayoutAfterResize) {
|
|
206
|
+
// The call to `this.onNewItemsRendered()` must precede the call to
|
|
207
|
+
// `.measureItemHeights()` which is called in `.onRendered()` because
|
|
208
|
+
// `this.onNewItemsRendered()` updates `firstMeasuredItemIndex` and
|
|
209
|
+
// `lastMeasuredItemIndex` of `this.itemHeights` in case of a prepend.
|
|
210
|
+
//
|
|
211
|
+
// If after prepending items the scroll position
|
|
212
|
+
// should be "restored" so that there's no "jump" of content
|
|
213
|
+
// then it means that all previous items have just been rendered
|
|
214
|
+
// in a single pass, and there's no need to update layout again.
|
|
215
|
+
//
|
|
216
|
+
if (_this.onNewItemsRendered(itemsDiff, newState) !== 'SEAMLESS_PREPEND') {
|
|
217
|
+
layoutUpdateReason = _Layout.LAYOUT_REASON.ITEMS_CHANGED;
|
|
218
|
+
}
|
|
147
219
|
}
|
|
148
|
-
}
|
|
220
|
+
}
|
|
149
221
|
|
|
222
|
+
var stateUpdate; // Re-measure item heights.
|
|
223
|
+
// Also, measure vertical spacing (if not measured) and fix `<table/>` padding.
|
|
224
|
+
//
|
|
225
|
+
// This block should go after `if (newItems !== previousItems) {}`
|
|
226
|
+
// because `this.itemHeights` can get `.reset()` there, which would
|
|
227
|
+
// discard all the measurements done here, and having currently shown
|
|
228
|
+
// item height measurements is required.
|
|
229
|
+
//
|
|
150
230
|
|
|
151
|
-
if (newState.firstShownItemIndex !== prevState.firstShownItemIndex || newState.lastShownItemIndex !== prevState.lastShownItemIndex || newState.items !== prevState.items) {
|
|
152
|
-
_this.
|
|
153
|
-
}
|
|
231
|
+
if (newState.firstShownItemIndex !== prevState.firstShownItemIndex || newState.lastShownItemIndex !== prevState.lastShownItemIndex || newState.items !== prevState.items || resetLayoutAfterResize) {
|
|
232
|
+
var verticalSpacingStateUpdate = _this.measureItemHeightsAndSpacingAndUpdateTablePadding();
|
|
154
233
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
234
|
+
if (verticalSpacingStateUpdate) {
|
|
235
|
+
stateUpdate = _objectSpread(_objectSpread({}, stateUpdate), verticalSpacingStateUpdate);
|
|
236
|
+
}
|
|
237
|
+
} // Clean up "before resize" item heights and adjust the scroll position accordingly.
|
|
238
|
+
// Calling `this.beforeResize.cleanUpBeforeResizeItemHeights()` might trigger
|
|
239
|
+
// a `this.setState()` call but that wouldn't matter because `beforeResize`
|
|
240
|
+
// properties have already been modified directly in `state` (a hacky technique)
|
|
161
241
|
|
|
162
|
-
_defineProperty(this, "updateShownItemIndexes", function () {
|
|
163
|
-
(0, _debug["default"])('~ Layout results ' + (_this.bypass ? '(bypass) ' : '') + '~');
|
|
164
242
|
|
|
165
|
-
var
|
|
243
|
+
var cleanedUpBeforeResize = _this.beforeResize.cleanUpBeforeResizeItemHeights(prevState);
|
|
166
244
|
|
|
167
|
-
|
|
245
|
+
if (cleanedUpBeforeResize !== undefined) {
|
|
246
|
+
var scrollBy = cleanedUpBeforeResize.scrollBy,
|
|
247
|
+
beforeResize = cleanedUpBeforeResize.beforeResize;
|
|
248
|
+
(0, _debug["default"])('Correct scroll position by', scrollBy);
|
|
168
249
|
|
|
169
|
-
|
|
250
|
+
_this.scroll.scrollByY(scrollBy);
|
|
170
251
|
|
|
252
|
+
stateUpdate = _objectSpread(_objectSpread({}, stateUpdate), {}, {
|
|
253
|
+
beforeResize: beforeResize
|
|
254
|
+
});
|
|
255
|
+
}
|
|
171
256
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
257
|
+
if (layoutUpdateReason) {
|
|
258
|
+
_this.updateStateRightAfterRender({
|
|
259
|
+
stateUpdate: stateUpdate,
|
|
260
|
+
reason: layoutUpdateReason
|
|
261
|
+
});
|
|
262
|
+
} else if (stateUpdate) {
|
|
263
|
+
_this.setState(stateUpdate);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
_defineProperty(this, "updateShownItemIndexes", function (_ref) {
|
|
268
|
+
var stateUpdate = _ref.stateUpdate;
|
|
269
|
+
var startedAt = Date.now(); // Get shown item indexes.
|
|
270
|
+
|
|
271
|
+
var _this$getShownItemInd = _this.getShownItemIndexes(),
|
|
272
|
+
firstShownItemIndex = _this$getShownItemInd.firstShownItemIndex,
|
|
273
|
+
lastShownItemIndex = _this$getShownItemInd.lastShownItemIndex,
|
|
274
|
+
shownItemsHeight = _this$getShownItemInd.shownItemsHeight,
|
|
275
|
+
firstNonMeasuredItemIndex = _this$getShownItemInd.firstNonMeasuredItemIndex; // If scroll position is scheduled to be restored after render,
|
|
181
276
|
// then the "anchor" item must be rendered, and all of the prepended
|
|
182
277
|
// items before it, all in a single pass. This way, all of the
|
|
183
278
|
// prepended items' heights could be measured right after the render
|
|
184
279
|
// has finished, and the scroll position can then be immediately restored.
|
|
185
280
|
|
|
186
281
|
|
|
187
|
-
if (_this.
|
|
188
|
-
if (lastShownItemIndex < _this.
|
|
189
|
-
lastShownItemIndex = _this.
|
|
282
|
+
if (_this.listHeightChangeWatcher.hasSnapshot()) {
|
|
283
|
+
if (lastShownItemIndex < _this.listHeightChangeWatcher.getAnchorItemIndex()) {
|
|
284
|
+
lastShownItemIndex = _this.listHeightChangeWatcher.getAnchorItemIndex();
|
|
190
285
|
} // `firstShownItemIndex` is always `0` when prepending items.
|
|
191
286
|
// And `lastShownItemIndex` always covers all prepended items in this case.
|
|
192
287
|
// None of the prepended items have been rendered before,
|
|
193
288
|
// so their heights are unknown. The code at the start of this function
|
|
194
|
-
// did therefore set `
|
|
289
|
+
// did therefore set `firstNonMeasuredItemIndex` to non-`undefined`
|
|
195
290
|
// in order to render just the first prepended item in order to
|
|
196
291
|
// measure it, and only then make a decision on how many other
|
|
197
292
|
// prepended items to render. But since we've instructed the code
|
|
@@ -203,7 +298,7 @@ function () {
|
|
|
203
298
|
// right after the first one.
|
|
204
299
|
|
|
205
300
|
|
|
206
|
-
|
|
301
|
+
firstNonMeasuredItemIndex = undefined;
|
|
207
302
|
} // Validate the heights of items to be hidden on next render.
|
|
208
303
|
// For example, a user could click a "Show more" button,
|
|
209
304
|
// or an "Expand YouTube video" button, which would result
|
|
@@ -213,17 +308,27 @@ function () {
|
|
|
213
308
|
|
|
214
309
|
|
|
215
310
|
if (!_this.validateWillBeHiddenItemHeightsAreAccurate(firstShownItemIndex, lastShownItemIndex)) {
|
|
216
|
-
// Redo layout, now with the correct item heights.
|
|
217
|
-
|
|
218
|
-
return _this.updateShownItemIndexes(
|
|
311
|
+
(0, _debug["default"])('~ Because some of the will-be-hidden item heights (listed above) have changed since they\'ve last been measured, redo layout. ~'); // Redo layout, now with the correct item heights.
|
|
312
|
+
|
|
313
|
+
return _this.updateShownItemIndexes({
|
|
314
|
+
stateUpdate: stateUpdate
|
|
315
|
+
});
|
|
219
316
|
} // Measure "before" items height.
|
|
220
317
|
|
|
221
318
|
|
|
222
|
-
var beforeItemsHeight = _this.layout.getBeforeItemsHeight(firstShownItemIndex
|
|
319
|
+
var beforeItemsHeight = _this.layout.getBeforeItemsHeight(firstShownItemIndex); // Measure "after" items height.
|
|
320
|
+
|
|
223
321
|
|
|
322
|
+
var afterItemsHeight = _this.layout.getAfterItemsHeight(lastShownItemIndex, _this.getItemsCount());
|
|
224
323
|
|
|
225
|
-
var
|
|
324
|
+
var layoutDuration = Date.now() - startedAt; // Debugging.
|
|
226
325
|
|
|
326
|
+
(0, _debug["default"])('~ Layout values ' + (_this.bypass ? '(bypass) ' : '') + '~');
|
|
327
|
+
|
|
328
|
+
if (layoutDuration < SLOW_LAYOUT_DURATION) {// log('Calculated in', layoutDuration, 'ms')
|
|
329
|
+
} else {
|
|
330
|
+
(0, _debug.warn)('Layout calculated in', layoutDuration, 'ms');
|
|
331
|
+
}
|
|
227
332
|
|
|
228
333
|
if (_this._getColumnsCount) {
|
|
229
334
|
(0, _debug["default"])('Columns count', _this.getColumnsCount());
|
|
@@ -233,56 +338,123 @@ function () {
|
|
|
233
338
|
(0, _debug["default"])('Last shown item index', lastShownItemIndex);
|
|
234
339
|
(0, _debug["default"])('Before items height', beforeItemsHeight);
|
|
235
340
|
(0, _debug["default"])('After items height (actual or estimated)', afterItemsHeight);
|
|
236
|
-
(0, _debug["default"])('Average item height (
|
|
341
|
+
(0, _debug["default"])('Average item height (used for estimated after items height calculation)', _this.itemHeights.getAverage());
|
|
237
342
|
|
|
238
343
|
if ((0, _debug.isDebug)()) {
|
|
239
344
|
(0, _debug["default"])('Item heights', _this.getState().itemHeights.slice());
|
|
240
345
|
(0, _debug["default"])('Item states', _this.getState().itemStates.slice());
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (redoLayoutAfterMeasuringItemHeights) {
|
|
244
|
-
// `this.redoLayoutReason` will be detected in `didUpdateState()`.
|
|
245
|
-
// `didUpdateState()` is triggered by `this.setState()` below.
|
|
246
|
-
_this.redoLayoutReason = _Layout.LAYOUT_REASON.ITEM_HEIGHT_NOT_MEASURED;
|
|
247
346
|
} // Optionally preload items to be rendered.
|
|
248
347
|
|
|
249
348
|
|
|
250
|
-
_this.onBeforeShowItems(_this.getState().items, _this.getState().itemHeights, firstShownItemIndex, lastShownItemIndex); //
|
|
349
|
+
_this.onBeforeShowItems(_this.getState().items, _this.getState().itemHeights, firstShownItemIndex, lastShownItemIndex); // Set `this.firstNonMeasuredItemIndex`.
|
|
251
350
|
|
|
252
351
|
|
|
253
|
-
_this.
|
|
352
|
+
_this.firstNonMeasuredItemIndex = firstNonMeasuredItemIndex; // Set "previously calculated layout".
|
|
353
|
+
//
|
|
354
|
+
// The "previously calculated layout" feature is not currently used.
|
|
355
|
+
//
|
|
356
|
+
// The current layout snapshot could be stored as a "previously calculated layout" variable
|
|
357
|
+
// so that it could theoretically be used when calculating new layout incrementally
|
|
358
|
+
// rather than from scratch, which would be an optimization.
|
|
359
|
+
//
|
|
360
|
+
// Currently, this feature is not used, and `shownItemsHeight` property
|
|
361
|
+
// is not returned at all, so don't set any "previously calculated layout".
|
|
362
|
+
//
|
|
363
|
+
|
|
364
|
+
if (shownItemsHeight === undefined) {
|
|
365
|
+
_this.previouslyCalculatedLayout = undefined;
|
|
366
|
+
} else {
|
|
367
|
+
// If "previously calculated layout" feature would be implmeneted,
|
|
368
|
+
// then this code would set "previously calculate layout" instance variable.
|
|
369
|
+
//
|
|
370
|
+
// What for would this instance variable be used?
|
|
371
|
+
//
|
|
372
|
+
// Instead of using a `this.previouslyCalculatedLayout` instance variable,
|
|
373
|
+
// this code could use `this.getState()` because it reflects what's currently on screen,
|
|
374
|
+
// but there's a single edge case when it could go out of sync —
|
|
375
|
+
// updating item heights externally via `.onItemHeightChange(i)`.
|
|
376
|
+
//
|
|
377
|
+
// If, for example, an item height was updated externally via `.onItemHeightChange(i)`
|
|
378
|
+
// then `this.getState().itemHeights` would get updated immediately but
|
|
379
|
+
// `this.getState().beforeItemsHeight` or `this.getState().afterItemsHeight`
|
|
380
|
+
// would still correspond to the previous item height, so those would be "stale".
|
|
381
|
+
// On the other hand, same values in `this.previouslyCalculatedLayout` instance variable
|
|
382
|
+
// can also be updated immediately, so they won't go out of sync with the updated item height.
|
|
383
|
+
// That seems the only edge case when using a separate `this.previouslyCalculatedLayout`
|
|
384
|
+
// instance variable instead of using `this.getState()` would theoretically be justified.
|
|
385
|
+
//
|
|
386
|
+
_this.previouslyCalculatedLayout = {
|
|
387
|
+
firstShownItemIndex: firstShownItemIndex,
|
|
388
|
+
lastShownItemIndex: lastShownItemIndex,
|
|
389
|
+
beforeItemsHeight: beforeItemsHeight,
|
|
390
|
+
shownItemsHeight: shownItemsHeight
|
|
391
|
+
};
|
|
392
|
+
} // Update `VirtualScroller` state.
|
|
393
|
+
// `VirtualScroller` automatically re-renders on state updates.
|
|
394
|
+
//
|
|
395
|
+
// All `state` properties updated here should be overwritten in
|
|
396
|
+
// the implementation of `setItems()` and `onResize()` methods
|
|
397
|
+
// so that the `state` is not left in an inconsistent state
|
|
398
|
+
// whenever there're concurrent `setState()` updates that could
|
|
399
|
+
// possibly conflict with one another — instead, those state updates
|
|
400
|
+
// should overwrite each other in terms of priority.
|
|
401
|
+
// These "on scroll" updates have the lowest priority compared to
|
|
402
|
+
// the state updates originating from `setItems()` and `onResize()` methods.
|
|
403
|
+
//
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
_this.setState(_objectSpread({
|
|
254
407
|
firstShownItemIndex: firstShownItemIndex,
|
|
255
408
|
lastShownItemIndex: lastShownItemIndex,
|
|
256
409
|
beforeItemsHeight: beforeItemsHeight,
|
|
257
|
-
afterItemsHeight: afterItemsHeight
|
|
258
|
-
|
|
259
|
-
// averageItemHeight: this.itemHeights.getAverage()
|
|
260
|
-
|
|
261
|
-
});
|
|
410
|
+
afterItemsHeight: afterItemsHeight
|
|
411
|
+
}, stateUpdate));
|
|
262
412
|
});
|
|
263
413
|
|
|
264
|
-
_defineProperty(this, "onUpdateShownItemIndexes", function (
|
|
265
|
-
var reason =
|
|
414
|
+
_defineProperty(this, "onUpdateShownItemIndexes", function (_ref2) {
|
|
415
|
+
var reason = _ref2.reason,
|
|
416
|
+
stateUpdate = _ref2.stateUpdate;
|
|
417
|
+
|
|
418
|
+
// In case of "don't do anything".
|
|
419
|
+
var skip = function skip() {
|
|
420
|
+
if (stateUpdate) {
|
|
421
|
+
_this.setState(stateUpdate);
|
|
422
|
+
}
|
|
423
|
+
}; // If new `items` have been set and are waiting to be applied,
|
|
424
|
+
// or if the viewport width has changed requiring a re-layout,
|
|
425
|
+
// then temporarily stop all other updates like "on scroll" updates.
|
|
426
|
+
// This prevents `state` being inconsistent, because, for example,
|
|
427
|
+
// both `setItems()` and this function could update `VirtualScroller` state
|
|
428
|
+
// and having them operate in parallel could result in incorrectly calculated
|
|
429
|
+
// `beforeItemsHeight` / `afterItemsHeight` / `firstShownItemIndex` /
|
|
430
|
+
// `lastShownItemIndex`, because, when operating in parallel, this function
|
|
431
|
+
// would have different `items` than the `setItems()` function, so their
|
|
432
|
+
// results could diverge.
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
if (_this.newItemsWillBeRendered || _this.resetLayoutAfterResize || _this.isResizing) {
|
|
436
|
+
return skip();
|
|
437
|
+
} // If there're no items then there's no need to re-layout anything.
|
|
438
|
+
|
|
266
439
|
|
|
267
|
-
// If there're no items then there's no need to re-layout anything.
|
|
268
440
|
if (_this.getItemsCount() === 0) {
|
|
269
|
-
return;
|
|
441
|
+
return skip();
|
|
270
442
|
} // Cancel a "re-layout when user stops scrolling" timer.
|
|
271
443
|
|
|
272
444
|
|
|
273
|
-
_this.scroll.
|
|
445
|
+
_this.scroll.cancelScheduledLayout(); // Cancel a re-layout that is scheduled to run at the next "frame",
|
|
274
446
|
// because a re-layout will be performed right now.
|
|
275
447
|
|
|
276
448
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
} // Perform a re-layout.
|
|
281
|
-
|
|
449
|
+
stateUpdate = _this.cancelLayoutTimer({
|
|
450
|
+
stateUpdate: stateUpdate
|
|
451
|
+
}); // Perform a re-layout.
|
|
282
452
|
|
|
283
|
-
(0, _debug["default"])("~
|
|
453
|
+
(0, _debug["default"])("~ Update Layout (on ".concat(reason, ") ~"));
|
|
284
454
|
|
|
285
|
-
_this.updateShownItemIndexes(
|
|
455
|
+
_this.updateShownItemIndexes({
|
|
456
|
+
stateUpdate: stateUpdate
|
|
457
|
+
});
|
|
286
458
|
});
|
|
287
459
|
|
|
288
460
|
_defineProperty(this, "updateLayout", function () {
|
|
@@ -295,12 +467,8 @@ function () {
|
|
|
295
467
|
return _this.updateLayout();
|
|
296
468
|
});
|
|
297
469
|
|
|
298
|
-
var
|
|
299
|
-
setState = options.setState,
|
|
300
|
-
onStateChange = options.onStateChange,
|
|
470
|
+
var onStateChange = options.onStateChange,
|
|
301
471
|
customState = options.customState,
|
|
302
|
-
preserveScrollPositionAtBottomOnMount = options.preserveScrollPositionAtBottomOnMount,
|
|
303
|
-
preserveScrollPositionOfTheBottomOfTheListOnMount = options.preserveScrollPositionOfTheBottomOfTheListOnMount,
|
|
304
472
|
initialScrollPosition = options.initialScrollPosition,
|
|
305
473
|
onScrollPositionChange = options.onScrollPositionChange,
|
|
306
474
|
measureItemsBatchSize = options.measureItemsBatchSize,
|
|
@@ -308,14 +476,17 @@ function () {
|
|
|
308
476
|
getColumnsCount = options.getColumnsCount,
|
|
309
477
|
getItemId = options.getItemId,
|
|
310
478
|
tbody = options.tbody,
|
|
311
|
-
_useTimeoutInRenderLoop = options._useTimeoutInRenderLoop
|
|
479
|
+
_useTimeoutInRenderLoop = options._useTimeoutInRenderLoop,
|
|
480
|
+
_waitForScrollingToStop = options._waitForScrollingToStop;
|
|
481
|
+
var getState = options.getState,
|
|
482
|
+
setState = options.setState;
|
|
312
483
|
var bypass = options.bypass,
|
|
313
484
|
estimatedItemHeight = options.estimatedItemHeight,
|
|
314
485
|
onItemInitialRender = options.onItemInitialRender,
|
|
315
486
|
onItemFirstRender = options.onItemFirstRender,
|
|
316
487
|
scrollableContainer = options.scrollableContainer,
|
|
317
488
|
state = options.state,
|
|
318
|
-
|
|
489
|
+
engine = options.engine;
|
|
319
490
|
(0, _debug["default"])('~ Initialize ~'); // If `state` is passed then use `items` from `state`
|
|
320
491
|
// instead of the `items` argument.
|
|
321
492
|
|
|
@@ -331,23 +502,42 @@ function () {
|
|
|
331
502
|
// For example, React Native, `<canvas/>`, etc.
|
|
332
503
|
|
|
333
504
|
|
|
334
|
-
if (!
|
|
335
|
-
|
|
505
|
+
if (!engine) {
|
|
506
|
+
engine = _Engine["default"];
|
|
507
|
+
} // Sometimes, when `new VirtualScroller()` instance is created,
|
|
508
|
+
// `getItemsContainerElement()` might not be ready to return the "container" DOM Element yet
|
|
509
|
+
// (for example, because it's not rendered yet). That's the reason why it's a getter function.
|
|
510
|
+
// For example, in React `<VirtualScroller/>` component, a `VirtualScroller`
|
|
511
|
+
// instance is created in the React component's `constructor()`, and at that time
|
|
512
|
+
// the container Element is not yet available. The container Element is available
|
|
513
|
+
// in `componentDidMount()`, but `componentDidMount()` is not executed on server,
|
|
514
|
+
// which would mean that React `<VirtualScroller/>` wouldn't render at all
|
|
515
|
+
// on server side, while with the `getItemsContainerElement()` approach, on server side,
|
|
516
|
+
// it still "renders" a list with a predefined amount of items in it by default.
|
|
517
|
+
// (`initiallyRenderedItemsCount`, or `1`).
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
this.getItemsContainerElement = getItemsContainerElement;
|
|
521
|
+
this.itemsContainer = engine.createItemsContainer(getItemsContainerElement); // Remove any accidental text nodes from container (like whitespace).
|
|
522
|
+
// Also guards against cases when someone accidentally tries
|
|
523
|
+
// using `VirtualScroller` on a non-empty element.
|
|
524
|
+
|
|
525
|
+
if (getItemsContainerElement()) {
|
|
526
|
+
this.itemsContainer.clear();
|
|
336
527
|
}
|
|
337
528
|
|
|
338
|
-
this.
|
|
339
|
-
|
|
340
|
-
// // Renders items which are outside of the screen by this "margin".
|
|
529
|
+
this.scrollableContainer = engine.createScrollableContainer(scrollableContainer, getItemsContainerElement); // if (prerenderMargin === undefined) {
|
|
530
|
+
// // Renders items which are outside of the screen by this "prerender margin".
|
|
341
531
|
// // Is the screen height by default: seems to be the optimal value
|
|
342
532
|
// // for "Page Up" / "Page Down" navigation and optimized mouse wheel scrolling.
|
|
343
|
-
//
|
|
533
|
+
// prerenderMargin = this.scrollableContainer ? this.scrollableContainer.getHeight() : 0
|
|
344
534
|
// }
|
|
345
535
|
// Work around `<tbody/>` not being able to have `padding`.
|
|
346
536
|
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/1
|
|
347
537
|
|
|
348
538
|
if (tbody) {
|
|
349
|
-
if (
|
|
350
|
-
throw new Error('`tbody` option is only supported for DOM rendering engine');
|
|
539
|
+
if (engine !== _Engine["default"]) {
|
|
540
|
+
throw new Error('[virtual-scroller] `tbody` option is only supported for DOM rendering engine');
|
|
351
541
|
}
|
|
352
542
|
|
|
353
543
|
(0, _debug["default"])('~ <tbody/> detected ~');
|
|
@@ -394,7 +584,7 @@ function () {
|
|
|
394
584
|
};
|
|
395
585
|
}
|
|
396
586
|
|
|
397
|
-
this.initialItems = items; // this.
|
|
587
|
+
this.initialItems = items; // this.prerenderMargin = prerenderMargin
|
|
398
588
|
|
|
399
589
|
this.onStateChange = onStateChange;
|
|
400
590
|
this._getColumnsCount = getColumnsCount;
|
|
@@ -403,95 +593,149 @@ function () {
|
|
|
403
593
|
this.onItemInitialRender = onItemInitialRender;
|
|
404
594
|
} // `onItemFirstRender(i)` is deprecated, use `onItemInitialRender(item)` instead.
|
|
405
595
|
else if (onItemFirstRender) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
596
|
+
this.onItemInitialRender = function (item) {
|
|
597
|
+
(0, _debug.warn)('`onItemFirstRender(i)` is deprecated, use `onItemInitialRender(item)` instead.');
|
|
598
|
+
|
|
599
|
+
var _this$getState = _this.getState(),
|
|
600
|
+
items = _this$getState.items;
|
|
601
|
+
|
|
602
|
+
var i = items.indexOf(item); // The `item` could also be non-found due to the inconsistency bug:
|
|
603
|
+
// The reason is that `i` can be non-consistent with the `items`
|
|
604
|
+
// passed to `<VirtualScroller/>` in React due to `setState()` not being
|
|
605
|
+
// instanteneous: when new `items` are passed to `<VirtualScroller/>`,
|
|
606
|
+
// `VirtualScroller.setState({ items })` is called, and if `onItemFirstRender(i)`
|
|
607
|
+
// is called after the aforementioned `setState()` is called but before it finishes,
|
|
608
|
+
// `i` would point to an index in "previous" `items` while the application
|
|
609
|
+
// would assume that `i` points to an index in the "new" `items`,
|
|
610
|
+
// resulting in an incorrect item being assumed by the application
|
|
611
|
+
// or even in an "array index out of bounds" error.
|
|
612
|
+
|
|
613
|
+
if (i >= 0) {
|
|
614
|
+
onItemFirstRender(i);
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
428
618
|
|
|
429
619
|
(0, _debug["default"])('Items count', items.length);
|
|
430
620
|
|
|
431
621
|
if (estimatedItemHeight) {
|
|
432
622
|
(0, _debug["default"])('Estimated item height', estimatedItemHeight);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
623
|
+
} // There're three main places where state is updated:
|
|
624
|
+
//
|
|
625
|
+
// * On scroll.
|
|
626
|
+
// * On window resize.
|
|
627
|
+
// * On set new items.
|
|
628
|
+
//
|
|
629
|
+
// State updates may be "asynchronous" (like in React), in which case the
|
|
630
|
+
// corresponding operation is "pending" until the state update is applied.
|
|
631
|
+
//
|
|
632
|
+
// If there's a "pending" window resize or a "pending" update of the set of items,
|
|
633
|
+
// then "on scroll" updates aren't dispatched.
|
|
634
|
+
//
|
|
635
|
+
// If there's a "pending" on scroll update and the window is resize or a new set
|
|
636
|
+
// of items is set, then that "pending" on scroll update gets overwritten.
|
|
637
|
+
//
|
|
638
|
+
// If there's a "pending" update of the set of items, then window resize handler
|
|
639
|
+
// sees that "pending" update and dispatches its own state update so that the
|
|
640
|
+
// "pending" state update originating from `setItems()` is not lost.
|
|
641
|
+
//
|
|
642
|
+
// If there's a "pending" window resize, and a new set of items is set,
|
|
643
|
+
// then the state update of the window resize handler gets overwritten.
|
|
644
|
+
// Create default `getState()`/`setState()` functions.
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
if (!getState) {
|
|
648
|
+
getState = function getState() {
|
|
447
649
|
return _this.state;
|
|
448
650
|
};
|
|
449
651
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
var prevState =
|
|
652
|
+
setState = function setState(stateUpdate, _ref3) {
|
|
653
|
+
var willUpdateState = _ref3.willUpdateState,
|
|
654
|
+
didUpdateState = _ref3.didUpdateState;
|
|
655
|
+
var prevState = getState(); // Because this variant of `.setState()` is "synchronous" (immediate),
|
|
454
656
|
// it can be written like `...prevState`, and no state updates would be lost.
|
|
455
657
|
// But if it was "asynchronous" (not immediate), then `...prevState`
|
|
456
658
|
// wouldn't work in all cases, because it could be stale in cases
|
|
457
659
|
// when more than a single `setState()` call is made before
|
|
458
660
|
// the state actually updates, making `prevState` stale.
|
|
459
661
|
|
|
662
|
+
var newState = _objectSpread(_objectSpread({}, prevState), stateUpdate);
|
|
460
663
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
664
|
+
willUpdateState(newState, prevState);
|
|
665
|
+
_this.state = newState; // // Is only used in tests.
|
|
666
|
+
// if (this._onStateUpdate) {
|
|
667
|
+
// this._onStateUpdate(stateUpdate)
|
|
668
|
+
// }
|
|
464
669
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
_this.didUpdateState(prevState);
|
|
670
|
+
didUpdateState(prevState);
|
|
468
671
|
};
|
|
469
672
|
}
|
|
470
673
|
|
|
674
|
+
this.getState = getState;
|
|
675
|
+
|
|
676
|
+
this.setState = function (stateUpdate) {
|
|
677
|
+
if ((0, _debug.isDebug)()) {
|
|
678
|
+
(0, _debug["default"])('Set state', (0, _getStateSnapshot["default"])(stateUpdate));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
setState(stateUpdate, {
|
|
682
|
+
willUpdateState: _this.willUpdateState,
|
|
683
|
+
didUpdateState: _this.didUpdateState
|
|
684
|
+
});
|
|
685
|
+
};
|
|
686
|
+
|
|
471
687
|
if (state) {
|
|
472
|
-
(0, _debug
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
//
|
|
476
|
-
// For example,
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
//
|
|
483
|
-
//
|
|
688
|
+
if ((0, _debug.isDebug)()) {
|
|
689
|
+
(0, _debug["default"])('Initial state (passed)', (0, _getStateSnapshot["default"])(state));
|
|
690
|
+
}
|
|
691
|
+
} // Check if the current `columnsCount` matches the one from state.
|
|
692
|
+
// For example, a developer might snapshot `VirtualScroller` state
|
|
693
|
+
// when the user navigates from the page containing the list
|
|
694
|
+
// in order to later restore the list's state when the user goes "Back".
|
|
695
|
+
// But, the user might have also resized the window while being on that
|
|
696
|
+
// "other" page, and when they come "Back", their snapshotted state
|
|
697
|
+
// no longer qualifies. Well, it does qualify, but only partially.
|
|
698
|
+
// For example, `itemStates` are still valid, but first and last shown
|
|
699
|
+
// item indexes aren't.
|
|
484
700
|
|
|
485
701
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
702
|
+
if (state) {
|
|
703
|
+
var shouldResetLayout;
|
|
704
|
+
var columnsCountForState = this.getActualColumnsCountForState();
|
|
705
|
+
|
|
706
|
+
if (columnsCountForState !== state.columnsCount) {
|
|
707
|
+
(0, _debug.warn)('~ Columns Count changed from', state.columnsCount || 1, 'to', columnsCountForState || 1, '~');
|
|
708
|
+
shouldResetLayout = true;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
var columnsCount = this.getActualColumnsCount();
|
|
712
|
+
var firstShownItemIndex = Math.floor(state.firstShownItemIndex / columnsCount) * columnsCount;
|
|
713
|
+
|
|
714
|
+
if (firstShownItemIndex !== state.firstShownItemIndex) {
|
|
715
|
+
(0, _debug.warn)('~ First Shown Item Index', state.firstShownItemIndex, 'is not divisible by Columns Count', columnsCount, '~');
|
|
716
|
+
shouldResetLayout = true;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (shouldResetLayout) {
|
|
720
|
+
(0, _debug.warn)('Reset Layout');
|
|
721
|
+
state = _objectSpread(_objectSpread({}, state), this.getInitialLayoutState(state.items));
|
|
722
|
+
}
|
|
723
|
+
} // Reset `verticalSpacing` so that it re-measures it after the list
|
|
724
|
+
// has been rendered initially. The rationale is that the `state`
|
|
725
|
+
// can't be "trusted" in a sense that the user might have resized
|
|
726
|
+
// their window after the `state` has been snapshotted, and changing
|
|
727
|
+
// window width might have activated different CSS `@media()` "queries"
|
|
728
|
+
// resulting in a potentially different vertical spacing.
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
if (state) {
|
|
732
|
+
state = _objectSpread(_objectSpread({}, state), {}, {
|
|
733
|
+
verticalSpacing: undefined
|
|
734
|
+
});
|
|
735
|
+
} // Create `ItemHeights` instance.
|
|
489
736
|
|
|
490
|
-
if (getContainerElement()) {
|
|
491
|
-
this.screen.clearElement(getContainerElement());
|
|
492
|
-
}
|
|
493
737
|
|
|
494
|
-
this.itemHeights = new _ItemHeights["default"](this.
|
|
738
|
+
this.itemHeights = new _ItemHeights["default"](this.itemsContainer, function (i) {
|
|
495
739
|
return _this.getState().itemHeights[i];
|
|
496
740
|
}, function (i, height) {
|
|
497
741
|
return _this.getState().itemHeights[i] = height;
|
|
@@ -505,57 +749,88 @@ function () {
|
|
|
505
749
|
bypass: bypass,
|
|
506
750
|
estimatedItemHeight: estimatedItemHeight,
|
|
507
751
|
measureItemsBatchSize: measureItemsBatchSize === undefined ? 50 : measureItemsBatchSize,
|
|
752
|
+
getPrerenderMargin: function getPrerenderMargin() {
|
|
753
|
+
return _this.getPrerenderMargin();
|
|
754
|
+
},
|
|
508
755
|
getVerticalSpacing: function getVerticalSpacing() {
|
|
509
756
|
return _this.getVerticalSpacing();
|
|
510
757
|
},
|
|
758
|
+
getVerticalSpacingBeforeResize: function getVerticalSpacingBeforeResize() {
|
|
759
|
+
return _this.getVerticalSpacingBeforeResize();
|
|
760
|
+
},
|
|
511
761
|
getColumnsCount: function getColumnsCount() {
|
|
512
762
|
return _this.getColumnsCount();
|
|
513
763
|
},
|
|
764
|
+
getColumnsCountBeforeResize: function getColumnsCountBeforeResize() {
|
|
765
|
+
return _this.getState().beforeResize && _this.getState().beforeResize.columnsCount;
|
|
766
|
+
},
|
|
514
767
|
getItemHeight: function getItemHeight(i) {
|
|
515
768
|
return _this.getState().itemHeights[i];
|
|
516
769
|
},
|
|
770
|
+
getItemHeightBeforeResize: function getItemHeightBeforeResize(i) {
|
|
771
|
+
return _this.getState().beforeResize && _this.getState().beforeResize.itemHeights[i];
|
|
772
|
+
},
|
|
773
|
+
getBeforeResizeItemsCount: function getBeforeResizeItemsCount() {
|
|
774
|
+
return _this.getState().beforeResize ? _this.getState().beforeResize.itemHeights.length : 0;
|
|
775
|
+
},
|
|
517
776
|
getAverageItemHeight: function getAverageItemHeight() {
|
|
518
777
|
return _this.itemHeights.getAverage();
|
|
778
|
+
},
|
|
779
|
+
getMaxVisibleAreaHeight: function getMaxVisibleAreaHeight() {
|
|
780
|
+
return _this.scrollableContainer && _this.scrollableContainer.getHeight();
|
|
781
|
+
},
|
|
782
|
+
//
|
|
783
|
+
// The "previously calculated layout" feature is not currently used.
|
|
784
|
+
//
|
|
785
|
+
// The current layout snapshot could be stored as a "previously calculated layout" variable
|
|
786
|
+
// so that it could theoretically be used when calculating new layout incrementally
|
|
787
|
+
// rather than from scratch, which would be an optimization.
|
|
788
|
+
//
|
|
789
|
+
getPreviouslyCalculatedLayout: function getPreviouslyCalculatedLayout() {
|
|
790
|
+
return _this.previouslyCalculatedLayout;
|
|
519
791
|
}
|
|
520
792
|
});
|
|
521
793
|
this.resize = new _Resize["default"]({
|
|
522
794
|
bypass: bypass,
|
|
523
795
|
scrollableContainer: this.scrollableContainer,
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
796
|
+
onStart: function onStart() {
|
|
797
|
+
(0, _debug["default"])('~ Scrollable container resize started ~');
|
|
798
|
+
_this.isResizing = true;
|
|
799
|
+
},
|
|
800
|
+
onStop: function onStop() {
|
|
801
|
+
(0, _debug["default"])('~ Scrollable container resize finished ~');
|
|
802
|
+
_this.isResizing = undefined;
|
|
803
|
+
},
|
|
804
|
+
onNoChange: function onNoChange() {
|
|
805
|
+
// There might have been some missed `this.onUpdateShownItemIndexes()` calls
|
|
806
|
+
// due to setting `this.isResizing` flag to `true` during the resize.
|
|
807
|
+
// So, update shown item indexes just in case.
|
|
808
|
+
_this.onUpdateShownItemIndexes({
|
|
809
|
+
reason: _Layout.LAYOUT_REASON.VIEWPORT_SIZE_UNCHANGED
|
|
810
|
+
});
|
|
811
|
+
},
|
|
812
|
+
onHeightChange: function onHeightChange() {
|
|
527
813
|
return _this.onUpdateShownItemIndexes({
|
|
528
|
-
reason:
|
|
814
|
+
reason: _Layout.LAYOUT_REASON.VIEWPORT_HEIGHT_CHANGED
|
|
529
815
|
});
|
|
530
816
|
},
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
// has changed, then the list width (or height) most likely also has changed,
|
|
534
|
-
// and also some CSS `@media()` rules might have been added or removed.
|
|
535
|
-
// So re-render the list entirely.
|
|
536
|
-
(0, _debug["default"])('~ Scrollable container size changed, re-measure item heights. ~');
|
|
537
|
-
_this.redoLayoutReason = _Layout.LAYOUT_REASON.RESIZE; // `this.layoutResetPending` flag will be cleared in `didUpdateState()`.
|
|
538
|
-
|
|
539
|
-
_this.layoutResetPending = true;
|
|
540
|
-
(0, _debug["default"])('Reset state'); // Calling `this.setState(state)` will trigger `didUpdateState()`.
|
|
541
|
-
// `didUpdateState()` will detect `this.redoLayoutReason`.
|
|
817
|
+
onWidthChange: function onWidthChange(prevWidth, newWidth) {
|
|
818
|
+
(0, _debug["default"])('~ Scrollable container width changed from', prevWidth, 'to', newWidth, '~');
|
|
542
819
|
|
|
543
|
-
_this.
|
|
820
|
+
_this.onResize();
|
|
544
821
|
}
|
|
545
822
|
});
|
|
546
|
-
|
|
547
|
-
if (preserveScrollPositionAtBottomOnMount) {
|
|
548
|
-
(0, _debug.warn)('`preserveScrollPositionAtBottomOnMount` option/property has been renamed to `preserveScrollPositionOfTheBottomOfTheListOnMount`');
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
this.preserveScrollPositionOfTheBottomOfTheListOnMount = preserveScrollPositionOfTheBottomOfTheListOnMount || preserveScrollPositionAtBottomOnMount;
|
|
552
823
|
this.scroll = new _Scroll["default"]({
|
|
553
824
|
bypass: this.bypass,
|
|
554
825
|
scrollableContainer: this.scrollableContainer,
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
826
|
+
itemsContainer: this.itemsContainer,
|
|
827
|
+
waitForScrollingToStop: _waitForScrollingToStop,
|
|
828
|
+
onScroll: function onScroll() {
|
|
829
|
+
var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
|
|
830
|
+
delayed = _ref4.delayed;
|
|
831
|
+
|
|
832
|
+
_this.onUpdateShownItemIndexes({
|
|
833
|
+
reason: delayed ? _Layout.LAYOUT_REASON.STOPPED_SCROLLING : _Layout.LAYOUT_REASON.SCROLL
|
|
559
834
|
});
|
|
560
835
|
},
|
|
561
836
|
initialScrollPosition: initialScrollPosition,
|
|
@@ -569,24 +844,53 @@ function () {
|
|
|
569
844
|
hasNonRenderedItemsAtTheBottom: function hasNonRenderedItemsAtTheBottom() {
|
|
570
845
|
return _this.getState().lastShownItemIndex < _this.getItemsCount() - 1;
|
|
571
846
|
},
|
|
572
|
-
|
|
573
|
-
return _this.
|
|
847
|
+
getLatestLayoutVisibleArea: function getLatestLayoutVisibleArea() {
|
|
848
|
+
return _this.latestLayoutVisibleArea;
|
|
574
849
|
},
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
getContainerElement: this.getContainerElement
|
|
850
|
+
getListTopOffset: this.getListTopOffsetInsideScrollableContainer,
|
|
851
|
+
getPrerenderMargin: function getPrerenderMargin() {
|
|
852
|
+
return _this.getPrerenderMargin();
|
|
853
|
+
}
|
|
580
854
|
});
|
|
581
|
-
this.
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
return _this.onUpdateShownItemIndexes({
|
|
585
|
-
reason: reason
|
|
586
|
-
});
|
|
587
|
-
},
|
|
588
|
-
getListTopOffsetInsideScrollableContainer: this.getListTopOffsetInsideScrollableContainer
|
|
855
|
+
this.listHeightChangeWatcher = new _ListHeightChangeWatcher["default"]({
|
|
856
|
+
itemsContainer: this.itemsContainer,
|
|
857
|
+
getListTopOffset: this.getListTopOffsetInsideScrollableContainer
|
|
589
858
|
});
|
|
859
|
+
|
|
860
|
+
if (engine.watchListTopOffset) {
|
|
861
|
+
this.listTopOffsetWatcher = engine.watchListTopOffset({
|
|
862
|
+
getListTopOffset: this.getListTopOffsetInsideScrollableContainer,
|
|
863
|
+
onListTopOffsetChange: function onListTopOffsetChange(_ref5) {
|
|
864
|
+
var reason = _ref5.reason;
|
|
865
|
+
return _this.onUpdateShownItemIndexes({
|
|
866
|
+
reason: _Layout.LAYOUT_REASON.TOP_OFFSET_CHANGED
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
this.beforeResize = new _BeforeResize["default"]({
|
|
873
|
+
getState: this.getState,
|
|
874
|
+
getVerticalSpacing: this.getVerticalSpacing,
|
|
875
|
+
getColumnsCount: this.getColumnsCount
|
|
876
|
+
}); // Possibly clean up "before resize" property in state.
|
|
877
|
+
// "Before resize" state property is cleaned up when all "before resize" item heights
|
|
878
|
+
// have been re-measured in an asynchronous `this.setState({ beforeResize: undefined })` call.
|
|
879
|
+
// If `VirtualScroller` state was snapshotted externally before that `this.setState()` call
|
|
880
|
+
// has been applied, then "before resize" property might have not been cleaned up properly.
|
|
881
|
+
|
|
882
|
+
this.beforeResize.onInitialState(state); // `this.verticalSpacing` acts as a "true" source for vertical spacing value.
|
|
883
|
+
// Vertical spacing is also stored in `state` but `state` updates could be
|
|
884
|
+
// "asynchronous" (not applied immediately) and `this.onUpdateShownItemIndexes()`
|
|
885
|
+
// requires vertical spacing to be correct at any time, without any delays.
|
|
886
|
+
// So, vertical spacing is also duplicated in `state`, but the "true" source
|
|
887
|
+
// is still `this.verticalSpacing`.
|
|
888
|
+
//
|
|
889
|
+
// `this.verticalSpacing` must be initialized before calling `this.getInitialState()`.
|
|
890
|
+
//
|
|
891
|
+
|
|
892
|
+
this.verticalSpacing = state ? state.verticalSpacing : undefined; // Set initial `state`.
|
|
893
|
+
|
|
590
894
|
this.setState(state || this.getInitialState(customState));
|
|
591
895
|
}
|
|
592
896
|
/**
|
|
@@ -601,63 +905,64 @@ function () {
|
|
|
601
905
|
value: function getInitialState(customState) {
|
|
602
906
|
var items = this.initialItems;
|
|
603
907
|
|
|
604
|
-
var state = _objectSpread({}, customState, this.getInitialLayoutState(items), {
|
|
908
|
+
var state = _objectSpread(_objectSpread(_objectSpread({}, customState), this.getInitialLayoutState(items)), {}, {
|
|
605
909
|
items: items,
|
|
606
910
|
itemStates: new Array(items.length)
|
|
607
911
|
});
|
|
608
912
|
|
|
609
|
-
(0, _debug
|
|
913
|
+
if ((0, _debug.isDebug)()) {
|
|
914
|
+
(0, _debug["default"])('Initial state (autogenerated)', (0, _getStateSnapshot["default"])(state));
|
|
915
|
+
}
|
|
916
|
+
|
|
610
917
|
(0, _debug["default"])('First shown item index', state.firstShownItemIndex);
|
|
611
918
|
(0, _debug["default"])('Last shown item index', state.lastShownItemIndex);
|
|
612
919
|
return state;
|
|
613
920
|
}
|
|
614
|
-
}, {
|
|
615
|
-
key: "getInitialLayoutValues",
|
|
616
|
-
value: function getInitialLayoutValues(_ref5) {
|
|
617
|
-
var itemsCount = _ref5.itemsCount,
|
|
618
|
-
bypass = _ref5.bypass;
|
|
619
|
-
return this.layout.getInitialLayoutValues({
|
|
620
|
-
bypass: bypass,
|
|
621
|
-
itemsCount: itemsCount,
|
|
622
|
-
visibleAreaHeightIncludingMargins: this.scrollableContainer && 2 * this.getMargin() + this.scrollableContainer.getHeight()
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
921
|
}, {
|
|
626
922
|
key: "getInitialLayoutState",
|
|
627
923
|
value: function getInitialLayoutState(items) {
|
|
628
924
|
var itemsCount = items.length;
|
|
629
925
|
|
|
630
|
-
var _this$
|
|
926
|
+
var _this$layout$getIniti = this.layout.getInitialLayoutValues({
|
|
631
927
|
itemsCount: itemsCount,
|
|
632
|
-
|
|
928
|
+
columnsCount: this.getColumnsCount()
|
|
633
929
|
}),
|
|
634
|
-
firstShownItemIndex = _this$
|
|
635
|
-
lastShownItemIndex = _this$
|
|
636
|
-
beforeItemsHeight = _this$
|
|
637
|
-
afterItemsHeight = _this$
|
|
930
|
+
firstShownItemIndex = _this$layout$getIniti.firstShownItemIndex,
|
|
931
|
+
lastShownItemIndex = _this$layout$getIniti.lastShownItemIndex,
|
|
932
|
+
beforeItemsHeight = _this$layout$getIniti.beforeItemsHeight,
|
|
933
|
+
afterItemsHeight = _this$layout$getIniti.afterItemsHeight;
|
|
638
934
|
|
|
639
935
|
var itemHeights = new Array(itemsCount); // Optionally preload items to be rendered.
|
|
640
936
|
|
|
641
|
-
this.onBeforeShowItems(items, itemHeights, firstShownItemIndex, lastShownItemIndex);
|
|
642
|
-
// because `this.setState()` gets called with this state on window resize,
|
|
643
|
-
// when `VirtualScroller` gets reset.
|
|
644
|
-
// Item states aren't included here because the state of all items should be
|
|
645
|
-
// preserved on window resize.
|
|
646
|
-
|
|
937
|
+
this.onBeforeShowItems(items, itemHeights, firstShownItemIndex, lastShownItemIndex);
|
|
647
938
|
return {
|
|
648
939
|
itemHeights: itemHeights,
|
|
649
|
-
columnsCount: this.
|
|
650
|
-
verticalSpacing:
|
|
940
|
+
columnsCount: this.getActualColumnsCountForState(),
|
|
941
|
+
verticalSpacing: this.verticalSpacing,
|
|
651
942
|
firstShownItemIndex: firstShownItemIndex,
|
|
652
943
|
lastShownItemIndex: lastShownItemIndex,
|
|
653
944
|
beforeItemsHeight: beforeItemsHeight,
|
|
654
945
|
afterItemsHeight: afterItemsHeight
|
|
655
946
|
};
|
|
656
|
-
}
|
|
947
|
+
} // Bind to `this` in order to prevent bugs when this function is passed by reference
|
|
948
|
+
// and then called with its `this` being unintentionally `window` resulting in
|
|
949
|
+
// the `if` condition being "falsy".
|
|
950
|
+
|
|
951
|
+
}, {
|
|
952
|
+
key: "getActualColumnsCount",
|
|
953
|
+
value: function getActualColumnsCount() {
|
|
954
|
+
return this.getActualColumnsCountForState() || 1;
|
|
955
|
+
} // Bind to `this` in order to prevent bugs when this function is passed by reference
|
|
956
|
+
// and then called with its `this` being unintentionally `window` resulting in
|
|
957
|
+
// the `if` condition being "falsy".
|
|
958
|
+
|
|
657
959
|
}, {
|
|
658
|
-
key: "
|
|
659
|
-
value: function
|
|
660
|
-
|
|
960
|
+
key: "getVerticalSpacingBeforeResize",
|
|
961
|
+
value: function getVerticalSpacingBeforeResize() {
|
|
962
|
+
// `beforeResize.verticalSpacing` can be `undefined`.
|
|
963
|
+
// For example, if `this.setState({ verticalSpacing })` call hasn't been applied
|
|
964
|
+
// before the resize happened (in case of an "asynchronous" state update).
|
|
965
|
+
return this.getState().beforeResize && this.getState().beforeResize.verticalSpacing || 0;
|
|
661
966
|
}
|
|
662
967
|
}, {
|
|
663
968
|
key: "getColumnsCount",
|
|
@@ -670,15 +975,19 @@ function () {
|
|
|
670
975
|
return this.getState().items.length;
|
|
671
976
|
}
|
|
672
977
|
}, {
|
|
673
|
-
key: "
|
|
674
|
-
value: function
|
|
675
|
-
//
|
|
676
|
-
//
|
|
677
|
-
//
|
|
978
|
+
key: "getPrerenderMargin",
|
|
979
|
+
value: function getPrerenderMargin() {
|
|
980
|
+
// The list component renders not only the items that're currently visible
|
|
981
|
+
// but also the items that lie within some extra vertical margin (called
|
|
982
|
+
// "prerender margin") on top and bottom for future scrolling: this way,
|
|
983
|
+
// there'll be significantly less layout recalculations as the user scrolls,
|
|
984
|
+
// because now it doesn't have to recalculate layout on each scroll event.
|
|
985
|
+
// By default, the "prerender margin" is equal to the screen height:
|
|
678
986
|
// this seems to be the optimal value for "Page Up" / "Page Down" navigation
|
|
679
987
|
// and optimized mouse wheel scrolling (a user is unlikely to continuously
|
|
680
|
-
// scroll past the height
|
|
681
|
-
// the
|
|
988
|
+
// scroll past the screen height, because they'd stop to read through
|
|
989
|
+
// the newly visible items first, and when they do stop scrolling, that's
|
|
990
|
+
// when layout gets recalculated).
|
|
682
991
|
var renderAheadMarginRatio = 1; // in scrollable container heights.
|
|
683
992
|
|
|
684
993
|
return this.scrollableContainer.getHeight() * renderAheadMarginRatio;
|
|
@@ -734,51 +1043,81 @@ function () {
|
|
|
734
1043
|
// otherwise `DOMVirtualScroller` would enter an infinite re-render loop.
|
|
735
1044
|
|
|
736
1045
|
this.isRendered = true;
|
|
737
|
-
this.
|
|
1046
|
+
var stateUpdate = this.measureItemHeightsAndSpacingAndUpdateTablePadding();
|
|
738
1047
|
this.resize.listen();
|
|
739
1048
|
this.scroll.listen(); // Work around `<tbody/>` not being able to have `padding`.
|
|
740
1049
|
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/1
|
|
741
1050
|
|
|
742
1051
|
if (this.tbody) {
|
|
743
|
-
(0, _tbody.addTbodyStyles)(this.
|
|
744
|
-
}
|
|
1052
|
+
(0, _tbody.addTbodyStyles)(this.getItemsContainerElement());
|
|
1053
|
+
} // Re-calculate layout and re-render the list.
|
|
1054
|
+
// Do that even if when an initial `state` parameter, containing layout values,
|
|
1055
|
+
// has been passed. The reason is that the `state` parameter can't be "trusted"
|
|
1056
|
+
// in a way that it could have been snapshotted for another window width and
|
|
1057
|
+
// the user might have resized their window since then.
|
|
745
1058
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
});
|
|
752
|
-
}
|
|
1059
|
+
|
|
1060
|
+
this.onUpdateShownItemIndexes({
|
|
1061
|
+
reason: _Layout.LAYOUT_REASON.MOUNTED,
|
|
1062
|
+
stateUpdate: stateUpdate
|
|
1063
|
+
});
|
|
753
1064
|
}
|
|
754
1065
|
}, {
|
|
755
|
-
key: "
|
|
756
|
-
value: function
|
|
757
|
-
//
|
|
758
|
-
this.measureVerticalSpacing(); // Measure "newly shown" item heights.
|
|
1066
|
+
key: "measureItemHeightsAndSpacingAndUpdateTablePadding",
|
|
1067
|
+
value: function measureItemHeightsAndSpacingAndUpdateTablePadding() {
|
|
1068
|
+
// Measure "newly shown" item heights.
|
|
759
1069
|
// Also re-validate already measured items' heights.
|
|
1070
|
+
this.itemHeights.measureItemHeights(this.getState().firstShownItemIndex, this.getState().lastShownItemIndex); // Update item vertical spacing.
|
|
760
1071
|
|
|
761
|
-
|
|
1072
|
+
var verticalSpacing = this.measureVerticalSpacing(); // Update `<tbody/>` `padding`.
|
|
762
1073
|
// (`<tbody/>` is different in a way that it can't have `margin`, only `padding`).
|
|
763
1074
|
// https://gitlab.com/catamphetamine/virtual-scroller/-/issues/1
|
|
764
1075
|
|
|
765
1076
|
if (this.tbody) {
|
|
766
|
-
(0, _tbody.setTbodyPadding)(this.
|
|
1077
|
+
(0, _tbody.setTbodyPadding)(this.getItemsContainerElement(), this.getState().beforeItemsHeight, this.getState().afterItemsHeight);
|
|
1078
|
+
} // Return a state update.
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
if (verticalSpacing !== undefined) {
|
|
1082
|
+
return {
|
|
1083
|
+
verticalSpacing: verticalSpacing
|
|
1084
|
+
};
|
|
767
1085
|
}
|
|
768
1086
|
}
|
|
769
1087
|
}, {
|
|
770
|
-
key: "
|
|
771
|
-
value: function
|
|
1088
|
+
key: "getVisibleArea",
|
|
1089
|
+
value: function getVisibleArea() {
|
|
772
1090
|
var visibleArea = this.scroll.getVisibleAreaBounds();
|
|
773
|
-
visibleArea
|
|
774
|
-
|
|
775
|
-
|
|
1091
|
+
this.latestLayoutVisibleArea = visibleArea; // Subtract the top offset of the list inside the scrollable container.
|
|
1092
|
+
|
|
1093
|
+
var listTopOffsetInsideScrollableContainer = this.getListTopOffsetInsideScrollableContainer();
|
|
1094
|
+
return {
|
|
1095
|
+
top: visibleArea.top - listTopOffsetInsideScrollableContainer,
|
|
1096
|
+
bottom: visibleArea.bottom - listTopOffsetInsideScrollableContainer
|
|
1097
|
+
};
|
|
776
1098
|
}
|
|
777
1099
|
/**
|
|
778
1100
|
* Returns the list's top offset relative to the scrollable container's top edge.
|
|
779
1101
|
* @return {number}
|
|
780
1102
|
*/
|
|
781
1103
|
|
|
1104
|
+
}, {
|
|
1105
|
+
key: "getItemScrollPosition",
|
|
1106
|
+
value:
|
|
1107
|
+
/**
|
|
1108
|
+
* Returns the items's top offset relative to the scrollable container's top edge.
|
|
1109
|
+
* @param {number} i — Item index
|
|
1110
|
+
* @return {[number]} Returns the item's scroll Y position. Returns `undefined` if any of the previous items haven't been rendered yet.
|
|
1111
|
+
*/
|
|
1112
|
+
function getItemScrollPosition(i) {
|
|
1113
|
+
var itemTopOffsetInList = this.layout.getItemTopOffset(i);
|
|
1114
|
+
|
|
1115
|
+
if (itemTopOffsetInList === undefined) {
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
return this.getListTopOffsetInsideScrollableContainer() + itemTopOffsetInList;
|
|
1120
|
+
}
|
|
782
1121
|
}, {
|
|
783
1122
|
key: "onUnmount",
|
|
784
1123
|
value: function onUnmount() {
|
|
@@ -792,18 +1131,41 @@ function () {
|
|
|
792
1131
|
this.stop();
|
|
793
1132
|
}
|
|
794
1133
|
}, {
|
|
795
|
-
key: "
|
|
796
|
-
value: function
|
|
797
|
-
|
|
798
|
-
this.resize.stop();
|
|
799
|
-
this.scroll.stop();
|
|
800
|
-
this.waitForStylesToLoad.stop();
|
|
1134
|
+
key: "cancelLayoutTimer",
|
|
1135
|
+
value: function cancelLayoutTimer(_ref6) {
|
|
1136
|
+
var stateUpdate = _ref6.stateUpdate;
|
|
801
1137
|
|
|
802
1138
|
if (this.layoutTimer) {
|
|
803
1139
|
(0, _requestAnimationFrameTimeout.clearTimeout)(this.layoutTimer);
|
|
804
|
-
this.layoutTimer = undefined;
|
|
1140
|
+
this.layoutTimer = undefined; // Merge state updates.
|
|
1141
|
+
|
|
1142
|
+
if (stateUpdate || this.layoutTimerStateUpdate) {
|
|
1143
|
+
stateUpdate = _objectSpread(_objectSpread({}, this.layoutTimerStateUpdate), stateUpdate);
|
|
1144
|
+
this.layoutTimerStateUpdate = undefined;
|
|
1145
|
+
return stateUpdate;
|
|
1146
|
+
}
|
|
1147
|
+
} else {
|
|
1148
|
+
return stateUpdate;
|
|
805
1149
|
}
|
|
806
1150
|
}
|
|
1151
|
+
}, {
|
|
1152
|
+
key: "scheduleLayoutTimer",
|
|
1153
|
+
value: function scheduleLayoutTimer(_ref7) {
|
|
1154
|
+
var _this2 = this;
|
|
1155
|
+
|
|
1156
|
+
var reason = _ref7.reason,
|
|
1157
|
+
stateUpdate = _ref7.stateUpdate;
|
|
1158
|
+
this.layoutTimerStateUpdate = stateUpdate;
|
|
1159
|
+
this.layoutTimer = (0, _requestAnimationFrameTimeout.setTimeout)(function () {
|
|
1160
|
+
_this2.layoutTimerStateUpdate = undefined;
|
|
1161
|
+
_this2.layoutTimer = undefined;
|
|
1162
|
+
|
|
1163
|
+
_this2.onUpdateShownItemIndexes({
|
|
1164
|
+
reason: reason,
|
|
1165
|
+
stateUpdate: stateUpdate
|
|
1166
|
+
});
|
|
1167
|
+
}, 0);
|
|
1168
|
+
}
|
|
807
1169
|
/**
|
|
808
1170
|
* Should be called right before `state` is updated.
|
|
809
1171
|
* @param {object} prevState
|
|
@@ -811,11 +1173,115 @@ function () {
|
|
|
811
1173
|
*/
|
|
812
1174
|
|
|
813
1175
|
}, {
|
|
814
|
-
key: "
|
|
815
|
-
value:
|
|
816
|
-
|
|
1176
|
+
key: "onNewItemsRendered",
|
|
1177
|
+
value: // After a new set of items has been rendered:
|
|
1178
|
+
//
|
|
1179
|
+
// * Restores scroll position when using `preserveScrollPositionOnPrependItems`
|
|
1180
|
+
// and items have been prepended.
|
|
1181
|
+
//
|
|
1182
|
+
// * Applies any "pending" `itemHeights` updates — those ones that happened
|
|
1183
|
+
// while an asynchronous `setState()` call in `setItems()` was pending.
|
|
1184
|
+
//
|
|
1185
|
+
// * Either creates or resets the snapshot of the current layout.
|
|
1186
|
+
//
|
|
1187
|
+
// The current layout snapshot could be stored as a "previously calculated layout" variable
|
|
1188
|
+
// so that it could theoretically be used when calculating new layout incrementally
|
|
1189
|
+
// rather than from scratch, which would be an optimization.
|
|
1190
|
+
//
|
|
1191
|
+
// The "previously calculated layout" feature is not currently used.
|
|
1192
|
+
//
|
|
1193
|
+
function onNewItemsRendered(itemsDiff, newLayout) {
|
|
1194
|
+
// If it's an "incremental" update.
|
|
1195
|
+
if (itemsDiff) {
|
|
1196
|
+
var prependedItemsCount = itemsDiff.prependedItemsCount,
|
|
1197
|
+
appendedItemsCount = itemsDiff.appendedItemsCount;
|
|
1198
|
+
|
|
1199
|
+
var _this$getState2 = this.getState(),
|
|
1200
|
+
itemHeights = _this$getState2.itemHeights,
|
|
1201
|
+
itemStates = _this$getState2.itemStates; // See if any items' heights changed while new items were being rendered.
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
if (this.itemHeightsThatChangedWhileNewItemsWereBeingRendered) {
|
|
1205
|
+
for (var _i = 0, _Object$keys = Object.keys(this.itemHeightsThatChangedWhileNewItemsWereBeingRendered); _i < _Object$keys.length; _i++) {
|
|
1206
|
+
var i = _Object$keys[_i];
|
|
1207
|
+
itemHeights[prependedItemsCount + parseInt(i)] = this.itemHeightsThatChangedWhileNewItemsWereBeingRendered[i];
|
|
1208
|
+
}
|
|
1209
|
+
} // See if any items' states changed while new items were being rendered.
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
if (this.itemStatesThatChangedWhileNewItemsWereBeingRendered) {
|
|
1213
|
+
for (var _i2 = 0, _Object$keys2 = Object.keys(this.itemStatesThatChangedWhileNewItemsWereBeingRendered); _i2 < _Object$keys2.length; _i2++) {
|
|
1214
|
+
var _i3 = _Object$keys2[_i2];
|
|
1215
|
+
itemStates[prependedItemsCount + parseInt(_i3)] = this.itemStatesThatChangedWhileNewItemsWereBeingRendered[_i3];
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (prependedItemsCount === 0) {
|
|
1220
|
+
// Adjust `this.previouslyCalculatedLayout`.
|
|
1221
|
+
if (this.previouslyCalculatedLayout) {
|
|
1222
|
+
if (this.previouslyCalculatedLayout.firstShownItemIndex === newLayout.firstShownItemIndex && this.previouslyCalculatedLayout.lastShownItemIndex === newLayout.lastShownItemIndex) {// `this.previouslyCalculatedLayout` stays the same.
|
|
1223
|
+
// `firstShownItemIndex` / `lastShownItemIndex` didn't get changed in `setItems()`,
|
|
1224
|
+
// so `beforeItemsHeight` and `shownItemsHeight` also stayed the same.
|
|
1225
|
+
} else {
|
|
1226
|
+
(0, _debug.warn)('Unexpected (non-matching) "firstShownItemIndex" or "lastShownItemIndex" encountered in "didUpdateState()" after appending items');
|
|
1227
|
+
(0, _debug.warn)('Previously calculated layout', this.previouslyCalculatedLayout);
|
|
1228
|
+
(0, _debug.warn)('New layout', newLayout);
|
|
1229
|
+
this.previouslyCalculatedLayout = undefined;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
return 'SEAMLESS_APPEND';
|
|
1234
|
+
} else {
|
|
1235
|
+
if (this.listHeightChangeWatcher.hasSnapshot()) {
|
|
1236
|
+
if (newLayout.firstShownItemIndex === 0) {
|
|
1237
|
+
// Restore (adjust) scroll position.
|
|
1238
|
+
(0, _debug["default"])('~ Restore Scroll Position ~');
|
|
1239
|
+
var listBottomOffsetChange = this.listHeightChangeWatcher.getListBottomOffsetChange({
|
|
1240
|
+
beforeItemsHeight: newLayout.beforeItemsHeight
|
|
1241
|
+
});
|
|
1242
|
+
this.listHeightChangeWatcher.reset();
|
|
1243
|
+
|
|
1244
|
+
if (listBottomOffsetChange) {
|
|
1245
|
+
(0, _debug["default"])('Scroll down by', listBottomOffsetChange);
|
|
1246
|
+
this.scroll.scrollByY(listBottomOffsetChange);
|
|
1247
|
+
} else {
|
|
1248
|
+
(0, _debug["default"])('Scroll position hasn\'t changed');
|
|
1249
|
+
} // Create new `this.previouslyCalculatedLayout`.
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
if (this.previouslyCalculatedLayout) {
|
|
1253
|
+
if (this.previouslyCalculatedLayout.firstShownItemIndex === 0 && this.previouslyCalculatedLayout.lastShownItemIndex === newLayout.lastShownItemIndex - prependedItemsCount) {
|
|
1254
|
+
this.previouslyCalculatedLayout = {
|
|
1255
|
+
beforeItemsHeight: 0,
|
|
1256
|
+
shownItemsHeight: this.previouslyCalculatedLayout.shownItemsHeight + listBottomOffsetChange,
|
|
1257
|
+
firstShownItemIndex: 0,
|
|
1258
|
+
lastShownItemIndex: newLayout.lastShownItemIndex
|
|
1259
|
+
};
|
|
1260
|
+
} else {
|
|
1261
|
+
(0, _debug.warn)('Unexpected (non-matching) "firstShownItemIndex" or "lastShownItemIndex" encountered in "didUpdateState()" after prepending items');
|
|
1262
|
+
(0, _debug.warn)('Previously calculated layout', this.previouslyCalculatedLayout);
|
|
1263
|
+
(0, _debug.warn)('New layout', newLayout);
|
|
1264
|
+
this.previouslyCalculatedLayout = undefined;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
817
1267
|
|
|
818
|
-
|
|
1268
|
+
return 'SEAMLESS_PREPEND';
|
|
1269
|
+
} else {
|
|
1270
|
+
(0, _debug.warn)("Unexpected \"firstShownItemIndex\" ".concat(newLayout.firstShownItemIndex, " encountered in \"didUpdateState()\" after prepending items. Expected 0."));
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
} // Reset `this.previouslyCalculatedLayout` in any case other than
|
|
1275
|
+
// SEAMLESS_PREPEND or SEAMLESS_APPEND.
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
this.previouslyCalculatedLayout = undefined;
|
|
1279
|
+
}
|
|
1280
|
+
}, {
|
|
1281
|
+
key: "updateStateRightAfterRender",
|
|
1282
|
+
value: function updateStateRightAfterRender(_ref8) {
|
|
1283
|
+
var reason = _ref8.reason,
|
|
1284
|
+
stateUpdate = _ref8.stateUpdate;
|
|
819
1285
|
|
|
820
1286
|
// In React, `setTimeout()` is used to prevent a React error:
|
|
821
1287
|
// "Maximum update depth exceeded.
|
|
@@ -824,63 +1290,79 @@ function () {
|
|
|
824
1290
|
// React limits the number of nested updates to prevent infinite loops."
|
|
825
1291
|
if (this._useTimeoutInRenderLoop) {
|
|
826
1292
|
// Cancel a previously scheduled re-layout.
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
} // Schedule a new re-layout.
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
this.layoutTimer = (0, _requestAnimationFrameTimeout.setTimeout)(function () {
|
|
833
|
-
_this2.layoutTimer = undefined;
|
|
1293
|
+
stateUpdate = this.cancelLayoutTimer({
|
|
1294
|
+
stateUpdate: stateUpdate
|
|
1295
|
+
}); // Schedule a new re-layout.
|
|
834
1296
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
}
|
|
1297
|
+
this.scheduleLayoutTimer({
|
|
1298
|
+
reason: reason,
|
|
1299
|
+
stateUpdate: stateUpdate
|
|
1300
|
+
});
|
|
839
1301
|
} else {
|
|
840
1302
|
this.onUpdateShownItemIndexes({
|
|
841
|
-
reason: reason
|
|
1303
|
+
reason: reason,
|
|
1304
|
+
stateUpdate: stateUpdate
|
|
842
1305
|
});
|
|
843
1306
|
}
|
|
844
1307
|
}
|
|
845
1308
|
}, {
|
|
846
1309
|
key: "measureVerticalSpacing",
|
|
847
1310
|
value: function measureVerticalSpacing() {
|
|
848
|
-
if (this.
|
|
1311
|
+
if (this.verticalSpacing === undefined) {
|
|
1312
|
+
var _this$getState3 = this.getState(),
|
|
1313
|
+
firstShownItemIndex = _this$getState3.firstShownItemIndex,
|
|
1314
|
+
lastShownItemIndex = _this$getState3.lastShownItemIndex;
|
|
1315
|
+
|
|
849
1316
|
(0, _debug["default"])('~ Measure item vertical spacing ~');
|
|
850
1317
|
var verticalSpacing = (0, _getVerticalSpacing["default"])({
|
|
851
|
-
|
|
852
|
-
|
|
1318
|
+
itemsContainer: this.itemsContainer,
|
|
1319
|
+
renderedItemsCount: lastShownItemIndex - firstShownItemIndex + 1
|
|
853
1320
|
});
|
|
854
1321
|
|
|
855
1322
|
if (verticalSpacing === undefined) {
|
|
856
1323
|
(0, _debug["default"])('Not enough items rendered to measure vertical spacing');
|
|
857
1324
|
} else {
|
|
858
1325
|
(0, _debug["default"])('Item vertical spacing', verticalSpacing);
|
|
859
|
-
this.
|
|
860
|
-
|
|
861
|
-
|
|
1326
|
+
this.verticalSpacing = verticalSpacing;
|
|
1327
|
+
|
|
1328
|
+
if (verticalSpacing !== 0) {
|
|
1329
|
+
return verticalSpacing;
|
|
1330
|
+
}
|
|
862
1331
|
}
|
|
863
1332
|
}
|
|
864
1333
|
}
|
|
865
1334
|
}, {
|
|
866
1335
|
key: "remeasureItemHeight",
|
|
867
1336
|
value: function remeasureItemHeight(i) {
|
|
868
|
-
var _this$
|
|
869
|
-
firstShownItemIndex = _this$
|
|
1337
|
+
var _this$getState4 = this.getState(),
|
|
1338
|
+
firstShownItemIndex = _this$getState4.firstShownItemIndex;
|
|
870
1339
|
|
|
871
1340
|
return this.itemHeights.remeasureItemHeight(i, firstShownItemIndex);
|
|
872
1341
|
}
|
|
873
1342
|
}, {
|
|
874
1343
|
key: "onItemStateChange",
|
|
875
|
-
value: function onItemStateChange(i,
|
|
1344
|
+
value: function onItemStateChange(i, newItemState) {
|
|
876
1345
|
if ((0, _debug.isDebug)()) {
|
|
877
1346
|
(0, _debug["default"])('~ Item state changed ~');
|
|
878
|
-
(0, _debug["default"])('Item', i);
|
|
1347
|
+
(0, _debug["default"])('Item', i); // Uses `JSON.stringify()` here instead of just outputting the JSON objects as is
|
|
1348
|
+
// because outputting JSON objects as is would show different results later when
|
|
1349
|
+
// the developer inspects those in the web browser console if those state objects
|
|
1350
|
+
// get modified in between they've been output to the console and the developer
|
|
1351
|
+
// decided to inspect them.
|
|
1352
|
+
|
|
879
1353
|
(0, _debug["default"])('Previous state' + '\n' + JSON.stringify(this.getState().itemStates[i], null, 2));
|
|
880
|
-
(0, _debug["default"])('New state' + '\n' + JSON.stringify(
|
|
1354
|
+
(0, _debug["default"])('New state' + '\n' + JSON.stringify(newItemState, null, 2));
|
|
881
1355
|
}
|
|
882
1356
|
|
|
883
|
-
this.getState().itemStates[i] =
|
|
1357
|
+
this.getState().itemStates[i] = newItemState; // Schedule the item state update for after the new items have been rendered.
|
|
1358
|
+
|
|
1359
|
+
if (this.newItemsWillBeRendered) {
|
|
1360
|
+
if (!this.itemStatesThatChangedWhileNewItemsWereBeingRendered) {
|
|
1361
|
+
this.itemStatesThatChangedWhileNewItemsWereBeingRendered = {};
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
this.itemStatesThatChangedWhileNewItemsWereBeingRendered[String(i)] = newItemState;
|
|
1365
|
+
}
|
|
884
1366
|
}
|
|
885
1367
|
}, {
|
|
886
1368
|
key: "onItemHeightChange",
|
|
@@ -888,18 +1370,13 @@ function () {
|
|
|
888
1370
|
(0, _debug["default"])('~ Re-measure item height ~');
|
|
889
1371
|
(0, _debug["default"])('Item', i);
|
|
890
1372
|
|
|
891
|
-
var _this$
|
|
892
|
-
itemHeights = _this$
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
if (previousHeight === undefined) {
|
|
897
|
-
return (0, _debug.reportError)("\"onItemHeightChange()\" has been called for item ".concat(i, ", but that item hasn't been rendered before."));
|
|
898
|
-
}
|
|
1373
|
+
var _this$getState5 = this.getState(),
|
|
1374
|
+
itemHeights = _this$getState5.itemHeights,
|
|
1375
|
+
firstShownItemIndex = _this$getState5.firstShownItemIndex,
|
|
1376
|
+
lastShownItemIndex = _this$getState5.lastShownItemIndex; // Check if the item is still rendered.
|
|
899
1377
|
|
|
900
|
-
var newHeight = this.remeasureItemHeight(i); // Check if the item is still rendered.
|
|
901
1378
|
|
|
902
|
-
if (
|
|
1379
|
+
if (!(i >= firstShownItemIndex && i <= lastShownItemIndex)) {
|
|
903
1380
|
// There could be valid cases when an item is no longer rendered
|
|
904
1381
|
// by the time `.onItemHeightChange(i)` gets called.
|
|
905
1382
|
// For example, suppose there's a list of several items on a page,
|
|
@@ -928,15 +1405,61 @@ function () {
|
|
|
928
1405
|
return (0, _debug.warn)('The item is no longer rendered. This is not necessarily a bug, and could happen, for example, when there\'re several `onItemHeightChange(i)` calls issued at the same time.');
|
|
929
1406
|
}
|
|
930
1407
|
|
|
1408
|
+
var previousHeight = itemHeights[i];
|
|
1409
|
+
|
|
1410
|
+
if (previousHeight === undefined) {
|
|
1411
|
+
return (0, _debug.reportError)("\"onItemHeightChange()\" has been called for item ".concat(i, ", but that item hasn't been rendered before."));
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
var newHeight = this.remeasureItemHeight(i);
|
|
931
1415
|
(0, _debug["default"])('Previous height', previousHeight);
|
|
932
1416
|
(0, _debug["default"])('New height', newHeight);
|
|
933
1417
|
|
|
934
1418
|
if (previousHeight !== newHeight) {
|
|
935
|
-
(0, _debug["default"])('~ Item height has changed ~'); //
|
|
1419
|
+
(0, _debug["default"])('~ Item height has changed ~'); // Update or reset previously calculated layout.
|
|
1420
|
+
|
|
1421
|
+
this.updatePreviouslyCalculatedLayoutOnItemHeightChange(i, previousHeight, newHeight); // Recalculate layout.
|
|
936
1422
|
|
|
937
1423
|
this.onUpdateShownItemIndexes({
|
|
938
1424
|
reason: _Layout.LAYOUT_REASON.ITEM_HEIGHT_CHANGED
|
|
939
|
-
});
|
|
1425
|
+
}); // Schedule the item height update for after the new items have been rendered.
|
|
1426
|
+
|
|
1427
|
+
if (this.newItemsWillBeRendered) {
|
|
1428
|
+
if (!this.itemHeightsThatChangedWhileNewItemsWereBeingRendered) {
|
|
1429
|
+
this.itemHeightsThatChangedWhileNewItemsWereBeingRendered = {};
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
this.itemHeightsThatChangedWhileNewItemsWereBeingRendered[String(i)] = newHeight;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
} // Updates the snapshot of the current layout when an item's height changes.
|
|
1436
|
+
//
|
|
1437
|
+
// The "previously calculated layout" feature is not currently used.
|
|
1438
|
+
//
|
|
1439
|
+
// The current layout snapshot could be stored as a "previously calculated layout" variable
|
|
1440
|
+
// so that it could theoretically be used when calculating new layout incrementally
|
|
1441
|
+
// rather than from scratch, which would be an optimization.
|
|
1442
|
+
//
|
|
1443
|
+
|
|
1444
|
+
}, {
|
|
1445
|
+
key: "updatePreviouslyCalculatedLayoutOnItemHeightChange",
|
|
1446
|
+
value: function updatePreviouslyCalculatedLayoutOnItemHeightChange(i, previousHeight, newHeight) {
|
|
1447
|
+
if (this.previouslyCalculatedLayout) {
|
|
1448
|
+
var heightDifference = newHeight - previousHeight;
|
|
1449
|
+
|
|
1450
|
+
if (i < this.previouslyCalculatedLayout.firstShownItemIndex) {
|
|
1451
|
+
// Patch `this.previouslyCalculatedLayout`'s `.beforeItemsHeight`.
|
|
1452
|
+
this.previouslyCalculatedLayout.beforeItemsHeight += heightDifference;
|
|
1453
|
+
} else if (i > this.previouslyCalculatedLayout.lastShownItemIndex) {
|
|
1454
|
+
// Could patch `.afterItemsHeight` of `this.previouslyCalculatedLayout` here,
|
|
1455
|
+
// if `.afterItemsHeight` property existed in `this.previouslyCalculatedLayout`.
|
|
1456
|
+
if (this.previouslyCalculatedLayout.afterItemsHeight !== undefined) {
|
|
1457
|
+
this.previouslyCalculatedLayout.afterItemsHeight += heightDifference;
|
|
1458
|
+
}
|
|
1459
|
+
} else {
|
|
1460
|
+
// Patch `this.previouslyCalculatedLayout`'s shown items height.
|
|
1461
|
+
this.previouslyCalculatedLayout.shownItemsHeight += newHeight - previousHeight;
|
|
1462
|
+
}
|
|
940
1463
|
}
|
|
941
1464
|
}
|
|
942
1465
|
/**
|
|
@@ -992,8 +1515,14 @@ function () {
|
|
|
992
1515
|
var actualItemHeight = this.remeasureItemHeight(i);
|
|
993
1516
|
|
|
994
1517
|
if (actualItemHeight !== previouslyMeasuredItemHeight) {
|
|
1518
|
+
if (isValid) {
|
|
1519
|
+
(0, _debug["default"])('~ Validate will-be-hidden item heights. ~'); // Update or reset previously calculated layout.
|
|
1520
|
+
|
|
1521
|
+
this.updatePreviouslyCalculatedLayoutOnItemHeightChange(i, previouslyMeasuredItemHeight, actualItemHeight);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
995
1524
|
isValid = false;
|
|
996
|
-
(0, _debug.warn)('Item', i, 'will be unmounted
|
|
1525
|
+
(0, _debug.warn)('Item index', i, 'is no longer visible and will be unmounted. Its height has changed from', previouslyMeasuredItemHeight, 'to', actualItemHeight, 'since it was last measured. This is not necessarily a bug, and could happen, for example, on screen width change, or when there\'re several `onItemHeightChange(i)` calls issued at the same time, and the first one triggers a re-layout before the rest of them have had a chance to be executed.');
|
|
997
1526
|
}
|
|
998
1527
|
}
|
|
999
1528
|
|
|
@@ -1002,23 +1531,65 @@ function () {
|
|
|
1002
1531
|
|
|
1003
1532
|
return isValid;
|
|
1004
1533
|
}
|
|
1534
|
+
}, {
|
|
1535
|
+
key: "getShownItemIndexes",
|
|
1536
|
+
value: function getShownItemIndexes() {
|
|
1537
|
+
var itemsCount = this.getItemsCount();
|
|
1538
|
+
|
|
1539
|
+
var _this$getVisibleArea = this.getVisibleArea(),
|
|
1540
|
+
visibleAreaTop = _this$getVisibleArea.top,
|
|
1541
|
+
visibleAreaBottom = _this$getVisibleArea.bottom;
|
|
1542
|
+
|
|
1543
|
+
if (this.bypass) {
|
|
1544
|
+
return {
|
|
1545
|
+
firstShownItemIndex: 0,
|
|
1546
|
+
lastShownItemIndex: itemsCount - 1 // shownItemsHeight: this.getState().itemHeights.reduce((sum, itemHeight) => sum + itemHeight, 0)
|
|
1547
|
+
|
|
1548
|
+
};
|
|
1549
|
+
} // Find the indexes of the items that are currently visible
|
|
1550
|
+
// (or close to being visible) in the scrollable container.
|
|
1551
|
+
// For scrollable containers other than the main screen, it could also
|
|
1552
|
+
// check the visibility of such scrollable container itself, because it
|
|
1553
|
+
// might be not visible.
|
|
1554
|
+
// If such kind of an optimization would hypothetically be implemented,
|
|
1555
|
+
// then it would also require listening for "scroll" events on the screen.
|
|
1556
|
+
// Overall, I suppose that such "actual visibility" feature would be
|
|
1557
|
+
// a very minor optimization and not something I'd deal with.
|
|
1558
|
+
|
|
1559
|
+
|
|
1560
|
+
var isVisible = visibleAreaTop < this.itemsContainer.getHeight() && visibleAreaBottom > 0;
|
|
1561
|
+
|
|
1562
|
+
if (!isVisible) {
|
|
1563
|
+
(0, _debug["default"])('The entire list is off-screen. No items are visible.');
|
|
1564
|
+
return this.layout.getNonVisibleListShownItemIndexes();
|
|
1565
|
+
} // Get shown item indexes.
|
|
1566
|
+
|
|
1567
|
+
|
|
1568
|
+
return this.layout.getShownItemIndexes({
|
|
1569
|
+
itemsCount: this.getItemsCount(),
|
|
1570
|
+
visibleAreaTop: visibleAreaTop,
|
|
1571
|
+
visibleAreaBottom: visibleAreaBottom
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1005
1574
|
/**
|
|
1006
1575
|
* Updates the "from" and "to" shown item indexes.
|
|
1007
1576
|
* If the list is visible and some of the items being shown are new
|
|
1008
1577
|
* and are required to be measured first, then
|
|
1009
|
-
* `
|
|
1578
|
+
* `firstNonMeasuredItemIndex` is defined.
|
|
1010
1579
|
* If the list is visible and all items being shown have been encountered
|
|
1011
|
-
* (and measured) before, then `
|
|
1580
|
+
* (and measured) before, then `firstNonMeasuredItemIndex` is `undefined`.
|
|
1581
|
+
*
|
|
1582
|
+
* The `stateUpdate` parameter is just an optional "additional" state update.
|
|
1012
1583
|
*/
|
|
1013
1584
|
|
|
1014
1585
|
}, {
|
|
1015
1586
|
key: "updateItems",
|
|
1016
|
-
|
|
1587
|
+
value:
|
|
1017
1588
|
/**
|
|
1018
1589
|
* @deprecated
|
|
1019
1590
|
* `.updateItems()` has been renamed to `.setItems()`.
|
|
1020
1591
|
*/
|
|
1021
|
-
|
|
1592
|
+
function updateItems(newItems, options) {
|
|
1022
1593
|
return this.setItems(newItems, options);
|
|
1023
1594
|
}
|
|
1024
1595
|
/**
|
|
@@ -1033,32 +1604,55 @@ function () {
|
|
|
1033
1604
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
1034
1605
|
|
|
1035
1606
|
// * @param {object} [newCustomState] — If `customState` was passed to `getInitialState()`, this `newCustomState` updates it.
|
|
1036
|
-
var _this$
|
|
1037
|
-
previousItems = _this$
|
|
1607
|
+
var _this$getState6 = this.getState(),
|
|
1608
|
+
previousItems = _this$getState6.items; // Even if `newItems` are equal to `this.state.items`,
|
|
1609
|
+
// still perform a `setState()` call, because, if `setState()` calls
|
|
1610
|
+
// were "asynchronous", there could be a situation when a developer
|
|
1611
|
+
// first calls `setItems(newItems)` and then `setItems(oldItems)`:
|
|
1612
|
+
// if this function did `return` `if (newItems === this.state.items)`
|
|
1613
|
+
// then `setState({ items: newItems })` would be scheduled as part of
|
|
1614
|
+
// `setItems(newItems)` call, but the subsequent `setItems(oldItems)` call
|
|
1615
|
+
// wouldn't do anything resulting in `newItems` being set as a result,
|
|
1616
|
+
// and that wouldn't be what the developer intended.
|
|
1038
1617
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1618
|
+
|
|
1619
|
+
var _this$getState7 = this.getState(),
|
|
1620
|
+
itemStates = _this$getState7.itemStates;
|
|
1621
|
+
|
|
1622
|
+
var _ref9 = this.resetLayoutAfterResize ? this.resetLayoutAfterResize.stateUpdate : this.getState(),
|
|
1623
|
+
itemHeights = _ref9.itemHeights;
|
|
1042
1624
|
|
|
1043
1625
|
(0, _debug["default"])('~ Update items ~');
|
|
1044
|
-
var
|
|
1045
|
-
var
|
|
1626
|
+
var layoutUpdate;
|
|
1627
|
+
var itemsUpdateInfo; // Compare the new items to the current items.
|
|
1046
1628
|
|
|
1047
|
-
|
|
1048
|
-
var _this$getState6 = this.getState(),
|
|
1049
|
-
firstShownItemIndex = _this$getState6.firstShownItemIndex,
|
|
1050
|
-
lastShownItemIndex = _this$getState6.lastShownItemIndex,
|
|
1051
|
-
beforeItemsHeight = _this$getState6.beforeItemsHeight,
|
|
1052
|
-
afterItemsHeight = _this$getState6.afterItemsHeight;
|
|
1629
|
+
var itemsDiff = this.getItemsDiff(previousItems, newItems); // See if it's an "incremental" items update.
|
|
1053
1630
|
|
|
1054
|
-
|
|
1631
|
+
if (itemsDiff) {
|
|
1632
|
+
var _ref10 = this.resetLayoutAfterResize ? this.resetLayoutAfterResize.stateUpdate : this.getState(),
|
|
1633
|
+
firstShownItemIndex = _ref10.firstShownItemIndex,
|
|
1634
|
+
lastShownItemIndex = _ref10.lastShownItemIndex,
|
|
1635
|
+
beforeItemsHeight = _ref10.beforeItemsHeight,
|
|
1636
|
+
afterItemsHeight = _ref10.afterItemsHeight;
|
|
1637
|
+
|
|
1638
|
+
var shouldRestoreScrollPosition = firstShownItemIndex === 0 && ( // `preserveScrollPosition` option name is deprecated,
|
|
1639
|
+
// use `preserveScrollPositionOnPrependItems` instead.
|
|
1640
|
+
options.preserveScrollPositionOnPrependItems || options.preserveScrollPosition);
|
|
1641
|
+
var prependedItemsCount = itemsDiff.prependedItemsCount,
|
|
1642
|
+
appendedItemsCount = itemsDiff.appendedItemsCount;
|
|
1643
|
+
layoutUpdate = this.layout.getLayoutUpdateForItemsDiff({
|
|
1055
1644
|
firstShownItemIndex: firstShownItemIndex,
|
|
1056
1645
|
lastShownItemIndex: lastShownItemIndex,
|
|
1057
1646
|
beforeItemsHeight: beforeItemsHeight,
|
|
1058
1647
|
afterItemsHeight: afterItemsHeight
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1648
|
+
}, {
|
|
1649
|
+
prependedItemsCount: prependedItemsCount,
|
|
1650
|
+
appendedItemsCount: appendedItemsCount
|
|
1651
|
+
}, {
|
|
1652
|
+
itemsCount: newItems.length,
|
|
1653
|
+
columnsCount: this.getActualColumnsCount(),
|
|
1654
|
+
shouldRestoreScrollPosition: shouldRestoreScrollPosition
|
|
1655
|
+
});
|
|
1062
1656
|
|
|
1063
1657
|
if (prependedItemsCount > 0) {
|
|
1064
1658
|
(0, _debug["default"])('Prepend', prependedItemsCount, 'items');
|
|
@@ -1066,6 +1660,34 @@ function () {
|
|
|
1066
1660
|
|
|
1067
1661
|
if (itemStates) {
|
|
1068
1662
|
itemStates = new Array(prependedItemsCount).concat(itemStates);
|
|
1663
|
+
} // Restore scroll position after prepending items (if requested).
|
|
1664
|
+
|
|
1665
|
+
|
|
1666
|
+
if (shouldRestoreScrollPosition) {
|
|
1667
|
+
(0, _debug["default"])('Will restore scroll position');
|
|
1668
|
+
this.listHeightChangeWatcher.snapshot({
|
|
1669
|
+
previousItems: previousItems,
|
|
1670
|
+
newItems: newItems,
|
|
1671
|
+
prependedItemsCount: prependedItemsCount
|
|
1672
|
+
}); // "Seamless prepend" scenario doesn't result in a re-layout,
|
|
1673
|
+
// so if any "non measured item" is currently pending,
|
|
1674
|
+
// it doesn't get reset and will be handled after `state` is updated.
|
|
1675
|
+
|
|
1676
|
+
if (this.firstNonMeasuredItemIndex !== undefined) {
|
|
1677
|
+
this.firstNonMeasuredItemIndex += prependedItemsCount;
|
|
1678
|
+
}
|
|
1679
|
+
} else {
|
|
1680
|
+
(0, _debug["default"])('Reset layout'); // Reset layout because none of the prepended items have been measured.
|
|
1681
|
+
|
|
1682
|
+
layoutUpdate = this.layout.getInitialLayoutValues({
|
|
1683
|
+
itemsCount: newItems.length,
|
|
1684
|
+
columnsCount: this.getActualColumnsCount()
|
|
1685
|
+
}); // Unschedule a potentially scheduled layout update
|
|
1686
|
+
// after measuring a previously non-measured item
|
|
1687
|
+
// because the list will be re-layout anyway
|
|
1688
|
+
// due to the new items being set.
|
|
1689
|
+
|
|
1690
|
+
this.firstNonMeasuredItemIndex = undefined;
|
|
1069
1691
|
}
|
|
1070
1692
|
}
|
|
1071
1693
|
|
|
@@ -1078,55 +1700,311 @@ function () {
|
|
|
1078
1700
|
}
|
|
1079
1701
|
}
|
|
1080
1702
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
if (prependedItemsCount > 0) {
|
|
1086
|
-
// `preserveScrollPosition` option name is deprecated,
|
|
1087
|
-
// use `preserveScrollPositionOnPrependItems` instead.
|
|
1088
|
-
if (options.preserveScrollPositionOnPrependItems || options.preserveScrollPosition) {
|
|
1089
|
-
if (this.getState().firstShownItemIndex === 0) {
|
|
1090
|
-
this.restoreScroll.captureScroll({
|
|
1091
|
-
previousItems: previousItems,
|
|
1092
|
-
newItems: newItems,
|
|
1093
|
-
prependedItemsCount: prependedItemsCount
|
|
1094
|
-
});
|
|
1095
|
-
this.layout.showItemsFromTheStart(layout);
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1703
|
+
itemsUpdateInfo = {
|
|
1704
|
+
prepend: prependedItemsCount > 0,
|
|
1705
|
+
append: appendedItemsCount > 0
|
|
1706
|
+
};
|
|
1099
1707
|
} else {
|
|
1100
1708
|
(0, _debug["default"])('Items have changed, and', itemsDiff ? 'a re-layout from scratch has been requested.' : 'it\'s not a simple append and/or prepend.', 'Rerender the entire list from scratch.');
|
|
1101
1709
|
(0, _debug["default"])('Previous items', previousItems);
|
|
1102
|
-
(0, _debug["default"])('New items', newItems);
|
|
1710
|
+
(0, _debug["default"])('New items', newItems); // Reset item heights and item states.
|
|
1711
|
+
|
|
1103
1712
|
itemHeights = new Array(newItems.length);
|
|
1104
1713
|
itemStates = new Array(newItems.length);
|
|
1105
|
-
|
|
1106
|
-
itemsCount: newItems.length
|
|
1107
|
-
|
|
1714
|
+
layoutUpdate = this.layout.getInitialLayoutValues({
|
|
1715
|
+
itemsCount: newItems.length,
|
|
1716
|
+
columnsCount: this.getActualColumnsCount()
|
|
1717
|
+
}); // Unschedule a potentially scheduled layout update
|
|
1718
|
+
// after measuring a previously non-measured item
|
|
1719
|
+
// because the list will be re-layout from scratch
|
|
1720
|
+
// due to the new items being set.
|
|
1721
|
+
|
|
1722
|
+
this.firstNonMeasuredItemIndex = undefined; // Also reset any potential pending scroll position restoration.
|
|
1723
|
+
// For example, imagine a developer first called `.setItems(incrementalItemsUpdate)`
|
|
1724
|
+
// and then called `.setItems(differentItems)` and there was no state update
|
|
1725
|
+
// in between those two calls. This could happen because state updates aren't
|
|
1726
|
+
// required to be "synchronous". On other words, calling `this.setState()`
|
|
1727
|
+
// doesn't necessarily mean that the state is applied immediately.
|
|
1728
|
+
// Imagine also that such "delayed" state updates could be batched,
|
|
1729
|
+
// like they do in React inside event handlers (though that doesn't apply to this case):
|
|
1730
|
+
// https://github.com/facebook/react/issues/10231#issuecomment-316644950
|
|
1731
|
+
// If `this.listHeightChangeWatcher` wasn't reset on `.setItems(differentItems)`
|
|
1732
|
+
// and if the second `this.setState()` call overwrites the first one
|
|
1733
|
+
// then it would attempt to restore scroll position in a situation when
|
|
1734
|
+
// it should no longer do that. Hence the reset here.
|
|
1735
|
+
|
|
1736
|
+
this.listHeightChangeWatcher.reset();
|
|
1737
|
+
itemsUpdateInfo = {
|
|
1738
|
+
replace: true
|
|
1739
|
+
};
|
|
1108
1740
|
}
|
|
1109
1741
|
|
|
1110
|
-
(0, _debug["default"])('~ Update state ~');
|
|
1111
|
-
(
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1742
|
+
(0, _debug["default"])('~ Update state ~'); // const layoutValuesAfterUpdate = {
|
|
1743
|
+
// ...this.getState(),
|
|
1744
|
+
// ...layoutUpdate
|
|
1745
|
+
// }
|
|
1746
|
+
// `layoutUpdate` is equivalent to `layoutValuesAfterUpdate` because
|
|
1747
|
+
// `layoutUpdate` contains all the relevant properties.
|
|
1748
|
+
|
|
1749
|
+
(0, _debug["default"])('First shown item index', layoutUpdate.firstShownItemIndex);
|
|
1750
|
+
(0, _debug["default"])('Last shown item index', layoutUpdate.lastShownItemIndex);
|
|
1751
|
+
(0, _debug["default"])('Before items height', layoutUpdate.beforeItemsHeight);
|
|
1752
|
+
(0, _debug["default"])('After items height (actual or estimated)', layoutUpdate.afterItemsHeight); // Optionally preload items to be rendered.
|
|
1753
|
+
//
|
|
1754
|
+
// `layoutUpdate` is equivalent to `layoutValuesAfterUpdate` because
|
|
1755
|
+
// `layoutUpdate` contains all the relevant properties.
|
|
1756
|
+
//
|
|
1757
|
+
|
|
1758
|
+
this.onBeforeShowItems(newItems, itemHeights, layoutUpdate.firstShownItemIndex, layoutUpdate.lastShownItemIndex); // `this.newItemsWillBeRendered` signals that new `items` are being rendered,
|
|
1759
|
+
// and that `VirtualScroller` should temporarily stop all other updates.
|
|
1760
|
+
//
|
|
1761
|
+
// `this.newItemsWillBeRendered` is cleared in `didUpdateState()`.
|
|
1762
|
+
//
|
|
1763
|
+
// The values in `this.newItemsWillBeRendered` are used, for example,
|
|
1764
|
+
// in `.onResize()` handler in order to not break state consistency when
|
|
1765
|
+
// state updates are "asynchronous" (delayed) and there's a window resize event
|
|
1766
|
+
// in between calling `setState()` below and that call actually being applied.
|
|
1767
|
+
//
|
|
1768
|
+
|
|
1769
|
+
this.newItemsWillBeRendered = _objectSpread(_objectSpread({}, itemsUpdateInfo), {}, {
|
|
1770
|
+
count: newItems.length,
|
|
1771
|
+
// `layoutUpdate` now contains all layout-related properties, even if those that
|
|
1772
|
+
// didn't change. So `firstShownItemIndex` is always in `this.newItemsWillBeRendered`.
|
|
1773
|
+
layout: layoutUpdate
|
|
1774
|
+
}); // `layoutUpdate` now contains all layout-related properties, even if those that
|
|
1775
|
+
// didn't change. So this part is no longer relevant.
|
|
1776
|
+
//
|
|
1777
|
+
// // If `firstShownItemIndex` is gonna be modified as a result of setting new items
|
|
1778
|
+
// // then keep that "new" `firstShownItemIndex` in order for it to be used by
|
|
1779
|
+
// // `onResize()` handler when it calculates "new" `firstShownItemIndex`
|
|
1780
|
+
// // based on the new columns count (corresponding to the new window width).
|
|
1781
|
+
// if (layoutUpdate.firstShownItemIndex !== undefined) {
|
|
1782
|
+
// this.newItemsWillBeRendered = {
|
|
1783
|
+
// ...this.newItemsWillBeRendered,
|
|
1784
|
+
// firstShownItemIndex: layoutUpdate.firstShownItemIndex
|
|
1785
|
+
// }
|
|
1786
|
+
// }
|
|
1787
|
+
// Update `VirtualScroller` state.
|
|
1788
|
+
//
|
|
1789
|
+
// This state update should overwrite all the `state` properties
|
|
1790
|
+
// that are also updated in the "on scroll" handler (`getShownItemIndexes()`):
|
|
1791
|
+
//
|
|
1792
|
+
// * `firstShownItemIndex`
|
|
1793
|
+
// * `lastShownItemIndex`
|
|
1794
|
+
// * `beforeItemsHeight`
|
|
1795
|
+
// * `afterItemsHeight`
|
|
1796
|
+
//
|
|
1797
|
+
// That's because this `setState()` update has a higher priority
|
|
1798
|
+
// than that of the "on scroll" handler, so it should overwrite
|
|
1799
|
+
// any potential state changes dispatched by the "on scroll" handler.
|
|
1800
|
+
//
|
|
1801
|
+
|
|
1802
|
+
var newState = _objectSpread(_objectSpread({}, layoutUpdate), {}, {
|
|
1121
1803
|
items: newItems,
|
|
1122
1804
|
itemStates: itemStates,
|
|
1123
1805
|
itemHeights: itemHeights
|
|
1124
|
-
}))
|
|
1806
|
+
}); // Introduced `shouldIncludeBeforeResizeValuesInState()` getter just to prevent
|
|
1807
|
+
// cluttering `state` with `beforeResize: undefined` property if `beforeResize`
|
|
1808
|
+
// hasn't ever been set in `state` previously.
|
|
1809
|
+
|
|
1810
|
+
|
|
1811
|
+
if (this.beforeResize.shouldIncludeBeforeResizeValuesInState()) {
|
|
1812
|
+
if (this.shouldDiscardBeforeResizeItemHeights()) {
|
|
1813
|
+
// Reset "before resize" item heights because now there're new items prepended
|
|
1814
|
+
// with unknown heights, or completely new items with unknown heights, so
|
|
1815
|
+
// `beforeItemsHeight` value won't be preserved anyway.
|
|
1816
|
+
newState.beforeResize = undefined;
|
|
1817
|
+
} else {
|
|
1818
|
+
// Overwrite `beforeResize` property in `state` even if it wasn't modified
|
|
1819
|
+
// because state updates could be "asynchronous" and in that case there could be
|
|
1820
|
+
// some previous `setState()` call from some previous `setItems()` call that
|
|
1821
|
+
// hasn't yet been applied, and that previous call might have scheduled setting
|
|
1822
|
+
// `state.beforeResize` property to `undefined` in order to reset it, but this
|
|
1823
|
+
// next `setState()` call might not require resetting `state.beforeResize` property
|
|
1824
|
+
// so it should undo resetting it by simply overwriting it with its normal value.
|
|
1825
|
+
newState.beforeResize = this.resetLayoutAfterResize ? this.resetLayoutAfterResize.stateUpdate.beforeResize : this.getState().beforeResize;
|
|
1826
|
+
}
|
|
1827
|
+
} // `newState` should also overwrite all `state` properties that're updated in `onResize()`
|
|
1828
|
+
// because `setItems()`'s state updates always overwrite `onResize()`'s state updates.
|
|
1829
|
+
// (The least-priority ones are `onScroll()` state updates, but those're simply skipped
|
|
1830
|
+
// if there's a pending `setItems()` or `onResize()` update).
|
|
1831
|
+
//
|
|
1832
|
+
// `state` property exceptions:
|
|
1833
|
+
//
|
|
1834
|
+
// `verticalSpacing` property is not updated here because it's fine setting it to
|
|
1835
|
+
// `undefined` in `onResize()` — it will simply be re-measured after the component re-renders.
|
|
1836
|
+
//
|
|
1837
|
+
// `columnsCount` property is also not updated here because by definition it's only
|
|
1838
|
+
// updated in `onResize()`.
|
|
1839
|
+
// Render.
|
|
1840
|
+
|
|
1841
|
+
|
|
1842
|
+
this.setState(newState);
|
|
1125
1843
|
}
|
|
1126
1844
|
}, {
|
|
1127
1845
|
key: "getItemsDiff",
|
|
1128
1846
|
value: function getItemsDiff(previousItems, newItems) {
|
|
1129
1847
|
return (0, _getItemsDiff2["default"])(previousItems, newItems, this.isItemEqual);
|
|
1848
|
+
} // Returns whether "before resize" item heights should be discarded
|
|
1849
|
+
// as a result of calling `setItems()` with a new set of items
|
|
1850
|
+
// when an asynchronous `setState()` call inside that function
|
|
1851
|
+
// hasn't been applied yet.
|
|
1852
|
+
//
|
|
1853
|
+
// If `setItems()` update was an "incremental" one and no items
|
|
1854
|
+
// have been prepended, then `firstShownItemIndex` is preserved,
|
|
1855
|
+
// and all items' heights before it should be kept in order to
|
|
1856
|
+
// preserve the top offset of the first shown item so that there's
|
|
1857
|
+
// no "content jumping".
|
|
1858
|
+
//
|
|
1859
|
+
// If `setItems()` update was an "incremental" one but there're
|
|
1860
|
+
// some prepended items, then it means that now there're new items
|
|
1861
|
+
// with unknown heights at the top, so the top offset of the first
|
|
1862
|
+
// shown item won't be preserved because there're no "before resize"
|
|
1863
|
+
// heights of those items.
|
|
1864
|
+
//
|
|
1865
|
+
// If `setItems()` update was not an "incremental" one, then don't
|
|
1866
|
+
// attempt to restore previous item heights after a potential window
|
|
1867
|
+
// width change because all item heights have been reset.
|
|
1868
|
+
//
|
|
1869
|
+
|
|
1870
|
+
}, {
|
|
1871
|
+
key: "shouldDiscardBeforeResizeItemHeights",
|
|
1872
|
+
value: function shouldDiscardBeforeResizeItemHeights() {
|
|
1873
|
+
if (this.newItemsWillBeRendered) {
|
|
1874
|
+
var _this$newItemsWillBeR = this.newItemsWillBeRendered,
|
|
1875
|
+
prepend = _this$newItemsWillBeR.prepend,
|
|
1876
|
+
replace = _this$newItemsWillBeR.replace;
|
|
1877
|
+
return prepend || replace;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}, {
|
|
1881
|
+
key: "onResize",
|
|
1882
|
+
value: function onResize() {
|
|
1883
|
+
// Reset "previously calculated layout".
|
|
1884
|
+
//
|
|
1885
|
+
// The "previously calculated layout" feature is not currently used.
|
|
1886
|
+
//
|
|
1887
|
+
// The current layout snapshot could be stored as a "previously calculated layout" variable
|
|
1888
|
+
// so that it could theoretically be used when calculating new layout incrementally
|
|
1889
|
+
// rather than from scratch, which would be an optimization.
|
|
1890
|
+
//
|
|
1891
|
+
this.previouslyCalculatedLayout = undefined; // Cancel any potential scheduled scroll position restoration.
|
|
1892
|
+
|
|
1893
|
+
this.listHeightChangeWatcher.reset(); // Get the most recent items count.
|
|
1894
|
+
// If there're a "pending" `setItems()` call then use the items count from that call
|
|
1895
|
+
// instead of using the count of currently shown `items` from `state`.
|
|
1896
|
+
// A `setItems()` call is "pending" when `setState()` operation is "asynchronous", that is
|
|
1897
|
+
// when `setState()` calls aren't applied immediately, like in React.
|
|
1898
|
+
|
|
1899
|
+
var itemsCount = this.newItemsWillBeRendered ? this.newItemsWillBeRendered.count : this.getState().itemHeights.length; // If layout values have been calculated as a result of a "pending" `setItems()` call,
|
|
1900
|
+
// then don't discard those new layout values and use them instead of the ones from `state`.
|
|
1901
|
+
//
|
|
1902
|
+
// A `setItems()` call is "pending" when `setState()` operation is "asynchronous", that is
|
|
1903
|
+
// when `setState()` calls aren't applied immediately, like in React.
|
|
1904
|
+
//
|
|
1905
|
+
|
|
1906
|
+
var layout = this.newItemsWillBeRendered ? this.newItemsWillBeRendered.layout : this.getState(); // Update `VirtualScroller` state.
|
|
1907
|
+
|
|
1908
|
+
var newState = {
|
|
1909
|
+
// This state update should also overwrite all the `state` properties
|
|
1910
|
+
// that are also updated in the "on scroll" handler (`getShownItemIndexes()`):
|
|
1911
|
+
//
|
|
1912
|
+
// * `firstShownItemIndex`
|
|
1913
|
+
// * `lastShownItemIndex`
|
|
1914
|
+
// * `beforeItemsHeight`
|
|
1915
|
+
// * `afterItemsHeight`
|
|
1916
|
+
//
|
|
1917
|
+
// That's because this `setState()` update has a higher priority
|
|
1918
|
+
// than that of the "on scroll" handler, so it should overwrite
|
|
1919
|
+
// any potential state changes dispatched by the "on scroll" handler.
|
|
1920
|
+
//
|
|
1921
|
+
// All these properties might have changed, but they're not
|
|
1922
|
+
// recalculated here becase they'll be recalculated after
|
|
1923
|
+
// this new state is applied (rendered).
|
|
1924
|
+
//
|
|
1925
|
+
firstShownItemIndex: layout.firstShownItemIndex,
|
|
1926
|
+
lastShownItemIndex: layout.lastShownItemIndex,
|
|
1927
|
+
beforeItemsHeight: layout.beforeItemsHeight,
|
|
1928
|
+
afterItemsHeight: layout.afterItemsHeight,
|
|
1929
|
+
// Reset item heights, because if scrollable container's width (or height)
|
|
1930
|
+
// has changed, then the list width (or height) most likely also has changed,
|
|
1931
|
+
// and also some CSS `@media()` rules might have been added or removed.
|
|
1932
|
+
// So re-render the list entirely.
|
|
1933
|
+
itemHeights: new Array(itemsCount),
|
|
1934
|
+
columnsCount: this.getActualColumnsCountForState(),
|
|
1935
|
+
// Re-measure vertical spacing after render because new CSS styles
|
|
1936
|
+
// might be applied for the new window width.
|
|
1937
|
+
verticalSpacing: undefined
|
|
1938
|
+
};
|
|
1939
|
+
var firstShownItemIndex = layout.firstShownItemIndex,
|
|
1940
|
+
lastShownItemIndex = layout.lastShownItemIndex; // Get the `columnsCount` for the new window width.
|
|
1941
|
+
|
|
1942
|
+
var newColumnsCount = this.getActualColumnsCount(); // Re-calculate `firstShownItemIndex` and `lastShownItemIndex`
|
|
1943
|
+
// based on the new `columnsCount` so that the whole row is visible.
|
|
1944
|
+
|
|
1945
|
+
var newFirstShownItemIndex = Math.floor(firstShownItemIndex / newColumnsCount) * newColumnsCount;
|
|
1946
|
+
var newLastShownItemIndex = Math.ceil((lastShownItemIndex + 1) / newColumnsCount) * newColumnsCount - 1; // Potentially update `firstShownItemIndex` if it needs to be adjusted in order to
|
|
1947
|
+
// correspond to the new `columnsCount`.
|
|
1948
|
+
|
|
1949
|
+
if (newFirstShownItemIndex !== firstShownItemIndex) {
|
|
1950
|
+
(0, _debug["default"])('Columns Count changed from', this.getState().columnsCount || 1, 'to', newColumnsCount);
|
|
1951
|
+
(0, _debug["default"])('First Shown Item Index needs to change from', firstShownItemIndex, 'to', newFirstShownItemIndex);
|
|
1952
|
+
} // Always rewrite `firstShownItemIndex` and `lastShownItemIndex`
|
|
1953
|
+
// as part of the `state` update, even if it hasn't been modified.
|
|
1954
|
+
//
|
|
1955
|
+
// The reason is that there could be two subsequent `onResize()` calls:
|
|
1956
|
+
// the first one could be user resizing the window to half of its width,
|
|
1957
|
+
// resulting in an "asynchronous" `setState()` call, and then, before that
|
|
1958
|
+
// `setState()` call is applied, a second resize event happens when the user
|
|
1959
|
+
// has resized the window back to its original width, meaning that the
|
|
1960
|
+
// `columnsCount` is back to its original value.
|
|
1961
|
+
// In that case, the final `newFirstShownItemIndex` will be equal to the
|
|
1962
|
+
// original `firstShownItemIndex` that was in `state` before the user
|
|
1963
|
+
// has started resizing the window, so, in the end, `state.firstShownItemIndex`
|
|
1964
|
+
// property wouldn't have changed, but it still has to be part of the final
|
|
1965
|
+
// state update in order to overwrite the previous update of `firstShownItemIndex`
|
|
1966
|
+
// property that has been scheduled to be applied in state after the first resize
|
|
1967
|
+
// happened.
|
|
1968
|
+
//
|
|
1969
|
+
|
|
1970
|
+
|
|
1971
|
+
newState.firstShownItemIndex = newFirstShownItemIndex;
|
|
1972
|
+
newState.lastShownItemIndex = newLastShownItemIndex;
|
|
1973
|
+
var verticalSpacing = this.getVerticalSpacing();
|
|
1974
|
+
var columnsCount = this.getColumnsCount(); // `beforeResize` is always overwritten in `state` here.
|
|
1975
|
+
// (once it has started being tracked in `state`)
|
|
1976
|
+
|
|
1977
|
+
if (this.shouldDiscardBeforeResizeItemHeights() || newFirstShownItemIndex === 0) {
|
|
1978
|
+
if (this.beforeResize.shouldIncludeBeforeResizeValuesInState()) {
|
|
1979
|
+
newState.beforeResize = undefined;
|
|
1980
|
+
}
|
|
1981
|
+
} // Snapshot "before resize" values in order to preserve the currently
|
|
1982
|
+
// shown items' vertical position on screen so that there's no "content jumping".
|
|
1983
|
+
else {
|
|
1984
|
+
// Keep "before resize" values in order to preserve the currently
|
|
1985
|
+
// shown items' vertical position on screen so that there's no
|
|
1986
|
+
// "content jumping". These "before resize" values will be discarded
|
|
1987
|
+
// when (if) the user scrolls back to the top of the list.
|
|
1988
|
+
newState.beforeResize = {
|
|
1989
|
+
verticalSpacing: verticalSpacing,
|
|
1990
|
+
columnsCount: columnsCount,
|
|
1991
|
+
itemHeights: this.beforeResize.snapshotBeforeResizeItemHeights({
|
|
1992
|
+
firstShownItemIndex: firstShownItemIndex,
|
|
1993
|
+
newFirstShownItemIndex: newFirstShownItemIndex,
|
|
1994
|
+
newColumnsCount: newColumnsCount
|
|
1995
|
+
})
|
|
1996
|
+
};
|
|
1997
|
+
} // `this.resetLayoutAfterResize` tells `VirtualScroller` that it should
|
|
1998
|
+
// temporarily stop other updates (like "on scroll" updates) and wait
|
|
1999
|
+
// for the new `state` to be applied, after which the `didUpdateState()`
|
|
2000
|
+
// function will clear this flag and perform a re-layout.
|
|
2001
|
+
|
|
2002
|
+
|
|
2003
|
+
this.resetLayoutAfterResize = {
|
|
2004
|
+
stateUpdate: newState
|
|
2005
|
+
}; // Rerender.
|
|
2006
|
+
|
|
2007
|
+
this.setState(newState);
|
|
1130
2008
|
}
|
|
1131
2009
|
}]);
|
|
1132
2010
|
|
|
@@ -1134,4 +2012,5 @@ function () {
|
|
|
1134
2012
|
}();
|
|
1135
2013
|
|
|
1136
2014
|
exports["default"] = VirtualScroller;
|
|
2015
|
+
var SLOW_LAYOUT_DURATION = 15; // in milliseconds.
|
|
1137
2016
|
//# sourceMappingURL=VirtualScroller.js.map
|