Compare commits

..

9 Commits

Author SHA1 Message Date
Paul Bottein
586486ef1e Create sections in section view 2025-09-12 14:08:09 +02:00
Simon Lamon
dcbc8b627f Migrate ha-tooltip to webawesome (#26540)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2025-09-12 13:09:24 +02:00
Aidan Timson
0d8d18617c Create and implement goBack helper function (#27015)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2025-09-12 11:39:45 +02:00
Lukas Waslowski
7eb87c78cc fix: Pass hass object to <ha-form/> in <hassio-addon-config/> (#26995)
fix: Pass  object to <ha-form/> in <hassio-addon-config/>
2025-09-12 05:03:53 -04:00
Paul Bottein
0eaf9ead9e Move section edit logic to its own component (#27017) 2025-09-12 08:51:10 +00:00
Paul Bottein
7082646fe5 Only copy/cut/delete selected automation rows (#26966) 2025-09-12 10:46:23 +02:00
karwosts
96d364b3bd Full width ha-select dropdowns for z-wave (#27013) 2025-09-12 10:45:31 +02:00
Wendelin
e726eb7370 Fix automation sidebar overflow icon size (#27016) 2025-09-12 10:43:55 +02:00
renovate[bot]
e6f587da78 Update dependency typescript-eslint to v8.43.0 (#27010)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-11 19:43:02 +02:00
64 changed files with 1182 additions and 1498 deletions

View File

@@ -6,21 +6,23 @@ A tooltip's target is its _first child element_, so you should only wrap one ele
Tooltips use `display: contents` so they won't interfere with how elements are positioned in a flex or grid layout. Tooltips use `display: contents` so they won't interfere with how elements are positioned in a flex or grid layout.
<ha-tooltip content="This is a tooltip"> <ha-button id="hover">Hover Me</ha-button>
<ha-button>Hover Me</ha-button> <ha-tooltip for="hover">
This is a tooltip
</ha-tooltip> </ha-tooltip>
``` ```
<ha-tooltip content="This is a tooltip"> <ha-button id="hover">Hover Me</ha-button>
<ha-button>Hover Me</ha-button> <ha-tooltip for="hover">
This is a tooltip
</ha-tooltip> </ha-tooltip>
``` ```
## Documentation ## Documentation
This element is based on shoelace `sl-tooltip` it only sets some css tokens and has a custom show/hide animation. This element is based on webawesome `wa-tooltip` it only sets some css tokens and has a custom show/hide animation.
<a href="https://shoelace.style/components/tooltip" target="_blank" rel="noopener noreferrer">Shoelace documentation</a> <a href="https://webawesome.com/docs/components/tooltip/" target="_blank" rel="noopener noreferrer">Webawesome documentation</a>
### HA style tokens ### HA style tokens
@@ -28,7 +30,7 @@ In your theme settings use this without the prefixed `--`.
- `--ha-tooltip-border-radius` (Default: 4px) - `--ha-tooltip-border-radius` (Default: 4px)
- `--ha-tooltip-arrow-size` (Default: 8px) - `--ha-tooltip-arrow-size` (Default: 8px)
- `--sl-tooltip-font-family` (Default: `var(--ha-font-family-body)`) - `--wa-tooltip-font-family` (Default: `var(--ha-font-family-body)`)
- `--ha-tooltip-font-size` (Default: `var(--ha-font-size-s)`) - `--ha-tooltip-font-size` (Default: `var(--ha-font-size-s)`)
- `--sl-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`) - `--wa-tooltip-font-weight` (Default: `var(--ha-font-weight-normal)`)
- `--sl-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`) - `--wa-tooltip-line-height` (Default: `var(--ha-line-height-condensed)`)

View File

@@ -199,6 +199,7 @@ class HassioAddonConfig extends LitElement {
<div class="card-content"> <div class="card-content">
${showForm ${showForm
? html`<ha-form ? html`<ha-form
.hass=${this.hass}
.disabled=${this.disabled} .disabled=${this.disabled}
.data=${this._options!} .data=${this._options!}
@value-changed=${this._configChanged} @value-changed=${this._configChanged}

View File

@@ -119,26 +119,27 @@ class HassioRepositoriesDialog extends LitElement {
<div>${repo.url}</div> <div>${repo.url}</div>
</div> </div>
<ha-tooltip <ha-tooltip
.for="icon-button-${repo.slug}"
class="delete" class="delete"
slot="end" slot="end"
.content=${this._dialogParams!.supervisor.localize( >
${this._dialogParams!.supervisor.localize(
usedRepositories.includes(repo.slug) usedRepositories.includes(repo.slug)
? "dialog.repositories.used" ? "dialog.repositories.used"
: "dialog.repositories.remove" : "dialog.repositories.remove"
)} )}
>
<div>
<ha-icon-button
.disabled=${usedRepositories.includes(repo.slug)}
.slug=${repo.slug}
.path=${usedRepositories.includes(repo.slug)
? mdiDeleteOff
: mdiDelete}
@click=${this._removeRepository}
>
</ha-icon-button>
</div>
</ha-tooltip> </ha-tooltip>
<div .id="icon-button-${repo.slug}">
<ha-icon-button
.disabled=${usedRepositories.includes(repo.slug)}
.slug=${repo.slug}
.path=${usedRepositories.includes(repo.slug)
? mdiDeleteOff
: mdiDelete}
@click=${this._removeRepository}
>
</ha-icon-button>
</div>
</ha-md-list-item> </ha-md-list-item>
` `
) )

View File

@@ -3,7 +3,7 @@ import type { PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import { navigate } from "../../../src/common/navigate"; import { goBack, navigate } from "../../../src/common/navigate";
import { extractSearchParam } from "../../../src/common/url/search-params"; import { extractSearchParam } from "../../../src/common/url/search-params";
import { nextRender } from "../../../src/common/util/render-status"; import { nextRender } from "../../../src/common/util/render-status";
import "../../../src/components/ha-icon-button"; import "../../../src/components/ha-icon-button";
@@ -193,7 +193,7 @@ class HassioIngressView extends LitElement {
title: addon.name, title: addon.name,
}); });
await nextRender(); await nextRender();
history.back(); goBack();
return; return;
} }
@@ -275,7 +275,7 @@ class HassioIngressView extends LitElement {
title: addon.name, title: addon.name,
}); });
await nextRender(); await nextRender();
history.back(); goBack();
return; return;
} }

View File

@@ -2,6 +2,7 @@ import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import type { Supervisor } from "../../../src/data/supervisor/supervisor"; import type { Supervisor } from "../../../src/data/supervisor/supervisor";
import { goBack } from "../../../src/common/navigate";
import "../../../src/layouts/hass-subpage"; import "../../../src/layouts/hass-subpage";
import type { HomeAssistant, Route } from "../../../src/types"; import type { HomeAssistant, Route } from "../../../src/types";
import "./update-available-card"; import "./update-available-card";
@@ -35,7 +36,7 @@ class UpdateAvailableDashboard extends LitElement {
} }
private _updateComplete() { private _updateComplete() {
history.back(); goBack();
} }
static styles = css` static styles = css`

View File

@@ -51,7 +51,7 @@
"@fullcalendar/list": "6.1.19", "@fullcalendar/list": "6.1.19",
"@fullcalendar/luxon3": "6.1.19", "@fullcalendar/luxon3": "6.1.19",
"@fullcalendar/timegrid": "6.1.19", "@fullcalendar/timegrid": "6.1.19",
"@home-assistant/webawesome": "3.0.0-beta.4.ha.2", "@home-assistant/webawesome": "3.0.0-beta.4.ha.3",
"@lezer/highlight": "1.2.1", "@lezer/highlight": "1.2.1",
"@lit-labs/motion": "1.0.9", "@lit-labs/motion": "1.0.9",
"@lit-labs/observers": "2.0.6", "@lit-labs/observers": "2.0.6",
@@ -84,7 +84,6 @@
"@mdi/js": "7.4.47", "@mdi/js": "7.4.47",
"@mdi/svg": "7.4.47", "@mdi/svg": "7.4.47",
"@replit/codemirror-indentation-markers": "6.5.3", "@replit/codemirror-indentation-markers": "6.5.3",
"@shoelace-style/shoelace": "2.20.1",
"@swc/helpers": "0.5.17", "@swc/helpers": "0.5.17",
"@thomasloven/round-slider": "0.6.0", "@thomasloven/round-slider": "0.6.0",
"@tsparticles/engine": "3.9.1", "@tsparticles/engine": "3.9.1",
@@ -218,7 +217,7 @@
"terser-webpack-plugin": "5.3.14", "terser-webpack-plugin": "5.3.14",
"ts-lit-plugin": "2.0.2", "ts-lit-plugin": "2.0.2",
"typescript": "5.9.2", "typescript": "5.9.2",
"typescript-eslint": "8.42.0", "typescript-eslint": "8.43.0",
"vite-tsconfig-paths": "5.1.4", "vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.4", "vitest": "3.2.4",
"webpack-stats-plugin": "1.1.3", "webpack-stats-plugin": "1.1.3",

View File

@@ -63,3 +63,21 @@ export const navigate = async (
}); });
return true; return true;
}; };
/**
* Navigate back in history, with fallback to a default path if no history exists.
* This prevents a user from getting stuck when they navigate directly to a page with no history.
*/
export const goBack = (fallbackPath?: string) => {
const { history } = mainWindow;
// Check if we have history to go back to
if (history.length > 1) {
history.back();
return;
}
// No history available, navigate to fallback path
const fallback = fallbackPath || "/";
navigate(fallback, { replace: true });
};

View File

@@ -12,9 +12,8 @@ class HaDataTableIcon extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-tooltip .content=${this.tooltip}> <ha-tooltip for="svg-icon">${this.tooltip}</ha-tooltip>
<ha-svg-icon .path=${this.path}></ha-svg-icon> <ha-svg-icon id="svg-icon" .path=${this.path}></ha-svg-icon>
</ha-tooltip>
`; `;
} }

View File

@@ -36,39 +36,38 @@ class StateInfo extends LitElement {
</div> </div>
${this.inDialog ${this.inDialog
? html`<div class="time-ago"> ? html`<div class="time-ago">
<ha-tooltip> <ha-tooltip for="relative-time">
<ha-relative-time <div class="row">
.hass=${this.hass} <span class="column-name">
.datetime=${this.stateObj.last_changed} ${this.hass.localize(
capitalize "ui.dialogs.more_info_control.last_changed"
></ha-relative-time> )}:
<div slot="content"> </span>
<div class="row"> <ha-relative-time
<span class="column-name"> .hass=${this.hass}
${this.hass.localize( .datetime=${this.stateObj.last_changed}
"ui.dialogs.more_info_control.last_changed" capitalize
)}: ></ha-relative-time>
</span> </div>
<ha-relative-time <div class="row">
.hass=${this.hass} <span>
.datetime=${this.stateObj.last_changed} ${this.hass.localize(
capitalize "ui.dialogs.more_info_control.last_updated"
></ha-relative-time> )}:
</div> </span>
<div class="row"> <ha-relative-time
<span> .hass=${this.hass}
${this.hass.localize( .datetime=${this.stateObj.last_updated}
"ui.dialogs.more_info_control.last_updated" capitalize
)}: ></ha-relative-time>
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_updated}
capitalize
></ha-relative-time>
</div>
</div> </div>
</ha-tooltip> </ha-tooltip>
<ha-relative-time
id="relative-time"
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
capitalize
></ha-relative-time>
</div>` </div>`
: html`<div class="extra-info"><slot></slot></div>`} : html`<div class="extra-info"><slot></slot></div>`}
</div>`; </div>`;

View File

@@ -67,20 +67,19 @@ export class HaAnalytics extends LitElement {
)} )}
</span> </span>
<span> <span>
<ha-tooltip <ha-switch
content=${this.localize( .id="switch-${preference}"
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled` @change=${this._handleRowClick}
)} .checked=${this.analytics?.preferences[preference]}
placement="right" .preference=${preference}
name=${preference}
?disabled=${baseEnabled} ?disabled=${baseEnabled}
> >
<ha-switch </ha-switch>
@change=${this._handleRowClick} <ha-tooltip .for="switch-${preference}" placement="right">
.checked=${this.analytics?.preferences[preference]} ${this.localize(
.preference=${preference} `ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
name=${preference} )}
>
</ha-switch>
</ha-tooltip> </ha-tooltip>
</span> </span>
</ha-settings-row> </ha-settings-row>

View File

@@ -83,15 +83,6 @@ export class HaAutomationRow extends LitElement {
!(ev.ctrlKey || ev.metaKey) && !(ev.ctrlKey || ev.metaKey) &&
!ev.shiftKey && !ev.shiftKey &&
(ev.key === "ArrowUp" || ev.key === "ArrowDown") (ev.key === "ArrowUp" || ev.key === "ArrowDown")
) &&
!(
(ev.ctrlKey || ev.metaKey) &&
!ev.shiftKey &&
!ev.altKey &&
(ev.key === "c" ||
ev.key === "x" ||
ev.key === "Delete" ||
ev.key === "Backspace")
) )
) { ) {
return; return;
@@ -112,22 +103,6 @@ export class HaAutomationRow extends LitElement {
return; return;
} }
if (ev.ctrlKey || ev.metaKey) {
if (ev.key === "c") {
fireEvent(this, "copy-row");
return;
}
if (ev.key === "x") {
fireEvent(this, "cut-row");
return;
}
if (ev.key === "Delete" || ev.key === "Backspace") {
fireEvent(this, "delete-row");
return;
}
}
this.click(); this.click();
} }

View File

@@ -49,7 +49,6 @@ export class HaExpansionPanel extends LitElement {
tabindex=${this.noCollapse ? -1 : 0} tabindex=${this.noCollapse ? -1 : 0}
aria-expanded=${this.expanded} aria-expanded=${this.expanded}
aria-controls="sect1" aria-controls="sect1"
part="summary"
> >
${this.leftChevron ? chevronIcon : nothing} ${this.leftChevron ? chevronIcon : nothing}
<slot name="leading-icon"></slot> <slot name="leading-icon"></slot>

View File

@@ -25,8 +25,9 @@ export class HaHelpTooltip extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-tooltip .placement=${this.position} .content=${this.label}> <ha-svg-icon id="svg-icon" .path=${mdiHelpCircle}></ha-svg-icon>
<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon> <ha-tooltip for="svg-icon" .placement=${this.position}>
${this.label}
</ha-tooltip> </ha-tooltip>
`; `;
} }

View File

@@ -74,16 +74,16 @@ export class HaIconOverflowMenu extends LitElement {
: item.divider : item.divider
? html`<div role="separator"></div>` ? html`<div role="separator"></div>`
: html`<ha-tooltip : html`<ha-tooltip
.disabled=${!item.tooltip} .disabled=${!item.tooltip}
.content=${item.tooltip ?? ""} .for="icon-button-${item.label}"
> >${item.tooltip ?? ""} </ha-tooltip
<ha-icon-button ><ha-icon-button
.id="icon-button-${item.label}"
@click=${item.action} @click=${item.action}
.label=${item.label} .label=${item.label}
.path=${item.path} .path=${item.path}
?disabled=${item.disabled} ?disabled=${item.disabled}
></ha-icon-button> ></ha-icon-button> `
</ha-tooltip>`
)} )}
`} `}
`; `;

View File

@@ -1,16 +1,39 @@
// @ts-ignore // @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css"; import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import "@material/mwc-menu/mwc-menu-surface"; import "@material/mwc-menu/mwc-menu-surface";
import { mdiPlus } from "@mdi/js"; import {
mdiClose,
mdiDevices,
mdiHome,
mdiLabel,
mdiPlus,
mdiTextureBox,
mdiUnfoldMoreVertical,
} from "@mdi/js";
import type { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light"; import type { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
import type { HassServiceTarget } from "home-assistant-js-websocket"; import type {
HassEntity,
HassServiceTarget,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit"; import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing, unsafeCSS } from "lit"; import { LitElement, css, html, nothing, unsafeCSS } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ensureArray } from "../common/array/ensure-array"; import { ensureArray } from "../common/array/ensure-array";
import { computeCssColor } from "../common/color/compute-color";
import { hex2rgb } from "../common/color/convert-color";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation"; import { stopPropagation } from "../common/dom/stop_propagation";
import { computeDeviceNameDisplay } from "../common/entity/compute_device_name";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateName } from "../common/entity/compute_state_name";
import { isValidEntityId } from "../common/entity/valid_entity_id"; import { isValidEntityId } from "../common/entity/valid_entity_id";
import type { AreaRegistryEntry } from "../data/area_registry";
import type { DeviceRegistryEntry } from "../data/device_registry";
import type { EntityRegistryDisplayEntry } from "../data/entity_registry";
import type { LabelRegistryEntry } from "../data/label_registry";
import { subscribeLabelRegistry } from "../data/label_registry";
import { SubscribeMixin } from "../mixins/subscribe-mixin"; import { SubscribeMixin } from "../mixins/subscribe-mixin";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
import "./device/ha-device-picker"; import "./device/ha-device-picker";
@@ -18,13 +41,12 @@ import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import "./entity/ha-entity-picker"; import "./entity/ha-entity-picker";
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker"; import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
import "./ha-area-floor-picker"; import "./ha-area-floor-picker";
import { floorDefaultIconPath } from "./ha-floor-icon";
import "./ha-icon-button"; import "./ha-icon-button";
import "./ha-input-helper-text"; import "./ha-input-helper-text";
import "./ha-label-picker"; import "./ha-label-picker";
import "./ha-svg-icon"; import "./ha-svg-icon";
import "./ha-tooltip"; import "./ha-tooltip";
import "./target-picker/ha-target-picker-item-group";
import type { TargetType } from "./target-picker/ha-target-picker-item-row";
@customElement("ha-target-picker") @customElement("ha-target-picker")
export class HaTargetPicker extends SubscribeMixin(LitElement) { export class HaTargetPicker extends SubscribeMixin(LitElement) {
@@ -36,8 +58,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@property() public helper?: string; @property() public helper?: string;
@property({ type: Boolean }) public compact = false;
@property({ attribute: false, type: Array }) public createDomains?: string[]; @property({ attribute: false, type: Array }) public createDomains?: string[];
/** /**
@@ -76,8 +96,18 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@query(".add-container", true) private _addContainer?: HTMLDivElement; @query(".add-container", true) private _addContainer?: HTMLDivElement;
@state() private _labels?: LabelRegistryEntry[];
private _opened = false; private _opened = false;
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeLabelRegistry(this.hass.connection, (labels) => {
this._labels = labels;
}),
];
}
protected render() { protected render() {
if (this.addOnTop) { if (this.addOnTop) {
return html` ${this._renderChips()} ${this._renderItems()} `; return html` ${this._renderChips()} ${this._renderItems()} `;
@@ -86,68 +116,87 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
} }
private _renderItems() { private _renderItems() {
if (
!this.value?.floor_id &&
!this.value?.area_id &&
!this.value?.device_id &&
!this.value?.entity_id &&
!this.value?.label_id
) {
return nothing;
}
return html` return html`
<div class="item-groups"> <div class="mdc-chip-set items">
${this.value?.floor_id || this.value?.area_id ${this.value?.floor_id
? html` ? ensureArray(this.value.floor_id).map((floor_id) => {
<ha-target-picker-item-group const floor = this.hass.floors[floor_id];
@remove-target-item=${this._handleRemove} return this._renderChip(
type="area" "floor_id",
.hass=${this.hass} floor_id,
.items=${{ floor?.name || floor_id,
floor: ensureArray(this.value?.floor_id), undefined,
area: ensureArray(this.value?.area_id), floor?.icon,
}} floor ? floorDefaultIconPath(floor) : mdiHome
.collapsed=${this.compact} );
> })
</ha-target-picker-item-group> : ""}
` ${this.value?.area_id
? ensureArray(this.value.area_id).map((area_id) => {
const area = this.hass.areas![area_id];
return this._renderChip(
"area_id",
area_id,
area?.name || area_id,
undefined,
area?.icon,
mdiTextureBox
);
})
: nothing} : nothing}
${this.value?.device_id ${this.value?.device_id
? html` ? ensureArray(this.value.device_id).map((device_id) => {
<ha-target-picker-item-group const device = this.hass.devices![device_id];
@remove-target-item=${this._handleRemove} return this._renderChip(
type="device" "device_id",
.hass=${this.hass} device_id,
.items=${{ device: ensureArray(this.value?.device_id) }} device
.collapsed=${this.compact} ? computeDeviceNameDisplay(device, this.hass)
> : device_id,
</ha-target-picker-item-group> undefined,
` undefined,
mdiDevices
);
})
: nothing} : nothing}
${this.value?.entity_id ${this.value?.entity_id
? html` ? ensureArray(this.value.entity_id).map((entity_id) => {
<ha-target-picker-item-group const entity = this.hass.states[entity_id];
@remove-target-item=${this._handleRemove} return this._renderChip(
type="entity" "entity_id",
.hass=${this.hass} entity_id,
.items=${{ entity: ensureArray(this.value?.entity_id) }} entity ? computeStateName(entity) : entity_id,
.collapsed=${this.compact} entity
> );
</ha-target-picker-item-group> })
`
: nothing} : nothing}
${this.value?.label_id ${this.value?.label_id
? html` ? ensureArray(this.value.label_id).map((label_id) => {
<ha-target-picker-item-group const label = this._labels?.find(
@remove-target-item=${this._handleRemove} (lbl) => lbl.label_id === label_id
type="label" );
.hass=${this.hass} let color = label?.color
.items=${{ label: ensureArray(this.value?.label_id) }} ? computeCssColor(label.color)
.collapsed=${this.compact} : undefined;
> if (color?.startsWith("var(")) {
</ha-target-picker-item-group> const computedStyles = getComputedStyle(this);
` color = computedStyles.getPropertyValue(
color.substring(4, color.length - 1)
);
}
if (color?.startsWith("#")) {
color = hex2rgb(color).join(",");
}
return this._renderChip(
"label_id",
label_id,
label ? label.name : label_id,
undefined,
label?.icon,
mdiLabel,
color
);
})
: nothing} : nothing}
</div> </div>
`; `;
@@ -250,6 +299,85 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
this._addMode = ev.currentTarget.type; this._addMode = ev.currentTarget.type;
} }
private _renderChip(
type: "floor_id" | "area_id" | "device_id" | "entity_id" | "label_id",
id: string,
name: string,
entityState?: HassEntity,
icon?: string | null,
fallbackIconPath?: string,
color?: string
) {
return html`
<div
class="mdc-chip ${classMap({
[type]: true,
})}"
style=${color
? `--color: rgb(${color}); --background-color: rgba(${color}, .5)`
: ""}
>
${icon
? html`<ha-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.icon=${icon}
></ha-icon>`
: fallbackIconPath
? html`<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${fallbackIconPath}
></ha-svg-icon>`
: ""}
${entityState
? html`<ha-state-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.hass=${this.hass}
.stateObj=${entityState}
></ha-state-icon>`
: ""}
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text">${name}</span>
</span>
</span>
${type === "entity_id"
? ""
: html`<span role="gridcell">
<ha-tooltip .for="expand-${id}"
>${this.hass.localize(
`ui.components.target-picker.expand_${type}`
)}
</ha-tooltip>
<ha-icon-button
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
.label=${this.hass.localize(
"ui.components.target-picker.expand"
)}
.path=${mdiUnfoldMoreVertical}
hide-title
.id="expand-${id}"
.type=${type}
@click=${this._handleExpand}
></ha-icon-button>
</span>`}
<span role="gridcell">
<ha-tooltip .for="remove-${id}">
${this.hass.localize(`ui.components.target-picker.remove_${type}`)}
</ha-tooltip>
<ha-icon-button
class="mdc-chip__icon mdc-chip__icon--trailing"
.label=${this.hass.localize("ui.components.target-picker.remove")}
.path=${mdiClose}
hide-title
.id="remove-${id}"
.type=${type}
@click=${this._handleRemove}
></ha-icon-button>
</span>
</div>
`;
}
private _renderPicker() { private _renderPicker() {
if (!this._addMode) { if (!this._addMode) {
return nothing; return nothing;
@@ -392,31 +520,130 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
}); });
} }
private _handleExpand(ev) {
const target = ev.currentTarget as any;
const newAreas: string[] = [];
const newDevices: string[] = [];
const newEntities: string[] = [];
if (target.type === "floor_id") {
Object.values(this.hass.areas).forEach((area) => {
if (
area.floor_id === target.id &&
!this.value!.area_id?.includes(area.area_id) &&
this._areaMeetsFilter(area)
) {
newAreas.push(area.area_id);
}
});
} else if (target.type === "area_id") {
Object.values(this.hass.devices).forEach((device) => {
if (
device.area_id === target.id &&
!this.value!.device_id?.includes(device.id) &&
this._deviceMeetsFilter(device)
) {
newDevices.push(device.id);
}
});
Object.values(this.hass.entities).forEach((entity) => {
if (
entity.area_id === target.id &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else if (target.type === "device_id") {
Object.values(this.hass.entities).forEach((entity) => {
if (
entity.device_id === target.id &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else if (target.type === "label_id") {
Object.values(this.hass.areas).forEach((area) => {
if (
area.labels.includes(target.id) &&
!this.value!.area_id?.includes(area.area_id) &&
this._areaMeetsFilter(area)
) {
newAreas.push(area.area_id);
}
});
Object.values(this.hass.devices).forEach((device) => {
if (
device.labels.includes(target.id) &&
!this.value!.device_id?.includes(device.id) &&
this._deviceMeetsFilter(device)
) {
newDevices.push(device.id);
}
});
Object.values(this.hass.entities).forEach((entity) => {
if (
entity.labels.includes(target.id) &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity, true)
) {
newEntities.push(entity.entity_id);
}
});
} else {
return;
}
let value = this.value;
if (newEntities.length) {
value = this._addItems(value, "entity_id", newEntities);
}
if (newDevices.length) {
value = this._addItems(value, "device_id", newDevices);
}
if (newAreas.length) {
value = this._addItems(value, "area_id", newAreas);
}
value = this._removeItem(value, target.type, target.id);
fireEvent(this, "value-changed", { value });
}
private _handleRemove(ev) { private _handleRemove(ev) {
const { type, id } = ev.detail; const target = ev.currentTarget as any;
fireEvent(this, "value-changed", { fireEvent(this, "value-changed", {
value: this._removeItem(this.value, type, id), value: this._removeItem(this.value, target.type, target.id),
}); });
} }
private _addItems(
value: this["value"],
type: string,
ids: string[]
): this["value"] {
return {
...value,
[type]: value![type] ? ensureArray(value![type])!.concat(ids) : ids,
};
}
private _removeItem( private _removeItem(
value: this["value"], value: this["value"],
type: TargetType, type: string,
id: string id: string
): this["value"] { ): this["value"] {
const typeId = `${type}_id`; const newVal = ensureArray(value![type])!.filter(
const newVal = ensureArray(value![typeId])!.filter(
(val) => String(val) !== id (val) => String(val) !== id
); );
if (newVal.length) { if (newVal.length) {
return { return {
...value, ...value,
[typeId]: newVal, [type]: newVal,
}; };
} }
const val = { ...value }!; const val = { ...value }!;
delete val[typeId]; delete val[type];
if (Object.keys(val).length) { if (Object.keys(val).length) {
return val; return val;
} }
@@ -448,6 +675,83 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
ev.preventDefault(); ev.preventDefault();
} }
private _areaMeetsFilter(area: AreaRegistryEntry): boolean {
const areaDevices = Object.values(this.hass.devices).filter(
(device) => device.area_id === area.area_id
);
if (areaDevices.some((device) => this._deviceMeetsFilter(device))) {
return true;
}
const areaEntities = Object.values(this.hass.entities).filter(
(entity) => entity.area_id === area.area_id
);
if (areaEntities.some((entity) => this._entityRegMeetsFilter(entity))) {
return true;
}
return false;
}
private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean {
const devEntities = Object.values(this.hass.entities).filter(
(entity) => entity.device_id === device.id
);
if (!devEntities.some((entity) => this._entityRegMeetsFilter(entity))) {
return false;
}
if (this.deviceFilter) {
if (!this.deviceFilter(device)) {
return false;
}
}
return true;
}
private _entityRegMeetsFilter(
entity: EntityRegistryDisplayEntry,
includeSecondary = false
): boolean {
if (entity.hidden || (entity.entity_category && !includeSecondary)) {
return false;
}
if (
this.includeDomains &&
!this.includeDomains.includes(computeDomain(entity.entity_id))
) {
return false;
}
if (this.includeDeviceClasses) {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
if (
!stateObj.attributes.device_class ||
!this.includeDeviceClasses!.includes(stateObj.attributes.device_class)
) {
return false;
}
}
if (this.entityFilter) {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
if (!this.entityFilter!(stateObj)) {
return false;
}
}
return true;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
${unsafeCSS(chipStyles)} ${unsafeCSS(chipStyles)}
@@ -506,6 +810,41 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
margin-inline-end: 0; margin-inline-end: 0;
margin-inline-start: initial; margin-inline-start: initial;
} }
.mdc-chip.area_id:not(.add),
.mdc-chip.floor_id:not(.add) {
border: 1px solid #fed6a4;
background: var(--card-background-color);
}
.mdc-chip.area_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.area_id.add,
.mdc-chip.floor_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.floor_id.add {
background: #fed6a4;
}
.mdc-chip.device_id:not(.add) {
border: 1px solid #a8e1fb;
background: var(--card-background-color);
}
.mdc-chip.device_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.device_id.add {
background: #a8e1fb;
}
.mdc-chip.entity_id:not(.add) {
border: 1px solid #d2e7b9;
background: var(--card-background-color);
}
.mdc-chip.entity_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.entity_id.add {
background: #d2e7b9;
}
.mdc-chip.label_id:not(.add) {
border: 1px solid var(--color, #e0e0e0);
background: var(--card-background-color);
}
.mdc-chip.label_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.label_id.add {
background: var(--background-color, #e0e0e0);
}
.mdc-chip:hover { .mdc-chip:hover {
z-index: 5; z-index: 5;
} }
@@ -525,12 +864,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
ha-tooltip { ha-tooltip {
--ha-tooltip-arrow-size: 0; --ha-tooltip-arrow-size: 0;
} }
.item-groups {
overflow: hidden;
border: 2px solid var(--divider-color);
border-radius: var(--ha-border-radius-lg);
}
`; `;
} }
} }
@@ -539,12 +872,4 @@ declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"ha-target-picker": HaTargetPicker; "ha-target-picker": HaTargetPicker;
} }
interface HASSDomEvents {
"remove-target-item": {
type: string;
id: string;
};
"remove-target-group": string;
}
} }

View File

@@ -1,50 +1,47 @@
import SlTooltip from "@shoelace-style/shoelace/dist/components/tooltip/tooltip.component"; import Tooltip from "@home-assistant/webawesome/dist/components/tooltip/tooltip";
import styles from "@shoelace-style/shoelace/dist/components/tooltip/tooltip.styles";
import { css } from "lit"; import { css } from "lit";
import { customElement } from "lit/decorators"; import type { CSSResultGroup } from "lit";
import { setDefaultAnimation } from "@shoelace-style/shoelace/dist/utilities/animation-registry"; import { customElement, property } from "lit/decorators";
setDefaultAnimation("tooltip.show", {
keyframes: [{ opacity: 0 }, { opacity: 1 }],
options: { duration: 150, easing: "ease" },
});
setDefaultAnimation("tooltip.hide", {
keyframes: [{ opacity: 1 }, { opacity: 0 }],
options: { duration: 400, easing: "ease" },
});
@customElement("ha-tooltip") @customElement("ha-tooltip")
export class HaTooltip extends SlTooltip { export class HaTooltip extends Tooltip {
static override styles = [ /** The amount of time to wait before showing the tooltip when the user mouses in. */
styles, @property({ attribute: "show-delay", type: Number }) showDelay = 150;
css`
:host { /** The amount of time to wait before hiding the tooltip when the user mouses out.. */
--sl-tooltip-background-color: var(--secondary-background-color); @property({ attribute: "hide-delay", type: Number }) hideDelay = 400;
--sl-tooltip-color: var(--primary-text-color);
--sl-tooltip-font-family: var( static get styles(): CSSResultGroup {
--ha-tooltip-font-family, return [
var(--ha-font-family-body) Tooltip.styles,
); css`
--sl-tooltip-font-size: var( :host {
--ha-tooltip-font-size, --wa-tooltip-background-color: var(--secondary-background-color);
var(--ha-font-size-s) --wa-tooltip-color: var(--primary-text-color);
); --wa-tooltip-font-family: var(
--sl-tooltip-font-weight: var( --ha-tooltip-font-family,
--ha-tooltip-font-weight, var(--ha-font-family-body)
var(--ha-font-weight-normal) );
); --wa-tooltip-font-size: var(
--sl-tooltip-line-height: var( --ha-tooltip-font-size,
--ha-tooltip-line-height, var(--ha-font-size-s)
var(--ha-line-height-condensed) );
); --wa-tooltip-font-weight: var(
--sl-tooltip-padding: 8px; --ha-tooltip-font-weight,
--sl-tooltip-border-radius: var(--ha-tooltip-border-radius, 4px); var(--ha-font-weight-normal)
--sl-tooltip-arrow-size: var(--ha-tooltip-arrow-size, 8px); );
--sl-z-index-tooltip: var(--ha-tooltip-z-index, 1000); --wa-tooltip-line-height: var(
} --ha-tooltip-line-height,
`, var(--ha-line-height-condensed)
]; );
--wa-tooltip-padding: 8px;
--wa-tooltip-border-radius: var(--ha-tooltip-border-radius, 4px);
--wa-tooltip-arrow-size: var(--ha-tooltip-arrow-size, 8px);
--wa-z-index-tooltip: var(--ha-tooltip-z-index, 1000);
}
`,
];
}
} }
declare global { declare global {

View File

@@ -642,9 +642,10 @@ export class HaMediaPlayerBrowse extends LitElement {
` `
: ""} : ""}
</div> </div>
<ha-tooltip distance="-4" .content=${child.title}> <ha-tooltip .for="grid-${child.title}" distance="-4">
<div class="title">${child.title}</div> ${child.title}
</ha-tooltip> </ha-tooltip>
<div .id="grid-${child.title}" class="title">${child.title}</div>
</ha-card> </ha-card>
</div> </div>
`; `;

View File

@@ -1,78 +0,0 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import type { HomeAssistant } from "../../types";
import "../ha-expansion-panel";
import "../ha-md-list";
import "./ha-target-picker-item-row";
@customElement("ha-target-picker-item-group")
export class HaTargetPickerItemGroup extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public type!: "entity" | "device" | "area" | "label";
@property({ attribute: false }) public items!: Partial<
Record<"entity" | "device" | "area" | "label" | "floor", string[]>
>;
@property({ type: Boolean }) public collapsed = false;
protected render() {
let count = 0;
Object.values(this.items).forEach((items) => {
if (items) {
count += items.length;
}
});
return html`<ha-expansion-panel .expanded=${!this.collapsed} left-chevron>
<div slot="header" class="heading">
${this.hass.localize(
`ui.components.target-picker.selected.${this.type}`,
{
count,
}
)}
</div>
<ha-md-list>
${Object.entries(this.items).map(([type, items]) =>
items
? items.map(
(item) =>
html`<ha-target-picker-item-row
.hass=${this.hass}
.type=${type as "entity" | "device" | "area" | "label"}
.itemId=${item}
></ha-target-picker-item-row>`
)
: nothing
)}
</ha-md-list>
</ha-expansion-panel>`;
}
static styles = css`
:host {
display: block;
--expansion-panel-content-padding: 0;
}
ha-expansion-panel::part(summary) {
background-color: var(--ha-color-fill-neutral-quiet-resting);
padding: 4px 8px;
font-weight: var(--ha-font-weight-bold);
color: var(--secondary-text-color);
display: flex;
justify-content: space-between;
min-height: unset;
}
ha-md-list {
padding: 0;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-target-picker-item-group": HaTargetPickerItemGroup;
}
}

View File

@@ -1,478 +0,0 @@
import { consume } from "@lit/context";
import {
mdiChevronDown,
mdiClose,
mdiDevices,
mdiHome,
mdiLabel,
mdiTextureBox,
} from "@mdi/js";
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import { computeAreaName } from "../../common/entity/compute_area_name";
import {
computeDeviceName,
computeDeviceNameDisplay,
} from "../../common/entity/compute_device_name";
import { computeDomain } from "../../common/entity/compute_domain";
import { computeEntityName } from "../../common/entity/compute_entity_name";
import { getEntityContext } from "../../common/entity/context/get_entity_context";
import { computeRTL } from "../../common/util/compute_rtl";
import { getConfigEntry } from "../../data/config_entries";
import { labelsContext } from "../../data/context";
import { domainToName } from "../../data/integration";
import type { LabelRegistryEntry } from "../../data/label_registry";
import {
extractFromTarget,
type ExtractFromTargetResult,
} from "../../data/target";
import type { HomeAssistant } from "../../types";
import { brandsUrl } from "../../util/brands-url";
import { floorDefaultIconPath } from "../ha-floor-icon";
import "../ha-icon-button";
import "../ha-md-list";
import type { HaMdList } from "../ha-md-list";
import "../ha-md-list-item";
import type { HaMdListItem } from "../ha-md-list-item";
import "../ha-state-icon";
import "../ha-svg-icon";
export type TargetType = "entity" | "device" | "area" | "label" | "floor";
@customElement("ha-target-picker-item-row")
export class HaTargetPickerItemRow extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ reflect: true }) public type!: TargetType;
@property({ attribute: "item-id" }) public itemId!: string;
@property({ type: Boolean, attribute: "sub-entry", reflect: true })
public subEntry = false;
@property({ attribute: false })
public parentEntries?: ExtractFromTargetResult;
@state() private _expanded = false;
@state() private _iconImg?: string;
@state() private _domainName?: string;
@state() private _entries?: ExtractFromTargetResult;
@state()
@consume({ context: labelsContext, subscribe: true })
_labelRegistry!: LabelRegistryEntry[];
@query("ha-md-list-item") public item?: HaMdListItem;
@query("ha-md-list") public list?: HaMdList;
@query("ha-target-picker-item-row") public itemRow?: HaTargetPickerItemRow;
protected willUpdate(changedProps: PropertyValues) {
if (!this.subEntry && changedProps.has("itemId")) {
this._updateItemData();
this._expanded = false;
}
}
protected render() {
const { name, context, iconPath, fallbackIconPath, stateObject } =
this._itemData(this.type, this.itemId);
const showDevices = ["floor", "area", "label"].includes(this.type);
const showEntities = this.type !== "entity";
const entries = this.parentEntries || this._entries;
// Don't show sub entries that have no entities
if (
this.subEntry &&
this.type !== "entity" &&
(!entries || entries.referenced_entities.length === 0)
) {
return nothing;
}
return html`
<ha-md-list-item
.disabled=${entries?.referenced_entities.length === 0}
.type=${this.type === "entity" ? "text" : "button"}
@click=${this._toggleExpand}
>
${this.type !== "entity"
? html`<ha-svg-icon
class="expand-button ${entries?.referenced_entities.length &&
this._expanded
? "expanded"
: ""}"
.path=${mdiChevronDown}
slot="start"
></ha-svg-icon>`
: nothing}
${iconPath
? html`<ha-icon slot="start" .icon=${iconPath}></ha-icon>`
: this._iconImg
? html`<img
slot="start"
alt=${this._domainName || ""}
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${this._iconImg}
/>`
: fallbackIconPath
? html`<ha-svg-icon
slot="start"
.path=${fallbackIconPath}
></ha-svg-icon>`
: stateObject
? html`
<ha-state-icon
.hass=${this.hass}
.stateObj=${stateObject}
slot="start"
>
</ha-state-icon>
`
: nothing}
<div slot="headline">${name}</div>
${context && !this.subEntry
? html`<span slot="supporting-text">${context}</span>`
: nothing}
${showEntities || showDevices || this._domainName
? html`
<div slot="end" class="summary">
${showEntities
? html`<span class="main"
>${this.hass.localize(
"ui.components.target-picker.entities_count",
{
count: entries?.referenced_entities.length,
}
)}</span
>`
: nothing}
${showDevices
? html`<span class="secondary"
>${this.hass.localize(
"ui.components.target-picker.devices_count",
{
count: entries?.referenced_devices.length,
}
)}</span
>`
: nothing}
${this._domainName && !showDevices
? html`<span class="secondary domain"
>${this._domainName}</span
>`
: nothing}
</div>
`
: nothing}
${!this.subEntry
? html`
<ha-icon-button
.path=${mdiClose}
slot="end"
@click=${this._removeItem}
></ha-icon-button>
`
: nothing}
</ha-md-list-item>
${this._expanded && entries && entries.referenced_entities
? this._renderEntries()
: nothing}
`;
}
private _renderEntries() {
const entries = this.parentEntries || this._entries;
let nextType =
this.type === "floor"
? "area"
: this.type === "area"
? "device"
: "entity";
if (this.type === "label") {
if (entries?.referenced_areas.length) {
nextType = "area";
} else if (entries?.referenced_devices.length) {
nextType = "device";
}
}
const rows1 =
(nextType === "area"
? entries?.referenced_areas
: nextType === "device"
? entries?.referenced_devices
: entries?.referenced_entities) || [];
const rows1Entries =
nextType === "entity"
? undefined
: rows1.map((rowItem) => {
const nextEntries = {
missing_areas: [] as string[],
missing_devices: [] as string[],
missing_floors: [] as string[],
missing_labels: [] as string[],
referenced_areas: [] as string[],
referenced_devices: [] as string[],
referenced_entities: [] as string[],
};
if (nextType === "area") {
nextEntries.referenced_devices =
entries?.referenced_devices.filter(
(device_id) =>
this.hass.devices?.[device_id]?.area_id === rowItem &&
entries?.referenced_entities.some(
(entity_id) =>
this.hass.entities?.[entity_id]?.device_id === device_id
)
) || ([] as string[]);
nextEntries.referenced_entities =
entries?.referenced_entities.filter((entity_id) => {
const entity = this.hass.entities[entity_id];
return (
entity.area_id === rowItem ||
!entity.device_id ||
nextEntries.referenced_devices.includes(entity.device_id)
);
}) || ([] as string[]);
return nextEntries;
}
nextEntries.referenced_entities =
entries?.referenced_entities.filter(
(entity_id) =>
this.hass.entities?.[entity_id]?.device_id === rowItem
) || ([] as string[]);
return nextEntries;
});
const rows2 =
nextType === "device" && entries
? entries.referenced_entities.filter(
(entity_id) => this.hass.entities[entity_id].area_id === this.itemId
)
: [];
return html`
<ha-md-list class="entries">
${rows1.map(
(itemId, index) => html`
<ha-target-picker-item-row
sub-entry
.hass=${this.hass}
.type=${nextType}
.itemId=${itemId}
.parentEntries=${rows1Entries?.[index]}
></ha-target-picker-item-row>
`
)}
${rows2.map(
(itemId) => html`
<ha-target-picker-item-row
sub-entry
.hass=${this.hass}
type="entity"
.itemId=${itemId}
></ha-target-picker-item-row>
`
)}
</ha-md-list>
`;
}
private async _updateItemData() {
try {
this._entries = await extractFromTarget(this.hass, {
[`${this.type}_id`]: [this.itemId],
});
} catch (e) {
// eslint-disable-next-line no-console
console.error("Failed to extract target", e);
}
}
private _itemData = memoizeOne((type: TargetType, item: string) => {
if (type === "floor") {
const floor = this.hass.floors?.[item];
return {
name: floor?.name || item,
iconPath: floor?.icon,
fallbackIconPath: floor ? floorDefaultIconPath(floor) : mdiHome,
};
}
if (type === "area") {
const area = this.hass.areas?.[item];
return {
name: area?.name || item,
context: area.floor_id && this.hass.floors?.[area.floor_id]?.name,
iconPath: area?.icon,
fallbackIconPath: mdiTextureBox,
};
}
if (type === "device") {
const device = this.hass.devices?.[item];
if (device.primary_config_entry) {
this._getDeviceDomain(device.primary_config_entry);
}
return {
name: device ? computeDeviceNameDisplay(device, this.hass) : item,
context: device?.area_id && this.hass.areas?.[device.area_id]?.name,
fallbackIconPath: mdiDevices,
};
}
if (type === "entity") {
this._setDomainName(computeDomain(item));
const stateObject = this.hass.states[item];
const entityName = computeEntityName(stateObject, this.hass);
const { area, device } = getEntityContext(stateObject, this.hass);
const deviceName = device ? computeDeviceName(device) : undefined;
const areaName = area ? computeAreaName(area) : undefined;
const context = [areaName, entityName ? deviceName : undefined]
.filter(Boolean)
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
return {
name: entityName || deviceName || item,
context,
stateObject,
};
}
// type label
const label = this._labelRegistry.find((lab) => lab.label_id === item);
return {
name: label?.name || item,
iconPath: label?.icon,
fallbackIconPath: mdiLabel,
};
});
private _setDomainName(domain: string) {
this._domainName = domainToName(this.hass.localize, domain);
}
private _removeItem(ev) {
ev.stopPropagation();
fireEvent(this, "remove-target-item", {
type: this.type,
id: this.itemId,
});
}
private async _getDeviceDomain(configEntryId: string) {
try {
const data = await getConfigEntry(this.hass, configEntryId);
const domain = data.config_entry.domain;
this._iconImg = brandsUrl({
domain: domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
});
this._setDomainName(domain);
} catch {
// failed to load config entry -> ignore
}
}
private _toggleExpand() {
const entries = this.parentEntries || this._entries;
if (
this.type === "entity" ||
!entries ||
entries.referenced_entities.length === 0
) {
return;
}
this._expanded = !this._expanded;
}
static styles = css`
:host {
--md-list-item-top-space: 0;
--md-list-item-bottom-space: 0;
--md-list-item-leading-space: 8px;
--md-list-item-trailing-space: 8px;
--md-list-item-two-line-container-height: 56px;
}
state-badge {
color: var(--ha-color-on-neutral-quiet);
}
img {
width: 24px;
height: 24px;
}
.expand-button {
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.expand-button.expanded {
transform: rotate(180deg);
}
ha-icon-button {
--mdc-icon-button-size: 32px;
}
.summary {
display: flex;
flex-direction: column;
align-items: flex-end;
line-height: var(--ha-line-height-condensed);
}
:host([sub-entry]) .summary {
margin-right: 48px;
}
.summary .main {
font-weight: var(--ha-font-weight-medium);
}
.summary .secondary {
font-size: var(--ha-font-size-s);
color: var(--secondary-text-color);
}
.summary .secondary.domain {
font-family: var(--ha-font-family-code);
}
@media all and (max-width: 870px) {
:host([sub-entry]) .summary {
display: none;
}
}
.entries {
padding: 0;
padding-left: 40px;
overflow: hidden;
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
border-bottom: 1px solid var(--ha-color-border-neutral-quiet);
}
:host([sub-entry]) .entries {
border-bottom: none;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-target-picker-item-row": HaTargetPickerItemRow;
}
}

View File

@@ -1,21 +0,0 @@
import type { HassServiceTarget } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../types";
export interface ExtractFromTargetResult {
missing_areas: string[];
missing_devices: string[];
missing_floors: string[];
missing_labels: string[];
referenced_areas: string[];
referenced_devices: string[];
referenced_entities: string[];
}
export const extractFromTarget = async (
hass: HomeAssistant,
target: HassServiceTarget
) =>
hass.callWS<ExtractFromTargetResult>({
type: "extract_from_target",
target,
});

View File

@@ -166,37 +166,36 @@ class MoreInfoWeather extends LitElement {
${this.hass.formatEntityState(this.stateObj)} ${this.hass.formatEntityState(this.stateObj)}
</div> </div>
<div class="time-ago"> <div class="time-ago">
<ha-tooltip> <ha-relative-time
<ha-relative-time id="relative-time"
.hass=${this.hass} .hass=${this.hass}
.datetime=${this.stateObj.last_changed} .datetime=${this.stateObj.last_changed}
capitalize capitalize
></ha-relative-time> ></ha-relative-time>
<div slot="content"> <ha-tooltip for="relative-time">
<div class="row"> <div class="row">
<span class="column-name"> <span class="column-name">
${this.hass.localize( ${this.hass.localize(
"ui.dialogs.more_info_control.last_changed" "ui.dialogs.more_info_control.last_changed"
)}: )}:
</span> </span>
<ha-relative-time <ha-relative-time
.hass=${this.hass} .hass=${this.hass}
.datetime=${this.stateObj.last_changed} .datetime=${this.stateObj.last_changed}
capitalize capitalize
></ha-relative-time> ></ha-relative-time>
</div> </div>
<div class="row"> <div class="row">
<span> <span>
${this.hass.localize( ${this.hass.localize(
"ui.dialogs.more_info_control.last_updated" "ui.dialogs.more_info_control.last_updated"
)}: )}:
</span> </span>
<ha-relative-time <ha-relative-time
.hass=${this.hass} .hass=${this.hass}
.datetime=${this.stateObj.last_updated} .datetime=${this.stateObj.last_updated}
capitalize capitalize
></ha-relative-time> ></ha-relative-time>
</div>
</div> </div>
</ha-tooltip> </ha-tooltip>
</div> </div>

View File

@@ -28,15 +28,14 @@ export class HuiPersistentNotificationItem extends LitElement {
<div class="time"> <div class="time">
<span> <span>
<ha-tooltip <ha-relative-time
.content=${this._computeTooltip(this.hass, this.notification)} id="relative-time"
placement="bottom" .hass=${this.hass}
> .datetime=${this.notification.created_at}
<ha-relative-time capitalize
.hass=${this.hass} ></ha-relative-time>
.datetime=${this.notification.created_at} <ha-tooltip for="relative-time" placement="bottom">
capitalize ${this._computeTooltip(this.hass, this.notification)}
></ha-relative-time>
</ha-tooltip> </ha-tooltip>
</span> </span>
</div> </div>

View File

@@ -1,6 +1,7 @@
import type { CSSResultGroup, TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { goBack } from "../common/navigate";
import "../components/ha-icon-button-arrow-prev"; import "../components/ha-icon-button-arrow-prev";
import "../components/ha-button"; import "../components/ha-button";
import "../components/ha-menu-button"; import "../components/ha-menu-button";
@@ -50,7 +51,7 @@ class HassErrorScreen extends LitElement {
} }
private _handleBack(): void { private _handleBack(): void {
history.back(); goBack();
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {

View File

@@ -1,6 +1,7 @@
import type { CSSResultGroup, TemplateResult } from "lit"; import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { goBack } from "../common/navigate";
import "../components/ha-spinner"; import "../components/ha-spinner";
import "../components/ha-icon-button-arrow-prev"; import "../components/ha-icon-button-arrow-prev";
import "../components/ha-menu-button"; import "../components/ha-menu-button";
@@ -49,7 +50,7 @@ class HassLoadingScreen extends LitElement {
} }
private _handleBack() { private _handleBack() {
history.back(); goBack();
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {

View File

@@ -2,6 +2,7 @@ import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, eventOptions, property } from "lit/decorators"; import { customElement, eventOptions, property } from "lit/decorators";
import { restoreScroll } from "../common/decorators/restore-scroll"; import { restoreScroll } from "../common/decorators/restore-scroll";
import { goBack } from "../common/navigate";
import "../components/ha-icon-button-arrow-prev"; import "../components/ha-icon-button-arrow-prev";
import "../components/ha-menu-button"; import "../components/ha-menu-button";
import type { HomeAssistant } from "../types"; import type { HomeAssistant } from "../types";
@@ -78,7 +79,7 @@ class HassSubpage extends LitElement {
this.backCallback(); this.backCallback();
return; return;
} }
history.back(); goBack();
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {

View File

@@ -4,6 +4,7 @@ import { customElement, eventOptions, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { canShowPage } from "../common/config/can_show_page"; import { canShowPage } from "../common/config/can_show_page";
import { goBack } from "../common/navigate";
import { restoreScroll } from "../common/decorators/restore-scroll"; import { restoreScroll } from "../common/decorators/restore-scroll";
import type { LocalizeFunc } from "../common/translations/localize"; import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-icon-button-arrow-prev"; import "../components/ha-icon-button-arrow-prev";
@@ -205,7 +206,7 @@ class HassTabsSubpage extends LitElement {
this.backCallback(); this.backCallback();
return; return;
} }
history.back(); goBack();
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {

View File

@@ -7,6 +7,7 @@ import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined"; import { ifDefined } from "lit/directives/if-defined";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { goBack } from "../../../common/navigate";
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name"; import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
@@ -50,6 +51,7 @@ import {
loadAreaRegistryDetailDialog, loadAreaRegistryDetailDialog,
showAreaRegistryDetailDialog, showAreaRegistryDetailDialog,
} from "./show-dialog-area-registry-detail"; } from "./show-dialog-area-registry-detail";
import { slugify } from "../../../common/string/slugify";
declare interface NameAndEntity<EntityType extends HassEntity> { declare interface NameAndEntity<EntityType extends HassEntity> {
name: string; name: string;
@@ -548,11 +550,14 @@ class HaConfigAreaPage extends LitElement {
private _renderScene(name: string, entityState: SceneEntity) { private _renderScene(name: string, entityState: SceneEntity) {
return html`<ha-tooltip return html`<ha-tooltip
.distance=${-4} .for="scene-${slugify(entityState.entity_id)}"
.disabled=${!!entityState.attributes.id} .distance=${-4}
.content=${this.hass.localize("ui.panel.config.devices.cant_edit")} .disabled=${!!entityState.attributes.id}
> >
${this.hass.localize("ui.panel.config.devices.cant_edit")}
</ha-tooltip>
<a <a
.id="scene-${slugify(entityState.entity_id)}"
href=${ifDefined( href=${ifDefined(
entityState.attributes.id entityState.attributes.id
? `/config/scene/edit/${entityState.attributes.id}` ? `/config/scene/edit/${entityState.attributes.id}`
@@ -563,17 +568,12 @@ class HaConfigAreaPage extends LitElement {
<span>${name}</span> <span>${name}</span>
<ha-icon-next slot="meta"></ha-icon-next> <ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item> </ha-list-item>
</a> </a> `;
</ha-tooltip>`;
} }
private _renderAutomation(name: string, entityState: AutomationEntity) { private _renderAutomation(name: string, entityState: AutomationEntity) {
return html`<ha-tooltip return html`<a
.disabled=${!!entityState.attributes.id} id="automation-${slugify(entityState.entity_id)}"
.distance=${-4}
.content=${this.hass.localize("ui.panel.config.devices.cant_edit")}
>
<a
href=${ifDefined( href=${ifDefined(
entityState.attributes.id entityState.attributes.id
? `/config/automation/edit/${encodeURIComponent(entityState.attributes.id)}` ? `/config/automation/edit/${encodeURIComponent(entityState.attributes.id)}`
@@ -585,7 +585,12 @@ class HaConfigAreaPage extends LitElement {
<ha-icon-next slot="meta"></ha-icon-next> <ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item> </ha-list-item>
</a> </a>
</ha-tooltip>`; <ha-tooltip
for="automation-${slugify(entityState.entity_id)}"
.disabled=${!!entityState.attributes.id}
.distance=${-4}
>${this.hass.localize("ui.panel.config.devices.cant_edit")}
</ha-tooltip>`;
} }
private _renderScript(name: string, entityState: ScriptEntity) { private _renderScript(name: string, entityState: ScriptEntity) {
@@ -643,7 +648,7 @@ class HaConfigAreaPage extends LitElement {
destructive: true, destructive: true,
confirm: async () => { confirm: async () => {
await deleteAreaRegistryEntry(this.hass!, area!.area_id); await deleteAreaRegistryEntry(this.hass!, area!.area_id);
afterNextRender(() => history.back()); afterNextRender(() => goBack("/config"));
}, },
}); });
} }

View File

@@ -258,14 +258,16 @@ export default class HaAutomationActionRow extends LitElement {
${type !== "condition" && ${type !== "condition" &&
(this.action as NonConditionAction).continue_on_error === true (this.action as NonConditionAction).continue_on_error === true
? html`<ha-tooltip ? html`<ha-svg-icon
slot="icons" id="svg-icon"
.content=${this.hass.localize( slot="icons"
"ui.panel.config.automation.editor.actions.continue_on_error" .path=${mdiAlertCircleCheck}
)} ></ha-svg-icon>
> <ha-tooltip for="svg-icon">
<ha-svg-icon .path=${mdiAlertCircleCheck}></ha-svg-icon> ${this.hass.localize(
</ha-tooltip>` "ui.panel.config.automation.editor.actions.continue_on_error"
)}
</ha-tooltip>`
: nothing} : nothing}
${!this.optionsInSidebar ${!this.optionsInSidebar
? html`<ha-md-button-menu ? html`<ha-md-button-menu
@@ -460,9 +462,6 @@ export default class HaAutomationActionRow extends LitElement {
.sortSelected=${this.sortSelected} .sortSelected=${this.sortSelected}
@click=${this._toggleSidebar} @click=${this._toggleSidebar}
@toggle-collapsed=${this._toggleCollapse} @toggle-collapsed=${this._toggleCollapse}
@copy-row=${this._copyAction}
@cut-row=${this._cutAction}
@delete-row=${this._onDelete}
>${this._renderRow()}</ha-automation-row >${this._renderRow()}</ha-automation-row
>` >`
: html` : html`

View File

@@ -369,9 +369,6 @@ export default class HaAutomationConditionRow extends LitElement {
.sortSelected=${this.sortSelected} .sortSelected=${this.sortSelected}
@click=${this._toggleSidebar} @click=${this._toggleSidebar}
@toggle-collapsed=${this._toggleCollapse} @toggle-collapsed=${this._toggleCollapse}
@copy-row=${this._copyCondition}
@cut-row=${this._cutCondition}
@delete-row=${this._onDelete}
>${this._renderRow()}</ha-automation-row >${this._renderRow()}</ha-automation-row
>` >`
: html` : html`

View File

@@ -24,7 +24,7 @@ import { property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { transform } from "../../../common/decorators/transform"; import { transform } from "../../../common/decorators/transform";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate"; import { goBack, navigate } from "../../../common/navigate";
import { promiseTimeout } from "../../../common/util/promise-timeout"; import { promiseTimeout } from "../../../common/util/promise-timeout";
import { afterNextRender } from "../../../common/util/render-status"; import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button"; import "../../../components/ha-button";
@@ -702,7 +702,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
{ err_no: err.status_code } { err_no: err.status_code }
), ),
}); });
history.back(); goBack("/config");
} }
} }
@@ -853,7 +853,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
private _backTapped = async () => { private _backTapped = async () => {
const result = await this._confirmUnsavedChanged(); const result = await this._confirmUnsavedChanged();
if (result) { if (result) {
afterNextRender(() => history.back()); afterNextRender(() => goBack("/config"));
} }
}; };
@@ -941,7 +941,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
private async _delete() { private async _delete() {
if (this.automationId) { if (this.automationId) {
await deleteAutomation(this.hass, this.automationId); await deleteAutomation(this.hass, this.automationId);
history.back(); goBack("/config");
} }
} }

View File

@@ -254,4 +254,7 @@ export const sidebarEditorStyles = css`
display: none; display: none;
} }
} }
ha-md-menu-item {
--mdc-icon-size: 24px;
}
`; `;

View File

@@ -358,9 +358,6 @@ export default class HaAutomationTriggerRow extends LitElement {
.highlight=${this.highlight} .highlight=${this.highlight}
.sortSelected=${this.sortSelected} .sortSelected=${this.sortSelected}
@click=${this._toggleSidebar} @click=${this._toggleSidebar}
@copy-row=${this._copyTrigger}
@cut-row=${this._cutTrigger}
@delete-row=${this._onDelete}
>${this._selected >${this._selected
? "selected" ? "selected"
: nothing}${this._renderRow()}</ha-automation-row : nothing}${this._renderRow()}</ha-automation-row

View File

@@ -5,7 +5,7 @@ import {
mdiGroup, mdiGroup,
mdiPlus, mdiPlus,
} from "@mdi/js"; } from "@mdi/js";
import { navigate } from "../../../../../../common/navigate"; import { goBack, navigate } from "../../../../../../common/navigate";
import type { DeviceRegistryEntry } from "../../../../../../data/device_registry"; import type { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import { fetchZHADevice } from "../../../../../../data/zha"; import { fetchZHADevice } from "../../../../../../data/zha";
import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box";
@@ -102,7 +102,7 @@ export const getZHADeviceActions = async (
ieee: zhaDevice.ieee, ieee: zhaDevice.ieee,
}); });
history.back(); goBack("/config");
}, },
}); });
} }

View File

@@ -89,6 +89,7 @@ import {
loadDeviceRegistryDetailDialog, loadDeviceRegistryDetailDialog,
showDeviceRegistryDetailDialog, showDeviceRegistryDetailDialog,
} from "./device-registry-detail/show-dialog-device-registry-detail"; } from "./device-registry-detail/show-dialog-device-registry-detail";
import { slugify } from "../../../common/string/slugify";
export interface EntityRegistryStateEntry extends EntityRegistryEntry { export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string | null; stateName?: string | null;
@@ -555,16 +556,21 @@ export class HaConfigDevicePage extends LitElement {
</a> </a>
` `
: html` : html`
<ha-list-item
.id="scene-${slugify(entityState.entity_id)}"
hasMeta
.scene=${entityState}
>
${computeStateName(entityState)}
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
<ha-tooltip <ha-tooltip
.for="scene-${slugify(entityState.entity_id)}"
placement="left" placement="left"
.content=${this.hass.localize( >
${this.hass.localize(
"ui.panel.config.devices.cant_edit" "ui.panel.config.devices.cant_edit"
)} )}
>
<ha-list-item hasMeta .scene=${entityState}>
${computeStateName(entityState)}
<ha-icon-next slot="meta"></ha-icon-next>
</ha-list-item>
</ha-tooltip> </ha-tooltip>
`; `;
})} })}

View File

@@ -671,13 +671,14 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
tabindex="0" tabindex="0"
style="display:inline-block; position: relative;" style="display:inline-block; position: relative;"
> >
<ha-tooltip <ha-svg-icon
placement="left" .id="svg-icon-${device.id}"
.content=${this.hass.localize( .path=${mdiCancel}
></ha-svg-icon>
<ha-tooltip .for="svg-icon-${device.id}" placement="left">
${this.hass.localize(
"ui.panel.config.entities.picker.status.disabled" "ui.panel.config.entities.picker.status.disabled"
)} )}
>
<ha-svg-icon .path=${mdiCancel}></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
</div> </div>
` `

View File

@@ -114,6 +114,7 @@ import { isHelperDomain } from "../helpers/const";
import "../integrations/ha-integration-overflow-menu"; import "../integrations/ha-integration-overflow-menu";
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog"; import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { slugify } from "../../../common/string/slugify";
export interface StateEntity export interface StateEntity
extends Omit<EntityRegistryEntry, "id" | "unique_id"> { extends Omit<EntityRegistryEntry, "id" | "unique_id"> {
@@ -392,9 +393,27 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
tabindex="0" tabindex="0"
style="display:inline-block; position: relative;" style="display:inline-block; position: relative;"
> >
<ha-svg-icon
.id="status-icon-${slugify(entry.entity_id)}"
style=${styleMap({
color: entry.unavailable ? "var(--error-color)" : "",
})}
.path=${entry.restored
? mdiRestoreAlert
: entry.unavailable
? mdiAlertCircle
: entry.disabled_by
? mdiCancel
: entry.hidden_by
? mdiEyeOff
: mdiPencilOff}
></ha-svg-icon>
<ha-tooltip <ha-tooltip
.for="status-icon-${slugify(entry.entity_id)}"
placement="left" placement="left"
.content=${entry.restored >
${entry.restored
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.entities.picker.status.not_provided" "ui.panel.config.entities.picker.status.not_provided"
) )
@@ -413,21 +432,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
: this.hass.localize( : this.hass.localize(
"ui.panel.config.entities.picker.status.unmanageable" "ui.panel.config.entities.picker.status.unmanageable"
)} )}
>
<ha-svg-icon
style=${styleMap({
color: entry.unavailable ? "var(--error-color)" : "",
})}
.path=${entry.restored
? mdiRestoreAlert
: entry.unavailable
? mdiAlertCircle
: entry.disabled_by
? mdiCancel
: entry.hidden_by
? mdiEyeOff
: mdiPencilOff}
></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
</div> </div>
` `

View File

@@ -236,17 +236,18 @@ export class DialogHelperDetail extends LitElement {
<span class="item-text"> ${label} </span> <span class="item-text"> ${label} </span>
${isLoaded ${isLoaded
? html`<ha-icon-next slot="meta"></ha-icon-next>` ? html`<ha-icon-next slot="meta"></ha-icon-next>`
: html`<ha-tooltip : html` <ha-svg-icon
hoist slot="meta"
slot="meta" .id="icon-${domain}"
.content=${this.hass.localize( path=${mdiAlertOutline}
"ui.dialogs.helper_settings.platform_not_loaded", @click=${stopPropagation}
{ platform: domain } ></ha-svg-icon>
)} <ha-tooltip .for="icon-${domain}">
@click=${stopPropagation} ${this.hass.localize(
> "ui.dialogs.helper_settings.platform_not_loaded",
<ha-svg-icon path=${mdiAlertOutline}></ha-svg-icon> { platform: domain }
</ha-tooltip>`} )}
</ha-tooltip>`}
</ha-list-item> </ha-list-item>
`; `;
})} })}

View File

@@ -110,6 +110,7 @@ import { renderConfigEntryError } from "../integrations/ha-config-integration-pa
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { isHelperDomain } from "./const"; import { isHelperDomain } from "./const";
import { showHelperDetailDialog } from "./show-dialog-helper-detail"; import { showHelperDetailDialog } from "./show-dialog-helper-detail";
import { slugify } from "../../../common/string/slugify";
interface HelperItem { interface HelperItem {
id: string; id: string;
@@ -361,13 +362,16 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
tabindex="0" tabindex="0"
style="display:inline-block; position: relative;" style="display:inline-block; position: relative;"
> >
<ha-svg-icon
.id="icon-edit-${slugify(helper.entity_id)}"
.path=${mdiPencilOff}
></ha-svg-icon>
<ha-tooltip <ha-tooltip
.for="icon-edit-${slugify(helper.entity_id)}"
placement="left" placement="left"
.content=${this.hass.localize( >${this.hass.localize(
"ui.panel.config.entities.picker.status.unmanageable" "ui.panel.config.entities.picker.status.unmanageable"
)} )}
>
<ha-svg-icon .path=${mdiPencilOff}></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
</div> </div>
` `

View File

@@ -159,29 +159,32 @@ export class HaIntegrationCard extends LitElement {
? "overwrites" ? "overwrites"
: "custom"}" : "custom"}"
> >
<ha-svg-icon
id="icon-custom"
.path=${mdiPackageVariant}
></ha-svg-icon>
<ha-tooltip <ha-tooltip
hoist for="icon-custom"
.placement=${computeRTL(this.hass) ? "right" : "left"} .placement=${computeRTL(this.hass) ? "right" : "left"}
.content=${this.hass.localize( >
${this.hass.localize(
this.manifest.overwrites_built_in this.manifest.overwrites_built_in
? "ui.panel.config.integrations.config_entry.custom_overwrites_core" ? "ui.panel.config.integrations.config_entry.custom_overwrites_core"
: "ui.panel.config.integrations.config_entry.custom_integration" : "ui.panel.config.integrations.config_entry.custom_integration"
)} )}
>
<ha-svg-icon .path=${mdiPackageVariant}></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
</span>` </span>`
: nothing} : nothing}
${this.manifest && this.manifest.iot_class?.startsWith("cloud_") ${this.manifest && this.manifest.iot_class?.startsWith("cloud_")
? html`<div class="icon cloud"> ? html`<div class="icon cloud">
<ha-svg-icon id="icon-cloud" .path=${mdiWeb}></ha-svg-icon>
<ha-tooltip <ha-tooltip
hoist for="icon-cloud"
.placement=${computeRTL(this.hass) ? "right" : "left"} .placement=${computeRTL(this.hass) ? "right" : "left"}
.content=${this.hass.localize( >
${this.hass.localize(
"ui.panel.config.integrations.config_entry.depends_on_cloud" "ui.panel.config.integrations.config_entry.depends_on_cloud"
)} )}
>
<ha-svg-icon .path=${mdiWeb}></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
</div>` </div>`
: nothing} : nothing}
@@ -189,15 +192,18 @@ export class HaIntegrationCard extends LitElement {
!this.manifest?.config_flow && !this.manifest?.config_flow &&
!this.items.every((itm) => itm.source === "system") !this.items.every((itm) => itm.source === "system")
? html`<div class="icon yaml"> ? html`<div class="icon yaml">
<ha-svg-icon
id="icon-yaml"
.path=${mdiFileCodeOutline}
></ha-svg-icon>
<ha-tooltip <ha-tooltip
hoist for="icon-yaml"
.placement=${computeRTL(this.hass) ? "right" : "left"} .placement=${computeRTL(this.hass) ? "right" : "left"}
.content=${this.hass.localize( >
${this.hass.localize(
"ui.panel.config.integrations.config_entry.no_config_flow" "ui.panel.config.integrations.config_entry.no_config_flow"
)} )}
> </ha-tooltip>
<ha-svg-icon .path=${mdiFileCodeOutline}></ha-svg-icon
></ha-tooltip>
</div>` </div>`
: nothing} : nothing}
</div> </div>

View File

@@ -74,45 +74,45 @@ export class HaIntegrationListItem extends ListItemBase {
} }
return html`<span class="mdc-deprecated-list-item__meta material-icons"> return html`<span class="mdc-deprecated-list-item__meta material-icons">
${this.integration.cloud ${this.integration.cloud
? html`<ha-tooltip ? html` <ha-svg-icon id="icon-cloud" .path=${mdiWeb}></ha-svg-icon>
placement="left" <ha-tooltip for="icon-cloud" placement="left"
.content=${this.hass.localize( >${this.hass.localize(
"ui.panel.config.integrations.config_entry.depends_on_cloud" "ui.panel.config.integrations.config_entry.depends_on_cloud"
)} )}
><ha-svg-icon .path=${mdiWeb}></ha-svg-icon </ha-tooltip>`
></ha-tooltip>`
: nothing} : nothing}
${!this.integration.is_built_in ${!this.integration.is_built_in
? html`<span ? html`<span
class=${this.integration.overwrites_built_in class=${this.integration.overwrites_built_in
? "overwrites" ? "overwrites"
: "custom"} : "custom"}
><ha-tooltip >
placement="left" <ha-svg-icon
.content=${this.hass.localize( id="icon-custom"
.path=${mdiPackageVariant}
></ha-svg-icon>
<ha-tooltip for="icon-custom" placement="left"
>${this.hass.localize(
this.integration.overwrites_built_in this.integration.overwrites_built_in
? "ui.panel.config.integrations.config_entry.custom_overwrites_core" ? "ui.panel.config.integrations.config_entry.custom_overwrites_core"
: "ui.panel.config.integrations.config_entry.custom_integration" : "ui.panel.config.integrations.config_entry.custom_integration"
)} )}</ha-tooltip
><ha-svg-icon ></span
.path=${mdiPackageVariant} >`
></ha-svg-icon></ha-tooltip
></span>`
: nothing} : nothing}
${!this.integration.config_flow && ${!this.integration.config_flow &&
!this.integration.integrations && !this.integration.integrations &&
!this.integration.iot_standards !this.integration.iot_standards
? html`<ha-tooltip ? html` <ha-svg-icon
placement="left" id="icon-yaml"
.content=${this.hass.localize(
"ui.panel.config.integrations.config_entry.yaml_only"
)}
>
<ha-svg-icon
.path=${mdiFileCodeOutline} .path=${mdiFileCodeOutline}
class="open-in-new" class="open-in-new"
></ha-svg-icon> ></ha-svg-icon>
</ha-tooltip>` <ha-tooltip for="icon-yaml" placement="left">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.yaml_only"
)}
</ha-tooltip>`
: html`<ha-icon-next></ha-icon-next>`} : html`<ha-icon-next></ha-icon-next>`}
</span>`; </span>`;
} }

View File

@@ -307,14 +307,18 @@ class DialogZHAReconfigureDevice extends LitElement {
` `
: html` : html`
<span class="stage"> <span class="stage">
<ha-svg-icon
.id="svg-icon-${clusterStatus
.cluster.name}"
.path=${mdiCloseCircle}
class="failed"
></ha-svg-icon>
<ha-tooltip <ha-tooltip
.for="svg-icon-${clusterStatus
.cluster.name}"
placement="top" placement="top"
.content=${attribute.status}
> >
<ha-svg-icon ${attribute.status}
.path=${mdiCloseCircle}
class="failed"
></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
</span> </span>
`} `}

View File

@@ -21,6 +21,7 @@ import "../../../../../components/ha-list-item";
import "../../../../../components/ha-progress-ring"; import "../../../../../components/ha-progress-ring";
import "../../../../../components/ha-spinner"; import "../../../../../components/ha-spinner";
import "../../../../../components/ha-svg-icon"; import "../../../../../components/ha-svg-icon";
import { goBack } from "../../../../../common/navigate";
import type { ConfigEntry } from "../../../../../data/config_entries"; import type { ConfigEntry } from "../../../../../data/config_entries";
import { import {
ERROR_STATES, ERROR_STATES,
@@ -618,7 +619,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
} }
private _handleBack(): void { private _handleBack(): void {
history.back(); goBack("/config");
} }
private _fetchData = async () => { private _fetchData = async () => {

View File

@@ -367,6 +367,7 @@ class ZWaveJSNodeConfig extends LitElement {
return html` return html`
${labelAndDescription} ${labelAndDescription}
<ha-select <ha-select
fixedMenuPosition
.disabled=${!item.metadata.writeable} .disabled=${!item.metadata.writeable}
.value=${item.value?.toString()} .value=${item.value?.toString()}
.key=${id} .key=${id}

View File

@@ -156,16 +156,18 @@ export class HaConfigLovelaceDashboards extends LitElement {
${dashboard.title} ${dashboard.title}
${dashboard.default ${dashboard.default
? html` ? html`
<ha-svg-icon
.id="default-icon-${dashboard.title}"
style="padding-left: 10px; padding-inline-start: 10px; padding-inline-end: initial; direction: var(--direction);"
.path=${mdiCheckCircleOutline}
></ha-svg-icon>
<ha-tooltip <ha-tooltip
.content=${this.hass.localize( .for="default-icon-${dashboard.title}"
`ui.panel.config.lovelace.dashboards.default_dashboard`
)}
placement="right" placement="right"
> >
<ha-svg-icon ${this.hass.localize(
style="padding-left: 10px; padding-inline-start: 10px; padding-inline-end: initial; direction: var(--direction);" `ui.panel.config.lovelace.dashboards.default_dashboard`
.path=${mdiCheckCircleOutline} )}
></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
` `
: nothing} : nothing}

View File

@@ -106,6 +106,7 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail"; import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail"; import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import { slugify } from "../../../common/string/slugify";
type SceneItem = SceneEntity & { type SceneItem = SceneEntity & {
name: string; name: string;
@@ -318,16 +319,18 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
template: (scene) => template: (scene) =>
!scene.attributes.id !scene.attributes.id
? html` ? html`
<ha-svg-icon
.id="svg-icon-${slugify(scene.entity_id)}"
.path=${mdiPencilOff}
style="color: var(--secondary-text-color)"
></ha-svg-icon>
<ha-tooltip <ha-tooltip
.for="svg-icon-${slugify(scene.entity_id)}"
placement="left" placement="left"
.content=${this.hass.localize( >
${this.hass.localize(
"ui.panel.config.scene.picker.only_editable" "ui.panel.config.scene.picker.only_editable"
)} )}
>
<ha-svg-icon
.path=${mdiPencilOff}
style="color: var(--secondary-text-color)"
></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
` `
: nothing, : nothing,

View File

@@ -24,7 +24,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name"; import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
import { computeDomain } from "../../../common/entity/compute_domain"; import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate"; import { goBack, navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import { afterNextRender } from "../../../common/util/render-status"; import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/device/ha-device-picker"; import "../../../components/device/ha-device-picker";
@@ -806,7 +806,7 @@ export class HaSceneEditor extends PreventUnsavedMixin(
{ err_no: err.status_code } { err_no: err.status_code }
), ),
}); });
history.back(); goBack("/config");
return; return;
} }
@@ -988,7 +988,7 @@ export class HaSceneEditor extends PreventUnsavedMixin(
if (this._mode === "live") { if (this._mode === "live") {
applyScene(this.hass, this._storedStates); applyScene(this.hass, this._storedStates);
} }
afterNextRender(() => history.back()); afterNextRender(() => goBack("/config"));
} }
private _deleteTapped(): void { private _deleteTapped(): void {
@@ -1012,7 +1012,7 @@ export class HaSceneEditor extends PreventUnsavedMixin(
if (this._mode === "live") { if (this._mode === "live") {
applyScene(this.hass, this._storedStates); applyScene(this.hass, this._storedStates);
} }
history.back(); goBack("/config");
} }
private async _confirmUnsavedChanged(): Promise<boolean> { private async _confirmUnsavedChanged(): Promise<boolean> {

View File

@@ -21,7 +21,7 @@ import { LitElement, css, html, nothing } from "lit";
import { property, query, state } from "lit/decorators"; import { property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate"; import { goBack, navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify"; import { slugify } from "../../../common/string/slugify";
import { promiseTimeout } from "../../../common/util/promise-timeout"; import { promiseTimeout } from "../../../common/util/promise-timeout";
import { afterNextRender } from "../../../common/util/render-status"; import { afterNextRender } from "../../../common/util/render-status";
@@ -596,7 +596,7 @@ export class HaScriptEditor extends SubscribeMixin(
{ err_no: resp.status_code || resp.code } { err_no: resp.status_code || resp.code }
) )
); );
history.back(); goBack("/config");
} }
); );
} }
@@ -762,7 +762,7 @@ export class HaScriptEditor extends SubscribeMixin(
private _backTapped = async () => { private _backTapped = async () => {
const result = await this._confirmUnsavedChanged(); const result = await this._confirmUnsavedChanged();
if (result) { if (result) {
afterNextRender(() => history.back()); afterNextRender(() => goBack("/config"));
} }
}; };
@@ -852,7 +852,7 @@ export class HaScriptEditor extends SubscribeMixin(
private async _delete() { private async _delete() {
await deleteScript(this.hass, this.scriptId!); await deleteScript(this.hass, this.scriptId!);
history.back(); goBack("/config");
} }
private async _switchUiMode() { private async _switchUiMode() {

View File

@@ -25,48 +25,47 @@ export class VoiceAssistantExposeAssistantIcon extends LitElement {
if (!this.assistant || !voiceAssistants[this.assistant]) return nothing; if (!this.assistant || !voiceAssistants[this.assistant]) return nothing;
return html` return html`
<div class="container" id="container">
<img
class="logo"
style=${styleMap({
filter: this.manual ? "grayscale(100%)" : undefined,
})}
alt=${voiceAssistants[this.assistant].name}
src=${brandsUrl({
domain: voiceAssistants[this.assistant].domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
crossorigin="anonymous"
referrerpolicy="no-referrer"
slot="prefix"
/>
${this.unsupported
? html`
<ha-svg-icon
.path=${mdiAlertCircle}
class="unsupported"
></ha-svg-icon>
`
: nothing}
</div>
<ha-tooltip <ha-tooltip
.disabled=${!this.unsupported && !this.manual} for="container"
placement="left" placement="left"
.disabled=${!this.unsupported && !this.manual}
> >
<div class="container"> ${this.unsupported
<img ? this.hass.localize(
class="logo" "ui.panel.config.voice_assistants.expose.not_supported"
style=${styleMap({ )
filter: this.manual ? "grayscale(100%)" : undefined, : ""}
})} ${this.unsupported && this.manual ? html`<br />` : nothing}
alt=${voiceAssistants[this.assistant].name} ${this.manual
src=${brandsUrl({ ? this.hass.localize(
domain: voiceAssistants[this.assistant].domain, "ui.panel.config.voice_assistants.expose.manually_configured"
type: "icon", )
darkOptimized: this.hass.themes?.darkMode, : nothing}
})}
crossorigin="anonymous"
referrerpolicy="no-referrer"
slot="prefix"
/>
${this.unsupported
? html`
<ha-svg-icon
.path=${mdiAlertCircle}
class="unsupported"
></ha-svg-icon>
`
: nothing}
</div>
<span slot="content">
${this.unsupported
? this.hass.localize(
"ui.panel.config.voice_assistants.expose.not_supported"
)
: ""}
${this.unsupported && this.manual ? html`<br />` : nothing}
${this.manual
? this.hass.localize(
"ui.panel.config.voice_assistants.expose.manually_configured"
)
: nothing}
</span>
</ha-tooltip> </ha-tooltip>
`; `;
} }

View File

@@ -607,34 +607,32 @@ export class VoiceAssistantsExpose extends LitElement {
> >
` `
: html` : html`
<ha-tooltip <ha-icon-button
.content=${this.hass.localize( id="expose-button"
@click=${this._exposeSelected}
.path=${mdiPlusBoxMultiple}
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.expose.expose"
)}
></ha-icon-button>
<ha-tooltip for="expose-button" placement="left">
${this.hass.localize(
"ui.panel.config.voice_assistants.expose.expose" "ui.panel.config.voice_assistants.expose.expose"
)} )}
placement="left"
>
<ha-icon-button
@click=${this._exposeSelected}
.path=${mdiPlusBoxMultiple}
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.expose.expose"
)}
></ha-icon-button>
</ha-tooltip> </ha-tooltip>
<ha-tooltip <ha-tooltip for="unexpose-button" placement="left">
content=${this.hass.localize( ${this.hass.localize(
"ui.panel.config.voice_assistants.expose.unexpose" "ui.panel.config.voice_assistants.expose.unexpose"
)} )}
placement="left"
>
<ha-icon-button
@click=${this._unexposeSelected}
.path=${mdiCloseBoxMultiple}
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.expose.unexpose"
)}
></ha-icon-button>
</ha-tooltip> </ha-tooltip>
<ha-icon-button
id="unexpose-button"
@click=${this._unexposeSelected}
.path=${mdiCloseBoxMultiple}
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.expose.unexpose"
)}
></ha-icon-button>
`} `}
</div> </div>
` `

View File

@@ -46,6 +46,7 @@ import "../ha-config-section";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showHomeZoneDetailDialog } from "./show-dialog-home-zone-detail"; import { showHomeZoneDetailDialog } from "./show-dialog-home-zone-detail";
import { showZoneDetailDialog } from "./show-dialog-zone-detail"; import { showZoneDetailDialog } from "./show-dialog-zone-detail";
import { slugify } from "../../../common/string/slugify";
@customElement("ha-config-zone") @customElement("ha-config-zone")
export class HaConfigZone extends SubscribeMixin(LitElement) { export class HaConfigZone extends SubscribeMixin(LitElement) {
@@ -200,17 +201,8 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
stateObject.entity_id === "zone.home" && stateObject.entity_id === "zone.home" &&
!this._canEditCore !this._canEditCore
? nothing ? nothing
: html`<ha-tooltip : html`<ha-icon-button
slot="meta" .id="zone-${slugify(stateObject.entity_id)}"
placement="left"
.content=${hass.localize(
"ui.panel.config.zone.configured_in_yaml"
)}
.disabled=${stateObject.entity_id === "zone.home"}
hoist
>
<ha-icon-button
.id=${!this.narrow ? stateObject.entity_id : ""}
.entityId=${stateObject.entity_id} .entityId=${stateObject.entity_id}
.noEdit=${stateObject.entity_id !== "zone.home" || .noEdit=${stateObject.entity_id !== "zone.home" ||
!this._canEditCore} !this._canEditCore}
@@ -222,8 +214,18 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
name: hass.config.location_name, name: hass.config.location_name,
})} })}
@click=${this._editHomeZone} @click=${this._editHomeZone}
slot="meta"
></ha-icon-button> ></ha-icon-button>
</ha-tooltip>`} <ha-tooltip
.for="zone-${slugify(stateObject.entity_id)}"
placement="left"
.disabled=${stateObject.entity_id === "zone.home"}
hoist
>
${hass.localize(
"ui.panel.config.zone.configured_in_yaml"
)}
</ha-tooltip>`}
</ha-list-item> </ha-list-item>
` `
)} )}

View File

@@ -13,7 +13,7 @@ import "../lovelace/components/hui-energy-period-selector";
import type { Lovelace } from "../lovelace/types"; import type { Lovelace } from "../lovelace/types";
import "../lovelace/views/hui-view"; import "../lovelace/views/hui-view";
import "../lovelace/views/hui-view-container"; import "../lovelace/views/hui-view-container";
import { navigate } from "../../common/navigate"; import { goBack, navigate } from "../../common/navigate";
import type { import type {
GridSourceTypeEnergyPreference, GridSourceTypeEnergyPreference,
SolarSourceTypeEnergyPreference, SolarSourceTypeEnergyPreference,
@@ -70,7 +70,7 @@ class PanelEnergy extends LitElement {
private _back(ev) { private _back(ev) {
ev.stopPropagation(); ev.stopPropagation();
history.back(); goBack();
} }
protected render(): TemplateResult { protected render(): TemplateResult {

View File

@@ -17,7 +17,7 @@ import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array"; import { ensureArray } from "../../common/array/ensure-array";
import { storage } from "../../common/decorators/storage"; import { storage } from "../../common/decorators/storage";
import { computeDomain } from "../../common/entity/compute_domain"; import { computeDomain } from "../../common/entity/compute_domain";
import { navigate } from "../../common/navigate"; import { goBack, navigate } from "../../common/navigate";
import { constructUrlCurrentPath } from "../../common/url/construct-url"; import { constructUrlCurrentPath } from "../../common/url/construct-url";
import { import {
createSearchParam, createSearchParam,
@@ -114,7 +114,7 @@ class HaPanelHistory extends LitElement {
} }
private _goBack(): void { private _goBack(): void {
history.back(); goBack();
} }
protected render() { protected render() {
@@ -182,7 +182,6 @@ class HaPanelHistory extends LitElement {
.disabled=${this._isLoading} .disabled=${this._isLoading}
add-on-top add-on-top
@value-changed=${this._targetsChanged} @value-changed=${this._targetsChanged}
compact
></ha-target-picker> ></ha-target-picker>
</div> </div>
${this._isLoading ${this._isLoading

View File

@@ -4,7 +4,7 @@ import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import type { HassServiceTarget } from "home-assistant-js-websocket"; import type { HassServiceTarget } from "home-assistant-js-websocket";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { navigate } from "../../common/navigate"; import { goBack, navigate } from "../../common/navigate";
import { constructUrlCurrentPath } from "../../common/url/construct-url"; import { constructUrlCurrentPath } from "../../common/url/construct-url";
import { import {
createSearchParam, createSearchParam,
@@ -60,7 +60,7 @@ export class HaPanelLogbook extends LitElement {
} }
private _goBack(): void { private _goBack(): void {
history.back(); goBack();
} }
protected render() { protected render() {
@@ -104,7 +104,6 @@ export class HaPanelLogbook extends LitElement {
.value=${this._targetPickerValue} .value=${this._targetPickerValue}
add-on-top add-on-top
@value-changed=${this._targetsChanged} @value-changed=${this._targetsChanged}
compact
></ha-target-picker> ></ha-target-picker>
</div> </div>

View File

@@ -133,14 +133,12 @@ class HuiEnergyCarbonGaugeCard
"--gauge-color": this._computeSeverity(value), "--gauge-color": this._computeSeverity(value),
})} })}
></ha-gauge> ></ha-gauge>
<ha-tooltip
.content=${this.hass.localize( <ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<ha-tooltip for="info" placement="left">
${this.hass.localize(
"ui.panel.lovelace.cards.energy.carbon_consumed_gauge.card_indicates_energy_used" "ui.panel.lovelace.cards.energy.carbon_consumed_gauge.card_indicates_energy_used"
)} )}
placement="left"
hoist
>
<ha-svg-icon .path=${mdiInformation}></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
<div class="name"> <div class="name">
${this.hass.localize( ${this.hass.localize(

View File

@@ -114,17 +114,15 @@ class HuiEnergyGridGaugeCard
label="kWh" label="kWh"
needle needle
></ha-gauge> ></ha-gauge>
<ha-tooltip placement="left" hoist> <ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<span slot="content"> <ha-tooltip for="info" placement="left">
${this.hass.localize( ${this.hass.localize(
"ui.panel.lovelace.cards.energy.grid_neutrality_gauge.energy_dependency" "ui.panel.lovelace.cards.energy.grid_neutrality_gauge.energy_dependency"
)} )}
<br /><br /> <br /><br />
${this.hass.localize( ${this.hass.localize(
"ui.panel.lovelace.cards.energy.grid_neutrality_gauge.color_explain" "ui.panel.lovelace.cards.energy.grid_neutrality_gauge.color_explain"
)} )}
</span>
<ha-svg-icon .path=${mdiInformation}></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
<div class="name"> <div class="name">
${returnedToGrid! >= consumedFromGrid! ${returnedToGrid! >= consumedFromGrid!

View File

@@ -110,14 +110,11 @@ class HuiEnergySelfSufficiencyGaugeCard
"--gauge-color": this._computeSeverity(value), "--gauge-color": this._computeSeverity(value),
})} })}
></ha-gauge> ></ha-gauge>
<ha-tooltip <ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
placement="left" <ha-tooltip for="info" placement="left">
.content=${this.hass.localize( ${this.hass.localize(
"ui.panel.lovelace.cards.energy.self_sufficiency_gauge.card_indicates_self_sufficiency_quota" "ui.panel.lovelace.cards.energy.self_sufficiency_gauge.card_indicates_self_sufficiency_quota"
)} )}
hoist
>
<ha-svg-icon .path=${mdiInformation}></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
<div class="name"> <div class="name">
${this.hass.localize( ${this.hass.localize(

View File

@@ -102,17 +102,15 @@ class HuiEnergySolarGaugeCard
"--gauge-color": this._computeSeverity(value), "--gauge-color": this._computeSeverity(value),
})} })}
></ha-gauge> ></ha-gauge>
<ha-tooltip placement="left" hoist> <ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<span slot="content"> <ha-tooltip for="info" placement="left">
${this.hass.localize( ${this.hass.localize(
"ui.panel.lovelace.cards.energy.solar_consumed_gauge.card_indicates_solar_energy_used" "ui.panel.lovelace.cards.energy.solar_consumed_gauge.card_indicates_solar_energy_used"
)} )}
<br /><br /> <br /><br />
${this.hass.localize( ${this.hass.localize(
"ui.panel.lovelace.cards.energy.solar_consumed_gauge.card_indicates_solar_energy_used_charge_home_bat" "ui.panel.lovelace.cards.energy.solar_consumed_gauge.card_indicates_solar_energy_used_charge_home_bat"
)} )}
</span>
<ha-svg-icon .path=${mdiInformation}></ha-svg-icon>
</ha-tooltip> </ha-tooltip>
<div class="name"> <div class="name">
${this.hass.localize( ${this.hass.localize(
@@ -176,10 +174,6 @@ class HuiEnergySolarGaugeCard
top: 4px; top: 4px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
ha-tooltip::part(base__popup) {
margin-top: 4px;
}
`; `;
} }

View File

@@ -0,0 +1,152 @@
import { mdiDelete, mdiDrag, mdiPencil } from "@mdi/js";
import type { CSSResultGroup, TemplateResult } from "lit";
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { deleteSection } from "../editor/config-util";
import { findLovelaceContainer } from "../editor/lovelace-path";
import { showEditSectionDialog } from "../editor/section-editor/show-edit-section-dialog";
import type { Lovelace } from "../types";
@customElement("hui-section-edit-mode")
export class HuiSectionEditMode extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace!: Lovelace;
@property({ attribute: false, type: Number }) public index!: number;
@property({ attribute: false, type: Number }) public viewIndex!: number;
protected render(): TemplateResult {
return html`
<div class="section-header">
<div class="section-actions">
<ha-svg-icon
aria-hidden="true"
class="handle"
.path=${mdiDrag}
></ha-svg-icon>
<ha-icon-button
.label=${this.hass.localize("ui.common.edit")}
@click=${this._editSection}
.path=${mdiPencil}
></ha-icon-button>
<ha-icon-button
.label=${this.hass.localize("ui.common.delete")}
@click=${this._deleteSection}
.path=${mdiDelete}
></ha-icon-button>
</div>
</div>
<div class="section-wrapper">
<slot></slot>
</div>
`;
}
private async _editSection(ev) {
ev.stopPropagation();
showEditSectionDialog(this, {
lovelace: this.lovelace!,
lovelaceConfig: this.lovelace!.config,
saveConfig: (newConfig) => {
this.lovelace!.saveConfig(newConfig);
},
viewIndex: this.viewIndex,
sectionIndex: this.index,
});
}
private async _deleteSection(ev) {
ev.stopPropagation();
const path = [this.viewIndex, this.index] as [number, number];
const section = findLovelaceContainer(this.lovelace!.config, path);
const cardCount = "cards" in section && section.cards?.length;
if (cardCount) {
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.lovelace.editor.delete_section.title"
),
text: this.hass.localize(
`ui.panel.lovelace.editor.delete_section.text`
),
confirmText: this.hass.localize("ui.common.delete"),
destructive: true,
});
if (!confirm) return;
}
const newConfig = deleteSection(
this.lovelace!.config,
this.viewIndex,
this.index
);
this.lovelace!.saveConfig(newConfig);
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.section-header {
position: relative;
height: 34px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.section-actions {
position: absolute;
height: 36px;
bottom: -2px;
right: 0;
inset-inline-end: 0;
inset-inline-start: initial;
opacity: 1;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s ease-in-out;
border-radius: var(--ha-card-border-radius, 12px);
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
background: var(--secondary-background-color);
--mdc-icon-button-size: 36px;
--mdc-icon-size: 20px;
color: var(--primary-text-color);
}
.handle {
cursor: grab;
padding: 8px;
}
.section-wrapper {
padding: 8px;
border-radius: var(--ha-card-border-radius, 12px);
border-start-end-radius: 0;
border: 2px dashed var(--divider-color);
min-height: var(--row-height);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-section-edit-mode": HuiSectionEditMode;
}
}

View File

@@ -26,7 +26,7 @@ import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event"; import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
import { navigate } from "../../common/navigate"; import { goBack, navigate } from "../../common/navigate";
import type { LocalizeKeys } from "../../common/translations/localize"; import type { LocalizeKeys } from "../../common/translations/localize";
import { constructUrlCurrentPath } from "../../common/url/construct-url"; import { constructUrlCurrentPath } from "../../common/url/construct-url";
import { import {
@@ -47,6 +47,7 @@ import "../../components/ha-menu-button";
import "../../components/ha-svg-icon"; import "../../components/ha-svg-icon";
import "../../components/ha-tab-group"; import "../../components/ha-tab-group";
import "../../components/ha-tab-group-tab"; import "../../components/ha-tab-group-tab";
import "../../components/ha-tooltip";
import { createAreaRegistryEntry } from "../../data/area_registry"; import { createAreaRegistryEntry } from "../../data/area_registry";
import type { LovelacePanelConfig } from "../../data/lovelace"; import type { LovelacePanelConfig } from "../../data/lovelace";
import type { LovelaceConfig } from "../../data/lovelace/config/types"; import type { LovelaceConfig } from "../../data/lovelace/config/types";
@@ -307,7 +308,7 @@ class HUIRoot extends LitElement {
(i) => i.visible && (!i.overflow || overflowCanPromote) (i) => i.visible && (!i.overflow || overflowCanPromote)
); );
buttonItems.forEach((item) => { buttonItems.forEach((item, index) => {
const label = [this.hass!.localize(item.key), item.suffix].join(" "); const label = [this.hass!.localize(item.key), item.suffix].join(" ");
const button = item.subItems const button = item.subItems
? html` ? html`
@@ -341,11 +342,14 @@ class HUIRoot extends LitElement {
</ha-button-menu> </ha-button-menu>
` `
: html` : html`
<ha-tooltip slot="actionItems" placement="bottom" .content=${label}> <ha-icon-button
<ha-icon-button slot="actionItems"
.path=${item.icon} .id="button-${index}"
@click=${item.buttonAction} .path=${item.icon}
></ha-icon-button> @click=${item.buttonAction}
></ha-icon-button>
<ha-tooltip placement="bottom" .for="button-${index}">
${label}
</ha-tooltip> </ha-tooltip>
`; `;
result.push(button); result.push(button);
@@ -799,7 +803,7 @@ class HUIRoot extends LitElement {
if (curViewConfig?.back_path != null) { if (curViewConfig?.back_path != null) {
navigate(curViewConfig.back_path, { replace: true }); navigate(curViewConfig.back_path, { replace: true });
} else if (history.length > 1) { } else if (history.length > 1) {
history.back(); goBack();
} else if (!views[0].subview) { } else if (!views[0].subview) {
navigate(this.route!.prefix, { replace: true }); navigate(this.route!.prefix, { replace: true });
} else { } else {

View File

@@ -99,12 +99,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
@item-removed=${this._cardRemoved} @item-removed=${this._cardRemoved}
invert-swap invert-swap
> >
<div <div class="container">
class="container ${classMap({
"edit-mode": editMode,
"import-only": this.importOnly,
})}"
>
${repeat( ${repeat(
cardsConfig, cardsConfig,
(cardConfig) => this._getKey(cardConfig), (cardConfig) => this._getKey(cardConfig),
@@ -238,19 +233,6 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
margin: 0 auto; margin: 0 auto;
} }
.container.edit-mode {
padding: 8px;
border-radius: var(--ha-card-border-radius, 12px);
border-start-end-radius: 0;
border: 2px dashed var(--divider-color);
min-height: var(--row-height);
}
.container.import-only {
border: none;
padding: 0 !important;
}
.card { .card {
border-radius: var(--ha-card-border-radius, 12px); border-radius: var(--ha-card-border-radius, 12px);
position: relative; position: relative;

View File

@@ -1,14 +1,8 @@
import { ResizeController } from "@lit-labs/observers/resize-controller"; import { ResizeController } from "@lit-labs/observers/resize-controller";
import { import { mdiEyeOff, mdiViewGridPlus } from "@mdi/js";
mdiDelete,
mdiDrag,
mdiEyeOff,
mdiPencil,
mdiViewGridPlus,
} from "@mdi/js";
import type { PropertyValues } from "lit"; import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat"; import { repeat } from "lit/directives/repeat";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
@@ -20,30 +14,27 @@ import "../../../components/ha-sortable";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import type { LovelaceViewElement } from "../../../data/lovelace"; import type { LovelaceViewElement } from "../../../data/lovelace";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section"; import type {
LovelaceSectionConfig,
LovelaceSectionRawConfig,
} from "../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view"; import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import type { HuiBadge } from "../badges/hui-badge"; import type { HuiBadge } from "../badges/hui-badge";
import "./hui-view-header";
import type { HuiCard } from "../cards/hui-card"; import type { HuiCard } from "../cards/hui-card";
import "../components/hui-badge-edit-mode"; import "../components/hui-badge-edit-mode";
import { import "../components/hui-section-edit-mode";
addSection, import { addSection, moveCard, moveSection } from "../editor/config-util";
deleteSection,
moveCard,
moveSection,
} from "../editor/config-util";
import type { LovelaceCardPath } from "../editor/lovelace-path"; import type { LovelaceCardPath } from "../editor/lovelace-path";
import { import {
findLovelaceContainer,
findLovelaceItems, findLovelaceItems,
getLovelaceContainerPath, getLovelaceContainerPath,
parseLovelaceCardPath, parseLovelaceCardPath,
} from "../editor/lovelace-path"; } from "../editor/lovelace-path";
import { showEditSectionDialog } from "../editor/section-editor/show-edit-section-dialog"; import "../sections/hui-section";
import type { HuiSection } from "../sections/hui-section"; import type { HuiSection } from "../sections/hui-section";
import type { Lovelace } from "../types"; import type { Lovelace } from "../types";
import "./hui-view-header";
export const DEFAULT_MAX_COLUMNS = 4; export const DEFAULT_MAX_COLUMNS = 4;
@@ -59,8 +50,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
@property({ attribute: false }) public isStrategy = false; @property({ attribute: false }) public isStrategy = false;
@property({ attribute: false }) public sections: HuiSection[] = [];
@property({ attribute: false }) public cards: HuiCard[] = []; @property({ attribute: false }) public cards: HuiCard[] = [];
@property({ attribute: false }) public badges: HuiBadge[] = []; @property({ attribute: false }) public badges: HuiBadge[] = [];
@@ -71,6 +60,8 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
@state() _dragging = false; @state() _dragging = false;
@query(".container") private _container?: HTMLElement;
private _columnsController = new ResizeController(this, { private _columnsController = new ResizeController(this, {
callback: (entries) => { callback: (entries) => {
const totalWidth = entries[0]?.contentRect.width; const totalWidth = entries[0]?.contentRect.width;
@@ -101,9 +92,9 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
this._config = config; this._config = config;
} }
private _sectionConfigKeys = new WeakMap<HuiSection, string>(); private _sectionConfigKeys = new WeakMap<LovelaceSectionRawConfig, string>();
private _getSectionKey(section: HuiSection) { private _getSectionKey(section: LovelaceSectionRawConfig) {
if (!this._sectionConfigKeys.has(section)) { if (!this._sectionConfigKeys.has(section)) {
this._sectionConfigKeys.set(section, Math.random().toString()); this._sectionConfigKeys.set(section, Math.random().toString());
} }
@@ -111,52 +102,44 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
} }
private _computeSectionsCount() { private _computeSectionsCount() {
this._sectionColumnCount = this.sections const sections = Array.from(
.filter((section) => !section.hidden) this._container?.querySelectorAll("hui-section") ?? []
.map((section) => section.config.column_span ?? 1) );
.reduce((acc, val) => acc + val, 0);
this._sectionColumnCount =
sections
.filter((section: HuiSection) => !section.hidden)
.map((section) => section.config.column_span ?? 1)
.reduce((acc, val) => acc + val, 0) || 0;
} }
private _sectionVisibilityChanged = () => { private _sectionVisibilityChanged = () => {
this._computeSectionsCount(); this._computeSectionsCount();
}; };
connectedCallback(): void { public updated(changedProps: PropertyValues) {
super.connectedCallback(); super.updated(changedProps);
this.addEventListener(
"section-visibility-changed",
this._sectionVisibilityChanged
);
}
disconnectedCallback(): void { if (changedProps.has("_config")) {
super.disconnectedCallback();
this.removeEventListener(
"section-visibility-changed",
this._sectionVisibilityChanged
);
}
willUpdate(changedProperties: PropertyValues<typeof this>): void {
if (changedProperties.has("sections")) {
this._computeSectionsCount(); this._computeSectionsCount();
} }
} }
protected render() { protected render() {
if (!this.lovelace) return nothing; if (!this.lovelace || !this._config) return nothing;
const sections = this.sections;
const totalSectionCount = const totalSectionCount =
this._sectionColumnCount + (this.lovelace?.editMode ? 1 : 0); this._sectionColumnCount + (this.lovelace?.editMode ? 1 : 0);
const editMode = this.lovelace.editMode; const editMode = this.lovelace.editMode;
const maxColumnCount = this._columnsController.value ?? 1; const maxColumnCount = this._columnsController.value ?? 1;
const sections = this._config.sections || [];
return html` return html`
<div <div
class="wrapper ${classMap({ class="wrapper ${classMap({
"top-margin": Boolean(this._config?.top_margin), "top-margin": Boolean(this._config.top_margin),
})}" })}"
> >
<hui-view-header <hui-view-header
@@ -164,7 +147,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
.badges=${this.badges} .badges=${this.badges}
.lovelace=${this.lovelace} .lovelace=${this.lovelace}
.viewIndex=${this.index} .viewIndex=${this.index}
.config=${this._config?.header} .config=${this._config.header}
style=${styleMap({ style=${styleMap({
"--max-column-count": maxColumnCount, "--max-column-count": maxColumnCount,
})} })}
@@ -179,7 +162,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
> >
<div <div
class="container ${classMap({ class="container ${classMap({
dense: Boolean(this._config?.dense_section_placement), dense: Boolean(this._config.dense_section_placement),
})}" })}"
style=${styleMap({ style=${styleMap({
"--total-section-count": totalSectionCount, "--total-section-count": totalSectionCount,
@@ -188,13 +171,14 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
> >
${repeat( ${repeat(
sections, sections,
(section) => this._getSectionKey(section), (section: LovelaceSectionRawConfig) =>
(section, idx) => { this._getSectionKey(section),
(section: LovelaceSectionRawConfig, idx) => {
const columnSpan = Math.min( const columnSpan = Math.min(
section.config.column_span || 1, section.column_span || 1,
maxColumnCount maxColumnCount
); );
const rowSpan = section.config.row_span || 1; const rowSpan = section.row_span || 1;
return html` return html`
<div <div
@@ -205,41 +189,28 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
})} })}
> >
${ ${
this.lovelace?.editMode editMode
? html` ? html`
<div class="section-header"> <hui-section-edit-mode
${editMode .hass=${this.hass}
? html` .lovelace=${this.lovelace}
<div class="section-actions"> .index=${idx}
<ha-svg-icon .viewIndex=${this.index}
aria-hidden="true" >
class="handle" ${section}
.path=${mdiDrag} </hui-section-edit-mode>
></ha-svg-icon>
<ha-icon-button
.label=${this.hass.localize(
"ui.common.edit"
)}
@click=${this._editSection}
.index=${idx}
.path=${mdiPencil}
></ha-icon-button>
<ha-icon-button
.label=${this.hass.localize(
"ui.common.delete"
)}
@click=${this._deleteSection}
.index=${idx}
.path=${mdiDelete}
></ha-icon-button>
</div>
`
: nothing}
</div>
` `
: nothing : section
} }
${section} <hui-section
.lovelace=${this.lovelace}
.hass=${this.hass}
.config=${this._config!.sections![idx]}
.viewIndex=${this.index}
.index=${idx}
.preview=${this.lovelace?.editMode || false}
@section-visibility-changed=${this._sectionVisibilityChanged}
></hui-section>
</div> </div>
</div> </div>
`; `;
@@ -278,7 +249,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
</ha-sortable> </ha-sortable>
` `
: nothing} : nothing}
${editMode && this._config?.cards?.length ${editMode && this._config.cards?.length
? html` ? html`
<div class="section imported-cards"> <div class="section imported-cards">
<div class="imported-card-header"> <div class="imported-card-header">
@@ -375,48 +346,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
this.lovelace!.saveConfig(newConfig); this.lovelace!.saveConfig(newConfig);
} }
private async _editSection(ev) {
const index = ev.currentTarget.index;
showEditSectionDialog(this, {
lovelace: this.lovelace!,
lovelaceConfig: this.lovelace!.config,
saveConfig: (newConfig) => {
this.lovelace!.saveConfig(newConfig);
},
viewIndex: this.index!,
sectionIndex: index,
});
}
private async _deleteSection(ev) {
const index = ev.currentTarget.index;
const path = [this.index!, index] as [number, number];
const section = findLovelaceContainer(this.lovelace!.config, path);
const cardCount = "cards" in section && section.cards?.length;
if (cardCount) {
const confirm = await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.lovelace.editor.delete_section.title"
),
text: this.hass.localize(
`ui.panel.lovelace.editor.delete_section.text`
),
confirmText: this.hass.localize("ui.common.delete"),
destructive: true,
});
if (!confirm) return;
}
const newConfig = deleteSection(this.lovelace!.config, this.index!, index);
this.lovelace!.saveConfig(newConfig);
}
private _sectionMoved(ev: CustomEvent) { private _sectionMoved(ev: CustomEvent) {
ev.stopPropagation(); ev.stopPropagation();
const { oldIndex, newIndex } = ev.detail; const { oldIndex, newIndex } = ev.detail;
@@ -486,11 +415,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
grid-auto-flow: row dense; grid-auto-flow: row dense;
} }
.handle {
cursor: grab;
padding: 8px;
}
.create-section-container { .create-section-container {
position: relative; position: relative;
display: flex; display: flex;
@@ -574,35 +498,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
); );
} }
.section-header {
position: relative;
height: 34px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.section-actions {
position: absolute;
height: 36px;
bottom: -2px;
right: 0;
inset-inline-end: 0;
inset-inline-start: initial;
opacity: 1;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s ease-in-out;
border-radius: var(--ha-card-border-radius, 12px);
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
background: var(--secondary-background-color);
--mdc-icon-button-size: 36px;
--mdc-icon-size: 20px;
color: var(--primary-text-color);
}
.imported-cards { .imported-cards {
--column-span: var(--column-count); --column-span: var(--column-count);
--row-span: 1; --row-span: 1;

View File

@@ -12,7 +12,6 @@ import type { LovelaceViewElement } from "../../../data/lovelace";
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge"; import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
import { ensureBadgeConfig } from "../../../data/lovelace/config/badge"; import { ensureBadgeConfig } from "../../../data/lovelace/config/badge";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card"; import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import type { import type {
LovelaceViewConfig, LovelaceViewConfig,
LovelaceViewRawConfig, LovelaceViewRawConfig,
@@ -39,9 +38,6 @@ import {
} from "../editor/delete-card"; } from "../editor/delete-card";
import type { LovelaceCardPath } from "../editor/lovelace-path"; import type { LovelaceCardPath } from "../editor/lovelace-path";
import { parseLovelaceCardPath } from "../editor/lovelace-path"; import { parseLovelaceCardPath } from "../editor/lovelace-path";
import { createErrorSectionConfig } from "../sections/hui-error-section";
import "../sections/hui-section";
import type { HuiSection } from "../sections/hui-section";
import { generateLovelaceViewStrategy } from "../strategies/get-strategy"; import { generateLovelaceViewStrategy } from "../strategies/get-strategy";
import type { Lovelace } from "../types"; import type { Lovelace } from "../types";
import { getViewType } from "./get-view-type"; import { getViewType } from "./get-view-type";
@@ -84,8 +80,6 @@ export class HUIView extends ReactiveElement {
@state() private _badges: HuiBadge[] = []; @state() private _badges: HuiBadge[] = [];
@state() private _sections: HuiSection[] = [];
private _layoutElementType?: string; private _layoutElementType?: string;
private _layoutElement?: LovelaceViewElement; private _layoutElement?: LovelaceViewElement;
@@ -128,28 +122,6 @@ export class HUIView extends ReactiveElement {
return element; return element;
} }
// Public to make demo happy
public createSectionElement(sectionConfig: LovelaceSectionConfig) {
const element = document.createElement("hui-section");
element.hass = this.hass;
element.lovelace = this.lovelace;
element.config = sectionConfig;
element.viewIndex = this.index;
element.preview = this.lovelace.editMode;
element.addEventListener(
"ll-rebuild",
(ev: Event) => {
// In edit mode let it go to hui-root and rebuild whole view.
if (!this.lovelace!.editMode) {
ev.stopPropagation();
this._rebuildSection(element, sectionConfig);
}
},
{ once: true }
);
return element;
}
protected createRenderRoot() { protected createRenderRoot() {
return this; return this;
} }
@@ -261,14 +233,6 @@ export class HUIView extends ReactiveElement {
element.hass = this.hass; element.hass = this.hass;
}); });
this._sections.forEach((element) => {
try {
element.hass = this.hass;
} catch (e: any) {
this._rebuildSection(element, createErrorSectionConfig(e.message));
}
});
this._layoutElement.hass = this.hass; this._layoutElement.hass = this.hass;
} }
if (changedProperties.has("narrow")) { if (changedProperties.has("narrow")) {
@@ -276,15 +240,6 @@ export class HUIView extends ReactiveElement {
} }
if (changedProperties.has("lovelace")) { if (changedProperties.has("lovelace")) {
this._layoutElement.lovelace = this.lovelace; this._layoutElement.lovelace = this.lovelace;
this._sections.forEach((element) => {
try {
element.hass = this.hass;
element.lovelace = this.lovelace;
element.preview = this.lovelace.editMode;
} catch (e: any) {
this._rebuildSection(element, createErrorSectionConfig(e.message));
}
});
this._cards.forEach((element) => { this._cards.forEach((element) => {
element.preview = this.lovelace.editMode; element.preview = this.lovelace.editMode;
}); });
@@ -335,7 +290,6 @@ export class HUIView extends ReactiveElement {
this._layoutElementConfig = viewConfig; this._layoutElementConfig = viewConfig;
this._createBadges(viewConfig); this._createBadges(viewConfig);
this._createCards(viewConfig); this._createCards(viewConfig);
this._createSections(viewConfig);
this._layoutElement!.isStrategy = isStrategy; this._layoutElement!.isStrategy = isStrategy;
this._layoutElement!.hass = this.hass; this._layoutElement!.hass = this.hass;
this._layoutElement!.narrow = this.narrow; this._layoutElement!.narrow = this.narrow;
@@ -343,7 +297,6 @@ export class HUIView extends ReactiveElement {
this._layoutElement!.index = this.index; this._layoutElement!.index = this.index;
this._layoutElement!.cards = this._cards; this._layoutElement!.cards = this._cards;
this._layoutElement!.badges = this._badges; this._layoutElement!.badges = this._badges;
this._layoutElement!.sections = this._sections;
if (addLayoutElement) { if (addLayoutElement) {
while (this.lastChild) { while (this.lastChild) {
@@ -474,36 +427,6 @@ export class HUIView extends ReactiveElement {
return element; return element;
}); });
} }
private _createSections(config: LovelaceViewConfig): void {
if (!config || !config.sections || !Array.isArray(config.sections)) {
this._sections = [];
return;
}
this._sections = config.sections.map((sectionConfig, index) => {
const element = this.createSectionElement(sectionConfig);
element.index = index;
return element;
});
}
private _rebuildSection(
sectionElToReplace: HuiSection,
config: LovelaceSectionConfig
): void {
const newSectionEl = this.createSectionElement(config);
newSectionEl.index = sectionElToReplace.index;
if (sectionElToReplace.parentElement) {
sectionElToReplace.parentElement!.replaceChild(
newSectionEl,
sectionElToReplace
);
}
this._sections = this._sections!.map((curSectionEl) =>
curSectionEl === sectionElToReplace ? newSectionEl : curSectionEl
);
}
} }
declare global { declare global {

View File

@@ -661,35 +661,20 @@
}, },
"target-picker": { "target-picker": {
"expand": "Expand", "expand": "Expand",
"collapse": "Collapse",
"expand_floor_id": "Split this floor into separate areas.", "expand_floor_id": "Split this floor into separate areas.",
"expand_area_id": "Split this area into separate devices and entities.", "expand_area_id": "Split this area into separate devices and entities.",
"expand_device_id": "Split this device into separate entities.", "expand_device_id": "Split this device into separate entities.",
"expand_label_id": "Split this label into separate areas, devices and entities.", "expand_label_id": "Split this label into separate areas, devices and entities.",
"remove": "Remove", "remove": "Remove",
"remove_floor_id": "Remove floor", "remove_floor_id": "Remove floor",
"remove_floors": "Remove floors",
"remove_area_id": "Remove area", "remove_area_id": "Remove area",
"remove_areas": "Remove areas",
"remove_device_id": "Remove device", "remove_device_id": "Remove device",
"remove_devices": "Remove devices",
"remove_entity_id": "Remove entity", "remove_entity_id": "Remove entity",
"remove_entitys": "Remove entities",
"remove_label_id": "Remove label", "remove_label_id": "Remove label",
"remove_labels": "Remove labels",
"add_area_id": "Choose area", "add_area_id": "Choose area",
"add_device_id": "Choose device", "add_device_id": "Choose device",
"add_entity_id": "Choose entity", "add_entity_id": "Choose entity",
"add_label_id": "Choose label", "add_label_id": "Choose label"
"devices_count": "{count} {count, plural,\n one {device}\n other {devices}\n}",
"entities_count": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
"selected": {
"entity": "Entities: {count}",
"device": "Devices: {count}",
"area": "Areas: {count}",
"label": "Labels: {count}",
"floor": "Floors: {count}"
}
}, },
"subpage-data-table": { "subpage-data-table": {
"filters": "Filters", "filters": "Filters",

177
yarn.lock
View File

@@ -1644,7 +1644,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@floating-ui/dom@npm:^1.6.12, @floating-ui/dom@npm:^1.6.13": "@floating-ui/dom@npm:^1.6.13":
version: 1.7.3 version: 1.7.3
resolution: "@floating-ui/dom@npm:1.7.3" resolution: "@floating-ui/dom@npm:1.7.3"
dependencies: dependencies:
@@ -1905,9 +1905,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@home-assistant/webawesome@npm:3.0.0-beta.4.ha.2": "@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3":
version: 3.0.0-beta.4.ha.2 version: 3.0.0-beta.4.ha.3
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.4.ha.2" resolution: "@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3"
dependencies: dependencies:
"@ctrl/tinycolor": "npm:^4.1.0" "@ctrl/tinycolor": "npm:^4.1.0"
"@floating-ui/dom": "npm:^1.6.13" "@floating-ui/dom": "npm:^1.6.13"
@@ -1919,7 +1919,7 @@ __metadata:
nanoid: "npm:^5.1.5" nanoid: "npm:^5.1.5"
qr-creator: "npm:^1.0.0" qr-creator: "npm:^1.0.0"
style-observer: "npm:^0.0.7" style-observer: "npm:^0.0.7"
checksum: 10/0ac66d43050571e2b86bb7b0181d428aa2a064e25745075b207a8fe96d873398eaead663172130dfe8d9ac0be575028f8f9f6b9f8a9cd12f81c8c82e9f60a0e9 checksum: 10/b9241821ed471ccbad86b0ea4697a2d41395f05fdc26f46e5edbc7f6b5eeab5d248251ef702326312ded00d5bf850ce0dcdcf7cd5e2e542b9d9cb9a84f3726da
languageName: node languageName: node
linkType: hard linkType: hard
@@ -2326,7 +2326,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@lit/react@npm:^1.0.6, @lit/react@npm:^1.0.8": "@lit/react@npm:^1.0.8":
version: 1.0.8 version: 1.0.8
resolution: "@lit/react@npm:1.0.8" resolution: "@lit/react@npm:1.0.8"
peerDependencies: peerDependencies:
@@ -4170,22 +4170,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@shoelace-style/shoelace@npm:2.20.1":
version: 2.20.1
resolution: "@shoelace-style/shoelace@npm:2.20.1"
dependencies:
"@ctrl/tinycolor": "npm:^4.1.0"
"@floating-ui/dom": "npm:^1.6.12"
"@lit/react": "npm:^1.0.6"
"@shoelace-style/animations": "npm:^1.2.0"
"@shoelace-style/localize": "npm:^3.2.1"
composed-offset-position: "npm:^0.0.6"
lit: "npm:^3.2.1"
qr-creator: "npm:^1.0.0"
checksum: 10/c3aabeac03d5fd5bc43799783562ab09c92bae98efbc43a931c7dcec608acc393771b6ed0da3f29e08570bb9d9a9e3bff7637cbf6f79ba7aa439f6641da4eb7c
languageName: node
linkType: hard
"@sindresorhus/merge-streams@npm:^2.1.0": "@sindresorhus/merge-streams@npm:^2.1.0":
version: 2.3.0 version: 2.3.0
resolution: "@sindresorhus/merge-streams@npm:2.3.0" resolution: "@sindresorhus/merge-streams@npm:2.3.0"
@@ -4974,106 +4958,106 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.42.0": "@typescript-eslint/eslint-plugin@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.42.0" resolution: "@typescript-eslint/eslint-plugin@npm:8.43.0"
dependencies: dependencies:
"@eslint-community/regexpp": "npm:^4.10.0" "@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.42.0" "@typescript-eslint/scope-manager": "npm:8.43.0"
"@typescript-eslint/type-utils": "npm:8.42.0" "@typescript-eslint/type-utils": "npm:8.43.0"
"@typescript-eslint/utils": "npm:8.42.0" "@typescript-eslint/utils": "npm:8.43.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0" "@typescript-eslint/visitor-keys": "npm:8.43.0"
graphemer: "npm:^1.4.0" graphemer: "npm:^1.4.0"
ignore: "npm:^7.0.0" ignore: "npm:^7.0.0"
natural-compare: "npm:^1.4.0" natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.1.0" ts-api-utils: "npm:^2.1.0"
peerDependencies: peerDependencies:
"@typescript-eslint/parser": ^8.42.0 "@typescript-eslint/parser": ^8.43.0
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0" typescript: ">=4.8.4 <6.0.0"
checksum: 10/fb5b0e0785f9fa9d5ef88e78ff189334b2d1c558efd7b5063508d50275224a8aa38d4af0478228b90d6be6620289384a8d814f05e0af8c952c204515c0f3514e checksum: 10/0e9d31f6c7d69f152c8ff32ca501f03834b44945f4587419e26f821841dd1c2705db5648f1bef68985f8c8d7300ca63b9c6dee4e0e756f337f96f60372c7b1f7
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/parser@npm:8.42.0": "@typescript-eslint/parser@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@typescript-eslint/parser@npm:8.42.0" resolution: "@typescript-eslint/parser@npm:8.43.0"
dependencies: dependencies:
"@typescript-eslint/scope-manager": "npm:8.42.0" "@typescript-eslint/scope-manager": "npm:8.43.0"
"@typescript-eslint/types": "npm:8.42.0" "@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0" "@typescript-eslint/typescript-estree": "npm:8.43.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0" "@typescript-eslint/visitor-keys": "npm:8.43.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0" typescript: ">=4.8.4 <6.0.0"
checksum: 10/25eb2d08c118742dc01c2aa279ea4ba2d277e2d9a042ffd4f9bda9e94d7ff2aa90b63aad1204a82617a5c63ddd3dd553d927944cd9c8345826484d0d523cf7ad checksum: 10/cb3bd8bd48627cd502bb3cc5bb444e32c99d47ac41c092c457fcf0109f4a67491a42537abee51eee13498345f5dbd00dd11ccbf7a1d782a81d5ec9ee3e5df3ad
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/project-service@npm:8.42.0": "@typescript-eslint/project-service@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@typescript-eslint/project-service@npm:8.42.0" resolution: "@typescript-eslint/project-service@npm:8.43.0"
dependencies: dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.42.0" "@typescript-eslint/tsconfig-utils": "npm:^8.43.0"
"@typescript-eslint/types": "npm:^8.42.0" "@typescript-eslint/types": "npm:^8.43.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
peerDependencies: peerDependencies:
typescript: ">=4.8.4 <6.0.0" typescript: ">=4.8.4 <6.0.0"
checksum: 10/3e91fd4b4d60edd6fe3e108e8e75947de8aa060aab1de63c23017e8afeca72ef405faa6fcdd17e8aa0023261a81135d095072dc31343c57395e50450258d9fa5 checksum: 10/ab22f5d6b72dc4f46e7e0e01df549702b60c51941072a4a2a803f006134cad49687a4444f423db1d0d9e84c57f84dbc1458b5db6866b39a292412db96c756846
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/scope-manager@npm:8.42.0": "@typescript-eslint/scope-manager@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@typescript-eslint/scope-manager@npm:8.42.0" resolution: "@typescript-eslint/scope-manager@npm:8.43.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:8.42.0" "@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0" "@typescript-eslint/visitor-keys": "npm:8.43.0"
checksum: 10/81be2d908a9d2d83bc9fe5e9219b04277b9fa466bfa7faf45dc076e4b33b39db2fb99b34b8832e329c7db48ddfdc7b78f6c92b564cd6eec99e124d3feaad8645 checksum: 10/a975ae96bdc019510e1dedd672f1877e6389837774d221240d37196610b307dc59f845f33e23dfff9a96de6e2c3b75e5571a8acc145238408c1e06286efc9de2
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.42.0, @typescript-eslint/tsconfig-utils@npm:^8.42.0": "@typescript-eslint/tsconfig-utils@npm:8.43.0, @typescript-eslint/tsconfig-utils@npm:^8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.42.0" resolution: "@typescript-eslint/tsconfig-utils@npm:8.43.0"
peerDependencies: peerDependencies:
typescript: ">=4.8.4 <6.0.0" typescript: ">=4.8.4 <6.0.0"
checksum: 10/927aa127983a62ddcbfbcd18806fd278e0bf18fade3cca658946f9ff4915e6a5c5cc85926afaa490512c88dd2950b2059f22b50b6d1f4461c9dbd755a4c71c1c checksum: 10/20cb7b553eba44a8c4b4af2d0cabbcff248494b8c87243be7fcd1bb00846344f0bbc5b2353027d8e9053ee3e0c3b491cbf1c024f9f60b7e370220e7b0620b96f
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/type-utils@npm:8.42.0": "@typescript-eslint/type-utils@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@typescript-eslint/type-utils@npm:8.42.0" resolution: "@typescript-eslint/type-utils@npm:8.43.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:8.42.0" "@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0" "@typescript-eslint/typescript-estree": "npm:8.43.0"
"@typescript-eslint/utils": "npm:8.42.0" "@typescript-eslint/utils": "npm:8.43.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.1.0" ts-api-utils: "npm:^2.1.0"
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0" typescript: ">=4.8.4 <6.0.0"
checksum: 10/8d876bbd23c956b604d973c49720060c251f4d8cab255f1fd04826a9a1e3ab7c1310400d49d9ec6cdac3288d7a23cd9fb48d42777651ba53c02b5e1a34efd6e9 checksum: 10/b82184ba5079b95cc7775ddda3f40a994b0594375c0e5597d89db0e74e4e8d0e4b8a29fea646c6ed126af04729a7caa1052c0726e8f170a4106802486879a00b
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/types@npm:8.42.0, @typescript-eslint/types@npm:^8.42.0": "@typescript-eslint/types@npm:8.43.0, @typescript-eslint/types@npm:^8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@typescript-eslint/types@npm:8.42.0" resolution: "@typescript-eslint/types@npm:8.43.0"
checksum: 10/7c39a35e5bb7083070872edc797ea60a3d6ceff0e3bdf85701919b71da83a51963562053a4b35c9e2a2b08c138fb595e14bc0b5c450e671a26059b58f8d8b4f4 checksum: 10/f2c3b3f9cfb680dcf52b686b978176ea095dfb16db3c720149784f40a34c73c861fc57a707b64658bc0409d54ecd0e0d23d5bc41ba7d3b94db47772e2609062a
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/typescript-estree@npm:8.42.0": "@typescript-eslint/typescript-estree@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@typescript-eslint/typescript-estree@npm:8.42.0" resolution: "@typescript-eslint/typescript-estree@npm:8.43.0"
dependencies: dependencies:
"@typescript-eslint/project-service": "npm:8.42.0" "@typescript-eslint/project-service": "npm:8.43.0"
"@typescript-eslint/tsconfig-utils": "npm:8.42.0" "@typescript-eslint/tsconfig-utils": "npm:8.43.0"
"@typescript-eslint/types": "npm:8.42.0" "@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/visitor-keys": "npm:8.42.0" "@typescript-eslint/visitor-keys": "npm:8.43.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2" fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3" is-glob: "npm:^4.0.3"
@@ -5082,32 +5066,32 @@ __metadata:
ts-api-utils: "npm:^2.1.0" ts-api-utils: "npm:^2.1.0"
peerDependencies: peerDependencies:
typescript: ">=4.8.4 <6.0.0" typescript: ">=4.8.4 <6.0.0"
checksum: 10/9bb5df97a2ac31e6e3ee6941e10702498a76d23235ba28a23d93e09aa75a2cbcd40dc74935d86706c8e2e55e1a8b6a34bb9fb234461920ed3d8a5abed68ba36b checksum: 10/d2a054b6279107150e9c15569e18c861a89e504caa0a14716a2c73a09174814a993748ff637941757e3e9af033a7eeed511c8dcf17f25d3b3322245af35fd1d0
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/utils@npm:8.42.0": "@typescript-eslint/utils@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@typescript-eslint/utils@npm:8.42.0" resolution: "@typescript-eslint/utils@npm:8.43.0"
dependencies: dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.0" "@eslint-community/eslint-utils": "npm:^4.7.0"
"@typescript-eslint/scope-manager": "npm:8.42.0" "@typescript-eslint/scope-manager": "npm:8.43.0"
"@typescript-eslint/types": "npm:8.42.0" "@typescript-eslint/types": "npm:8.43.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0" "@typescript-eslint/typescript-estree": "npm:8.43.0"
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0" typescript: ">=4.8.4 <6.0.0"
checksum: 10/41c6c0d01c414c94d7109e21deee73b416547b3be26240d0237a3004c6198f146afefc75feee5333bc957ece6a0856518750655e794fd68c96feec1001edbfe8 checksum: 10/2c04182084bf3ba391198c723635ce50557ec73b1ebcc7970f0281c345db92aebdbbd1202e9bb3152b3c62a61b043907dde385bb44fce33841c52257c18b0064
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/visitor-keys@npm:8.42.0": "@typescript-eslint/visitor-keys@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@typescript-eslint/visitor-keys@npm:8.42.0" resolution: "@typescript-eslint/visitor-keys@npm:8.43.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:8.42.0" "@typescript-eslint/types": "npm:8.43.0"
eslint-visitor-keys: "npm:^4.2.1" eslint-visitor-keys: "npm:^4.2.1"
checksum: 10/ef3aeabf7b01eb72e176053a4fe7a4c4f0769a9f58d1f7a920c97d365305b950c402ad34227209781996ae187652ccf0f47c31015f992c502b5fa898a9d44bd5 checksum: 10/d694425dd8592b9452640a82d638f4161ac880a8825f1cd6ce41b227bacff3a2e9106238344cbb85cb432593caf892bf4dcca0b73dcc884449ba88ee0ebec94a
languageName: node languageName: node
linkType: hard linkType: hard
@@ -9334,7 +9318,7 @@ __metadata:
"@fullcalendar/list": "npm:6.1.19" "@fullcalendar/list": "npm:6.1.19"
"@fullcalendar/luxon3": "npm:6.1.19" "@fullcalendar/luxon3": "npm:6.1.19"
"@fullcalendar/timegrid": "npm:6.1.19" "@fullcalendar/timegrid": "npm:6.1.19"
"@home-assistant/webawesome": "npm:3.0.0-beta.4.ha.2" "@home-assistant/webawesome": "npm:3.0.0-beta.4.ha.3"
"@lezer/highlight": "npm:1.2.1" "@lezer/highlight": "npm:1.2.1"
"@lit-labs/motion": "npm:1.0.9" "@lit-labs/motion": "npm:1.0.9"
"@lit-labs/observers": "npm:2.0.6" "@lit-labs/observers": "npm:2.0.6"
@@ -9374,7 +9358,6 @@ __metadata:
"@rsdoctor/rspack-plugin": "npm:1.2.3" "@rsdoctor/rspack-plugin": "npm:1.2.3"
"@rspack/core": "npm:1.5.2" "@rspack/core": "npm:1.5.2"
"@rspack/dev-server": "npm:1.1.4" "@rspack/dev-server": "npm:1.1.4"
"@shoelace-style/shoelace": "npm:2.20.1"
"@swc/helpers": "npm:0.5.17" "@swc/helpers": "npm:0.5.17"
"@thomasloven/round-slider": "npm:0.6.0" "@thomasloven/round-slider": "npm:0.6.0"
"@tsparticles/engine": "npm:3.9.1" "@tsparticles/engine": "npm:3.9.1"
@@ -9483,7 +9466,7 @@ __metadata:
tinykeys: "npm:3.0.0" tinykeys: "npm:3.0.0"
ts-lit-plugin: "npm:2.0.2" ts-lit-plugin: "npm:2.0.2"
typescript: "npm:5.9.2" typescript: "npm:5.9.2"
typescript-eslint: "npm:8.42.0" typescript-eslint: "npm:8.43.0"
ua-parser-js: "npm:2.0.4" ua-parser-js: "npm:2.0.4"
vite-tsconfig-paths: "npm:5.1.4" vite-tsconfig-paths: "npm:5.1.4"
vitest: "npm:3.2.4" vitest: "npm:3.2.4"
@@ -14515,18 +14498,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"typescript-eslint@npm:8.42.0": "typescript-eslint@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "typescript-eslint@npm:8.42.0" resolution: "typescript-eslint@npm:8.43.0"
dependencies: dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.42.0" "@typescript-eslint/eslint-plugin": "npm:8.43.0"
"@typescript-eslint/parser": "npm:8.42.0" "@typescript-eslint/parser": "npm:8.43.0"
"@typescript-eslint/typescript-estree": "npm:8.42.0" "@typescript-eslint/typescript-estree": "npm:8.43.0"
"@typescript-eslint/utils": "npm:8.42.0" "@typescript-eslint/utils": "npm:8.43.0"
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0" typescript: ">=4.8.4 <6.0.0"
checksum: 10/7f71501823b2c1e87e89ff00d6d8eb40c7514630dbb6b7b44c4dd830c95709357270763df2d711a8ea7bb0b58bd69534f15b01db4550dc6e745df8fec8f6a3ae checksum: 10/51bd655f43aa6363932dee0d290fb26752875afdf6360a9940bc1c744b67ef82a1715392a65490ba4aa8a0490ad0ae0eb8903d831949f68af6a4e89e01a85b1c
languageName: node languageName: node
linkType: hard linkType: hard