From a8836404d4093ea364180304f5124a4cafc364a7 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 27 Aug 2025 16:33:48 +0200 Subject: [PATCH 01/84] Use entity picture for home favorite and update home dashboard icon (#26732) * Add strategy icon * Use entity picture for favorite --- .../dark/icon-dashboard-home.svg | 76 +++++++++++++++++++ .../light/icon-dashboard-home.svg | 76 +++++++++++++++++++ .../config/dashboard/dialog-new-dashboard.ts | 4 +- .../home/home-main-view-strategy.ts | 1 + 4 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 public/static/images/dashboard-options/dark/icon-dashboard-home.svg create mode 100644 public/static/images/dashboard-options/light/icon-dashboard-home.svg diff --git a/public/static/images/dashboard-options/dark/icon-dashboard-home.svg b/public/static/images/dashboard-options/dark/icon-dashboard-home.svg new file mode 100644 index 0000000000..f1056e3d28 --- /dev/null +++ b/public/static/images/dashboard-options/dark/icon-dashboard-home.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/images/dashboard-options/light/icon-dashboard-home.svg b/public/static/images/dashboard-options/light/icon-dashboard-home.svg new file mode 100644 index 0000000000..1ee9ea7018 --- /dev/null +++ b/public/static/images/dashboard-options/light/icon-dashboard-home.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/panels/config/dashboard/dialog-new-dashboard.ts b/src/panels/config/dashboard/dialog-new-dashboard.ts index bdd2e067a1..3a28c9b27b 100644 --- a/src/panels/config/dashboard/dialog-new-dashboard.ts +++ b/src/panels/config/dashboard/dialog-new-dashboard.ts @@ -49,8 +49,8 @@ const STRATEGIES = [ { type: "home", images: { - light: "/static/images/dashboard-options/light/icon-dashboard-areas.svg", - dark: "/static/images/dashboard-options/dark/icon-dashboard-areas.svg", + light: "/static/images/dashboard-options/light/icon-dashboard-home.svg", + dark: "/static/images/dashboard-options/dark/icon-dashboard-home.svg", }, name: "ui.panel.config.lovelace.dashboards.dialog_new.strategy.home.title", description: diff --git a/src/panels/lovelace/strategies/home/home-main-view-strategy.ts b/src/panels/lovelace/strategies/home/home-main-view-strategy.ts index 93a4520703..43be46e482 100644 --- a/src/panels/lovelace/strategies/home/home-main-view-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-main-view-strategy.ts @@ -96,6 +96,7 @@ export class HomeMainViewStrategy extends ReactiveElement { ({ type: "tile", entity: entityId, + show_entity_picture: true, }) as TileCardConfig ) ); From a9f2254bbc4a0823b7271aad99ec9722f01ab837 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 27 Aug 2025 16:59:23 +0200 Subject: [PATCH 02/84] Improve network adapter configuration discoverability (#26734) --- src/components/ha-network.ts | 28 ++++++++++++++++++- .../config/network/ha-config-network.ts | 5 +--- src/translations/en.json | 1 + 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/components/ha-network.ts b/src/components/ha-network.ts index 5ad2a025f8..e5866acc04 100644 --- a/src/components/ha-network.ts +++ b/src/components/ha-network.ts @@ -1,4 +1,4 @@ -import { mdiStar } from "@mdi/js"; +import { mdiInformationOutline, mdiStar } from "@mdi/js"; import type { CSSResultGroup, TemplateResult } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -71,6 +71,17 @@ export class HaNetwork extends LitElement { ${this.hass.localize("ui.panel.config.network.adapter.detected")}: ${format_auto_detected_interfaces(this.networkConfig.adapters)} + ${!configured_adapters.length + ? html`
+ + ${this.hass.localize( + "ui.panel.config.network.adapter.auto_configure_manual_hint" + )} +
` + : nothing}
${configured_adapters.length || this._expanded @@ -171,6 +182,21 @@ export class HaNetwork extends LitElement { span[slot="description"] { cursor: pointer; } + + .info-text { + display: flex; + align-items: center; + margin-top: 8px; + color: var(--secondary-text-color); + } + + .info-icon { + width: 18px; + height: 18px; + color: var(--info-color, var(--primary-color)); + margin-right: 8px; + flex-shrink: 0; + } `, ]; } diff --git a/src/panels/config/network/ha-config-network.ts b/src/panels/config/network/ha-config-network.ts index a7599c40d7..aba8c71ab9 100644 --- a/src/panels/config/network/ha-config-network.ts +++ b/src/panels/config/network/ha-config-network.ts @@ -23,10 +23,7 @@ class ConfigNetwork extends LitElement { @state() private _error?: { code: string; message: string }; protected render() { - if ( - !this.hass.userData?.showAdvanced || - !isComponentLoaded(this.hass, "network") - ) { + if (!isComponentLoaded(this.hass, "network")) { return nothing; } diff --git a/src/translations/en.json b/src/translations/en.json index 5bb9e29f0a..f18091d5d3 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6664,6 +6664,7 @@ "ip_information": "IP information", "adapter": { "auto_configure": "Autoconfigure", + "auto_configure_manual_hint": "Uncheck to manually select network adapters", "detected": "Detected", "adapter": "Adapter" } From 50ad5e376f15d5f6a644adbe57b58169c41235ec Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 28 Aug 2025 12:53:15 +0100 Subject: [PATCH 03/84] Move automation and script `ha-alert`s to main flow (#26735) Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> --- src/components/ha-alert.ts | 3 +- .../config/automation/ha-automation-editor.ts | 158 +++++++++++------- .../automation/manual-automation-editor.ts | 33 +--- src/panels/config/script/ha-script-editor.ts | 137 +++++++++------ .../config/script/manual-script-editor.ts | 9 +- 5 files changed, 191 insertions(+), 149 deletions(-) diff --git a/src/components/ha-alert.ts b/src/components/ha-alert.ts index 8661364e3a..6db2544eb9 100644 --- a/src/components/ha-alert.ts +++ b/src/components/ha-alert.ts @@ -119,10 +119,11 @@ class HaAlert extends LitElement { .main-content { overflow-wrap: anywhere; word-break: break-word; + line-height: normal; margin-left: 8px; margin-right: 0; margin-inline-start: 8px; - margin-inline-end: 0; + margin-inline-end: 8px; } .title { margin-top: 2px; diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 3f27a0bac8..8093caa9e9 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -432,62 +432,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin( class=${this._mode === "yaml" ? "yaml-mode" : ""} @subscribe-automation-config=${this._subscribeAutomationConfig} > -
- ${this._errors || stateObj?.state === UNAVAILABLE - ? html` - ${this._errors || this._validationErrors} - ${stateObj?.state === UNAVAILABLE - ? html`` - : nothing} - ` - : ""} - ${this._blueprintConfig - ? html` - ${this.hass.localize( - "ui.panel.config.automation.editor.confirm_take_control" - )} -
- ${this.hass.localize("ui.common.yes")} - ${this.hass.localize("ui.common.no")} -
-
` - : this._readOnly - ? html`${this.hass.localize( - "ui.panel.config.automation.editor.read_only" - )} - - ${this.hass.localize( - "ui.panel.config.automation.editor.migrate" - )} - - ` - : nothing} -
${this._mode === "gui" ? html`
@@ -499,7 +443,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin( .isWide=${this.isWide} .stateObj=${stateObj} .config=${this._config} - .disabled=${Boolean(this._readOnly)} + .disabled=${this._readOnly} .saving=${this._saving} .dirty=${this._dirty} @value-changed=${this._valueChanged} @@ -513,13 +457,94 @@ export class HaAutomationEditor extends PreventUnsavedMixin( .isWide=${this.isWide} .stateObj=${stateObj} .config=${this._config} - .disabled=${Boolean(this._readOnly)} + .disabled=${this._readOnly} .dirty=${this._dirty} .saving=${this._saving} @value-changed=${this._valueChanged} @save-automation=${this._handleSaveAutomation} @editor-save=${this._handleSaveAutomation} - > + > +
+ ${this._errors || stateObj?.state === UNAVAILABLE + ? html` + ${this._errors || this._validationErrors} + ${stateObj?.state === UNAVAILABLE + ? html`` + : nothing} + ` + : nothing} + ${this._blueprintConfig + ? html` + ${this.hass.localize( + "ui.panel.config.automation.editor.confirm_take_control" + )} +
+ ${this.hass.localize( + "ui.common.yes" + )} + ${this.hass.localize( + "ui.common.no" + )} +
+
` + : this._readOnly + ? html`${this.hass.localize( + "ui.panel.config.automation.editor.read_only" + )} + + ${this.hass.localize( + "ui.panel.config.automation.editor.migrate" + )} + + ` + : nothing} + ${stateObj?.state === "off" + ? html` + + ${this.hass.localize( + "ui.panel.config.automation.editor.disabled" + )} + + ${this.hass.localize( + "ui.panel.config.automation.editor.enable" + )} + + + ` + : nothing} +
+ `}
` @@ -542,7 +567,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin( ` - : ""} + : nothing} .error-wrapper { - position: absolute; - top: 4px; - z-index: 3; + :not(.yaml-mode) > .alert-wrapper { + position: sticky; + top: -24px; + margin-top: -24px; + z-index: 100; width: 100%; display: flex; flex-direction: column; align-items: center; + gap: 8px; } - :not(.yaml-mode) > .error-wrapper ha-alert { + :not(.yaml-mode) > .alert-wrapper ha-alert { background-color: var(--card-background-color); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); border-radius: var(--ha-border-radius-sm); + margin-bottom: 0; } manual-automation-editor { diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 37c4a11e00..b22ba74c58 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -112,20 +112,6 @@ export class HaManualAutomationEditor extends LitElement { private _renderContent() { return html` - ${this.stateObj?.state === "off" - ? html` - - ${this.hass.localize( - "ui.panel.config.automation.editor.disabled" - )} - - ${this.hass.localize( - "ui.panel.config.automation.editor.enable" - )} - - - ` - : nothing} ${this.config.description ? html`
-
${this._renderContent()}
+
+ + ${this._renderContent()} +
{ - if (!this.hass || !this.stateObj) { - return; - } - await this.hass.callService("automation", "turn_on", { - entity_id: this.stateObj.entity_id, - }); - } - private _saveAutomation() { this._closeSidebar(); fireEvent(this, "save-automation"); @@ -656,9 +636,8 @@ export class HaManualAutomationEditor extends LitElement { line-height: 0; } - ha-alert { - display: block; - margin-bottom: 16px; + .description { + margin-top: 16px; } `, ]; diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 584a999fd8..27199efb9a 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -384,60 +384,6 @@ export class HaScriptEditor extends SubscribeMixin(
-
- ${this._errors || stateObj?.state === UNAVAILABLE - ? html` - ${this._errors || this._validationErrors} - ${stateObj?.state === UNAVAILABLE - ? html`` - : nothing} - ` - : nothing} - ${this._blueprintConfig - ? html` - ${this.hass.localize( - "ui.panel.config.script.editor.confirm_take_control" - )} -
- ${this.hass.localize("ui.common.yes")} - ${this.hass.localize("ui.common.no")} -
-
` - : this._readOnly - ? html`${this.hass.localize( - "ui.panel.config.script.editor.read_only" - )} - - ${this.hass.localize( - "ui.panel.config.script.editor.migrate" - )} - - ` - : nothing} -
${this._mode === "gui" ? html`
@@ -467,7 +413,68 @@ export class HaScriptEditor extends SubscribeMixin( @value-changed=${this._valueChanged} @editor-save=${this._handleSaveScript} @save-script=${this._handleSaveScript} - > + > +
+ ${this._errors || stateObj?.state === UNAVAILABLE + ? html` + ${this._errors || this._validationErrors} + ${stateObj?.state === UNAVAILABLE + ? html`` + : nothing} + ` + : nothing} + ${this._blueprintConfig + ? html` + ${this.hass.localize( + "ui.panel.config.script.editor.confirm_take_control" + )} +
+ ${this.hass.localize( + "ui.common.yes" + )} + ${this.hass.localize( + "ui.common.no" + )} +
+
` + : this._readOnly + ? html`${this.hass.localize( + "ui.panel.config.script.editor.read_only" + )} + + ${this.hass.localize( + "ui.panel.config.script.editor.migrate" + )} + + ` + : nothing} +
+ `}
` @@ -1092,6 +1099,26 @@ export class HaScriptEditor extends SubscribeMixin( border-radius: var(--ha-border-radius-sm); } + .alert-wrapper { + position: sticky; + top: -24px; + margin-top: -24px; + margin-bottom: 8px; + z-index: 100; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + } + + .alert-wrapper ha-alert { + background-color: var(--card-background-color); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border-radius: var(--ha-border-radius-sm); + margin-bottom: 0; + } + manual-script-editor { max-width: 1540px; padding: 0 12px; diff --git a/src/panels/config/script/manual-script-editor.ts b/src/panels/config/script/manual-script-editor.ts index c699b8f11a..439bd6aad3 100644 --- a/src/panels/config/script/manual-script-editor.ts +++ b/src/panels/config/script/manual-script-editor.ts @@ -201,7 +201,10 @@ export class HaManualScriptEditor extends LitElement { })} >
-
${this._renderContent()}
+
+ + ${this._renderContent()} +
Date: Thu, 28 Aug 2025 09:08:33 -0700 Subject: [PATCH 04/84] Hide 'options' from enum more info (#26736) * Hide 'options' from enum more info * restrict to specific domain and class --- src/components/ha-attributes.ts | 13 +++++++++++-- src/data/entity_attributes.ts | 9 +++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/components/ha-attributes.ts b/src/components/ha-attributes.ts index 9a9b467b58..7dc3116bd7 100644 --- a/src/components/ha-attributes.ts +++ b/src/components/ha-attributes.ts @@ -3,11 +3,15 @@ import type { CSSResultGroup, PropertyValues } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display"; -import { STATE_ATTRIBUTES } from "../data/entity_attributes"; +import { + STATE_ATTRIBUTES, + STATE_ATTRIBUTES_DOMAIN_CLASS, +} from "../data/entity_attributes"; import { haStyle } from "../resources/styles"; import type { HomeAssistant } from "../types"; import "./ha-attribute-value"; import "./ha-expansion-panel"; +import { computeStateDomain } from "../common/entity/compute_state_domain"; @customElement("ha-attributes") class HaAttributes extends LitElement { @@ -22,7 +26,12 @@ class HaAttributes extends LitElement { private get _filteredAttributes() { return this._computeDisplayAttributes( STATE_ATTRIBUTES.concat( - this.extraFilters ? this.extraFilters.split(",") : [] + this.extraFilters ? this.extraFilters.split(",") : [], + (this.stateObj && + STATE_ATTRIBUTES_DOMAIN_CLASS[computeStateDomain(this.stateObj)]?.[ + this.stateObj.attributes?.device_class + ]) || + [] ) ); } diff --git a/src/data/entity_attributes.ts b/src/data/entity_attributes.ts index a694d1f12f..5a9bede73d 100644 --- a/src/data/entity_attributes.ts +++ b/src/data/entity_attributes.ts @@ -1,6 +1,7 @@ import { formatDurationDigital } from "../common/datetime/format_duration"; import type { FrontendLocaleData } from "./translation"; +// These attributes are hidden from the more-info window for all entities. export const STATE_ATTRIBUTES = [ "entity_id", "assumed_state", @@ -26,6 +27,14 @@ export const STATE_ATTRIBUTES = [ "available_tones", ]; +// These attributes are hidden from the more-info window for entities of the +// matching domain and device_class. +export const STATE_ATTRIBUTES_DOMAIN_CLASS = { + sensor: { + enum: ["options"], + }, +}; + export const TEMPERATURE_ATTRIBUTES = new Set([ "temperature", "current_temperature", From 0dfc10af5f269eaa1563071b421122836fed5654 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 28 Aug 2025 01:34:45 -0400 Subject: [PATCH 05/84] Show configured area sensors on climate domain dashboard (#26737) --- src/panels/lovelace/card-features/types.ts | 1 + .../strategies/areas/helpers/areas-strategy-helper.ts | 8 +++++++- .../strategies/home/home-climate-view-strategy.ts | 10 ++++++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts index 27b2969703..7dcf1fb225 100644 --- a/src/panels/lovelace/card-features/types.ts +++ b/src/panels/lovelace/card-features/types.ts @@ -239,6 +239,7 @@ export type LovelaceCardFeatureConfig = | FanOscillateCardFeatureConfig | FanPresetModesCardFeatureConfig | FanSpeedCardFeatureConfig + | HistoryChartCardFeatureConfig | HumidifierToggleCardFeatureConfig | HumidifierModesCardFeatureConfig | LawnMowerCommandsCardFeatureConfig diff --git a/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts b/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts index 0dfb37103e..a3684103ea 100644 --- a/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts +++ b/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts @@ -15,6 +15,7 @@ import type { HomeAssistant } from "../../../../../types"; import { supportsAlarmModesCardFeature } from "../../../card-features/hui-alarm-modes-card-feature"; import { supportsCoverOpenCloseCardFeature } from "../../../card-features/hui-cover-open-close-card-feature"; import { supportsFanSpeedCardFeature } from "../../../card-features/hui-fan-speed-card-feature"; +import { supportsHistoryChartCardFeature } from "../../../card-features/hui-history-chart-card-feature"; import { supportsLightBrightnessCardFeature } from "../../../card-features/hui-light-brightness-card-feature"; import { supportsLockCommandsCardFeature } from "../../../card-features/hui-lock-commands-card-feature"; import { supportsTargetTemperatureCardFeature } from "../../../card-features/hui-target-temperature-card-feature"; @@ -237,7 +238,12 @@ export const computeAreaTileCardConfig = let feature: LovelaceCardFeatureConfig | undefined; if (includeFeature) { - if (supportsLightBrightnessCardFeature(hass, context)) { + if (supportsHistoryChartCardFeature(hass, context)) { + feature = { + type: "history-chart", + hours_to_show: 24, + }; + } else if (supportsLightBrightnessCardFeature(hass, context)) { feature = { type: "light-brightness", }; diff --git a/src/panels/lovelace/strategies/home/home-climate-view-strategy.ts b/src/panels/lovelace/strategies/home/home-climate-view-strategy.ts index 7cf5b90d6e..9d677dd391 100644 --- a/src/panels/lovelace/strategies/home/home-climate-view-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-climate-view-strategy.ts @@ -23,6 +23,7 @@ const processAreasForClimate = ( entities: string[] ): LovelaceCardConfig[] => { const cards: LovelaceCardConfig[] = []; + const computeTileCard = computeAreaTileCardConfig(hass, "", true); for (const areaId of areaIds) { const area = hass.areas[areaId]; @@ -33,8 +34,6 @@ const processAreasForClimate = ( }); const areaEntities = entities.filter(areaFilter); - const computeTileCard = computeAreaTileCardConfig(hass, "", true); - if (areaEntities.length > 0) { cards.push({ heading_style: "subtitle", @@ -46,6 +45,13 @@ const processAreasForClimate = ( }, }); + if (hass.areas[areaId].temperature_entity_id) { + cards.push(computeTileCard(hass.areas[areaId].temperature_entity_id)); + } + if (hass.areas[areaId].humidity_entity_id) { + cards.push(computeTileCard(hass.areas[areaId].humidity_entity_id)); + } + for (const entityId of areaEntities) { cards.push(computeTileCard(entityId)); } From e19413b6ca0cbe513bd479a38ef8f85d809a4705 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 28 Aug 2025 01:26:44 -0400 Subject: [PATCH 06/84] Show binary sensors with graphs on the security dashboard (#26738) --- .../strategies/home/helpers/home-summaries.ts | 2 +- .../strategies/home/home-security-view-strategy.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/panels/lovelace/strategies/home/helpers/home-summaries.ts b/src/panels/lovelace/strategies/home/helpers/home-summaries.ts index a9dbd1f7ee..974560762e 100644 --- a/src/panels/lovelace/strategies/home/helpers/home-summaries.ts +++ b/src/panels/lovelace/strategies/home/helpers/home-summaries.ts @@ -65,7 +65,7 @@ export const HOME_SUMMARIES_FILTERS: Record = { }, { domain: "binary_sensor", - device_class: ["door", "garage_door"], + device_class: ["door", "garage_door", "motion"], entity_category: "none", }, ], diff --git a/src/panels/lovelace/strategies/home/home-security-view-strategy.ts b/src/panels/lovelace/strategies/home/home-security-view-strategy.ts index b80fd9f5ad..759df98206 100644 --- a/src/panels/lovelace/strategies/home/home-security-view-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-security-view-strategy.ts @@ -12,6 +12,7 @@ import { } from "../areas/helpers/areas-strategy-helper"; import { getHomeStructure } from "./helpers/home-structure"; import { findEntities, HOME_SUMMARIES_FILTERS } from "./helpers/home-summaries"; +import { computeDomain } from "../../../../common/entity/compute_domain"; export interface HomeSecurityViewStrategyConfig { type: "home-security"; @@ -23,6 +24,8 @@ const processAreasForSecurity = ( entities: string[] ): LovelaceCardConfig[] => { const cards: LovelaceCardConfig[] = []; + const computeTileCard = computeAreaTileCardConfig(hass, "", false); + const computeTileCardWithFeature = computeAreaTileCardConfig(hass, "", true); for (const areaId of areaIds) { const area = hass.areas[areaId]; @@ -33,8 +36,6 @@ const processAreasForSecurity = ( }); const areaEntities = entities.filter(areaFilter); - const computeTileCard = computeAreaTileCardConfig(hass, "", false); - if (areaEntities.length > 0) { cards.push({ heading_style: "subtitle", @@ -47,7 +48,12 @@ const processAreasForSecurity = ( }); for (const entityId of areaEntities) { - cards.push(computeTileCard(entityId)); + cards.push( + computeDomain(entityId) === "binary_sensor" && + hass.states[entityId]?.attributes.device_class === "motion" + ? computeTileCardWithFeature(entityId) + : computeTileCard(entityId) + ); } } } From 90a1b135e1e9735fef1f58ca0b75f154e1deb43e Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Thu, 28 Aug 2025 13:30:14 +0200 Subject: [PATCH 07/84] Fix automation editor drag selected row in/out nested (#26740) Fix nested sort --- src/components/ha-sortable.ts | 11 +++++ .../action/ha-automation-action-row.ts | 4 ++ .../automation/action/ha-automation-action.ts | 48 ++++++++++++++++--- .../condition/ha-automation-condition-row.ts | 4 ++ .../condition/ha-automation-condition.ts | 48 ++++++++++++++++--- .../option/ha-automation-option-row.ts | 4 ++ .../automation/option/ha-automation-option.ts | 45 +++++++++++++++-- .../trigger/ha-automation-trigger-row.ts | 4 ++ .../trigger/ha-automation-trigger.ts | 38 +++++++++++++-- 9 files changed, 184 insertions(+), 22 deletions(-) diff --git a/src/components/ha-sortable.ts b/src/components/ha-sortable.ts index d5bf21638d..64fd388724 100644 --- a/src/components/ha-sortable.ts +++ b/src/components/ha-sortable.ts @@ -21,9 +21,15 @@ declare global { }; "drag-start": undefined; "drag-end": undefined; + "item-cloned": HaSortableClonedEventData; } } +export interface HaSortableClonedEventData { + item: any; + clone: any; +} + export type HaSortableOptions = Omit< SortableInstance.SortableOptions, "onStart" | "onChoose" | "onEnd" | "onUpdate" | "onAdd" | "onRemove" @@ -148,6 +154,7 @@ export class HaSortable extends LitElement { onUpdate: this._handleUpdate, onAdd: this._handleAdd, onRemove: this._handleRemove, + onClone: this._handleClone, }; if (this.draggableSelector) { @@ -187,6 +194,10 @@ export class HaSortable extends LitElement { fireEvent(this, "item-removed", { index: evt.oldIndex }); }; + private _handleClone = (evt) => { + fireEvent(this, "item-cloned", evt); + }; + private _handleEnd = async (evt) => { fireEvent(this, "drag-end"); // put back in original location diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 4e82501a89..da0ff1ad91 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -747,6 +747,10 @@ export default class HaAutomationActionRow extends LitElement { this._collapsed = !this._collapsed; } + public isSelected() { + return this._selected; + } + static styles = rowStyles; } diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index b8467f894b..f9d93844e5 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -10,6 +10,7 @@ import { listenMediaQuery } from "../../../../common/dom/media_query"; import { nextRender } from "../../../../common/util/render-status"; import "../../../../components/ha-button"; import "../../../../components/ha-sortable"; +import type { HaSortableClonedEventData } from "../../../../components/ha-sortable"; import "../../../../components/ha-svg-icon"; import { ACTION_BUILDING_BLOCKS, @@ -61,6 +62,8 @@ export default class HaAutomationAction extends LitElement { private _focusLastActionOnChange = false; + private _focusActionIndexOnChange?: number; + private _actionKeys = new WeakMap(); private _unsubMql?: () => void; @@ -89,6 +92,7 @@ export default class HaAutomationAction extends LitElement { @item-moved=${this._actionMoved} @item-added=${this._actionAdded} @item-removed=${this._actionRemoved} + @item-cloned=${this._actionCloned} >
${repeat( @@ -154,19 +158,27 @@ export default class HaAutomationAction extends LitElement { protected updated(changedProps: PropertyValues) { super.updated(changedProps); - if (changedProps.has("actions") && this._focusLastActionOnChange) { - this._focusLastActionOnChange = false; + if ( + changedProps.has("actions") && + (this._focusLastActionOnChange || + this._focusActionIndexOnChange !== undefined) + ) { + const mode = this._focusLastActionOnChange ? "new" : "moved"; const row = this.shadowRoot!.querySelector( - "ha-automation-action-row:last-of-type" + `ha-automation-action-row:${mode === "new" ? "last-of-type" : `nth-of-type(${this._focusActionIndexOnChange! + 1})`}` )!; + + this._focusLastActionOnChange = false; + this._focusActionIndexOnChange = undefined; + row.updateComplete.then(() => { // on new condition open the settings in the sidebar, except for building blocks const type = getAutomationActionType(row.action); if ( type && this.optionsInSidebar && - !ACTION_BUILDING_BLOCKS.includes(type) + (!ACTION_BUILDING_BLOCKS.includes(type) || mode === "moved") ) { row.openSidebar(); if (this.narrow) { @@ -176,8 +188,14 @@ export default class HaAutomationAction extends LitElement { }); } } - row.expand(); - row.focus(); + + if (mode === "new") { + row.expand(); + } + + if (!this.optionsInSidebar) { + row.focus(); + } }); } } @@ -277,6 +295,12 @@ export default class HaAutomationAction extends LitElement { private async _actionAdded(ev: CustomEvent): Promise { ev.stopPropagation(); const { index, data } = ev.detail; + let selected = false; + if (data?.["ha-automation-row-selected"]) { + selected = true; + delete data["ha-automation-row-selected"]; + } + let actions = [ ...this.actions.slice(0, index), data, @@ -284,6 +308,9 @@ export default class HaAutomationAction extends LitElement { ]; // Add action locally to avoid UI jump this.actions = actions; + if (selected) { + this._focusActionIndexOnChange = actions.length === 1 ? 0 : index; + } await nextRender(); if (this.actions !== actions) { // Ensure action is added even after update @@ -292,6 +319,9 @@ export default class HaAutomationAction extends LitElement { data, ...this.actions.slice(index), ]; + if (selected) { + this._focusActionIndexOnChange = actions.length === 1 ? 0 : index; + } } fireEvent(this, "value-changed", { value: actions }); } @@ -327,6 +357,12 @@ export default class HaAutomationAction extends LitElement { fireEvent(this, "value-changed", { value: actions }); } + private _actionCloned(ev: CustomEvent) { + if (ev.detail.item.action && ev.detail.item.isSelected()) { + ev.detail.item.action["ha-automation-row-selected"] = true; + } + } + private _duplicateAction(ev: CustomEvent) { ev.stopPropagation(); const index = (ev.target as any).index; diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 7dfb69f090..6809388b4d 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -684,6 +684,10 @@ export default class HaAutomationConditionRow extends LitElement { this._collapsed = !this._collapsed; } + public isSelected() { + return this._selected; + } + static get styles(): CSSResultGroup { return [ rowStyles, diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 5723013eec..f88c4612b0 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -11,6 +11,7 @@ import { nextRender } from "../../../../common/util/render-status"; import "../../../../components/ha-button"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-sortable"; +import type { HaSortableClonedEventData } from "../../../../components/ha-sortable"; import "../../../../components/ha-svg-icon"; import type { AutomationClipboard, @@ -59,6 +60,8 @@ export default class HaAutomationCondition extends LitElement { private _focusLastConditionOnChange = false; + private _focusConditionIndexOnChange?: number; + private _conditionKeys = new WeakMap(); private _unsubMql?: () => void; @@ -100,16 +103,25 @@ export default class HaAutomationCondition extends LitElement { fireEvent(this, "value-changed", { value: updatedConditions, }); - } else if (this._focusLastConditionOnChange) { - this._focusLastConditionOnChange = false; + } else if ( + this._focusLastConditionOnChange || + this._focusConditionIndexOnChange !== undefined + ) { + const mode = this._focusLastConditionOnChange ? "new" : "moved"; + const row = this.shadowRoot!.querySelector( - "ha-automation-condition-row:last-of-type" + `ha-automation-condition-row:${mode === "new" ? "last-of-type" : `nth-of-type(${this._focusConditionIndexOnChange! + 1})`}` )!; + + this._focusLastConditionOnChange = false; + this._focusConditionIndexOnChange = undefined; + row.updateComplete.then(() => { // on new condition open the settings in the sidebar, except for building blocks if ( this.optionsInSidebar && - !CONDITION_BUILDING_BLOCKS.includes(row.condition.condition) + (!CONDITION_BUILDING_BLOCKS.includes(row.condition.condition) || + mode === "moved") ) { row.openSidebar(); if (this.narrow) { @@ -119,8 +131,14 @@ export default class HaAutomationCondition extends LitElement { }); } } - row.expand(); - row.focus(); + + if (mode === "new") { + row.expand(); + } + + if (!this.optionsInSidebar) { + row.focus(); + } }); } } @@ -151,6 +169,7 @@ export default class HaAutomationCondition extends LitElement { @item-moved=${this._conditionMoved} @item-added=${this._conditionAdded} @item-removed=${this._conditionRemoved} + @item-cloned=${this._conditionCloned} >
${repeat( @@ -294,6 +313,11 @@ export default class HaAutomationCondition extends LitElement { private async _conditionAdded(ev: CustomEvent): Promise { ev.stopPropagation(); const { index, data } = ev.detail; + let selected = false; + if (data?.["ha-automation-row-selected"]) { + selected = true; + delete data["ha-automation-row-selected"]; + } let conditions = [ ...this.conditions.slice(0, index), data, @@ -301,6 +325,9 @@ export default class HaAutomationCondition extends LitElement { ]; // Add condition locally to avoid UI jump this.conditions = conditions; + if (selected) { + this._focusConditionIndexOnChange = conditions.length === 1 ? 0 : index; + } await nextRender(); if (this.conditions !== conditions) { // Ensure condition is added even after update @@ -309,6 +336,9 @@ export default class HaAutomationCondition extends LitElement { data, ...this.conditions.slice(index), ]; + if (selected) { + this._focusConditionIndexOnChange = conditions.length === 1 ? 0 : index; + } } fireEvent(this, "value-changed", { value: conditions }); } @@ -325,6 +355,12 @@ export default class HaAutomationCondition extends LitElement { fireEvent(this, "value-changed", { value: conditions }); } + private _conditionCloned(ev: CustomEvent) { + if (ev.detail.item.isSelected()) { + ev.detail.item.condition["ha-automation-row-selected"] = true; + } + } + private _conditionChanged(ev: CustomEvent) { ev.stopPropagation(); const conditions = [...this.conditions]; diff --git a/src/panels/config/automation/option/ha-automation-option-row.ts b/src/panels/config/automation/option/ha-automation-option-row.ts index bf9d25d1aa..ed9c72c32a 100644 --- a/src/panels/config/automation/option/ha-automation-option-row.ts +++ b/src/panels/config/automation/option/ha-automation-option-row.ts @@ -438,6 +438,10 @@ export default class HaAutomationOptionRow extends LitElement { this._collapsed = !this._collapsed; } + public isSelected() { + return this._selected; + } + static get styles(): CSSResultGroup { return [ rowStyles, diff --git a/src/panels/config/automation/option/ha-automation-option.ts b/src/panels/config/automation/option/ha-automation-option.ts index 48bc6f0743..d6c50da7cd 100644 --- a/src/panels/config/automation/option/ha-automation-option.ts +++ b/src/panels/config/automation/option/ha-automation-option.ts @@ -10,6 +10,7 @@ import { listenMediaQuery } from "../../../../common/dom/media_query"; import { nextRender } from "../../../../common/util/render-status"; import "../../../../components/ha-button"; import "../../../../components/ha-sortable"; +import type { HaSortableClonedEventData } from "../../../../components/ha-sortable"; import "../../../../components/ha-svg-icon"; import type { AutomationClipboard } from "../../../../data/automation"; import type { Option } from "../../../../data/script"; @@ -50,6 +51,8 @@ export default class HaAutomationOption extends LitElement { private _focusLastOptionOnChange = false; + private _focusOptionIndexOnChange?: number; + private _optionsKeys = new WeakMap(); private _unsubMql?: () => void; @@ -78,6 +81,7 @@ export default class HaAutomationOption extends LitElement { @item-moved=${this._optionMoved} @item-added=${this._optionAdded} @item-removed=${this._optionRemoved} + @item-cloned=${this._optionCloned} >
${repeat( @@ -143,12 +147,20 @@ export default class HaAutomationOption extends LitElement { protected updated(changedProps: PropertyValues) { super.updated(changedProps); - if (changedProps.has("options") && this._focusLastOptionOnChange) { - this._focusLastOptionOnChange = false; + if ( + changedProps.has("options") && + (this._focusLastOptionOnChange || + this._focusOptionIndexOnChange !== undefined) + ) { + const mode = this._focusLastOptionOnChange ? "new" : "moved"; const row = this.shadowRoot!.querySelector( - "ha-automation-option-row:last-of-type" + `ha-automation-option-row:${mode === "new" ? "last-of-type" : `nth-of-type(${this._focusOptionIndexOnChange! + 1})`}` )!; + + this._focusLastOptionOnChange = false; + this._focusOptionIndexOnChange = undefined; + row.updateComplete.then(() => { if (this.narrow) { row.scrollIntoView({ @@ -156,8 +168,16 @@ export default class HaAutomationOption extends LitElement { behavior: "smooth", }); } - row.expand(); - row.focus(); + + if (mode === "new") { + row.expand(); + } + + if (this.optionsInSidebar) { + row.openSidebar(); + } else { + row.focus(); + } }); } } @@ -215,6 +235,12 @@ export default class HaAutomationOption extends LitElement { private async _optionAdded(ev: CustomEvent): Promise { ev.stopPropagation(); const { index, data } = ev.detail; + let selected = false; + if (data?.["ha-automation-row-selected"]) { + selected = true; + delete data["ha-automation-row-selected"]; + } + const options = [ ...this.options.slice(0, index), data, @@ -222,6 +248,9 @@ export default class HaAutomationOption extends LitElement { ]; // Add option locally to avoid UI jump this.options = options; + if (selected) { + this._focusOptionIndexOnChange = options.length === 1 ? 0 : index; + } await nextRender(); fireEvent(this, "value-changed", { value: this.options }); } @@ -238,6 +267,12 @@ export default class HaAutomationOption extends LitElement { fireEvent(this, "value-changed", { value: options }); } + private _optionCloned(ev: CustomEvent) { + if (ev.detail.item.isSelected()) { + ev.detail.item.option["ha-automation-row-selected"] = true; + } + } + private _optionChanged(ev: CustomEvent) { ev.stopPropagation(); const options = [...this.options]; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index bcf4797e4b..c982f01ece 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -666,6 +666,10 @@ export default class HaAutomationTriggerRow extends LitElement { customElements.get(`ha-automation-trigger-${type}`) !== undefined ); + public isSelected() { + return this._selected; + } + static get styles(): CSSResultGroup { return [ rowStyles, diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index f4630e6356..68d8988e73 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -11,6 +11,7 @@ import { nextRender } from "../../../../common/util/render-status"; import "../../../../components/ha-button"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-sortable"; +import type { HaSortableClonedEventData } from "../../../../components/ha-sortable"; import "../../../../components/ha-svg-icon"; import type { AutomationClipboard, @@ -57,6 +58,8 @@ export default class HaAutomationTrigger extends LitElement { private _focusLastTriggerOnChange = false; + private _focusTriggerIndexOnChange?: number; + private _triggerKeys = new WeakMap(); private _unsubMql?: () => void; @@ -85,6 +88,7 @@ export default class HaAutomationTrigger extends LitElement { @item-moved=${this._triggerMoved} @item-added=${this._triggerAdded} @item-removed=${this._triggerRemoved} + @item-cloned=${this._triggerCloned} >
${repeat( @@ -172,12 +176,18 @@ export default class HaAutomationTrigger extends LitElement { protected updated(changedProps: PropertyValues) { super.updated(changedProps); - if (changedProps.has("triggers") && this._focusLastTriggerOnChange) { - this._focusLastTriggerOnChange = false; - + if ( + changedProps.has("triggers") && + (this._focusLastTriggerOnChange || + this._focusTriggerIndexOnChange !== undefined) + ) { const row = this.shadowRoot!.querySelector( - "ha-automation-trigger-row:last-of-type" + `ha-automation-trigger-row:${this._focusLastTriggerOnChange ? "last-of-type" : `nth-of-type(${this._focusTriggerIndexOnChange! + 1})`}` )!; + + this._focusLastTriggerOnChange = false; + this._focusTriggerIndexOnChange = undefined; + row.updateComplete.then(() => { if (this.optionsInSidebar) { row.openSidebar(); @@ -189,8 +199,8 @@ export default class HaAutomationTrigger extends LitElement { } } else { row.expand(); + row.focus(); } - row.focus(); }); } } @@ -244,6 +254,12 @@ export default class HaAutomationTrigger extends LitElement { private async _triggerAdded(ev: CustomEvent): Promise { ev.stopPropagation(); const { index, data } = ev.detail; + let selected = false; + if (data?.["ha-automation-row-selected"]) { + selected = true; + delete data["ha-automation-row-selected"]; + } + let triggers = [ ...this.triggers.slice(0, index), data, @@ -251,6 +267,9 @@ export default class HaAutomationTrigger extends LitElement { ]; // Add trigger locally to avoid UI jump this.triggers = triggers; + if (selected) { + this._focusTriggerIndexOnChange = triggers.length === 1 ? 0 : index; + } await nextRender(); if (this.triggers !== triggers) { // Ensure trigger is added even after update @@ -259,6 +278,9 @@ export default class HaAutomationTrigger extends LitElement { data, ...this.triggers.slice(index), ]; + if (selected) { + this._focusTriggerIndexOnChange = triggers.length === 1 ? 0 : index; + } } fireEvent(this, "value-changed", { value: triggers }); } @@ -275,6 +297,12 @@ export default class HaAutomationTrigger extends LitElement { fireEvent(this, "value-changed", { value: triggers }); } + private _triggerCloned(ev: CustomEvent) { + if (ev.detail.item.isSelected()) { + ev.detail.item.trigger["ha-automation-row-selected"] = true; + } + } + private _triggerChanged(ev: CustomEvent) { ev.stopPropagation(); const triggers = [...this.triggers]; From fc71fd6bc34647abb79540ebbc0610d4c2cfc327 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Thu, 28 Aug 2025 11:05:31 +0200 Subject: [PATCH 08/84] Improve section descriptions in Automation editor (#26741) Replace "listed here" or "list of" with "added here" --- src/translations/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index f18091d5d3..c8b1fd7ff5 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3850,7 +3850,7 @@ "triggers": { "name": "Triggers", "header": "When", - "description": "A trigger is a specific event happening in or around your home, for example: ''When the sun sets''. Any trigger listed here will start your automation.", + "description": "A trigger is a specific event happening in or around your home, for example: ''When the sun sets''. Any trigger added here will start your automation.", "learn_more": "Learn more about triggers", "triggered": "Triggered", "add": "Add trigger", @@ -4110,7 +4110,7 @@ "conditions": { "name": "Conditions", "header": "And if", - "description": "This list of conditions needs to be satisfied for the automation to run. A condition can be satisfied or not at any given time, for example: ''If {user} is home''. You can use building blocks to create more complex conditions.", + "description": "All conditions added here need to be satisfied for the automation to run. A condition can be satisfied or not at any given time, for example: ''If {user} is home''. You can use building blocks to create more complex conditions.", "learn_more": "Learn more about conditions", "add": "Add condition", "search": "Search condition", @@ -4277,7 +4277,7 @@ "actions": { "name": "Actions", "header": "Then do", - "description": "This list of actions will be performed in sequence when the automation runs. An action usually controls one of your areas, devices, or entities, for example: 'Turn on the lights'. You can use building blocks to create more complex sequences of actions.", + "description": "All actions added here will be performed in sequence when the automation runs. An action usually controls one of your areas, devices, or entities, for example: 'Turn on the lights'. You can use building blocks to create more complex sequences of actions.", "learn_more": "Learn more about actions", "add": "Add action", "search": "Search action", From 60472276480fe09bd829c504d3f479b02d1b42ad Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Thu, 28 Aug 2025 18:42:45 +0200 Subject: [PATCH 09/84] Automation editor: overflow changes and style fixes (#26744) * Fix for width also for blueprint editor * Fix overflow menus * Fix option icons * Fix iOS bottom sheet flickering and drag handle * Fix mobile padding * Fix padding in sidebar * Fix overflow placement * Add new a11y sort * Remove overflow in rows * Fix a11y select row * Revert "Fix a11y select row" This reverts commit 54260c4a374513075f890b82ad663cbc05df9556. * Fix option padding on blueprint --------- Co-authored-by: Paul Bottein --- src/components/ha-automation-row.ts | 41 ++- src/components/ha-bottom-sheet.ts | 1 + src/components/ha-md-button-menu.ts | 17 ++ .../action/ha-automation-action-editor.ts | 1 + .../action/ha-automation-action-row.ts | 264 +++++++++-------- .../automation/action/ha-automation-action.ts | 54 +++- .../ha-automation-condition-editor.ts | 3 + .../condition/ha-automation-condition-row.ts | 269 ++++++++--------- .../condition/ha-automation-condition.ts | 50 +++- .../option/ha-automation-option-row.ts | 109 +++---- .../automation/option/ha-automation-option.ts | 61 +++- .../sidebar/ha-automation-sidebar-card.ts | 3 + .../ha-automation-sidebar-condition.ts | 1 + .../sidebar/ha-automation-sidebar-option.ts | 2 +- .../sidebar/ha-automation-sidebar-trigger.ts | 1 + src/panels/config/automation/styles.ts | 20 +- .../trigger/ha-automation-trigger-editor.ts | 3 + .../trigger/ha-automation-trigger-row.ts | 273 +++++++++--------- .../trigger/ha-automation-trigger.ts | 50 +++- .../config/script/ha-script-field-row.ts | 34 ++- 20 files changed, 783 insertions(+), 474 deletions(-) diff --git a/src/components/ha-automation-row.ts b/src/components/ha-automation-row.ts index 29834058da..e15a65bd50 100644 --- a/src/components/ha-automation-row.ts +++ b/src/components/ha-automation-row.ts @@ -1,7 +1,7 @@ import { mdiChevronUp } from "@mdi/js"; import type { TemplateResult } from "lit"; import { css, html, LitElement, nothing } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import "./ha-icon-button"; @@ -16,12 +16,18 @@ export class HaAutomationRow extends LitElement { @property({ type: Boolean, reflect: true }) public selected = false; + @property({ type: Boolean, reflect: true, attribute: "sort-selected" }) + public sortSelected = false; + @property({ type: Boolean, reflect: true }) public disabled = false; @property({ type: Boolean, reflect: true, attribute: "building-block" }) public buildingBlock = false; + @query(".row") + private _rowElement?: HTMLDivElement; + protected render(): TemplateResult { return html`
this._rowElement?.focus()); + } + static styles = css` :host { display: block; @@ -134,6 +165,11 @@ export class HaAutomationRow extends LitElement { overflow-wrap: anywhere; margin: 0 12px; } + :host([sort-selected]) .row { + box-shadow: + 0px 0px 8px 4px rgba(var(--rgb-accent-color), 0.8), + inset 0px 2px 8px 4px rgba(var(--rgb-accent-color), 0.4); + } `; } @@ -144,5 +180,6 @@ declare global { interface HASSDomEvents { "toggle-collapsed": undefined; + "stop-sort-selection": undefined; } } diff --git a/src/components/ha-bottom-sheet.ts b/src/components/ha-bottom-sheet.ts index 2c4223e220..5737fe9750 100644 --- a/src/components/ha-bottom-sheet.ts +++ b/src/components/ha-bottom-sheet.ts @@ -197,6 +197,7 @@ export class HaBottomSheet extends LitElement { justify-content: center; align-items: center; z-index: 7; + padding-bottom: 76px; } .handle-wrapper .handle::after { content: ""; diff --git a/src/components/ha-md-button-menu.ts b/src/components/ha-md-button-menu.ts index 637e773f71..4e07f40bee 100644 --- a/src/components/ha-md-button-menu.ts +++ b/src/components/ha-md-button-menu.ts @@ -16,9 +16,23 @@ export class HaMdButtonMenu extends LitElement { @property() public positioning?: "fixed" | "absolute" | "popover"; + @property({ attribute: "anchor-corner" }) public anchorCorner: + | "start-start" + | "start-end" + | "end-start" + | "end-end" = "end-start"; + + @property({ attribute: "menu-corner" }) public menuCorner: + | "start-start" + | "start-end" + | "end-start" + | "end-end" = "start-start"; + @property({ type: Boolean, attribute: "has-overflow" }) public hasOverflow = false; + @property({ type: Boolean }) public quick = false; + @query("ha-md-menu", true) private _menu!: HaMdMenu; public get items() { @@ -39,8 +53,11 @@ export class HaMdButtonMenu extends LitElement {
diff --git a/src/panels/config/automation/action/ha-automation-action-editor.ts b/src/panels/config/automation/action/ha-automation-action-editor.ts index 926733e5de..d2756e8a1a 100644 --- a/src/panels/config/automation/action/ha-automation-action-editor.ts +++ b/src/panels/config/automation/action/ha-automation-action-editor.ts @@ -53,6 +53,7 @@ export default class HaAutomationActionEditor extends LitElement { this.disabled || (this.action.enabled === false && !this.yamlMode), yaml: yamlMode, indent: this.indent, + card: !this.inSidebar, })} > ${yamlMode diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index da0ff1ad91..034b55073a 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -26,6 +26,7 @@ import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; import { handleStructError } from "../../../../common/structs/handle-errors"; import "../../../../components/ha-automation-row"; +import type { HaAutomationRow } from "../../../../components/ha-automation-row"; import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-icon-button"; @@ -153,6 +154,9 @@ export default class HaAutomationActionRow extends LitElement { @property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar = false; + @property({ type: Boolean, attribute: "sort-selected" }) + public sortSelected = false; + @storage({ key: "automationClipboard", state: false, @@ -186,6 +190,9 @@ export default class HaAutomationActionRow extends LitElement { @query("ha-automation-action-editor") private _actionEditor?: HaAutomationActionEditor; + @query("ha-automation-row") + private _automationRowElement?: HaAutomationRow; + protected firstUpdated(changedProperties: PropertyValues): void { super.firstUpdated(changedProperties); @@ -254,138 +261,136 @@ export default class HaAutomationActionRow extends LitElement { ` : nothing} + ${!this.optionsInSidebar + ? html` + - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.run" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.rename" + )} + + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + - ${!this.optionsInSidebar - ? html` - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.run" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.rename" - )} - - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.copy" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.copy" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.cut" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.cut" - )} - - ` - : nothing} + + ${this.hass.localize("ui.panel.config.automation.editor.move_up")} + - - ${this.hass.localize("ui.panel.config.automation.editor.move_up")} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.move_down" + )} + - - ${this.hass.localize("ui.panel.config.automation.editor.move_down")} - + + ${this.hass.localize( + `ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}` + )} + + - ${!this.optionsInSidebar - ? html` - ${this.hass.localize( - `ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}` - )} - - + - - - - ${this.action.enabled === false - ? this.hass.localize( - "ui.panel.config.automation.editor.actions.enable" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - + ${this.action.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - ` - : nothing} - - + slot="start" + .path=${mdiDelete} + > + + ` + : nothing} ${!this.optionsInSidebar ? html`${this._warnings ? html`${this._renderRow()}` : html` @@ -697,9 +703,11 @@ export default class HaAutomationActionRow extends LitElement { this._collapsed = false; if (this.narrow) { - this.scrollIntoView({ - block: "start", - behavior: "smooth", + requestAnimationFrame(() => { + this.scrollIntoView({ + block: "start", + behavior: "smooth", + }); }); } } @@ -751,6 +759,10 @@ export default class HaAutomationActionRow extends LitElement { return this._selected; } + public focus() { + this._automationRowElement?.focus(); + } + static styles = rowStyles; } diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index f9d93844e5..e78f874ec7 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -48,6 +48,8 @@ export default class HaAutomationAction extends LitElement { @state() private _showReorder = false; + @state() private _rowSortSelected?: number; + @state() @storage({ key: "automationClipboard", @@ -94,7 +96,7 @@ export default class HaAutomationAction extends LitElement { @item-removed=${this._actionRemoved} @item-cloned=${this._actionCloned} > -
+
${repeat( this.actions, (action) => this._getKey(action), @@ -115,10 +117,20 @@ export default class HaAutomationAction extends LitElement { .hass=${this.hass} ?highlight=${this.highlightedActions?.includes(action)} .optionsInSidebar=${this.optionsInSidebar} + .sortSelected=${this._rowSortSelected === idx} + @stop-sort-selection=${this._stopSortSelection} > ${this._showReorder && !this.disabled ? html` -
+
` @@ -264,18 +276,30 @@ export default class HaAutomationAction extends LitElement { return this._actionKeys.get(action)!; } - private _moveUp(ev) { + private async _moveUp(ev) { ev.stopPropagation(); const index = (ev.target as any).index; - const newIndex = index - 1; - this._move(index, newIndex); + if (!(ev.target as HaAutomationActionRow).first) { + const newIndex = index - 1; + this._move(index, newIndex); + if (this._rowSortSelected === index) { + this._rowSortSelected = newIndex; + } + ev.target.focus(); + } } - private _moveDown(ev) { + private async _moveDown(ev) { ev.stopPropagation(); const index = (ev.target as any).index; - const newIndex = index + 1; - this._move(index, newIndex); + if (!(ev.target as HaAutomationActionRow).last) { + const newIndex = index + 1; + this._move(index, newIndex); + if (this._rowSortSelected === index) { + this._rowSortSelected = newIndex; + } + ev.target.focus(); + } } private _move(oldIndex: number, newIndex: number) { @@ -371,6 +395,20 @@ export default class HaAutomationAction extends LitElement { }); } + private _handleDragKeydown(ev: KeyboardEvent) { + if (ev.key === "Enter" || ev.key === " ") { + ev.stopPropagation(); + this._rowSortSelected = + this._rowSortSelected === undefined + ? (ev.target as any).index + : undefined; + } + } + + private _stopSortSelection() { + this._rowSortSelected = undefined; + } + static styles = [ automationRowsStyles, css` diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index 5fbeb8ac5b..92b4927e1d 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -28,6 +28,8 @@ export default class HaAutomationConditionEditor extends LitElement { @property({ type: Boolean }) public narrow = false; + @property({ type: Boolean, attribute: "sidebar" }) public inSidebar = false; + @property({ type: Boolean, reflect: true }) public selected = false; @property({ type: Boolean, attribute: "supported" }) public uiSupported = @@ -55,6 +57,7 @@ export default class HaAutomationConditionEditor extends LitElement { (this.condition.enabled === false && !this.yamlMode), yaml: yamlMode, indent: this.indent, + card: !this.inSidebar, })} > ${yamlMode diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 6809388b4d..8b14e08f0a 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -26,6 +26,7 @@ import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; import { handleStructError } from "../../../../common/structs/handle-errors"; import "../../../../components/ha-automation-row"; +import type { HaAutomationRow } from "../../../../components/ha-automation-row"; import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-icon-button"; @@ -115,6 +116,9 @@ export default class HaAutomationConditionRow extends LitElement { @property({ type: Boolean }) public narrow = false; + @property({ type: Boolean, attribute: "sort-selected" }) + public sortSelected = false; + @state() private _collapsed = true; @state() private _warnings?: string[]; @@ -145,6 +149,9 @@ export default class HaAutomationConditionRow extends LitElement { @query("ha-automation-condition-editor") public conditionEditor?: HaAutomationConditionEditor; + @query("ha-automation-row") + private _automationRowElement?: HaAutomationRow; + private _renderRow() { return html` - - - + ${!this.optionsInSidebar + ? html` + + - ${!this.optionsInSidebar - ? html` - - ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.test" - )} - - - - ${this.hass.localize( - "ui.panel.config.automation.editor.conditions.rename" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.conditions.test" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.conditions.rename" + )} + + - + - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.copy" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.copy" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.cut" - )} - - - ` - : nothing} + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.cut" + )} + + - - ${this.hass.localize("ui.panel.config.automation.editor.move_up")} - + + ${this.hass.localize("ui.panel.config.automation.editor.move_up")} + - - ${this.hass.localize("ui.panel.config.automation.editor.move_down")} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.move_down" + )} + - ${!this.optionsInSidebar - ? html` - ${this.hass.localize( - `ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}` - )} - - + + ${this.hass.localize( + `ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}` + )} + + - + - - ${this.condition.enabled === false - ? this.hass.localize( - "ui.panel.config.automation.editor.actions.enable" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - + ${this.condition.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - ` - : nothing} - - + slot="start" + .path=${mdiDelete} + > + + ` + : nothing} ${!this.optionsInSidebar ? html`${this._warnings ? html`${this._renderRow()}` : html` @@ -668,9 +673,11 @@ export default class HaAutomationConditionRow extends LitElement { this._collapsed = false; if (this.narrow) { - this.scrollIntoView({ - block: "start", - behavior: "smooth", + requestAnimationFrame(() => { + this.scrollIntoView({ + block: "start", + behavior: "smooth", + }); }); } } @@ -688,6 +695,10 @@ export default class HaAutomationConditionRow extends LitElement { return this._selected; } + public focus() { + this._automationRowElement?.focus(); + } + static get styles(): CSSResultGroup { return [ rowStyles, diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index f88c4612b0..a521c4d2b4 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -46,6 +46,8 @@ export default class HaAutomationCondition extends LitElement { @state() private _showReorder = false; + @state() private _rowSortSelected?: number; + @state() @storage({ key: "automationClipboard", @@ -171,7 +173,7 @@ export default class HaAutomationCondition extends LitElement { @item-removed=${this._conditionRemoved} @item-cloned=${this._conditionCloned} > -
+
${repeat( this.conditions.filter((c) => typeof c === "object"), (condition) => this._getKey(condition), @@ -193,10 +195,20 @@ export default class HaAutomationCondition extends LitElement { .hass=${this.hass} ?highlight=${this.highlightedConditions?.includes(cond)} .optionsInSidebar=${this.optionsInSidebar} + .sortSelected=${this._rowSortSelected === idx} + @stop-sort-selection=${this._stopSortSelection} > ${this._showReorder && !this.disabled ? html` -
+
` @@ -285,15 +297,27 @@ export default class HaAutomationCondition extends LitElement { private _moveUp(ev) { ev.stopPropagation(); const index = (ev.target as any).index; - const newIndex = index - 1; - this._move(index, newIndex); + if (!(ev.target as HaAutomationConditionRow).first) { + const newIndex = index - 1; + this._move(index, newIndex); + if (this._rowSortSelected === index) { + this._rowSortSelected = newIndex; + } + ev.target.focus(); + } } private _moveDown(ev) { ev.stopPropagation(); const index = (ev.target as any).index; - const newIndex = index + 1; - this._move(index, newIndex); + if (!(ev.target as HaAutomationConditionRow).last) { + const newIndex = index + 1; + this._move(index, newIndex); + if (this._rowSortSelected === index) { + this._rowSortSelected = newIndex; + } + ev.target.focus(); + } } private _move(oldIndex: number, newIndex: number) { @@ -390,6 +414,20 @@ export default class HaAutomationCondition extends LitElement { }); } + private _handleDragKeydown(ev: KeyboardEvent) { + if (ev.key === "Enter" || ev.key === " ") { + ev.stopPropagation(); + this._rowSortSelected = + this._rowSortSelected === undefined + ? (ev.target as any).index + : undefined; + } + } + + private _stopSortSelection() { + this._rowSortSelected = undefined; + } + static styles = [ automationRowsStyles, css` diff --git a/src/panels/config/automation/option/ha-automation-option-row.ts b/src/panels/config/automation/option/ha-automation-option-row.ts index ed9c72c32a..a03cce79f9 100644 --- a/src/panels/config/automation/option/ha-automation-option-row.ts +++ b/src/panels/config/automation/option/ha-automation-option-row.ts @@ -17,6 +17,7 @@ import { preventDefaultStopPropagation } from "../../../../common/dom/prevent_de import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter"; import "../../../../components/ha-automation-row"; +import type { HaAutomationRow } from "../../../../components/ha-automation-row"; import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-icon-button"; @@ -63,6 +64,9 @@ export default class HaAutomationOptionRow extends LitElement { @property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar = false; + @property({ type: Boolean, attribute: "sort-selected" }) + public sortSelected = false; + @state() private _expanded = false; @state() private _selected = false; @@ -79,6 +83,9 @@ export default class HaAutomationOptionRow extends LitElement { @query("ha-automation-action") private _actionElement?: HaAutomationAction; + @query("ha-automation-row") + private _automationRowElement?: HaAutomationRow; + private _expandedChanged(ev) { if (ev.currentTarget.id !== "option") { return; @@ -123,14 +130,17 @@ export default class HaAutomationOptionRow extends LitElement { - ${this.option + ${this.option && !this.optionsInSidebar ? html` - ${!this.optionsInSidebar - ? html` - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.rename" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.rename" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.duplicate" - )} - - - ` - : nothing} + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.duplicate" + )} + + + + - ${!this.optionsInSidebar - ? html` - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.type.choose.remove_option" - )} - - ` - : nothing} + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.type.choose.remove_option" + )} + + ` : nothing} @@ -215,6 +216,7 @@ export default class HaAutomationOptionRow extends LitElement { return html`
${this._renderRow()}` : html` @@ -398,9 +401,11 @@ export default class HaAutomationOptionRow extends LitElement { this._collapsed = false; if (this.narrow) { - this.scrollIntoView({ - block: "start", - behavior: "smooth", + requestAnimationFrame(() => { + this.scrollIntoView({ + block: "start", + behavior: "smooth", + }); }); } } @@ -442,6 +447,10 @@ export default class HaAutomationOptionRow extends LitElement { return this._selected; } + public focus() { + this._automationRowElement?.focus(); + } + static get styles(): CSSResultGroup { return [ rowStyles, diff --git a/src/panels/config/automation/option/ha-automation-option.ts b/src/panels/config/automation/option/ha-automation-option.ts index d6c50da7cd..b07f2dbd00 100644 --- a/src/panels/config/automation/option/ha-automation-option.ts +++ b/src/panels/config/automation/option/ha-automation-option.ts @@ -1,7 +1,7 @@ import { mdiDrag, mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; import type { PropertyValues } from "lit"; -import { LitElement, html, nothing } from "lit"; +import { LitElement, css, html, nothing } from "lit"; import { customElement, property, queryAll, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; import { storage } from "../../../../common/decorators/storage"; @@ -37,6 +37,8 @@ export default class HaAutomationOption extends LitElement { @state() private _showReorder = false; + @state() private _rowSortSelected?: number; + @state() @storage({ key: "automationClipboard", @@ -83,7 +85,7 @@ export default class HaAutomationOption extends LitElement { @item-removed=${this._optionRemoved} @item-cloned=${this._optionCloned} > -
+
${repeat( this.options, (option) => this._getKey(option), @@ -102,10 +104,20 @@ export default class HaAutomationOption extends LitElement { @value-changed=${this._optionChanged} .hass=${this.hass} .optionsInSidebar=${this.optionsInSidebar} + .sortSelected=${this._rowSortSelected === idx} + @stop-sort-selection=${this._stopSortSelection} > ${this._showReorder && !this.disabled ? html` -
+
` @@ -207,15 +219,27 @@ export default class HaAutomationOption extends LitElement { private _moveUp(ev) { ev.stopPropagation(); const index = (ev.target as any).index; - const newIndex = index - 1; - this._move(index, newIndex); + if (!(ev.target as HaAutomationOptionRow).first) { + const newIndex = index - 1; + this._move(index, newIndex); + if (this._rowSortSelected === index) { + this._rowSortSelected = newIndex; + } + ev.target.focus(); + } } private _moveDown(ev) { ev.stopPropagation(); const index = (ev.target as any).index; - const newIndex = index + 1; - this._move(index, newIndex); + if (!(ev.target as HaAutomationOptionRow).last) { + const newIndex = index + 1; + this._move(index, newIndex); + if (this._rowSortSelected === index) { + this._rowSortSelected = newIndex; + } + ev.target.focus(); + } } private _move(oldIndex: number, newIndex: number) { @@ -304,7 +328,28 @@ export default class HaAutomationOption extends LitElement { fireEvent(this, "show-default-actions"); }; - static styles = automationRowsStyles; + private _handleDragKeydown(ev: KeyboardEvent) { + if (ev.key === "Enter" || ev.key === " ") { + ev.stopPropagation(); + this._rowSortSelected = + this._rowSortSelected === undefined + ? (ev.target as any).index + : undefined; + } + } + + private _stopSortSelection() { + this._rowSortSelected = undefined; + } + + static styles = [ + automationRowsStyles, + css` + :host([root]) .rows { + padding-right: 8px; + } + `, + ]; } declare global { diff --git a/src/panels/config/automation/sidebar/ha-automation-sidebar-card.ts b/src/panels/config/automation/sidebar/ha-automation-sidebar-card.ts index 8bcf1c0beb..f449e121eb 100644 --- a/src/panels/config/automation/sidebar/ha-automation-sidebar-card.ts +++ b/src/panels/config/automation/sidebar/ha-automation-sidebar-card.ts @@ -67,10 +67,13 @@ export default class HaAutomationSidebarCard extends LitElement { `} `; } diff --git a/src/panels/config/automation/sidebar/ha-automation-sidebar-option.ts b/src/panels/config/automation/sidebar/ha-automation-sidebar-option.ts index b4e7320d04..4e2e68857e 100644 --- a/src/panels/config/automation/sidebar/ha-automation-sidebar-option.ts +++ b/src/panels/config/automation/sidebar/ha-automation-sidebar-option.ts @@ -67,7 +67,7 @@ export default class HaAutomationSidebarOption extends LitElement { "ui.panel.config.automation.editor.actions.duplicate" )} diff --git a/src/panels/config/automation/sidebar/ha-automation-sidebar-trigger.ts b/src/panels/config/automation/sidebar/ha-automation-sidebar-trigger.ts index 3cf6c617fa..a1f31d7e3d 100644 --- a/src/panels/config/automation/sidebar/ha-automation-sidebar-trigger.ts +++ b/src/panels/config/automation/sidebar/ha-automation-sidebar-trigger.ts @@ -193,6 +193,7 @@ export default class HaAutomationSidebarTrigger extends LitElement { .yamlMode=${this.yamlMode} .disabled=${this.disabled} @ui-mode-not-available=${this._handleUiModeNotAvailable} + sidebar > `; diff --git a/src/panels/config/automation/styles.ts b/src/panels/config/automation/styles.ts index 667572edf0..8b820526c3 100644 --- a/src/panels/config/automation/styles.ts +++ b/src/panels/config/automation/styles.ts @@ -54,7 +54,7 @@ export const editorStyles = css` pointer-events: none; } - .card-content { + .card-content.card { padding: 16px; } .card-content.yaml { @@ -69,7 +69,7 @@ export const indentStyle = css` .selector-row, :host([indent]) ha-form { margin-left: 12px; - padding: 12px 24px 16px 16px; + padding: 12px 20px 16px 16px; border-left: 2px solid var(--ha-color-border-neutral-quiet); border-bottom: 2px solid var(--ha-color-border-neutral-quiet); border-radius: 0; @@ -168,7 +168,6 @@ export const manualEditorStyles = css` @media all and (max-width: 870px) { .split-view { gap: 0; - margin-right: -8px; } .sidebar { height: 0; @@ -197,6 +196,9 @@ export const automationRowsStyles = css` flex-direction: column; gap: 16px; } + .rows.no-sidebar { + margin-right: 0; + } .sortable-ghost { background: none; border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg)); @@ -209,9 +211,19 @@ export const automationRowsStyles = css` scroll-margin-top: 48px; } .handle { - padding: 12px; + margin: 4px; + padding: 8px; cursor: move; /* fallback if grab cursor is unsupported */ cursor: grab; + border-radius: var(--ha-border-radius-pill); + } + .handle:focus { + outline: var(--wa-focus-ring); + background: var(--ha-color-fill-neutral-quiet-resting); + } + .handle.active { + outline: var(--wa-focus-ring); + background: var(--ha-color-fill-neutral-normal-active); } .handle ha-svg-icon { pointer-events: none; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-editor.ts b/src/panels/config/automation/trigger/ha-automation-trigger-editor.ts index 3017b8ead4..2ae092d7ba 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-editor.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-editor.ts @@ -29,6 +29,8 @@ export default class HaAutomationTriggerEditor extends LitElement { @property({ type: Boolean, attribute: "show-id" }) public showId = false; + @property({ type: Boolean, attribute: "sidebar" }) public inSidebar = false; + @query("ha-yaml-editor") public yamlEditor?: HaYamlEditor; protected render() { @@ -47,6 +49,7 @@ export default class HaAutomationTriggerEditor extends LitElement { this.trigger.enabled === false && !this.yamlMode), yaml: yamlMode, + card: !this.inSidebar, })} > ${yamlMode diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index c982f01ece..1efd7bad89 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -28,6 +28,7 @@ import { handleStructError } from "../../../../common/structs/handle-errors"; import { debounce } from "../../../../common/util/debounce"; import "../../../../components/ha-alert"; import "../../../../components/ha-automation-row"; +import type { HaAutomationRow } from "../../../../components/ha-automation-row"; import "../../../../components/ha-card"; import "../../../../components/ha-expansion-panel"; import "../../../../components/ha-icon-button"; @@ -115,6 +116,9 @@ export default class HaAutomationTriggerRow extends LitElement { @property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar = false; + @property({ type: Boolean, attribute: "sort-selected" }) + public sortSelected = false; + @state() private _yamlMode = false; @state() private _triggered?: Record; @@ -132,6 +136,9 @@ export default class HaAutomationTriggerRow extends LitElement { @query("ha-automation-trigger-editor") public triggerEditor?: HaAutomationTriggerEditor; + @query("ha-automation-row") + private _automationRowElement?: HaAutomationRow; + @storage({ key: "automationClipboard", state: false, @@ -165,145 +172,142 @@ export default class HaAutomationTriggerRow extends LitElement { - - + ${!this.optionsInSidebar + ? html` + + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.rename" + )} + + - ${!this.optionsInSidebar - ? html` - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.rename" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.edit_id" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.edit_id" - )} - - + - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.duplicate" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.duplicate" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.copy" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.copy" - )} - - + + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.cut" + )} + + - - ${this.hass.localize( - "ui.panel.config.automation.editor.triggers.cut" - )} - - ` - : nothing} + + ${this.hass.localize("ui.panel.config.automation.editor.move_up")} + - - ${this.hass.localize("ui.panel.config.automation.editor.move_up")} - + + ${this.hass.localize( + "ui.panel.config.automation.editor.move_down" + )} + - - ${this.hass.localize("ui.panel.config.automation.editor.move_down")} - + + ${this.hass.localize( + `ui.panel.config.automation.editor.edit_${!yamlMode ? "yaml" : "ui"}` + )} + + - ${!this.optionsInSidebar - ? html` - - ${this.hass.localize( - `ui.panel.config.automation.editor.edit_${!yamlMode ? "yaml" : "ui"}` - )} - - + - - - - ${"enabled" in this.trigger && this.trigger.enabled === false - ? this.hass.localize( - "ui.panel.config.automation.editor.actions.enable" - ) - : this.hass.localize( - "ui.panel.config.automation.editor.actions.disable" - )} - - - + ${"enabled" in this.trigger && this.trigger.enabled === false + ? this.hass.localize( + "ui.panel.config.automation.editor.actions.enable" + ) + : this.hass.localize( + "ui.panel.config.automation.editor.actions.disable" + )} + + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - - ` - : nothing} - + slot="start" + .path=${mdiDelete} + > + + ` + : nothing} ${!this.optionsInSidebar ? html`${this._warnings ? html`${this._selected ? "selected" : nothing}${this._renderRow()} { + this.scrollIntoView({ + block: "start", + behavior: "smooth", + }); }); } } @@ -670,6 +677,10 @@ export default class HaAutomationTriggerRow extends LitElement { return this._selected; } + public focus() { + this._automationRowElement?.focus(); + } + static get styles(): CSSResultGroup { return [ rowStyles, diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 68d8988e73..2cc412f377 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -47,6 +47,8 @@ export default class HaAutomationTrigger extends LitElement { @state() private _showReorder = false; + @state() private _rowSortSelected?: number; + @state() @storage({ key: "automationClipboard", @@ -90,7 +92,7 @@ export default class HaAutomationTrigger extends LitElement { @item-removed=${this._triggerRemoved} @item-cloned=${this._triggerCloned} > -
+
${repeat( this.triggers, (trigger) => this._getKey(trigger), @@ -110,10 +112,20 @@ export default class HaAutomationTrigger extends LitElement { .narrow=${this.narrow} ?highlight=${this.highlightedTriggers?.includes(trg)} .optionsInSidebar=${this.optionsInSidebar} + .sortSelected=${this._rowSortSelected === idx} + @stop-sort-selection=${this._stopSortSelection} > ${this._showReorder && !this.disabled ? html` -
+
` @@ -226,15 +238,27 @@ export default class HaAutomationTrigger extends LitElement { private _moveUp(ev) { ev.stopPropagation(); const index = (ev.target as any).index; - const newIndex = index - 1; - this._move(index, newIndex); + if (!(ev.target as HaAutomationTriggerRow).first) { + const newIndex = index - 1; + this._move(index, newIndex); + if (this._rowSortSelected === index) { + this._rowSortSelected = newIndex; + } + ev.target.focus(); + } } private _moveDown(ev) { ev.stopPropagation(); const index = (ev.target as any).index; - const newIndex = index + 1; - this._move(index, newIndex); + if (!(ev.target as HaAutomationTriggerRow).last) { + const newIndex = index + 1; + this._move(index, newIndex); + if (this._rowSortSelected === index) { + this._rowSortSelected = newIndex; + } + ev.target.focus(); + } } private _move(oldIndex: number, newIndex: number) { @@ -330,6 +354,20 @@ export default class HaAutomationTrigger extends LitElement { }); } + private _handleDragKeydown(ev: KeyboardEvent) { + if (ev.key === "Enter" || ev.key === " ") { + ev.stopPropagation(); + this._rowSortSelected = + this._rowSortSelected === undefined + ? (ev.target as any).index + : undefined; + } + } + + private _stopSortSelection() { + this._rowSortSelected = undefined; + } + static styles = [ automationRowsStyles, css` diff --git a/src/panels/config/script/ha-script-field-row.ts b/src/panels/config/script/ha-script-field-row.ts index 4c69022565..c274255b6d 100644 --- a/src/panels/config/script/ha-script-field-row.ts +++ b/src/panels/config/script/ha-script-field-row.ts @@ -1,8 +1,11 @@ +import { mdiDelete } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { LitElement, css, html, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../../common/dom/fire_event"; +import { preventDefaultStopPropagation } from "../../../common/dom/prevent_default_stop_propagation"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; import type { LocalizeKeys } from "../../../common/translations/localize"; import "../../../components/ha-automation-row"; import "../../../components/ha-card"; @@ -61,6 +64,29 @@ export default class HaScriptFieldRow extends LitElement {

${this.key}

+ + + + ${this.hass.localize( + "ui.panel.config.automation.editor.actions.delete" + )} + + +
{ + this.scrollIntoView({ + block: "start", + behavior: "smooth", + }); }); } } From da08aa7fb074585d36011d3e3c874e89948e168e Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Thu, 28 Aug 2025 14:37:06 +0200 Subject: [PATCH 10/84] Different spelling fixes of user-facing strings (#26745) * Different spelling fixes of user-facing strings * Fix menu "Application credentials" menu item name --- src/translations/en.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index c8b1fd7ff5..2c9aa4646e 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -453,7 +453,7 @@ "media_content_id": "Media content ID", "media_content_type": "Media content type", "media_content_id_detail": "The ID of the content to play. Platform dependent.", - "media_content_type_detail": "The type of the content to play, such as image, music, tv show, video, episode, channel, or playlist." + "media_content_type_detail": "The type of the content to play, such as image, music, TV show, video, episode, channel, or playlist." }, "file": { "upload_failed": "Upload failed", @@ -5447,8 +5447,8 @@ "config_entry": { "application_credentials": { "delete_title": "Application credentials", - "delete_prompt": "Would you like to also delete Application Credentials for this integration?", - "delete_detail": "If you delete them, you will need to enter credentials when setting up the integration again. If you keep them, they will be used automatically when setting up the integration again or may be accessed from the Application Credentials menu.", + "delete_prompt": "Would you like to also delete application credentials for this integration?", + "delete_detail": "If you delete them, you will need to enter credentials when setting up the integration again. If you keep them, they will be used automatically when setting up the integration again or may be accessed from the Application credentials menu.", "delete_error_title": "Deleting application credentials failed", "dismiss": "Keep", "learn_more": "Learn more about application credentials" @@ -6571,7 +6571,7 @@ "in_progress": "We're communicating with the device. This may take some time.", "failed": "The command failed. Additional information may be available in the logs.", "success": "Your device is ready to be added to another Matter platform.", - "scan_code": "With their app, scan the QR code or enter the sharing code below to finish set up.", + "scan_code": "With their app, scan the QR code or enter the sharing code below to finish setup.", "copy_code": "Copy code" } }, From 176924241c287a247800fff695b81959f0d696db Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Thu, 28 Aug 2025 16:32:52 +0300 Subject: [PATCH 11/84] Increase disk usage request timeout (#26748) --- src/data/hassio/host.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/hassio/host.ts b/src/data/hassio/host.ts index 3f927cf787..33e4a2d0a8 100644 --- a/src/data/hassio/host.ts +++ b/src/data/hassio/host.ts @@ -195,6 +195,7 @@ export const fetchHostDisksUsage = async (hass: HomeAssistant) => { type: "supervisor/api", endpoint: "/host/disks/default/usage", method: "get", + timeout: 3600, // seconds. This can take a while }); } From 424d71c55a5b52a7d1a8f7cfefd7242914cdb606 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Thu, 28 Aug 2025 17:05:53 +0100 Subject: [PATCH 12/84] Change loading detailed storage to use `ha-alert` with spinner (#26749) * Change spinner overlay to use `ha-alert` with messaging * Use spinner for icon slot --- .../storage/ha-config-section-storage.ts | 27 ++++++++++--------- src/translations/en.json | 1 + 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/panels/config/storage/ha-config-section-storage.ts b/src/panels/config/storage/ha-config-section-storage.ts index 7357ef937b..5a7e094495 100644 --- a/src/panels/config/storage/ha-config-section-storage.ts +++ b/src/panels/config/storage/ha-config-section-storage.ts @@ -327,8 +327,7 @@ class HaConfigSectionStorage extends LitElement { >${roundWithOneDecimal(freeSpaceGB)} GB`, }); - const chart = html` - - `; - return storageInfo || storageInfo === null - ? chart - : html` -
- ${chart} -
- -
-
- `; + + ${!storageInfo || storageInfo === null + ? html` + + ${this.hass.localize( + "ui.panel.config.storage.loading_detailed" + )}` + : nothing}`; } ); @@ -522,6 +519,10 @@ class HaConfigSectionStorage extends LitElement { ha-icon-next { width: 24px; } + + ha-alert ha-spinner { + --ha-spinner-size: 24px; + } `; } diff --git a/src/translations/en.json b/src/translations/en.json index 2c9aa4646e..1b4b0267c6 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6674,6 +6674,7 @@ "description": "{percent_used} used - {free_space} free", "used_space": "Storage", "detailed_description": "{used} of {total} used", + "loading_detailed": "Loading detailed storage information...", "segments": { "used": "Used space", "free": "Free space", From 810b43760e6e95e15fb637021dcc0b055d5148ac Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 28 Aug 2025 18:51:59 +0200 Subject: [PATCH 13/84] Bumped version to 20250828.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 616ed113f1..4faa404075 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250827.0" +version = "20250828.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" From 8055286a1ff05b139094d52bcadb3a2f534a71d2 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 29 Aug 2025 14:19:13 +0200 Subject: [PATCH 14/84] Use fixed layout for automation sidebar to have scrollbar on the side (#26751) Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> --- .../automation/action/ha-automation-action.ts | 11 +-- .../condition/ha-automation-condition.ts | 11 +-- .../config/automation/ha-automation-editor.ts | 19 ---- .../automation/manual-automation-editor.ts | 57 ++++++------ src/panels/config/automation/styles.ts | 89 +++++++------------ .../trigger/ha-automation-trigger.ts | 11 +-- .../config/script/manual-script-editor.ts | 53 +++++------ 7 files changed, 92 insertions(+), 159 deletions(-) diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index e78f874ec7..67270fd89a 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -1,7 +1,7 @@ import { mdiDrag, mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; import type { PropertyValues } from "lit"; -import { LitElement, css, html, nothing } from "lit"; +import { LitElement, html, nothing } from "lit"; import { customElement, property, queryAll, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; import { storage } from "../../../../common/decorators/storage"; @@ -409,14 +409,7 @@ export default class HaAutomationAction extends LitElement { this._rowSortSelected = undefined; } - static styles = [ - automationRowsStyles, - css` - :host([root]) .rows { - padding-right: 8px; - } - `, - ]; + static styles = automationRowsStyles; } declare global { diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index a521c4d2b4..3809de574e 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -1,7 +1,7 @@ import { mdiDrag, mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; import type { PropertyValues } from "lit"; -import { css, html, LitElement, nothing } from "lit"; +import { html, LitElement, nothing } from "lit"; import { customElement, property, queryAll, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; import { storage } from "../../../../common/decorators/storage"; @@ -428,14 +428,7 @@ export default class HaAutomationCondition extends LitElement { this._rowSortSelected = undefined; } - static styles = [ - automationRowsStyles, - css` - :host([root]) .rows { - padding-right: 8px; - } - `, - ]; + static styles = automationRowsStyles; } declare global { diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 8093caa9e9..e047d874be 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -1181,25 +1181,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin( display: block; } - :not(.yaml-mode) > .alert-wrapper { - position: sticky; - top: -24px; - margin-top: -24px; - z-index: 100; - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; - } - - :not(.yaml-mode) > .alert-wrapper ha-alert { - background-color: var(--card-background-color); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - border-radius: var(--ha-border-radius-sm); - margin-bottom: 0; - } - manual-automation-editor { max-width: 1540px; padding: 0 12px; diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index b22ba74c58..ffea6df3c8 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -23,7 +23,6 @@ import { extractSearchParam, removeSearchParam, } from "../../../common/url/search-params"; -import { computeRTL } from "../../../common/util/compute_rtl"; import "../../../components/ha-button"; import "../../../components/ha-fab"; import "../../../components/ha-icon-button"; @@ -93,9 +92,6 @@ export class HaManualAutomationEditor extends LitElement { @state() private _sidebarConfig?: SidebarConfig; - @query(".content") - private _contentElement?: HTMLDivElement; - @query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar; private _previousConfig?: ManualAutomationConfig; @@ -260,8 +256,7 @@ export class HaManualAutomationEditor extends LitElement { return html`
@@ -269,31 +264,31 @@ export class HaManualAutomationEditor extends LitElement { ${this._renderContent()}
- - - +
+ + + +
+
+ -
`; } @@ -344,8 +339,6 @@ export class HaManualAutomationEditor extends LitElement { private _handleCloseSidebar() { this._sidebarConfig = undefined; - // fix content shift when bottom rows are scrolled into view - this._contentElement?.scrollIntoView(); } private _triggerChanged(ev: CustomEvent): void { diff --git a/src/panels/config/automation/styles.ts b/src/panels/config/automation/styles.ts index 8b820526c3..6779fc178d 100644 --- a/src/panels/config/automation/styles.ts +++ b/src/panels/config/automation/styles.ts @@ -69,7 +69,7 @@ export const indentStyle = css` .selector-row, :host([indent]) ha-form { margin-left: 12px; - padding: 12px 20px 16px 16px; + padding: 12px 0 16px 16px; border-left: 2px solid var(--ha-color-border-neutral-quiet); border-bottom: 2px solid var(--ha-color-border-neutral-quiet); border-radius: 0; @@ -108,77 +108,59 @@ export const saveFabStyles = css` export const manualEditorStyles = css` :host { display: block; + --sidebar-width: 0; + --sidebar-gap: 0; } - .split-view { + .has-sidebar { + --sidebar-width: min(35vw, 500px); + --sidebar-gap: 16px; + } + + .fab-positioner { display: flex; - flex-direction: row; - height: 100%; - position: relative; - gap: 16px; + justify-content: flex-end; } - .split-view.sidebar-hidden { - gap: 0; + .fab-positioner ha-fab { + position: fixed; + right: unset; + left: unset; + bottom: calc(-80px - var(--safe-area-inset-bottom)); + transition: bottom 0.3s; + } + .fab-positioner ha-fab.dirty { + bottom: 16px; } .content-wrapper { - position: relative; - flex: 6; + padding-right: calc(var(--sidebar-width) + var(--sidebar-gap)); + padding-inline-end: calc(var(--sidebar-width) + var(--sidebar-gap)); + padding-inline-start: 0; } .content { - padding: 32px 16px 64px 0; - height: calc(100vh - 153px); - height: calc(100dvh - 153px); - overflow-y: auto; - overflow-x: hidden; + padding-top: 24px; + padding-bottom: 72px; } - .sidebar { - padding: 12px 0; - flex: 4; - height: calc(100vh - 81px); - height: calc(100dvh - 81px); - width: 40%; - } - .split-view.sidebar-hidden .sidebar { - border-color: transparent; - border-width: 0; - overflow: hidden; - flex: 0; - visibility: hidden; - } - - .sidebar.overlay { + ha-automation-sidebar { position: fixed; - bottom: 8px; - right: 8px; - height: calc(100% - 70px); - padding: 0; - z-index: 5; - box-shadow: -8px 0 16px rgba(0, 0, 0, 0.2); + top: calc(var(--header-height) + 16px); + height: calc(-81px + 100dvh); + width: var(--sidebar-width); + display: block; } - .sidebar.overlay.rtl { - right: unset; - left: 8px; + ha-automation-sidebar.hidden { + display: none; } - @media all and (max-width: 870px) { - .split-view { - gap: 0; - } - .sidebar { - height: 0; - width: 0; - flex: 0; - } + .sidebar-positioner { + display: flex; + justify-content: flex-end; } - .split-view.sidebar-hidden .sidebar.overlay { - width: 0; - } .description { margin: 0; } @@ -189,9 +171,6 @@ export const manualEditorStyles = css` export const automationRowsStyles = css` .rows { - padding: 16px 0 16px 16px; - margin: -16px; - margin-right: -20px; display: flex; flex-direction: column; gap: 16px; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 2cc412f377..8b5dc00333 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -1,7 +1,7 @@ import { mdiDrag, mdiPlus } from "@mdi/js"; import deepClone from "deep-clone-simple"; import type { PropertyValues } from "lit"; -import { css, html, LitElement, nothing } from "lit"; +import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { repeat } from "lit/directives/repeat"; import { storage } from "../../../../common/decorators/storage"; @@ -368,14 +368,7 @@ export default class HaAutomationTrigger extends LitElement { this._rowSortSelected = undefined; } - static styles = [ - automationRowsStyles, - css` - :host([root]) .rows { - padding-right: 8px; - } - `, - ]; + static styles = automationRowsStyles; } declare global { diff --git a/src/panels/config/script/manual-script-editor.ts b/src/panels/config/script/manual-script-editor.ts index 439bd6aad3..c363c4f5af 100644 --- a/src/panels/config/script/manual-script-editor.ts +++ b/src/panels/config/script/manual-script-editor.ts @@ -22,7 +22,6 @@ import { extractSearchParam, removeSearchParam, } from "../../../common/url/search-params"; -import { computeRTL } from "../../../common/util/compute_rtl"; import "../../../components/ha-icon-button"; import "../../../components/ha-markdown"; import type { SidebarConfig } from "../../../data/automation"; @@ -196,8 +195,7 @@ export class HaManualScriptEditor extends LitElement { return html`
@@ -205,30 +203,33 @@ export class HaManualScriptEditor extends LitElement { ${this._renderContent()}
- - - +
+
+ + + +
+
+
+ -
`; } From 37f3682ffa26bb467ffa5b2879fe2955a23fc4dd Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Fri, 29 Aug 2025 08:39:06 +0200 Subject: [PATCH 15/84] Automation editor: fix focus handling (#26755) --- src/components/ha-automation-row.ts | 4 +- src/data/automation.ts | 6 +- .../action/ha-automation-action-row.ts | 5 +- .../condition/ha-automation-condition-row.ts | 5 +- .../automation/ha-automation-sidebar.ts | 2 +- .../option/ha-automation-option-row.ts | 5 +- .../trigger/ha-automation-trigger-row.ts | 5 +- .../config/script/ha-script-field-row.ts | 63 ++++++++----------- .../config/script/manual-script-editor.ts | 18 ++++-- 9 files changed, 59 insertions(+), 54 deletions(-) diff --git a/src/components/ha-automation-row.ts b/src/components/ha-automation-row.ts index e15a65bd50..6a0858682c 100644 --- a/src/components/ha-automation-row.ts +++ b/src/components/ha-automation-row.ts @@ -103,7 +103,9 @@ export class HaAutomationRow extends LitElement { } public focus() { - requestAnimationFrame(() => this._rowElement?.focus()); + requestAnimationFrame(() => { + this._rowElement?.focus(); + }); } static styles = css` diff --git a/src/data/automation.ts b/src/data/automation.ts index 00e69fadff..f629d73a44 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -558,11 +558,11 @@ export interface AutomationClipboard { export interface BaseSidebarConfig { toggleYamlMode: () => boolean; delete: () => void; + close: (focus?: boolean) => void; } export interface TriggerSidebarConfig extends BaseSidebarConfig { save: (value: Trigger) => void; - close: () => void; rename: () => void; disable: () => void; duplicate: () => void; @@ -575,7 +575,6 @@ export interface TriggerSidebarConfig extends BaseSidebarConfig { export interface ConditionSidebarConfig extends BaseSidebarConfig { save: (value: Condition) => void; - close: () => void; rename: () => void; disable: () => void; test: () => void; @@ -589,7 +588,6 @@ export interface ConditionSidebarConfig extends BaseSidebarConfig { export interface ActionSidebarConfig extends BaseSidebarConfig { save: (value: Action) => void; - close: () => void; rename: () => void; disable: () => void; duplicate: () => void; @@ -604,7 +602,6 @@ export interface ActionSidebarConfig extends BaseSidebarConfig { } export interface OptionSidebarConfig extends BaseSidebarConfig { - close: () => void; rename: () => void; duplicate: () => void; defaultOption?: boolean; @@ -612,7 +609,6 @@ export interface OptionSidebarConfig extends BaseSidebarConfig { export interface ScriptFieldSidebarConfig extends BaseSidebarConfig { save: (value: Field) => void; - close: () => void; config: { field: Field; selector: boolean; diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 034b55073a..d34bee9a05 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -676,9 +676,12 @@ export default class HaAutomationActionRow extends LitElement { save: (value) => { fireEvent(this, "value-changed", { value }); }, - close: () => { + close: (focus?: boolean) => { this._selected = false; fireEvent(this, "close-sidebar"); + if (focus) { + this.focus(); + } }, rename: () => { this._renameAction(); diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 8b14e08f0a..bda6801e42 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -648,9 +648,12 @@ export default class HaAutomationConditionRow extends LitElement { save: (value) => { fireEvent(this, "value-changed", { value }); }, - close: () => { + close: (focus?: boolean) => { this._selected = false; fireEvent(this, "close-sidebar"); + if (focus) { + this.focus(); + } }, rename: () => { this._renameCondition(); diff --git a/src/panels/config/automation/ha-automation-sidebar.ts b/src/panels/config/automation/ha-automation-sidebar.ts index 14a60b0319..a62ee4d440 100644 --- a/src/panels/config/automation/ha-automation-sidebar.ts +++ b/src/panels/config/automation/ha-automation-sidebar.ts @@ -193,7 +193,7 @@ export default class HaAutomationSidebar extends LitElement { } private _closeSidebar() { - this.config?.close(); + this.config?.close(true); } private _toggleYamlMode = () => { diff --git a/src/panels/config/automation/option/ha-automation-option-row.ts b/src/panels/config/automation/option/ha-automation-option-row.ts index a03cce79f9..eee47b8cb4 100644 --- a/src/panels/config/automation/option/ha-automation-option-row.ts +++ b/src/panels/config/automation/option/ha-automation-option-row.ts @@ -385,9 +385,12 @@ export default class HaAutomationOptionRow extends LitElement { public openSidebar(): void { fireEvent(this, "open-sidebar", { - close: () => { + close: (focus?: boolean) => { this._selected = false; fireEvent(this, "close-sidebar"); + if (focus) { + this.focus(); + } }, rename: () => { this._renameOption(); diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 1efd7bad89..926c25bb2f 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -482,9 +482,12 @@ export default class HaAutomationTriggerRow extends LitElement { save: (value) => { fireEvent(this, "value-changed", { value }); }, - close: () => { + close: (focus?: boolean) => { this._selected = false; fireEvent(this, "close-sidebar"); + if (focus) { + this.focus(); + } }, rename: () => { this._renameTrigger(); diff --git a/src/panels/config/script/ha-script-field-row.ts b/src/panels/config/script/ha-script-field-row.ts index c274255b6d..867ca2f958 100644 --- a/src/panels/config/script/ha-script-field-row.ts +++ b/src/panels/config/script/ha-script-field-row.ts @@ -1,17 +1,12 @@ -import { mdiDelete } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { LitElement, css, html, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../../common/dom/fire_event"; -import { preventDefaultStopPropagation } from "../../../common/dom/prevent_default_stop_propagation"; -import { stopPropagation } from "../../../common/dom/stop_propagation"; import type { LocalizeKeys } from "../../../common/translations/localize"; import "../../../components/ha-automation-row"; +import type { HaAutomationRow } from "../../../components/ha-automation-row"; import "../../../components/ha-card"; -import "../../../components/ha-icon-button"; -import "../../../components/ha-md-button-menu"; -import "../../../components/ha-md-menu-item"; import type { ScriptFieldSidebarConfig } from "../../../data/automation"; import type { Field } from "../../../data/script"; import { SELECTOR_SELECTOR_BUILDING_BLOCKS } from "../../../data/selector/selector_selector"; @@ -50,6 +45,12 @@ export default class HaScriptFieldRow extends LitElement { @query("ha-script-field-selector-editor") private _selectorEditor?: HaScriptFieldSelectorEditor; + @query("ha-automation-row:first-of-type") + private _fieldRowElement?: HaAutomationRow; + + @query(".selector-row ha-automation-row") + private _selectorRowElement?: HaAutomationRow; + protected render() { return html` @@ -64,29 +65,6 @@ export default class HaScriptFieldRow extends LitElement {

${this.key}

- - - - ${this.hass.localize( - "ui.panel.config.automation.editor.actions.delete" - )} - - -
{ fireEvent(this, "value-changed", { value }); }, - close: () => { + close: (focus?: boolean) => { if (selectorEditor) { this._selectorRowSelected = false; + if (focus) { + this.focusSelector(); + } } else { this._selected = false; + if (focus) { + this.focus(); + } } fireEvent(this, "close-sidebar"); }, @@ -280,15 +264,19 @@ export default class HaScriptFieldRow extends LitElement { }); }; + public focus() { + this._fieldRowElement?.focus(); + } + + public focusSelector() { + this._selectorRowElement?.focus(); + } + static get styles(): CSSResultGroup { return [ haStyle, indentStyle, css` - ha-button-menu, - ha-icon-button { - --mdc-theme-text-primary-on-background: var(--primary-text-color); - } .disabled { opacity: 0.5; pointer-events: none; @@ -334,9 +322,6 @@ export default class HaScriptFieldRow extends LitElement { ); } - ha-md-menu-item[disabled] { - --mdc-theme-text-primary-on-background: var(--disabled-text-color); - } .warning ul { margin: 4px 0; } @@ -352,6 +337,10 @@ export default class HaScriptFieldRow extends LitElement { border-color: var(--state-inactive-color); box-shadow: var(--shadow-default), var(--shadow-focus); } + + .selector-row { + padding: 12px 0 16px 16px; + } `, ]; } diff --git a/src/panels/config/script/manual-script-editor.ts b/src/panels/config/script/manual-script-editor.ts index c363c4f5af..b8cc9ca973 100644 --- a/src/panels/config/script/manual-script-editor.ts +++ b/src/panels/config/script/manual-script-editor.ts @@ -37,6 +37,7 @@ import { showToast } from "../../../util/toast"; import "../automation/action/ha-automation-action"; import type HaAutomationAction from "../automation/action/ha-automation-action"; import "../automation/ha-automation-sidebar"; +import type HaAutomationSidebar from "../automation/ha-automation-sidebar"; import { showPasteReplaceDialog } from "../automation/paste-replace-dialog/show-dialog-paste-replace"; import { manualEditorStyles, saveFabStyles } from "../automation/styles"; import "./ha-script-fields"; @@ -68,17 +69,19 @@ export class HaManualScriptEditor extends LitElement { @property({ attribute: false }) public dirty = false; - @query("ha-script-fields") - private _scriptFields?: HaScriptFields; - - private _openFields = false; - @state() private _pastedConfig?: ScriptConfig; @state() private _sidebarConfig?: SidebarConfig; + @query("ha-script-fields") + private _scriptFields?: HaScriptFields; + + @query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar; + private _previousConfig?: ScriptConfig; + private _openFields = false; + public addFields() { this._openFields = true; fireEvent(this, "value-changed", { @@ -456,10 +459,13 @@ export class HaManualScriptEditor extends LitElement { }); } - private _openSidebar(ev: CustomEvent) { + private async _openSidebar(ev: CustomEvent) { // deselect previous selected row this._sidebarConfig?.close?.(); this._sidebarConfig = ev.detail; + + await this._sidebarElement?.updateComplete; + this._sidebarElement?.focus(); } private _sidebarConfigChanged(ev: CustomEvent<{ value: SidebarConfig }>) { From f494a6453a10133f1c051e831ed5f0ad92daebe0 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Fri, 29 Aug 2025 09:59:31 +0200 Subject: [PATCH 16/84] Improve OAuth setup explanation (#26758) * Improve OAuth setup explanation * Add "your" * Include "application" in headline --- src/translations/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index 1b4b0267c6..96232402aa 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5647,10 +5647,10 @@ }, "application_credentials": { "caption": "Application credentials", - "description": "Manage the OAuth application credentials used by Integrations", + "description": "Manage the OAuth application credentials used by integrations", "editor": { - "caption": "Add credentials", - "description": "OAuth is used to grant Home Assistant access to information on other websites without giving a password. This mechanism is used by companies such as Spotify, Google, Withings, Microsoft, and Twitter.", + "caption": "Add application credentials", + "description": "OAuth is used to grant Home Assistant secure delegated access to information on other websites without revealing your personal credentials.", "missing_credentials": "Setting up {integration} requires configuring application credentials.", "missing_credentials_domain_link": "View {integration} documentation", "view_documentation": "View application credentials documentation", From 7674eee0fbe71d376218894ac2b6d5796acfd07a Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 29 Aug 2025 10:05:05 +0200 Subject: [PATCH 17/84] Fix alert z-index for automation and script (#26759) --- src/panels/config/script/ha-script-editor.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 27199efb9a..4f419cdf6b 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -1104,12 +1104,13 @@ export class HaScriptEditor extends SubscribeMixin( top: -24px; margin-top: -24px; margin-bottom: 8px; - z-index: 100; + z-index: 1; width: 100%; display: flex; flex-direction: column; align-items: center; gap: 8px; + pointer-events: none; } .alert-wrapper ha-alert { @@ -1117,6 +1118,7 @@ export class HaScriptEditor extends SubscribeMixin( box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); border-radius: var(--ha-border-radius-sm); margin-bottom: 0; + pointer-events: auto; } manual-script-editor { From f7ec8650ebb8da3b4d6d66e88cb37846755f1d9b Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 29 Aug 2025 14:17:55 +0200 Subject: [PATCH 18/84] Add translations for home dashboard (#26763) --- .../hui-home-dashboard-strategy-editor.ts | 8 +++++-- .../strategies/home/helpers/home-summaries.ts | 10 +++++--- .../home/home-area-view-strategy.ts | 23 ++++++++++++------- .../home/home-climate-view-strategy.ts | 10 ++++++-- .../home/home-dashboard-strategy.ts | 13 +++++++---- .../home/home-lights-view-strategy.ts | 10 ++++++-- .../home/home-main-view-strategy.ts | 19 ++++++++------- .../home/home-media-players-view-strategy.ts | 10 ++++++-- .../home/home-security-view-strategy.ts | 10 ++++++-- src/translations/en.json | 18 +++++++++++++++ 10 files changed, 97 insertions(+), 34 deletions(-) diff --git a/src/panels/lovelace/strategies/home/editor/hui-home-dashboard-strategy-editor.ts b/src/panels/lovelace/strategies/home/editor/hui-home-dashboard-strategy-editor.ts index fd5c1c8f75..9886afdb73 100644 --- a/src/panels/lovelace/strategies/home/editor/hui-home-dashboard-strategy-editor.ts +++ b/src/panels/lovelace/strategies/home/editor/hui-home-dashboard-strategy-editor.ts @@ -29,8 +29,12 @@ export class HuiHomeDashboardStrategyEditor = { +export const HOME_SUMMARIES_ICONS: Record = { lights: "mdi:lamps", climate: "mdi:home-thermometer", security: "mdi:security", media_players: "mdi:multimedia", }; -export const HOME_SUMMARIES_FILTERS: Record = { +export const HOME_SUMMARIES_FILTERS: Record = { lights: [{ domain: "light", entity_category: "none" }], climate: [ { domain: "climate", entity_category: "none" }, @@ -90,3 +91,6 @@ export const findEntities = ( return results; }; + +export const getSummaryLabel = (localize: LocalizeFunc, summary: HomeSummary) => + localize(`ui.panel.lovelace.strategy.home.summary_list.${summary}`); diff --git a/src/panels/lovelace/strategies/home/home-area-view-strategy.ts b/src/panels/lovelace/strategies/home/home-area-view-strategy.ts index 24ca6a4f96..7f091a0f46 100644 --- a/src/panels/lovelace/strategies/home/home-area-view-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-area-view-strategy.ts @@ -14,10 +14,11 @@ import type { HeadingCardConfig } from "../../cards/types"; import { computeAreaTileCardConfig } from "../areas/helpers/areas-strategy-helper"; import { findEntities, + getSummaryLabel, HOME_SUMMARIES, HOME_SUMMARIES_FILTERS, HOME_SUMMARIES_ICONS, - type HomeSummaries, + type HomeSummary, } from "./helpers/home-summaries"; export interface HomeAreaViewStrategyConfig { @@ -96,7 +97,7 @@ export class HomeAreaViewStrategy extends ReactiveElement { acc[summary] = findEntities(areaEntities, filterFunctions); return acc; }, - {} as Record + {} as Record ); const { @@ -110,7 +111,11 @@ export class HomeAreaViewStrategy extends ReactiveElement { sections.push({ type: "grid", cards: [ - computeHeadingCard("Lights", HOME_SUMMARIES_ICONS.lights, "lights"), + computeHeadingCard( + getSummaryLabel(hass.localize, "lights"), + HOME_SUMMARIES_ICONS.lights, + "lights" + ), ...lights.map(computeTileCard), ], }); @@ -121,7 +126,7 @@ export class HomeAreaViewStrategy extends ReactiveElement { type: "grid", cards: [ computeHeadingCard( - "Climate", + getSummaryLabel(hass.localize, "climate"), HOME_SUMMARIES_ICONS.climate, "climate" ), @@ -135,7 +140,7 @@ export class HomeAreaViewStrategy extends ReactiveElement { type: "grid", cards: [ computeHeadingCard( - "Security", + getSummaryLabel(hass.localize, "security"), HOME_SUMMARIES_ICONS.security, "security" ), @@ -149,7 +154,7 @@ export class HomeAreaViewStrategy extends ReactiveElement { type: "grid", cards: [ computeHeadingCard( - "Media players", + getSummaryLabel(hass.localize, "media_players"), HOME_SUMMARIES_ICONS.media_players, "media-players" ), @@ -229,9 +234,11 @@ export class HomeAreaViewStrategy extends ReactiveElement { const device = hass.devices[deviceId]; let heading = ""; if (device) { - heading = computeDeviceName(device) || "Unnamed device"; + heading = + computeDeviceName(device) || + hass.localize("ui.panel.lovelace.strategy.home.unamed_device"); } else { - heading = "Others"; + heading = hass.localize("ui.panel.lovelace.strategy.home.others"); } deviceSections.push({ diff --git a/src/panels/lovelace/strategies/home/home-climate-view-strategy.ts b/src/panels/lovelace/strategies/home/home-climate-view-strategy.ts index 9d677dd391..456d67fa9e 100644 --- a/src/panels/lovelace/strategies/home/home-climate-view-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-climate-view-strategy.ts @@ -95,7 +95,10 @@ export class HomeClimateViewStrategy extends ReactiveElement { cards: [ { type: "heading", - heading: floorCount > 1 ? floor.name : "Areas", + heading: + floorCount > 1 + ? floor.name + : hass.localize("ui.panel.lovelace.strategy.home.areas"), }, ], }; @@ -116,7 +119,10 @@ export class HomeClimateViewStrategy extends ReactiveElement { cards: [ { type: "heading", - heading: floorCount > 1 ? "Other areas" : "Areas", + heading: + floorCount > 1 + ? hass.localize("ui.panel.lovelace.strategy.home.other_areas") + : hass.localize("ui.panel.lovelace.strategy.home.areas"), }, ], }; diff --git a/src/panels/lovelace/strategies/home/home-dashboard-strategy.ts b/src/panels/lovelace/strategies/home/home-dashboard-strategy.ts index 35e9da886c..10bf4a9d41 100644 --- a/src/panels/lovelace/strategies/home/home-dashboard-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-dashboard-strategy.ts @@ -6,7 +6,10 @@ import type { LovelaceViewRawConfig } from "../../../../data/lovelace/config/vie import type { HomeAssistant } from "../../../../types"; import { getAreas } from "../areas/helpers/areas-strategy-helper"; import type { LovelaceStrategyEditor } from "../types"; -import { HOME_SUMMARIES_ICONS } from "./helpers/home-summaries"; +import { + getSummaryLabel, + HOME_SUMMARIES_ICONS, +} from "./helpers/home-summaries"; import type { HomeAreaViewStrategyConfig } from "./home-area-view-strategy"; import type { HomeMainViewStrategyConfig } from "./home-main-view-strategy"; @@ -60,7 +63,7 @@ export class HomeDashboardStrategy extends ReactiveElement { }); const lightView = { - title: "Lights", + title: getSummaryLabel(hass.localize, "lights"), path: "lights", subview: true, strategy: { @@ -70,7 +73,7 @@ export class HomeDashboardStrategy extends ReactiveElement { } satisfies LovelaceViewRawConfig; const climateView = { - title: "Climate", + title: getSummaryLabel(hass.localize, "climate"), path: "climate", subview: true, strategy: { @@ -80,7 +83,7 @@ export class HomeDashboardStrategy extends ReactiveElement { } satisfies LovelaceViewRawConfig; const securityView = { - title: "Security", + title: getSummaryLabel(hass.localize, "security"), path: "security", subview: true, strategy: { @@ -90,7 +93,7 @@ export class HomeDashboardStrategy extends ReactiveElement { } satisfies LovelaceViewRawConfig; const mediaPlayersView = { - title: "Media players", + title: getSummaryLabel(hass.localize, "media_players"), path: "media-players", subview: true, strategy: { diff --git a/src/panels/lovelace/strategies/home/home-lights-view-strategy.ts b/src/panels/lovelace/strategies/home/home-lights-view-strategy.ts index 41681ba74b..b502779a6a 100644 --- a/src/panels/lovelace/strategies/home/home-lights-view-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-lights-view-strategy.ts @@ -89,7 +89,10 @@ export class HomeLightsViewStrategy extends ReactiveElement { cards: [ { type: "heading", - heading: floorCount > 1 ? floor.name : "Areas", + heading: + floorCount > 1 + ? floor.name + : hass.localize("ui.panel.lovelace.strategy.home.areas"), }, ], }; @@ -110,7 +113,10 @@ export class HomeLightsViewStrategy extends ReactiveElement { cards: [ { type: "heading", - heading: floorCount > 1 ? "Other areas" : "Areas", + heading: + floorCount > 1 + ? hass.localize("ui.panel.lovelace.strategy.home.other_areas") + : hass.localize("ui.panel.lovelace.strategy.home.areas"), }, ], }; diff --git a/src/panels/lovelace/strategies/home/home-main-view-strategy.ts b/src/panels/lovelace/strategies/home/home-main-view-strategy.ts index 43be46e482..ce2fb6aefe 100644 --- a/src/panels/lovelace/strategies/home/home-main-view-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-main-view-strategy.ts @@ -15,7 +15,10 @@ import type { WeatherForecastCardConfig, } from "../../cards/types"; import { getAreas } from "../areas/helpers/areas-strategy-helper"; -import { HOME_SUMMARIES_ICONS } from "./helpers/home-summaries"; +import { + getSummaryLabel, + HOME_SUMMARIES_ICONS, +} from "./helpers/home-summaries"; export interface HomeMainViewStrategyConfig { type: "home-main"; @@ -63,7 +66,7 @@ export class HomeMainViewStrategy extends ReactiveElement { { type: "heading", heading_style: "title", - heading: "Areas", + heading: hass.localize("ui.panel.lovelace.strategy.home.areas"), }, ...areas.map((area) => computeAreaCard(area.area_id, hass) @@ -108,12 +111,12 @@ export class HomeMainViewStrategy extends ReactiveElement { cards: [ { type: "heading", - heading: "Summaries", + heading: hass.localize("ui.panel.lovelace.strategy.home.summaries"), }, { type: "button", icon: HOME_SUMMARIES_ICONS.lights, - name: "Lights", + name: getSummaryLabel(hass.localize, "lights"), icon_height: "24px", grid_options: { rows: 2, @@ -127,7 +130,7 @@ export class HomeMainViewStrategy extends ReactiveElement { { type: "button", icon: HOME_SUMMARIES_ICONS.climate, - name: "Climate", + name: getSummaryLabel(hass.localize, "climate"), icon_height: "30px", grid_options: { rows: 2, @@ -141,7 +144,7 @@ export class HomeMainViewStrategy extends ReactiveElement { { type: "button", icon: HOME_SUMMARIES_ICONS.security, - name: "Security", + name: getSummaryLabel(hass.localize, "security"), icon_height: "30px", grid_options: { rows: 2, @@ -155,7 +158,7 @@ export class HomeMainViewStrategy extends ReactiveElement { { type: "button", icon: HOME_SUMMARIES_ICONS.media_players, - name: "Media Players", + name: getSummaryLabel(hass.localize, "media_players"), icon_height: "30px", grid_options: { rows: 2, @@ -232,7 +235,7 @@ export class HomeMainViewStrategy extends ReactiveElement { card: { type: "markdown", text_only: true, - content: "## Welcome {{user}}", + content: `## ${hass.localize("ui.panel.lovelace.strategy.home.welcome_user", { user: "{{ user }}" })}`, } satisfies MarkdownCardConfig, }, }; diff --git a/src/panels/lovelace/strategies/home/home-media-players-view-strategy.ts b/src/panels/lovelace/strategies/home/home-media-players-view-strategy.ts index 48c7617ecb..7cb6c9ec40 100644 --- a/src/panels/lovelace/strategies/home/home-media-players-view-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-media-players-view-strategy.ts @@ -87,7 +87,10 @@ export class HomeMMediaPlayersViewStrategy extends ReactiveElement { cards: [ { type: "heading", - heading: floorCount > 1 ? floor.name : "Areas", + heading: + floorCount > 1 + ? floor.name + : hass.localize("ui.panel.lovelace.strategy.home.areas"), }, ], }; @@ -108,7 +111,10 @@ export class HomeMMediaPlayersViewStrategy extends ReactiveElement { cards: [ { type: "heading", - heading: floorCount > 1 ? "Other areas" : "Areas", + heading: + floorCount > 1 + ? hass.localize("ui.panel.lovelace.strategy.home.other_areas") + : hass.localize("ui.panel.lovelace.strategy.home.areas"), }, ], }; diff --git a/src/panels/lovelace/strategies/home/home-security-view-strategy.ts b/src/panels/lovelace/strategies/home/home-security-view-strategy.ts index 759df98206..fd3916542e 100644 --- a/src/panels/lovelace/strategies/home/home-security-view-strategy.ts +++ b/src/panels/lovelace/strategies/home/home-security-view-strategy.ts @@ -95,7 +95,10 @@ export class HomeSecurityViewStrategy extends ReactiveElement { cards: [ { type: "heading", - heading: floorCount > 1 ? floor.name : "Areas", + heading: + floorCount > 1 + ? floor.name + : hass.localize("ui.panel.lovelace.strategy.home.areas"), }, ], }; @@ -116,7 +119,10 @@ export class HomeSecurityViewStrategy extends ReactiveElement { cards: [ { type: "heading", - heading: floorCount > 1 ? "Other areas" : "Areas", + heading: + floorCount > 1 + ? hass.localize("ui.panel.lovelace.strategy.home.other_areas") + : hass.localize("ui.panel.lovelace.strategy.home.areas"), }, ], }; diff --git a/src/translations/en.json b/src/translations/en.json index 96232402aa..605aa94753 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6817,6 +6817,20 @@ }, "other_areas": "Other areas", "areas": "Areas" + }, + "home": { + "summary_list": { + "climate": "Climate", + "lights": "Lights", + "security": "Security", + "media_players": "Media players" + }, + "welcome_user": "Welcome {user}", + "summaries": "Summaries", + "areas": "Areas", + "other_areas": "Other areas", + "unamed_device": "Unnamed device", + "others": "Others" } }, "cards": { @@ -8176,6 +8190,10 @@ "no_entities": "No entities in this group, the section will not be displayed", "use_compact_card": "Use compact card", "use_large_card": "Use large card" + }, + "home": { + "favorite_entities": "Favorite entities", + "add_favorite_entity": "Add favorite entity" } }, "view": { From 7c15633f6dca9118a9085cae0d9a6141ca3dd065 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Fri, 29 Aug 2025 07:08:11 -0700 Subject: [PATCH 19/84] Don't use context for media selector with 'accept' (#26773) --- .../ha-selector/ha-selector-media.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/ha-selector/ha-selector-media.ts b/src/components/ha-selector/ha-selector-media.ts index 43632feb85..fae8d781ac 100644 --- a/src/components/ha-selector/ha-selector-media.ts +++ b/src/components/ha-selector/ha-selector-media.ts @@ -53,9 +53,15 @@ export class HaMediaSelector extends LitElement { private _contextEntities: string[] | undefined; + private get _hasAccept(): boolean { + return !!this.selector?.media?.accept?.length; + } + willUpdate(changedProps: PropertyValues) { if (changedProps.has("context")) { - this._contextEntities = ensureArray(this.context?.filter_entity); + if (!this._hasAccept) { + this._contextEntities = ensureArray(this.context?.filter_entity); + } } if (changedProps.has("value")) { @@ -99,10 +105,8 @@ export class HaMediaSelector extends LitElement { (stateObj && supportsFeature(stateObj, MediaPlayerEntityFeature.BROWSE_MEDIA)); - const hasAccept = this.selector?.media?.accept?.length; - return html` - ${hasAccept || + ${this._hasAccept || (this._contextEntities && this._contextEntities.length <= 1) ? nothing : html` @@ -148,7 +152,7 @@ export class HaMediaSelector extends LitElement { : this.value.metadata?.title || this.value.media_content_id} @click=${this._pickMedia} @keydown=${this._handleKeyDown} - class=${this.disabled || (!entityId && !hasAccept) + class=${this.disabled || (!entityId && !this._hasAccept) ? "disabled" : ""} > @@ -215,7 +219,7 @@ export class HaMediaSelector extends LitElement { private _entityChanged(ev: CustomEvent) { ev.stopPropagation(); - if (this.context?.filter_entity) { + if (!this._hasAccept && this.context?.filter_entity) { fireEvent(this, "value-changed", { value: { media_content_id: "", @@ -257,7 +261,7 @@ export class HaMediaSelector extends LitElement { media_content_type: id.media_content_type, media_content_id: id.media_content_id, })), - ...(this.context?.filter_entity + ...(!this._hasAccept && this.context?.filter_entity ? { browse_entity_id: this._getActiveEntityId() } : {}), }, From 3837b3e630d3bc2210cf6250a3a6d027f3831871 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 29 Aug 2025 17:48:01 +0200 Subject: [PATCH 20/84] Bumped version to 20250829.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4faa404075..c5b7ce6891 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250828.0" +version = "20250829.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend" From cbacde12faad5da1e6f01a6c36f3d69466b6134d Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Mon, 1 Sep 2025 13:26:17 +0200 Subject: [PATCH 21/84] Automation editor: fix yaml editor and editor switch (#26772) --- src/data/automation.ts | 5 ++- .../action/ha-automation-action-editor.ts | 2 +- .../action/ha-automation-action-row.ts | 2 +- .../ha-automation-condition-editor.ts | 3 +- .../condition/ha-automation-condition-row.ts | 2 +- .../automation/ha-automation-sidebar.ts | 19 ++++++---- .../automation/manual-automation-editor.ts | 4 ++ .../option/ha-automation-option-row.ts | 1 - .../sidebar/ha-automation-sidebar-action.ts | 37 +++++++++++++------ .../ha-automation-sidebar-condition.ts | 35 ++++++++++++------ ...utomation-sidebar-script-field-selector.ts | 29 +++++++++++---- .../ha-automation-sidebar-script-field.ts | 33 ++++++++++++----- .../sidebar/ha-automation-sidebar-trigger.ts | 37 +++++++++++++------ .../trigger/ha-automation-trigger-editor.ts | 2 +- .../trigger/ha-automation-trigger-row.ts | 2 +- .../config/script/ha-script-field-editor.ts | 9 ++++- .../config/script/ha-script-field-row.ts | 2 +- .../script/ha-script-field-selector-editor.ts | 2 +- .../config/script/manual-script-editor.ts | 4 ++ 19 files changed, 156 insertions(+), 74 deletions(-) diff --git a/src/data/automation.ts b/src/data/automation.ts index f629d73a44..e79c34b62f 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -556,7 +556,6 @@ export interface AutomationClipboard { } export interface BaseSidebarConfig { - toggleYamlMode: () => boolean; delete: () => void; close: (focus?: boolean) => void; } @@ -568,6 +567,7 @@ export interface TriggerSidebarConfig extends BaseSidebarConfig { duplicate: () => void; cut: () => void; copy: () => void; + toggleYamlMode: () => void; config: Trigger; yamlMode: boolean; uiSupported: boolean; @@ -581,6 +581,7 @@ export interface ConditionSidebarConfig extends BaseSidebarConfig { duplicate: () => void; cut: () => void; copy: () => void; + toggleYamlMode: () => void; config: Condition; yamlMode: boolean; uiSupported: boolean; @@ -594,6 +595,7 @@ export interface ActionSidebarConfig extends BaseSidebarConfig { cut: () => void; copy: () => void; run: () => void; + toggleYamlMode: () => void; config: { action: Action; }; @@ -615,6 +617,7 @@ export interface ScriptFieldSidebarConfig extends BaseSidebarConfig { key: string; excludeKeys: string[]; }; + toggleYamlMode: () => void; yamlMode: boolean; } diff --git a/src/panels/config/automation/action/ha-automation-action-editor.ts b/src/panels/config/automation/action/ha-automation-action-editor.ts index d2756e8a1a..97cea3f6c1 100644 --- a/src/panels/config/automation/action/ha-automation-action-editor.ts +++ b/src/panels/config/automation/action/ha-automation-action-editor.ts @@ -97,7 +97,7 @@ export default class HaAutomationActionEditor extends LitElement { if (!ev.detail.isValid) { return; } - fireEvent(this, "value-changed", { + fireEvent(this, "yaml-changed", { value: migrateAutomationAction(ev.detail.value), }); } diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index d34bee9a05..b8cffcc161 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -688,7 +688,7 @@ export default class HaAutomationActionRow extends LitElement { }, toggleYamlMode: () => { this._toggleYamlMode(); - return this._yamlMode; + this.openSidebar(); }, disable: this._onDisable, delete: this._onDelete, diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index 92b4927e1d..614ec520e3 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -103,8 +103,7 @@ export default class HaAutomationConditionEditor extends LitElement { if (!ev.detail.isValid) { return; } - // @ts-ignore - fireEvent(this, "value-changed", { value: ev.detail.value, yaml: true }); + fireEvent(this, "yaml-changed", { value: ev.detail.value }); } private _onUiChanged(ev: CustomEvent) { diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index bda6801e42..d025377af5 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -660,7 +660,7 @@ export default class HaAutomationConditionRow extends LitElement { }, toggleYamlMode: () => { this._toggleYamlMode(); - return this._yamlMode; + this.openSidebar(); }, disable: this._onDisable, delete: this._onDelete, diff --git a/src/panels/config/automation/ha-automation-sidebar.ts b/src/panels/config/automation/ha-automation-sidebar.ts index a62ee4d440..3c184c2372 100644 --- a/src/panels/config/automation/ha-automation-sidebar.ts +++ b/src/panels/config/automation/ha-automation-sidebar.ts @@ -1,6 +1,5 @@ import { css, html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/ha-bottom-sheet"; import type { HaBottomSheet } from "../../../components/ha-bottom-sheet"; import { @@ -34,6 +33,8 @@ export default class HaAutomationSidebar extends LitElement { @property({ type: Boolean }) public narrow = false; + @property({ attribute: "sidebar-key" }) public sidebarKey?: string; + @state() private _yamlMode = false; @query("ha-bottom-sheet") private _bottomSheetElement?: HaBottomSheet; @@ -52,6 +53,7 @@ export default class HaAutomationSidebar extends LitElement { .narrow=${this.narrow} .disabled=${this.disabled} .yamlMode=${this._yamlMode} + .sidebarKey=${this.sidebarKey} @toggle-yaml-mode=${this._toggleYamlMode} @close-sidebar=${this._handleCloseSidebar} > @@ -67,6 +69,7 @@ export default class HaAutomationSidebar extends LitElement { .narrow=${this.narrow} .disabled=${this.disabled} .yamlMode=${this._yamlMode} + .sidebarKey=${this.sidebarKey} @toggle-yaml-mode=${this._toggleYamlMode} @close-sidebar=${this._handleCloseSidebar} > @@ -82,6 +85,7 @@ export default class HaAutomationSidebar extends LitElement { .narrow=${this.narrow} .disabled=${this.disabled} .yamlMode=${this._yamlMode} + .sidebarKey=${this.sidebarKey} @toggle-yaml-mode=${this._toggleYamlMode} @close-sidebar=${this._handleCloseSidebar} > @@ -110,6 +114,7 @@ export default class HaAutomationSidebar extends LitElement { .narrow=${this.narrow} .disabled=${this.disabled} .yamlMode=${this._yamlMode} + .sidebarKey=${this.sidebarKey} @toggle-yaml-mode=${this._toggleYamlMode} @close-sidebar=${this._handleCloseSidebar} > @@ -125,6 +130,7 @@ export default class HaAutomationSidebar extends LitElement { .narrow=${this.narrow} .disabled=${this.disabled} .yamlMode=${this._yamlMode} + .sidebarKey=${this.sidebarKey} @toggle-yaml-mode=${this._toggleYamlMode} @close-sidebar=${this._handleCloseSidebar} > @@ -197,13 +203,7 @@ export default class HaAutomationSidebar extends LitElement { } private _toggleYamlMode = () => { - this._yamlMode = this.config!.toggleYamlMode(); - fireEvent(this, "value-changed", { - value: { - ...this.config, - yamlMode: this._yamlMode, - }, - }); + (this.config as ActionSidebarConfig)?.toggleYamlMode(); }; static styles = css` @@ -235,5 +235,8 @@ declare global { interface HASSDomEvents { "toggle-yaml-mode": undefined; + "yaml-changed": { + value: unknown; + }; } } diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index ffea6df3c8..3fc30ecba7 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -92,6 +92,8 @@ export class HaManualAutomationEditor extends LitElement { @state() private _sidebarConfig?: SidebarConfig; + @state() private _sidebarKey?: string; + @query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar; private _previousConfig?: ManualAutomationConfig; @@ -287,6 +289,7 @@ export class HaManualAutomationEditor extends LitElement { .config=${this._sidebarConfig} @value-changed=${this._sidebarConfigChanged} .disabled=${this.disabled} + .sidebarKey=${this._sidebarKey} >
@@ -314,6 +317,7 @@ export class HaManualAutomationEditor extends LitElement { // deselect previous selected row this._sidebarConfig?.close?.(); this._sidebarConfig = ev.detail; + this._sidebarKey = JSON.stringify(this._sidebarConfig); await this._sidebarElement?.updateComplete; this._sidebarElement?.focus(); diff --git a/src/panels/config/automation/option/ha-automation-option-row.ts b/src/panels/config/automation/option/ha-automation-option-row.ts index eee47b8cb4..cfb8df9aa2 100644 --- a/src/panels/config/automation/option/ha-automation-option-row.ts +++ b/src/panels/config/automation/option/ha-automation-option-row.ts @@ -395,7 +395,6 @@ export default class HaAutomationOptionRow extends LitElement { rename: () => { this._renameOption(); }, - toggleYamlMode: () => false, // no yaml mode for options delete: this._removeOption, duplicate: this._duplicateOption, defaultOption: !!this.defaultActions, diff --git a/src/panels/config/automation/sidebar/ha-automation-sidebar-action.ts b/src/panels/config/automation/sidebar/ha-automation-sidebar-action.ts index 25f84f19db..3d9708d571 100644 --- a/src/panels/config/automation/sidebar/ha-automation-sidebar-action.ts +++ b/src/panels/config/automation/sidebar/ha-automation-sidebar-action.ts @@ -11,6 +11,7 @@ import { } from "@mdi/js"; import { html, LitElement } from "lit"; import { customElement, property, query, state } from "lit/decorators"; +import { keyed } from "lit/directives/keyed"; import { fireEvent } from "../../../../common/dom/fire_event"; import { handleStructError } from "../../../../common/structs/handle-errors"; import type { LocalizeKeys } from "../../../../common/translations/localize"; @@ -41,6 +42,8 @@ export default class HaAutomationSidebarAction extends LitElement { @property({ type: Boolean }) public narrow = false; + @property({ attribute: "sidebar-key" }) public sidebarKey?: string; + @state() private _warnings?: string[]; @query(".sidebar-editor") @@ -181,18 +184,22 @@ export default class HaAutomationSidebarAction extends LitElement { ${description && !this.yamlMode ? html`
${description}
` - : html``} + : keyed( + this.sidebarKey, + html`` + )} `; } @@ -220,6 +227,12 @@ export default class HaAutomationSidebarAction extends LitElement { } } + private _yamlChangedSidebar(ev: CustomEvent) { + ev.stopPropagation(); + + this.config?.save?.(ev.detail.value); + } + private _toggleYamlMode = () => { fireEvent(this, "toggle-yaml-mode"); }; diff --git a/src/panels/config/automation/sidebar/ha-automation-sidebar-condition.ts b/src/panels/config/automation/sidebar/ha-automation-sidebar-condition.ts index 4e081cd32a..2e343d0c69 100644 --- a/src/panels/config/automation/sidebar/ha-automation-sidebar-condition.ts +++ b/src/panels/config/automation/sidebar/ha-automation-sidebar-condition.ts @@ -11,6 +11,7 @@ import { } from "@mdi/js"; import { html, LitElement } from "lit"; import { customElement, property, query, state } from "lit/decorators"; +import { keyed } from "lit/directives/keyed"; import { fireEvent } from "../../../../common/dom/fire_event"; import { handleStructError } from "../../../../common/structs/handle-errors"; import type { ConditionSidebarConfig } from "../../../../data/automation"; @@ -35,6 +36,8 @@ export default class HaAutomationSidebarCondition extends LitElement { @property({ type: Boolean }) public narrow = false; + @property({ attribute: "sidebar-key" }) public sidebarKey?: string; + @state() private _warnings?: string[]; @query(".sidebar-editor") @@ -173,17 +176,21 @@ export default class HaAutomationSidebarCondition extends LitElement { ${description && !this.yamlMode ? html`
${description}
` - : html` `} + : keyed( + this.sidebarKey, + html`` + )} `; } @@ -209,6 +216,12 @@ export default class HaAutomationSidebarCondition extends LitElement { } } + private _yamlChangedSidebar(ev: CustomEvent) { + ev.stopPropagation(); + + this.config?.save?.(ev.detail.value); + } + private _toggleYamlMode = () => { fireEvent(this, "toggle-yaml-mode"); }; diff --git a/src/panels/config/automation/sidebar/ha-automation-sidebar-script-field-selector.ts b/src/panels/config/automation/sidebar/ha-automation-sidebar-script-field-selector.ts index b9bb622d98..dfdab29f12 100644 --- a/src/panels/config/automation/sidebar/ha-automation-sidebar-script-field-selector.ts +++ b/src/panels/config/automation/sidebar/ha-automation-sidebar-script-field-selector.ts @@ -1,6 +1,7 @@ import { mdiDelete, mdiPlaylistEdit } from "@mdi/js"; import { html, LitElement } from "lit"; import { customElement, property, query, state } from "lit/decorators"; +import { keyed } from "lit/directives/keyed"; import { fireEvent } from "../../../../common/dom/fire_event"; import type { LocalizeKeys } from "../../../../common/translations/localize"; import type { ScriptFieldSidebarConfig } from "../../../../data/automation"; @@ -24,6 +25,8 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement { @property({ type: Boolean }) public narrow = false; + @property({ attribute: "sidebar-key" }) public sidebarKey?: string; + @state() private _warnings?: string[]; @query(".sidebar-editor") @@ -81,14 +84,18 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement { )} - + ${keyed( + this.sidebarKey, + html`` + )} `; } @@ -116,6 +123,12 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement { } } + private _yamlChangedSidebar(ev: CustomEvent) { + ev.stopPropagation(); + + this.config?.save?.(ev.detail.value); + } + private _toggleYamlMode = () => { fireEvent(this, "toggle-yaml-mode"); }; diff --git a/src/panels/config/automation/sidebar/ha-automation-sidebar-script-field.ts b/src/panels/config/automation/sidebar/ha-automation-sidebar-script-field.ts index d316bbb010..8729ea2d7a 100644 --- a/src/panels/config/automation/sidebar/ha-automation-sidebar-script-field.ts +++ b/src/panels/config/automation/sidebar/ha-automation-sidebar-script-field.ts @@ -1,6 +1,7 @@ import { mdiDelete, mdiPlaylistEdit } from "@mdi/js"; import { html, LitElement } from "lit"; import { customElement, property, query, state } from "lit/decorators"; +import { keyed } from "lit/directives/keyed"; import { fireEvent } from "../../../../common/dom/fire_event"; import type { ScriptFieldSidebarConfig } from "../../../../data/automation"; import type { HomeAssistant } from "../../../../types"; @@ -23,6 +24,8 @@ export default class HaAutomationSidebarScriptField extends LitElement { @property({ type: Boolean }) public narrow = false; + @property({ attribute: "sidebar-key" }) public sidebarKey?: string; + @state() private _warnings?: string[]; @query(".sidebar-editor") @@ -74,16 +77,20 @@ export default class HaAutomationSidebarScriptField extends LitElement { )} - + ${keyed( + this.sidebarKey, + html`` + )} `; } @@ -110,6 +117,12 @@ export default class HaAutomationSidebarScriptField extends LitElement { } } + private _yamlChangedSidebar(ev: CustomEvent) { + ev.stopPropagation(); + + this.config?.save?.(ev.detail.value); + } + private _toggleYamlMode = () => { fireEvent(this, "toggle-yaml-mode"); }; diff --git a/src/panels/config/automation/sidebar/ha-automation-sidebar-trigger.ts b/src/panels/config/automation/sidebar/ha-automation-sidebar-trigger.ts index a1f31d7e3d..a727a22486 100644 --- a/src/panels/config/automation/sidebar/ha-automation-sidebar-trigger.ts +++ b/src/panels/config/automation/sidebar/ha-automation-sidebar-trigger.ts @@ -11,6 +11,7 @@ import { } from "@mdi/js"; import { html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; +import { keyed } from "lit/directives/keyed"; import { fireEvent } from "../../../../common/dom/fire_event"; import { handleStructError } from "../../../../common/structs/handle-errors"; import type { TriggerSidebarConfig } from "../../../../data/automation"; @@ -35,6 +36,8 @@ export default class HaAutomationSidebarTrigger extends LitElement { @property({ type: Boolean }) public narrow = false; + @property({ attribute: "sidebar-key" }) public sidebarKey?: string; + @state() private _requestShowId = false; @state() private _warnings?: string[]; @@ -183,18 +186,22 @@ export default class HaAutomationSidebarTrigger extends LitElement { )} - + ${keyed( + this.sidebarKey, + html`` + )} `; } @@ -221,6 +228,12 @@ export default class HaAutomationSidebarTrigger extends LitElement { } } + private _yamlChangedSidebar(ev: CustomEvent) { + ev.stopPropagation(); + + this.config?.save?.(ev.detail.value); + } + private _toggleYamlMode = () => { fireEvent(this, "toggle-yaml-mode"); }; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-editor.ts b/src/panels/config/automation/trigger/ha-automation-trigger-editor.ts index 2ae092d7ba..e354a56713 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-editor.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-editor.ts @@ -121,7 +121,7 @@ export default class HaAutomationTriggerEditor extends LitElement { if (!ev.detail.isValid) { return; } - fireEvent(this, "value-changed", { + fireEvent(this, "yaml-changed", { value: migrateAutomationTrigger(ev.detail.value), }); } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 926c25bb2f..2117e29c09 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -494,7 +494,7 @@ export default class HaAutomationTriggerRow extends LitElement { }, toggleYamlMode: () => { this._toggleYamlMode(); - return this._yamlMode; + this.openSidebar(); }, disable: this._onDisable, delete: this._onDelete, diff --git a/src/panels/config/script/ha-script-field-editor.ts b/src/panels/config/script/ha-script-field-editor.ts index 3d96cca29c..78e0b44022 100644 --- a/src/panels/config/script/ha-script-field-editor.ts +++ b/src/panels/config/script/ha-script-field-editor.ts @@ -152,7 +152,12 @@ export default class HaScriptFieldEditor extends LitElement { ev.stopPropagation(); const value = { ...ev.detail.value }; - if (typeof value !== "object" || Object.keys(value).length !== 1) { + if ( + typeof value !== "object" || + Object.keys(value).length !== 1 || + !value[Object.keys(value)[0]] || + !value[Object.keys(value)[0]].selector + ) { this._yamlError = "yaml_error"; return; } @@ -165,7 +170,7 @@ export default class HaScriptFieldEditor extends LitElement { const newValue = { ...value[key], key }; - fireEvent(this, "value-changed", { value: newValue }); + fireEvent(this, "yaml-changed", { value: newValue }); } private _computeLabelCallback = ( diff --git a/src/panels/config/script/ha-script-field-row.ts b/src/panels/config/script/ha-script-field-row.ts index 867ca2f958..58eff00d2b 100644 --- a/src/panels/config/script/ha-script-field-row.ts +++ b/src/panels/config/script/ha-script-field-row.ts @@ -218,7 +218,7 @@ export default class HaScriptFieldRow extends LitElement { }, toggleYamlMode: () => { this._toggleYamlMode(); - return this._yamlMode; + this.openSidebar(); }, delete: this._onDelete, config: { diff --git a/src/panels/config/script/ha-script-field-selector-editor.ts b/src/panels/config/script/ha-script-field-selector-editor.ts index e33b64fc09..f7fed6ad7a 100644 --- a/src/panels/config/script/ha-script-field-selector-editor.ts +++ b/src/panels/config/script/ha-script-field-selector-editor.ts @@ -132,7 +132,7 @@ export default class HaScriptFieldSelectorEditor extends LitElement { return; } - fireEvent(this, "value-changed", { value }); + fireEvent(this, "yaml-changed", { value }); } private _computeLabelCallback = ( diff --git a/src/panels/config/script/manual-script-editor.ts b/src/panels/config/script/manual-script-editor.ts index b8cc9ca973..6a513e2173 100644 --- a/src/panels/config/script/manual-script-editor.ts +++ b/src/panels/config/script/manual-script-editor.ts @@ -73,6 +73,8 @@ export class HaManualScriptEditor extends LitElement { @state() private _sidebarConfig?: SidebarConfig; + @state() private _sidebarKey?: string; + @query("ha-script-fields") private _scriptFields?: HaScriptFields; @@ -223,6 +225,7 @@ export class HaManualScriptEditor extends LitElement {