From 7cbebfd60309dc5ba2f3181250858894fdf2ace4 Mon Sep 17 00:00:00 2001
From: ildar170975 <71872483+ildar170975@users.noreply.github.com>
Date: Wed, 13 Nov 2024 19:05:03 +0300
Subject: [PATCH 01/19] Add outline to a label (2) (#22803)
* Update ha-filter-labels.ts
* Update ha-automation-picker.ts
* Update ha-scene-dashboard.ts
* Update ha-script-picker.ts
* Update ha-config-devices-dashboard.ts
* Update ha-config-entities.ts
* Update ha-config-helpers.ts
* Update ha-filter-labels.ts
* Update ha-automation-picker.ts
* Update ha-config-devices-dashboard.ts
* Update ha-config-helpers.ts
* Update ha-script-picker.ts
* Update ha-scene-dashboard.ts
* Update ha-config-entities.ts
* Update ha-data-table-labels.ts
* Update ha-label.ts
* Update ha-label.ts
---
src/components/data-table/ha-data-table-labels.ts | 1 -
src/components/ha-label.ts | 1 +
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/data-table/ha-data-table-labels.ts b/src/components/data-table/ha-data-table-labels.ts
index 31dc5be185..b35656be82 100644
--- a/src/components/data-table/ha-data-table-labels.ts
+++ b/src/components/data-table/ha-data-table-labels.ts
@@ -113,7 +113,6 @@ class HaDataTableLabels extends LitElement {
ha-label {
--ha-label-background-color: var(--color, var(--grey-color));
--ha-label-background-opacity: 0.5;
- outline: 1px solid var(--outline-color);
}
ha-button-menu {
border-radius: 10px;
diff --git a/src/components/ha-label.ts b/src/components/ha-label.ts
index 668f28017e..1b15d914ed 100644
--- a/src/components/ha-label.ts
+++ b/src/components/ha-label.ts
@@ -26,6 +26,7 @@ class HaLabel extends LitElement {
0.15
);
--ha-label-background-opacity: 1;
+ border: 1px solid var(--outline-color);
position: relative;
box-sizing: border-box;
display: inline-flex;
From 6bdc7af09f9f8d2b133d1ab420ab2a98f370cc75 Mon Sep 17 00:00:00 2001
From: ildar170975 <71872483+ildar170975@users.noreply.github.com>
Date: Thu, 14 Nov 2024 10:05:01 +0300
Subject: [PATCH 02/19] Add outline to a label (3) - consistency (#22812)
Update ha-config-labels.ts
---
src/panels/config/labels/ha-config-labels.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/panels/config/labels/ha-config-labels.ts b/src/panels/config/labels/ha-config-labels.ts
index 1d974bb7a5..2e52ddb663 100644
--- a/src/panels/config/labels/ha-config-labels.ts
+++ b/src/panels/config/labels/ha-config-labels.ts
@@ -106,7 +106,8 @@ export class HaConfigLabels extends LitElement {
style="
background-color: ${computeCssColor(label.color)};
border-radius: 10px;
- outline: 1px solid var(--outline-color);
+ border: 1px solid var(--outline-color);
+ box-sizing: border-box;
width: 20px;
height: 20px;"
>`
From b918862bb1de0a8dfc90eac22adb3640a0f2fe3a Mon Sep 17 00:00:00 2001
From: Simon Lamon <32477463+silamon@users.noreply.github.com>
Date: Thu, 14 Nov 2024 08:08:54 +0100
Subject: [PATCH 03/19] Confirm uses to if for clarity (#22816)
---
src/translations/en.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/translations/en.json b/src/translations/en.json
index 68eb675f05..19653aba39 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -3290,7 +3290,7 @@
"state": "[%key:ui::panel::config::automation::editor::triggers::type::state::label%]",
"description": {
"picker": "If an entity (or attribute) is in a specific state.",
- "no_entity": "Confirm state",
+ "no_entity": "If state confirmed",
"full": "If{hasAttribute, select, \n true { {attribute} of}\n other {}\n} {numberOfEntities, plural,\n =0 {an entity is}\n one {{entities} is}\n other {{entities} are}\n} {numberOfStates, plural,\n =0 {a state}\n other {{states}}\n}{hasDuration, select, \n true { for {duration}} \n other {}\n }"
}
},
@@ -3353,7 +3353,7 @@
"zone": "[%key:ui::panel::config::automation::editor::triggers::type::zone::label%]",
"description": {
"picker": "If someone (or something) is in a zone.",
- "full": "Confirm {entity} {numberOfEntities, plural,\n one {is}\n other {are}\n} in {zone} {numberOfZones, plural,\n one {zone} \n other {zones}\n} "
+ "full": "If {entity} {numberOfEntities, plural,\n one {is}\n other {are}\n} in {zone} {numberOfZones, plural,\n one {zone} \n other {zones}\n}"
}
}
}
From 17982e0bdc7d843973dff89f8a801f03877ca35d Mon Sep 17 00:00:00 2001
From: Simon Lamon <32477463+silamon@users.noreply.github.com>
Date: Thu, 14 Nov 2024 08:11:20 +0100
Subject: [PATCH 04/19] Fix swapped plural and singular match use in and
condition (#22815)
* Fix swapped plural and singular match
* Test if => if
---
src/translations/en.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/translations/en.json b/src/translations/en.json
index 19653aba39..7b7a7c72da 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -3242,7 +3242,7 @@
"description": {
"picker": "Test if multiple conditions are true.",
"no_conditions": "If multiple conditions match",
- "full": "If {count} {count, plural,\n one {condition match}\n other {conditions matches}\n}"
+ "full": "If {count} {count, plural,\n one {condition matches}\n other {conditions match}\n}"
}
},
"device": {
From b83be385141a6d4513afc4848902745bcc31054f Mon Sep 17 00:00:00 2001
From: Simon Lamon <32477463+silamon@users.noreply.github.com>
Date: Thu, 14 Nov 2024 08:28:15 +0100
Subject: [PATCH 05/19] Introduce calendar trigger description (#22814)
---
src/common/datetime/format_duration.ts | 60 ++++++++++++++++++++++++++
src/data/automation_i18n.ts | 37 +++++++++++++++-
src/translations/en.json | 3 +-
3 files changed, 98 insertions(+), 2 deletions(-)
diff --git a/src/common/datetime/format_duration.ts b/src/common/datetime/format_duration.ts
index c530a20d4a..9279a8b0ce 100644
--- a/src/common/datetime/format_duration.ts
+++ b/src/common/datetime/format_duration.ts
@@ -1,5 +1,6 @@
import type { HaDurationData } from "../../components/ha-duration-input";
import type { FrontendLocaleData } from "../../data/translation";
+import { formatListWithAnds } from "../string/format-list";
const leftPad = (num: number) => (num < 10 ? `0${num}` : num);
@@ -42,3 +43,62 @@ export const formatDuration = (
}
return null;
};
+
+export const formatDurationLong = (
+ locale: FrontendLocaleData,
+ duration: HaDurationData
+) => {
+ const d = duration.days || 0;
+ const h = duration.hours || 0;
+ const m = duration.minutes || 0;
+ const s = duration.seconds || 0;
+ const ms = duration.milliseconds || 0;
+
+ const parts: string[] = [];
+ if (d > 0) {
+ parts.push(
+ Intl.NumberFormat(locale.language, {
+ style: "unit",
+ unit: "day",
+ unitDisplay: "long",
+ }).format(d)
+ );
+ }
+ if (h > 0) {
+ parts.push(
+ Intl.NumberFormat(locale.language, {
+ style: "unit",
+ unit: "hour",
+ unitDisplay: "long",
+ }).format(h)
+ );
+ }
+ if (m > 0) {
+ parts.push(
+ Intl.NumberFormat(locale.language, {
+ style: "unit",
+ unit: "minute",
+ unitDisplay: "long",
+ }).format(m)
+ );
+ }
+ if (s > 0) {
+ parts.push(
+ Intl.NumberFormat(locale.language, {
+ style: "unit",
+ unit: "second",
+ unitDisplay: "long",
+ }).format(s)
+ );
+ }
+ if (ms > 0) {
+ parts.push(
+ Intl.NumberFormat(locale.language, {
+ style: "unit",
+ unit: "millisecond",
+ unitDisplay: "long",
+ }).format(ms)
+ );
+ }
+ return formatListWithAnds(locale, parts);
+};
diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts
index 5f8b9eb95c..2651f71246 100644
--- a/src/data/automation_i18n.ts
+++ b/src/data/automation_i18n.ts
@@ -1,6 +1,9 @@
import type { HassConfig } from "home-assistant-js-websocket";
import { ensureArray } from "../common/array/ensure-array";
-import { formatDuration } from "../common/datetime/format_duration";
+import {
+ formatDuration,
+ formatDurationLong,
+} from "../common/datetime/format_duration";
import {
formatTime,
formatTimeWithSeconds,
@@ -720,6 +723,38 @@ const tryDescribeTrigger = (
}`;
}
+ // Calendar Trigger
+ if (trigger.trigger === "calendar") {
+ const calendarEntity = hass.states[trigger.entity_id]
+ ? computeStateName(hass.states[trigger.entity_id])
+ : trigger.entity_id;
+
+ let offsetChoice = trigger.offset.startsWith("-") ? "before" : "after";
+ let offset: string | string[] = trigger.offset.startsWith("-")
+ ? trigger.offset.substring(1).split(":")
+ : trigger.offset.split(":");
+ const duration = {
+ hours: offset.length > 0 ? +offset[0] : 0,
+ minutes: offset.length > 1 ? +offset[1] : 0,
+ seconds: offset.length > 2 ? +offset[2] : 0,
+ };
+ offset = formatDurationLong(hass.locale, duration);
+ if (offset === "") {
+ offsetChoice = "other";
+ }
+
+ return hass.localize(
+ `${triggerTranslationBaseKey}.calendar.description.full`,
+ {
+ eventChoice: trigger.event,
+ offsetChoice: offsetChoice,
+ offset: offset,
+ hasCalendar: trigger.entity_id ? "true" : "false",
+ calendar: calendarEntity,
+ }
+ );
+ }
+
return (
hass.localize(
`ui.panel.config.automation.editor.triggers.type.${trigger.trigger}.label`
diff --git a/src/translations/en.json b/src/translations/en.json
index 7b7a7c72da..cda8a005c5 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -2995,7 +2995,8 @@
"before": "Before",
"after": "After",
"description": {
- "picker": "When a calendar event starts or ends."
+ "picker": "When a calendar event starts or ends.",
+ "full": "When{offsetChoice, select, \n before { it's {offset} before}\n after { it's {offset} after}\n other {}\n} a calendar event{eventChoice, select, \n start { starts}\n end { ends}\n other { starts or ends}\n}{hasCalendar, select, \n true { in {calendar}}\n other {}\n}"
}
},
"device": {
From 991cf83ff3c14ca119c38aed4e1fc0f2af1e794e Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 15 Nov 2024 12:00:34 +0100
Subject: [PATCH 06/19] Update dependency
@babel/helper-define-polyfill-provider to v0.6.3 (#22829)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 2 +-
yarn.lock | 10 +++++-----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package.json b/package.json
index fbc8dcc112..b19ef25920 100644
--- a/package.json
+++ b/package.json
@@ -153,7 +153,7 @@
},
"devDependencies": {
"@babel/core": "7.26.0",
- "@babel/helper-define-polyfill-provider": "0.6.2",
+ "@babel/helper-define-polyfill-provider": "0.6.3",
"@babel/plugin-proposal-decorators": "7.25.9",
"@babel/plugin-transform-runtime": "7.25.9",
"@babel/preset-env": "7.26.0",
diff --git a/yarn.lock b/yarn.lock
index f4dcb76f3a..58f89b9187 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -144,9 +144,9 @@ __metadata:
languageName: node
linkType: hard
-"@babel/helper-define-polyfill-provider@npm:0.6.2, @babel/helper-define-polyfill-provider@npm:^0.6.2":
- version: 0.6.2
- resolution: "@babel/helper-define-polyfill-provider@npm:0.6.2"
+"@babel/helper-define-polyfill-provider@npm:0.6.3, @babel/helper-define-polyfill-provider@npm:^0.6.2":
+ version: 0.6.3
+ resolution: "@babel/helper-define-polyfill-provider@npm:0.6.3"
dependencies:
"@babel/helper-compilation-targets": "npm:^7.22.6"
"@babel/helper-plugin-utils": "npm:^7.22.5"
@@ -155,7 +155,7 @@ __metadata:
resolve: "npm:^1.14.2"
peerDependencies:
"@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0
- checksum: 10/bb32ec12024d3f16e70641bc125d2534a97edbfdabbc9f69001ec9c4ce46f877c7a224c566aa6c8c510c3b0def2e43dc4433bf6a40896ba5ce0cef4ea5ccbcff
+ checksum: 10/b79a77ac8fbf1aaf6c7f99191871760508e87d75a374ff3c39c6599a17d9bb82284797cd451769305764e504546caf22ae63367b22d6e45e32d0a8f4a34aab53
languageName: node
linkType: hard
@@ -8636,7 +8636,7 @@ __metadata:
resolution: "home-assistant-frontend@workspace:."
dependencies:
"@babel/core": "npm:7.26.0"
- "@babel/helper-define-polyfill-provider": "npm:0.6.2"
+ "@babel/helper-define-polyfill-provider": "npm:0.6.3"
"@babel/plugin-proposal-decorators": "npm:7.25.9"
"@babel/plugin-transform-runtime": "npm:7.25.9"
"@babel/preset-env": "npm:7.26.0"
From d47966cdf79e1f50c4b1d31ef2df2b7c41356d89 Mon Sep 17 00:00:00 2001
From: karwosts <32912880+karwosts@users.noreply.github.com>
Date: Fri, 15 Nov 2024 08:13:04 -0800
Subject: [PATCH 07/19] Allow attaching additional data to schedule in UI
(#22798)
* Allow attaching additional data to schedule in UI
* use expandable
---
.../forms/dialog-schedule-block-info.ts | 55 ++++++++++++++-----
.../forms/show-dialog-schedule-block-info.ts | 1 +
src/translations/en.json | 4 +-
3 files changed, 44 insertions(+), 16 deletions(-)
diff --git a/src/panels/config/helpers/forms/dialog-schedule-block-info.ts b/src/panels/config/helpers/forms/dialog-schedule-block-info.ts
index 7063e57ab2..b4815eec10 100644
--- a/src/panels/config/helpers/forms/dialog-schedule-block-info.ts
+++ b/src/panels/config/helpers/forms/dialog-schedule-block-info.ts
@@ -1,5 +1,6 @@
import type { CSSResultGroup } from "lit";
import { html, LitElement, nothing } from "lit";
+import memoizeOne from "memoize-one";
import { property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { createCloseHeading } from "../../../../components/ha-dialog";
@@ -13,19 +14,6 @@ import type {
} from "./show-dialog-schedule-block-info";
import type { SchemaUnion } from "../../../../components/ha-form/types";
-const SCHEMA = [
- {
- name: "from",
- required: true,
- selector: { time: { no_second: true } },
- },
- {
- name: "to",
- required: true,
- selector: { time: { no_second: true } },
- },
-];
-
class DialogScheduleBlockInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -35,10 +23,39 @@ class DialogScheduleBlockInfo extends LitElement {
@state() private _params?: ScheduleBlockInfoDialogParams;
+ private _expand = false;
+
+ private _schema = memoizeOne((expand: boolean) => [
+ {
+ name: "from",
+ required: true,
+ selector: { time: { no_second: true } },
+ },
+ {
+ name: "to",
+ required: true,
+ selector: { time: { no_second: true } },
+ },
+ {
+ name: "advanced_settings",
+ type: "expandable" as const,
+ flatten: true,
+ expanded: expand,
+ schema: [
+ {
+ name: "data",
+ required: false,
+ selector: { object: {} },
+ },
+ ],
+ },
+ ]);
+
public showDialog(params: ScheduleBlockInfoDialogParams): void {
this._params = params;
this._error = undefined;
this._data = params.block;
+ this._expand = !!params.block?.data;
}
public closeDialog(): void {
@@ -66,7 +83,7 @@ class DialogScheduleBlockInfo extends LitElement {
) => {
+ private _computeLabelCallback = (
+ schema: SchemaUnion>
+ ) => {
switch (schema.name) {
case "from":
return this.hass!.localize("ui.dialogs.helper_settings.schedule.start");
case "to":
return this.hass!.localize("ui.dialogs.helper_settings.schedule.end");
+ case "data":
+ return this.hass!.localize("ui.dialogs.helper_settings.schedule.data");
+ case "advanced_settings":
+ return this.hass!.localize(
+ "ui.dialogs.helper_settings.schedule.advanced_settings"
+ );
}
return "";
};
diff --git a/src/panels/config/helpers/forms/show-dialog-schedule-block-info.ts b/src/panels/config/helpers/forms/show-dialog-schedule-block-info.ts
index 14a5909592..0a95789f86 100644
--- a/src/panels/config/helpers/forms/show-dialog-schedule-block-info.ts
+++ b/src/panels/config/helpers/forms/show-dialog-schedule-block-info.ts
@@ -3,6 +3,7 @@ import { fireEvent } from "../../../../common/dom/fire_event";
export interface ScheduleBlockInfo {
from: string;
to: string;
+ data?: Record;
}
export interface ScheduleBlockInfoDialogParams {
diff --git a/src/translations/en.json b/src/translations/en.json
index cda8a005c5..1b056b3d3b 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -1612,7 +1612,9 @@
"confirm_delete": "Do you want to delete this item?",
"edit_schedule_block": "Edit schedule block",
"start": "Start",
- "end": "End"
+ "end": "End",
+ "data": "Additional data",
+ "advanced_settings": "Advanced settings"
},
"template": {
"time": "[%key:ui::panel::developer-tools::tabs::templates::time%]",
From cae5540c445c327c3006c1a093fcfc80ed9573ce Mon Sep 17 00:00:00 2001
From: Petar Petrov
Date: Fri, 15 Nov 2024 18:40:38 +0200
Subject: [PATCH 08/19] ZWaveJS: Configuration.resetAll is only supported on CC
v4+ (#22823)
---
.../zwave_js/zwave_js-node-config.ts | 39 +++++++++++++------
1 file changed, 27 insertions(+), 12 deletions(-)
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts
index 6ae6654b41..30f1cde47b 100644
--- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts
+++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts
@@ -22,12 +22,14 @@ import "../../../../../components/buttons/ha-progress-button";
import type { HaProgressButton } from "../../../../../components/buttons/ha-progress-button";
import { computeDeviceName } from "../../../../../data/device_registry";
import type {
+ ZWaveJSNodeCapabilities,
ZWaveJSNodeConfigParam,
ZWaveJSNodeConfigParams,
ZWaveJSSetConfigParamResult,
ZwaveJSNodeMetadata,
} from "../../../../../data/zwave_js";
import {
+ fetchZwaveNodeCapabilities,
fetchZwaveNodeConfigParameters,
fetchZwaveNodeMetadata,
invokeZWaveCCApi,
@@ -68,6 +70,8 @@ class ZWaveJSNodeConfig extends LitElement {
@state() private _config?: ZWaveJSNodeConfigParams;
+ @state() private _canResetAll = false;
+
@state() private _results: Record = {};
@state() private _error?: string;
@@ -183,17 +187,19 @@ class ZWaveJSNodeConfig extends LitElement {
`
)}
-
-
- ${this.hass.localize(
- "ui.panel.config.zwave_js.node_config.reset_to_default.button_label"
- )}
-
-
+ ${this._canResetAll
+ ? html`
+
+ ${this.hass.localize(
+ "ui.panel.config.zwave_js.node_config.reset_to_default.button_label"
+ )}
+
+
`
+ : nothing}
${this.hass.localize(
"ui.panel.config.zwave_js.node_config.custom_config"
@@ -468,10 +474,19 @@ class ZWaveJSNodeConfig extends LitElement {
return;
}
- [this._nodeMetadata, this._config] = await Promise.all([
+ let capabilities: ZWaveJSNodeCapabilities | undefined;
+ [this._nodeMetadata, this._config, capabilities] = await Promise.all([
fetchZwaveNodeMetadata(this.hass, device.id),
fetchZwaveNodeConfigParameters(this.hass, device.id),
+ fetchZwaveNodeCapabilities(this.hass, device.id),
]);
+ this._canResetAll =
+ capabilities &&
+ Object.values(capabilities).some((endpoint) =>
+ endpoint.some(
+ (capability) => capability.id === 0x70 && capability.version >= 4
+ )
+ );
}
private async _openResetDialog(event: Event) {
From b8a13dd6ebdb50aad3fe820d9ace41152ec511ca Mon Sep 17 00:00:00 2001
From: Petar Petrov
Date: Fri, 15 Nov 2024 18:55:02 +0200
Subject: [PATCH 09/19] ZWaveJS: Add names to colors in Installer Settings
(#22819)
---
...zwave_js-capability-control-color-switch.ts | 18 ++++++++++++++++--
src/translations/en.json | 13 ++++++++++++-
2 files changed, 28 insertions(+), 3 deletions(-)
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/capability-controls/zwave_js-capability-control-color-switch.ts b/src/panels/config/integrations/integration-panels/zwave_js/capability-controls/zwave_js-capability-control-color-switch.ts
index d7facea8ac..69ee23b3f1 100644
--- a/src/panels/config/integrations/integration-panels/zwave_js/capability-controls/zwave_js-capability-control-color-switch.ts
+++ b/src/panels/config/integrations/integration-panels/zwave_js/capability-controls/zwave_js-capability-control-color-switch.ts
@@ -8,6 +8,18 @@ import "../../../../../../components/ha-circular-progress";
import { extractApiErrorMessage } from "../../../../../../data/hassio/common";
import "./zwave_js-capability-control-multilevel-switch";
+enum ColorComponent {
+ "Warm White" = 0,
+ "Cold White",
+ Red,
+ Green,
+ Blue,
+ Amber,
+ Cyan,
+ Purple,
+ Index,
+}
+
@customElement("zwave_js-capability-control-color_switch")
class ZWaveJSCapabilityColorSwitch extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -20,7 +32,7 @@ class ZWaveJSCapabilityColorSwitch extends LitElement {
@property({ type: Number }) public version!: number;
- @state() private _color_components?: number[];
+ @state() private _color_components?: ColorComponent[];
@state() private _error?: string;
@@ -37,7 +49,9 @@ class ZWaveJSCapabilityColorSwitch extends LitElement {
${this.hass.localize(
"ui.panel.config.zwave_js.node_installer.capability_controls.color_switch.color_component"
)}:
- ${color}
+ ${this.hass.localize(
+ `ui.panel.config.zwave_js.node_installer.capability_controls.color_switch.colors.${color}`
+ )}
Date: Sat, 16 Nov 2024 09:02:45 +0100
Subject: [PATCH 10/19] Update dependency @codemirror/autocomplete to v6.18.3
(#22842)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 2 +-
yarn.lock | 10 +++++-----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package.json b/package.json
index b19ef25920..723026b4a2 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
"dependencies": {
"@babel/runtime": "7.26.0",
"@braintree/sanitize-url": "7.1.0",
- "@codemirror/autocomplete": "6.18.2",
+ "@codemirror/autocomplete": "6.18.3",
"@codemirror/commands": "6.7.1",
"@codemirror/language": "6.10.3",
"@codemirror/legacy-modes": "6.4.2",
diff --git a/yarn.lock b/yarn.lock
index 58f89b9187..2c199ae782 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1265,9 +1265,9 @@ __metadata:
languageName: node
linkType: hard
-"@codemirror/autocomplete@npm:6.18.2":
- version: 6.18.2
- resolution: "@codemirror/autocomplete@npm:6.18.2"
+"@codemirror/autocomplete@npm:6.18.3":
+ version: 6.18.3
+ resolution: "@codemirror/autocomplete@npm:6.18.3"
dependencies:
"@codemirror/language": "npm:^6.0.0"
"@codemirror/state": "npm:^6.0.0"
@@ -1278,7 +1278,7 @@ __metadata:
"@codemirror/state": ^6.0.0
"@codemirror/view": ^6.0.0
"@lezer/common": ^1.0.0
- checksum: 10/35bd17afb53e8c99b1342964616f0bcc13f5f06a5d4e2d9936afdaea61742b1c20b3856d513c5d5676e3a9b6fd95e997c842467d21dfa106845e65ab1720b2f4
+ checksum: 10/a9a684cfc4a5d5d7293993a2480c9f3a5c270b2b4a2f192d9179b4458bb30f9f299912915a1d919320c1281c8e15507cfbc76e792f42f304e5f333e4019f5723
languageName: node
linkType: hard
@@ -8644,7 +8644,7 @@ __metadata:
"@babel/runtime": "npm:7.26.0"
"@braintree/sanitize-url": "npm:7.1.0"
"@bundle-stats/plugin-webpack-filter": "npm:4.16.0"
- "@codemirror/autocomplete": "npm:6.18.2"
+ "@codemirror/autocomplete": "npm:6.18.3"
"@codemirror/commands": "npm:6.7.1"
"@codemirror/language": "npm:6.10.3"
"@codemirror/legacy-modes": "npm:6.4.2"
From 0db2b45cc37438bff9862e5a0bb56e2813d7b9a7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 16 Nov 2024 10:16:36 +0100
Subject: [PATCH 11/19] Bump cross-spawn from 7.0.3 to 7.0.5 (#22843)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.5.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.5)
---
updated-dependencies:
- dependency-name: cross-spawn
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
yarn.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 2c199ae782..6320d40038 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6506,13 +6506,13 @@ __metadata:
linkType: hard
"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3":
- version: 7.0.3
- resolution: "cross-spawn@npm:7.0.3"
+ version: 7.0.5
+ resolution: "cross-spawn@npm:7.0.5"
dependencies:
path-key: "npm:^3.1.0"
shebang-command: "npm:^2.0.0"
which: "npm:^2.0.1"
- checksum: 10/e1a13869d2f57d974de0d9ef7acbf69dc6937db20b918525a01dacb5032129bd552d290d886d981e99f1b624cb03657084cc87bd40f115c07ecf376821c729ce
+ checksum: 10/c95062469d4bdbc1f099454d01c0e77177a3733012d41bf907a71eb8d22d2add43b5adf6a0a14ef4e7feaf804082714d6c262ef4557a1c480b86786c120d18e2
languageName: node
linkType: hard
From b056b7155789c71125e6b82aa83a1b8a1ed9d641 Mon Sep 17 00:00:00 2001
From: Joost Lekkerkerker
Date: Sat, 16 Nov 2024 13:20:06 +0100
Subject: [PATCH 12/19] Only download verified translations (#22844)
---
build-scripts/gulp/download-translations.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/build-scripts/gulp/download-translations.js b/build-scripts/gulp/download-translations.js
index b9f2c97583..b12eb228f0 100644
--- a/build-scripts/gulp/download-translations.js
+++ b/build-scripts/gulp/download-translations.js
@@ -127,6 +127,7 @@ gulp.task("fetch-lokalise", async function () {
replace_breaks: false,
json_unescaped_slashes: true,
export_empty_as: "skip",
+ filter_data: ["verified"],
})
.then((download) => fetch(download.bundle_url))
.then((response) => {
From c7dae49c4204f8267a6983b343242017c92e6a1a Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 16 Nov 2024 18:23:45 +0100
Subject: [PATCH 13/19] Update dependency marked to v15 (#22782)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 2 +-
yarn.lock | 10 +++++-----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package.json b/package.json
index 723026b4a2..6a0fb23b52 100644
--- a/package.json
+++ b/package.json
@@ -121,7 +121,7 @@
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
"lit": "2.8.0",
"luxon": "3.5.0",
- "marked": "14.1.4",
+ "marked": "15.0.0",
"memoize-one": "6.0.0",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2",
diff --git a/yarn.lock b/yarn.lock
index 6320d40038..929d3a379e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8798,7 +8798,7 @@ __metadata:
luxon: "npm:3.5.0"
magic-string: "npm:0.30.12"
map-stream: "npm:0.0.7"
- marked: "npm:14.1.4"
+ marked: "npm:15.0.0"
memoize-one: "npm:6.0.0"
mocha: "npm:10.8.2"
node-vibrant: "npm:3.2.1-alpha.1"
@@ -10522,12 +10522,12 @@ __metadata:
languageName: node
linkType: hard
-"marked@npm:14.1.4":
- version: 14.1.4
- resolution: "marked@npm:14.1.4"
+"marked@npm:15.0.0":
+ version: 15.0.0
+ resolution: "marked@npm:15.0.0"
bin:
marked: bin/marked.js
- checksum: 10/e3526e7907aa1c13481d205b667a178bd372c01318439e4cd8a3d4b55e3983bccef8c17489129c6a0e31dbecb0b417deff6c27f9f16083faa4eea16a22784a86
+ checksum: 10/f7c21c3a3364a52069aac072c7492ebac6eda03f495905d15f1999f246dadbd26d621ff4a4e834a44fa5d26830e576a031b2c00ec104002d38e4e59150a6d6ff
languageName: node
linkType: hard
From f51bc402032081c3c5417a36b4c0a5db3dc5ea2b Mon Sep 17 00:00:00 2001
From: karwosts <32912880+karwosts@users.noreply.github.com>
Date: Sun, 17 Nov 2024 07:09:03 -0800
Subject: [PATCH 14/19] Catch yaml errors in script editor (#22853)
---
src/panels/config/script/ha-script-editor.ts | 29 ++++++++++++++++++--
1 file changed, 27 insertions(+), 2 deletions(-)
diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts
index a3eaaea6a4..88da99c7af 100644
--- a/src/panels/config/script/ha-script-editor.ts
+++ b/src/panels/config/script/ha-script-editor.ts
@@ -79,6 +79,8 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@state() private _errors?: string;
+ @state() private _yamlErrors?: string;
+
@state() private _entityId?: string;
@state() private _mode: "gui" | "yaml" = "gui";
@@ -602,12 +604,14 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
private _yamlChanged(ev: CustomEvent) {
ev.stopPropagation();
+ this._dirty = true;
if (!ev.detail.isValid) {
+ this._yamlErrors = ev.detail.errorMsg;
return;
}
+ this._yamlErrors = undefined;
this._config = ev.detail.value;
this._errors = undefined;
- this._dirty = true;
}
private async confirmUnsavedChanged(): Promise {
@@ -723,7 +727,21 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
history.back();
}
- private _switchUiMode() {
+ private async _switchUiMode() {
+ if (this._yamlErrors) {
+ const result = await showConfirmationDialog(this, {
+ text: html`${this.hass.localize(
+ "ui.panel.config.automation.editor.switch_ui_yaml_error"
+ )}
${this._yamlErrors}`,
+ confirmText: this.hass!.localize("ui.common.continue"),
+ destructive: true,
+ dismissText: this.hass!.localize("ui.common.cancel"),
+ });
+ if (!result) {
+ return;
+ }
+ }
+ this._yamlErrors = undefined;
this._mode = "gui";
}
@@ -763,6 +781,13 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
}
private async _saveScript(): Promise {
+ if (this._yamlErrors) {
+ showToast(this, {
+ message: this._yamlErrors,
+ });
+ return;
+ }
+
if (!this.scriptId) {
const saved = await this._promptScriptAlias();
if (!saved) {
From fa821b1c4f8306e7d84133a3e98c2f0adcdd921e Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 17 Nov 2024 17:47:56 +0100
Subject: [PATCH 15/19] Update dependency @types/chromecast-caf-receiver to
v6.0.18 (#22849)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
package.json | 2 +-
yarn.lock | 10 +++++-----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package.json b/package.json
index 6a0fb23b52..bfc7041414 100644
--- a/package.json
+++ b/package.json
@@ -166,7 +166,7 @@
"@octokit/rest": "21.0.2",
"@open-wc/dev-server-hmr": "0.1.4",
"@types/babel__plugin-transform-runtime": "7.9.5",
- "@types/chromecast-caf-receiver": "6.0.17",
+ "@types/chromecast-caf-receiver": "6.0.18",
"@types/chromecast-caf-sender": "1.0.10",
"@types/color-name": "2.0.0",
"@types/glob": "8.1.0",
diff --git a/yarn.lock b/yarn.lock
index 929d3a379e..2e004d88b4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3792,10 +3792,10 @@ __metadata:
languageName: node
linkType: hard
-"@types/chromecast-caf-receiver@npm:6.0.17":
- version: 6.0.17
- resolution: "@types/chromecast-caf-receiver@npm:6.0.17"
- checksum: 10/a8cd00861da900dd56795fc4a7d483c25dfccda9732bea0aaf314e28be2a254cd8c892ebddc44c9ce62087570a9d225767fa3904767993af3e5c79adb80ea966
+"@types/chromecast-caf-receiver@npm:6.0.18":
+ version: 6.0.18
+ resolution: "@types/chromecast-caf-receiver@npm:6.0.18"
+ checksum: 10/abf21a7d1959a3b78be55ed927fe0764ce70a5c574f3fcb364008eb248f0c19f6c5df714c47640075d51dfe2467f0e281bedf562721e4838a20c50ca638fd306
languageName: node
linkType: hard
@@ -8713,7 +8713,7 @@ __metadata:
"@replit/codemirror-indentation-markers": "npm:6.5.3"
"@thomasloven/round-slider": "npm:0.6.0"
"@types/babel__plugin-transform-runtime": "npm:7.9.5"
- "@types/chromecast-caf-receiver": "npm:6.0.17"
+ "@types/chromecast-caf-receiver": "npm:6.0.18"
"@types/chromecast-caf-sender": "npm:1.0.10"
"@types/color-name": "npm:2.0.0"
"@types/glob": "npm:8.1.0"
From 1f5f6c5f8ab9990be1a7787ef06f23b5b07639f8 Mon Sep 17 00:00:00 2001
From: Matt Way
Date: Mon, 18 Nov 2024 03:55:43 +1100
Subject: [PATCH 16/19] Fix back gesture on Android activating buttons (#22852)
Touchcancel event cancels touch regardless of cancelled flag
Co-authored-by: Benjamin Paul
---
.../lovelace/common/directives/action-handler-directive.ts | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/panels/lovelace/common/directives/action-handler-directive.ts b/src/panels/lovelace/common/directives/action-handler-directive.ts
index d39db84b6b..ef931d895b 100644
--- a/src/panels/lovelace/common/directives/action-handler-directive.ts
+++ b/src/panels/lovelace/common/directives/action-handler-directive.ts
@@ -149,7 +149,10 @@ class ActionHandler extends HTMLElement implements ActionHandlerType {
element.actionHandler.end = (ev: Event) => {
// Don't respond when moved or scrolled while touch
- if (["touchend", "touchcancel"].includes(ev.type) && this.cancelled) {
+ if (
+ ev.type === "touchcancel" ||
+ (ev.type === "touchend" && this.cancelled)
+ ) {
return;
}
const target = ev.target as HTMLElement;
From 03ea08f98c7202989897a69a2b42dc197f1da140 Mon Sep 17 00:00:00 2001
From: Paul Bottein
Date: Mon, 18 Nov 2024 07:53:01 +0100
Subject: [PATCH 17/19] Create a section when dropping card on the create
section button (#22790)
* Create a section when dropping card on the create section button
* Add translations
* Add title by default
* And case when moving a heading card
---
.../lovelace/views/hui-sections-view.ts | 170 +++++++++++++++---
src/translations/en.json | 1 +
2 files changed, 144 insertions(+), 27 deletions(-)
diff --git a/src/panels/lovelace/views/hui-sections-view.ts b/src/panels/lovelace/views/hui-sections-view.ts
index 8dff28e127..944e3a1632 100644
--- a/src/panels/lovelace/views/hui-sections-view.ts
+++ b/src/panels/lovelace/views/hui-sections-view.ts
@@ -7,7 +7,7 @@ import {
mdiViewGridPlus,
} from "@mdi/js";
import type { CSSResultGroup, PropertyValues } from "lit";
-import { LitElement, css, html, 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 { repeat } from "lit/directives/repeat";
@@ -20,6 +20,7 @@ import "../../../components/ha-sortable";
import "../../../components/ha-svg-icon";
import type { LovelaceViewElement } from "../../../data/lovelace";
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
+import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../types";
@@ -27,8 +28,19 @@ import type { HuiBadge } from "../badges/hui-badge";
import "../badges/hui-view-badges";
import type { HuiCard } from "../cards/hui-card";
import "../components/hui-badge-edit-mode";
-import { addSection, deleteSection, moveSection } from "../editor/config-util";
-import { findLovelaceContainer } from "../editor/lovelace-path";
+import {
+ addSection,
+ deleteSection,
+ moveCard,
+ moveSection,
+} from "../editor/config-util";
+import type { LovelaceCardPath } from "../editor/lovelace-path";
+import {
+ findLovelaceContainer,
+ findLovelaceItems,
+ getLovelaceContainerPath,
+ parseLovelaceCardPath,
+} from "../editor/lovelace-path";
import { showEditSectionDialog } from "../editor/section-editor/show-edit-section-dialog";
import type { HuiSection } from "../sections/hui-section";
import type { Lovelace } from "../types";
@@ -231,19 +243,35 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
)}
${editMode
? html`
-
+
+
+
+ ${this.hass.localize(
+ "ui.panel.lovelace.editor.section.drop_card_create_section"
+ )}
+
+
+
+
+
`
: nothing}
${editMode && this._config?.cards?.length
@@ -280,6 +308,52 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
`;
}
+ private _defaultSection(includeHeading: boolean): LovelaceSectionConfig {
+ return {
+ type: "grid",
+ cards: includeHeading
+ ? [
+ {
+ type: "heading",
+ heading: this.hass!.localize(
+ "ui.panel.lovelace.editor.section.default_section_title"
+ ),
+ },
+ ]
+ : [],
+ };
+ }
+
+ private _handleCardAdded(ev) {
+ const { data } = ev.detail;
+ const oldPath = data as LovelaceCardPath;
+
+ const { cardIndex } = parseLovelaceCardPath(oldPath);
+ const containerPath = getLovelaceContainerPath(oldPath);
+ const cards = findLovelaceItems(
+ "cards",
+ this.lovelace!.config,
+ containerPath
+ );
+ const cardConfig = cards![cardIndex];
+
+ const configWithNewSection = addSection(
+ this.lovelace!.config,
+ this.index!,
+ this._defaultSection(cardConfig.type !== "heading") // If we move a heading card, we don't want to include a heading in the new section
+ );
+ const viewConfig = configWithNewSection.views[
+ this.index!
+ ] as LovelaceViewConfig;
+ const newPath = [
+ this.index!,
+ viewConfig.sections!.length - 1,
+ 1,
+ ] as LovelaceCardPath;
+ const newConfig = moveCard(configWithNewSection, oldPath, newPath);
+ this.lovelace!.saveConfig(newConfig);
+ }
+
private _importedCardSectionConfig = memoizeOne(
(cards: LovelaceCardConfig[]) => ({
type: "grid",
@@ -288,17 +362,11 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
);
private _createSection(): void {
- const newConfig = addSection(this.lovelace!.config, this.index!, {
- type: "grid",
- cards: [
- {
- type: "heading",
- heading: this.hass!.localize(
- "ui.panel.lovelace.editor.section.default_section_title"
- ),
- },
- ],
- });
+ const newConfig = addSection(
+ this.lovelace!.config,
+ this.index!,
+ this._defaultSection(true)
+ );
this.lovelace!.saveConfig(newConfig);
}
@@ -415,8 +483,55 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
padding: 8px;
}
- .create-section {
+ .create-section-container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
margin-top: 36px;
+ }
+
+ .create-section-container .card {
+ display: none;
+ }
+
+ .create-section-container:has(.card) .drop-helper {
+ display: flex;
+ }
+ .create-section-container:has(.card) .create-section {
+ display: none;
+ }
+
+ .drop-helper {
+ display: none;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ outline: none;
+ background: none;
+ cursor: pointer;
+ border-radius: var(--ha-card-border-radius, 12px);
+ border: 2px dashed var(--primary-color);
+ height: calc(var(--row-height) + 2 * (var(--row-gap) + 2px));
+ padding: 8px;
+ box-sizing: border-box;
+ width: 100%;
+ --ha-ripple-color: var(--primary-color);
+ --ha-ripple-hover-opacity: 0.04;
+ --ha-ripple-pressed-opacity: 0.12;
+ }
+
+ .drop-helper p {
+ color: var(--primary-text-color);
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 24px;
+ text-align: center;
+ }
+
+ .create-section {
+ display: block;
+ position: relative;
outline: none;
background: none;
cursor: pointer;
@@ -426,6 +541,7 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
height: calc(var(--row-height) + 2 * (var(--row-gap) + 2px));
padding: 8px;
box-sizing: border-box;
+ width: 100%;
--ha-ripple-color: var(--primary-color);
--ha-ripple-hover-opacity: 0.04;
--ha-ripple-pressed-opacity: 0.12;
diff --git a/src/translations/en.json b/src/translations/en.json
index df961152e8..8d775b4ce5 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -5895,6 +5895,7 @@
"section": {
"add_badge": "Add badge",
"add_card": "[%key:ui::panel::lovelace::editor::edit_card::add%]",
+ "drop_card_create_section": "Drop card here to create a new section",
"create_section": "Create section",
"default_section_title": "New section",
"imported_cards_title": "Imported cards",
From 1990b8fa842c3c9eab824fd739a81036b9a46005 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Mon, 18 Nov 2024 08:54:58 +0100
Subject: [PATCH 18/19] Bump softprops/action-gh-release from 2.0.9 to 2.1.0
(#22854)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/release.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 4f2e82c56b..1ff067e3c8 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -55,7 +55,7 @@ jobs:
script/release
- name: Upload release assets
- uses: softprops/action-gh-release@v2.0.9
+ uses: softprops/action-gh-release@v2.1.0
with:
files: |
dist/*.whl
From 23b55484c35976f02d91aed07986613007521714 Mon Sep 17 00:00:00 2001
From: Paul Bottein
Date: Mon, 18 Nov 2024 09:43:52 +0100
Subject: [PATCH 19/19] Improve grid size editor (#22697)
* Use table instead of grid
* Add animation
* Change size
* Simplify precideMode logic
* Add tooltip and improve slider style
* Improve size
* Back to default instead of min
* Limit number of cells for the grid when more than 24 cells
---
src/components/ha-grid-size-picker.ts | 122 ++++++------
.../lovelace/common/compute-card-grid-size.ts | 10 +-
.../card-editor/ha-grid-layout-slider.ts | 182 +++++++++++++++++-
.../card-editor/hui-card-layout-editor.ts | 75 +++-----
4 files changed, 273 insertions(+), 116 deletions(-)
diff --git a/src/components/ha-grid-size-picker.ts b/src/components/ha-grid-size-picker.ts
index f3b5607c21..49a293e33d 100644
--- a/src/components/ha-grid-size-picker.ts
+++ b/src/components/ha-grid-size-picker.ts
@@ -2,9 +2,7 @@ import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../panels/lovelace/editor/card-editor/ha-grid-layout-slider";
import "./ha-icon-button";
-
import { mdiRestore } from "@mdi/js";
-import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
import { conditionalClamp } from "../common/number/clamp";
@@ -20,7 +18,7 @@ export class HaGridSizeEditor extends LitElement {
@property({ attribute: false }) public rows = 8;
- @property({ attribute: false }) public columns = 4;
+ @property({ attribute: false }) public columns = 12;
@property({ attribute: false }) public rowMin?: number;
@@ -32,6 +30,8 @@ export class HaGridSizeEditor extends LitElement {
@property({ attribute: false }) public isDefault?: boolean;
+ @property({ attribute: false }) public step: number = 1;
+
@state() public _localValue?: CardGridSize = { rows: 1, columns: 1 };
protected willUpdate(changedProperties) {
@@ -51,8 +51,9 @@ export class HaGridSizeEditor extends LitElement {
const rowMin = this.rowMin ?? 1;
const rowMax = this.rowMax ?? this.rows;
- const columnMin = this.columnMin ?? 1;
- const columnMax = this.columnMax ?? this.columns;
+ const columnMin = Math.ceil((this.columnMin ?? 1) / this.step) * this.step;
+ const columnMax =
+ Math.ceil((this.columnMax ?? this.columns) / this.step) * this.step;
const rowValue = autoHeight ? rowMin : this._localValue?.rows;
const columnValue = this._localValue?.columns;
@@ -67,9 +68,11 @@ export class HaGridSizeEditor extends LitElement {
.max=${columnMax}
.range=${this.columns}
.value=${fullWidth ? this.columns : this.value?.columns}
+ .step=${this.step}
@value-changed=${this._valueChanged}
@slider-moved=${this._sliderMoved}
.disabled=${disabledColumns}
+ tooltip-mode="always"
>
${!this.isDefault
? html`
@@ -102,34 +106,44 @@ export class HaGridSizeEditor extends LitElement {
`
: nothing}
-
-
- ${Array(this.rows * this.columns)
+
+
+ ${Array(this.rows)
.fill(0)
.map((_, index) => {
- const row = Math.floor(index / this.columns) + 1;
- const column = (index % this.columns) + 1;
+ const row = index + 1;
return html`
-
+
+ ${Array(this.columns)
+ .fill(0)
+ .map((__, columnIndex) => {
+ const column = columnIndex + 1;
+ if (
+ column % this.step !== 0 ||
+ (this.columns > 24 && column % 3 !== 0)
+ ) {
+ return nothing;
+ }
+ return html`
+ |
+ `;
+ })}
+
`;
})}
-
-
+
+
`;
@@ -223,42 +237,40 @@ export class HaGridSizeEditor extends LitElement {
}
.reset {
grid-area: reset;
+ --mdc-icon-button-size: 36px;
}
.preview {
position: relative;
grid-area: preview;
}
- .preview > div {
- position: relative;
- display: grid;
- grid-template-columns: repeat(var(--total-columns), 1fr);
- grid-template-rows: repeat(var(--total-rows), 25px);
- gap: 4px;
+ .preview table,
+ .preview tr,
+ .preview td {
+ border: 2px dotted var(--divider-color);
+ border-collapse: collapse;
}
- .preview .cell {
- background-color: var(--disabled-color);
- grid-column: span 1;
- grid-row: span 1;
- border-radius: 4px;
- opacity: 0.2;
- cursor: pointer;
- }
- .preview .selected {
- position: absolute;
- pointer-events: none;
- top: 0;
- left: 0;
- height: 100%;
+ .preview table {
width: 100%;
}
- .selected .cell {
- background-color: var(--primary-color);
- grid-column: 1 / span min(var(--columns, 0), var(--total-columns));
- grid-row: 1 / span min(var(--rows, 0), var(--total-rows));
- opacity: 0.5;
+ .preview tr {
+ height: 30px;
}
- .preview.full-width .selected .cell {
- grid-column: 1 / -1;
+ .preview td {
+ cursor: pointer;
+ }
+ .preview-card {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: var(--primary-color);
+ opacity: 0.3;
+ border-radius: 8px;
+ height: calc(var(--rows, 1) * 30px);
+ width: calc(var(--columns, 1) * 100% / var(--total-columns, 12));
+ pointer-events: none;
+ transition:
+ width ease-in-out 180ms,
+ height ease-in-out 180ms;
}
`,
];
diff --git a/src/panels/lovelace/common/compute-card-grid-size.ts b/src/panels/lovelace/common/compute-card-grid-size.ts
index cc0be5ac76..49bc4946ca 100644
--- a/src/panels/lovelace/common/compute-card-grid-size.ts
+++ b/src/panels/lovelace/common/compute-card-grid-size.ts
@@ -3,16 +3,11 @@ import type { LovelaceGridOptions, LovelaceLayoutOptions } from "../types";
export const GRID_COLUMN_MULTIPLIER = 3;
-export const multiplyBy =
(
+const multiplyBy = (
value: T,
multiplier: number
): T => (typeof value === "number" ? ((value * multiplier) as T) : value);
-export const divideBy = (
- value: T,
- divider: number
-): T => (typeof value === "number" ? (Math.ceil(value / divider) as T) : value);
-
export const migrateLayoutToGridOptions = (
options: LovelaceLayoutOptions
): LovelaceGridOptions => {
@@ -42,6 +37,9 @@ export type CardGridSize = {
columns: number | "full";
};
+export const isPreciseMode = (options: LovelaceGridOptions) =>
+ typeof options.columns === "number" && options.columns % 3 !== 0;
+
export const computeCardGridSize = (
options: LovelaceGridOptions
): CardGridSize => {
diff --git a/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts b/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts
index 206436ce95..e133be40c1 100644
--- a/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts
+++ b/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts
@@ -23,6 +23,8 @@ const A11Y_KEY_CODES = new Set([
"End",
]);
+type TooltipMode = "never" | "always" | "interaction";
+
@customElement("ha-grid-layout-slider")
export class HaGridLayoutSlider extends LitElement {
@property({ type: Boolean, reflect: true })
@@ -34,6 +36,9 @@ export class HaGridLayoutSlider extends LitElement {
@property({ attribute: "touch-action" })
public touchAction?: string;
+ @property({ attribute: "tooltip-mode" })
+ public tooltipMode: TooltipMode = "interaction";
+
@property({ type: Number })
public value?: number;
@@ -52,6 +57,9 @@ export class HaGridLayoutSlider extends LitElement {
@state()
public pressed = false;
+ @state()
+ public tooltipVisible = false;
+
private _mc?: HammerManager;
private get _range() {
@@ -135,11 +143,13 @@ export class HaGridLayoutSlider extends LitElement {
this._mc.on("panstart", () => {
if (this.disabled) return;
this.pressed = true;
+ this._showTooltip();
savedValue = this.value;
});
this._mc.on("pancancel", () => {
if (this.disabled) return;
this.pressed = false;
+ this._hideTooltip();
this.value = savedValue;
});
this._mc.on("panmove", (e) => {
@@ -152,6 +162,7 @@ export class HaGridLayoutSlider extends LitElement {
this._mc.on("panend", (e) => {
if (this.disabled) return;
this.pressed = false;
+ this._hideTooltip();
const percentage = this._getPercentageFromEvent(e);
const value = this._percentageToValue(percentage);
this.value = this._steppedValue(this._boundedValue(value));
@@ -223,6 +234,23 @@ export class HaGridLayoutSlider extends LitElement {
fireEvent(this, "value-changed", { value: this.value });
}
+ private _tooltipTimeout?: number;
+
+ _showTooltip() {
+ if (this._tooltipTimeout != null) window.clearTimeout(this._tooltipTimeout);
+ this.tooltipVisible = true;
+ }
+
+ _hideTooltip(delay?: number) {
+ if (!delay) {
+ this.tooltipVisible = false;
+ return;
+ }
+ this._tooltipTimeout = window.setTimeout(() => {
+ this.tooltipVisible = false;
+ }, delay);
+ }
+
private _getPercentageFromEvent = (e: HammerInput) => {
if (this.vertical) {
const y = e.center.y;
@@ -236,6 +264,30 @@ export class HaGridLayoutSlider extends LitElement {
return Math.max(Math.min(1, (x - offset) / total), 0);
};
+ private _renderTooltip() {
+ if (this.tooltipMode === "never") return nothing;
+
+ const position = this.vertical ? "left" : "top";
+
+ const visible =
+ this.tooltipMode === "always" ||
+ (this.tooltipVisible && this.tooltipMode === "interaction");
+
+ const value = this._boundedValue(this._steppedValue(this.value ?? 0));
+
+ return html`
+
+ ${value}
+
+ `;
+ }
+
protected render(): TemplateResult {
return html`
+
+ ${Array(this._range / this.step)
+ .fill(0)
+ .map((_, i) => {
+ const percentage = i / (this._range / this.step);
+ const disabled =
+ this.min >= i * this.step || i * this.step > this.max;
+ if (disabled) {
+ return nothing;
+ }
+ return html`
+
+ `;
+ })}
${this.value !== undefined
? html``
: nothing}
+ ${this._renderTooltip()}
`;
@@ -269,7 +341,7 @@ export class HaGridLayoutSlider extends LitElement {
return css`
:host {
display: block;
- --grid-layout-slider: 48px;
+ --grid-layout-slider: 36px;
height: var(--grid-layout-slider);
width: 100%;
outline: none;
@@ -297,6 +369,7 @@ export class HaGridLayoutSlider extends LitElement {
}
.slider * {
pointer-events: none;
+ user-select: none;
}
.track {
position: absolute;
@@ -315,12 +388,11 @@ export class HaGridLayoutSlider extends LitElement {
position: absolute;
inset: 0;
background: var(--disabled-color);
- opacity: 0.2;
+ opacity: 0.4;
}
.active {
position: absolute;
- background: grey;
- opacity: 0.7;
+ background: var(--primary-color);
top: 0;
right: calc(var(--max) * 100%);
bottom: 0;
@@ -351,6 +423,27 @@ export class HaGridLayoutSlider extends LitElement {
height: 16px;
width: 100%;
}
+ .dot {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ opacity: 0.6;
+ margin: auto;
+ width: 4px;
+ height: 4px;
+ flex-shrink: 0;
+ transform: translate(-50%, 0);
+ background: var(--card-background-color);
+ left: calc(var(--value, 0%) * 100%);
+ border-radius: 2px;
+ }
+ :host([vertical]) .dot {
+ transform: translate(0, -50%);
+ left: 0;
+ right: 0;
+ bottom: inherit;
+ top: calc(var(--value, 0%) * 100%);
+ }
.handle::after {
position: absolute;
inset: 0;
@@ -358,7 +451,7 @@ export class HaGridLayoutSlider extends LitElement {
border-radius: 2px;
height: 100%;
margin: auto;
- background: grey;
+ background: var(--primary-color);
content: "";
}
:host([vertical]) .handle::after {
@@ -374,9 +467,88 @@ export class HaGridLayoutSlider extends LitElement {
:host(:disabled) .active {
background: var(--disabled-color);
}
+
+ .tooltip {
+ position: absolute;
+ background-color: var(--clear-background-color);
+ color: var(--primary-text-color);
+ font-size: var(--control-slider-tooltip-font-size);
+ border-radius: 0.8em;
+ padding: 0.2em 0.4em;
+ opacity: 0;
+ white-space: nowrap;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+ transition:
+ opacity 180ms ease-in-out,
+ left 180ms ease-in-out,
+ bottom 180ms ease-in-out;
+ --handle-spacing: calc(2 * var(--handle-margin) + var(--handle-size));
+ --slider-tooltip-margin: 0px;
+ --slider-tooltip-range: 100%;
+ --slider-tooltip-offset: 0px;
+ --slider-tooltip-position: calc(
+ min(
+ max(
+ var(--value) * var(--slider-tooltip-range) +
+ var(--slider-tooltip-offset),
+ 0%
+ ),
+ 100%
+ )
+ );
+ }
+ .tooltip.start {
+ --slider-tooltip-offset: calc(-0.5 * (var(--handle-spacing)));
+ }
+ .tooltip.end {
+ --slider-tooltip-offset: calc(0.5 * (var(--handle-spacing)));
+ }
+ .tooltip.cursor {
+ --slider-tooltip-range: calc(100% - var(--handle-spacing));
+ --slider-tooltip-offset: calc(0.5 * (var(--handle-spacing)));
+ }
+ .tooltip.show-handle {
+ --slider-tooltip-range: calc(100% - var(--handle-spacing));
+ --slider-tooltip-offset: calc(0.5 * (var(--handle-spacing)));
+ }
+ .tooltip.visible {
+ opacity: 1;
+ }
+ .tooltip.top {
+ transform: translate3d(-50%, -100%, 0);
+ top: var(--slider-tooltip-margin);
+ left: 50%;
+ }
+ .tooltip.bottom {
+ transform: translate3d(-50%, 100%, 0);
+ bottom: var(--slider-tooltip-margin);
+ left: 50%;
+ }
+ .tooltip.left {
+ transform: translate3d(-100%, -50%, 0);
+ top: 50%;
+ left: var(--slider-tooltip-margin);
+ }
+ .tooltip.right {
+ transform: translate3d(100%, -50%, 0);
+ top: 50%;
+ right: var(--slider-tooltip-margin);
+ }
+ :host(:not([vertical])) .tooltip.top,
+ :host(:not([vertical])) .tooltip.bottom {
+ left: var(--slider-tooltip-position);
+ }
+ :host([vertical]) .tooltip.right,
+ :host([vertical]) .tooltip.left {
+ top: var(--slider-tooltip-position);
+ }
+
.pressed .handle {
transition: none;
}
+ .pressed .tooltip {
+ transition: opacity 180ms ease-in-out;
+ }
`;
}
}
diff --git a/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts b/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts
index 494dcd9edb..a79e6eeccd 100644
--- a/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts
+++ b/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts
@@ -26,16 +26,12 @@ import type { HuiCard } from "../../cards/hui-card";
import type { CardGridSize } from "../../common/compute-card-grid-size";
import {
computeCardGridSize,
- divideBy,
GRID_COLUMN_MULTIPLIER,
+ isPreciseMode,
migrateLayoutToGridOptions,
- multiplyBy,
} from "../../common/compute-card-grid-size";
import type { LovelaceGridOptions } from "../../types";
-const computePreciseMode = (columns?: number | string) =>
- typeof columns === "number" && columns % 3 !== 0;
-
@customElement("hui-card-layout-editor")
export class HuiCardLayoutEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -63,22 +59,6 @@ export class HuiCardLayoutEditor extends LitElement {
private _computeCardGridSize = memoizeOne(computeCardGridSize);
- private _simplifyOptions = (
- options: LovelaceGridOptions
- ): LovelaceGridOptions => ({
- ...options,
- columns: divideBy(options.columns, GRID_COLUMN_MULTIPLIER),
- max_columns: divideBy(options.max_columns, GRID_COLUMN_MULTIPLIER),
- min_columns: divideBy(options.min_columns, GRID_COLUMN_MULTIPLIER),
- });
-
- private _standardizeOptions = (options: LovelaceGridOptions) => ({
- ...options,
- columns: multiplyBy(options.columns, GRID_COLUMN_MULTIPLIER),
- max_columns: multiplyBy(options.max_columns, GRID_COLUMN_MULTIPLIER),
- min_columns: multiplyBy(options.min_columns, GRID_COLUMN_MULTIPLIER),
- });
-
private _isDefault = memoizeOne(
(options?: LovelaceGridOptions) =>
options?.columns === undefined && options?.rows === undefined
@@ -101,14 +81,11 @@ export class HuiCardLayoutEditor extends LitElement {
this._defaultGridOptions
);
- const gridOptions = this._preciseMode
- ? options
- : this._simplifyOptions(options);
+ const gridOptions = options;
const gridValue = this._computeCardGridSize(gridOptions);
const columnSpan = this.sectionConfig.column_span ?? 1;
- const gridTotalColumns =
- (12 * columnSpan) / (this._preciseMode ? 1 : GRID_COLUMN_MULTIPLIER);
+ const gridTotalColumns = 12 * columnSpan;
return html`