Replace paper tabs by shoelace tabs (#24909)

pull/24996/head
Bram Kragten 2025-04-10 16:20:24 +02:00 committed by GitHub
parent a6c9702ab2
commit 430e47c0fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 377 additions and 463 deletions

View File

@ -182,6 +182,7 @@ module.exports.babelOptions = ({
include: /\/node_modules\//,
exclude: [
"element-internals-polyfill",
"@shoelace-style",
"@?lit(?:-labs|-element|-html)?",
].map((p) => new RegExp(`/node_modules/${p}/`)),
},

View File

@ -3,7 +3,6 @@ export const demoThemeJimpower = () => ({
"paper-item-icon-color": "var(--primary-text-color)",
"primary-color": "#5294E2",
"label-badge-red": "var(--accent-color)",
"paper-tabs-selection-bar-color": "green",
"light-primary-color": "var(--accent-color)",
"primary-background-color": "#383C45",
"primary-text-color": "#FFFFFF",

View File

@ -4,7 +4,6 @@ export const demoThemeKernehed = () => ({
"paper-item-icon-color": "var(--primary-text-color)",
"primary-color": "#2980b9",
"label-badge-red": "var(--accent-color)",
"paper-tabs-selection-bar-color": "green",
"primary-text-color": "#FFFFFF",
"light-primary-color": "var(--accent-color)",
"primary-background-color": "#222222",

View File

@ -86,7 +86,6 @@
"@mdi/svg": "7.4.47",
"@polymer/paper-item": "3.0.1",
"@polymer/paper-listbox": "3.0.1",
"@polymer/paper-tabs": "3.1.0",
"@polymer/polymer": "3.5.2",
"@replit/codemirror-indentation-markers": "6.5.3",
"@shoelace-style/shoelace": "2.20.1",

View File

@ -1,115 +0,0 @@
import type { PaperIconButtonElement } from "@polymer/paper-icon-button/paper-icon-button";
import type { PaperTabElement } from "@polymer/paper-tabs/paper-tab";
import "@polymer/paper-tabs/paper-tabs";
import type { PaperTabsElement } from "@polymer/paper-tabs/paper-tabs";
import { customElement } from "lit/decorators";
import type { Constructor } from "../types";
// eslint-disable-next-line @typescript-eslint/naming-convention
const PaperTabs = customElements.get(
"paper-tabs"
) as Constructor<PaperTabsElement>;
let subTemplate: HTMLTemplateElement;
@customElement("ha-tabs")
export class HaTabs extends PaperTabs {
private _firstTabWidth = 0;
private _lastTabWidth = 0;
private _lastLeftHiddenState = false;
private _lastRightHiddenState = false;
static get template(): HTMLTemplateElement {
if (!subTemplate) {
subTemplate = (PaperTabs as any).template.cloneNode(true);
const superStyle = subTemplate.content.querySelector("style");
// Add "noink" attribute for scroll buttons to disable animation.
subTemplate.content
.querySelectorAll("paper-icon-button")
.forEach((arrow: PaperIconButtonElement) => {
arrow.setAttribute("noink", "");
});
superStyle!.appendChild(
document.createTextNode(`
#selectionBar {
box-sizing: border-box;
}
.not-visible {
display: none;
}
paper-icon-button {
width: 24px;
height: 48px;
padding: 0;
margin: 0;
}
`)
);
}
return subTemplate;
}
// Get first and last tab's width for _affectScroll
// eslint-disable-next-line @typescript-eslint/naming-convention
public _tabChanged(tab: PaperTabElement, old: PaperTabElement): void {
super._tabChanged(tab, old);
const tabs = this.querySelectorAll("paper-tab:not(.hide-tab)");
if (tabs.length > 0) {
this._firstTabWidth = tabs[0].clientWidth;
this._lastTabWidth = tabs[tabs.length - 1].clientWidth;
}
// Scroll active tab into view if needed.
const selected = this.querySelector(".iron-selected");
if (selected) {
selected.scrollIntoView();
this._affectScroll(0); // Ensure scroll arrows match scroll position
}
}
/**
* Modify _affectScroll so that when the scroll arrows appear
* while scrolling and the tab container shrinks we can counteract
* the jump in tab position so that the scroll still appears smooth.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
public _affectScroll(dx: number): void {
if (this._firstTabWidth === 0 || this._lastTabWidth === 0) {
return;
}
this.$.tabsContainer.scrollLeft += dx;
const scrollLeft = this.$.tabsContainer.scrollLeft;
const dirRTL = this.dir === "rtl";
const boolCondition1 = Math.abs(scrollLeft) < this._firstTabWidth;
const boolCondition2 =
Math.abs(scrollLeft) + this._lastTabWidth > this._tabContainerScrollSize;
this._leftHidden = !dirRTL ? boolCondition1 : boolCondition2;
this._rightHidden = !dirRTL ? boolCondition2 : boolCondition1;
if (!dirRTL) {
if (this._lastLeftHiddenState !== this._leftHidden) {
this._lastLeftHiddenState = this._leftHidden;
this.$.tabsContainer.scrollLeft += this._leftHidden ? -23 : 23;
}
} else if (this._lastRightHiddenState !== this._rightHidden) {
this._lastRightHiddenState = this._rightHidden;
this.$.tabsContainer.scrollLeft -= this._rightHidden ? -23 : 23;
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-tabs": HaTabs;
}
}

View File

@ -0,0 +1,159 @@
import TabGroup from "@shoelace-style/shoelace/dist/components/tab-group/tab-group.component";
import TabGroupStyles from "@shoelace-style/shoelace/dist/components/tab-group/tab-group.styles";
import "@shoelace-style/shoelace/dist/components/tab/tab";
import type { PropertyValues } from "lit";
import { css } from "lit";
import { customElement, query } from "lit/decorators";
@customElement("sl-tab-group")
// @ts-ignore
export class HaSlTabGroup extends TabGroup {
private _mouseIsDown = false;
private _scrolled = false;
private _mouseReleasedAt?: number;
private _scrollStartX = 0;
private _scrollLeft = 0;
@query(".tab-group__nav", true) private _scrollContainer?: HTMLElement;
public disconnectedCallback(): void {
super.disconnectedCallback();
window.removeEventListener("mousemove", this._mouseMove);
}
override setAriaLabels() {
// Override the method to prevent setting aria-labels, as we don't use panels
// and don't want to set aria-labels for the tabs
}
override getAllPanels() {
// Override the method to prevent querying for panels
// and return an empty array instead
// as we don't use panels
return [];
}
protected override firstUpdated(_changedProperties: PropertyValues): void {
super.firstUpdated(_changedProperties);
const scrollContainer = this._scrollContainer;
if (scrollContainer) {
scrollContainer.addEventListener("mousedown", this._mouseDown);
scrollContainer.addEventListener("mouseup", this._mouseUp);
}
}
// @ts-ignore
protected override handleClick(event: MouseEvent) {
if (
this._mouseReleasedAt &&
new Date().getTime() - this._mouseReleasedAt < 100
) {
return;
}
// @ts-ignore
super.handleClick(event);
}
private _mouseDown = (event: MouseEvent) => {
const scrollContainer = this._scrollContainer;
if (!scrollContainer) {
return;
}
this._scrollStartX = event.pageX - scrollContainer.offsetLeft;
this._scrollLeft = scrollContainer.scrollLeft;
this._mouseIsDown = true;
this._scrolled = false;
window.addEventListener("mousemove", this._mouseMove);
};
private _mouseUp = () => {
this._mouseIsDown = false;
if (this._scrolled) {
this._mouseReleasedAt = new Date().getTime();
}
window.removeEventListener("mousemove", this._mouseMove);
};
private _mouseMove = (event: MouseEvent) => {
if (!this._mouseIsDown) {
return;
}
const scrollContainer = this._scrollContainer;
if (!scrollContainer) {
return;
}
const x = event.pageX - scrollContainer.offsetLeft;
const scroll = x - this._scrollStartX;
if (!this._scrolled) {
this._scrolled = Math.abs(scroll) > 1;
}
scrollContainer.scrollLeft = this._scrollLeft - scroll;
};
static override styles = [
TabGroupStyles,
css`
:host {
--sl-spacing-3x-small: 0.125rem;
--sl-spacing-2x-small: 0.25rem;
--sl-spacing-x-small: 0.5rem;
--sl-spacing-small: 0.75rem;
--sl-spacing-medium: 1rem;
--sl-spacing-large: 1.25rem;
--sl-spacing-x-large: 1.75rem;
--sl-spacing-2x-large: 2.25rem;
--sl-spacing-3x-large: 3rem;
--sl-spacing-4x-large: 4.5rem;
--sl-transition-x-slow: 1000ms;
--sl-transition-slow: 500ms;
--sl-transition-medium: 250ms;
--sl-transition-fast: 150ms;
--sl-transition-x-fast: 50ms;
--transition-speed: var(--sl-transition-fast);
--sl-border-radius-small: 0.1875rem;
--sl-border-radius-medium: 0.25rem;
--sl-border-radius-large: 0.5rem;
--sl-border-radius-x-large: 1rem;
--sl-border-radius-circle: 50%;
--sl-border-radius-pill: 9999px;
--sl-color-neutral-600: inherit;
--sl-font-weight-semibold: 500;
--sl-font-size-small: 14px;
--sl-color-primary-600: var(
--ha-tab-active-text-color,
var(--primary-color)
);
--track-color: var(--ha-tab-track-color, var(--divider-color));
--indicator-color: var(--ha-tab-indicator-color, var(--primary-color));
}
::slotted(sl-tab:not([active])) {
opacity: 0.8;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
// @ts-ignore
"sl-tab-group": HaSlTabGroup;
}
}

View File

@ -1,6 +1,4 @@
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/paper-tabs/paper-tab";
import "@polymer/paper-tabs/paper-tabs";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
@ -10,6 +8,7 @@ import "../../components/ha-menu-button";
import "../../components/ha-button-menu";
import "../../components/ha-icon-button";
import "../../components/ha-list-item";
import "../../components/sl-tab-group";
import { haStyle } from "../../resources/styles";
import type { HomeAssistant, Route } from "../../types";
import "./developer-tools-router";
@ -51,36 +50,37 @@ class PanelDeveloperTools extends LitElement {
</ha-list-item>
</ha-button-menu>
</div>
<paper-tabs
scrollable
attr-for-selected="page-name"
.selected=${page}
@selected-changed=${this._handlePageSelected}
>
<paper-tab page-name="yaml">
<sl-tab-group @sl-tab-show=${this._handlePageSelected}>
<sl-tab slot="nav" panel="yaml" .active=${page === "yaml"}>
${this.hass.localize("ui.panel.developer-tools.tabs.yaml.title")}
</paper-tab>
<paper-tab page-name="state">
</sl-tab>
<sl-tab slot="nav" panel="state" .active=${page === "state"}>
${this.hass.localize("ui.panel.developer-tools.tabs.states.title")}
</paper-tab>
<paper-tab page-name="action">
</sl-tab>
<sl-tab slot="nav" panel="action" .active=${page === "action"}>
${this.hass.localize("ui.panel.developer-tools.tabs.actions.title")}
</paper-tab>
<paper-tab page-name="template">
</sl-tab>
<sl-tab slot="nav" panel="template" .active=${page === "template"}>
${this.hass.localize(
"ui.panel.developer-tools.tabs.templates.title"
)}
</paper-tab>
<paper-tab page-name="event">
</sl-tab>
<sl-tab slot="nav" panel="event" .active=${page === "event"}>
${this.hass.localize("ui.panel.developer-tools.tabs.events.title")}
</paper-tab>
<paper-tab page-name="statistics">
</sl-tab>
<sl-tab
slot="nav"
panel="statistics"
.active=${page === "statistics"}
>
${this.hass.localize(
"ui.panel.developer-tools.tabs.statistics.title"
)}
</paper-tab>
<paper-tab page-name="assist">Assist</paper-tab>
</paper-tabs>
</sl-tab>
<sl-tab slot="nav" panel="assist" .active=${page === "assist"}
>Assist</sl-tab
>
</sl-tab-group>
</div>
<developer-tools-router
.route=${this.route}
@ -90,8 +90,11 @@ class PanelDeveloperTools extends LitElement {
`;
}
private _handlePageSelected(ev) {
const newPage = ev.detail.value;
private _handlePageSelected(ev: CustomEvent<{ name: string }>) {
const newPage = ev.detail.name;
if (!newPage) {
return;
}
if (newPage !== this._page) {
navigate(`/developer-tools/${newPage}`);
} else {
@ -161,16 +164,10 @@ class PanelDeveloperTools extends LitElement {
flex: 1 1 100%;
max-width: 100%;
}
paper-tabs {
margin-left: max(env(safe-area-inset-left), 24px);
margin-right: max(env(safe-area-inset-right), 24px);
margin-inline-start: max(env(safe-area-inset-left), 24px);
margin-inline-end: max(env(safe-area-inset-right), 24px);
--paper-tabs-selection-bar-color: var(
--app-header-selection-bar-color,
var(--app-header-text-color, #fff)
);
text-transform: uppercase;
sl-tab-group {
--ha-tab-active-text-color: var(--text-primary-color);
--ha-tab-track-color: var(--app-header-background-color);
--ha-tab-indicator-color: var(--text-primary-color);
}
`,
];

View File

@ -6,12 +6,11 @@ import {
mdiListBoxOutline,
mdiPlus,
} from "@mdi/js";
import "@polymer/paper-tabs";
import "@polymer/paper-tabs/paper-tab";
import deepClone from "deep-clone-simple";
import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { keyed } from "lit/directives/keyed";
import {
any,
array,
@ -21,17 +20,17 @@ import {
optional,
string,
} from "superstruct";
import { keyed } from "lit/directives/keyed";
import { storage } from "../../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import { fireEvent } from "../../../../common/dom/fire_event";
import type {
HaFormSchema,
SchemaUnion,
} from "../../../../components/ha-form/types";
import { storage } from "../../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-icon-button-arrow-prev";
import "../../../../components/ha-icon-button-arrow-next";
import "../../../../components/ha-icon-button-arrow-prev";
import "../../../../components/sl-tab-group";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import type { HomeAssistant } from "../../../../types";
@ -124,24 +123,18 @@ export class HuiStackCardEditor
></ha-form>
<div class="card-config">
<div class="toolbar">
<paper-tabs
.selected=${selected}
scrollable
@iron-activate=${this._handleSelectedCard}
>
<sl-tab-group @sl-tab-show=${this._handleSelectedCard}>
${this._config.cards.map(
(_card, i) => html` <paper-tab> ${i + 1} </paper-tab> `
(_card, i) =>
html`<sl-tab slot="nav" .panel=${i} .active=${i === selected}>
${i + 1}
</sl-tab>`
)}
</paper-tabs>
<paper-tabs
id="add-card"
.selected=${selected === numcards ? "0" : undefined}
@iron-activate=${this._handleSelectedCard}
>
<paper-tab>
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
</paper-tab>
</paper-tabs>
</sl-tab-group>
<ha-icon-button
@click=${this._handleAddCard}
.path=${mdiPlus}
></ha-icon-button>
</div>
<div id="editor">
@ -234,14 +227,16 @@ export class HuiStackCardEditor
return this._keys.get(key)!;
}
protected async _handleAddCard() {
this._selectedCard = this._config!.cards.length;
await this.updateComplete;
this.renderRoot.querySelector("sl-tab-group")!.syncIndicator();
}
protected _handleSelectedCard(ev) {
if (ev.target.id === "add-card") {
this._selectedCard = this._config!.cards.length;
return;
}
this._setMode(true);
this._guiModeAvailable = true;
this._selectedCard = parseInt(ev.detail.selected, 10);
this._selectedCard = parseInt(ev.detail.name, 10);
}
protected _handleConfigChanged(ev: HASSDomEvent<ConfigChangedEvent>) {
@ -344,17 +339,13 @@ export class HuiStackCardEditor
css`
.toolbar {
display: flex;
--paper-tabs-selection-bar-color: var(--primary-color);
--paper-tab-ink: var(--primary-color);
justify-content: space-between;
align-items: center;
}
paper-tabs {
display: flex;
font-size: 14px;
sl-tab-group {
flex-grow: 1;
}
#add-card {
max-width: 32px;
padding: 0;
min-width: 0;
--ha-tab-track-color: var(--card-background-color);
}
#card-options {

View File

@ -15,10 +15,8 @@ import {
mdiShape,
mdiViewDashboard,
} from "@mdi/js";
import "@polymer/paper-tabs/paper-tab";
import "@polymer/paper-tabs/paper-tabs";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
@ -34,7 +32,6 @@ import {
extractSearchParamsObject,
removeSearchParam,
} from "../../common/url/search-params";
import { computeRTLDirection } from "../../common/util/compute_rtl";
import { debounce } from "../../common/util/debounce";
import { afterNextRender } from "../../common/util/render-status";
import "../../components/ha-button-menu";
@ -44,7 +41,7 @@ import "../../components/ha-icon-button-arrow-next";
import "../../components/ha-icon-button-arrow-prev";
import "../../components/ha-menu-button";
import "../../components/ha-svg-icon";
import "../../components/ha-tabs";
import "../../components/sl-tab-group";
import type { LovelacePanelConfig } from "../../data/lovelace";
import type { LovelaceConfig } from "../../data/lovelace/config/types";
import { isStrategyDashboard } from "../../data/lovelace/config/types";
@ -76,9 +73,9 @@ import { getLovelaceStrategy } from "./strategies/get-strategy";
import { isLegacyStrategyConfig } from "./strategies/legacy-strategy";
import type { Lovelace } from "./types";
import "./views/hui-view";
import "./views/hui-view-container";
import type { HUIView } from "./views/hui-view";
import "./views/hui-view-background";
import "./views/hui-view-container";
import { showShortcutsDialog } from "../../dialogs/shortcuts/show-shortcuts-dialog";
@customElement("hui-root")
@ -302,6 +299,77 @@ class HUIRoot extends LitElement {
const background = curViewConfig?.background || this.config.background;
const tabs = html`<sl-tab-group @sl-tab-show=${this._handleViewSelected}>
${views.map(
(view, index) => html`
<sl-tab
slot="nav"
panel=${index}
.active=${this._curView === index}
aria-label=${ifDefined(view.title)}
class=${classMap({
icon: Boolean(view.icon),
"hide-tab": Boolean(
!this._editMode &&
view.visible !== undefined &&
((Array.isArray(view.visible) &&
!view.visible.some(
(e) => e.user === this.hass!.user?.id
)) ||
view.visible === false)
),
})}
>
${this._editMode
? html`
<ha-icon-button-arrow-prev
.hass=${this.hass}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.move_left"
)}
class="edit-icon view"
@click=${this._moveViewLeft}
.disabled=${this._curView === 0}
></ha-icon-button-arrow-prev>
`
: nothing}
${view.icon
? html`
<ha-icon
class=${classMap({
"child-view-icon": Boolean(view.subview),
})}
title=${ifDefined(view.title)}
.icon=${view.icon}
></ha-icon>
`
: view.title || "Unnamed view"}
${this._editMode
? html`
<ha-icon-button
.title=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.edit"
)}
class="edit-icon view"
.path=${mdiPencil}
@click=${this._editView}
></ha-icon-button>
<ha-icon-button-arrow-next
.hass=${this.hass}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.move_right"
)}
class="edit-icon view"
@click=${this._moveViewRight}
.disabled=${(this._curView! as number) + 1 === views.length}
></ha-icon-button-arrow-next>
`
: nothing}
</sl-tab>
`
)}
</sl-tab-group>`;
return html`
<div
class=${classMap({
@ -345,44 +413,7 @@ class HUIRoot extends LitElement {
${curViewConfig?.subview
? html`<div class="main-title">${curViewConfig.title}</div>`
: views.filter((view) => !view.subview).length > 1
? html`
<ha-tabs
slot="title"
scrollable
.selected=${this._curView}
@iron-activate=${this._handleViewSelected}
dir=${computeRTLDirection(this.hass!)}
>
${views.map(
(view) => html`
<paper-tab
aria-label=${ifDefined(view.title)}
class=${classMap({
"hide-tab": Boolean(
view.subview ||
(view.visible !== undefined &&
((Array.isArray(view.visible) &&
!view.visible.some(
(e) =>
e.user === this.hass!.user?.id
)) ||
view.visible === false))
),
})}
>
${view.icon
? html`
<ha-icon
title=${ifDefined(view.title)}
.icon=${view.icon}
></ha-icon>
`
: view.title || "Unnamed view"}
</paper-tab>
`
)}
</ha-tabs>
`
? tabs
: html`
<div class="main-title">
${views[0]?.title ?? dashboardTitle}
@ -392,93 +423,19 @@ class HUIRoot extends LitElement {
`}
</div>
${this._editMode
? html`
<paper-tabs
scrollable
.selected=${this._curView}
@iron-activate=${this._handleViewSelected}
dir=${computeRTLDirection(this.hass!)}
>
${views.map(
(view) => html`
<paper-tab
aria-label=${ifDefined(view.title)}
class=${classMap({
"hide-tab": Boolean(
!this._editMode &&
view.visible !== undefined &&
((Array.isArray(view.visible) &&
!view.visible.some(
(e) => e.user === this.hass!.user?.id
)) ||
view.visible === false)
),
})}
>
${this._editMode
? html`
<ha-icon-button-arrow-prev
.hass=${this.hass}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.move_left"
)}
class="edit-icon view"
@click=${this._moveViewLeft}
?disabled=${this._curView === 0}
></ha-icon-button-arrow-prev>
`
: ""}
${view.icon
? html`
<ha-icon
class=${classMap({
"child-view-icon": Boolean(view.subview),
})}
title=${ifDefined(view.title)}
.icon=${view.icon}
></ha-icon>
`
: view.title || "Unnamed view"}
${this._editMode
? html`
<ha-svg-icon
title=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.edit"
)}
class="edit-icon view"
.path=${mdiPencil}
@click=${this._editView}
></ha-svg-icon>
<ha-icon-button-arrow-next
.hass=${this.hass}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.move_right"
)}
class="edit-icon view"
@click=${this._moveViewRight}
?disabled=${(this._curView! as number) + 1 ===
views.length}
></ha-icon-button-arrow-next>
`
: ""}
</paper-tab>
`
? html`<div class="edit-tab-bar">
${tabs}
<ha-icon-button
slot="nav"
id="add-view"
@click=${this._addView}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.add"
)}
${this._editMode
? html`
<ha-icon-button
id="add-view"
@click=${this._addView}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.add"
)}
.path=${mdiPlus}
></ha-icon-button>
`
: ""}
</paper-tabs>
`
: ""}
.path=${mdiPlus}
></ha-icon-button>
</div>`
: nothing}
</div>
<hui-view-container
.hass=${this.hass}
@ -949,7 +906,7 @@ class HUIRoot extends LitElement {
private _handleViewSelected(ev) {
ev.preventDefault();
const viewIndex = ev.detail.selected as number;
const viewIndex = Number(ev.detail.name);
if (viewIndex !== this._curView) {
const path = this.config.views[viewIndex].path || viewIndex;
this._navigateToView(path);
@ -1082,46 +1039,95 @@ class HUIRoot extends LitElement {
display: flex;
align-items: center;
}
ha-tabs {
width: 100%;
height: 100%;
margin-left: 4px;
margin-inline-start: 4px;
margin-inline-end: initial;
}
ha-tabs,
paper-tabs {
--paper-tabs-selection-bar-color: var(
sl-tab-group {
--ha-tab-indicator-color: var(
--app-header-selection-bar-color,
var(--app-header-text-color, #fff)
var(--app-header-text-color, white)
);
--ha-tab-active-text-color: var(--app-header-text-color, white);
--ha-tab-track-color: transparent;
align-self: flex-end;
flex-grow: 1;
min-width: 0;
height: 100%;
}
sl-tab-group::part(nav) {
padding: 0;
}
sl-tab-group::part(scroll-button) {
background-color: var(--app-header-background-color);
background: linear-gradient(
90deg,
var(--app-header-background-color),
transparent
);
z-index: 1;
}
sl-tab-group::part(scroll-button--end) {
background: linear-gradient(
270deg,
var(--app-header-background-color),
transparent
);
}
.edit-mode sl-tab-group::part(scroll-button) {
background-color: var(--app-header-edit-background-color, #455a64);
background: linear-gradient(
90deg,
var(--app-header-edit-background-color, #455a64),
transparent
);
}
.edit-mode sl-tab-group::part(scroll-button--end) {
background: linear-gradient(
270deg,
var(--app-header-edit-background-color, #455a64),
transparent
);
text-transform: uppercase;
}
.edit-mode div[main-title] {
pointer-events: auto;
}
.edit-mode paper-tabs {
background-color: var(--app-header-edit-background-color, #455a64);
.edit-tab-bar {
display: flex;
}
.edit-mode sl-tab-group {
flex-grow: 0;
color: var(--app-header-edit-text-color, #fff);
}
paper-tab.iron-selected .edit-icon {
.edit-mode sl-tab {
height: 54px;
}
sl-tab {
height: calc(var(--header-height, 56px) - 2px);
}
sl-tab[aria-selected="true"] .edit-icon {
display: inline-flex;
}
sl-tab::part(base) {
padding-top: calc((var(--header-height) - 20px) / 2);
padding-bottom: calc((var(--header-height) - 20px) / 2 - 2px);
}
sl-tab.icon::part(base) {
padding-top: calc((var(--header-height) - 20px) / 2 - 2px);
padding-bottom: calc((var(--header-height) - 20px) / 2 - 4px);
}
.edit-mode sl-tab[aria-selected="true"]::part(base) {
padding: 4px 0 2px 0;
}
.edit-icon {
color: var(--accent-color);
padding-left: 8px;
padding-inline-start: 8px;
padding: 0 8px;
vertical-align: middle;
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);
direction: var(--direction);
}
.edit-icon:last-child {
padding-left: 0;
}
.edit-icon.view {
display: none;
}
#add-view {
position: absolute;
height: 44px;
}
#add-view ha-svg-icon {
background-color: var(--accent-color);
border-radius: 4px;

122
yarn.lock
View File

@ -3645,17 +3645,6 @@ __metadata:
languageName: node
linkType: hard
"@polymer/iron-checked-element-behavior@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/iron-checked-element-behavior@npm:3.0.1"
dependencies:
"@polymer/iron-form-element-behavior": "npm:^3.0.0-pre.26"
"@polymer/iron-validatable-behavior": "npm:^3.0.0-pre.26"
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/2e8b09bb417d13e7c9862e7bb882b486b0b8df56b25f7b551bf671b87606c3a3629b6451695d94eade0d468c8d5723a152d8255ccd94ff354a14cb7ffe4e60b5
languageName: node
linkType: hard
"@polymer/iron-flex-layout@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/iron-flex-layout@npm:3.0.1"
@ -3665,36 +3654,6 @@ __metadata:
languageName: node
linkType: hard
"@polymer/iron-form-element-behavior@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/iron-form-element-behavior@npm:3.0.1"
dependencies:
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/b48f2ae7e29e7762f53c316e006a67ab53f61f9f5b631fd459169889f5b77194766d7d546ffbd2dbd68a0bbe717a26df5e3f8045e7a60dbae05817231c689ed6
languageName: node
linkType: hard
"@polymer/iron-icon@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/iron-icon@npm:3.0.1"
dependencies:
"@polymer/iron-flex-layout": "npm:^3.0.0-pre.26"
"@polymer/iron-meta": "npm:^3.0.0-pre.26"
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/6bea3e965e9b61ed1d3567064e01c6f0e4586a79afe4354b53ef2dffe8b7157e120dc1b83c43df27a7cf27170b2fffbe9d4da4abf6afcdcc9dd7f8b7913bcd60
languageName: node
linkType: hard
"@polymer/iron-iconset-svg@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/iron-iconset-svg@npm:3.0.1"
dependencies:
"@polymer/iron-meta": "npm:^3.0.0-pre.26"
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/22dbad29ef88f0087ee6b5aa95fe45f65190c9ee8274cd3558ee9527e04269b2736d2f0c6eb151b3eb39dc5275c63b3197fe50fbc56fd855295b1fe44114b6ed
languageName: node
linkType: hard
"@polymer/iron-menu-behavior@npm:^3.0.0-pre.26":
version: 3.0.2
resolution: "@polymer/iron-menu-behavior@npm:3.0.2"
@ -3707,24 +3666,6 @@ __metadata:
languageName: node
linkType: hard
"@polymer/iron-meta@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/iron-meta@npm:3.0.1"
dependencies:
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/3b4c3f51d60a28f6c54b6bd0f4dfe92ab0ec208701d00a30aefeacf889015d7670a9c9e050024466b59360a15282351f5e02c128f7a6f00189a4aa6bc2546436
languageName: node
linkType: hard
"@polymer/iron-resizable-behavior@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/iron-resizable-behavior@npm:3.0.1"
dependencies:
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/9b4ccdb1602758b9fa05398b7046e7a164ea971e147a81d22e81183d60d697766ba384d9154a5e620cc28d8a4914ae229823d8368b2307c40c749045cab1e27f
languageName: node
linkType: hard
"@polymer/iron-selector@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/iron-selector@npm:3.0.1"
@ -3734,40 +3675,6 @@ __metadata:
languageName: node
linkType: hard
"@polymer/iron-validatable-behavior@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/iron-validatable-behavior@npm:3.0.1"
dependencies:
"@polymer/iron-meta": "npm:^3.0.0-pre.26"
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/d2a88a1f20c04daf1c09a4b95719b2991242f1d83a065bc91f2713c7e6d1d13cf139d7847427f17f2fd31a7ed1f7a95b07a5628c02a3ea111f83f86582fa29a9
languageName: node
linkType: hard
"@polymer/paper-behaviors@npm:^3.0.0-pre.27":
version: 3.0.1
resolution: "@polymer/paper-behaviors@npm:3.0.1"
dependencies:
"@polymer/iron-behaviors": "npm:^3.0.0-pre.26"
"@polymer/iron-checked-element-behavior": "npm:^3.0.0-pre.26"
"@polymer/paper-ripple": "npm:^3.0.0-pre.26"
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/70e054943c9c37bad903ab591fc92d4a27668ae5025639997d886e8182c81663f59094eaaa9e0900a8f12e6dd825a361afdecc5873cc02561b214305ce4b05f8
languageName: node
linkType: hard
"@polymer/paper-icon-button@npm:^3.0.0-pre.26":
version: 3.0.2
resolution: "@polymer/paper-icon-button@npm:3.0.2"
dependencies:
"@polymer/iron-icon": "npm:^3.0.0-pre.26"
"@polymer/paper-behaviors": "npm:^3.0.0-pre.27"
"@polymer/paper-styles": "npm:^3.0.0-pre.26"
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/f6e763186538b9adcace8941e2b8405ad1bb9b34059073cf05131f43c1ab3af02b6d9e168a07a645a6186b690ef9e0c4283e4df2521bdb3ea99bc6d23bd14e4d
languageName: node
linkType: hard
"@polymer/paper-item@npm:3.0.1":
version: 3.0.1
resolution: "@polymer/paper-item@npm:3.0.1"
@ -3792,16 +3699,6 @@ __metadata:
languageName: node
linkType: hard
"@polymer/paper-ripple@npm:^3.0.0-pre.26":
version: 3.0.2
resolution: "@polymer/paper-ripple@npm:3.0.2"
dependencies:
"@polymer/iron-a11y-keys-behavior": "npm:^3.0.0-pre.26"
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/db2b8e237e175a7b02fa6bed35750f41d80c7885d4a938006272198e6326af85837e265af0e3ab53b38818c47270bb2b8b15a5355f9ce9dd4dc4106d78d64a53
languageName: node
linkType: hard
"@polymer/paper-styles@npm:^3.0.0-pre.26":
version: 3.0.1
resolution: "@polymer/paper-styles@npm:3.0.1"
@ -3813,24 +3710,6 @@ __metadata:
languageName: node
linkType: hard
"@polymer/paper-tabs@npm:3.1.0":
version: 3.1.0
resolution: "@polymer/paper-tabs@npm:3.1.0"
dependencies:
"@polymer/iron-behaviors": "npm:^3.0.0-pre.26"
"@polymer/iron-flex-layout": "npm:^3.0.0-pre.26"
"@polymer/iron-icon": "npm:^3.0.0-pre.26"
"@polymer/iron-iconset-svg": "npm:^3.0.0-pre.26"
"@polymer/iron-menu-behavior": "npm:^3.0.0-pre.26"
"@polymer/iron-resizable-behavior": "npm:^3.0.0-pre.26"
"@polymer/paper-behaviors": "npm:^3.0.0-pre.27"
"@polymer/paper-icon-button": "npm:^3.0.0-pre.26"
"@polymer/paper-styles": "npm:^3.0.0-pre.26"
"@polymer/polymer": "npm:^3.0.0"
checksum: 10/a12accadfd9a8029dbf48663625c79b0d3a83fff63f3b0e84bd7d4ce2b44c327f703bdbfad0f3f7fc7c84f6d895d788fcc9cdca49b0630d80fb0d7a1c9585499
languageName: node
linkType: hard
"@polymer/polymer@npm:3.5.2":
version: 3.5.2
resolution: "@polymer/polymer@npm:3.5.2"
@ -9637,7 +9516,6 @@ __metadata:
"@octokit/rest": "npm:21.1.1"
"@polymer/paper-item": "npm:3.0.1"
"@polymer/paper-listbox": "npm:3.0.1"
"@polymer/paper-tabs": "npm:3.1.0"
"@polymer/polymer": "npm:3.5.2"
"@replit/codemirror-indentation-markers": "npm:6.5.3"
"@rsdoctor/rspack-plugin": "npm:1.0.1"