@vaadin/component-base 23.1.0-beta4 → 23.1.0-rc3
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/package.json +2 -2
- package/src/debounce.d.ts +2 -2
- package/src/debounce.js +46 -46
- package/src/dom-utils.d.ts +14 -0
- package/src/dom-utils.js +41 -0
- package/src/element-mixin.js +1 -1
- package/src/focus-trap-controller.d.ts +5 -5
- package/src/iron-list-core.js +28 -236
- package/src/keyboard-mixin.d.ts +14 -1
- package/src/keyboard-mixin.js +36 -4
- package/src/media-query-controller.d.ts +9 -9
- package/src/resize-mixin.d.ts +6 -6
- package/src/slot-controller.d.ts +14 -14
- package/src/slot-controller.js +10 -10
- package/src/virtualizer-iron-list-adapter.js +39 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/component-base",
|
|
3
|
-
"version": "23.1.0-
|
|
3
|
+
"version": "23.1.0-rc3",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"@vaadin/testing-helpers": "^0.3.2",
|
|
43
43
|
"sinon": "^13.0.2"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "49c312fbe0228adb559296d45655bbfd4eac6235"
|
|
46
46
|
}
|
package/src/debounce.d.ts
CHANGED
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
import { AsyncInterface } from './async.js';
|
|
11
11
|
|
|
12
12
|
export declare class Debouncer {
|
|
13
|
-
constructor();
|
|
14
|
-
|
|
15
13
|
/**
|
|
16
14
|
* Creates a debouncer if no debouncer is passed as a parameter
|
|
17
15
|
* or it cancels an active debouncer otherwise. The following
|
|
@@ -48,6 +46,8 @@ export declare class Debouncer {
|
|
|
48
46
|
*/
|
|
49
47
|
static debounce(debouncer: Debouncer | null, asyncModule: AsyncInterface, callback: () => any): Debouncer;
|
|
50
48
|
|
|
49
|
+
constructor();
|
|
50
|
+
|
|
51
51
|
/**
|
|
52
52
|
* Sets the scheduler; that is, a module with the Async interface,
|
|
53
53
|
* a callback and optional arguments to be passed to the run function
|
package/src/debounce.js
CHANGED
|
@@ -12,6 +12,52 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
|
|
12
12
|
* @summary Collapse multiple callbacks into one invocation after a timer.
|
|
13
13
|
*/
|
|
14
14
|
export class Debouncer {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a debouncer if no debouncer is passed as a parameter
|
|
17
|
+
* or it cancels an active debouncer otherwise. The following
|
|
18
|
+
* example shows how a debouncer can be called multiple times within a
|
|
19
|
+
* microtask and "debounced" such that the provided callback function is
|
|
20
|
+
* called once. Add this method to a custom element:
|
|
21
|
+
*
|
|
22
|
+
* ```js
|
|
23
|
+
* import {microTask} from '@vaadin/component-base/src/async.js';
|
|
24
|
+
* import {Debouncer} from '@vaadin/component-base/src/debounce.js';
|
|
25
|
+
* // ...
|
|
26
|
+
*
|
|
27
|
+
* _debounceWork() {
|
|
28
|
+
* this._debounceJob = Debouncer.debounce(this._debounceJob,
|
|
29
|
+
* microTask, () => this._doWork());
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* If the `_debounceWork` method is called multiple times within the same
|
|
34
|
+
* microtask, the `_doWork` function will be called only once at the next
|
|
35
|
+
* microtask checkpoint.
|
|
36
|
+
*
|
|
37
|
+
* Note: In testing it is often convenient to avoid asynchrony. To accomplish
|
|
38
|
+
* this with a debouncer, you can use `enqueueDebouncer` and
|
|
39
|
+
* `flush`. For example, extend the above example by adding
|
|
40
|
+
* `enqueueDebouncer(this._debounceJob)` at the end of the
|
|
41
|
+
* `_debounceWork` method. Then in a test, call `flush` to ensure
|
|
42
|
+
* the debouncer has completed.
|
|
43
|
+
*
|
|
44
|
+
* @param {Debouncer?} debouncer Debouncer object.
|
|
45
|
+
* @param {!AsyncInterface} asyncModule Object with Async interface
|
|
46
|
+
* @param {function()} callback Callback to run.
|
|
47
|
+
* @return {!Debouncer} Returns a debouncer object.
|
|
48
|
+
*/
|
|
49
|
+
static debounce(debouncer, asyncModule, callback) {
|
|
50
|
+
if (debouncer instanceof Debouncer) {
|
|
51
|
+
// Cancel the async callback, but leave in debouncerQueue if it was
|
|
52
|
+
// enqueued, to maintain 1.x flush order
|
|
53
|
+
debouncer._cancelAsync();
|
|
54
|
+
} else {
|
|
55
|
+
debouncer = new Debouncer();
|
|
56
|
+
}
|
|
57
|
+
debouncer.setConfig(asyncModule, callback);
|
|
58
|
+
return debouncer;
|
|
59
|
+
}
|
|
60
|
+
|
|
15
61
|
constructor() {
|
|
16
62
|
this._asyncModule = null;
|
|
17
63
|
this._callback = null;
|
|
@@ -85,52 +131,6 @@ export class Debouncer {
|
|
|
85
131
|
isActive() {
|
|
86
132
|
return this._timer != null;
|
|
87
133
|
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Creates a debouncer if no debouncer is passed as a parameter
|
|
91
|
-
* or it cancels an active debouncer otherwise. The following
|
|
92
|
-
* example shows how a debouncer can be called multiple times within a
|
|
93
|
-
* microtask and "debounced" such that the provided callback function is
|
|
94
|
-
* called once. Add this method to a custom element:
|
|
95
|
-
*
|
|
96
|
-
* ```js
|
|
97
|
-
* import {microTask} from '@vaadin/component-base/src/async.js';
|
|
98
|
-
* import {Debouncer} from '@vaadin/component-base/src/debounce.js';
|
|
99
|
-
* // ...
|
|
100
|
-
*
|
|
101
|
-
* _debounceWork() {
|
|
102
|
-
* this._debounceJob = Debouncer.debounce(this._debounceJob,
|
|
103
|
-
* microTask, () => this._doWork());
|
|
104
|
-
* }
|
|
105
|
-
* ```
|
|
106
|
-
*
|
|
107
|
-
* If the `_debounceWork` method is called multiple times within the same
|
|
108
|
-
* microtask, the `_doWork` function will be called only once at the next
|
|
109
|
-
* microtask checkpoint.
|
|
110
|
-
*
|
|
111
|
-
* Note: In testing it is often convenient to avoid asynchrony. To accomplish
|
|
112
|
-
* this with a debouncer, you can use `enqueueDebouncer` and
|
|
113
|
-
* `flush`. For example, extend the above example by adding
|
|
114
|
-
* `enqueueDebouncer(this._debounceJob)` at the end of the
|
|
115
|
-
* `_debounceWork` method. Then in a test, call `flush` to ensure
|
|
116
|
-
* the debouncer has completed.
|
|
117
|
-
*
|
|
118
|
-
* @param {Debouncer?} debouncer Debouncer object.
|
|
119
|
-
* @param {!AsyncInterface} asyncModule Object with Async interface
|
|
120
|
-
* @param {function()} callback Callback to run.
|
|
121
|
-
* @return {!Debouncer} Returns a debouncer object.
|
|
122
|
-
*/
|
|
123
|
-
static debounce(debouncer, asyncModule, callback) {
|
|
124
|
-
if (debouncer instanceof Debouncer) {
|
|
125
|
-
// Cancel the async callback, but leave in debouncerQueue if it was
|
|
126
|
-
// enqueued, to maintain 1.x flush order
|
|
127
|
-
debouncer._cancelAsync();
|
|
128
|
-
} else {
|
|
129
|
-
debouncer = new Debouncer();
|
|
130
|
-
}
|
|
131
|
-
debouncer.setConfig(asyncModule, callback);
|
|
132
|
-
return debouncer;
|
|
133
|
-
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
let debouncerQueue = new Set();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns an array of ancestor root nodes for the given node.
|
|
9
|
+
*
|
|
10
|
+
* A root node is either a document node or a document fragment node (Shadow Root).
|
|
11
|
+
* The array is collected by a bottom-up DOM traversing that starts with the given node
|
|
12
|
+
* and involves both the light DOM and ancestor shadow DOM trees.
|
|
13
|
+
*/
|
|
14
|
+
export function getAncestorRootNodes(node: Node): Node[];
|
package/src/dom-utils.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) 2021 - 2022 Vaadin Ltd.
|
|
4
|
+
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns an array of ancestor root nodes for the given node.
|
|
9
|
+
*
|
|
10
|
+
* A root node is either a document node or a document fragment node (Shadow Root).
|
|
11
|
+
* The array is collected by a bottom-up DOM traversing that starts with the given node
|
|
12
|
+
* and involves both the light DOM and ancestor shadow DOM trees.
|
|
13
|
+
*
|
|
14
|
+
* @param {Node} node
|
|
15
|
+
* @return {Node[]}
|
|
16
|
+
*/
|
|
17
|
+
export function getAncestorRootNodes(node) {
|
|
18
|
+
const result = [];
|
|
19
|
+
|
|
20
|
+
while (node) {
|
|
21
|
+
if (node.nodeType === Node.DOCUMENT_NODE) {
|
|
22
|
+
result.push(node);
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
27
|
+
result.push(node);
|
|
28
|
+
node = node.host;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (node.assignedSlot) {
|
|
33
|
+
node = node.assignedSlot;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
node = node.parentNode;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
}
|
package/src/element-mixin.js
CHANGED
|
@@ -9,17 +9,17 @@ import { ReactiveController } from 'lit';
|
|
|
9
9
|
* A controller for trapping focus within a DOM node.
|
|
10
10
|
*/
|
|
11
11
|
export class FocusTrapController implements ReactiveController {
|
|
12
|
+
/**
|
|
13
|
+
* The controller host element.
|
|
14
|
+
*/
|
|
15
|
+
host: HTMLElement;
|
|
16
|
+
|
|
12
17
|
constructor(node: HTMLElement);
|
|
13
18
|
|
|
14
19
|
hostConnected(): void;
|
|
15
20
|
|
|
16
21
|
hostDisconnected(): void;
|
|
17
22
|
|
|
18
|
-
/**
|
|
19
|
-
* The controller host element.
|
|
20
|
-
*/
|
|
21
|
-
host: HTMLElement;
|
|
22
|
-
|
|
23
23
|
/**
|
|
24
24
|
* Activates a focus trap for a DOM node that will prevent focus from escaping the node.
|
|
25
25
|
* The trap can be deactivated with the `.releaseFocus()` method.
|
package/src/iron-list-core.js
CHANGED
|
@@ -115,32 +115,11 @@ export const ironList = {
|
|
|
115
115
|
*/
|
|
116
116
|
_maxPages: 2,
|
|
117
117
|
|
|
118
|
-
/**
|
|
119
|
-
* The maximum items per row
|
|
120
|
-
*/
|
|
121
|
-
_itemsPerRow: 1,
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* The width of each grid item
|
|
125
|
-
*/
|
|
126
|
-
_itemWidth: 0,
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* The height of the row in grid layout.
|
|
130
|
-
*/
|
|
131
|
-
_rowHeight: 0,
|
|
132
|
-
|
|
133
118
|
/**
|
|
134
119
|
* The cost of stamping a template in ms.
|
|
135
120
|
*/
|
|
136
121
|
_templateCost: 0,
|
|
137
122
|
|
|
138
|
-
/**
|
|
139
|
-
* Needed to pass event.model property to declarative event handlers -
|
|
140
|
-
* see polymer/polymer#4339.
|
|
141
|
-
*/
|
|
142
|
-
_parentModel: true,
|
|
143
|
-
|
|
144
123
|
/**
|
|
145
124
|
* The bottom of the physical content.
|
|
146
125
|
*/
|
|
@@ -166,8 +145,7 @@ export const ironList = {
|
|
|
166
145
|
* The height of the physical content that isn't on the screen.
|
|
167
146
|
*/
|
|
168
147
|
get _hiddenContentSize() {
|
|
169
|
-
|
|
170
|
-
return size - this._viewportHeight;
|
|
148
|
+
return this._physicalSize - this._viewportHeight;
|
|
171
149
|
},
|
|
172
150
|
|
|
173
151
|
/**
|
|
@@ -182,7 +160,7 @@ export const ironList = {
|
|
|
182
160
|
* `_physicalStart`.
|
|
183
161
|
*/
|
|
184
162
|
get _maxVirtualStart() {
|
|
185
|
-
const virtualCount = this.
|
|
163
|
+
const virtualCount = this._virtualCount;
|
|
186
164
|
return Math.max(0, virtualCount - this._physicalCount);
|
|
187
165
|
},
|
|
188
166
|
|
|
@@ -192,9 +170,6 @@ export const ironList = {
|
|
|
192
170
|
|
|
193
171
|
set _virtualStart(val) {
|
|
194
172
|
val = this._clamp(val, 0, this._maxVirtualStart);
|
|
195
|
-
if (this.grid) {
|
|
196
|
-
val -= val % this._itemsPerRow;
|
|
197
|
-
}
|
|
198
173
|
this._virtualStartVal = val;
|
|
199
174
|
},
|
|
200
175
|
|
|
@@ -210,9 +185,6 @@ export const ironList = {
|
|
|
210
185
|
if (val < 0) {
|
|
211
186
|
val = this._physicalCount + val;
|
|
212
187
|
}
|
|
213
|
-
if (this.grid) {
|
|
214
|
-
val -= val % this._itemsPerRow;
|
|
215
|
-
}
|
|
216
188
|
this._physicalStartVal = val;
|
|
217
189
|
},
|
|
218
190
|
|
|
@@ -264,11 +236,7 @@ export const ironList = {
|
|
|
264
236
|
physicalOffset += this._getPhysicalSizeIncrement(pidx);
|
|
265
237
|
|
|
266
238
|
if (physicalOffset > this._scrollPosition) {
|
|
267
|
-
return
|
|
268
|
-
}
|
|
269
|
-
// Handle a partially rendered final row in grid mode
|
|
270
|
-
if (this.grid && this._virtualCount - 1 === vidx) {
|
|
271
|
-
return vidx - (vidx % this._itemsPerRow);
|
|
239
|
+
return vidx;
|
|
272
240
|
}
|
|
273
241
|
}) || 0;
|
|
274
242
|
this._firstVisibleIndexVal = idx;
|
|
@@ -284,38 +252,19 @@ export const ironList = {
|
|
|
284
252
|
get lastVisibleIndex() {
|
|
285
253
|
let idx = this._lastVisibleIndexVal;
|
|
286
254
|
if (idx == null) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
physicalOffset += this._getPhysicalSizeIncrement(pidx);
|
|
296
|
-
});
|
|
297
|
-
}
|
|
255
|
+
let physicalOffset = this._physicalTop + this._scrollOffset;
|
|
256
|
+
this._iterateItems((pidx, vidx) => {
|
|
257
|
+
if (physicalOffset < this._scrollBottom) {
|
|
258
|
+
idx = vidx;
|
|
259
|
+
}
|
|
260
|
+
physicalOffset += this._getPhysicalSizeIncrement(pidx);
|
|
261
|
+
});
|
|
262
|
+
|
|
298
263
|
this._lastVisibleIndexVal = idx;
|
|
299
264
|
}
|
|
300
265
|
return idx;
|
|
301
266
|
},
|
|
302
267
|
|
|
303
|
-
get _defaultScrollTarget() {
|
|
304
|
-
return this;
|
|
305
|
-
},
|
|
306
|
-
|
|
307
|
-
get _virtualRowCount() {
|
|
308
|
-
return Math.ceil(this._virtualCount / this._itemsPerRow);
|
|
309
|
-
},
|
|
310
|
-
|
|
311
|
-
get _estRowsInView() {
|
|
312
|
-
return Math.ceil(this._viewportHeight / this._rowHeight);
|
|
313
|
-
},
|
|
314
|
-
|
|
315
|
-
get _physicalRows() {
|
|
316
|
-
return Math.ceil(this._physicalCount / this._itemsPerRow);
|
|
317
|
-
},
|
|
318
|
-
|
|
319
268
|
get _scrollOffset() {
|
|
320
269
|
return this._scrollerPaddingTop + this.scrollOffset;
|
|
321
270
|
},
|
|
@@ -335,7 +284,7 @@ export const ironList = {
|
|
|
335
284
|
// Random access.
|
|
336
285
|
if (Math.abs(delta) > this._physicalSize && this._physicalSize > 0) {
|
|
337
286
|
delta -= this._scrollOffset;
|
|
338
|
-
const idxAdjustment = Math.round(delta / this._physicalAverage)
|
|
287
|
+
const idxAdjustment = Math.round(delta / this._physicalAverage);
|
|
339
288
|
this._virtualStart += idxAdjustment;
|
|
340
289
|
this._physicalStart += idxAdjustment;
|
|
341
290
|
// Estimate new physical offset based on the virtual start index.
|
|
@@ -344,10 +293,7 @@ export const ironList = {
|
|
|
344
293
|
// more than the scroll position however, since that would result in
|
|
345
294
|
// the physical items not covering the viewport, and leading to
|
|
346
295
|
// _increasePoolIfNeeded to run away creating items to try to fill it.
|
|
347
|
-
this._physicalTop = Math.min(
|
|
348
|
-
Math.floor(this._virtualStart / this._itemsPerRow) * this._physicalAverage,
|
|
349
|
-
this._scrollPosition,
|
|
350
|
-
);
|
|
296
|
+
this._physicalTop = Math.min(Math.floor(this._virtualStart) * this._physicalAverage, this._scrollPosition);
|
|
351
297
|
this._update();
|
|
352
298
|
} else if (this._physicalCount > 0) {
|
|
353
299
|
const reusables = this._getReusables(isScrollingDown);
|
|
@@ -371,7 +317,7 @@ export const ironList = {
|
|
|
371
317
|
* @param {boolean} fromTop If the potential reusable items are above the scrolling region.
|
|
372
318
|
*/
|
|
373
319
|
_getReusables(fromTop) {
|
|
374
|
-
let ith,
|
|
320
|
+
let ith, offsetContent, physicalItemHeight;
|
|
375
321
|
const idxs = [];
|
|
376
322
|
const protectedOffsetContent = this._hiddenContentSize * this._ratio;
|
|
377
323
|
const virtualStart = this._virtualStart;
|
|
@@ -385,12 +331,9 @@ export const ironList = {
|
|
|
385
331
|
|
|
386
332
|
if (fromTop) {
|
|
387
333
|
ith = this._physicalStart;
|
|
388
|
-
lastIth = this._physicalEnd;
|
|
389
334
|
offsetContent = scrollTop - top;
|
|
390
335
|
} else {
|
|
391
336
|
ith = this._physicalEnd;
|
|
392
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
393
|
-
lastIth = this._physicalStart;
|
|
394
337
|
offsetContent = bottom - scrollBottom;
|
|
395
338
|
}
|
|
396
339
|
// eslint-disable-next-line no-constant-condition
|
|
@@ -438,7 +381,6 @@ export const ironList = {
|
|
|
438
381
|
if ((itemSet && itemSet.length === 0) || this._physicalCount === 0) {
|
|
439
382
|
return;
|
|
440
383
|
}
|
|
441
|
-
this._manageFocus();
|
|
442
384
|
this._assignModels(itemSet);
|
|
443
385
|
this._updateMetrics(itemSet);
|
|
444
386
|
// Adjust offset after measuring.
|
|
@@ -464,19 +406,11 @@ export const ironList = {
|
|
|
464
406
|
* Increases the pool size.
|
|
465
407
|
*/
|
|
466
408
|
_increasePoolIfNeeded(count) {
|
|
467
|
-
|
|
409
|
+
const nextPhysicalCount = this._clamp(
|
|
468
410
|
this._physicalCount + count,
|
|
469
411
|
DEFAULT_PHYSICAL_COUNT,
|
|
470
412
|
this._virtualCount - this._virtualStart,
|
|
471
413
|
);
|
|
472
|
-
nextPhysicalCount = this._convertIndexToCompleteRow(nextPhysicalCount);
|
|
473
|
-
if (this.grid) {
|
|
474
|
-
const correction = nextPhysicalCount % this._itemsPerRow;
|
|
475
|
-
if (correction && nextPhysicalCount - correction <= this._physicalCount) {
|
|
476
|
-
nextPhysicalCount += this._itemsPerRow;
|
|
477
|
-
}
|
|
478
|
-
nextPhysicalCount -= correction;
|
|
479
|
-
}
|
|
480
414
|
const delta = nextPhysicalCount - this._physicalCount;
|
|
481
415
|
let nextIncrease = Math.round(this._physicalCount * 0.5);
|
|
482
416
|
|
|
@@ -508,8 +442,6 @@ export const ironList = {
|
|
|
508
442
|
this._templateCost = (window.performance.now() - ts) / delta;
|
|
509
443
|
nextIncrease = Math.round(this._physicalCount * 0.5);
|
|
510
444
|
}
|
|
511
|
-
// The upper bounds is not fixed when dealing with a grid that doesn't
|
|
512
|
-
// fill it's last row with the exact number of items per row.
|
|
513
445
|
if (this._virtualEnd >= this._virtualCount - 1 || nextIncrease === 0) {
|
|
514
446
|
// Do nothing.
|
|
515
447
|
} else if (!this._isClientFull()) {
|
|
@@ -547,17 +479,6 @@ export const ironList = {
|
|
|
547
479
|
}
|
|
548
480
|
},
|
|
549
481
|
|
|
550
|
-
_gridChanged(newGrid, oldGrid) {
|
|
551
|
-
if (typeof oldGrid === 'undefined') {
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
this.notifyResize();
|
|
555
|
-
flush();
|
|
556
|
-
if (newGrid) {
|
|
557
|
-
this._updateGridMetrics();
|
|
558
|
-
}
|
|
559
|
-
},
|
|
560
|
-
|
|
561
482
|
/**
|
|
562
483
|
* Called when the items have changed. That is, reassignments
|
|
563
484
|
* to `items`, splices or updates to a single item.
|
|
@@ -577,32 +498,7 @@ export const ironList = {
|
|
|
577
498
|
if (this._scrollTop > this._scrollOffset) {
|
|
578
499
|
this._resetScrollPosition(0);
|
|
579
500
|
}
|
|
580
|
-
this._removeFocusedItem();
|
|
581
501
|
this._debounce('_render', this._render, animationFrame);
|
|
582
|
-
} else if (change.path === 'items.splices') {
|
|
583
|
-
this._adjustVirtualIndex(change.value.indexSplices);
|
|
584
|
-
this._virtualCount = this.items ? this.items.length : 0;
|
|
585
|
-
// Only blur if at least one item is added or removed.
|
|
586
|
-
const itemAddedOrRemoved = change.value.indexSplices.some((splice) => {
|
|
587
|
-
return splice.addedCount > 0 || splice.removed.length > 0;
|
|
588
|
-
});
|
|
589
|
-
if (itemAddedOrRemoved) {
|
|
590
|
-
// Only blur activeElement if it is a descendant of the list (#505,
|
|
591
|
-
// #507).
|
|
592
|
-
const activeElement = this._getActiveElement();
|
|
593
|
-
if (this.contains(activeElement)) {
|
|
594
|
-
activeElement.blur();
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
// Render only if the affected index is rendered.
|
|
598
|
-
const affectedIndexRendered = change.value.indexSplices.some((splice) => {
|
|
599
|
-
return splice.index + splice.addedCount >= this._virtualStart && splice.index <= this._virtualEnd;
|
|
600
|
-
});
|
|
601
|
-
if (!this._isClientFull() || affectedIndexRendered) {
|
|
602
|
-
this._debounce('_render', this._render, animationFrame);
|
|
603
|
-
}
|
|
604
|
-
} else if (change.path !== 'items.length') {
|
|
605
|
-
this._forwardItemPath(change.path, change.value);
|
|
606
502
|
}
|
|
607
503
|
},
|
|
608
504
|
|
|
@@ -677,17 +573,8 @@ export const ironList = {
|
|
|
677
573
|
this._physicalAverageCount += this._physicalSizes[pidx] ? 1 : 0;
|
|
678
574
|
}, itemSet);
|
|
679
575
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
this._physicalSize = Math.ceil(this._physicalCount / this._itemsPerRow) * this._rowHeight;
|
|
683
|
-
} else {
|
|
684
|
-
oldPhysicalSize =
|
|
685
|
-
this._itemsPerRow === 1
|
|
686
|
-
? oldPhysicalSize
|
|
687
|
-
: Math.ceil(this._physicalCount / this._itemsPerRow) * this._rowHeight;
|
|
688
|
-
this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSize;
|
|
689
|
-
this._itemsPerRow = 1;
|
|
690
|
-
}
|
|
576
|
+
this._physicalSize = this._physicalSize + newPhysicalSize - oldPhysicalSize;
|
|
577
|
+
|
|
691
578
|
// Update the average if it measured something.
|
|
692
579
|
if (this._physicalAverageCount !== prevAvgCount) {
|
|
693
580
|
this._physicalAverage = Math.round(
|
|
@@ -696,12 +583,6 @@ export const ironList = {
|
|
|
696
583
|
}
|
|
697
584
|
},
|
|
698
585
|
|
|
699
|
-
_updateGridMetrics() {
|
|
700
|
-
this._itemWidth = this._physicalCount > 0 ? this._physicalItems[0].getBoundingClientRect().width : 200;
|
|
701
|
-
this._rowHeight = this._physicalCount > 0 ? this._physicalItems[0].offsetHeight : 200;
|
|
702
|
-
this._itemsPerRow = this._itemWidth ? Math.floor(this._viewportWidth / this._itemWidth) : this._itemsPerRow;
|
|
703
|
-
},
|
|
704
|
-
|
|
705
586
|
/**
|
|
706
587
|
* Updates the position of the physical items.
|
|
707
588
|
*/
|
|
@@ -710,59 +591,14 @@ export const ironList = {
|
|
|
710
591
|
|
|
711
592
|
let y = this._physicalTop;
|
|
712
593
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
this._iterateItems((pidx, vidx) => {
|
|
718
|
-
const modulus = vidx % this._itemsPerRow;
|
|
719
|
-
let x = Math.floor(modulus * this._itemWidth + rowOffset);
|
|
720
|
-
if (this._isRTL) {
|
|
721
|
-
x *= -1;
|
|
722
|
-
}
|
|
723
|
-
this.translate3d(`${x}px`, `${y}px`, 0, this._physicalItems[pidx]);
|
|
724
|
-
if (this._shouldRenderNextRow(vidx)) {
|
|
725
|
-
y += this._rowHeight;
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
} else {
|
|
729
|
-
const order = [];
|
|
730
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
731
|
-
this._iterateItems((pidx, vidx) => {
|
|
732
|
-
const item = this._physicalItems[pidx];
|
|
733
|
-
this.translate3d(0, `${y}px`, 0, item);
|
|
734
|
-
y += this._physicalSizes[pidx];
|
|
735
|
-
const itemId = item.id;
|
|
736
|
-
if (itemId) {
|
|
737
|
-
order.push(itemId);
|
|
738
|
-
}
|
|
739
|
-
});
|
|
740
|
-
if (order.length) {
|
|
741
|
-
this.setAttribute('aria-owns', order.join(' '));
|
|
742
|
-
}
|
|
743
|
-
}
|
|
594
|
+
this._iterateItems((pidx) => {
|
|
595
|
+
this.translate3d(0, `${y}px`, 0, this._physicalItems[pidx]);
|
|
596
|
+
y += this._physicalSizes[pidx];
|
|
597
|
+
});
|
|
744
598
|
},
|
|
745
599
|
|
|
746
600
|
_getPhysicalSizeIncrement(pidx) {
|
|
747
|
-
|
|
748
|
-
return this._physicalSizes[pidx];
|
|
749
|
-
}
|
|
750
|
-
if (this._computeVidx(pidx) % this._itemsPerRow !== this._itemsPerRow - 1) {
|
|
751
|
-
return 0;
|
|
752
|
-
}
|
|
753
|
-
return this._rowHeight;
|
|
754
|
-
},
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Returns, based on the current index,
|
|
758
|
-
* whether or not the next index will need
|
|
759
|
-
* to be rendered on a new row.
|
|
760
|
-
*
|
|
761
|
-
* @param {number} vidx Virtual index
|
|
762
|
-
* @return {boolean}
|
|
763
|
-
*/
|
|
764
|
-
_shouldRenderNextRow(vidx) {
|
|
765
|
-
return vidx % this._itemsPerRow === this._itemsPerRow - 1;
|
|
601
|
+
return this._physicalSizes[pidx];
|
|
766
602
|
},
|
|
767
603
|
|
|
768
604
|
/**
|
|
@@ -799,16 +635,12 @@ export const ironList = {
|
|
|
799
635
|
* @param {boolean=} forceUpdate If true, updates the height no matter what.
|
|
800
636
|
*/
|
|
801
637
|
_updateScrollerSize(forceUpdate) {
|
|
802
|
-
|
|
803
|
-
this.
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
this._physicalBottom +
|
|
807
|
-
Math.max(this._virtualCount - this._physicalCount - this._virtualStart, 0) * this._physicalAverage;
|
|
808
|
-
}
|
|
638
|
+
this._estScrollHeight =
|
|
639
|
+
this._physicalBottom +
|
|
640
|
+
Math.max(this._virtualCount - this._physicalCount - this._virtualStart, 0) * this._physicalAverage;
|
|
641
|
+
|
|
809
642
|
forceUpdate = forceUpdate || this._scrollHeight === 0;
|
|
810
643
|
forceUpdate = forceUpdate || this._scrollPosition >= this._estScrollHeight - this._physicalSize;
|
|
811
|
-
forceUpdate = forceUpdate || (this.grid && this.$.items.style.height < this._estScrollHeight);
|
|
812
644
|
// Amortize height adjustment, so it won't trigger large repaints too often.
|
|
813
645
|
if (forceUpdate || Math.abs(this._estScrollHeight - this._scrollHeight) >= this._viewportHeight) {
|
|
814
646
|
this.$.items.style.height = `${this._estScrollHeight}px`;
|
|
@@ -835,13 +667,12 @@ export const ironList = {
|
|
|
835
667
|
idx = this._clamp(idx, 0, this._virtualCount - 1);
|
|
836
668
|
// Update the virtual start only when needed.
|
|
837
669
|
if (!this._isIndexRendered(idx) || idx >= this._maxVirtualStart) {
|
|
838
|
-
this._virtualStart =
|
|
670
|
+
this._virtualStart = idx - 1;
|
|
839
671
|
}
|
|
840
|
-
this._manageFocus();
|
|
841
672
|
this._assignModels();
|
|
842
673
|
this._updateMetrics();
|
|
843
674
|
// Estimate new physical offset.
|
|
844
|
-
this._physicalTop =
|
|
675
|
+
this._physicalTop = this._virtualStart * this._physicalAverage;
|
|
845
676
|
|
|
846
677
|
let currentTopItem = this._physicalStart;
|
|
847
678
|
let currentVirtualItem = this._virtualStart;
|
|
@@ -896,49 +727,10 @@ export const ironList = {
|
|
|
896
727
|
);
|
|
897
728
|
},
|
|
898
729
|
|
|
899
|
-
/**
|
|
900
|
-
* Updates the size of a given list item.
|
|
901
|
-
*
|
|
902
|
-
* @method updateSizeForItem
|
|
903
|
-
* @param {Object} item The item instance.
|
|
904
|
-
*/
|
|
905
|
-
updateSizeForItem(item) {
|
|
906
|
-
return this.updateSizeForIndex(this.items.indexOf(item));
|
|
907
|
-
},
|
|
908
|
-
|
|
909
|
-
/**
|
|
910
|
-
* Updates the size of the item at the given index in the items array.
|
|
911
|
-
*
|
|
912
|
-
* @method updateSizeForIndex
|
|
913
|
-
* @param {number} index The index of the item in the items array.
|
|
914
|
-
*/
|
|
915
|
-
updateSizeForIndex(index) {
|
|
916
|
-
if (!this._isIndexRendered(index)) {
|
|
917
|
-
return null;
|
|
918
|
-
}
|
|
919
|
-
this._updateMetrics([this._getPhysicalIndex(index)]);
|
|
920
|
-
this._positionItems();
|
|
921
|
-
return null;
|
|
922
|
-
},
|
|
923
|
-
|
|
924
|
-
/**
|
|
925
|
-
* Converts a random index to the index of the item that completes it's row.
|
|
926
|
-
* Allows for better order and fill computation when grid == true.
|
|
927
|
-
*/
|
|
928
|
-
_convertIndexToCompleteRow(idx) {
|
|
929
|
-
// When grid == false _itemPerRow can be unset.
|
|
930
|
-
this._itemsPerRow = this._itemsPerRow || 1;
|
|
931
|
-
return this.grid ? Math.ceil(idx / this._itemsPerRow) * this._itemsPerRow : idx;
|
|
932
|
-
},
|
|
933
|
-
|
|
934
730
|
_isIndexRendered(idx) {
|
|
935
731
|
return idx >= this._virtualStart && idx <= this._virtualEnd;
|
|
936
732
|
},
|
|
937
733
|
|
|
938
|
-
_isIndexVisible(idx) {
|
|
939
|
-
return idx >= this.firstVisibleIndex && idx <= this.lastVisibleIndex;
|
|
940
|
-
},
|
|
941
|
-
|
|
942
734
|
_getPhysicalIndex(vidx) {
|
|
943
735
|
return (this._physicalStart + (vidx - this._virtualStart)) % this._physicalCount;
|
|
944
736
|
},
|
package/src/keyboard-mixin.d.ts
CHANGED
|
@@ -14,7 +14,20 @@ export declare function KeyboardMixin<T extends Constructor<HTMLElement>>(base:
|
|
|
14
14
|
|
|
15
15
|
export declare class KeyboardMixinClass {
|
|
16
16
|
/**
|
|
17
|
-
* A handler for the
|
|
17
|
+
* A handler for the "Enter" key. By default, it does nothing.
|
|
18
|
+
* Override the method to implement your own behavior.
|
|
19
|
+
*/
|
|
20
|
+
protected _onEnter(event: KeyboardEvent): void;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A handler for the "Escape" key. By default, it does nothing.
|
|
24
|
+
* Override the method to implement your own behavior.
|
|
25
|
+
*/
|
|
26
|
+
protected _onEscape(event: KeyboardEvent): void;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A handler for the `keydown` event. By default, it calls
|
|
30
|
+
* separate methods for handling "Enter" and "Escape" keys.
|
|
18
31
|
* Override the method to implement your own behavior.
|
|
19
32
|
*/
|
|
20
33
|
protected _onKeyDown(event: KeyboardEvent): void;
|
package/src/keyboard-mixin.js
CHANGED
|
@@ -29,14 +29,24 @@ export const KeyboardMixin = dedupingMixin(
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* A handler for the `keydown` event. By default, it
|
|
32
|
+
* A handler for the `keydown` event. By default, it calls
|
|
33
|
+
* separate methods for handling "Enter" and "Escape" keys.
|
|
33
34
|
* Override the method to implement your own behavior.
|
|
34
35
|
*
|
|
35
|
-
* @param {KeyboardEvent}
|
|
36
|
+
* @param {KeyboardEvent} event
|
|
36
37
|
* @protected
|
|
37
38
|
*/
|
|
38
|
-
_onKeyDown(
|
|
39
|
-
|
|
39
|
+
_onKeyDown(event) {
|
|
40
|
+
switch (event.key) {
|
|
41
|
+
case 'Enter':
|
|
42
|
+
this._onEnter(event);
|
|
43
|
+
break;
|
|
44
|
+
case 'Escape':
|
|
45
|
+
this._onEscape(event);
|
|
46
|
+
break;
|
|
47
|
+
default:
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
/**
|
|
@@ -49,5 +59,27 @@ export const KeyboardMixin = dedupingMixin(
|
|
|
49
59
|
_onKeyUp(_event) {
|
|
50
60
|
// To be implemented.
|
|
51
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A handler for the "Enter" key. By default, it does nothing.
|
|
65
|
+
* Override the method to implement your own behavior.
|
|
66
|
+
*
|
|
67
|
+
* @param {KeyboardEvent} _event
|
|
68
|
+
* @protected
|
|
69
|
+
*/
|
|
70
|
+
_onEnter(_event) {
|
|
71
|
+
// To be implemented.
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A handler for the "Escape" key. By default, it does nothing.
|
|
76
|
+
* Override the method to implement your own behavior.
|
|
77
|
+
*
|
|
78
|
+
* @param {KeyboardEvent} _event
|
|
79
|
+
* @protected
|
|
80
|
+
*/
|
|
81
|
+
_onEscape(_event) {
|
|
82
|
+
// To be implemented.
|
|
83
|
+
}
|
|
52
84
|
},
|
|
53
85
|
);
|
|
@@ -9,15 +9,6 @@ import { ReactiveController } from 'lit';
|
|
|
9
9
|
* A controller for listening on media query changes.
|
|
10
10
|
*/
|
|
11
11
|
export class MediaQueryController implements ReactiveController {
|
|
12
|
-
/**
|
|
13
|
-
* @param {HTMLElement} host
|
|
14
|
-
*/
|
|
15
|
-
constructor(query: string, callback: (matches: boolean) => void);
|
|
16
|
-
|
|
17
|
-
hostConnected(): void;
|
|
18
|
-
|
|
19
|
-
hostDisconnected(): void;
|
|
20
|
-
|
|
21
12
|
/**
|
|
22
13
|
* The CSS media query to evaluate.
|
|
23
14
|
*/
|
|
@@ -27,4 +18,13 @@ export class MediaQueryController implements ReactiveController {
|
|
|
27
18
|
* Function to call when media query changes.
|
|
28
19
|
*/
|
|
29
20
|
protected callback: (matches: boolean) => void;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {HTMLElement} host
|
|
24
|
+
*/
|
|
25
|
+
constructor(query: string, callback: (matches: boolean) => void);
|
|
26
|
+
|
|
27
|
+
hostConnected(): void;
|
|
28
|
+
|
|
29
|
+
hostDisconnected(): void;
|
|
30
30
|
}
|
package/src/resize-mixin.d.ts
CHANGED
|
@@ -11,15 +11,15 @@ import { Constructor } from '@open-wc/dedupe-mixin';
|
|
|
11
11
|
export declare function ResizeMixin<T extends Constructor<HTMLElement>>(base: T): T & Constructor<ResizeMixinClass>;
|
|
12
12
|
|
|
13
13
|
export declare class ResizeMixinClass {
|
|
14
|
-
/**
|
|
15
|
-
* A handler invoked on host resize. By default, it does nothing.
|
|
16
|
-
* Override the method to implement your own behavior.
|
|
17
|
-
*/
|
|
18
|
-
protected _onResize(contentRect: DOMRect): void;
|
|
19
|
-
|
|
20
14
|
/**
|
|
21
15
|
* When true, the parent element resize will be also observed.
|
|
22
16
|
* Override this getter and return `true` to enable this.
|
|
23
17
|
*/
|
|
24
18
|
protected readonly _observeParent: boolean;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A handler invoked on host resize. By default, it does nothing.
|
|
22
|
+
* Override the method to implement your own behavior.
|
|
23
|
+
*/
|
|
24
|
+
protected _onResize(contentRect: DOMRect): void;
|
|
25
25
|
}
|
package/src/slot-controller.d.ts
CHANGED
|
@@ -6,15 +6,6 @@
|
|
|
6
6
|
import { ReactiveController } from 'lit';
|
|
7
7
|
|
|
8
8
|
export class SlotController extends EventTarget implements ReactiveController {
|
|
9
|
-
constructor(
|
|
10
|
-
host: HTMLElement,
|
|
11
|
-
slotName: string,
|
|
12
|
-
slotFactory?: () => HTMLElement,
|
|
13
|
-
slotInitializer?: (host: HTMLElement, node: HTMLElement) => void,
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
hostConnected(): void;
|
|
17
|
-
|
|
18
9
|
/**
|
|
19
10
|
* The controller host element.
|
|
20
11
|
*/
|
|
@@ -25,17 +16,26 @@ export class SlotController extends EventTarget implements ReactiveController {
|
|
|
25
16
|
*/
|
|
26
17
|
node: HTMLElement;
|
|
27
18
|
|
|
28
|
-
/**
|
|
29
|
-
* Return a reference to the node managed by the controller.
|
|
30
|
-
*/
|
|
31
|
-
getSlotChild(): Node;
|
|
32
|
-
|
|
33
19
|
protected initialized: boolean;
|
|
34
20
|
|
|
35
21
|
protected defaultNode: Node;
|
|
36
22
|
|
|
37
23
|
protected defaultId: string;
|
|
38
24
|
|
|
25
|
+
constructor(
|
|
26
|
+
host: HTMLElement,
|
|
27
|
+
slotName: string,
|
|
28
|
+
slotFactory?: () => HTMLElement,
|
|
29
|
+
slotInitializer?: (host: HTMLElement, node: HTMLElement) => void,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
hostConnected(): void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Return a reference to the node managed by the controller.
|
|
36
|
+
*/
|
|
37
|
+
getSlotChild(): Node;
|
|
38
|
+
|
|
39
39
|
protected attachDefaultNode(): Node | undefined;
|
|
40
40
|
|
|
41
41
|
protected initNode(node: Node): void;
|
package/src/slot-controller.js
CHANGED
|
@@ -10,16 +10,6 @@ import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nod
|
|
|
10
10
|
* A controller for providing content to slot element and observing changes.
|
|
11
11
|
*/
|
|
12
12
|
export class SlotController extends EventTarget {
|
|
13
|
-
constructor(host, slotName, slotFactory, slotInitializer) {
|
|
14
|
-
super();
|
|
15
|
-
|
|
16
|
-
this.host = host;
|
|
17
|
-
this.slotName = slotName;
|
|
18
|
-
this.slotFactory = slotFactory;
|
|
19
|
-
this.slotInitializer = slotInitializer;
|
|
20
|
-
this.defaultId = SlotController.generateId(slotName, host);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
13
|
/**
|
|
24
14
|
* Ensure that every instance has unique ID.
|
|
25
15
|
*
|
|
@@ -40,6 +30,16 @@ export class SlotController extends EventTarget {
|
|
|
40
30
|
return `${prefix}-${host.localName}-${this[field]}`;
|
|
41
31
|
}
|
|
42
32
|
|
|
33
|
+
constructor(host, slotName, slotFactory, slotInitializer) {
|
|
34
|
+
super();
|
|
35
|
+
|
|
36
|
+
this.host = host;
|
|
37
|
+
this.slotName = slotName;
|
|
38
|
+
this.slotFactory = slotFactory;
|
|
39
|
+
this.slotInitializer = slotInitializer;
|
|
40
|
+
this.defaultId = SlotController.generateId(slotName, host);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
43
|
hostConnected() {
|
|
44
44
|
if (!this.initialized) {
|
|
45
45
|
let node = this.getSlotChild();
|
|
@@ -26,6 +26,11 @@ export class IronListAdapter {
|
|
|
26
26
|
// Iron-list uses this value to determine how many pages of elements to render
|
|
27
27
|
this._maxPages = 1.3;
|
|
28
28
|
|
|
29
|
+
// Placeholder height (used for sizing elements that have intrinsic 0 height after update)
|
|
30
|
+
this.__placeholderHeight = 200;
|
|
31
|
+
// A queue of 10 previous element heights
|
|
32
|
+
this.__elementHeightQueue = Array(10);
|
|
33
|
+
|
|
29
34
|
this.timeouts = {
|
|
30
35
|
SCROLL_REORDER: 500,
|
|
31
36
|
IGNORE_WHEEL: 500,
|
|
@@ -60,10 +65,6 @@ export class IronListAdapter {
|
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
_manageFocus() {}
|
|
64
|
-
|
|
65
|
-
_removeFocusedItem() {}
|
|
66
|
-
|
|
67
68
|
get scrollOffset() {
|
|
68
69
|
return 0;
|
|
69
70
|
}
|
|
@@ -135,9 +136,9 @@ export class IronListAdapter {
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
__updateElement(el, index, forceSameIndexUpdates) {
|
|
138
|
-
// Clean up temporary
|
|
139
|
-
if (el.style.
|
|
140
|
-
el.style.
|
|
139
|
+
// Clean up temporary placeholder sizing
|
|
140
|
+
if (el.style.paddingTop) {
|
|
141
|
+
el.style.paddingTop = '';
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
if (!this.__preventElementUpdates && (el.__lastUpdatedIndex !== index || forceSameIndexUpdates)) {
|
|
@@ -145,12 +146,22 @@ export class IronListAdapter {
|
|
|
145
146
|
el.__lastUpdatedIndex = index;
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
|
|
149
|
+
const elementHeight = el.offsetHeight;
|
|
150
|
+
if (elementHeight === 0) {
|
|
149
151
|
// If the elements have 0 height after update (for example due to lazy rendering),
|
|
150
152
|
// it results in iron-list requesting to create an unlimited count of elements.
|
|
151
|
-
// Assign a temporary
|
|
153
|
+
// Assign a temporary placeholder sizing to elements that would otherwise end up having
|
|
152
154
|
// no height.
|
|
153
|
-
el.style.
|
|
155
|
+
el.style.paddingTop = `${this.__placeholderHeight}px`;
|
|
156
|
+
} else {
|
|
157
|
+
// Add element height to the queue
|
|
158
|
+
this.__elementHeightQueue.push(elementHeight);
|
|
159
|
+
this.__elementHeightQueue.shift();
|
|
160
|
+
|
|
161
|
+
// Calcualte new placeholder height based on the average of the defined values in the
|
|
162
|
+
// element height queue
|
|
163
|
+
const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
|
|
164
|
+
this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
|
|
154
165
|
}
|
|
155
166
|
}
|
|
156
167
|
|
|
@@ -301,16 +312,28 @@ export class IronListAdapter {
|
|
|
301
312
|
|
|
302
313
|
_scrollHandler() {
|
|
303
314
|
this._adjustVirtualIndexOffset(this._scrollTop - (this.__previousScrollTop || 0));
|
|
315
|
+
const delta = this.scrollTarget.scrollTop - this._scrollPosition;
|
|
304
316
|
|
|
305
317
|
super._scrollHandler();
|
|
306
318
|
|
|
307
319
|
if (this._physicalCount !== 0) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
320
|
+
const isScrollingDown = delta >= 0;
|
|
321
|
+
const reusables = this._getReusables(!isScrollingDown);
|
|
322
|
+
|
|
323
|
+
if (reusables.indexes.length) {
|
|
324
|
+
// After running super._scrollHandler, fix internal properties to workaround an iron-list issue.
|
|
325
|
+
// See https://github.com/vaadin/web-components/issues/1691
|
|
326
|
+
this._physicalTop = reusables.physicalTop;
|
|
327
|
+
|
|
328
|
+
if (isScrollingDown) {
|
|
329
|
+
this._virtualStart -= reusables.indexes.length;
|
|
330
|
+
this._physicalStart -= reusables.indexes.length;
|
|
331
|
+
} else {
|
|
332
|
+
this._virtualStart += reusables.indexes.length;
|
|
333
|
+
this._physicalStart += reusables.indexes.length;
|
|
334
|
+
}
|
|
335
|
+
this._resizeHandler();
|
|
336
|
+
}
|
|
314
337
|
}
|
|
315
338
|
|
|
316
339
|
if (this.reorderElements) {
|