Compare commits

..

8 Commits

Author SHA1 Message Date
Petar Petrov 190ef6bb87 Show diagnostic moisture binary sensors on security dashboard 2026-05-19 10:27:23 +03:00
Jan-Philipp Benecke 91b6a4c4b6 Migrate energy sources table and drop mwc data table dependency (#52097)
* Migrate energy sources table and drop mwc data table dependency

* Address review comments

* Address review comments
2026-05-19 09:58:18 +03:00
karwosts 643cc4ca7d Make energy electric sources nameable (#52051)
Make electric sources nameable

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2026-05-19 06:37:49 +00:00
renovate[bot] 9ef71e6cf4 Update tsparticles to v4.0.1 (#52095)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-19 07:18:29 +02:00
renovate[bot] bface72af7 Lock file maintenance (#52096)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-19 07:18:12 +02:00
Paul Bottein 90028b2e22 Clarify cleaning order hint in vacuum more info (#52087) 2026-05-18 22:29:36 +02:00
Ben Hamilton (Ben Gertzfield) 914c48abd5 Allow media player source card feature when list is empty (#52094) 2026-05-18 19:05:12 +00:00
renovate[bot] 79c082acde Update dependency eslint to v10.4.0 (#52093)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-18 20:36:03 +02:00
18 changed files with 1120 additions and 963 deletions
+3 -4
View File
@@ -62,7 +62,6 @@
"@lit-labs/virtualizer": "2.1.1",
"@lit/context": "1.1.6",
"@lit/reactive-element": "2.1.2",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
"@material/mwc-base": "0.27.0",
"@material/mwc-formfield": "patch:@material/mwc-formfield@npm%3A0.27.0#~/.yarn/patches/@material-mwc-formfield-npm-0.27.0-9528cb60f6.patch",
"@material/mwc-list": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
@@ -75,8 +74,8 @@
"@replit/codemirror-indentation-markers": "6.5.3",
"@swc/helpers": "0.5.21",
"@thomasloven/round-slider": "0.6.0",
"@tsparticles/engine": "4.0.0",
"@tsparticles/preset-links": "4.0.0",
"@tsparticles/engine": "4.0.1",
"@tsparticles/preset-links": "4.0.1",
"@vibrant/color": "4.0.4",
"@webcomponents/scoped-custom-element-registry": "0.0.10",
"@webcomponents/webcomponentsjs": "2.8.0",
@@ -166,7 +165,7 @@
"babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.4",
"del": "8.0.1",
"eslint": "10.3.0",
"eslint": "10.4.0",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-webpack": "0.13.11",
"eslint-plugin-import-x": "4.16.2",
+3
View File
@@ -148,6 +148,7 @@ export interface GridSourceTypeEnergyPreference {
power_config?: PowerConfig;
cost_adjustment_day: number;
name?: string;
}
export interface SolarSourceTypeEnergyPreference {
@@ -156,6 +157,7 @@ export interface SolarSourceTypeEnergyPreference {
stat_energy_from: string;
stat_rate?: string;
config_entry_solar_forecast: string[] | null;
name?: string;
}
export interface BatterySourceTypeEnergyPreference {
@@ -165,6 +167,7 @@ export interface BatterySourceTypeEnergyPreference {
stat_rate?: string; // always available if power_config is set
power_config?: PowerConfig;
stat_soc?: string;
name?: string;
}
export interface GasSourceTypeEnergyPreference {
type: "gas";
@@ -200,12 +200,13 @@ class MoreInfoMediaPlayer extends LitElement {
protected _renderSourceControl() {
if (
!this.stateObj ||
!supportsFeature(this.stateObj, MediaPlayerEntityFeature.SELECT_SOURCE) ||
!this.stateObj.attributes.source_list?.length
!supportsFeature(this.stateObj, MediaPlayerEntityFeature.SELECT_SOURCE)
) {
return nothing;
}
const sourceList = this.stateObj.attributes.source_list || [];
return html`<ha-tooltip for="source-button">
${this.hass.localize(`ui.card.media_player.source`)}
</ha-tooltip>
@@ -217,7 +218,7 @@ class MoreInfoMediaPlayer extends LitElement {
.path=${mdiLoginVariant}
>
</ha-icon-button>
${this.stateObj.attributes.source_list!.map(
${sourceList.map(
(source) =>
html`<ha-dropdown-item
.value=${source}
@@ -1,6 +1,6 @@
import { mdiBatteryHigh, mdiDelete, mdiPencil, mdiPlus } from "@mdi/js";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-card";
@@ -100,19 +100,24 @@ export class EnergyBatterySettings extends LitElement {
></ha-svg-icon>`}
<div class="content">
<span class="label"
>${getStatisticLabel(
>${source.name ||
getStatisticLabel(
this.hass,
source.stat_energy_from,
this.statsMetadata?.[source.stat_energy_from]
)}</span
>
<span class="label"
>${getStatisticLabel(
this.hass,
source.stat_energy_to,
this.statsMetadata?.[source.stat_energy_to]
)}</span
>
${source.name
? nothing
: html`
<span class="label"
>${getStatisticLabel(
this.hass,
source.stat_energy_to,
this.statsMetadata?.[source.stat_energy_to]
)}</span
>
`}
</div>
<ha-icon-button
.label=${this.hass.localize(
@@ -153,6 +158,7 @@ export class EnergyBatterySettings extends LitElement {
private _addSource() {
showEnergySettingsBatteryDialog(this, {
statsMetadata: this.statsMetadata,
battery_sources: this.preferences.energy_sources.filter(
(src) => src.type === "battery"
) as BatterySourceTypeEnergyPreference[],
@@ -169,6 +175,7 @@ export class EnergyBatterySettings extends LitElement {
const origSource: BatterySourceTypeEnergyPreference =
ev.currentTarget.closest(".row").source;
showEnergySettingsBatteryDialog(this, {
statsMetadata: this.statsMetadata,
source: { ...origSource },
battery_sources: this.preferences.energy_sources.filter(
(src) => src.type === "battery"
@@ -124,13 +124,16 @@ export class EnergyGridSettings extends LitElement {
></ha-svg-icon>`}
<div class="content">
<span class="label"
>${getStatisticLabel(
>${source.name ||
getStatisticLabel(
this.hass,
primaryStat,
this.statsMetadata?.[primaryStat]
)}</span
>
${source.stat_energy_from && source.stat_energy_to
${source.stat_energy_from &&
source.stat_energy_to &&
!source.name
? html`<span class="label secondary"
>${getStatisticLabel(
this.hass,
@@ -266,6 +269,7 @@ export class EnergyGridSettings extends LitElement {
private _addSource() {
showEnergySettingsGridDialog(this, {
statsMetadata: this.statsMetadata,
grid_sources: this._getGridSources(),
saveCallback: async (source) => {
const preferences: EnergyPreferences = {
@@ -283,6 +287,7 @@ export class EnergyGridSettings extends LitElement {
const sourceIndex: number = row.sourceIndex;
showEnergySettingsGridDialog(this, {
statsMetadata: this.statsMetadata,
source: { ...origSource },
grid_sources: this._getGridSources(),
saveCallback: async (newSource) => {
@@ -101,7 +101,8 @@ export class EnergySolarSettings extends LitElement {
.path=${mdiSolarPower}
></ha-svg-icon>`}
<span class="content"
>${getStatisticLabel(
>${source.name ||
getStatisticLabel(
this.hass,
source.stat_energy_from,
this.statsMetadata?.[source.stat_energy_from]
@@ -154,6 +155,7 @@ export class EnergySolarSettings extends LitElement {
private _addSource() {
showEnergySettingsSolarDialog(this, {
statsMetadata: this.statsMetadata,
info: this.info!,
solar_sources: this.preferences.energy_sources.filter(
(src) => src.type === "solar"
@@ -171,6 +173,7 @@ export class EnergySolarSettings extends LitElement {
const origSource: SolarSourceTypeEnergyPreference =
ev.currentTarget.closest(".row").source;
showEnergySettingsSolarDialog(this, {
statsMetadata: this.statsMetadata,
info: this.info!,
source: { ...origSource },
solar_sources: this.preferences.energy_sources.filter(
@@ -6,6 +6,7 @@ import "../../../../components/entity/ha-statistic-picker";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog";
import "../../../../components/ha-dialog-footer";
import "../../../../components/input/ha-input";
import type {
BatterySourceTypeEnergyPreference,
PowerConfig,
@@ -14,6 +15,11 @@ import {
emptyBatteryEnergyPreference,
energyStatisticHelpUrl,
} from "../../../../data/energy";
import {
getStatisticLabel,
getStatisticMetadata,
isExternalStatistic,
} from "../../../../data/recorder";
import { getSensorDeviceClassConvertibleUnits } from "../../../../data/sensor";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../resources/styles";
@@ -27,6 +33,7 @@ import {
type PowerType,
} from "./ha-energy-power-config";
import type { EnergySettingsBatteryDialogParams } from "./show-dialogs-energy";
import type { HaInput } from "../../../../components/input/ha-input";
const energyUnitClasses = ["energy"];
const socStatisticsUnits = ["%"];
@@ -174,6 +181,32 @@ export class DialogEnergyBatterySettings
)}
></ha-statistic-picker>
<ha-input
.label=${this.hass.localize(
"ui.panel.config.energy.battery.dialog.display_name"
)}
type="text"
.disabled=${!(
this._source?.stat_energy_from || this._source?.stat_energy_to
)}
.value=${this._source?.name || ""}
.placeholder=${this._source?.stat_energy_from
? getStatisticLabel(
this.hass,
this._source.stat_energy_from,
this._params?.statsMetadata?.[this._source.stat_energy_from]
)
: this._source?.stat_energy_to
? getStatisticLabel(
this.hass,
this._source.stat_energy_to,
this._params?.statsMetadata?.[this._source.stat_energy_to]
)
: ""}
@input=${this._nameChanged}
>
</ha-input>
<ha-energy-power-config
.hass=${this.hass}
.powerType=${this._powerType}
@@ -232,12 +265,39 @@ export class DialogEnergyBatterySettings
return true;
}
private async _updateMetadata(statId: string) {
if (
statId &&
isExternalStatistic(statId) &&
this._params?.statsMetadata &&
!(statId in this._params.statsMetadata)
) {
const [metadata] = await getStatisticMetadata(this.hass, [statId]);
if (metadata) {
this._params.statsMetadata[statId] = metadata;
this.requestUpdate("_params");
}
}
}
private _statisticToChanged(ev: ValueChangedEvent<string>) {
this._source = { ...this._source!, stat_energy_to: ev.detail.value };
this._updateMetadata(ev.detail.value);
}
private _statisticFromChanged(ev: ValueChangedEvent<string>) {
this._source = { ...this._source!, stat_energy_from: ev.detail.value };
this._updateMetadata(ev.detail.value);
}
private _nameChanged(ev: InputEvent) {
this._source = {
...this._source!,
name: (ev.target as HaInput).value,
};
if (!this._source.name) {
delete this._source.name;
}
}
private _handlePowerConfigChanged(
@@ -261,6 +321,9 @@ export class DialogEnergyBatterySettings
stat_energy_from: this._source!.stat_energy_from,
stat_energy_to: this._source!.stat_energy_to,
};
if (this._source?.name) {
source.name = this._source.name;
}
// Only include power_config if a power type is selected
if (this._powerType !== "none") {
@@ -19,7 +19,11 @@ import {
emptyGridSourceEnergyPreference,
energyStatisticHelpUrl,
} from "../../../../data/energy";
import { isExternalStatistic } from "../../../../data/recorder";
import {
getStatisticLabel,
getStatisticMetadata,
isExternalStatistic,
} from "../../../../data/recorder";
import { getSensorDeviceClassConvertibleUnits } from "../../../../data/sensor";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../resources/styles";
@@ -33,6 +37,7 @@ import {
type PowerType,
} from "./ha-energy-power-config";
import type { EnergySettingsGridDialogParams } from "./show-dialogs-energy";
import type { HaInput } from "../../../../components/input/ha-input";
const energyUnitClasses = ["energy"];
@@ -224,6 +229,33 @@ export class DialogEnergyGridSettings
)}
></ha-statistic-picker>
<ha-input
class="name"
.label=${this.hass.localize(
"ui.panel.config.energy.grid.dialog.display_name"
)}
type="text"
.disabled=${!(
this._source?.stat_energy_from || this._source?.stat_energy_to
)}
.value=${this._source?.name || ""}
.placeholder=${this._source?.stat_energy_from
? getStatisticLabel(
this.hass,
this._source.stat_energy_from,
this._params?.statsMetadata?.[this._source.stat_energy_from]
)
: this._source?.stat_energy_to
? getStatisticLabel(
this.hass,
this._source.stat_energy_to,
this._params?.statsMetadata?.[this._source.stat_energy_to]
)
: ""}
@input=${this._nameChanged}
>
</ha-input>
<p class="section-label">
${this.hass.localize(
"ui.panel.config.energy.grid.dialog.import_cost"
@@ -444,6 +476,21 @@ export class DialogEnergyGridSettings
return true;
}
private async _updateMetadata(statId: string) {
if (
statId &&
isExternalStatistic(statId) &&
this._params?.statsMetadata &&
!(statId in this._params.statsMetadata)
) {
const [metadata] = await getStatisticMetadata(this.hass, [statId]);
if (metadata) {
this._params.statsMetadata[statId] = metadata;
this.requestUpdate("_params");
}
}
}
private _statisticFromChanged(ev: ValueChangedEvent<string>) {
this._source = { ...this._source!, stat_energy_from: ev.detail.value };
// Reset cost type if switching to external statistic with incompatible cost type
@@ -459,6 +506,7 @@ export class DialogEnergyGridSettings
number_energy_price: null,
};
}
this._updateMetadata(ev.detail.value);
}
private _statisticToChanged(ev: ValueChangedEvent<string>) {
@@ -487,6 +535,17 @@ export class DialogEnergyGridSettings
number_energy_price_export: null,
};
}
this._updateMetadata(ev.detail.value);
}
private _nameChanged(ev: InputEvent) {
this._source = {
...this._source!,
name: (ev.target as HaInput).value,
};
if (!this._source.name) {
delete this._source.name;
}
}
private _handleImportCostTypeChanged(ev: Event) {
@@ -569,6 +628,9 @@ export class DialogEnergyGridSettings
number_energy_price_export: this._source!.number_energy_price_export,
cost_adjustment_day: this._source!.cost_adjustment_day,
};
if (this._source?.name) {
source.name = this._source.name;
}
// Only include power_config if a power type is selected
if (this._powerType !== "none") {
@@ -601,6 +663,9 @@ export class DialogEnergyGridSettings
ha-input:last-of-type {
margin-bottom: 0;
}
ha-input.name {
margin-top: var(--ha-space-4);
}
ha-radio-group {
margin-bottom: var(--ha-space-4);
}
@@ -11,6 +11,7 @@ import "../../../../components/ha-dialog";
import "../../../../components/ha-dialog-footer";
import "../../../../components/ha-svg-icon";
import "../../../../components/radio/ha-radio-group";
import "../../../../components/input/ha-input";
import type { HaRadioGroup } from "../../../../components/radio/ha-radio-group";
import "../../../../components/radio/ha-radio-option";
import type { ConfigEntry } from "../../../../data/config_entries";
@@ -27,6 +28,12 @@ import { haStyle, haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
import { brandsUrl } from "../../../../util/brands-url";
import type { EnergySettingsSolarDialogParams } from "./show-dialogs-energy";
import {
getStatisticLabel,
getStatisticMetadata,
isExternalStatistic,
} from "../../../../data/recorder";
import type { HaInput } from "../../../../components/input/ha-input";
const energyUnitClasses = ["energy"];
const powerUnitClasses = ["power"];
@@ -129,6 +136,24 @@ export class DialogEnergySolarSettings
autofocus
></ha-statistic-picker>
<ha-input
.label=${this.hass.localize(
"ui.panel.config.energy.solar.dialog.display_name"
)}
type="text"
.disabled=${!this._source?.stat_energy_from}
.value=${this._source?.name || ""}
.placeholder=${this._source?.stat_energy_from
? getStatisticLabel(
this.hass,
this._source.stat_energy_from,
this._params?.statsMetadata?.[this._source.stat_energy_from]
)
: ""}
@input=${this._nameChanged}
>
</ha-input>
<ha-statistic-picker
.hass=${this.hass}
.includeUnitClass=${powerUnitClasses}
@@ -284,14 +309,38 @@ export class DialogEnergySolarSettings
});
}
private _statisticChanged(ev: ValueChangedEvent<string>) {
private async _statisticChanged(ev: ValueChangedEvent<string>) {
this._source = { ...this._source!, stat_energy_from: ev.detail.value };
if (
ev.detail.value &&
isExternalStatistic(ev.detail.value) &&
this._params?.statsMetadata &&
!(ev.detail.value in this._params.statsMetadata)
) {
const [metadata] = await getStatisticMetadata(this.hass, [
ev.detail.value,
]);
if (metadata) {
this._params.statsMetadata[ev.detail.value] = metadata;
this.requestUpdate("_params");
}
}
}
private _powerStatisticChanged(ev: ValueChangedEvent<string>) {
this._source = { ...this._source!, stat_rate: ev.detail.value };
}
private _nameChanged(ev: InputEvent) {
this._source = {
...this._source!,
name: (ev.target as HaInput).value,
};
if (!this._source.name) {
delete this._source.name;
}
}
private async _save() {
try {
if (!this._forecast) {
@@ -14,6 +14,7 @@ import type { StatisticsMetaData } from "../../../../data/recorder";
export interface EnergySettingsGridDialogParams {
source?: GridSourceTypeEnergyPreference;
grid_sources: GridSourceTypeEnergyPreference[];
statsMetadata?: Record<string, StatisticsMetaData>;
saveCallback: (source: GridSourceTypeEnergyPreference) => Promise<void>;
}
@@ -21,12 +22,14 @@ export interface EnergySettingsSolarDialogParams {
info: EnergyInfo;
source?: SolarSourceTypeEnergyPreference;
solar_sources: SolarSourceTypeEnergyPreference[];
statsMetadata?: Record<string, StatisticsMetaData>;
saveCallback: (source: SolarSourceTypeEnergyPreference) => Promise<void>;
}
export interface EnergySettingsBatteryDialogParams {
source?: BatterySourceTypeEnergyPreference;
battery_sources: BatterySourceTypeEnergyPreference[];
statsMetadata?: Record<string, StatisticsMetaData>;
saveCallback: (source: BatterySourceTypeEnergyPreference) => Promise<void>;
}
@@ -26,8 +26,7 @@ export const supportsMediaPlayerSourceCardFeature = (
const domain = computeDomain(stateObj.entity_id);
return (
domain === "media_player" &&
supportsFeature(stateObj, MediaPlayerEntityFeature.SELECT_SOURCE) &&
!!stateObj.attributes.source_list?.length
supportsFeature(stateObj, MediaPlayerEntityFeature.SELECT_SOURCE)
);
};
@@ -355,11 +355,13 @@ export class HuiEnergySolarGraphCard
name: this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_solar_graph.production",
{
name: getStatisticLabel(
this.hass,
source.stat_energy_from,
statisticsMetaData[source.stat_energy_from]
),
name:
source.name ||
getStatisticLabel(
this.hass,
source.stat_energy_from,
statisticsMetaData[source.stat_energy_from]
),
}
),
barMaxWidth: 50,
@@ -463,11 +465,13 @@ export class HuiEnergySolarGraphCard
name: this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_solar_graph.forecast",
{
name: getStatisticLabel(
this.hass,
source.stat_energy_from,
statisticsMetaData[source.stat_energy_from]
),
name:
source.name ||
getStatisticLabel(
this.hass,
source.stat_energy_from,
statisticsMetaData[source.stat_energy_from]
),
}
),
step: false,
@@ -1,8 +1,6 @@
// @ts-ignore
import dataTableStyles from "@material/data-table/dist/mdc.data-table.min.css";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, unsafeCSS, nothing } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
@@ -551,7 +549,13 @@ export class HuiEnergySourcesTableCard
null,
null,
showCosts,
compare
compare,
source.name
? this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_sources_table.named_battery_discharged",
{ name: source.name }
)
: ""
)}${this._renderRow(
computedStyles,
"battery_in",
@@ -563,7 +567,13 @@ export class HuiEnergySourcesTableCard
null,
null,
showCosts,
compare
compare,
source.name
? this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_sources_table.named_battery_charged",
{ name: source.name }
)
: ""
)}`;
})}
${types.battery
@@ -630,6 +640,15 @@ export class HuiEnergySourcesTableCard
return nothing;
}
const name = !source.name
? ""
: source.stat_energy_to
? this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_sources_table.named_grid_imported",
{ name: source.name }
)
: source.name;
return this._renderRow(
computedStyles,
"grid_consumption",
@@ -641,7 +660,8 @@ export class HuiEnergySourcesTableCard
cost,
costCompare,
showCosts,
compare
compare,
name
);
})();
@@ -670,6 +690,15 @@ export class HuiEnergySourcesTableCard
return nothing;
}
const name = !source.name
? ""
: source.stat_energy_from
? this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_sources_table.named_grid_exported",
{ name: source.name }
)
: source.name;
return this._renderRow(
computedStyles,
"grid_return",
@@ -681,7 +710,8 @@ export class HuiEnergySourcesTableCard
-cost,
-costCompare,
showCosts,
compare
compare,
name
);
})();
@@ -756,63 +786,120 @@ export class HuiEnergySourcesTableCard
}
}
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(dataTableStyles)}
.mdc-data-table {
width: 100%;
border: 0;
}
.mdc-data-table__header-cell,
.mdc-data-table__cell {
color: var(--primary-text-color);
border-bottom-color: var(--divider-color);
text-align: var(--float-start);
}
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
background-color: rgba(var(--rgb-primary-text-color), 0.04);
}
.clickable {
cursor: pointer;
}
.total {
--mdc-typography-body2-font-weight: var(--ha-font-weight-medium);
}
.total .mdc-data-table__cell {
border-top: 1px solid var(--divider-color);
}
ha-card {
max-height: 100%;
overflow: auto;
}
.card-header {
padding-bottom: 0;
}
.content {
padding: 16px;
}
.has-header {
padding-top: 0;
}
.cell-bullet {
width: 32px;
padding-right: 0;
padding-inline-end: 0;
padding-inline-start: 16px;
direction: var(--direction);
}
.bullet {
border-width: 1px;
border-style: solid;
border-radius: var(--ha-border-radius-sm);
height: 16px;
width: 32px;
}
.mdc-data-table__cell--numeric {
direction: ltr;
}
`;
}
static styles: CSSResultGroup = css`
.mdc-data-table__content,
.mdc-data-table__cell {
font-family: var(--ha-font-family-body);
-moz-osx-font-smoothing: var(--ha-moz-osx-font-smoothing);
-webkit-font-smoothing: var(--ha-font-smoothing);
font-size: var(--ha-font-size-m);
line-height: var(--ha-line-height-normal);
font-weight: var(--ha-font-weight-normal);
letter-spacing: 0.0178571429em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-data-table {
background-color: var(--card-background-color);
border-radius: var(--ha-border-radius-sm);
border: 0;
box-sizing: border-box;
display: inline-flex;
flex-direction: column;
position: relative;
width: 100%;
}
.mdc-data-table__table-container {
-webkit-overflow-scrolling: touch;
overflow-x: auto;
width: 100%;
}
.mdc-data-table__table {
min-width: 100%;
border: 0;
border-spacing: 0;
table-layout: fixed;
white-space: nowrap;
}
.mdc-data-table__header-row {
height: 56px;
}
.mdc-data-table__row {
background-color: inherit;
height: 52px;
}
.mdc-data-table__header-cell,
.mdc-data-table__cell {
border-bottom-width: 1px;
border-bottom-style: solid;
box-sizing: border-box;
color: var(--primary-text-color);
border-bottom-color: var(--divider-color);
overflow: hidden;
padding: 0 16px;
text-align: var(--float-start);
text-overflow: ellipsis;
}
.mdc-data-table__header-cell {
background-color: var(--card-background-color);
font-family: var(--ha-font-family-body);
-moz-osx-font-smoothing: var(--ha-moz-osx-font-smoothing);
-webkit-font-smoothing: var(--ha-font-smoothing);
font-size: var(--ha-font-size-m);
line-height: var(--ha-line-height-normal);
font-weight: var(--ha-font-weight-medium);
letter-spacing: 0.0071428571em;
text-decoration: inherit;
text-transform: inherit;
}
.mdc-data-table__row:last-child .mdc-data-table__cell {
border-bottom: none;
}
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
background-color: rgba(var(--rgb-primary-text-color), 0.04);
}
.clickable {
cursor: pointer;
}
.total .mdc-data-table__cell {
border-top: 1px solid var(--divider-color);
font-weight: var(--ha-font-weight-medium);
}
ha-card {
max-height: 100%;
overflow: auto;
}
.card-header {
padding-bottom: 0;
}
.content {
padding: 16px;
}
.has-header {
padding-top: 0;
}
.cell-bullet {
width: 32px;
padding-right: 0;
padding-inline-end: 0;
padding-inline-start: 16px;
direction: var(--direction);
}
.bullet {
border-width: 1px;
border-style: solid;
border-radius: var(--ha-border-radius-sm);
height: 16px;
width: 32px;
}
.mdc-data-table__cell--numeric {
text-align: var(--float-end);
direction: ltr;
}
.mdc-data-table__header-cell--numeric {
text-align: var(--float-end);
}
`;
}
declare global {
@@ -259,6 +259,16 @@ export class HuiEnergyUsageGraphCard
from_battery?: string[];
} = {};
const statLabels: {
to_grid: Record<string, string>;
from_grid: Record<string, string>;
to_battery: Record<string, string>;
} = {
to_grid: {},
from_grid: {},
to_battery: {},
};
for (const source of energyData.prefs.energy_sources) {
if (source.type === "solar") {
if (statIds.solar) {
@@ -277,6 +287,12 @@ export class HuiEnergyUsageGraphCard
statIds.to_battery = [source.stat_energy_to];
statIds.from_battery = [source.stat_energy_from];
}
if (source.name) {
statLabels.to_battery[source.stat_energy_to] = this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_sources_table.named_battery_charged",
{ name: source.name }
);
}
continue;
}
@@ -291,6 +307,15 @@ export class HuiEnergyUsageGraphCard
} else {
statIds.from_grid = [gridSource.stat_energy_from];
}
if (gridSource.name) {
statLabels.from_grid[gridSource.stat_energy_from] =
gridSource.stat_energy_to
? this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_usage_graph.named_grid_consumed",
{ name: gridSource.name }
)
: gridSource.name;
}
}
if (gridSource.stat_energy_to) {
if (statIds.to_grid) {
@@ -298,6 +323,15 @@ export class HuiEnergyUsageGraphCard
} else {
statIds.to_grid = [gridSource.stat_energy_to];
}
if (gridSource.name) {
statLabels.to_grid[gridSource.stat_energy_to] =
gridSource.stat_energy_from
? this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_usage_graph.named_grid_exported",
{ name: gridSource.name }
)
: gridSource.name;
}
}
}
@@ -320,7 +354,7 @@ export class HuiEnergyUsageGraphCard
}
});
const labels = {
const typeLabels = {
used_grid: this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_usage_graph.combined_from_grid"
),
@@ -354,7 +388,8 @@ export class HuiEnergyUsageGraphCard
statIds,
colorIndices,
computedStyles,
labels,
typeLabels,
statLabels,
trackY,
true
)
@@ -381,7 +416,8 @@ export class HuiEnergyUsageGraphCard
statIds,
colorIndices,
computedStyles,
labels,
typeLabels,
statLabels,
trackY,
false
)
@@ -415,11 +451,16 @@ export class HuiEnergyUsageGraphCard
},
colorIndices: Record<string, Record<string, number>>,
computedStyles: CSSStyleDeclaration,
labels: {
typeLabels: {
used_grid: string;
used_solar: string;
used_battery: string;
},
statLabels: {
to_grid: Record<string, string>;
from_grid: Record<string, string>;
to_battery: Record<string, string>;
},
trackY: (v: number) => void,
compare = false
) {
@@ -540,9 +581,10 @@ export class HuiEnergyUsageGraphCard
type: "bar",
cursor: "default",
name:
type in labels
? labels[type]
: getStatisticLabel(
type in typeLabels
? typeLabels[type]
: statLabels[type]?.[statId] ||
getStatisticLabel(
this.hass,
statId,
statisticsMetaData[statId]
@@ -61,10 +61,11 @@ export const securityEntityFilters: EntityFilter[] = [
],
entity_category: "none",
},
// We also want the tamper sensors when they are diagnostic
// We also want the tamper and moisture sensors when they are diagnostic
// (some integrations, e.g. homee, mark water leak alarms as diagnostic)
{
domain: "binary_sensor",
device_class: ["tamper"],
device_class: ["moisture", "tamper"],
entity_category: "diagnostic",
},
];
+13 -3
View File
@@ -1751,7 +1751,7 @@
"no_areas_text_non_admin": "Ask an administrator to map your vacuum's segments to areas.",
"configure_area_mapping": "Configure area mapping",
"configure": "Configure",
"clean_areas_order_hint": "Cleaning order may not be supported by your vacuum.",
"clean_areas_order_hint": "The order in which areas are cleaned may not be supported by your vacuum.",
"other_areas": "Other areas"
},
"person": {
@@ -4115,6 +4115,7 @@
"energy_from_helper": "Pick a sensor which measures grid import in either of {unit}.",
"energy_to_grid": "Energy exported to grid",
"energy_to_helper": "Pick a sensor which measures grid export in either of {unit}.",
"display_name": "[%key:ui::panel::config::energy::device_consumption::dialog::display_name%]",
"import_cost": "Cost tracking",
"import_cost_para": "Select how Home Assistant should keep track of the costs of the imported energy.",
"no_cost_tracking": "Do not track costs",
@@ -4172,6 +4173,7 @@
"dialog": {
"header": "Configure solar panels",
"entity_para": "Pick a sensor which measures solar production in either of {unit}.",
"display_name": "[%key:ui::panel::config::energy::device_consumption::dialog::display_name%]",
"solar_production_energy": "Solar production energy",
"solar_production_power": "Solar production power",
"solar_production_forecast": "Solar production forecast",
@@ -4195,6 +4197,7 @@
"energy_helper_out": "Pick a sensor that measures the electricity flowing out of the battery in either of {unit}.",
"energy_into_battery": "Energy charged into the battery",
"energy_out_of_battery": "Energy discharged from the battery",
"display_name": "[%key:ui::panel::config::energy::device_consumption::dialog::display_name%]",
"state_of_charge": "Battery state of charge sensor",
"state_of_charge_helper": "Sensor reporting battery state of charge as %.",
"power": "Battery power",
@@ -8570,7 +8573,10 @@
"total_usage": "+{num} kWh",
"combined_from_grid": "Combined from grid",
"consumed_solar": "Consumed solar",
"consumed_battery": "Consumed battery"
"consumed_battery": "Consumed battery",
"named_battery_charged": "[%key:ui::panel::lovelace::cards::energy::energy_sources_table::named_battery_charged%]",
"named_grid_consumed": "Consumed {name}",
"named_grid_exported": "[%key:ui::panel::lovelace::cards::energy::energy_sources_table::named_grid_exported%]"
},
"energy_sources_table": {
"grid_total": "Grid total",
@@ -8583,7 +8589,11 @@
"previous_energy": "Previous usage",
"previous_cost": "Previous cost",
"battery_total": "Battery total",
"total_costs": "Total costs"
"total_costs": "Total costs",
"named_battery_charged": "{name} charged",
"named_battery_discharged": "{name} discharged",
"named_grid_imported": "{name} imported",
"named_grid_exported": "{name} exported"
},
"energy_solar_graph": {
"production": "Production {name}",
+1 -1
View File
@@ -1,2 +1,2 @@
export const IFRAME_SANDBOX =
"allow-forms allow-popups allow-pointer-lock allow-scripts allow-modals allow-downloads";
"allow-forms allow-popups allow-pointer-lock allow-same-origin allow-scripts allow-modals allow-downloads";
+669 -853
View File
File diff suppressed because it is too large Load Diff