@vaadin/card 25.2.7 → 25.3.0-alpha10
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/custom-elements.json +7 -0
- package/package.json +12 -12
- package/src/title-controller.d.ts +23 -0
- package/src/title-controller.js +139 -0
- package/src/vaadin-card.js +19 -76
- package/web-types.json +2 -4
- package/web-types.lit.json +1 -1
package/custom-elements.json
CHANGED
|
@@ -56,6 +56,13 @@
|
|
|
56
56
|
"description": "The title of the card. When set, any custom slotted title is removed and this string-based title is used instead. If this title is used, an `aria-labelledby` attribute that points to the generated title element is set.",
|
|
57
57
|
"fieldName": "cardTitle"
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
"type": {
|
|
61
|
+
"text": "string"
|
|
62
|
+
},
|
|
63
|
+
"description": "The theme variants to apply to the component.",
|
|
64
|
+
"name": "theme"
|
|
65
|
+
},
|
|
59
66
|
{
|
|
60
67
|
"name": "title-heading-level",
|
|
61
68
|
"type": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/card",
|
|
3
|
-
"version": "25.
|
|
3
|
+
"version": "25.3.0-alpha10",
|
|
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": "
|
|
38
|
-
"@vaadin/vaadin-themable-mixin": "
|
|
37
|
+
"@vaadin/component-base": "25.3.0-alpha10",
|
|
38
|
+
"@vaadin/vaadin-themable-mixin": "25.3.0-alpha10",
|
|
39
39
|
"lit": "^3.0.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@vaadin/aura": "
|
|
43
|
-
"@vaadin/avatar": "
|
|
44
|
-
"@vaadin/button": "
|
|
45
|
-
"@vaadin/chai-plugins": "
|
|
46
|
-
"@vaadin/icon": "
|
|
47
|
-
"@vaadin/icons": "
|
|
48
|
-
"@vaadin/test-runner-commands": "
|
|
42
|
+
"@vaadin/aura": "25.3.0-alpha10",
|
|
43
|
+
"@vaadin/avatar": "25.3.0-alpha10",
|
|
44
|
+
"@vaadin/button": "25.3.0-alpha10",
|
|
45
|
+
"@vaadin/chai-plugins": "25.3.0-alpha10",
|
|
46
|
+
"@vaadin/icon": "25.3.0-alpha10",
|
|
47
|
+
"@vaadin/icons": "25.3.0-alpha10",
|
|
48
|
+
"@vaadin/test-runner-commands": "25.3.0-alpha10",
|
|
49
49
|
"@vaadin/testing-helpers": "^2.0.0",
|
|
50
|
-
"@vaadin/vaadin-lumo-styles": "
|
|
50
|
+
"@vaadin/vaadin-lumo-styles": "25.3.0-alpha10",
|
|
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": "
|
|
58
|
+
"gitHead": "f2833abdf9b613fa0d0ed216830e3f4de87b7dac"
|
|
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
|
+
}
|
package/src/vaadin-card.js
CHANGED
|
@@ -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.
|
|
@@ -61,6 +61,7 @@ import { cardStyles } from './styles/vaadin-card-base-styles.js';
|
|
|
61
61
|
*
|
|
62
62
|
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
|
|
63
63
|
*
|
|
64
|
+
* @attr {string} theme - The theme variants to apply to the component.
|
|
64
65
|
* @customElement vaadin-card
|
|
65
66
|
* @extends HTMLElement
|
|
66
67
|
*/
|
|
@@ -86,7 +87,6 @@ class Card extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(Li
|
|
|
86
87
|
*/
|
|
87
88
|
cardTitle: {
|
|
88
89
|
type: String,
|
|
89
|
-
observer: '__cardTitleChanged',
|
|
90
90
|
},
|
|
91
91
|
|
|
92
92
|
/**
|
|
@@ -97,7 +97,6 @@ class Card extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(Li
|
|
|
97
97
|
titleHeadingLevel: {
|
|
98
98
|
type: Number,
|
|
99
99
|
reflectToAttribute: true,
|
|
100
|
-
observer: '__titleHeadingLevelChanged',
|
|
101
100
|
},
|
|
102
101
|
};
|
|
103
102
|
}
|
|
@@ -139,9 +138,26 @@ class Card extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(Li
|
|
|
139
138
|
/** @protected */
|
|
140
139
|
firstUpdated() {
|
|
141
140
|
super.firstUpdated();
|
|
141
|
+
|
|
142
|
+
this.__titleController = new TitleController(this);
|
|
143
|
+
this.addController(this.__titleController);
|
|
144
|
+
|
|
142
145
|
this._onSlotChange();
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
/** @protected */
|
|
149
|
+
updated(props) {
|
|
150
|
+
super.updated(props);
|
|
151
|
+
|
|
152
|
+
if (props.has('cardTitle')) {
|
|
153
|
+
this.__titleController.setTitle(this.cardTitle);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (props.has('titleHeadingLevel')) {
|
|
157
|
+
this.__titleController.setLevel(this.titleHeadingLevel);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
145
161
|
/** @private */
|
|
146
162
|
_onSlotChange() {
|
|
147
163
|
this.toggleAttribute('_m', this.querySelector(':scope > [slot="media"]'));
|
|
@@ -158,9 +174,6 @@ class Card extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(Li
|
|
|
158
174
|
this.toggleAttribute('_hs', this.querySelector(':scope > [slot="header-suffix"]'));
|
|
159
175
|
this.toggleAttribute('_c', this.__hasContent());
|
|
160
176
|
this.toggleAttribute('_f', this.querySelector(':scope > [slot="footer"]'));
|
|
161
|
-
if (this.__getCustomTitleElement()) {
|
|
162
|
-
this.__clearStringTitle();
|
|
163
|
-
}
|
|
164
177
|
}
|
|
165
178
|
|
|
166
179
|
/** @private */
|
|
@@ -169,76 +182,6 @@ class Card extends ElementMixin(ThemableMixin(PolylitMixin(LumoInjectionMixin(Li
|
|
|
169
182
|
return slot.assignedNodes({ flatten: true }).filter((node) => !isEmptyTextNode(node)).length > 0;
|
|
170
183
|
}
|
|
171
184
|
|
|
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
185
|
/**
|
|
243
186
|
* @protected
|
|
244
187
|
* @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.
|
|
4
|
+
"version": "25.3.0-alpha10",
|
|
5
5
|
"description-markup": "markdown",
|
|
6
6
|
"contributions": {
|
|
7
7
|
"html": {
|
|
@@ -24,9 +24,7 @@
|
|
|
24
24
|
"description": "The theme variants to apply to the component.",
|
|
25
25
|
"value": {
|
|
26
26
|
"type": [
|
|
27
|
-
"string"
|
|
28
|
-
"null",
|
|
29
|
-
"undefined"
|
|
27
|
+
"string"
|
|
30
28
|
]
|
|
31
29
|
}
|
|
32
30
|
},
|