@vaadin/component-base 24.2.0-alpha5 → 24.2.0-alpha7

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.2.0-alpha5",
3
+ "version": "24.2.0-alpha7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -42,5 +42,5 @@
42
42
  "@vaadin/testing-helpers": "^0.4.3",
43
43
  "sinon": "^13.0.2"
44
44
  },
45
- "gitHead": "73db22a5e8993e3ce48d1e6540d30eff9cb5c257"
45
+ "gitHead": "81ecf12d2d59a3e5b467273c37a391e31932dc9c"
46
46
  }
@@ -13,6 +13,13 @@
13
13
  */
14
14
  export function getAncestorRootNodes(node: Node): Node[];
15
15
 
16
+ /**
17
+ * Returns the list of flattened elements for the given `node`.
18
+ * This list consists of a node's children and, for any children that are
19
+ * `<slot>` elements, the expanded flattened list of `assignedElements`.
20
+ */
21
+ export function getFlattenedElements(node: Node): Element[];
22
+
16
23
  /**
17
24
  * Traverses the given node and its parents, including those that are across
18
25
  * the shadow root boundaries, until it finds a node that matches the selector.
package/src/dom-utils.js CHANGED
@@ -40,6 +40,27 @@ export function getAncestorRootNodes(node) {
40
40
  return result;
41
41
  }
42
42
 
43
+ /**
44
+ * Returns the list of flattened elements for the given `node`.
45
+ * This list consists of a node's children and, for any children that are
46
+ * `<slot>` elements, the expanded flattened list of `assignedElements`.
47
+ *
48
+ * @param {Node} node
49
+ * @return {Element[]}
50
+ */
51
+ export function getFlattenedElements(node) {
52
+ const result = [];
53
+ let elements;
54
+ if (node.localName === 'slot') {
55
+ elements = node.assignedElements();
56
+ } else {
57
+ result.push(node);
58
+ elements = [...node.children];
59
+ }
60
+ elements.forEach((elem) => result.push(...getFlattenedElements(elem)));
61
+ return result;
62
+ }
63
+
43
64
  /**
44
65
  * Traverses the given node and its parents, including those that are across
45
66
  * the shadow root boundaries, until it finds a node that matches the selector.
@@ -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.2.0-alpha5';
48
+ return '24.2.0-alpha7';
49
49
  }
50
50
 
51
51
  /** @protected */
@@ -3,7 +3,6 @@
3
3
  * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
7
6
  import { animationFrame } from './async.js';
8
7
  import { Debouncer } from './debounce.js';
9
8
 
@@ -47,30 +46,41 @@ export class OverflowController {
47
46
  * @protected
48
47
  */
49
48
  observe() {
50
- this.__resizeObserver = new ResizeObserver(() => {
49
+ const { host } = this;
50
+
51
+ this.__resizeObserver = new ResizeObserver((entries) => {
51
52
  this.__debounceOverflow = Debouncer.debounce(this.__debounceOverflow, animationFrame, () => {
52
53
  this.__updateOverflow();
53
54
  });
54
55
  });
55
56
 
56
- this.__resizeObserver.observe(this.host);
57
+ this.__resizeObserver.observe(host);
57
58
 
58
- this.__childObserver = new FlattenedNodesObserver(this.host, (info) => {
59
- info.addedNodes.forEach((node) => {
60
- if (node.nodeType === Node.ELEMENT_NODE) {
61
- this.__resizeObserver.observe(node);
62
- }
63
- });
59
+ // Observe initial children
60
+ [...host.children].forEach((child) => {
61
+ this.__resizeObserver.observe(child);
62
+ });
64
63
 
65
- info.removedNodes.forEach((node) => {
66
- if (node.nodeType === Node.ELEMENT_NODE) {
67
- this.__resizeObserver.unobserve(node);
68
- }
64
+ this.__childObserver = new MutationObserver((mutations) => {
65
+ mutations.forEach(({ addedNodes, removedNodes }) => {
66
+ addedNodes.forEach((node) => {
67
+ if (node.nodeType === Node.ELEMENT_NODE) {
68
+ this.__resizeObserver.observe(node);
69
+ }
70
+ });
71
+
72
+ removedNodes.forEach((node) => {
73
+ if (node.nodeType === Node.ELEMENT_NODE) {
74
+ this.__resizeObserver.unobserve(node);
75
+ }
76
+ });
69
77
  });
70
78
 
71
79
  this.__updateOverflow();
72
80
  });
73
81
 
82
+ this.__childObserver.observe(host, { childList: true });
83
+
74
84
  // Update overflow attribute on scroll
75
85
  this.scrollTarget.addEventListener('scroll', this.__boundOnScroll);
76
86
 
@@ -103,6 +103,24 @@ const PolylitMixinImplementation = (superclass) => {
103
103
  });
104
104
  }
105
105
 
106
+ if (options.sync) {
107
+ result = {
108
+ get: defaultDescriptor.get,
109
+ set(value) {
110
+ const oldValue = this[name];
111
+ this[key] = value;
112
+ this.requestUpdate(name, oldValue, options);
113
+
114
+ // Enforce synchronous update
115
+ if (this.hasUpdated) {
116
+ this.performUpdate();
117
+ }
118
+ },
119
+ configurable: true,
120
+ enumerable: true,
121
+ };
122
+ }
123
+
106
124
  if (options.readOnly) {
107
125
  const setter = defaultDescriptor.set;
108
126
 
@@ -175,7 +193,7 @@ const PolylitMixinImplementation = (superclass) => {
175
193
  this.$ = {};
176
194
  }
177
195
 
178
- this.shadowRoot.querySelectorAll('[id]').forEach((node) => {
196
+ this.renderRoot.querySelectorAll('[id]').forEach((node) => {
179
197
  this.$[node.id] = node;
180
198
  });
181
199
  }
@@ -202,8 +220,8 @@ const PolylitMixinImplementation = (superclass) => {
202
220
  }
203
221
 
204
222
  if (!this.__isReadyInvoked) {
205
- this.ready();
206
223
  this.__isReadyInvoked = true;
224
+ this.ready();
207
225
  }
208
226
  }
209
227
 
@@ -3,8 +3,8 @@
3
3
  * Copyright (c) 2021 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js';
7
6
  import { isEmptyTextNode } from './dom-utils.js';
7
+ import { SlotObserver } from './slot-observer.js';
8
8
  import { generateUniqueId } from './unique-id-utils.js';
9
9
 
10
10
  /**
@@ -199,17 +199,17 @@ export class SlotController extends EventTarget {
199
199
  const selector = slotName === '' ? 'slot:not([name])' : `slot[name=${slotName}]`;
200
200
  const slot = this.host.shadowRoot.querySelector(selector);
201
201
 
202
- this.__slotObserver = new FlattenedNodesObserver(slot, (info) => {
202
+ this.__slotObserver = new SlotObserver(slot, ({ addedNodes, removedNodes }) => {
203
203
  const current = this.multiple ? this.nodes : [this.node];
204
204
 
205
205
  // Calling `slot.assignedNodes()` includes whitespace text nodes in case of default slot:
206
206
  // unlike comment nodes, they are not filtered out. So we need to manually ignore them.
207
- const newNodes = info.addedNodes.filter((node) => !isEmptyTextNode(node) && !current.includes(node));
207
+ const newNodes = addedNodes.filter((node) => !isEmptyTextNode(node) && !current.includes(node));
208
208
 
209
- if (info.removedNodes.length) {
210
- this.nodes = current.filter((node) => !info.removedNodes.includes(node));
209
+ if (removedNodes.length) {
210
+ this.nodes = current.filter((node) => !removedNodes.includes(node));
211
211
 
212
- info.removedNodes.forEach((node) => {
212
+ removedNodes.forEach((node) => {
213
213
  this.teardownNode(node);
214
214
  });
215
215
  }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+
7
+ /**
8
+ * A helper for observing slot changes.
9
+ */
10
+ export class SlotObserver {
11
+ constructor(
12
+ slot: HTMLSlotElement,
13
+ callback: (info: { addedNodes: Node[]; movedNodes: Node[]; removedNodes: Node[] }) => void,
14
+ );
15
+
16
+ /**
17
+ * Activates an observer. This method is automatically called when
18
+ * a `SlotObserver` is created. It should only be called to re-activate
19
+ * an observer that has been deactivated via the `disconnect` method.
20
+ */
21
+ connect(): void;
22
+
23
+ /**
24
+ * Deactivates the observer. After calling this method the observer callback
25
+ * will not be called when changes to slotted nodes occur. The `connect` method
26
+ * may be subsequently called to reactivate the observer.
27
+ */
28
+ disconnect(): void;
29
+
30
+ /**
31
+ * Run the observer callback synchronously.
32
+ */
33
+ flush(): void;
34
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2023 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+
7
+ /**
8
+ * A helper for observing slot changes.
9
+ */
10
+ export class SlotObserver {
11
+ constructor(slot, callback) {
12
+ /** @type HTMLSlotElement */
13
+ this.slot = slot;
14
+
15
+ /** @type Function */
16
+ this.callback = callback;
17
+
18
+ /** @type {Node[]} */
19
+ this._storedNodes = [];
20
+
21
+ this._connected = false;
22
+ this._scheduled = false;
23
+
24
+ this._boundSchedule = () => {
25
+ this._schedule();
26
+ };
27
+
28
+ this.connect();
29
+ this._schedule();
30
+ }
31
+
32
+ /**
33
+ * Activates an observer. This method is automatically called when
34
+ * a `SlotObserver` is created. It should only be called to re-activate
35
+ * an observer that has been deactivated via the `disconnect` method.
36
+ */
37
+ connect() {
38
+ this.slot.addEventListener('slotchange', this._boundSchedule);
39
+ this._connected = true;
40
+ }
41
+
42
+ /**
43
+ * Deactivates the observer. After calling this method the observer callback
44
+ * will not be called when changes to slotted nodes occur. The `connect` method
45
+ * may be subsequently called to reactivate the observer.
46
+ */
47
+ disconnect() {
48
+ this.slot.removeEventListener('slotchange', this._boundSchedule);
49
+ this._connected = false;
50
+ }
51
+
52
+ /** @private */
53
+ _schedule() {
54
+ if (!this._scheduled) {
55
+ this._scheduled = true;
56
+
57
+ queueMicrotask(() => {
58
+ this.flush();
59
+ });
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Run the observer callback synchronously.
65
+ */
66
+ flush() {
67
+ if (!this._connected) {
68
+ return;
69
+ }
70
+
71
+ this._scheduled = false;
72
+
73
+ this._processNodes();
74
+ }
75
+
76
+ /** @private */
77
+ _processNodes() {
78
+ const currentNodes = this.slot.assignedNodes({ flatten: true });
79
+
80
+ let addedNodes = [];
81
+ const removedNodes = [];
82
+ const movedNodes = [];
83
+
84
+ if (currentNodes.length) {
85
+ addedNodes = currentNodes.filter((node) => !this._storedNodes.includes(node));
86
+ }
87
+
88
+ if (this._storedNodes.length) {
89
+ this._storedNodes.forEach((node, index) => {
90
+ const idx = currentNodes.indexOf(node);
91
+ if (idx === -1) {
92
+ removedNodes.push(node);
93
+ } else if (idx !== index) {
94
+ movedNodes.push(node);
95
+ }
96
+ });
97
+ }
98
+
99
+ if (addedNodes.length || removedNodes.length || movedNodes.length) {
100
+ this.callback({ addedNodes, movedNodes, removedNodes });
101
+ }
102
+
103
+ this._storedNodes = currentNodes;
104
+ }
105
+ }