@vaadin/card 25.3.0-alpha2 → 25.3.0-alpha3

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/card",
3
- "version": "25.3.0-alpha2",
3
+ "version": "25.3.0-alpha3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -34,20 +34,20 @@
34
34
  "web-component"
35
35
  ],
36
36
  "dependencies": {
37
- "@vaadin/component-base": "25.3.0-alpha2",
38
- "@vaadin/vaadin-themable-mixin": "25.3.0-alpha2",
37
+ "@vaadin/component-base": "25.3.0-alpha3",
38
+ "@vaadin/vaadin-themable-mixin": "25.3.0-alpha3",
39
39
  "lit": "^3.0.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@vaadin/aura": "25.3.0-alpha2",
43
- "@vaadin/avatar": "25.3.0-alpha2",
44
- "@vaadin/button": "25.3.0-alpha2",
45
- "@vaadin/chai-plugins": "25.3.0-alpha2",
46
- "@vaadin/icon": "25.3.0-alpha2",
47
- "@vaadin/icons": "25.3.0-alpha2",
48
- "@vaadin/test-runner-commands": "25.3.0-alpha2",
42
+ "@vaadin/aura": "25.3.0-alpha3",
43
+ "@vaadin/avatar": "25.3.0-alpha3",
44
+ "@vaadin/button": "25.3.0-alpha3",
45
+ "@vaadin/chai-plugins": "25.3.0-alpha3",
46
+ "@vaadin/icon": "25.3.0-alpha3",
47
+ "@vaadin/icons": "25.3.0-alpha3",
48
+ "@vaadin/test-runner-commands": "25.3.0-alpha3",
49
49
  "@vaadin/testing-helpers": "^2.0.0",
50
- "@vaadin/vaadin-lumo-styles": "25.3.0-alpha2",
50
+ "@vaadin/vaadin-lumo-styles": "25.3.0-alpha3",
51
51
  "sinon": "^22.0.0"
52
52
  },
53
53
  "customElements": "custom-elements.json",
@@ -55,5 +55,5 @@
55
55
  "web-types.json",
56
56
  "web-types.lit.json"
57
57
  ],
58
- "gitHead": "52eba6153ecf8bfaae44bcb727595e77d90b963c"
58
+ "gitHead": "ba0c4c55ea219eadd9aefe244f53e87803a066c8"
59
59
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2024 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { SlotChildObserveController } from '@vaadin/component-base/src/slot-child-observe-controller.js';
7
+
8
+ /**
9
+ * A controller to manage the card title element.
10
+ */
11
+ export class TitleController extends SlotChildObserveController {
12
+ constructor(host: HTMLElement);
13
+
14
+ /**
15
+ * Set title based on the corresponding host property.
16
+ */
17
+ setTitle(title: string): void;
18
+
19
+ /**
20
+ * Set heading level based on the corresponding host property.
21
+ */
22
+ setLevel(level: number): void;
23
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2024 - 2026 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { SlotChildObserveController } from '@vaadin/component-base/src/slot-child-observe-controller.js';
7
+
8
+ /**
9
+ * A controller to manage the card title element.
10
+ */
11
+ export class TitleController extends SlotChildObserveController {
12
+ constructor(host) {
13
+ // Do not provide tag name, as we create the title lazily.
14
+ super(host, 'title', null, { uniqueIdPrefix: 'card-title' });
15
+ }
16
+
17
+ /**
18
+ * Set title based on the corresponding host property.
19
+ *
20
+ * @param {string} title
21
+ */
22
+ setTitle(title) {
23
+ this.title = title;
24
+
25
+ if (title && title.trim() !== '') {
26
+ // The string title replaces a custom slotted title, if any.
27
+ const child = this.getSlotChild();
28
+ if (child && child !== this.defaultNode) {
29
+ child.remove();
30
+ }
31
+
32
+ // Create or restore the default title node.
33
+ this.restoreDefaultNode();
34
+ this.updateDefaultNode(this.node);
35
+ } else if (this.node && this.node === this.defaultNode) {
36
+ // Clearing the string title removes the generated node.
37
+ this.node.remove();
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Set heading level based on the corresponding host property.
43
+ *
44
+ * @param {number} level
45
+ */
46
+ setLevel(level) {
47
+ this.level = level;
48
+
49
+ // When the default title is used, update it.
50
+ if (this.node && this.node === this.defaultNode) {
51
+ this.updateDefaultNode(this.node);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Override method inherited from `SlotController` to set up the default
57
+ * title node as a heading and label the card by it. The heading role and
58
+ * `aria-labelledby` are only applied to the generated string title, never
59
+ * to a custom slotted title.
60
+ *
61
+ * @param {Node} node
62
+ * @protected
63
+ * @override
64
+ */
65
+ initNode(node) {
66
+ if (node && node === this.defaultNode) {
67
+ node.setAttribute('role', 'heading');
68
+ this.host.setAttribute('aria-labelledby', node.id);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Override method inherited from `SlotChildObserveController` to handle a
74
+ * custom title element. Skips the default `super` call so the custom node
75
+ * keeps no generated ID. Clears the string title property, as a custom
76
+ * title takes over.
77
+ *
78
+ * @param {Node} _node
79
+ * @protected
80
+ * @override
81
+ */
82
+ initCustomNode(_node) {
83
+ this.host.cardTitle = '';
84
+ }
85
+
86
+ /**
87
+ * Override method inherited from `SlotChildObserveController` to drop the
88
+ * labelling once the generated title node leaves the slot and is not
89
+ * replaced by a custom title.
90
+ *
91
+ * @param {Node} node
92
+ * @protected
93
+ * @override
94
+ */
95
+ teardownNode(node) {
96
+ if (this.getSlotChild() !== this.defaultNode) {
97
+ this.host.removeAttribute('aria-labelledby');
98
+ }
99
+
100
+ super.teardownNode(node);
101
+ }
102
+
103
+ /**
104
+ * Override method inherited from `SlotChildObserveController`
105
+ * to create the default title element lazily as needed.
106
+ *
107
+ * @protected
108
+ * @override
109
+ */
110
+ restoreDefaultNode() {
111
+ const { title } = this;
112
+
113
+ // No title yet, create one.
114
+ if (title && title.trim() !== '') {
115
+ this.tagName = 'div';
116
+
117
+ const node = this.attachDefaultNode();
118
+ this.initNode(node);
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Override method inherited from `SlotChildObserveController`
124
+ * to update the default title element.
125
+ *
126
+ * @param {Node | undefined} node
127
+ * @protected
128
+ * @override
129
+ */
130
+ updateDefaultNode(node) {
131
+ if (node) {
132
+ node.textContent = this.title;
133
+ node.setAttribute('aria-level', this.level || 2);
134
+ }
135
+
136
+ // Notify the host after update.
137
+ super.updateDefaultNode(node);
138
+ }
139
+ }
@@ -8,10 +8,10 @@ import { defineCustomElement } from '@vaadin/component-base/src/define.js';
8
8
  import { isEmptyTextNode } from '@vaadin/component-base/src/dom-utils.js';
9
9
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
10
10
  import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
11
- import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
12
11
  import { LumoInjectionMixin } from '@vaadin/vaadin-themable-mixin/lumo-injection-mixin.js';
13
12
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
14
13
  import { cardStyles } from './styles/vaadin-card-base-styles.js';
14
+ import { TitleController } from './title-controller.js';
15
15
 
16
16
  /**
17
17
  * `<vaadin-card>` is a versatile container for grouping related content and actions.
@@ -86,7 +86,6 @@ class Card extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(Li
86
86
  */
87
87
  cardTitle: {
88
88
  type: String,
89
- observer: '__cardTitleChanged',
90
89
  },
91
90
 
92
91
  /**
@@ -97,7 +96,6 @@ class Card extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(Li
97
96
  titleHeadingLevel: {
98
97
  type: Number,
99
98
  reflectToAttribute: true,
100
- observer: '__titleHeadingLevelChanged',
101
99
  },
102
100
  };
103
101
  }
@@ -139,9 +137,26 @@ class Card extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(Li
139
137
  /** @protected */
140
138
  firstUpdated() {
141
139
  super.firstUpdated();
140
+
141
+ this.__titleController = new TitleController(this);
142
+ this.addController(this.__titleController);
143
+
142
144
  this._onSlotChange();
143
145
  }
144
146
 
147
+ /** @protected */
148
+ updated(props) {
149
+ super.updated(props);
150
+
151
+ if (props.has('cardTitle')) {
152
+ this.__titleController.setTitle(this.cardTitle);
153
+ }
154
+
155
+ if (props.has('titleHeadingLevel')) {
156
+ this.__titleController.setLevel(this.titleHeadingLevel);
157
+ }
158
+ }
159
+
145
160
  /** @private */
146
161
  _onSlotChange() {
147
162
  this.toggleAttribute('_m', this.querySelector(':scope > [slot="media"]'));
@@ -158,9 +173,6 @@ class Card extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(Li
158
173
  this.toggleAttribute('_hs', this.querySelector(':scope > [slot="header-suffix"]'));
159
174
  this.toggleAttribute('_c', this.__hasContent());
160
175
  this.toggleAttribute('_f', this.querySelector(':scope > [slot="footer"]'));
161
- if (this.__getCustomTitleElement()) {
162
- this.__clearStringTitle();
163
- }
164
176
  }
165
177
 
166
178
  /** @private */
@@ -169,76 +181,6 @@ class Card extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(Li
169
181
  return slot.assignedNodes({ flatten: true }).filter((node) => !isEmptyTextNode(node)).length > 0;
170
182
  }
171
183
 
172
- /** @private */
173
- __clearStringTitle() {
174
- const stringTitleElement = this.__getStringTitleElement();
175
- if (stringTitleElement) {
176
- this.removeChild(stringTitleElement);
177
- }
178
- const ariaLabelledby = this.getAttribute('aria-labelledby');
179
- if (ariaLabelledby?.startsWith('card-title-')) {
180
- this.removeAttribute('aria-labelledby');
181
- }
182
- if (this.cardTitle) {
183
- this.cardTitle = '';
184
- }
185
- }
186
-
187
- /** @private */
188
- __getCustomTitleElement() {
189
- return Array.from(this.querySelectorAll('[slot="title"]')).find((el) => {
190
- return !el.hasAttribute('card-string-title');
191
- });
192
- }
193
-
194
- /** @private */
195
- __cardTitleChanged(title) {
196
- if (!title) {
197
- this.__clearStringTitle();
198
- return;
199
- }
200
- const customTitleElement = this.__getCustomTitleElement();
201
- if (customTitleElement) {
202
- this.removeChild(customTitleElement);
203
- }
204
- let stringTitleElement = this.__getStringTitleElement();
205
- if (!stringTitleElement) {
206
- stringTitleElement = this.__createStringTitleElement();
207
- this.appendChild(stringTitleElement);
208
- this.setAttribute('aria-labelledby', stringTitleElement.id);
209
- }
210
- stringTitleElement.textContent = title;
211
- }
212
-
213
- /** @private */
214
- __createStringTitleElement() {
215
- const stringTitleElement = document.createElement('div');
216
- stringTitleElement.setAttribute('slot', 'title');
217
- stringTitleElement.setAttribute('role', 'heading');
218
- this.__setTitleHeadingLevel(stringTitleElement, this.titleHeadingLevel);
219
- stringTitleElement.setAttribute('card-string-title', '');
220
- stringTitleElement.id = `card-title-${generateUniqueId()}`;
221
- return stringTitleElement;
222
- }
223
-
224
- /** @private */
225
- __titleHeadingLevelChanged(titleHeadingLevel) {
226
- const stringTitleElement = this.__getStringTitleElement();
227
- if (stringTitleElement) {
228
- this.__setTitleHeadingLevel(stringTitleElement, titleHeadingLevel);
229
- }
230
- }
231
-
232
- /** @private */
233
- __setTitleHeadingLevel(stringTitleElement, titleHeadingLevel) {
234
- stringTitleElement.setAttribute('aria-level', titleHeadingLevel || 2);
235
- }
236
-
237
- /** @private */
238
- __getStringTitleElement() {
239
- return this.querySelector('[slot="title"][card-string-title]');
240
- }
241
-
242
184
  /**
243
185
  * @protected
244
186
  * @override
package/web-types.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/card",
4
- "version": "25.3.0-alpha2",
4
+ "version": "25.3.0-alpha3",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/web-types",
3
3
  "name": "@vaadin/card",
4
- "version": "25.3.0-alpha2",
4
+ "version": "25.3.0-alpha3",
5
5
  "description-markup": "markdown",
6
6
  "framework": "lit",
7
7
  "framework-config": {