diff --git a/pyproject.toml b/pyproject.toml
index a7effa1919..fc74110b4b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
-version = "20250210.0"
+version = "20250214.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts
index 81029851a0..78902bc018 100644
--- a/src/components/chart/ha-chart-base.ts
+++ b/src/components/chart/ha-chart-base.ts
@@ -6,6 +6,7 @@ import type { DataZoomComponentOption } from "echarts/components";
import type { EChartsType } from "echarts/core";
import type {
ECElementEvent,
+ SetOptionOpts,
XAXisOption,
YAXisOption,
} from "echarts/types/dist/shared";
@@ -83,19 +84,19 @@ export class HaChartBase extends LitElement {
this._listeners.push(
listenMediaQuery("(prefers-reduced-motion)", (matches) => {
- this._reducedMotion = matches;
- this.chart?.setOption({ animation: !this._reducedMotion });
+ if (this._reducedMotion !== matches) {
+ this._reducedMotion = matches;
+ this.chart?.setOption({ animation: !this._reducedMotion });
+ }
})
);
// Add keyboard event listeners
const handleKeyDown = (ev: KeyboardEvent) => {
- if ((isMac && ev.metaKey) || (!isMac && ev.ctrlKey)) {
+ if ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) {
this._modifierPressed = true;
if (!this.options?.dataZoom) {
- this.chart?.setOption({
- dataZoom: this._getDataZoomConfig(),
- });
+ this.chart?.setOption({ dataZoom: this._getDataZoomConfig() });
}
}
};
@@ -104,9 +105,7 @@ export class HaChartBase extends LitElement {
if ((isMac && ev.key === "Meta") || (!isMac && ev.key === "Control")) {
this._modifierPressed = false;
if (!this.options?.dataZoom) {
- this.chart?.setOption({
- dataZoom: this._getDataZoomConfig(),
- });
+ this.chart?.setOption({ dataZoom: this._getDataZoomConfig() });
}
}
};
@@ -124,27 +123,26 @@ export class HaChartBase extends LitElement {
}
public willUpdate(changedProps: PropertyValues): void {
- super.willUpdate(changedProps);
-
- if (!this.hasUpdated || !this.chart) {
+ if (!this.chart) {
return;
}
if (changedProps.has("_themes")) {
this._setupChart();
return;
}
+ let chartOptions: ECOption = {};
+ const chartUpdateParams: SetOptionOpts = { lazyUpdate: true };
if (changedProps.has("data")) {
- this.chart.setOption(
- { series: this.data },
- { lazyUpdate: true, replaceMerge: ["series"] }
- );
+ chartOptions.series = this.data;
+ chartUpdateParams.replaceMerge = ["series"];
}
- if (changedProps.has("options") || changedProps.has("_isZoomed")) {
- this.chart.setOption(this._createOptions(), {
- lazyUpdate: true,
- // if we replace the whole object, it will reset the dataZoom
- replaceMerge: ["grid"],
- });
+ if (changedProps.has("options")) {
+ chartOptions = { ...chartOptions, ...this._createOptions() };
+ } else if (this._isTouchDevice && changedProps.has("_isZoomed")) {
+ chartOptions.dataZoom = this._getDataZoomConfig();
+ }
+ if (Object.keys(chartOptions).length > 0) {
+ this.chart.setOption(chartOptions, chartUpdateParams);
}
}
@@ -158,7 +156,6 @@ export class HaChartBase extends LitElement {
style=${styleMap({
height: this.height ?? `${this._getDefaultHeight()}px`,
})}
- @wheel=${this._handleWheel}
>
${this._isZoomed
@@ -240,8 +237,8 @@ export class HaChartBase extends LitElement {
type: "inside",
orient: "horizontal",
filterMode: "none",
- moveOnMouseMove: this._isZoomed,
- preventDefaultMouseMove: this._isZoomed,
+ moveOnMouseMove: !this._isTouchDevice || this._isZoomed,
+ preventDefaultMouseMove: !this._isTouchDevice || this._isZoomed,
zoomLock: !this._isTouchDevice && !this._modifierPressed,
};
}
@@ -514,23 +511,6 @@ export class HaChartBase extends LitElement {
private _handleZoomReset() {
this.chart?.dispatchAction({ type: "dataZoom", start: 0, end: 100 });
- this._modifierPressed = false;
- }
-
- private _handleWheel(e: WheelEvent) {
- // if the window is not focused, we don't receive the keydown events but scroll still works
- if (!this.options?.dataZoom) {
- const modifierPressed = (isMac && e.metaKey) || (!isMac && e.ctrlKey);
- if (modifierPressed) {
- e.preventDefault();
- }
- if (modifierPressed !== this._modifierPressed) {
- this._modifierPressed = modifierPressed;
- this.chart?.setOption({
- dataZoom: this._getDataZoomConfig(),
- });
- }
- }
}
static styles = css`
diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts
index 76e73a89e7..f7e60f7804 100644
--- a/src/components/chart/state-history-chart-line.ts
+++ b/src/components/chart/state-history-chart-line.ts
@@ -75,6 +75,8 @@ export class StateHistoryChartLine extends LitElement {
@state() private _yWidth = 25;
+ @state() private _visualMap?: VisualMapComponentOption[];
+
private _chartTime: Date = new Date();
protected render() {
@@ -92,7 +94,7 @@ export class StateHistoryChartLine extends LitElement {
`;
}
- private _renderTooltip(params: any) {
+ private _renderTooltip = (params: any) => {
const time = params[0].axisValue;
const title =
formatDateTimeWithSeconds(
@@ -115,7 +117,7 @@ export class StateHistoryChartLine extends LitElement {
return;
}
// If the datapoint is not found, we need to find the last datapoint before the current time
- let lastData;
+ let lastData: any;
const data = dataset.data || [];
for (let i = data.length - 1; i >= 0; i--) {
const point = data[i];
@@ -175,7 +177,7 @@ export class StateHistoryChartLine extends LitElement {
})
.join("
")
);
- }
+ };
private _datasetHidden(ev: CustomEvent) {
this._hiddenStats.add(ev.detail.name);
@@ -208,8 +210,8 @@ export class StateHistoryChartLine extends LitElement {
changedProps.has("minYAxis") ||
changedProps.has("maxYAxis") ||
changedProps.has("fitYData") ||
- changedProps.has("_chartData") ||
changedProps.has("paddingYAxis") ||
+ changedProps.has("_visualMap") ||
changedProps.has("_yWidth")
) {
const rtl = computeRTL(this.hass);
@@ -280,37 +282,11 @@ export class StateHistoryChartLine extends LitElement {
right: rtl ? Math.max(this.paddingYAxis, this._yWidth) : 1,
bottom: 30,
},
- visualMap: this._chartData
- .map((_, seriesIndex) => {
- const dataIndex = this._datasetToDataIndex[seriesIndex];
- const data = this.data[dataIndex];
- if (!data.statistics || data.statistics.length === 0) {
- return false;
- }
- // render stat data with a slightly transparent line
- const firstStateTS =
- data.states[0]?.last_changed ?? this.endTime.getTime();
- return {
- show: false,
- seriesIndex,
- dimension: 0,
- pieces: [
- {
- max: firstStateTS - 0.01,
- colorAlpha: 0.5,
- },
- {
- min: firstStateTS,
- colorAlpha: 1,
- },
- ],
- };
- })
- .filter(Boolean) as VisualMapComponentOption[],
+ visualMap: this._visualMap,
tooltip: {
trigger: "axis",
appendTo: document.body,
- formatter: this._renderTooltip.bind(this),
+ formatter: this._renderTooltip,
},
};
}
@@ -725,6 +701,33 @@ export class StateHistoryChartLine extends LitElement {
this._chartData = datasets;
this._entityIds = entityIds;
this._datasetToDataIndex = datasetToDataIndex;
+ const visualMap: VisualMapComponentOption[] = [];
+ this._chartData.forEach((_, seriesIndex) => {
+ const dataIndex = this._datasetToDataIndex[seriesIndex];
+ const data = this.data[dataIndex];
+ if (!data.statistics || data.statistics.length === 0) {
+ return;
+ }
+ // render stat data with a slightly transparent line
+ const firstStateTS =
+ data.states[0]?.last_changed ?? this.endTime.getTime();
+ visualMap.push({
+ show: false,
+ seriesIndex,
+ dimension: 0,
+ pieces: [
+ {
+ max: firstStateTS - 0.01,
+ colorAlpha: 0.5,
+ },
+ {
+ min: firstStateTS,
+ colorAlpha: 1,
+ },
+ ],
+ });
+ });
+ this._visualMap = visualMap.length > 0 ? visualMap : undefined;
}
private _clampYAxis(value?: number | ((values: any) => number)) {
diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts
index fd34fa5733..7954d76010 100644
--- a/src/components/chart/statistics-chart.ts
+++ b/src/components/chart/statistics-chart.ts
@@ -273,11 +273,13 @@ export class StatisticsChart extends LitElement {
this._chartOptions = {
xAxis: [
{
+ id: "xAxis",
type: "time",
min: startTime,
- max: endTime,
+ max: this.endTime,
},
{
+ id: "hiddenAxis",
type: "time",
show: false,
},
@@ -368,7 +370,6 @@ export class StatisticsChart extends LitElement {
if (endTime > new Date()) {
endTime = new Date();
}
- this.endTime = endTime;
let unit: string | undefined | null;
diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts
index 335d84a7ac..726434b6de 100644
--- a/src/panels/config/dashboard/ha-config-dashboard.ts
+++ b/src/panels/config/dashboard/ha-config-dashboard.ts
@@ -66,6 +66,18 @@ const randomTip = (hass: HomeAssistant, narrow: boolean) => {
rel="noreferrer"
>${hass.localize("ui.panel.config.tips.join_x")}`,
+ mastodon: html`${hass.localize("ui.panel.config.tips.join_mastodon")}`,
+ bluesky: html`${hass.localize("ui.panel.config.tips.join_bluesky")}`,
discord: html` {
- showZWaveJSAddNodeDialog(this, {
- entry_id: this.configEntryId,
- dsk: message.dsk,
- onStop: () => setTimeout(() => this._fetchData(), 100),
- });
+ if (!this._dialogOpen) {
+ showZWaveJSAddNodeDialog(this, {
+ entry_id: this.configEntryId,
+ dsk: message.dsk,
+ onStop: () => {
+ setTimeout(() => this._fetchData(), 100);
+ this._dialogOpen = false;
+ },
+ });
+ this._dialogOpen = true;
+ }
}),
];
}
@@ -570,11 +578,17 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
}
private async _addNodeClicked() {
- showZWaveJSAddNodeDialog(this, {
- entry_id: this.configEntryId!,
- // refresh the data after the dialog is closed. add a small delay for the inclusion state to update
- onStop: () => setTimeout(() => this._fetchData(), 100),
- });
+ if (!this._dialogOpen) {
+ showZWaveJSAddNodeDialog(this, {
+ entry_id: this.configEntryId!,
+ // refresh the data after the dialog is closed. add a small delay for the inclusion state to update
+ onStop: () => {
+ setTimeout(() => this._fetchData(), 100);
+ this._dialogOpen = false;
+ },
+ });
+ this._dialogOpen = true;
+ }
}
private async _removeNodeClicked() {
diff --git a/src/panels/lovelace/cards/hui-statistics-graph-card.ts b/src/panels/lovelace/cards/hui-statistics-graph-card.ts
index 3754725c9d..a8402a8010 100644
--- a/src/panels/lovelace/cards/hui-statistics-graph-card.ts
+++ b/src/panels/lovelace/cards/hui-statistics-graph-card.ts
@@ -327,7 +327,7 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
);
const endDate = this._energyEnd;
try {
- let unitClass;
+ let unitClass: string | undefined | null;
if (this._config!.unit && this._metadata) {
const metadata = Object.values(this._metadata).find(
(metaData) =>
diff --git a/src/panels/lovelace/sections/hui-grid-section.ts b/src/panels/lovelace/sections/hui-grid-section.ts
index f50a5dc7b4..059fe80852 100644
--- a/src/panels/lovelace/sections/hui-grid-section.ts
+++ b/src/panels/lovelace/sections/hui-grid-section.ts
@@ -240,6 +240,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
.container.edit-mode {
padding: 8px;
border-radius: var(--ha-card-border-radius, 12px);
+ border-start-end-radius: 0;
border: 2px dashed var(--divider-color);
min-height: var(--row-height);
}
diff --git a/src/translations/en.json b/src/translations/en.json
index 80d7b4f6de..e68ca304f9 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -6052,8 +6052,10 @@
},
"tips": {
"tip": "Tip!",
- "join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}",
+ "join": "Join the community on our {forums}, {mastodon}, {bluesky}, {twitter}, {discord}, {blog} or {newsletter}",
"join_x": "X (formerly Twitter)",
+ "join_mastodon": "Mastodon",
+ "join_bluesky": "Bluesky",
"join_forums": "Forums",
"join_chat": "Chat",
"join_blog": "Blog",