Skip to content

Commit 158e64d

Browse files
authored
feat: auto-update widget title level (#7740)
1 parent 6586681 commit 158e64d

File tree

4 files changed

+166
-4
lines changed

4 files changed

+166
-4
lines changed

packages/dashboard/src/title-controller.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
import { SlotChildObserveController } from '@vaadin/component-base/src/slot-child-observe-controller.js';
77

8+
const DEFAULT_TITLE_LEVEL = '2';
9+
810
/**
911
* A controller to manage the widget or section title element.
1012
*/
@@ -21,6 +23,17 @@ export class TitleController extends SlotChildObserveController {
2123
setTitle(title) {
2224
this.title = title;
2325

26+
const titleLevel =
27+
getComputedStyle(this.host).getPropertyValue('--_vaadin-dashboard-title-level') || DEFAULT_TITLE_LEVEL;
28+
const newTagName = `h${titleLevel}`;
29+
if (this.tagName !== newTagName) {
30+
if (this.defaultNode) {
31+
this.defaultNode.remove();
32+
delete this.defaultNode;
33+
}
34+
this.tagName = newTagName;
35+
}
36+
2437
// Restore the default title, if needed.
2538
const titleNode = this.getSlotChild();
2639
if (!titleNode) {
@@ -41,7 +54,6 @@ export class TitleController extends SlotChildObserveController {
4154
* @override
4255
*/
4356
restoreDefaultNode() {
44-
this.tagName = 'h2';
4557
this.attachDefaultNode();
4658
}
4759

packages/dashboard/src/vaadin-dashboard-section.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class DashboardSection extends ControllerMixin(ElementMixin(PolylitMixin(LitElem
4444
}
4545
4646
::slotted(*) {
47+
--_vaadin-dashboard-title-level: 3;
4748
--_vaadin-dashboard-item-column: span
4849
min(
4950
var(--vaadin-dashboard-item-colspan, 1),

packages/dashboard/src/vaadin-dashboard-widget.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,18 @@ class DashboardWidget extends ControllerMixin(ElementMixin(PolylitMixin(LitEleme
9292
});
9393
}
9494

95+
/** @protected */
96+
connectedCallback() {
97+
super.connectedCallback();
98+
99+
const undefinedAncestor = this.closest('*:not(:defined)');
100+
if (undefinedAncestor) {
101+
customElements.whenDefined(undefinedAncestor.localName).then(() => queueMicrotask(() => this.__updateTitle()));
102+
} else {
103+
this.__updateTitle();
104+
}
105+
}
106+
95107
/** @protected */
96108
ready() {
97109
super.ready();
@@ -103,8 +115,13 @@ class DashboardWidget extends ControllerMixin(ElementMixin(PolylitMixin(LitEleme
103115
}
104116

105117
/** @private */
106-
__onWidgetTitleChanged(widgetTitle) {
107-
this.__titleController.setTitle(widgetTitle);
118+
__onWidgetTitleChanged() {
119+
this.__updateTitle();
120+
}
121+
122+
/** @private */
123+
__updateTitle() {
124+
this.__titleController.setTitle(this.widgetTitle);
108125
}
109126
}
110127

packages/dashboard/test/dashboard-widget.test.ts

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { expect } from '@vaadin/chai-plugins';
22
import { fixtureSync, nextFrame } from '@vaadin/testing-helpers';
33
import '../vaadin-dashboard-widget.js';
4-
import type { DashboardWidget } from '../vaadin-dashboard-widget.js';
4+
import { DashboardSection } from '../vaadin-dashboard-section.js';
5+
import { DashboardWidget } from '../vaadin-dashboard-widget.js';
56

67
describe('dashboard widget', () => {
78
let widget: DashboardWidget;
@@ -95,3 +96,134 @@ describe('dashboard widget', () => {
9596
});
9697
});
9798
});
99+
100+
describe('widget title level', () => {
101+
it('should have h2 title by default', async () => {
102+
const widget = fixtureSync(`<vaadin-dashboard-widget widget-title="foo"></vaadin-dashboard-widget>`);
103+
await nextFrame();
104+
105+
const title = widget.querySelector('[slot="title"]');
106+
expect(title?.localName).to.equal('h2');
107+
});
108+
109+
it('should have h2 title by default on the section', async () => {
110+
const section = fixtureSync(`<vaadin-dashboard-section section-title="foo"></vaadin-dashboard-section>`);
111+
await nextFrame();
112+
113+
const title = section.querySelector('[slot="title"]');
114+
expect(title?.localName).to.equal('h2');
115+
});
116+
117+
it('should have h3 title when rendered inside a section', async () => {
118+
const widget = fixtureSync(`
119+
<vaadin-dashboard-section>
120+
<vaadin-dashboard-widget widget-title="foo"></vaadin-dashboard-widget>
121+
</vaadin-dashboard-section>
122+
`).querySelector('vaadin-dashboard-widget')!;
123+
await nextFrame();
124+
125+
const title = widget.querySelector('[slot="title"]');
126+
expect(title?.localName).to.equal('h3');
127+
});
128+
129+
it('should have h2 title after moving out of a section', async () => {
130+
const widget = fixtureSync(`
131+
<div>
132+
<vaadin-dashboard-section>
133+
<vaadin-dashboard-widget widget-title="foo"></vaadin-dashboard-widget>
134+
</vaadin-dashboard-section>
135+
</div>
136+
`).querySelector('vaadin-dashboard-widget')!;
137+
await nextFrame();
138+
139+
const wrapper = widget.closest('div')!;
140+
wrapper.appendChild(widget);
141+
await nextFrame();
142+
143+
const title = widget.querySelector('[slot="title"]');
144+
expect(title?.localName).to.equal('h2');
145+
});
146+
147+
it('should have h3 title after moving into a section', async () => {
148+
const widget = fixtureSync(`
149+
<div>
150+
<vaadin-dashboard-widget widget-title="foo"></vaadin-dashboard-widget>
151+
<vaadin-dashboard-section></vaadin-dashboard-section>
152+
</div>
153+
`).querySelector('vaadin-dashboard-widget')!;
154+
await nextFrame();
155+
156+
const section = widget.nextElementSibling as DashboardSection;
157+
section.appendChild(widget);
158+
await nextFrame();
159+
160+
const title = widget.querySelector('[slot="title"]');
161+
expect(title?.localName).to.equal('h3');
162+
});
163+
164+
it('should have h3 title after defining parent section', async () => {
165+
const widget = fixtureSync(`
166+
<my-custom-section>
167+
<vaadin-dashboard-widget widget-title="foo"></vaadin-dashboard-widget>
168+
</my-custom-section>
169+
`).querySelector('vaadin-dashboard-widget')!;
170+
await nextFrame();
171+
172+
class MyCustomSection extends DashboardSection {}
173+
customElements.define('my-custom-section', MyCustomSection);
174+
await nextFrame();
175+
176+
const title = widget.querySelector('[slot="title"]');
177+
expect(title?.localName).to.equal('h3');
178+
});
179+
180+
it('should have h3 title after defining the widget', async () => {
181+
const widget = fixtureSync(`
182+
<vaadin-dashboard-section>
183+
<my-custom-widget widget-title="foo"></my-custom-widget>
184+
</vaadin-dashboard-section>
185+
`).querySelector('my-custom-widget')!;
186+
await nextFrame();
187+
188+
class MyCustomWidget extends DashboardWidget {}
189+
customElements.define('my-custom-widget', MyCustomWidget);
190+
await nextFrame();
191+
192+
const title = widget.querySelector('[slot="title"]');
193+
expect(title?.localName).to.equal('h3');
194+
});
195+
196+
it('should have h3 title after moving a wrapped widget into a section', async () => {
197+
const widget = fixtureSync(`
198+
<div>
199+
<div id="wrapper">
200+
<vaadin-dashboard-widget widget-title="foo"></vaadin-dashboard-widget>
201+
</div>
202+
<vaadin-dashboard-section></vaadin-dashboard-section>
203+
</div>
204+
`).querySelector('vaadin-dashboard-widget')!;
205+
await nextFrame();
206+
207+
const wrapper = widget.closest('div#wrapper')!;
208+
const section = wrapper.nextElementSibling as DashboardSection;
209+
section.appendChild(wrapper);
210+
await nextFrame();
211+
212+
const title = widget.querySelector('[slot="title"]');
213+
expect(title?.localName).to.equal('h3');
214+
});
215+
216+
it('should not replace an explicitly defined widget title element', async () => {
217+
const widget = fixtureSync(`
218+
<vaadin-dashboard-section>
219+
<vaadin-dashboard-widget>
220+
<h2 slot="title">foo</h2>
221+
</vaadin-dashboard-widget>
222+
</vaadin-dashboard-section>
223+
`).querySelector('vaadin-dashboard-widget')!;
224+
await nextFrame();
225+
226+
const title = widget.querySelector('[slot="title"]');
227+
expect(title?.localName).to.equal('h2');
228+
});
229+
});

0 commit comments

Comments
 (0)