@vaadin/component-base 24.1.0-alpha1 → 24.1.0-alpha10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/component-base",
3
- "version": "24.1.0-alpha1",
3
+ "version": "24.1.0-alpha10",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -42,5 +42,5 @@
42
42
  "@vaadin/testing-helpers": "^0.4.0",
43
43
  "sinon": "^13.0.2"
44
44
  },
45
- "gitHead": "599a339181595923b9ad6373d6888d8a79540141"
45
+ "gitHead": "12e39be7eb3b49c68708e8ca3de2fb22e91051a1"
46
46
  }
package/src/async.js CHANGED
@@ -20,13 +20,10 @@
20
20
  * asynchronous tasks.
21
21
  */
22
22
 
23
- // Microtask implemented using Mutation Observer
24
23
  let microtaskCurrHandle = 0;
25
24
  let microtaskLastHandle = 0;
26
25
  const microtaskCallbacks = [];
27
- let microtaskNodeContent = 0;
28
26
  let microtaskScheduled = false;
29
- const microtaskNode = document.createTextNode('');
30
27
 
31
28
  function microtaskFlush() {
32
29
  microtaskScheduled = false;
@@ -47,8 +44,6 @@ function microtaskFlush() {
47
44
  microtaskLastHandle += len;
48
45
  }
49
46
 
50
- new window.MutationObserver(microtaskFlush).observe(microtaskNode, { characterData: true });
51
-
52
47
  /**
53
48
  * Async interface wrapper around `setTimeout`.
54
49
  *
@@ -166,12 +161,6 @@ export { idlePeriod };
166
161
  /**
167
162
  * Async interface for enqueuing callbacks that run at microtask timing.
168
163
  *
169
- * Note that microtask timing is achieved via a single `MutationObserver`,
170
- * and thus callbacks enqueued with this API will all run in a single
171
- * batch, and not interleaved with other microtasks such as promises.
172
- * Promises are avoided as an implementation choice for the time being
173
- * due to Safari bugs that cause Promises to lack microtask guarantees.
174
- *
175
164
  * @namespace
176
165
  * @summary Async interface for enqueuing callbacks that run at microtask
177
166
  * timing.
@@ -187,8 +176,7 @@ const microTask = {
187
176
  run(callback) {
188
177
  if (!microtaskScheduled) {
189
178
  microtaskScheduled = true;
190
- microtaskNode.textContent = microtaskNodeContent;
191
- microtaskNodeContent += 1;
179
+ queueMicrotask(() => microtaskFlush());
192
180
  }
193
181
  microtaskCallbacks.push(callback);
194
182
  const result = microtaskCurrHandle;
@@ -13,6 +13,16 @@
13
13
  */
14
14
  export function getAncestorRootNodes(node: Node): Node[];
15
15
 
16
+ /**
17
+ * Takes a string with values separated by space and returns a set the values
18
+ */
19
+ export function deserializeAttributeValue(value: string): Set<string>;
20
+
21
+ /**
22
+ * Takes a set of string values and returns a string with values separated by space
23
+ */
24
+ export function serializeAttributeValue(values: Set<string>): string;
25
+
16
26
  /**
17
27
  * Adds a value to an attribute containing space-delimited values.
18
28
  */
package/src/dom-utils.js CHANGED
@@ -41,10 +41,12 @@ export function getAncestorRootNodes(node) {
41
41
  }
42
42
 
43
43
  /**
44
+ * Takes a string with values separated by space and returns a set the values
45
+ *
44
46
  * @param {string} value
45
47
  * @return {Set<string>}
46
48
  */
47
- function deserializeAttributeValue(value) {
49
+ export function deserializeAttributeValue(value) {
48
50
  if (!value) {
49
51
  return new Set();
50
52
  }
@@ -53,11 +55,13 @@ function deserializeAttributeValue(value) {
53
55
  }
54
56
 
55
57
  /**
58
+ * Takes a set of string values and returns a string with values separated by space
59
+ *
56
60
  * @param {Set<string>} values
57
61
  * @return {string}
58
62
  */
59
- function serializeAttributeValue(values) {
60
- return [...values].join(' ');
63
+ export function serializeAttributeValue(values) {
64
+ return values ? [...values].join(' ') : '';
61
65
  }
62
66
 
63
67
  /**
@@ -45,7 +45,7 @@ const registered = new Set();
45
45
  export const ElementMixin = (superClass) =>
46
46
  class VaadinElementMixin extends DirMixin(superClass) {
47
47
  static get version() {
48
- return '24.1.0-alpha1';
48
+ return '24.1.0-alpha10';
49
49
  }
50
50
 
51
51
  /** @protected */
@@ -136,11 +136,15 @@ export class IronListAdapter {
136
136
  }
137
137
 
138
138
  update(startIndex = 0, endIndex = this.size - 1) {
139
+ const updatedElements = [];
139
140
  this.__getVisibleElements().forEach((el) => {
140
141
  if (el.__virtualIndex >= startIndex && el.__virtualIndex <= endIndex) {
141
142
  this.__updateElement(el, el.__virtualIndex, true);
143
+ updatedElements.push(el);
142
144
  }
143
145
  });
146
+
147
+ this.__afterElementsUpdated(updatedElements);
144
148
  }
145
149
 
146
150
  /**
@@ -203,28 +207,40 @@ export class IronListAdapter {
203
207
  this.updateElement(el, index);
204
208
  el.__lastUpdatedIndex = index;
205
209
  }
210
+ }
206
211
 
207
- const elementHeight = el.offsetHeight;
208
- if (elementHeight === 0) {
209
- // If the elements have 0 height after update (for example due to lazy rendering),
210
- // it results in iron-list requesting to create an unlimited count of elements.
211
- // Assign a temporary placeholder sizing to elements that would otherwise end up having
212
- // no height.
213
- el.style.paddingTop = `${this.__placeholderHeight}px`;
214
-
215
- // Manually schedule the resize handler to make sure the placeholder padding is
216
- // cleared in case the resize observer never triggers.
217
- requestAnimationFrame(() => this._resizeHandler());
218
- } else {
219
- // Add element height to the queue
220
- this.__elementHeightQueue.push(elementHeight);
221
- this.__elementHeightQueue.shift();
222
-
223
- // Calcualte new placeholder height based on the average of the defined values in the
224
- // element height queue
225
- const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
226
- this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
227
- }
212
+ /**
213
+ * Called synchronously right after elements have been updated.
214
+ * This is a good place to do any post-update work.
215
+ *
216
+ * @param {!Array<!HTMLElement>} updatedElements
217
+ */
218
+ __afterElementsUpdated(updatedElements) {
219
+ updatedElements.forEach((el) => {
220
+ const elementHeight = el.offsetHeight;
221
+ if (elementHeight === 0) {
222
+ // If the elements have 0 height after update (for example due to lazy rendering),
223
+ // it results in iron-list requesting to create an unlimited count of elements.
224
+ // Assign a temporary placeholder sizing to elements that would otherwise end up having
225
+ // no height.
226
+ el.style.paddingTop = `${this.__placeholderHeight}px`;
227
+
228
+ // Manually schedule the resize handler to make sure the placeholder padding is
229
+ // cleared in case the resize observer never triggers.
230
+ this.__placeholderClearDebouncer = Debouncer.debounce(this.__placeholderClearDebouncer, animationFrame, () =>
231
+ this._resizeHandler(),
232
+ );
233
+ } else {
234
+ // Add element height to the queue
235
+ this.__elementHeightQueue.push(elementHeight);
236
+ this.__elementHeightQueue.shift();
237
+
238
+ // Calculate new placeholder height based on the average of the defined values in the
239
+ // element height queue
240
+ const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
241
+ this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
242
+ }
243
+ });
228
244
  }
229
245
 
230
246
  __getIndexScrollOffset(index) {
@@ -249,42 +265,28 @@ export class IronListAdapter {
249
265
  this._debouncers._increasePoolIfNeeded.cancel();
250
266
  }
251
267
 
252
- // Prevent element update while the scroll position is being restored
253
- this.__preventElementUpdates = true;
254
-
255
- // Record the scroll position before changing the size
256
- let fvi; // First visible index
257
- let fviOffsetBefore; // Scroll offset of the first visible index
258
- if (size > 0) {
259
- fvi = this.adjustedFirstVisibleIndex;
260
- fviOffsetBefore = this.__getIndexScrollOffset(fvi);
261
- }
262
-
263
268
  // Change the size
264
269
  this.__size = size;
265
270
 
266
- this._itemsChanged({
267
- path: 'items',
268
- });
269
- flush();
270
-
271
- // Try to restore the scroll position if the new size is larger than 0
272
- if (size > 0) {
273
- fvi = Math.min(fvi, size - 1);
274
- this.scrollToIndex(fvi);
275
-
276
- const fviOffsetAfter = this.__getIndexScrollOffset(fvi);
277
- if (fviOffsetBefore !== undefined && fviOffsetAfter !== undefined) {
278
- this._scrollTop += fviOffsetBefore - fviOffsetAfter;
279
- }
271
+ if (!this._physicalItems) {
272
+ // Not initialized yet
273
+ this._itemsChanged({
274
+ path: 'items',
275
+ });
276
+ this.__preventElementUpdates = true;
277
+ flush();
278
+ this.__preventElementUpdates = false;
279
+ } else {
280
+ // Already initialized, just update _virtualCount
281
+ this._virtualCount = this.items.length;
280
282
  }
281
283
 
282
284
  if (!this.elementsContainer.children.length) {
283
285
  requestAnimationFrame(() => this._resizeHandler());
284
286
  }
285
287
 
286
- this.__preventElementUpdates = false;
287
- // Schedule and flush a resize handler
288
+ // Schedule and flush a resize handler. This will cause a
289
+ // re-render for the elements.
288
290
  this._resizeHandler();
289
291
  flush();
290
292
  }
@@ -349,16 +351,20 @@ export class IronListAdapter {
349
351
 
350
352
  /** @private */
351
353
  _assignModels(itemSet) {
354
+ const updatedElements = [];
352
355
  this._iterateItems((pidx, vidx) => {
353
356
  const el = this._physicalItems[pidx];
354
357
  el.hidden = vidx >= this.size;
355
358
  if (!el.hidden) {
356
359
  el.__virtualIndex = vidx + (this._vidxOffset || 0);
357
360
  this.__updateElement(el, el.__virtualIndex);
361
+ updatedElements.push(el);
358
362
  } else {
359
363
  delete el.__lastUpdatedIndex;
360
364
  }
361
365
  }, itemSet);
366
+
367
+ this.__afterElementsUpdated(updatedElements);
362
368
  }
363
369
 
364
370
  /** @private */
@@ -557,6 +563,29 @@ export class IronListAdapter {
557
563
  );
558
564
  }
559
565
 
566
+ /**
567
+ * Increases the pool size.
568
+ * @override
569
+ */
570
+ _increasePoolIfNeeded(count) {
571
+ if (this._physicalCount > 2 && count) {
572
+ // The iron-list logic has already created some physical items and
573
+ // has decided to create more. Since each item creation round is
574
+ // expensive, let's try to create the remaining items in one go.
575
+
576
+ // Calculate the total item count that would be needed to fill the viewport
577
+ // plus the buffer assuming rest of the items to be of the average size
578
+ // of the items already created.
579
+ const totalItemCount = Math.ceil(this._optPhysicalSize / this._physicalAverage);
580
+ const missingItemCount = totalItemCount - this._physicalCount;
581
+ // Create the remaining items in one go. Use a maximum of 100 items
582
+ // as a safety measure.
583
+ super._increasePoolIfNeeded(Math.max(count, Math.min(100, missingItemCount)));
584
+ } else {
585
+ super._increasePoolIfNeeded(count);
586
+ }
587
+ }
588
+
560
589
  /**
561
590
  * @returns {Number|undefined} - The browser's default font-size in pixels
562
591
  * @private