@vaadin/component-base 24.0.0-alpha5 → 24.0.0-alpha6

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.0.0-alpha5",
3
+ "version": "24.0.0-alpha6",
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": "fc0b1721eda9e39cb289b239e440fc9e29573a31"
45
+ "gitHead": "0004ac92b6e5f415b5fa949e0582d1d11e527b1f"
46
46
  }
@@ -39,7 +39,7 @@ const registered = new Set();
39
39
  export const ElementMixin = (superClass) =>
40
40
  class VaadinElementMixin extends DirMixin(superClass) {
41
41
  static get version() {
42
- return '24.0.0-alpha5';
42
+ return '24.0.0-alpha6';
43
43
  }
44
44
 
45
45
  /** @protected */
@@ -54,10 +54,19 @@ export class SlotController extends EventTarget implements ReactiveController {
54
54
  */
55
55
  getSlotChild(): Node;
56
56
 
57
+ /**
58
+ * Create and attach default node using the provided tag name, if any.
59
+ */
57
60
  protected attachDefaultNode(): Node | undefined;
58
61
 
62
+ /**
63
+ * Run both `initCustomNode` and `initNode` for a custom slotted node.
64
+ */
59
65
  protected initAddedNode(node: Node): void;
60
66
 
67
+ /**
68
+ * Run `slotInitializer` for the node managed by the controller.
69
+ */
61
70
  protected initNode(node: Node): void;
62
71
 
63
72
  /**
@@ -92,7 +92,7 @@ export class SlotController extends EventTarget {
92
92
  }
93
93
 
94
94
  /**
95
- * Create and attach default node using the slot factory.
95
+ * Create and attach default node using the provided tag name, if any.
96
96
  * @return {Node | undefined}
97
97
  * @protected
98
98
  */
@@ -102,7 +102,7 @@ export class SlotController extends EventTarget {
102
102
  // Check if the node was created previously and if so, reuse it.
103
103
  let node = this.defaultNode;
104
104
 
105
- // Slot factory is optional, some slots don't have default content.
105
+ // Tag name is optional, sometimes we don't init default content.
106
106
  if (!node && tagName) {
107
107
  node = document.createElement(tagName);
108
108
  if (node instanceof Element) {
@@ -145,6 +145,8 @@ export class SlotController extends EventTarget {
145
145
  }
146
146
 
147
147
  /**
148
+ * Run `slotInitializer` for the node managed by the controller.
149
+ *
148
150
  * @param {Node} node
149
151
  * @protected
150
152
  */
@@ -173,7 +175,12 @@ export class SlotController extends EventTarget {
173
175
  */
174
176
  teardownNode(_node) {}
175
177
 
176
- /** @protected */
178
+ /**
179
+ * Run both `initCustomNode` and `initNode` for a custom slotted node.
180
+ *
181
+ * @param {Node} node
182
+ * @protected
183
+ */
177
184
  initAddedNode(node) {
178
185
  if (node !== this.defaultNode) {
179
186
  this.initCustomNode(node);
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2022 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { SlotController } from './slot-controller.js';
7
+
8
+ /**
9
+ * A controller that observes slotted element mutations, especially ID attribute
10
+ * and the text content, and fires an event to notify host element about those.
11
+ */
12
+ export class SlotObserveController extends SlotController {
13
+ /**
14
+ * Setup the mutation observer on the node to update ID and notify host.
15
+ * Node doesn't get observed automatically until this method is called.
16
+ */
17
+ protected observeNode(node: Node): void;
18
+
19
+ /**
20
+ * Override to restore default node when a custom one is removed.
21
+ */
22
+ protected restoreDefaultNode(): void;
23
+
24
+ /**
25
+ * Override to update default node text on property change.
26
+ */
27
+ protected updateDefaultNode(node: Node): void;
28
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2022 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { SlotController } from './slot-controller.js';
7
+
8
+ /**
9
+ * A controller that observes slotted element mutations, especially ID attribute
10
+ * and the text content, and fires an event to notify host element about those.
11
+ */
12
+ export class SlotObserveController extends SlotController {
13
+ constructor(host, slot, tagName, config = {}) {
14
+ super(host, slot, tagName, { ...config, useUniqueId: true });
15
+ }
16
+
17
+ /**
18
+ * Override to initialize the newly added custom node.
19
+ *
20
+ * @param {Node} node
21
+ * @protected
22
+ * @override
23
+ */
24
+ initCustomNode(node) {
25
+ this.__updateNodeId(node);
26
+ this.__notifyChange(node);
27
+ }
28
+
29
+ /**
30
+ * Override to notify the controller host about removal of
31
+ * the custom node, and to apply the default one if needed.
32
+ *
33
+ * @param {Node} _node
34
+ * @protected
35
+ * @override
36
+ */
37
+ teardownNode(_node) {
38
+ const node = this.getSlotChild();
39
+
40
+ // Custom node is added to the slot
41
+ if (node && node !== this.defaultNode) {
42
+ this.__notifyChange(node);
43
+ } else {
44
+ this.restoreDefaultNode();
45
+ this.updateDefaultNode(this.node);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Override method inherited from `SlotMixin`
51
+ * to set ID attribute on the default node.
52
+ *
53
+ * @return {Node}
54
+ * @protected
55
+ * @override
56
+ */
57
+ attachDefaultNode() {
58
+ const node = super.attachDefaultNode();
59
+
60
+ if (node) {
61
+ this.__updateNodeId(node);
62
+ }
63
+
64
+ return node;
65
+ }
66
+
67
+ /**
68
+ * Override to restore default node when a custom one is removed.
69
+ *
70
+ * @protected
71
+ */
72
+ restoreDefaultNode() {
73
+ // To be implemented
74
+ }
75
+
76
+ /**
77
+ * Override to update default node text on property change.
78
+ *
79
+ * @param {Node} node
80
+ * @protected
81
+ */
82
+ updateDefaultNode(node) {
83
+ this.__notifyChange(node);
84
+ }
85
+
86
+ /**
87
+ * Setup the mutation observer on the node to update ID and notify host.
88
+ * Node doesn't get observed automatically until this method is called.
89
+ *
90
+ * @param {Node} node
91
+ * @protected
92
+ */
93
+ observeNode(node) {
94
+ // Stop observing the previous node, if any.
95
+ if (this.__nodeObserver) {
96
+ this.__nodeObserver.disconnect();
97
+ }
98
+
99
+ this.__nodeObserver = new MutationObserver((mutations) => {
100
+ mutations.forEach((mutation) => {
101
+ const target = mutation.target;
102
+
103
+ // Ensure the mutation target is the currently connected node
104
+ // to ignore async mutations dispatched for removed element.
105
+ const isCurrentNodeMutation = target === this.node;
106
+
107
+ if (mutation.type === 'attributes') {
108
+ // We use attributeFilter to only observe ID mutation,
109
+ // no need to check for attribute name separately.
110
+ if (isCurrentNodeMutation && target.id !== this.defaultId) {
111
+ this.__updateNodeId(target);
112
+ }
113
+ } else if (isCurrentNodeMutation || target.parentElement === this.node) {
114
+ // Node text content has changed.
115
+ this.__notifyChange(this.node);
116
+ }
117
+ });
118
+ });
119
+
120
+ // Observe changes to node ID attribute, text content and children.
121
+ this.__nodeObserver.observe(node, {
122
+ attributes: true,
123
+ attributeFilter: ['id'],
124
+ childList: true,
125
+ subtree: true,
126
+ characterData: true,
127
+ });
128
+ }
129
+
130
+ /**
131
+ * Returns true if a node is an HTML element with children,
132
+ * or is a defined custom element, or has non-empty text.
133
+ *
134
+ * @param {Node} node
135
+ * @return {boolean}
136
+ * @private
137
+ */
138
+ __hasContent(node) {
139
+ if (!node) {
140
+ return false;
141
+ }
142
+
143
+ return (
144
+ node.children.length > 0 ||
145
+ (node.nodeType === Node.ELEMENT_NODE && customElements.get(node.localName)) ||
146
+ (node.textContent && node.textContent.trim() !== '')
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Fire an event to notify the controller host about node changes.
152
+ *
153
+ * @param {Node} node
154
+ * @private
155
+ */
156
+ __notifyChange(node) {
157
+ this.dispatchEvent(
158
+ new CustomEvent('slot-content-changed', {
159
+ detail: { hasContent: this.__hasContent(node), node },
160
+ }),
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Set default ID on the node in case it is an HTML element.
166
+ *
167
+ * @param {Node} node
168
+ * @private
169
+ */
170
+ __updateNodeId(node) {
171
+ if (node.nodeType === Node.ELEMENT_NODE && !node.id) {
172
+ node.id = this.defaultId;
173
+ }
174
+ }
175
+ }