@vaadin/component-base 23.6.3 → 23.6.5

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": "23.6.3",
3
+ "version": "23.6.5",
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": "4addcb881465a37108f64abc59d7219c7187a432"
45
+ "gitHead": "d55853f02f62ce8a506a3ffd4f4a91dfb5cc66a2"
46
46
  }
@@ -44,7 +44,7 @@ const registered = new Set();
44
44
  export const ElementMixin = (superClass) =>
45
45
  class VaadinElementMixin extends DirMixin(superClass) {
46
46
  static get version() {
47
- return '23.6.3';
47
+ return '23.6.5';
48
48
  }
49
49
 
50
50
  /** @protected */
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2000 - 2024 Vaadin Ltd.
4
+ *
5
+ * This program is available under Vaadin Commercial License and Service Terms.
6
+ *
7
+ *
8
+ * See https://vaadin.com/commercial-license-and-service-terms for the full
9
+ * license.
10
+ */
11
+
12
+ /**
13
+ * Recursively copies own properties of `source` into `target` and returns
14
+ * `target`. Plain objects are merged, other values are assigned as they are.
15
+ * An object is plain when it inherits from `Object.prototype` or from nothing,
16
+ * so values such as a `Date` or a class instance are assigned, not merged.
17
+ *
18
+ * Merges a single source. Use `deepMergePartials()` to merge several objects,
19
+ * or to merge objects that only provide some of the properties.
20
+ *
21
+ * Both arguments are expected to be plain objects. When either of them is not,
22
+ * `target` is returned without changes. A property of the target that is not a
23
+ * plain object is replaced with the merged object, unlike the arguments.
24
+ *
25
+ * Keys that would modify `Object.prototype`, such as `__proto__`, are ignored.
26
+ */
27
+ export function deepMerge<T extends object>(target: T, source: object): T;
28
+
29
+ /**
30
+ * Recursively merges partial objects into `target` in order and returns
31
+ * `target`, so that a later source overrides an earlier one.
32
+ *
33
+ * Values that are `null` or `undefined` are skipped, so a source that only
34
+ * provides some of the properties does not remove the others. For the same
35
+ * reason, a property that the target already has is not replaced with an
36
+ * object when the source has one for the same key. Arrays are copied one level
37
+ * deep, so that the result does not share an array with any of the sources.
38
+ *
39
+ * Sources that are not plain objects are ignored. When `target` is not a plain
40
+ * object, it is returned without changes.
41
+ *
42
+ * Keys that would modify `Object.prototype`, such as `__proto__`, are ignored.
43
+ */
44
+ export function deepMergePartials<T extends object>(target: T, ...sources: object[]): T;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2000 - 2024 Vaadin Ltd.
4
+ *
5
+ * This program is available under Vaadin Commercial License and Service Terms.
6
+ *
7
+ *
8
+ * See https://vaadin.com/commercial-license-and-service-terms for the full
9
+ * license.
10
+ */
11
+
12
+ /**
13
+ * Keys that are not copied while merging, as assigning them would modify
14
+ * `Object.prototype` instead of the merge target, and so affect every
15
+ * object in the application.
16
+ */
17
+ const IGNORED_KEYS = ['__proto__', 'constructor', 'prototype'];
18
+
19
+ const { hasOwnProperty } = Object.prototype;
20
+
21
+ const isPlainObject = (value) => {
22
+ if (!value || typeof value !== 'object') {
23
+ return false;
24
+ }
25
+ const prototype = Object.getPrototypeOf(value);
26
+ return prototype === Object.prototype || prototype === null;
27
+ };
28
+
29
+ /**
30
+ * Merges `source` into `target`. With `partial`, the source is treated as an
31
+ * object that may provide only some of the properties: nullish values are
32
+ * skipped and arrays are copied instead of shared.
33
+ */
34
+ function merge(target, source, partial) {
35
+ if (!isPlainObject(target) || !isPlainObject(source)) {
36
+ return target;
37
+ }
38
+
39
+ Object.keys(source).forEach((key) => {
40
+ if (IGNORED_KEYS.includes(key)) {
41
+ return;
42
+ }
43
+
44
+ const value = source[key];
45
+
46
+ if (isPlainObject(value)) {
47
+ // Only merge into an own plain object, so that the merge can never
48
+ // continue into an object inherited from the prototype chain.
49
+ if (!hasOwnProperty.call(target, key) || !isPlainObject(target[key])) {
50
+ // With `partial`, a value that the target already has is kept, so that
51
+ // a source property of an unexpected type does not remove a default.
52
+ if (partial && hasOwnProperty.call(target, key) && target[key]) {
53
+ return;
54
+ }
55
+
56
+ target[key] = {};
57
+ }
58
+
59
+ merge(target[key], value, partial);
60
+ } else if (partial && Array.isArray(value)) {
61
+ target[key] = [...value];
62
+ } else if (!partial || (value !== undefined && value !== null)) {
63
+ target[key] = value;
64
+ }
65
+ });
66
+
67
+ return target;
68
+ }
69
+
70
+ /**
71
+ * Recursively copies own properties of `source` into `target` and returns
72
+ * `target`. Plain objects are merged, other values are assigned as they are.
73
+ * An object is plain when it inherits from `Object.prototype` or from nothing,
74
+ * so values such as a `Date` or a class instance are assigned, not merged.
75
+ *
76
+ * Merges a single source. Use `deepMergePartials()` to merge several objects,
77
+ * or to merge objects that only provide some of the properties.
78
+ *
79
+ * Both arguments are expected to be plain objects. When either of them is not,
80
+ * `target` is returned without changes. A property of the target that is not a
81
+ * plain object is replaced with the merged object, unlike the arguments.
82
+ *
83
+ * Keys that would modify `Object.prototype`, such as `__proto__`, are ignored.
84
+ *
85
+ * @param {object} target the object to merge into, modified in place
86
+ * @param {object} source the object to copy the properties from
87
+ * @return {object} the `target` object
88
+ */
89
+ export function deepMerge(target, source) {
90
+ return merge(target, source, false);
91
+ }
92
+
93
+ /**
94
+ * Recursively merges partial objects into `target` in order and returns
95
+ * `target`, so that a later source overrides an earlier one.
96
+ *
97
+ * Values that are `null` or `undefined` are skipped, so a source that only
98
+ * provides some of the properties does not remove the others. For the same
99
+ * reason, a property that the target already has is not replaced with an
100
+ * object when the source has one for the same key. Arrays are copied one level
101
+ * deep, so that the result does not share an array with any of the sources.
102
+ *
103
+ * Sources that are not plain objects are ignored. When `target` is not a plain
104
+ * object, it is returned without changes.
105
+ *
106
+ * Keys that would modify `Object.prototype`, such as `__proto__`, are ignored.
107
+ *
108
+ * @param {object} target the object to merge into, modified in place
109
+ * @param {...object} sources the objects to copy the properties from
110
+ * @return {object} the `target` object
111
+ */
112
+ export function deepMergePartials(target, ...sources) {
113
+ sources.forEach((source) => merge(target, source, true));
114
+
115
+ return target;
116
+ }
@@ -139,11 +139,15 @@ export class IronListAdapter {
139
139
  }
140
140
 
141
141
  update(startIndex = 0, endIndex = this.size - 1) {
142
+ const updatedElements = [];
142
143
  this.__getVisibleElements().forEach((el) => {
143
144
  if (el.__virtualIndex >= startIndex && el.__virtualIndex <= endIndex) {
144
145
  this.__updateElement(el, el.__virtualIndex, true);
146
+ updatedElements.push(el);
145
147
  }
146
148
  });
149
+
150
+ this.__afterElementsUpdated(updatedElements);
147
151
  }
148
152
 
149
153
  /**
@@ -206,28 +210,40 @@ export class IronListAdapter {
206
210
  this.updateElement(el, index);
207
211
  el.__lastUpdatedIndex = index;
208
212
  }
213
+ }
209
214
 
210
- const elementHeight = el.offsetHeight;
211
- if (elementHeight === 0) {
212
- // If the elements have 0 height after update (for example due to lazy rendering),
213
- // it results in iron-list requesting to create an unlimited count of elements.
214
- // Assign a temporary placeholder sizing to elements that would otherwise end up having
215
- // no height.
216
- el.style.paddingTop = `${this.__placeholderHeight}px`;
217
-
218
- // Manually schedule the resize handler to make sure the placeholder padding is
219
- // cleared in case the resize observer never triggers.
220
- requestAnimationFrame(() => this._resizeHandler());
221
- } else {
222
- // Add element height to the queue
223
- this.__elementHeightQueue.push(elementHeight);
224
- this.__elementHeightQueue.shift();
225
-
226
- // Calcualte new placeholder height based on the average of the defined values in the
227
- // element height queue
228
- const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
229
- this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
230
- }
215
+ /**
216
+ * Called synchronously right after elements have been updated.
217
+ * This is a good place to do any post-update work.
218
+ *
219
+ * @param {!Array<!HTMLElement>} updatedElements
220
+ */
221
+ __afterElementsUpdated(updatedElements) {
222
+ updatedElements.forEach((el) => {
223
+ const elementHeight = el.offsetHeight;
224
+ if (elementHeight === 0) {
225
+ // If the elements have 0 height after update (for example due to lazy rendering),
226
+ // it results in iron-list requesting to create an unlimited count of elements.
227
+ // Assign a temporary placeholder sizing to elements that would otherwise end up having
228
+ // no height.
229
+ el.style.paddingTop = `${this.__placeholderHeight}px`;
230
+
231
+ // Manually schedule the resize handler to make sure the placeholder padding is
232
+ // cleared in case the resize observer never triggers.
233
+ this.__placeholderClearDebouncer = Debouncer.debounce(this.__placeholderClearDebouncer, animationFrame, () =>
234
+ this._resizeHandler(),
235
+ );
236
+ } else {
237
+ // Add element height to the queue
238
+ this.__elementHeightQueue.push(elementHeight);
239
+ this.__elementHeightQueue.shift();
240
+
241
+ // Calculate new placeholder height based on the average of the defined values in the
242
+ // element height queue
243
+ const filteredHeights = this.__elementHeightQueue.filter((h) => h !== undefined);
244
+ this.__placeholderHeight = Math.round(filteredHeights.reduce((a, b) => a + b, 0) / filteredHeights.length);
245
+ }
246
+ });
231
247
  }
232
248
 
233
249
  __getIndexScrollOffset(index) {
@@ -352,16 +368,20 @@ export class IronListAdapter {
352
368
 
353
369
  /** @private */
354
370
  _assignModels(itemSet) {
371
+ const updatedElements = [];
355
372
  this._iterateItems((pidx, vidx) => {
356
373
  const el = this._physicalItems[pidx];
357
374
  el.hidden = vidx >= this.size;
358
375
  if (!el.hidden) {
359
376
  el.__virtualIndex = vidx + (this._vidxOffset || 0);
360
377
  this.__updateElement(el, el.__virtualIndex);
378
+ updatedElements.push(el);
361
379
  } else {
362
380
  delete el.__lastUpdatedIndex;
363
381
  }
364
382
  }, itemSet);
383
+
384
+ this.__afterElementsUpdated(updatedElements);
365
385
  }
366
386
 
367
387
  /** @private */