Compare commits

...

28 Commits

Author SHA1 Message Date
Paul Bottein
82b28b547a Fix control select menu color in ios (#29892) 2026-02-27 17:26:04 +01:00
Bram Kragten
61c2c750b4 Fix overflow for icon buttons (#29891) 2026-02-27 15:44:21 +00:00
Petar Petrov
117690ee70 Fix sensor card graph not updating when value is unchanged (#29889) 2026-02-27 15:41:54 +00:00
Petar Petrov
e753de85eb Make hui-sections-view always fill the screen so footer is at the bottom (#29890) 2026-02-27 15:39:21 +00:00
Paul Bottein
a240019968 Add render icon property to ha-control-select-menu (#29881) 2026-02-27 16:23:58 +01:00
Petar Petrov
0bdf4b8777 Fix monetary device class state display with non-ISO 4217 currency symbols (#29887) 2026-02-27 14:59:14 +01:00
renovate[bot]
6337828ed8 Update dependency barcode-detector to v3.1.0 (#29886)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 15:46:29 +02:00
Aidan Timson
b8e5af652b Add audits and yaml mode to more info details (#29854)
* Add audits and yaml mode to more info details

* Reset yaml mode on back

* Use mapped array for state entries

* Typo

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Memoize

* Rename

* Fix

* Format audits in normal mode

* Refactor, dont pass hass

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2026-02-27 14:45:55 +01:00
Petar Petrov
e4ae29e8b5 Fix energy compare tooltip showing wrong year (#29885) 2026-02-27 14:37:52 +01:00
Aidan Timson
08231dbbb0 Use large width on system log dialogs (#29879) 2026-02-27 12:46:10 +01:00
Paul Bottein
0ca656933d Revert "Add render icon property to ha-control-select-menu"
This reverts commit b23cf8eba4.
2026-02-27 12:21:23 +01:00
Paul Bottein
b23cf8eba4 Add render icon property to ha-control-select-menu 2026-02-27 12:20:52 +01:00
Robert Resch
61b546415d Revert "Add vacuum mapping not configured issue" (#29876) 2026-02-27 11:18:49 +01:00
Brandon Chen
4e1b709303 Fix YAML content invisible in dark mode for conversation debug result… (#29874) 2026-02-27 09:11:28 +01:00
renovate[bot]
34e65b302d Update dependency typescript-eslint to v8.56.1 (#29868)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 18:08:04 +00:00
renovate[bot]
336d0e1b9d Update dependency @html-eslint/eslint-plugin to v0.57.0 (#29863)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-26 17:25:10 +01:00
Paul Bottein
58d4cf8d84 Fix scrollbar in 2026.3 (#29865) 2026-02-26 16:44:12 +01:00
Aidan Timson
d3453aff37 Add missing theming variable support to dialog and bottom sheet (#29857) 2026-02-26 16:43:20 +01:00
Aidan Timson
64ff2e414c Add thread configuration my link (#29861) 2026-02-26 15:06:46 +00:00
Wendelin
2ca25c980f Fix quick search icon size (#29858) 2026-02-26 15:59:27 +01:00
Aidan Timson
73d93bc601 Add matter configuration my link (#29859) 2026-02-26 14:41:43 +00:00
Wendelin
5ca6a8aced Fix ha-icon-button-toggle selected style (#29856) 2026-02-26 13:02:12 +00:00
Aidan Timson
7ff4993e0b Fix esc closing dialogs with prevent scrim close (#29851) 2026-02-26 13:20:05 +02:00
Norbert Rittel
4e6fbacccc Remove trailing periods from "Learn more" etc. links / tooltips (#29835) 2026-02-26 10:38:54 +00:00
Petar Petrov
2958d49e36 Convert Energy Now tiles to badges (#29845) 2026-02-26 10:38:01 +00:00
Norbert Rittel
92289dc7ea Improve "Create a new … helper" option in entity picker (#29853) 2026-02-26 10:34:42 +00:00
Petar Petrov
f6c1a890e4 Dynamically calculate the date range picker's vertical opening direction (#29850) 2026-02-26 09:33:34 +00:00
Wendelin
d06321ed43 Fix protocols dashboards fab padding (#29847) 2026-02-26 10:31:50 +02:00
53 changed files with 987 additions and 797 deletions

View File

@@ -515,6 +515,14 @@ export class DemoHaAdaptiveDialog extends LitElement {
<td><code>--ha-dialog-surface-background</code></td>
<td>Dialog/sheet background color.</td>
</tr>
<tr>
<td><code>--ha-dialog-surface-backdrop-filter</code></td>
<td>Dialog/sheet surface backdrop filter.</td>
</tr>
<tr>
<td><code>--dialog-box-shadow</code></td>
<td>Dialog surface box shadow (dialog mode only).</td>
</tr>
<tr>
<td><code>--ha-dialog-border-radius</code></td>
<td>Border radius of the dialog surface (dialog mode only).</td>
@@ -527,6 +535,34 @@ export class DemoHaAdaptiveDialog extends LitElement {
<td><code>--ha-dialog-hide-duration</code></td>
<td>Hide animation duration (dialog mode only).</td>
</tr>
<tr>
<td><code>--ha-dialog-scrim-backdrop-filter</code></td>
<td>Dialog/sheet scrim backdrop filter.</td>
</tr>
<tr>
<td><code>--dialog-backdrop-filter</code></td>
<td>Dialog/sheet scrim backdrop filter (legacy fallback).</td>
</tr>
<tr>
<td><code>--mdc-dialog-scrim-color</code></td>
<td>Dialog/sheet scrim color (legacy compatibility).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-surface-background</code></td>
<td>Bottom sheet background color (sheet mode only).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-surface-backdrop-filter</code></td>
<td>Bottom sheet surface backdrop filter (sheet mode only).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-scrim-backdrop-filter</code></td>
<td>Bottom sheet scrim backdrop filter (sheet mode only).</td>
</tr>
<tr>
<td><code>--ha-bottom-sheet-scrim-color</code></td>
<td>Bottom sheet scrim color (sheet mode only).</td>
</tr>
</tbody>
</table>

View File

@@ -380,13 +380,29 @@ export class DemoHaDialog extends LitElement {
<td><code>--ha-dialog-surface-background</code></td>
<td>Dialog background color.</td>
</tr>
<tr>
<td><code>--ha-dialog-surface-backdrop-filter</code></td>
<td>Backdrop filter applied to the dialog surface.</td>
</tr>
<tr>
<td><code>--dialog-box-shadow</code></td>
<td>Dialog surface box shadow.</td>
</tr>
<tr>
<td><code>--ha-dialog-border-radius</code></td>
<td>Border radius of the dialog surface.</td>
</tr>
<tr>
<td><code>--dialog-z-index</code></td>
<td>Z-index for the dialog.</td>
<td><code>--ha-dialog-scrim-backdrop-filter</code></td>
<td>Backdrop filter applied to the dialog scrim.</td>
</tr>
<tr>
<td><code>--dialog-backdrop-filter</code></td>
<td>Legacy fallback for the dialog scrim backdrop filter.</td>
</tr>
<tr>
<td><code>--mdc-dialog-scrim-color</code></td>
<td>Dialog scrim color (legacy compatibility).</td>
</tr>
<tr>
<td><code>--dialog-surface-margin-top</code></td>

View File

@@ -92,7 +92,7 @@
"@webcomponents/scoped-custom-element-registry": "0.0.10",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"barcode-detector": "3.0.8",
"barcode-detector": "3.1.0",
"color-name": "2.1.0",
"comlink": "4.4.2",
"core-js": "3.48.0",
@@ -149,7 +149,7 @@
"@babel/plugin-transform-runtime": "7.29.0",
"@babel/preset-env": "7.29.0",
"@bundle-stats/plugin-webpack-filter": "4.21.10",
"@html-eslint/eslint-plugin": "0.56.0",
"@html-eslint/eslint-plugin": "0.57.0",
"@lokalise/node-api": "15.6.1",
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.1.0",
@@ -214,7 +214,7 @@
"terser-webpack-plugin": "5.3.16",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.3",
"typescript-eslint": "8.56.0",
"typescript-eslint": "8.56.1",
"vite-tsconfig-paths": "6.1.1",
"vitest": "4.0.18",
"webpack-stats-plugin": "1.1.3",

View File

@@ -133,33 +133,34 @@ const computeStateToPartsFromEntityAttributes = (
),
});
} catch (_err) {
// fallback to default
// fallback to default numeric formatting below
}
const TYPE_MAP: Record<string, ValuePart["type"]> = {
integer: "value",
group: "value",
decimal: "value",
fraction: "value",
literal: "literal",
currency: "unit",
};
if (parts.length) {
const TYPE_MAP: Record<string, ValuePart["type"]> = {
integer: "value",
group: "value",
decimal: "value",
fraction: "value",
literal: "literal",
currency: "unit",
};
const valueParts: ValuePart[] = [];
const valueParts: ValuePart[] = [];
for (const part of parts) {
const type = TYPE_MAP[part.type];
if (!type) continue;
const last = valueParts[valueParts.length - 1];
// Merge consecutive numeric parts (e.g. "1" + "," + "234" + "." + "56" → "1,234.56")
if (type === "value" && last?.type === "value") {
last.value += part.value;
} else {
valueParts.push({ type, value: part.value });
for (const part of parts) {
const type = TYPE_MAP[part.type];
if (!type) continue;
const last = valueParts[valueParts.length - 1];
// Merge consecutive numeric parts (e.g. "1" + "," + "234" + "." + "56" → "1,234.56")
if (type === "value" && last?.type === "value") {
last.value += part.value;
} else {
valueParts.push({ type, value: part.value });
}
}
return valueParts;
}
return valueParts;
}
// default processing of numeric values

View File

@@ -31,9 +31,18 @@ type DialogSheetMode = "dialog" | "bottom-sheet";
* @slot footer - Dialog/sheet footer content.
*
* @cssprop --ha-dialog-surface-background - Dialog/sheet background color.
* @cssprop --ha-dialog-surface-backdrop-filter - Dialog/sheet backdrop filter.
* @cssprop --dialog-box-shadow - Dialog box shadow (dialog mode only).
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface (dialog mode only).
* @cssprop --ha-dialog-show-duration - Show animation duration (dialog mode only).
* @cssprop --ha-dialog-hide-duration - Hide animation duration (dialog mode only).
* @cssprop --ha-dialog-scrim-backdrop-filter - Dialog/sheet scrim backdrop filter.
* @cssprop --dialog-backdrop-filter - Dialog/sheet scrim backdrop filter (legacy).
* @cssprop --mdc-dialog-scrim-color - Dialog/sheet scrim color (legacy).
* @cssprop --ha-bottom-sheet-surface-background - Bottom sheet background color (sheet mode only).
* @cssprop --ha-bottom-sheet-surface-backdrop-filter - Bottom sheet backdrop filter (sheet mode only).
* @cssprop --ha-bottom-sheet-scrim-backdrop-filter - Bottom sheet scrim backdrop filter (sheet mode only).
* @cssprop --ha-bottom-sheet-scrim-color - Bottom sheet scrim color (sheet mode only).
*
* @attr {boolean} open - Controls the dialog/sheet open state.
* @attr {("alert"|"standard")} type - Dialog type (dialog mode only). Defaults to "standard".

View File

@@ -25,6 +25,27 @@ const SWIPE_LOCKED_COMPONENTS = new Set([
const SWIPE_LOCKED_CLASSES = new Set(["volume-slider-container", "forecast"]);
/**
* Home Assistant bottom sheet component.
*
* @element ha-bottom-sheet
* @extends {LitElement}
*
* @cssprop --ha-bottom-sheet-height - Preferred height of the bottom sheet.
* @cssprop --ha-bottom-sheet-max-height - Maximum height of the bottom sheet.
* @cssprop --ha-bottom-sheet-max-width - Maximum width of the bottom sheet.
* @cssprop --ha-bottom-sheet-border-radius - Top border radius of the bottom sheet.
* @cssprop --ha-bottom-sheet-surface-background - Bottom sheet background color.
* @cssprop --ha-bottom-sheet-surface-backdrop-filter - Bottom sheet surface backdrop filter.
* @cssprop --ha-bottom-sheet-scrim-backdrop-filter - Bottom sheet scrim backdrop filter.
* @cssprop --ha-bottom-sheet-scrim-color - Bottom sheet scrim color.
*
* @cssprop --ha-dialog-surface-background - Bottom sheet background color fallback.
* @cssprop --ha-dialog-surface-backdrop-filter - Bottom sheet surface backdrop filter fallback.
* @cssprop --ha-dialog-scrim-backdrop-filter - Bottom sheet scrim backdrop filter fallback.
* @cssprop --dialog-backdrop-filter - Bottom sheet scrim backdrop filter legacy fallback.
* @cssprop --mdc-dialog-scrim-color - Bottom sheet scrim color legacy fallback.
*/
@customElement("ha-bottom-sheet")
export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
@property({ attribute: false }) public hass?: HomeAssistant;
@@ -141,6 +162,9 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
private _handleKeyDown = (ev: KeyboardEvent) => {
if (ev.key === "Escape") {
this._escapePressed = true;
if (this.preventScrimClose) {
ev.preventDefault();
}
ev.stopPropagation();
(ev.currentTarget as WaDrawer).open = false;
}
@@ -382,6 +406,26 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
transform: var(--dialog-transform);
transition: var(--dialog-transition);
}
wa-drawer::part(dialog)::backdrop {
-webkit-backdrop-filter: var(
--ha-bottom-sheet-scrim-backdrop-filter,
var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
)
);
backdrop-filter: var(
--ha-bottom-sheet-scrim-backdrop-filter,
var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
)
);
background-color: var(
--ha-bottom-sheet-scrim-color,
var(--mdc-dialog-scrim-color, none)
);
}
wa-drawer::part(body) {
max-width: var(--ha-bottom-sheet-max-width);
width: 100%;
@@ -396,7 +440,18 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
);
background-color: var(
--ha-bottom-sheet-surface-background,
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
)
);
-webkit-backdrop-filter: var(
--ha-bottom-sheet-surface-backdrop-filter,
var(--ha-dialog-surface-backdrop-filter, none)
);
backdrop-filter: var(
--ha-bottom-sheet-surface-backdrop-filter,
var(--ha-dialog-surface-backdrop-filter, none)
);
padding: var(
--ha-bottom-sheet-padding,

View File

@@ -245,7 +245,7 @@ export class HaButton extends Button {
}
.label {
overflow: hidden;
overflow: var(--ha-button-label-overflow, hidden);
text-overflow: ellipsis;
padding: var(--ha-space-1) 0;
}

View File

@@ -1,11 +1,9 @@
import { mdiMenuDown } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import type { HomeAssistant } from "../types";
import "./ha-attribute-icon";
import "./ha-dropdown";
import "./ha-dropdown-item";
import "./ha-icon";
@@ -16,17 +14,10 @@ export interface SelectOption {
value: string;
iconPath?: string;
icon?: string;
attributeIcon?: {
stateObj: HassEntity;
attribute: string;
attributeValue?: string;
};
}
@customElement("ha-control-select-menu")
export class HaControlSelectMenu extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, attribute: "show-arrow" })
public showArrow = false;
@@ -47,6 +38,9 @@ export class HaControlSelectMenu extends LitElement {
@property({ attribute: false }) public options: SelectOption[] = [];
@property({ attribute: false })
public renderIcon?: (value: string) => TemplateResult<1> | typeof nothing;
@query("button") private _triggerButton!: HTMLButtonElement;
public override render() {
@@ -94,14 +88,8 @@ export class HaControlSelectMenu extends LitElement {
? html`<ha-svg-icon slot="icon" .path=${option.iconPath}></ha-svg-icon>`
: option.icon
? html`<ha-icon slot="icon" .icon=${option.icon}></ha-icon>`
: option.attributeIcon
? html`<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${option.attributeIcon.stateObj}
.attribute=${option.attributeIcon.attribute}
.attributeValue=${option.attributeIcon.attributeValue}
></ha-attribute-icon>`
: this.renderIcon
? html`<span slot="icon">${this.renderIcon(option.value)}</span>`
: nothing}
${option.label}</ha-dropdown-item
>`;
@@ -119,24 +107,20 @@ export class HaControlSelectMenu extends LitElement {
}
private _renderIcon() {
const { iconPath, icon, attributeIcon } =
this.getValueObject(this.options, this.value) ?? {};
const value = this.getValueObject(this.options, this.value);
const defaultIcon = this.querySelector("[slot='icon']");
return html`
<div class="icon">
${iconPath
? html`<ha-svg-icon slot="icon" .path=${iconPath}></ha-svg-icon>`
: icon
? html`<ha-icon slot="icon" .icon=${icon}></ha-icon>`
: attributeIcon
? html`<ha-attribute-icon
slot="icon"
.hass=${this.hass}
.stateObj=${attributeIcon.stateObj}
.attribute=${attributeIcon.attribute}
.attributeValue=${attributeIcon.attributeValue}
></ha-attribute-icon>`
${value?.iconPath
? html`<ha-svg-icon
slot="icon"
.path=${value.iconPath}
></ha-svg-icon>`
: value?.icon
? html`<ha-icon slot="icon" .icon=${value.icon}></ha-icon>`
: this.renderIcon && this.value
? this.renderIcon(this.value)
: defaultIcon
? html`<slot name="icon"></slot>`
: nothing}
@@ -172,12 +156,12 @@ export class HaControlSelectMenu extends LitElement {
font-size: var(--ha-font-size-m);
line-height: 1.4;
width: auto;
color: var(--primary-text-color);
-webkit-tap-highlight-color: transparent;
}
.select-anchor {
border: none;
text-align: left;
color: var(--primary-text-color);
height: var(--control-select-menu-height);
padding: var(--control-select-menu-padding);
overflow: hidden;

View File

@@ -93,6 +93,8 @@ export class HaDateRangePicker extends LitElement {
| "center"
| "inline";
@state() private _calcedVerticalOpeningDirection?: "up" | "down";
protected willUpdate(changedProps: PropertyValues) {
if (
(!this.hasUpdated && this.ranges === undefined) ||
@@ -134,7 +136,9 @@ export class HaDateRangePicker extends LitElement {
opening-direction=${ifDefined(
this.openingDirection || this._calcedOpeningDirection
)}
opens-vertical=${ifDefined(this.verticalOpeningDirection)}
opens-vertical=${ifDefined(
this.verticalOpeningDirection || this._calcedVerticalOpeningDirection
)}
first-day=${firstWeekdayIndex(this.hass.locale)}
language=${this.hass.locale.language}
@change=${this._handleChange}
@@ -328,17 +332,24 @@ export class HaDateRangePicker extends LitElement {
private _handleClick() {
// calculate opening direction if not set
if (!this._dateRangePicker.open && !this.openingDirection) {
const datePickerPosition = this.getBoundingClientRect().x;
let opens: "right" | "left" | "center" | "inline";
if (datePickerPosition > (2 * window.innerWidth) / 3) {
opens = "left";
} else if (datePickerPosition < window.innerWidth / 3) {
opens = "right";
} else {
opens = "center";
if (!this._dateRangePicker.open) {
if (!this.openingDirection) {
const datePickerPosition = this.getBoundingClientRect().x;
let opens: "right" | "left" | "center" | "inline";
if (datePickerPosition > (2 * window.innerWidth) / 3) {
opens = "left";
} else if (datePickerPosition < window.innerWidth / 3) {
opens = "right";
} else {
opens = "center";
}
this._calcedOpeningDirection = opens;
}
if (!this.verticalOpeningDirection) {
const rect = this.getBoundingClientRect();
this._calcedVerticalOpeningDirection =
rect.top > window.innerHeight / 2 ? "up" : "down";
}
this._calcedOpeningDirection = opens;
}
}

View File

@@ -52,7 +52,12 @@ type DialogHideEvent = CustomEvent<{ source?: Element }>;
* @cssprop --ha-dialog-show-duration - Show animation duration.
* @cssprop --ha-dialog-hide-duration - Hide animation duration.
* @cssprop --ha-dialog-surface-background - Dialog background color.
* @cssprop --ha-dialog-surface-backdrop-filter - Dialog backdrop filter.
* @cssprop --dialog-box-shadow - Dialog box shadow.
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface.
* @cssprop --ha-dialog-scrim-backdrop-filter - Dialog scrim backdrop filter.
* @cssprop --dialog-backdrop-filter - Dialog scrim backdrop filter (legacy).
* @cssprop --mdc-dialog-scrim-color - Dialog scrim color (legacy).
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
*
* @attr {boolean} open - Controls the dialog open state.
@@ -239,6 +244,9 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
private _handleKeyDown(ev: KeyboardEvent) {
if (ev.key === "Escape") {
this._escapePressed = true;
if (this.preventScrimClose) {
ev.preventDefault();
}
ev.stopPropagation();
(ev.currentTarget as WaDialog).open = false;
}
@@ -268,10 +276,6 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
--spacing: var(--dialog-content-padding, var(--ha-space-6));
--show-duration: var(--ha-dialog-show-duration, 200ms);
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
--ha-dialog-surface-background: var(
--card-background-color,
var(--ha-color-surface-default)
);
--wa-color-surface-raised: var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
@@ -302,6 +306,12 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
}
wa-dialog::part(dialog) {
-webkit-backdrop-filter: var(
--ha-dialog-surface-backdrop-filter,
none
);
backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none);
box-shadow: var(--dialog-box-shadow, var(--wa-shadow-l));
color: var(--primary-text-color);
min-width: var(--width, var(--full-width));
max-width: var(--width, var(--full-width));
@@ -331,6 +341,18 @@ export class HaDialog extends ScrollableFadeMixin(LitElement) {
overflow: hidden;
}
wa-dialog::part(dialog)::backdrop {
-webkit-backdrop-filter: var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
);
backdrop-filter: var(
--ha-dialog-scrim-backdrop-filter,
var(--dialog-backdrop-filter, none)
);
background-color: var(--mdc-dialog-scrim-color, none);
}
@media all and (max-width: 450px), all and (max-height: 500px) {
:host([type="standard"]) {
--ha-dialog-border-radius: 0;

View File

@@ -37,6 +37,9 @@ export class HaIconButtonToggle extends HaIconButton {
background-color: transparent;
border: 2px solid var(--primary-text-color);
}
:host([selected]) ha-button::after {
opacity: 0;
}
:host([selected]) ha-button::part(base) {
color: var(--primary-background-color);
background-color: unset;

View File

@@ -74,6 +74,7 @@ export class HaIconButton extends LitElement {
);
--wa-color-on-normal: currentColor;
--wa-color-fill-quiet: transparent;
--ha-button-label-overflow: visible;
}
ha-button::after {
content: "";

View File

@@ -9,6 +9,7 @@ import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-icon-button-group";
import "../../../components/ha-icon-button-toggle";
@@ -39,6 +40,38 @@ class MoreInfoClimate extends LitElement {
@state() private _mainControl: MainControl = "temperature";
private _renderPresetModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="preset_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private _renderFanModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="fan_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private _renderSwingModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="swing_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private _renderSwingHorizontalModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="swing_horizontal_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
protected willUpdate(changedProps: PropertyValues): void {
if (
changedProps.has("stateObj") &&
@@ -205,12 +238,8 @@ class MoreInfoClimate extends LitElement {
"preset_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "preset_mode",
attributeValue: mode,
},
}))}
.renderIcon=${this._renderPresetModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>
@@ -234,12 +263,8 @@ class MoreInfoClimate extends LitElement {
"fan_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "fan_mode",
attributeValue: mode,
},
}))}
.renderIcon=${this._renderFanModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiFan}></ha-svg-icon>
</ha-control-select-menu>
@@ -263,12 +288,8 @@ class MoreInfoClimate extends LitElement {
"swing_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "swing_mode",
attributeValue: mode,
},
}))}
.renderIcon=${this._renderSwingModeIcon}
>
<ha-svg-icon
slot="icon"
@@ -297,13 +318,9 @@ class MoreInfoClimate extends LitElement {
"swing_horizontal_mode",
mode
),
attributeIcon: {
stateObj,
attribute: "swing_horizontal_mode",
attributeValue: mode,
},
})
)}
.renderIcon=${this._renderSwingHorizontalModeIcon}
>
<ha-svg-icon
slot="icon"

View File

@@ -40,6 +40,22 @@ class MoreInfoFan extends LitElement {
@state() public _presetMode?: string;
private _renderPresetModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="preset_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private _renderDirectionIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="direction"
.attributeValue=${value}
></ha-attribute-icon>`;
private _toggle = () => {
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
forwardHaptic(this, "light");
@@ -192,15 +208,9 @@ class MoreInfoFan extends LitElement {
"preset_mode",
mode
),
attributeIcon: this.stateObj
? {
stateObj: this.stateObj,
attribute: "preset_mode",
attributeValue: mode,
}
: undefined,
})
)}
.renderIcon=${this._renderPresetModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>
@@ -226,14 +236,8 @@ class MoreInfoFan extends LitElement {
direction
)
: direction,
attributeIcon: this.stateObj
? {
stateObj: this.stateObj,
attribute: "direction",
attributeValue: direction,
}
: undefined,
}))}
.renderIcon=${this._renderDirectionIcon}
>
<ha-attribute-icon
slot="icon"

View File

@@ -23,6 +23,14 @@ class MoreInfoHumidifier extends LitElement {
@state() public _mode?: string;
private _renderModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="mode"
.attributeValue=${value}
></ha-attribute-icon>`;
protected willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (changedProps.has("stateObj")) {
@@ -106,14 +114,8 @@ class MoreInfoHumidifier extends LitElement {
mode
)
: mode,
attributeIcon: stateObj
? {
stateObj,
attribute: "mode",
attributeValue: mode,
}
: undefined,
})) || []}
.renderIcon=${this._renderModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -55,6 +55,14 @@ class MoreInfoLight extends LitElement {
@state() private _mainControl: MainControl = "brightness";
private _renderEffectIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="effect"
.attributeValue=${value}
></ha-attribute-icon>`;
protected updated(changedProps: PropertyValues<typeof this>): void {
if (changedProps.has("stateObj")) {
this._effect = this.stateObj?.attributes.effect;
@@ -271,15 +279,9 @@ class MoreInfoLight extends LitElement {
effect
)
: effect,
attributeIcon: this.stateObj
? {
stateObj: this.stateObj,
attribute: "effect",
attributeValue: effect,
}
: undefined,
})
)}
.renderIcon=${this._renderEffectIcon}
>
<ha-svg-icon slot="icon" .path=${mdiCreation}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -24,6 +24,14 @@ class MoreInfoWaterHeater extends LitElement {
@property({ attribute: false }) public stateObj?: WaterHeaterEntity;
private _renderOperationModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this.stateObj}
attribute="operation_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
protected render() {
if (!this.stateObj) {
return nothing;
@@ -85,12 +93,8 @@ class MoreInfoWaterHeater extends LitElement {
.map((mode) => ({
value: mode,
label: this.hass.formatEntityState(stateObj, mode),
attributeIcon: {
stateObj,
attribute: "operation_mode",
attributeValue: mode,
},
}))}
.renderIcon=${this._renderOperationModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiWaterBoiler}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -2,17 +2,27 @@ import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { computeAttributeNameDisplay } from "../../common/entity/compute_attribute_display";
import checkValidDate from "../../common/datetime/check_valid_date";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import "../../components/ha-attribute-value";
import "../../components/ha-card";
import type { LocalizeKeys } from "../../common/translations/localize";
import { computeShownAttributes } from "../../data/entity/entity_attributes";
import type { ExtEntityRegistryEntry } from "../../data/entity/entity_registry";
import type { HomeAssistant } from "../../types";
import "../../components/ha-yaml-editor";
interface DetailsViewParams {
entityId: string;
}
interface DetailEntry {
translationKey: LocalizeKeys;
value: string;
}
@customElement("ha-more-info-details")
class HaMoreInfoDetails extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -21,6 +31,8 @@ class HaMoreInfoDetails extends LitElement {
@property({ attribute: false }) public params?: DetailsViewParams;
@property({ attribute: false }) public yamlMode = false;
@state() private _stateObj?: HassEntity;
protected willUpdate(changedProps: PropertyValues): void {
@@ -37,60 +49,127 @@ class HaMoreInfoDetails extends LitElement {
return nothing;
}
const translatedState = this.hass.formatEntityState(this._stateObj);
const detailsAttributes = computeShownAttributes(this._stateObj);
const detailsAttributeSet = new Set(detailsAttributes);
const builtInAttributes = Object.keys(this._stateObj.attributes).filter(
(attribute) => !detailsAttributeSet.has(attribute)
const { stateEntries, attributes, yamlData } = this._getDetailData(
this._stateObj
);
const allAttributes = [...detailsAttributes, ...builtInAttributes];
return html`
<div class="content">
<section class="section">
<h2 class="section-title">
${this.hass.localize(
"ui.components.entity.entity-state-picker.state"
)}
</h2>
<ha-card>
<div class="card-content">
<div class="attribute-group">
<div class="data-entry">
<div class="key">
${this.hass.localize(
"ui.dialogs.more_info_control.translated"
)}
${this.yamlMode
? html`<ha-yaml-editor
.hass=${this.hass}
.value=${yamlData}
read-only
auto-update
></ha-yaml-editor>`
: html`
<section class="section">
<h2 class="section-title">
${this.hass.localize(
"ui.components.entity.entity-state-picker.state"
)}
</h2>
<ha-card>
<div class="card-content">
<div class="data-group">
${stateEntries.map(
(entry) =>
html`<div class="data-entry">
<div class="key">
${this.hass.localize(entry.translationKey)}
</div>
<div class="value">${entry.value}</div>
</div>`
)}
</div>
</div>
<div class="value">${translatedState}</div>
</div>
<div class="data-entry">
<div class="key">
${this.hass.localize("ui.dialogs.more_info_control.raw")}
</div>
<div class="value">${this._stateObj.state}</div>
</div>
</div>
</div>
</ha-card>
</section>
</ha-card>
</section>
<section class="section">
<h2 class="section-title">
${this.hass.localize("ui.dialogs.more_info_control.attributes")}
</h2>
<ha-card>
<div class="card-content">
<div class="attribute-group">
${this._renderAttributes(allAttributes)}
</div>
</div>
</ha-card>
</section>
<section class="section">
<h2 class="section-title">
${this.hass.localize(
"ui.dialogs.more_info_control.attributes"
)}
</h2>
<ha-card>
<div class="card-content">
<div class="data-group">
${this._renderAttributes(attributes)}
</div>
</div>
</ha-card>
</section>
`}
</div>
`;
}
private _getDetailData = memoizeOne(
(
stateObj: HassEntity
): {
stateEntries: DetailEntry[];
attributes: string[];
yamlData: {
state: {
translated: string;
raw: string;
last_changed: string;
last_updated: string;
};
attributes: Record<string, string>;
};
} => {
const translatedState = this.hass.formatEntityState(stateObj);
const detailsAttributes = computeShownAttributes(stateObj);
const detailsAttributeSet = new Set(detailsAttributes);
const builtInAttributes = Object.keys(stateObj.attributes).filter(
(attribute) => !detailsAttributeSet.has(attribute)
);
return {
stateEntries: [
{
translationKey: "ui.dialogs.more_info_control.translated",
value: translatedState,
},
{
translationKey: "ui.dialogs.more_info_control.raw",
value: stateObj.state,
},
{
translationKey: "ui.dialogs.more_info_control.last_changed",
value: this._formatTimestamp(stateObj.last_changed),
},
{
translationKey: "ui.dialogs.more_info_control.last_updated",
value: this._formatTimestamp(stateObj.last_updated),
},
],
attributes: [...detailsAttributes, ...builtInAttributes],
yamlData: {
state: {
translated: translatedState,
raw: stateObj.state,
last_changed: stateObj.last_changed,
last_updated: stateObj.last_updated,
},
attributes: stateObj.attributes,
},
};
}
);
private _formatTimestamp(value: string): string {
const date = new Date(value);
return checkValidDate(date)
? formatDateTimeWithSeconds(date, this.hass.locale, this.hass.config)
: value;
}
private _renderAttributes(attributes: string[]) {
if (attributes.length === 0) {
return html`<div class="empty">
@@ -159,7 +238,7 @@ class HaMoreInfoDetails extends LitElement {
border-bottom: 1px solid var(--divider-color);
}
.attribute-group .data-entry:last-of-type {
.data-group .data-entry:last-of-type {
border-bottom: none;
}

View File

@@ -1,6 +1,7 @@
import {
mdiChartBoxOutline,
mdiClose,
mdiCodeBraces,
mdiCogOutline,
mdiDevices,
mdiDotsVertical,
@@ -132,6 +133,8 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
@state() private _infoEditMode = false;
@state() private _detailsYamlMode = false;
@state() private _isEscapeEnabled = true;
@state() private _sensorNumericDeviceClasses?: string[] = [];
@@ -182,6 +185,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
this._parentEntityIds = [];
this._entry = undefined;
this._infoEditMode = false;
this._detailsYamlMode = false;
this._initialView = DEFAULT_VIEW;
this._currView = DEFAULT_VIEW;
this._childView = undefined;
@@ -251,6 +255,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
private _goBack() {
if (this._childView) {
this._childView = undefined;
this._detailsYamlMode = false;
return;
}
if (this._initialView !== this._currView) {
@@ -314,6 +319,10 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
this._infoEditMode = !this._infoEditMode;
}
private _toggleDetailsYamlMode() {
this._detailsYamlMode = !this._detailsYamlMode;
}
private _handleToggleInfoEditModeEvent(ev) {
this._infoEditMode = ev.detail;
}
@@ -637,7 +646,18 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
</ha-dropdown-item>
</ha-dropdown>
`
: nothing}
: this._childView?.viewTag === "ha-more-info-details"
? html`
<ha-icon-button
slot="headerActionItems"
.label=${this.hass.localize(
"ui.dialogs.more_info_control.toggle_yaml_mode"
)}
.path=${mdiCodeBraces}
@click=${this._toggleDetailsYamlMode}
></ha-icon-button>
`
: nothing}
<div
class=${classMap({
"content-wrapper": true,
@@ -663,6 +683,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
hass: this.hass,
entry: this._entry,
params: this._childView.viewParams,
yamlMode: this._detailsYamlMode,
})}
</div>
`
@@ -731,6 +752,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
if (changedProps.has("_currView")) {
this._childView = undefined;
this._infoEditMode = false;
this._detailsYamlMode = false;
}
}

View File

@@ -288,7 +288,7 @@ export class QuickBar extends LitElement {
<ha-combo-box-item
tabindex="-1"
type="button"
style="--mdc-icon-size: 32px;"
style="--mdc-icon-size: 24px;"
>
${"stateObj" in item && item.stateObj
? html`
@@ -302,6 +302,7 @@ export class QuickBar extends LitElement {
? html`
<ha-domain-icon
slot="start"
style="margin: var(--ha-space-1);"
.hass=${this.hass}
.domain=${item.domain}
brand-fallback
@@ -319,7 +320,11 @@ export class QuickBar extends LitElement {
/>
`
: item.icon
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
? html`<ha-icon
style="margin: var(--ha-space-1);"
slot="start"
.icon=${item.icon}
></ha-icon>`
: "iconColor" in item && item.iconColor
? html`
<div
@@ -333,7 +338,11 @@ export class QuickBar extends LitElement {
</div>
`
: html`
<ha-svg-icon slot="start" .path=${iconPath}></ha-svg-icon>
<ha-svg-icon
style="margin: var(--ha-space-1);"
slot="start"
.path=${iconPath}
></ha-svg-icon>
`}
<span slot="headline">${item.primary}</span>
${item.secondary

View File

@@ -36,7 +36,7 @@ import { showQuickBar } from "../../../dialogs/quick-bar/show-dialog-quick-bar";
import { showRestartDialog } from "../../../dialogs/restart/show-dialog-restart";
import { showShortcutsDialog } from "../../../dialogs/shortcuts/show-shortcuts-dialog";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle, haStyleScrollbar } from "../../../resources/styles";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { isMac } from "../../../util/is_mac";
@@ -255,90 +255,88 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
</ha-dropdown-item>
</ha-dropdown>
<div class="content ha-scrollbar">
<ha-config-section
.narrow=${this.narrow}
.isWide=${this.isWide}
full-width
>
${repairsIssues.length || canInstallUpdates.length
? html`<ha-card outlined>
${repairsIssues.length
? html`
<ha-config-repairs
.hass=${this.hass}
.narrow=${this.narrow}
.total=${totalRepairIssues}
.repairsIssues=${repairsIssues}
></ha-config-repairs>
${totalRepairIssues > repairsIssues.length
? html`
<ha-assist-chip
href="/config/repairs"
.label=${this.hass.localize(
"ui.panel.config.repairs.more_repairs",
{
count:
totalRepairIssues - repairsIssues.length,
}
)}
>
</ha-assist-chip>
`
: ""}
`
: ""}
${repairsIssues.length && canInstallUpdates.length
? html`<hr />`
: ""}
${canInstallUpdates.length
? html`
<ha-config-updates
.hass=${this.hass}
.narrow=${this.narrow}
.total=${totalUpdates}
.updateEntities=${canInstallUpdates}
.isInstallable=${true}
></ha-config-updates>
${totalUpdates > canInstallUpdates.length
? html`
<ha-assist-chip
href="/config/updates"
label=${this.hass.localize(
"ui.panel.config.updates.more_updates",
{
count:
totalUpdates - canInstallUpdates.length,
}
)}
>
</ha-assist-chip>
`
: ""}
`
: ""}
</ha-card>`
: ""}
${this._pages(
this.cloudStatus,
isComponentLoaded(this.hass, "cloud"),
this.hass.auth.external?.config.hasSettingsScreen
).map((categoryPages) =>
categoryPages.length === 0
? nothing
: html`
<ha-card outlined>
<ha-config-navigation
<ha-config-section
.narrow=${this.narrow}
.isWide=${this.isWide}
full-width
>
${repairsIssues.length || canInstallUpdates.length
? html`<ha-card outlined>
${repairsIssues.length
? html`
<ha-config-repairs
.hass=${this.hass}
.narrow=${this.narrow}
.pages=${categoryPages}
></ha-config-navigation>
</ha-card>
`
)}
<ha-tip .hass=${this.hass}>${this._tip}</ha-tip>
</ha-config-section>
</div>
.total=${totalRepairIssues}
.repairsIssues=${repairsIssues}
></ha-config-repairs>
${totalRepairIssues > repairsIssues.length
? html`
<ha-assist-chip
href="/config/repairs"
.label=${this.hass.localize(
"ui.panel.config.repairs.more_repairs",
{
count:
totalRepairIssues - repairsIssues.length,
}
)}
>
</ha-assist-chip>
`
: ""}
`
: ""}
${repairsIssues.length && canInstallUpdates.length
? html`<hr />`
: ""}
${canInstallUpdates.length
? html`
<ha-config-updates
.hass=${this.hass}
.narrow=${this.narrow}
.total=${totalUpdates}
.updateEntities=${canInstallUpdates}
.isInstallable=${true}
></ha-config-updates>
${totalUpdates > canInstallUpdates.length
? html`
<ha-assist-chip
href="/config/updates"
label=${this.hass.localize(
"ui.panel.config.updates.more_updates",
{
count:
totalUpdates - canInstallUpdates.length,
}
)}
>
</ha-assist-chip>
`
: ""}
`
: ""}
</ha-card>`
: ""}
${this._pages(
this.cloudStatus,
isComponentLoaded(this.hass, "cloud"),
this.hass.auth.external?.config.hasSettingsScreen
).map((categoryPages) =>
categoryPages.length === 0
? nothing
: html`
<ha-card outlined>
<ha-config-navigation
.hass=${this.hass}
.narrow=${this.narrow}
.pages=${categoryPages}
></ha-config-navigation>
</ha-card>
`
)}
<ha-tip .hass=${this.hass}>${this._tip}</ha-tip>
</ha-config-section>
</ha-top-app-bar-fixed>
`;
}
@@ -394,36 +392,7 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleScrollbar,
css`
:host {
display: block;
height: 100%;
}
ha-top-app-bar-fixed {
height: 100%;
overflow: hidden;
}
.content {
height: calc(
100vh - var(--header-height, 0px) - var(
--safe-area-inset-top,
0px
) - var(--safe-area-inset-bottom, 0px)
);
height: calc(
100dvh - var(--header-height, 0px) - var(
--safe-area-inset-top,
0px
) - var(--safe-area-inset-bottom, 0px)
);
padding-bottom: var(--ha-space-5);
box-sizing: border-box;
overflow-x: hidden;
}
:host(:not([narrow])) ha-card:last-child {
margin-bottom: 24px;
}

View File

@@ -20,9 +20,9 @@ import "../../../../../components/ha-md-list-item";
import "../../../../../components/ha-svg-icon";
import type { ConfigEntry } from "../../../../../data/config_entries";
import { getConfigEntries } from "../../../../../data/config_entries";
import type { HomeAssistant } from "../../../../../types";
import "../../../../../layouts/hass-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../types";
const THREAD_ICON =
"m 17.126982,8.0730792 c 0,-0.7297242 -0.593746,-1.32357 -1.323637,-1.32357 -0.729454,0 -1.323199,0.5938458 -1.323199,1.32357 v 1.3234242 l 1.323199,1.458e-4 c 0.729891,0 1.323637,-0.5937006 1.323637,-1.32357 z M 11.999709,0 C 5.3829818,0 0,5.3838955 0,12.001455 0,18.574352 5.3105455,23.927406 11.865164,24 V 12.012075 l -3.9275642,-2.91e-4 c -1.1669814,0 -2.1169453,0.949979 -2.1169453,2.118323 0,1.16718 0.9499639,2.116868 2.1169453,2.116868 v 2.615717 c -2.6093089,0 -4.732218,-2.12327 -4.732218,-4.732585 0,-2.61048 2.1229091,-4.7343308 4.732218,-4.7343308 l 3.9275642,5.82e-4 v -1.323279 c 0,-2.172296 1.766691,-3.9395777 3.938181,-3.9395777 2.171928,0 3.9392,1.7672817 3.9392,3.9395777 0,2.1721498 -1.767272,3.9395768 -3.9392,3.9395768 l -1.323199,-1.45e-4 V 23.744102 C 19.911127,22.597726 24,17.768833 24,12.001455 24,5.3838955 18.616727,0 11.999709,0 Z";
@@ -312,7 +312,8 @@ export class MatterConfigDashboard extends LitElement {
}
.container {
padding: var(--ha-space-2) var(--ha-space-4) var(--ha-space-4);
padding: var(--ha-space-2) var(--ha-space-4)
calc(var(--ha-space-16) + var(--safe-area-inset-bottom, 0px));
}
a[slot="fab"] {

View File

@@ -37,10 +37,10 @@ import {
} from "../../../../../data/zha";
import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
import { fileDownload } from "../../../../../util/file_download";
import "../../../../../layouts/hass-subpage";
import { haStyle } from "../../../../../resources/styles";
import type { HomeAssistant, Route } from "../../../../../types";
import { fileDownload } from "../../../../../util/file_download";
@customElement("zha-config-dashboard")
class ZHAConfigDashboard extends LitElement {
@@ -520,7 +520,8 @@ class ZHAConfigDashboard extends LitElement {
}
.container {
padding: var(--ha-space-2) var(--ha-space-4) var(--ha-space-4);
padding: var(--ha-space-2) var(--ha-space-4)
calc(var(--ha-space-20) + var(--safe-area-inset-bottom, 0px));
}
`,
];

View File

@@ -18,6 +18,7 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { goBack } from "../../../../../common/navigate";
import "../../../../../components/ha-button";
import "../../../../../components/ha-card";
import "../../../../../components/ha-fab";
@@ -28,7 +29,6 @@ import "../../../../../components/ha-md-list-item";
import "../../../../../components/ha-progress-ring";
import "../../../../../components/ha-spinner";
import "../../../../../components/ha-svg-icon";
import { goBack } from "../../../../../common/navigate";
import type { ConfigEntry } from "../../../../../data/config_entries";
import {
ERROR_STATES,
@@ -968,7 +968,8 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
}
.container {
padding: var(--ha-space-2) var(--ha-space-4) var(--ha-space-4);
padding: var(--ha-space-2) var(--ha-space-4)
calc(var(--ha-space-16) + var(--safe-area-inset-bottom, 0px));
}
`,
];

View File

@@ -86,6 +86,7 @@ class DialogSystemLogDetail extends LitElement {
<ha-dialog
.hass=${this.hass}
.open=${this._open}
width="large"
@closed=${this._dialogClosed}
>
<span slot="headerTitle">${title}</span>

View File

@@ -143,8 +143,7 @@ class HaConfigRepairs extends LitElement {
}
} else if (
issue.domain === "vacuum" &&
(issue.translation_key === "segments_changed" ||
issue.translation_key === "segments_mapping_not_configured")
issue.translation_key === "segments_changed"
) {
const data = await fetchRepairsIssueData(
this.hass.connection,

View File

@@ -670,10 +670,10 @@ export class AssistPipelineDebug extends LitElement {
background-color: var(--light-primary-color);
color: var(--text-light-primary-color, var(--primary-text-color));
direction: var(--direction);
--primary-text-color: var(
--text-light-primary-color,
var(--primary-text-color)
);
}
.tool_result [slot="header"] {
color: var(--text-light-primary-color, var(--primary-text-color));
}
.message.user,

View File

@@ -7,11 +7,7 @@ import type { HomeAssistant } from "../../../types";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import {
LARGE_SCREEN_CONDITION,
SMALL_SCREEN_CONDITION,
} from "../../lovelace/strategies/helpers/screen-conditions";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";
@customElement("power-view-strategy")
export class PowerViewStrategy extends ReactiveElement {
@@ -49,22 +45,15 @@ export class PowerViewStrategy extends ReactiveElement {
(source) => source.type === "gas" && source.stat_rate
);
const tileSection: LovelaceSectionConfig = {
type: "grid",
cards: [],
column_span: 2,
};
const chartsSection: LovelaceSectionConfig = {
type: "grid",
cards: [],
column_span: 2,
};
const tiles: LovelaceCardConfig[] = [];
const badges: LovelaceBadgeConfig[] = [];
const view: LovelaceViewConfig = {
type: "sections",
sections: [tileSection, chartsSection],
max_columns: 2,
sections: [chartsSection],
};
// No sources configured
@@ -80,11 +69,10 @@ export class PowerViewStrategy extends ReactiveElement {
}
if (hasPowerSources) {
const card = {
badges.push({
type: "power-total",
collection_key: collectionKey,
};
tiles.push(card);
});
chartsSection.cards!.push({
title: hass.localize("ui.panel.energy.cards.power_sources_graph_title"),
@@ -97,19 +85,17 @@ export class PowerViewStrategy extends ReactiveElement {
}
if (hasGasSources) {
const card = {
badges.push({
type: "gas-total",
collection_key: collectionKey,
};
tiles.push({ ...card });
});
}
if (hasWaterSources) {
const card = {
badges.push({
type: "water-total",
collection_key: collectionKey,
};
tiles.push({ ...card });
});
}
if (hasPowerDevices) {
@@ -148,21 +134,8 @@ export class PowerViewStrategy extends ReactiveElement {
});
}
tiles.forEach((card) => {
tileSection.cards!.push({
...card,
grid_options: { columns: 24 / tiles.length },
});
});
if (tiles.length > 2) {
// On small screens with 3 tiles, show them in 1 column
tileSection.visibility = [LARGE_SCREEN_CONDITION];
view.sections!.unshift({
type: "grid",
cards: tiles,
visibility: [SMALL_SCREEN_CONDITION],
});
if (badges.length) {
view.badges = badges;
}
return view;

View File

@@ -3,11 +3,8 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../components/ha-card";
import "../../../../components/ha-badge";
import "../../../../components/ha-svg-icon";
import "../../../../components/tile/ha-tile-container";
import "../../../../components/tile/ha-tile-icon";
import "../../../../components/tile/ha-tile-info";
import type { EnergyData, EnergyPreferences } from "../../../../data/energy";
import {
formatFlowRateShort,
@@ -16,18 +13,17 @@ import {
} from "../../../../data/energy";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard, LovelaceGridOptions } from "../../types";
import { tileCardStyle } from "../tile/tile-card-style";
import type { GasTotalCardConfig } from "../types";
import type { LovelaceBadge } from "../../types";
import type { GasTotalBadgeConfig } from "../types";
@customElement("hui-gas-total-card")
export class HuiGasTotalCard
@customElement("hui-gas-total-badge")
export class HuiGasTotalBadge
extends SubscribeMixin(LitElement)
implements LovelaceCard
implements LovelaceBadge
{
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _config?: GasTotalCardConfig;
@state() private _config?: GasTotalBadgeConfig;
@state() private _data?: EnergyData;
@@ -35,7 +31,7 @@ export class HuiGasTotalCard
protected hassSubscribeRequiredHostProps = ["_config"];
public setConfig(config: GasTotalCardConfig): void {
public setConfig(config: GasTotalBadgeConfig): void {
this._config = config;
}
@@ -49,34 +45,19 @@ export class HuiGasTotalCard
];
}
public getCardSize(): Promise<number> | number {
return 1;
}
getGridOptions(): LovelaceGridOptions {
return {
columns: 12,
min_columns: 6,
rows: 1,
min_rows: 1,
};
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (changedProps.has("_config") || changedProps.has("_data")) {
return true;
}
// Check if any of the tracked entity states have changed
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || !this._entities.size) {
return true;
}
// Only update if one of our tracked entities changed
for (const entityId of this._entities) {
if (oldHass.states[entityId] !== this.hass.states[entityId]) {
if (oldHass.states[entityId] !== this.hass?.states[entityId]) {
return true;
}
}
@@ -122,32 +103,22 @@ export class HuiGasTotalCard
this.hass.localize("ui.panel.lovelace.cards.energy.gas_total_title");
return html`
<ha-card>
<ha-tile-container .interactive=${false}>
<ha-tile-icon slot="icon" data-domain="sensor" data-state="active">
<ha-svg-icon slot="icon" .path=${mdiFire}></ha-svg-icon>
</ha-tile-icon>
<ha-tile-info slot="info">
<span slot="primary" class="primary">${name}</span>
<span slot="secondary" class="secondary">${displayValue}</span>
</ha-tile-info>
</ha-tile-container>
</ha-card>
<ha-badge .label=${name}>
<ha-svg-icon slot="icon" .path=${mdiFire}></ha-svg-icon>
${displayValue}
</ha-badge>
`;
}
static styles = [
tileCardStyle,
css`
:host {
--tile-color: var(--energy-gas-color);
}
`,
];
static styles = css`
ha-badge {
--badge-color: var(--energy-gas-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"hui-gas-total-card": HuiGasTotalCard;
"hui-gas-total-badge": HuiGasTotalBadge;
}
}

View File

@@ -4,11 +4,8 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { formatNumber } from "../../../../common/number/format_number";
import "../../../../components/ha-card";
import "../../../../components/ha-badge";
import "../../../../components/ha-svg-icon";
import "../../../../components/tile/ha-tile-container";
import "../../../../components/tile/ha-tile-icon";
import "../../../../components/tile/ha-tile-info";
import type { EnergyData, EnergyPreferences } from "../../../../data/energy";
import {
getEnergyDataCollection,
@@ -16,18 +13,17 @@ import {
} from "../../../../data/energy";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard, LovelaceGridOptions } from "../../types";
import { tileCardStyle } from "../tile/tile-card-style";
import type { PowerTotalCardConfig } from "../types";
import type { LovelaceBadge } from "../../types";
import type { PowerTotalBadgeConfig } from "../types";
@customElement("hui-power-total-card")
export class HuiPowerTotalCard
@customElement("hui-power-total-badge")
export class HuiPowerTotalBadge
extends SubscribeMixin(LitElement)
implements LovelaceCard
implements LovelaceBadge
{
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _config?: PowerTotalCardConfig;
@state() private _config?: PowerTotalBadgeConfig;
@state() private _data?: EnergyData;
@@ -35,7 +31,7 @@ export class HuiPowerTotalCard
protected hassSubscribeRequiredHostProps = ["_config"];
public setConfig(config: PowerTotalCardConfig): void {
public setConfig(config: PowerTotalBadgeConfig): void {
this._config = config;
}
@@ -49,34 +45,19 @@ export class HuiPowerTotalCard
];
}
public getCardSize(): Promise<number> | number {
return 1;
}
getGridOptions(): LovelaceGridOptions {
return {
columns: 12,
min_columns: 6,
rows: 1,
min_rows: 1,
};
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (changedProps.has("_config") || changedProps.has("_data")) {
return true;
}
// Check if any of the tracked entity states have changed
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || !this._entities.size) {
return true;
}
// Only update if one of our tracked entities changed
for (const entityId of this._entities) {
if (oldHass.states[entityId] !== this.hass.states[entityId]) {
if (oldHass.states[entityId] !== this.hass?.states[entityId]) {
return true;
}
}
@@ -94,10 +75,10 @@ export class HuiPowerTotalCard
this._entities.clear();
let solar = 0;
let from_grid = 0;
let to_grid = 0;
let from_battery = 0;
let to_battery = 0;
let fromGrid = 0;
let toGrid = 0;
let fromBattery = 0;
let toBattery = 0;
prefs.energy_sources.forEach((source) => {
if (source.type === "solar" && source.stat_rate) {
@@ -105,17 +86,17 @@ export class HuiPowerTotalCard
if (value > 0) solar += value;
} else if (source.type === "grid" && source.stat_rate) {
const value = this._getCurrentPower(source.stat_rate);
if (value > 0) from_grid += value;
else if (value < 0) to_grid += Math.abs(value);
if (value > 0) fromGrid += value;
else if (value < 0) toGrid += Math.abs(value);
} else if (source.type === "battery" && source.stat_rate) {
const value = this._getCurrentPower(source.stat_rate);
if (value > 0) from_battery += value;
else if (value < 0) to_battery += Math.abs(value);
if (value > 0) fromBattery += value;
else if (value < 0) toBattery += Math.abs(value);
}
});
const used_total = from_grid + solar + from_battery - to_grid - to_battery;
return Math.max(0, used_total);
const usedTotal = fromGrid + solar + fromBattery - toGrid - toBattery;
return Math.max(0, usedTotal);
}
protected render() {
@@ -141,35 +122,22 @@ export class HuiPowerTotalCard
this.hass.localize("ui.panel.lovelace.cards.energy.power_total_title");
return html`
<ha-card>
<ha-tile-container .interactive=${false}>
<ha-tile-icon slot="icon" data-domain="sensor" data-state="active">
<ha-svg-icon
slot="icon"
.path=${mdiHomeLightningBolt}
></ha-svg-icon>
</ha-tile-icon>
<ha-tile-info slot="info">
<span slot="primary" class="primary">${name}</span>
<span slot="secondary" class="secondary">${displayValue}</span>
</ha-tile-info>
</ha-tile-container>
</ha-card>
<ha-badge .label=${name}>
<ha-svg-icon slot="icon" .path=${mdiHomeLightningBolt}></ha-svg-icon>
${displayValue}
</ha-badge>
`;
}
static styles = [
tileCardStyle,
css`
:host {
--tile-color: var(--primary-color);
}
`,
];
static styles = css`
ha-badge {
--badge-color: var(--primary-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"hui-power-total-card": HuiPowerTotalCard;
"hui-power-total-badge": HuiPowerTotalBadge;
}
}

View File

@@ -3,11 +3,8 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../components/ha-card";
import "../../../../components/ha-badge";
import "../../../../components/ha-svg-icon";
import "../../../../components/tile/ha-tile-container";
import "../../../../components/tile/ha-tile-icon";
import "../../../../components/tile/ha-tile-info";
import type { EnergyData, EnergyPreferences } from "../../../../data/energy";
import {
formatFlowRateShort,
@@ -16,18 +13,17 @@ import {
} from "../../../../data/energy";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard, LovelaceGridOptions } from "../../types";
import { tileCardStyle } from "../tile/tile-card-style";
import type { WaterTotalCardConfig } from "../types";
import type { LovelaceBadge } from "../../types";
import type { WaterTotalBadgeConfig } from "../types";
@customElement("hui-water-total-card")
export class HuiWaterTotalCard
@customElement("hui-water-total-badge")
export class HuiWaterTotalBadge
extends SubscribeMixin(LitElement)
implements LovelaceCard
implements LovelaceBadge
{
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _config?: WaterTotalCardConfig;
@state() private _config?: WaterTotalBadgeConfig;
@state() private _data?: EnergyData;
@@ -35,7 +31,7 @@ export class HuiWaterTotalCard
protected hassSubscribeRequiredHostProps = ["_config"];
public setConfig(config: WaterTotalCardConfig): void {
public setConfig(config: WaterTotalBadgeConfig): void {
this._config = config;
}
@@ -49,34 +45,19 @@ export class HuiWaterTotalCard
];
}
public getCardSize(): Promise<number> | number {
return 1;
}
getGridOptions(): LovelaceGridOptions {
return {
columns: 12,
min_columns: 6,
rows: 1,
min_rows: 1,
};
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
if (changedProps.has("_config") || changedProps.has("_data")) {
return true;
}
// Check if any of the tracked entity states have changed
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || !this._entities.size) {
return true;
}
// Only update if one of our tracked entities changed
for (const entityId of this._entities) {
if (oldHass.states[entityId] !== this.hass.states[entityId]) {
if (oldHass.states[entityId] !== this.hass?.states[entityId]) {
return true;
}
}
@@ -122,32 +103,22 @@ export class HuiWaterTotalCard
this.hass.localize("ui.panel.lovelace.cards.energy.water_total_title");
return html`
<ha-card>
<ha-tile-container .interactive=${false}>
<ha-tile-icon slot="icon" data-domain="sensor" data-state="active">
<ha-svg-icon slot="icon" .path=${mdiWater}></ha-svg-icon>
</ha-tile-icon>
<ha-tile-info slot="info">
<span slot="primary" class="primary">${name}</span>
<span slot="secondary" class="secondary">${displayValue}</span>
</ha-tile-info>
</ha-tile-container>
</ha-card>
<ha-badge .label=${name}>
<ha-svg-icon slot="icon" .path=${mdiWater}></ha-svg-icon>
${displayValue}
</ha-badge>
`;
}
static styles = [
tileCardStyle,
css`
:host {
--tile-color: var(--energy-water-color);
}
`,
];
static styles = css`
ha-badge {
--badge-color: var(--energy-water-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"hui-water-total-card": HuiWaterTotalCard;
"hui-water-total-badge": HuiWaterTotalBadge;
}
}

View File

@@ -48,3 +48,20 @@ export interface EntityBadgeConfig extends LovelaceBadgeConfig {
*/
display_type?: DisplayType;
}
interface EnergyTotalBadgeConfig extends LovelaceBadgeConfig {
title?: string;
collection_key?: string;
}
export interface PowerTotalBadgeConfig extends EnergyTotalBadgeConfig {
type: "power-total";
}
export interface WaterTotalBadgeConfig extends EnergyTotalBadgeConfig {
type: "water-total";
}
export interface GasTotalBadgeConfig extends EnergyTotalBadgeConfig {
type: "gas-total";
}

View File

@@ -49,6 +49,14 @@ class HuiClimateFanModesCardFeature
@state() _currentFanMode?: string;
private _renderFanModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="fan_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -175,14 +183,8 @@ class HuiClimateFanModesCardFeature
.value=${this._currentFanMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj: stateObj,
attribute: "fan_mode",
attributeValue: option.value,
},
}))}
.options=${options}
.renderIcon=${this._renderFanModeIcon}
><ha-svg-icon slot="icon" .path=${mdiFan}></ha-svg-icon>
</ha-control-select-menu>
`;

View File

@@ -48,6 +48,14 @@ class HuiClimatePresetModesCardFeature
@state() _currentPresetMode?: string;
private _renderPresetModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="preset_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -179,14 +187,8 @@ class HuiClimatePresetModesCardFeature
.value=${this._currentPresetMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj: stateObj,
attribute: "preset_mode",
attributeValue: option.value,
},
}))}
.options=${options}
.renderIcon=${this._renderPresetModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -48,6 +48,14 @@ class HuiClimateSwingHorizontalModesCardFeature
@state() _currentSwingHorizontalMode?: string;
private _renderSwingHorizontalModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="swing_horizontal_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -187,14 +195,8 @@ class HuiClimateSwingHorizontalModesCardFeature
.value=${this._currentSwingHorizontalMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj: stateObj,
attribute: "swing_horizontal_mode",
attributeValue: option.value,
},
}))}
.options=${options}
.renderIcon=${this._renderSwingHorizontalModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiArrowOscillating}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -48,6 +48,14 @@ class HuiClimateSwingModesCardFeature
@state() _currentSwingMode?: string;
private _renderSwingModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="swing_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -179,14 +187,8 @@ class HuiClimateSwingModesCardFeature
.value=${this._currentSwingMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj,
attribute: "swing_mode",
attributeValue: option.value,
},
}))}
.options=${options}
.renderIcon=${this._renderSwingModeIcon}
><ha-svg-icon slot="icon" .path=${mdiArrowOscillating}></ha-svg-icon>
</ha-control-select-menu>
`;

View File

@@ -47,6 +47,14 @@ class HuiFanPresetModesCardFeature
@state() _currentPresetMode?: string;
private _renderPresetModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="preset_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -173,14 +181,8 @@ class HuiFanPresetModesCardFeature
.value=${this._currentPresetMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj: stateObj,
attribute: "preset_mode",
attributeValue: option.value,
},
}))}
.options=${options}
.renderIcon=${this._renderPresetModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -48,6 +48,14 @@ class HuiHumidifierModesCardFeature
@state() _currentMode?: string;
private _renderModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -174,14 +182,8 @@ class HuiHumidifierModesCardFeature
.value=${this._currentMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj,
attribute: "mode",
attributeValue: option.value,
},
}))}
.options=${options}
.renderIcon=${this._renderModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -49,6 +49,14 @@ class HuiWaterHeaterOperationModeCardFeature
@state() _currentOperationMode?: OperationMode;
private _renderOperationModeIcon = (value: string) =>
html`<ha-attribute-icon
.hass=${this.hass}
.stateObj=${this._stateObj}
attribute="operation_mode"
.attributeValue=${value}
></ha-attribute-icon>`;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
@@ -153,14 +161,8 @@ class HuiWaterHeaterOperationModeCardFeature
.value=${this._currentOperationMode}
.disabled=${this._stateObj.state === UNAVAILABLE}
@wa-select=${this._valueChanged}
.options=${options.map((option) => ({
...option,
attributeIcon: {
stateObj: this._stateObj,
attribute: "operation_mode",
attributeValue: option.value,
},
}))}
.options=${options}
.renderIcon=${this._renderOperationModeIcon}
>
<ha-svg-icon slot="icon" .path=${mdiWaterBoiler}></ha-svg-icon>
</ha-control-select-menu>

View File

@@ -218,7 +218,9 @@ function formatTooltip(
}
// when comparing the first value is offset to match the main period
// and the real date is in the third value
const date = new Date(params[0].value?.[2] ?? params[0].value?.[0]);
// find the first param with the real date to handle gap-filled entries
const origDate = params.find((p) => p.value?.[2] != null)?.value?.[2];
const date = new Date(origDate ?? params[0].value?.[0]);
let period: string;
if (suggestedPeriod === "month") {

View File

@@ -265,21 +265,6 @@ export interface PowerSourcesGraphCardConfig extends EnergyCardBaseConfig {
show_legend?: boolean;
}
export interface PowerTotalCardConfig extends EnergyCardBaseConfig {
type: "power-total";
title?: string;
}
export interface WaterTotalCardConfig extends EnergyCardBaseConfig {
type: "water-total";
title?: string;
}
export interface GasTotalCardConfig extends EnergyCardBaseConfig {
type: "gas-total";
title?: string;
}
export interface PowerSankeyCardConfig extends EnergyCardBaseConfig {
type: "power-sankey";
title?: string;

View File

@@ -10,6 +10,9 @@ const ALWAYS_LOADED_TYPES = new Set(["error", "entity"]);
const LAZY_LOAD_TYPES = {
"entity-filter": () => import("../badges/hui-entity-filter-badge"),
"state-label": () => import("../badges/hui-state-label-badge"),
"power-total": () => import("../badges/energy/hui-power-total-badge"),
"gas-total": () => import("../badges/energy/hui-gas-total-badge"),
"water-total": () => import("../badges/energy/hui-water-total-badge"),
};
// This will not return an error card but will throw the error

View File

@@ -71,9 +71,6 @@ const LAZY_LOAD_TYPES = {
import("../cards/water/hui-water-flow-sankey-card"),
"power-sources-graph": () =>
import("../cards/energy/hui-power-sources-graph-card"),
"power-total": () => import("../cards/energy/hui-power-total-card"),
"water-total": () => import("../cards/energy/hui-water-total-card"),
"gas-total": () => import("../cards/energy/hui-gas-total-card"),
"power-sankey": () => import("../cards/energy/hui-power-sankey-card"),
"entity-filter": () => import("../cards/hui-entity-filter-card"),
error: () => import("../cards/hui-error-card"),

View File

@@ -6,6 +6,7 @@ import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/ha-spinner";
import type { HistoryStates } from "../../../data/history";
import { subscribeHistoryStatesTimeWindow } from "../../../data/history";
import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
@@ -66,6 +67,8 @@ export class HuiGraphHeaderFooter
private _error?: string;
private _history?: HistoryStates;
private _interval?: number;
private _subscribed?: Promise<(() => Promise<void>) | undefined>;
@@ -161,24 +164,8 @@ export class HuiGraphHeaderFooter
// Message came in before we had a chance to unload
return;
}
const width = this.clientWidth || this.offsetWidth;
// sample to 1 point per hour or 1 point per 5 pixels
const maxDetails = Math.max(
10,
this._config.detail! > 1
? Math.max(width / 5, this._config.hours_to_show!)
: this._config.hours_to_show!
);
const useMean = this._config.detail !== 2;
const { points } = coordinatesMinimalResponseCompressedState(
combinedHistory[this._config.entity],
width,
width / 5,
maxDetails,
{ minY: this._config.limits?.min, maxY: this._config.limits?.max },
useMean
);
this._coordinates = points;
this._history = combinedHistory;
this._computeCoordinates();
},
this._config.hours_to_show!,
[this._config.entity]
@@ -190,10 +177,63 @@ export class HuiGraphHeaderFooter
this._setRedrawTimer();
}
private _redrawGraph() {
if (this._coordinates) {
this._coordinates = [...this._coordinates];
private _computeCoordinates() {
if (!this._history || !this._config) {
return;
}
const entityHistory = this._history[this._config.entity];
if (!entityHistory?.length) {
return;
}
const width = this.clientWidth || this.offsetWidth;
// sample to 1 point per hour or 1 point per 5 pixels
const maxDetails = Math.max(
10,
this._config.detail! > 1
? Math.max(width / 5, this._config.hours_to_show!)
: this._config.hours_to_show!
);
const useMean = this._config.detail !== 2;
const { points } = coordinatesMinimalResponseCompressedState(
entityHistory,
width,
width / 5,
maxDetails,
{ minY: this._config.limits?.min, maxY: this._config.limits?.max },
useMean
);
this._coordinates = points;
}
private _redrawGraph() {
if (!this._history || !this._config?.hours_to_show) {
return;
}
const entityId = this._config.entity;
const entityHistory = this._history[entityId];
if (entityHistory?.length) {
const purgeBeforeTimestamp =
(Date.now() - this._config.hours_to_show * 60 * 60 * 1000) / 1000;
let purgedHistory = entityHistory.filter(
(entry) => entry.lu >= purgeBeforeTimestamp
);
if (purgedHistory.length !== entityHistory.length) {
if (
!purgedHistory.length ||
purgedHistory[0].lu !== purgeBeforeTimestamp
) {
// Preserve the last expired state as the start boundary
const lastExpiredState = {
...entityHistory[entityHistory.length - purgedHistory.length - 1],
};
lastExpiredState.lu = purgeBeforeTimestamp;
delete lastExpiredState.lc;
purgedHistory = [lastExpiredState, ...purgedHistory];
}
this._history = { ...this._history, [entityId]: purgedHistory };
}
}
this._computeCoordinates();
}
private _setRedrawTimer() {
@@ -211,6 +251,7 @@ export class HuiGraphHeaderFooter
this._subscribed.then((unsub) => unsub?.());
this._subscribed = undefined;
}
this._history = undefined;
}
protected updated(changedProps: PropertyValues) {

View File

@@ -631,11 +631,8 @@ class HUIRoot extends LitElement {
`;
}
private _handleContainerScroll = () => {
this.toggleAttribute(
"scrolled",
this._viewRoot ? this._viewRoot.scrollTop !== 0 : false
);
private _handleWindowScroll = () => {
this.toggleAttribute("scrolled", window.scrollY !== 0);
};
private _locationChanged = () => {
@@ -666,7 +663,7 @@ class HUIRoot extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._viewRoot?.addEventListener("scroll", this._handleContainerScroll, {
window.addEventListener("scroll", this._handleWindowScroll, {
passive: true,
});
this._handleUrlChanged();
@@ -677,7 +674,7 @@ class HUIRoot extends LitElement {
public connectedCallback(): void {
super.connectedCallback();
this._viewRoot?.addEventListener("scroll", this._handleContainerScroll, {
window.addEventListener("scroll", this._handleWindowScroll, {
passive: true,
});
window.addEventListener("popstate", this._handlePopState);
@@ -688,13 +685,10 @@ class HUIRoot extends LitElement {
public disconnectedCallback(): void {
super.disconnectedCallback();
this._viewRoot?.removeEventListener("scroll", this._handleContainerScroll);
window.removeEventListener("scroll", this._handleWindowScroll);
window.removeEventListener("popstate", this._handlePopState);
window.removeEventListener("location-changed", this._locationChanged);
this.toggleAttribute(
"scrolled",
this._viewRoot ? this._viewRoot.scrollTop !== 0 : false
);
this.toggleAttribute("scrolled", window.scrollY !== 0);
// Re-enable history scroll restoration when leaving the page
window.history.scrollRestoration = "auto";
}
@@ -827,11 +821,9 @@ class HUIRoot extends LitElement {
(this._restoreScroll && this._viewScrollPositions[newSelectView]) ||
0;
this._restoreScroll = false;
requestAnimationFrame(() => {
if (this._viewRoot) {
this._viewRoot.scrollTo({ behavior: "auto", top: position });
}
});
requestAnimationFrame(() =>
scrollTo({ behavior: "auto", top: position })
);
}
this._selectView(newSelectView, force);
});
@@ -1156,7 +1148,7 @@ class HUIRoot extends LitElement {
const path = this.config.views[viewIndex].path || viewIndex;
this._navigateToView(path);
} else if (!this._editMode) {
this._viewRoot?.scrollTo({ behavior: "smooth", top: 0 });
scrollTo({ behavior: "smooth", top: 0 });
}
}
@@ -1167,7 +1159,7 @@ class HUIRoot extends LitElement {
// Save scroll position of current view
if (this._curView != null) {
this._viewScrollPositions[this._curView] = this._viewRoot?.scrollTop ?? 0;
this._viewScrollPositions[this._curView] = window.scrollY;
}
viewIndex = viewIndex === undefined ? 0 : viewIndex;
@@ -1475,14 +1467,9 @@ class HUIRoot extends LitElement {
hui-view-container {
position: relative;
display: flex;
height: calc(
100vh - var(--header-height) - var(--safe-area-inset-top) - var(
--view-container-padding-top,
0px
)
);
min-height: 100vh;
box-sizing: border-box;
margin-top: calc(
padding-top: calc(
var(--header-height) + var(--safe-area-inset-top) +
var(--view-container-padding-top, 0px)
);
@@ -1498,6 +1485,8 @@ class HUIRoot extends LitElement {
padding-inline-start: var(--safe-area-inset-left);
}
hui-view-container > * {
display: flex;
flex-direction: column;
flex: 1 1 100%;
max-width: 100%;
}
@@ -1505,12 +1494,7 @@ class HUIRoot extends LitElement {
* In edit mode we have the tab bar on a new line *
*/
hui-view-container.has-tab-bar {
height: calc(
100vh - var(--header-height, 56px) - calc(
var(--tab-bar-height, 56px) - 2px
) - var(--safe-area-inset-top, 0px)
);
margin-top: calc(
padding-top: calc(
var(--header-height, 56px) +
calc(var(--tab-bar-height, 56px) - 2px) +
var(--safe-area-inset-top, 0px)

View File

@@ -418,15 +418,11 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
}
private _toggleView() {
// The scroll container is the hui-view-container parent
const scrollContainer = this.closest("hui-view-container");
const scrollTop = scrollContainer?.scrollTop ?? 0;
// Save current scroll position
if (this._sidebarTabActive) {
this._sidebarScrollTop = scrollTop;
this._sidebarScrollTop = window.scrollY;
} else {
this._contentScrollTop = scrollTop;
this._contentScrollTop = window.scrollY;
}
this._sidebarTabActive = !this._sidebarTabActive;
@@ -442,7 +438,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
const scrollY = this._sidebarTabActive
? this._sidebarScrollTop
: this._contentScrollTop;
scrollContainer?.scrollTo(0, scrollY);
window.scrollTo(0, scrollY);
});
}
@@ -465,6 +461,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
--column-min-width: var(--ha-view-sections-column-min-width, 320px);
--top-margin: var(--ha-view-sections-extra-top-margin, 80px);
display: block;
flex: 1;
}
@media (max-width: 600px) {
@@ -474,7 +471,9 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
}
.wrapper {
display: block;
display: flex;
flex-direction: column;
min-height: calc(100% - 2 * var(--row-gap));
padding: var(--row-gap) var(--column-gap);
box-sizing: content-box;
margin: 0 auto;
@@ -507,6 +506,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
gap: var(--row-gap) var(--column-gap);
padding: var(--row-gap) 0;
align-items: flex-start;
flex: 1 0 auto;
}
.wrapper.has-sidebar .container {

View File

@@ -4,7 +4,6 @@ import { customElement, property, state } from "lit/decorators";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { listenMediaQuery } from "../../../common/dom/media_query";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import { haStyleScrollbar } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
type BackgroundConfig = LovelaceViewConfig["background"];
@@ -23,7 +22,6 @@ class HuiViewContainer extends LitElement {
public connectedCallback(): void {
super.connectedCallback();
this.classList.add("ha-scrollbar");
this._setUpMediaQuery();
this._applyTheme();
}
@@ -76,16 +74,11 @@ class HuiViewContainer extends LitElement {
}
}
static styles = [
haStyleScrollbar,
css`
:host {
display: block;
height: 100%;
-webkit-overflow-scrolling: touch;
}
`,
];
static styles = css`
:host {
display: relative;
}
`;
}
declare global {

View File

@@ -102,6 +102,14 @@ export const getMyRedirects = (): Redirects => ({
component: "zwave_js",
redirect: "/config/zwave_js/dashboard",
},
config_matter: {
component: "matter",
redirect: "/config/matter/dashboard",
},
config_thread: {
component: "thread",
redirect: "/config/thread",
},
add_zigbee_device: {
component: "zha",
redirect: "/config/zha/add",

View File

@@ -244,20 +244,17 @@ export const haStyleDialogFixedTop = css`
`;
export const haStyleScrollbar = css`
.ha-scrollbar::-webkit-scrollbar,
:host(.ha-scrollbar)::-webkit-scrollbar {
.ha-scrollbar::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
.ha-scrollbar::-webkit-scrollbar-thumb,
:host(.ha-scrollbar)::-webkit-scrollbar-thumb {
.ha-scrollbar::-webkit-scrollbar-thumb {
border-radius: var(--ha-border-radius-sm);
background: var(--scrollbar-thumb-color);
}
.ha-scrollbar,
:host(.ha-scrollbar) {
.ha-scrollbar {
overflow-y: auto;
scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;

View File

@@ -359,6 +359,8 @@ export const darkColorStyles = css`
--outline-hover-color: rgba(225, 225, 225, 0.24);
--shadow-color: rgba(0, 0, 0, 0.48);
--scrollbar-thumb-color: rgb(110, 110, 110);
--mdc-ripple-color: #aaaaaa;
--mdc-linear-progress-buffer-color: rgba(255, 255, 255, 0.1);

View File

@@ -51,6 +51,21 @@ export const mainStyles = css`
/* dialog backdrop filter */
--ha-dialog-scrim-backdrop-filter: brightness(68%);
/* scrollbar */
scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;
}
html::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
html::-webkit-scrollbar-thumb {
border-radius: var(--ha-border-radius-sm);
background: var(--scrollbar-thumb-color);
border: 3px solid transparent;
}
`;

View File

@@ -548,7 +548,7 @@
},
"template": {
"yaml_warning": "It appears you may be writing YAML into this template field (saw ''{string}''), which is likely incorrect. This field is intended for templates only (e.g. '{{ states(sensor.test) > 0 }}' ).",
"learn_more": "Learn more about templating."
"learn_more": "Learn more about templating"
},
"text": {
"show_password": "Show password",
@@ -679,9 +679,9 @@
"no_entities": "You don't have any entities",
"no_match": "No entities found for {term}",
"show_entities": "Show entities",
"new_entity": "Create a new entity",
"new_entity": "Creates a new entity",
"placeholder": "Select an entity",
"create_helper": "Create a new {domain, select, \n undefined {} \n other {{domain} }\n } helper.",
"create_helper": "Create a new {domain, select, \n undefined {} \n other {{domain} }\n }helper",
"unknown": "Unknown entity selected"
},
"entity-name-picker": {
@@ -1524,6 +1524,7 @@
"settings": "Settings",
"edit": "Edit entity",
"details": "Details",
"toggle_yaml_mode": "Toggle YAML mode",
"translated": "Translated",
"raw": "Raw",
"back_to_info": "Back to info",
@@ -3811,7 +3812,7 @@
"grid": {
"title": "Electricity grid",
"sub": "Configure the amount of energy that you consume from the grid and, if you produce energy, give back to the grid. This allows Home Assistant to track your whole home energy usage.",
"learn_more": "More information on how to get started.",
"learn_more": "More information on how to get started",
"grid_connections": "Grid connections",
"add_connection": "Add grid connection",
"edit_connection": "Edit grid connection",
@@ -3872,7 +3873,7 @@
"solar": {
"title": "Solar panels",
"sub": "Let Home Assistant monitor your solar panels and give you insight on their performance.",
"learn_more": "More information on how to get started.",
"learn_more": "More information on how to get started",
"solar_production": "Solar production",
"edit_solar_production": "Edit solar production",
"delete_solar_production": "Remove solar production",
@@ -3895,7 +3896,7 @@
"battery": {
"title": "Home battery storage",
"sub": "If you have a battery system, you can configure it to monitor how much energy was stored and used from your battery.",
"learn_more": "More information on how to get started.",
"learn_more": "More information on how to get started",
"battery_systems": "Battery systems",
"edit_battery_system": "Edit battery system",
"delete_battery_system": "Remove battery system",
@@ -3922,7 +3923,7 @@
"gas": {
"title": "Gas consumption",
"sub": "Let Home Assistant monitor your gas usage.",
"learn_more": "More information on how to get started.",
"learn_more": "More information on how to get started",
"gas_consumption": "Gas consumption",
"edit_gas_source": "Edit gas source",
"delete_gas_source": "Remove gas source",
@@ -3951,7 +3952,7 @@
"water": {
"title": "Water consumption",
"sub": "Let Home Assistant monitor your water usage.",
"learn_more": "More information on how to get started.",
"learn_more": "More information on how to get started",
"water_consumption": "Water consumption",
"edit_water_source": "Edit water source",
"delete_water_source": "Remove water source",
@@ -3977,7 +3978,7 @@
"device_consumption": {
"title": "Individual electrical devices",
"sub": "Tracking the energy usage of individual devices allows Home Assistant to break down your energy usage by device.",
"learn_more": "More information on how to get started.",
"learn_more": "More information on how to get started",
"add_stat": "Pick entity to track energy of",
"selected_stat": "Tracking energy for",
"devices": "Devices",
@@ -3996,7 +3997,7 @@
"device_consumption_water": {
"title": "Individual water devices",
"sub": "Tracking the water usage of individual devices allows Home Assistant to break down your water usage by device.",
"learn_more": "More information on how to get started.",
"learn_more": "More information on how to get started",
"devices": "Devices",
"add_device": "Add device",
"dialog": {
@@ -5748,7 +5749,7 @@
"key_not_null": "The field key must not be empty.",
"key_not_unique": "The field key must not be the same value as another field.",
"fields": "Fields",
"link_help_fields": "Learn more about fields.",
"link_help_fields": "Learn more about fields",
"add_fields": "Add fields",
"add_field": "Add field",
"field": "field",
@@ -5776,7 +5777,7 @@
"delete_confirm_text": "{name} will be permanently deleted.",
"sequence": "Sequence",
"sequence_sentence": "The sequence of actions of this script.",
"link_available_actions": "Learn more about available actions.",
"link_available_actions": "Learn more about available actions",
"leave": {
"unsaved_new_title": "Save new script?",
"unsaved_new_text": "You can save your changes, or delete this script. You can't undo this action.",
@@ -6049,7 +6050,7 @@
"no_hooks_yet_link_integration": "webhook-based integration",
"no_hooks_yet2": " or by creating a ",
"no_hooks_yet_link_automation": "webhook automation",
"link_learn_more": "Learn more about creating webhook-powered automations.",
"link_learn_more": "Learn more about creating webhook-powered automations",
"loading": "Loading…",
"manage": "Manage",
"disable_hook_error_msg": "Failed to disable webhook:"

265
yarn.lock
View File

@@ -1990,74 +1990,75 @@ __metadata:
languageName: node
linkType: hard
"@html-eslint/core@npm:^0.56.0":
version: 0.56.0
resolution: "@html-eslint/core@npm:0.56.0"
"@html-eslint/core@npm:^0.57.0":
version: 0.57.0
resolution: "@html-eslint/core@npm:0.57.0"
dependencies:
"@html-eslint/types": "npm:^0.56.0"
"@html-eslint/types": "npm:^0.57.0"
eslint: "npm:^9.39.1"
html-standard: "npm:^0.0.13"
checksum: 10/0722e7405e56b256c471229dbd0ccb49cf0edd3e5224a087240f1954c74af0a6f289e5be0b83957480f1fa5abf6f7cbad7212134a6872a3fd66e1dc1ad879d66
checksum: 10/18b7aaf455969008dc5b79f6fd4b38792d893053e77c098c56a9238fe9b3ac3fa36441a88b908ade71de2f5179d5b4c45f552d7da5beff1d5c8df9cb3fd14e63
languageName: node
linkType: hard
"@html-eslint/eslint-plugin@npm:0.56.0":
version: 0.56.0
resolution: "@html-eslint/eslint-plugin@npm:0.56.0"
"@html-eslint/eslint-plugin@npm:0.57.0":
version: 0.57.0
resolution: "@html-eslint/eslint-plugin@npm:0.57.0"
dependencies:
"@eslint/plugin-kit": "npm:^0.4.1"
"@html-eslint/core": "npm:^0.56.0"
"@html-eslint/parser": "npm:^0.56.0"
"@html-eslint/template-parser": "npm:^0.56.0"
"@html-eslint/template-syntax-parser": "npm:^0.56.0"
"@html-eslint/types": "npm:^0.56.0"
"@html-eslint/core": "npm:^0.57.0"
"@html-eslint/parser": "npm:^0.57.0"
"@html-eslint/template-parser": "npm:^0.57.0"
"@html-eslint/template-syntax-parser": "npm:^0.57.0"
"@html-eslint/types": "npm:^0.57.0"
"@rviscomi/capo.js": "npm:^2.1.0"
peerDependencies:
eslint: ">=8.0.0 || ^10.0.0-0"
checksum: 10/16c5d638045266ca201b5dc154b3706262801668b2edde4f0905bdba9ba2e14b7175eacf9ca7d2ad5a5a03e3369843822d34ee607b6b7fbbeb020ac7f87629fb
checksum: 10/34cf11eaab3c07436c9c8b2896ed331c3ba5d591db5d8a8ff79d19b174e6e090323eb71e1368ad93d2afa945d4740749c599436a03be1195d4e4a4b0d3d9e9b6
languageName: node
linkType: hard
"@html-eslint/parser@npm:^0.56.0":
version: 0.56.0
resolution: "@html-eslint/parser@npm:0.56.0"
"@html-eslint/parser@npm:^0.57.0":
version: 0.57.0
resolution: "@html-eslint/parser@npm:0.57.0"
dependencies:
"@eslint/css-tree": "npm:^3.6.9"
"@html-eslint/template-syntax-parser": "npm:^0.56.0"
"@html-eslint/types": "npm:^0.56.0"
"@html-eslint/template-syntax-parser": "npm:^0.57.0"
"@html-eslint/types": "npm:^0.57.0"
css-tree: "npm:^3.1.0"
es-html-parser: "npm:0.3.1"
checksum: 10/1fead2be97481f461ee7030f4ea9274e5f969540118b4429e3eea361f62138660c1b7538668c7f8d55a8ae6985deff3056968d71bae62b91fb001d4209a24b50
checksum: 10/d742a26ef8122eab82e35ab27de6fe40cd45d2ac59ade9bed9d16df0da3cd18425c7892fedd245b9d60da2a4fbfb8c6afe05b1b6415033ab8fad996b94be020e
languageName: node
linkType: hard
"@html-eslint/template-parser@npm:^0.56.0":
version: 0.56.0
resolution: "@html-eslint/template-parser@npm:0.56.0"
"@html-eslint/template-parser@npm:^0.57.0":
version: 0.57.0
resolution: "@html-eslint/template-parser@npm:0.57.0"
dependencies:
"@html-eslint/types": "npm:^0.56.0"
"@html-eslint/types": "npm:^0.57.0"
es-html-parser: "npm:0.3.1"
checksum: 10/45e41dc8fb46336f40d1b78ef55ee3150d7a1034d393529eb40ce37a7d0a8fa04f6ed8f9a63478ff884d9588b1359b97a799681a37cd485d237dce867b7f9d20
checksum: 10/160d7f6156f242ed7bd4e7f13d79c350bf5db7fef089ba7b404ef770d80547620e9ee860ead8892a4079668db1cdccf6a6c6b91183a8c478a634314b39efea32
languageName: node
linkType: hard
"@html-eslint/template-syntax-parser@npm:^0.56.0":
version: 0.56.0
resolution: "@html-eslint/template-syntax-parser@npm:0.56.0"
"@html-eslint/template-syntax-parser@npm:^0.57.0":
version: 0.57.0
resolution: "@html-eslint/template-syntax-parser@npm:0.57.0"
dependencies:
"@html-eslint/types": "npm:^0.56.0"
checksum: 10/2cc4f2cbfa58c6381b2542ca86e424a97429326aa0b8b6b09e0a51d27b0e26fd723a4550e4c17536e00cfd506cbd0c61d8fd3dff1c829714c4432406096b4a01
"@html-eslint/types": "npm:^0.57.0"
checksum: 10/147d8d15ebc5ea9b75eea04463b9f29cfc476be2e98c5a2129442be6f66d32a9e8bb11a1be13cc3778df25e14d7ed1cd345342951ecec05292545f64bb3d92c6
languageName: node
linkType: hard
"@html-eslint/types@npm:^0.56.0":
version: 0.56.0
resolution: "@html-eslint/types@npm:0.56.0"
"@html-eslint/types@npm:^0.57.0":
version: 0.57.0
resolution: "@html-eslint/types@npm:0.57.0"
dependencies:
"@types/css-tree": "npm:^2.3.11"
"@types/estree": "npm:^1.0.6"
es-html-parser: "npm:0.3.1"
eslint: "npm:^9.39.1"
checksum: 10/5359ccfb8f431ccedfe7d9c0b20d883d19bbb6a24eee925c036c7774d6f2177eda021826abbc6f947b59c2364d7cf25a596591e852c1141000a3fab6ea70acc9
checksum: 10/44d0bde592786a2da966beab13c3d543e6669b1e38b6e5feea0f01121eec928be0081969df3ada1f27c9bd43d6286596afa56a2e075da843251079a940cf0825
languageName: node
linkType: hard
@@ -4257,6 +4258,13 @@ __metadata:
languageName: node
linkType: hard
"@rviscomi/capo.js@npm:^2.1.0":
version: 2.1.0
resolution: "@rviscomi/capo.js@npm:2.1.0"
checksum: 10/83ce30106ec6bd4b4791af12556119c568cb9d0ec7550b7b015a8c2c1c49687d8c6605564415d9d85e991581958212109ee5caa0db4166c1d3628033175e80b8
languageName: node
linkType: hard
"@shoelace-style/animations@npm:^1.2.0":
version: 1.2.0
resolution: "@shoelace-style/animations@npm:1.2.0"
@@ -5063,138 +5071,138 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.56.0"
"@typescript-eslint/eslint-plugin@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/eslint-plugin@npm:8.56.1"
dependencies:
"@eslint-community/regexpp": "npm:^4.12.2"
"@typescript-eslint/scope-manager": "npm:8.56.0"
"@typescript-eslint/type-utils": "npm:8.56.0"
"@typescript-eslint/utils": "npm:8.56.0"
"@typescript-eslint/visitor-keys": "npm:8.56.0"
"@typescript-eslint/scope-manager": "npm:8.56.1"
"@typescript-eslint/type-utils": "npm:8.56.1"
"@typescript-eslint/utils": "npm:8.56.1"
"@typescript-eslint/visitor-keys": "npm:8.56.1"
ignore: "npm:^7.0.5"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.4.0"
peerDependencies:
"@typescript-eslint/parser": ^8.56.0
"@typescript-eslint/parser": ^8.56.1
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/44201eae518c759cf3110f7e0a374372ef22bffa3cca61685aebe916b06bcb5f3ed6ffedba252199dca0006dfc22c54b132cd0337fd15e8c083eda22f9cf6b0c
checksum: 10/669d19cff91fcad5fe34dba97cc8c0c2df3160ae14646759fb23dfd6ffbb861d00d8d081e74d1060d544bfba0ea4d05588c5b73ae104907af26cc18189c0d139
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/parser@npm:8.56.0"
"@typescript-eslint/parser@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/parser@npm:8.56.1"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.56.0"
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/typescript-estree": "npm:8.56.0"
"@typescript-eslint/visitor-keys": "npm:8.56.0"
"@typescript-eslint/scope-manager": "npm:8.56.1"
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/typescript-estree": "npm:8.56.1"
"@typescript-eslint/visitor-keys": "npm:8.56.1"
debug: "npm:^4.4.3"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/9bdb2c7915665a1031499049974997020bbc34557803a8c3718b323d5583a3fdfc27797ec714b617743225c8dce9ab589eb442b71c435b464ad45365a6fbba57
checksum: 10/280b041a69153caf9e721b307781830483dd39d881b02d993156635bd8600e30e6a816aaead8bdd662ae5149b8870aef7b3823d3b98ec974d924c23a786fb6d9
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/project-service@npm:8.56.0"
"@typescript-eslint/project-service@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/project-service@npm:8.56.1"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.56.0"
"@typescript-eslint/types": "npm:^8.56.0"
"@typescript-eslint/tsconfig-utils": "npm:^8.56.1"
"@typescript-eslint/types": "npm:^8.56.1"
debug: "npm:^4.4.3"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/b46cc78bfb50ee84cb12e2e99c1a3d9606161980cf56ba33be6244ccff2ba4c78ceec46706ad597508fda167e0f965d849c1c516400ad41259027be3fbd815eb
checksum: 10/5e7fdc95aebcefc72fec77806bb0821a9a59e5e88f86d72b15ad011eb6110da05419b803875f021716a219fc7fb8517331a6736364344c8613a90209539a6d32
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/scope-manager@npm:8.56.0"
"@typescript-eslint/scope-manager@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/scope-manager@npm:8.56.1"
dependencies:
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/visitor-keys": "npm:8.56.0"
checksum: 10/3662355120ea8e21ce01c999decbd2a09fe4edb1c01e376fe347952d968ecfedff99b9484334e133e41284a15f2e1bc8efd490b1e73a16980614445c25b07b0d
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/visitor-keys": "npm:8.56.1"
checksum: 10/f358cf8bd32952eed005d4f34c1e95805baefe35abee96d866222b0eff8027cc02f831cee04b308707d74db2b415437a134191207b4213ee8acbc6d67a435616
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.56.0, @typescript-eslint/tsconfig-utils@npm:^8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.56.0"
"@typescript-eslint/tsconfig-utils@npm:8.56.1, @typescript-eslint/tsconfig-utils@npm:^8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/tsconfig-utils@npm:8.56.1"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/b1834aeffcdc07835eae0bf52aca573cba7e6528b5c1483e9b1f7f4f9e1f6450a8650796be11140e0437caf7eb1b0f9711c22989c8294547534f12614a759760
checksum: 10/d509f1ae4b14969173e498db6d15c833b6407db456c7fca9e25396798a35014229a73754691f353c4a99f5f0bbb4535b4144f42f84e596645a16d88a2022135f
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/type-utils@npm:8.56.0"
"@typescript-eslint/type-utils@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/type-utils@npm:8.56.1"
dependencies:
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/typescript-estree": "npm:8.56.0"
"@typescript-eslint/utils": "npm:8.56.0"
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/typescript-estree": "npm:8.56.1"
"@typescript-eslint/utils": "npm:8.56.1"
debug: "npm:^4.4.3"
ts-api-utils: "npm:^2.4.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/f272b9acc004f125cbf0df18265a43ba50cd3666262afc663585acdd1be6b6b30724bd8cf4cd5aa2757b7f10ceafa92fd1af30c1931fb22ac38521eda7f79c89
checksum: 10/2b07c674c26d797d05c05779ac5c89761b6b96680ecaf01440957727d12c6d06a2e48f0b139e45752eb4b53bf13c03940628656c519d362082b716d6a0ece6d9
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.56.0, @typescript-eslint/types@npm:^8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/types@npm:8.56.0"
checksum: 10/d7549535c99d9202742bf0191bcc2822c2d18a03e206be9ad5a6f6b0902de7381c93e8c238754fe5d1dfdcc22d7e3bbafa032f63ba165d6dc03e180f84b138f9
"@typescript-eslint/types@npm:8.56.1, @typescript-eslint/types@npm:^8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/types@npm:8.56.1"
checksum: 10/4bcffab5b0fd48adb731fcade86a776ca4a66e229defa5a282b58ba9c95af16ffc459a7d188e27c988a35be1f6fb5b812f9cf0952692eac38d5b3e87daafb20a
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/typescript-estree@npm:8.56.0"
"@typescript-eslint/typescript-estree@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/typescript-estree@npm:8.56.1"
dependencies:
"@typescript-eslint/project-service": "npm:8.56.0"
"@typescript-eslint/tsconfig-utils": "npm:8.56.0"
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/visitor-keys": "npm:8.56.0"
"@typescript-eslint/project-service": "npm:8.56.1"
"@typescript-eslint/tsconfig-utils": "npm:8.56.1"
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/visitor-keys": "npm:8.56.1"
debug: "npm:^4.4.3"
minimatch: "npm:^9.0.5"
minimatch: "npm:^10.2.2"
semver: "npm:^7.7.3"
tinyglobby: "npm:^0.2.15"
ts-api-utils: "npm:^2.4.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/55c8cfc7e265f320d780e69a677838821225fad8b853108ce2095f01509bf2ee8943280df9ac75560ed86265ef0e0a979ae4cb375d712f648b336032de79d19b
checksum: 10/af39dae0a8fada72295a11f0efb49f311241134b0a3d819100eeda6a2f92368844645873ba785de5513ad541cd9c2ba17b9bfed2679daac4682fa2a3b627f087
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/utils@npm:8.56.0"
"@typescript-eslint/utils@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/utils@npm:8.56.1"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.9.1"
"@typescript-eslint/scope-manager": "npm:8.56.0"
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/typescript-estree": "npm:8.56.0"
"@typescript-eslint/scope-manager": "npm:8.56.1"
"@typescript-eslint/types": "npm:8.56.1"
"@typescript-eslint/typescript-estree": "npm:8.56.1"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/f357bd15fe568cba0b89371e9a724eda38d78361a21dc0c4f49b0af4a23a140c77e2a8c6285c6fe8d8277e256a8a137aef7bcf6d97428eecd0c6e72ef08849ae
checksum: 10/528cbd187d8288a8cfce24a043f993b93711087f53d2b6f95cdd615a1a4087af1dab083a747761af97474a621c7b14f11c84ee50c250f31566ebc64cf73867fe
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.56.0":
version: 8.56.0
resolution: "@typescript-eslint/visitor-keys@npm:8.56.0"
"@typescript-eslint/visitor-keys@npm:8.56.1":
version: 8.56.1
resolution: "@typescript-eslint/visitor-keys@npm:8.56.1"
dependencies:
"@typescript-eslint/types": "npm:8.56.0"
"@typescript-eslint/types": "npm:8.56.1"
eslint-visitor-keys: "npm:^5.0.0"
checksum: 10/1eaa26ffe8a2c83d42d428beef207d793aef73c2e306f94e716d39519eaaa07547da898c7d63a5c406a3662895d735b4b1f33b513a1addb69473c65e7d92a2b5
checksum: 10/efed6a9867e7be203eec543e5a65da5aaec96aae55fba6fe74a305bf600e57c707764835e82bb8eb541f49a9b70442ff1e1a0ecf3543c78c91b84dda43b95c53
languageName: node
linkType: hard
@@ -6078,12 +6086,12 @@ __metadata:
languageName: node
linkType: hard
"barcode-detector@npm:3.0.8":
version: 3.0.8
resolution: "barcode-detector@npm:3.0.8"
"barcode-detector@npm:3.1.0":
version: 3.1.0
resolution: "barcode-detector@npm:3.1.0"
dependencies:
zxing-wasm: "npm:2.2.4"
checksum: 10/7de6225f659c69a0f4101d080a9e0812f2404c485fa2406424b8b13eaff274f6e7405c94de24827a09def52c32a63b19b9c9fba61c5274b074d558f696ec1684
zxing-wasm: "npm:3.0.0"
checksum: 10/60767161081b827e290b60bb3416999dee616bab39291ee55565df9b72d59f0bbbf511fd3bb85db18eee7c0ad9acf1ff90359cdb21e10f80793acd0105c86a1d
languageName: node
linkType: hard
@@ -9192,7 +9200,7 @@ __metadata:
"@fullcalendar/luxon3": "npm:6.1.20"
"@fullcalendar/timegrid": "npm:6.1.20"
"@home-assistant/webawesome": "npm:3.2.1-ha.3"
"@html-eslint/eslint-plugin": "npm:0.56.0"
"@html-eslint/eslint-plugin": "npm:0.57.0"
"@lezer/highlight": "npm:1.2.3"
"@lit-labs/motion": "npm:1.1.0"
"@lit-labs/observers": "npm:2.1.0"
@@ -9260,7 +9268,7 @@ __metadata:
app-datepicker: "npm:5.1.1"
babel-loader: "npm:10.0.0"
babel-plugin-template-html-minifier: "npm:4.1.0"
barcode-detector: "npm:3.0.8"
barcode-detector: "npm:3.1.0"
browserslist-useragent-regexp: "npm:4.1.3"
color-name: "npm:2.1.0"
comlink: "npm:4.4.2"
@@ -9335,7 +9343,7 @@ __metadata:
tinykeys: "npm:3.0.0"
ts-lit-plugin: "npm:2.0.2"
typescript: "npm:5.9.3"
typescript-eslint: "npm:8.56.0"
typescript-eslint: "npm:8.56.1"
ua-parser-js: "npm:2.0.9"
vite-tsconfig-paths: "npm:6.1.1"
vitest: "npm:4.0.18"
@@ -11117,15 +11125,6 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:^9.0.5":
version: 9.0.5
resolution: "minimatch@npm:9.0.5"
dependencies:
brace-expansion: "npm:^2.0.1"
checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348
languageName: node
linkType: hard
"minimist@npm:^1.2.0, minimist@npm:^1.2.6":
version: 1.2.8
resolution: "minimist@npm:1.2.8"
@@ -14140,12 +14139,12 @@ __metadata:
languageName: node
linkType: hard
"type-fest@npm:^5.2.0":
version: 5.4.1
resolution: "type-fest@npm:5.4.1"
"type-fest@npm:^5.4.4":
version: 5.4.4
resolution: "type-fest@npm:5.4.4"
dependencies:
tagged-tag: "npm:^1.0.0"
checksum: 10/be7d4749e1e5cf2e2c9904fa1aaf9da5eef6c47c130881bf93bfd5a670b2ab59c5502466768e42c521281056a2375b1617176a75cf6c52b575f4bbabbd450b21
checksum: 10/0bbdca645f95740587f389a2d712fe8d5e9ab7d13e74aac97cf396112510abcaab6b75fd90d65172bc13b02fdfc827e6a871322cc9c1c1a5a2754d9ab264c6f5
languageName: node
linkType: hard
@@ -14212,18 +14211,18 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.56.0":
version: 8.56.0
resolution: "typescript-eslint@npm:8.56.0"
"typescript-eslint@npm:8.56.1":
version: 8.56.1
resolution: "typescript-eslint@npm:8.56.1"
dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.56.0"
"@typescript-eslint/parser": "npm:8.56.0"
"@typescript-eslint/typescript-estree": "npm:8.56.0"
"@typescript-eslint/utils": "npm:8.56.0"
"@typescript-eslint/eslint-plugin": "npm:8.56.1"
"@typescript-eslint/parser": "npm:8.56.1"
"@typescript-eslint/typescript-estree": "npm:8.56.1"
"@typescript-eslint/utils": "npm:8.56.1"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/7c07af35e6b4eaaebad4b50c37bc6dd50f0cecebe5a4e648ef117fd43a8496f3132020061e19a2fbaf826978e91c100054e638701bf89db8f342dd1353bb5b7e
checksum: 10/e18cd347ddce0f0e5b28121346f27736a418adf68e73d613690ea3a1d0adfe03bc393f77a8872c2cef77ca74bcc0974212d1775c360de33a9987a94cda11a05b
languageName: node
linkType: hard
@@ -15704,14 +15703,14 @@ __metadata:
languageName: node
linkType: hard
"zxing-wasm@npm:2.2.4":
version: 2.2.4
resolution: "zxing-wasm@npm:2.2.4"
"zxing-wasm@npm:3.0.0":
version: 3.0.0
resolution: "zxing-wasm@npm:3.0.0"
dependencies:
"@types/emscripten": "npm:^1.41.5"
type-fest: "npm:^5.2.0"
type-fest: "npm:^5.4.4"
peerDependencies:
"@types/emscripten": ">=1.39.6"
checksum: 10/e5928cbb066c854c970cbf724e978e502c3469d69de2469bd37d1f6ab8f5d2a2acdbaa9dea32d35cfc058e2b482e29a9c4f12161d9df3e1e952c30dda3a96d8c
checksum: 10/0acf04829acf8f3987173af011784642792fc877c7765f79222fe33efff8af09fbf95bf5d590d2490ae39ec411e6c4de06ea24e96d4eb48189b9d06f7502eaa2
languageName: node
linkType: hard