@vaadin/component-base 24.0.0-alpha2 → 24.0.0-alpha4

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/index.d.ts CHANGED
@@ -8,5 +8,4 @@ export { FocusTrapController } from './src/focus-trap-controller.js';
8
8
  export { KeyboardMixin } from './src/keyboard-mixin.js';
9
9
  export { ResizeMixin } from './src/resize-mixin.js';
10
10
  export { SlotController } from './src/slot-controller.js';
11
- export { SlotMixin } from './src/slot-mixin.js';
12
11
  export { TabindexMixin } from './src/tabindex-mixin.js';
package/index.js CHANGED
@@ -7,5 +7,4 @@ export { FocusMixin } from './src/focus-mixin.js';
7
7
  export { FocusTrapController } from './src/focus-trap-controller.js';
8
8
  export { KeyboardMixin } from './src/keyboard-mixin.js';
9
9
  export { SlotController } from './src/slot-controller.js';
10
- export { SlotMixin } from './src/slot-mixin.js';
11
10
  export { TabindexMixin } from './src/tabindex-mixin.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaadin/component-base",
3
- "version": "24.0.0-alpha2",
3
+ "version": "24.0.0-alpha4",
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": "0c16c01a6807e629a84f5a982793afecc1a7ced0"
45
+ "gitHead": "66be46e82c4d0a673859fbc9bdb1581dd89f360c"
46
46
  }
@@ -11,6 +11,8 @@ import type { Constructor } from '@open-wc/dedupe-mixin';
11
11
  export declare function DirMixin<T extends Constructor<HTMLElement>>(base: T): Constructor<DirMixinClass> & T;
12
12
 
13
13
  export declare class DirMixinClass {
14
+ protected readonly __isRTL: boolean;
15
+
14
16
  protected __getNormalizedScrollLeft(element: Element | null): number;
15
17
 
16
18
  protected __setNormalizedScrollLeft(element: Element | null, scrollLeft: number): void;
package/src/dir-mixin.js CHANGED
@@ -112,6 +112,14 @@ export const DirMixin = (superClass) =>
112
112
  this.__unsubscribe();
113
113
  }
114
114
 
115
+ /**
116
+ * @return {boolean}
117
+ * @protected
118
+ */
119
+ get __isRTL() {
120
+ return this.getAttribute('dir') === 'rtl';
121
+ }
122
+
115
123
  /** @protected */
116
124
  _valueToNodeAttribute(node, value, attribute) {
117
125
  // Override default Polymer attribute reflection to match native behavior of HTMLElement.dir property
@@ -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-alpha2';
42
+ return '24.0.0-alpha4';
43
43
  }
44
44
 
45
45
  /** @protected */
@@ -16,8 +16,16 @@ export class SlotController extends EventTarget implements ReactiveController {
16
16
  */
17
17
  node: HTMLElement;
18
18
 
19
+ /**
20
+ * The list of slotted nodes managed by the controller.
21
+ * Only used when `multiple` property is set to `true`.
22
+ */
23
+ nodes: HTMLElement[];
24
+
19
25
  protected initialized: boolean;
20
26
 
27
+ protected multiple: boolean;
28
+
21
29
  protected defaultNode: Node;
22
30
 
23
31
  protected defaultId: string;
@@ -25,13 +33,22 @@ export class SlotController extends EventTarget implements ReactiveController {
25
33
  constructor(
26
34
  host: HTMLElement,
27
35
  slotName: string,
28
- slotFactory?: () => HTMLElement,
29
- slotInitializer?: (host: HTMLElement, node: HTMLElement) => void,
30
- useUniqueId?: boolean,
36
+ tagName?: string,
37
+ config?: {
38
+ multiple?: boolean;
39
+ observe?: boolean;
40
+ useUniqueId?: boolean;
41
+ initializer?(host: HTMLElement, node: HTMLElement): void;
42
+ },
31
43
  );
32
44
 
33
45
  hostConnected(): void;
34
46
 
47
+ /**
48
+ * Return the list of nodes matching the slot managed by the controller.
49
+ */
50
+ getSlotChildren(): Node[];
51
+
35
52
  /**
36
53
  * Return a reference to the node managed by the controller.
37
54
  */
@@ -39,6 +56,8 @@ export class SlotController extends EventTarget implements ReactiveController {
39
56
 
40
57
  protected attachDefaultNode(): Node | undefined;
41
58
 
59
+ protected initAddedNode(node: Node): void;
60
+
42
61
  protected initNode(node: Node): void;
43
62
 
44
63
  /**
@@ -54,5 +73,5 @@ export class SlotController extends EventTarget implements ReactiveController {
54
73
  /**
55
74
  * Setup the observer to manage slot content changes.
56
75
  */
57
- protected observe(): void;
76
+ protected observeSlot(): void;
58
77
  }
@@ -23,13 +23,21 @@ export class SlotController extends EventTarget {
23
23
  return `${prefix}-${host.localName}-${generateUniqueId()}`;
24
24
  }
25
25
 
26
- constructor(host, slotName, slotFactory, slotInitializer, useUniqueId) {
26
+ constructor(host, slotName, tagName, config = {}) {
27
27
  super();
28
28
 
29
+ const { initializer, multiple, observe, useUniqueId } = config;
30
+
29
31
  this.host = host;
30
32
  this.slotName = slotName;
31
- this.slotFactory = slotFactory;
32
- this.slotInitializer = slotInitializer;
33
+ this.tagName = tagName;
34
+ this.observe = typeof observe === 'boolean' ? observe : true;
35
+ this.multiple = typeof multiple === 'boolean' ? multiple : false;
36
+ this.slotInitializer = initializer;
37
+
38
+ if (multiple) {
39
+ this.nodes = [];
40
+ }
33
41
 
34
42
  // Only generate the default ID if requested by the controller.
35
43
  if (useUniqueId) {
@@ -39,38 +47,63 @@ export class SlotController extends EventTarget {
39
47
 
40
48
  hostConnected() {
41
49
  if (!this.initialized) {
42
- let node = this.getSlotChild();
43
-
44
- if (!node) {
45
- node = this.attachDefaultNode();
50
+ if (this.multiple) {
51
+ this.initMultiple();
46
52
  } else {
47
- this.node = node;
48
- this.initCustomNode(node);
53
+ this.initSingle();
49
54
  }
50
55
 
51
- this.initNode(node);
52
-
53
- // TODO: Consider making this behavior opt-in to improve performance.
54
- this.observe();
56
+ if (this.observe) {
57
+ this.observeSlot();
58
+ }
55
59
 
56
60
  this.initialized = true;
57
61
  }
58
62
  }
59
63
 
64
+ /** @protected */
65
+ initSingle() {
66
+ let node = this.getSlotChild();
67
+
68
+ if (!node) {
69
+ node = this.attachDefaultNode();
70
+ this.initNode(node);
71
+ } else {
72
+ this.node = node;
73
+ this.initAddedNode(node);
74
+ }
75
+ }
76
+
77
+ /** @protected */
78
+ initMultiple() {
79
+ const children = this.getSlotChildren();
80
+
81
+ if (children.length === 0) {
82
+ const defaultNode = this.attachDefaultNode();
83
+ this.nodes = [defaultNode];
84
+ this.initNode(defaultNode);
85
+ } else {
86
+ this.nodes = children;
87
+ children.forEach((node) => {
88
+ this.initAddedNode(node);
89
+ });
90
+ }
91
+ }
92
+
60
93
  /**
61
94
  * Create and attach default node using the slot factory.
62
95
  * @return {Node | undefined}
63
96
  * @protected
64
97
  */
65
98
  attachDefaultNode() {
66
- const { host, slotName, slotFactory } = this;
99
+ const { host, slotName, tagName } = this;
67
100
 
68
101
  // Check if the node was created previously and if so, reuse it.
69
102
  let node = this.defaultNode;
70
103
 
71
104
  // Slot factory is optional, some slots don't have default content.
72
- if (!node && slotFactory) {
73
- node = slotFactory(host);
105
+ if (!node && tagName) {
106
+ node = document.createElement(tagName);
74
107
  if (node instanceof Element) {
75
108
  if (slotName !== '') {
76
109
  node.setAttribute('slot', slotName);
@@ -88,12 +121,12 @@ export class SlotController extends EventTarget {
88
121
  }
89
122
 
90
123
  /**
91
- * Return a reference to the node managed by the controller.
124
+ * Return the list of nodes matching the slot managed by the controller.
92
125
  * @return {Node}
93
126
  */
94
- getSlotChild() {
127
+ getSlotChildren() {
95
128
  const { slotName } = this;
96
- return Array.from(this.host.childNodes).find((node) => {
129
+ return Array.from(this.host.childNodes).filter((node) => {
97
130
  // Either an element (any slot) or a text node (only un-named slot).
98
131
  return (
99
132
  (node.nodeType === Node.ELEMENT_NODE && node.slot === slotName) ||
@@ -102,6 +135,14 @@ export class SlotController extends EventTarget {
102
135
  });
103
136
  }
104
137
 
138
+ /**
139
+ * Return a reference to the node managed by the controller.
140
+ * @return {Node}
141
+ */
142
+ getSlotChild() {
143
+ return this.getSlotChildren()[0];
144
+ }
145
+
105
146
  /**
106
147
  * @param {Node} node
107
148
  * @protected
@@ -111,7 +152,7 @@ export class SlotController extends EventTarget {
111
152
  // Don't try to bind `this` to initializer (normally it's arrow function).
112
153
  // Instead, pass the host as a first argument to access component's state.
113
154
  if (slotInitializer) {
114
- slotInitializer(this.host, node);
155
+ slotInitializer(node, this.host);
115
156
  }
116
157
  }
117
158
 
@@ -131,19 +172,26 @@ export class SlotController extends EventTarget {
131
172
  */
132
173
  teardownNode(_node) {}
133
174
 
175
+ /** @protected */
176
+ initAddedNode(node) {
177
+ if (node !== this.defaultNode) {
178
+ this.initCustomNode(node);
179
+ this.initNode(node);
180
+ }
181
+ }
182
+
134
183
  /**
135
184
  * Setup the observer to manage slot content changes.
136
185
  * @protected
137
186
  */
138
- observe() {
187
+ observeSlot() {
139
188
  const { slotName } = this;
140
189
  const selector = slotName === '' ? 'slot:not([name])' : `slot[name=${slotName}]`;
141
190
  const slot = this.host.shadowRoot.querySelector(selector);
142
191
 
143
192
  this.__slotObserver = new FlattenedNodesObserver(slot, (info) => {
144
- // TODO: support default slot with multiple nodes (e.g. confirm-dialog)
145
- const current = this.node;
146
- const newNode = info.addedNodes.find((node) => node !== current);
193
+ const current = this.multiple ? this.nodes : [this.node];
194
+ const newNodes = info.addedNodes.filter((node) => !current.includes(node));
147
195
 
148
196
  if (info.removedNodes.length) {
149
197
  info.removedNodes.forEach((node) => {
@@ -151,18 +199,22 @@ export class SlotController extends EventTarget {
151
199
  });
152
200
  }
153
201
 
154
- if (newNode) {
202
+ if (newNodes && newNodes.length > 0) {
155
203
  // Custom node is added, remove the current one.
156
- if (current && current.isConnected) {
157
- this.host.removeChild(current);
158
- }
159
-
160
- this.node = newNode;
161
-
162
- if (newNode !== this.defaultNode) {
163
- this.initCustomNode(newNode);
204
+ current.forEach((node) => {
205
+ if (node && node.isConnected) {
206
+ node.parentNode.removeChild(node);
207
+ }
208
+ });
164
209
 
165
- this.initNode(newNode);
210
+ if (this.multiple) {
211
+ this.nodes = newNodes;
212
+ newNodes.forEach((node) => {
213
+ this.initAddedNode(node);
214
+ });
215
+ } else {
216
+ this.node = newNodes[0];
217
+ this.initAddedNode(this.node);
166
218
  }
167
219
  }
168
220
  });
@@ -1,18 +0,0 @@
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
- import type { Constructor } from '@open-wc/dedupe-mixin';
7
-
8
- /**
9
- * A mixin to provide content for named slots defined by component.
10
- */
11
- export declare function SlotMixin<T extends Constructor<HTMLElement>>(base: T): Constructor<SlotMixinClass> & T;
12
-
13
- export declare class SlotMixinClass {
14
- /**
15
- * List of named slots to initialize.
16
- */
17
- protected readonly slots: Record<string, () => HTMLElement>;
18
- }
package/src/slot-mixin.js DELETED
@@ -1,60 +0,0 @@
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
- import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
7
-
8
- /**
9
- * A mixin to provide content for named slots defined by component.
10
- *
11
- * @polymerMixin
12
- */
13
- export const SlotMixin = dedupingMixin(
14
- (superclass) =>
15
- class SlotMixinClass extends superclass {
16
- /**
17
- * List of named slots to initialize.
18
- * @protected
19
- */
20
- get slots() {
21
- return {};
22
- }
23
-
24
- /** @protected */
25
- ready() {
26
- super.ready();
27
- this._connectSlotMixin();
28
- }
29
-
30
- /** @private */
31
- _connectSlotMixin() {
32
- Object.keys(this.slots).forEach((slotName) => {
33
- // Ignore labels of nested components, if any
34
- const hasContent = this._getDirectSlotChild(slotName) !== undefined;
35
-
36
- if (!hasContent) {
37
- const slotFactory = this.slots[slotName];
38
- const slotContent = slotFactory();
39
- if (slotContent instanceof Element) {
40
- if (slotName !== '') {
41
- slotContent.setAttribute('slot', slotName);
42
- }
43
- this.appendChild(slotContent);
44
- }
45
- }
46
- });
47
- }
48
-
49
- /** @protected */
50
- _getDirectSlotChild(slotName) {
51
- return Array.from(this.childNodes).find((node) => {
52
- // Either an element (any slot) or a text node (only un-named slot).
53
- return (
54
- (node.nodeType === Node.ELEMENT_NODE && node.slot === slotName) ||
55
- (node.nodeType === Node.TEXT_NODE && node.textContent.trim() && slotName === '')
56
- );
57
- });
58
- }
59
- },
60
- );