From 6f753c490995a16b53452cfc6e0d29e509916646 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Fri, 27 Jun 2025 17:41:58 +0200 Subject: [PATCH 01/16] Use entity format state if only one entity for that domain in the area card (#25964) Use entity format state if only one entity is area card --- src/panels/lovelace/cards/hui-area-card.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/panels/lovelace/cards/hui-area-card.ts b/src/panels/lovelace/cards/hui-area-card.ts index 1f545e3a78..68dce2164e 100644 --- a/src/panels/lovelace/cards/hui-area-card.ts +++ b/src/panels/lovelace/cards/hui-area-card.ts @@ -348,6 +348,14 @@ export class HuiAreaCard extends LitElement implements LovelaceCard { return undefined; } + // If only one entity, return its formatted state + if (entities.length === 1) { + const stateObj = entities[0]; + return isUnavailableState(stateObj.state) + ? "" + : this.hass.formatEntityState(stateObj); + } + // Use the first entity's unit_of_measurement for formatting const uom = entities.find( (entity) => entity.attributes.unit_of_measurement From 619974ffdbae8fa5a627248b004658cc53522df4 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Fri, 27 Jun 2025 15:57:52 +0200 Subject: [PATCH 02/16] Dev Tools: Remove excessive space from "Input date times" (#25973) Remove excessive space from "input date times" --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index 27c3e1b050..020803358c 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -8665,7 +8665,7 @@ "input_button": "Input buttons", "input_text": "Input texts", "input_number": "Input numbers", - "input_datetime": "Input date times", + "input_datetime": "Input datetimes", "input_select": "Input selects", "template": "Template entities", "universal": "Universal media player entities", From e306e29d95a08a90fe523df6cb5ae3ae927c857b Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Sun, 29 Jun 2025 09:25:29 +0200 Subject: [PATCH 03/16] Fix sentence-casing, spelling and grammar issues (#25981) * Fix sentence-casing, spelling and grammar issues * Add "IP information" to the list * More sentence-casing issues --- src/translations/en.json | 98 ++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index 020803358c..e9d1e039b2 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -51,7 +51,7 @@ "owner": "Owner", "system-admin": "Administrators", "system-users": "Users", - "system-read-only": "Read-Only Users" + "system-read-only": "Read-only users" }, "config_entry": { "disabled_by": { @@ -471,7 +471,7 @@ "radius_meters": "[%key:ui::panel::config::core::section::core::core_config::elevation_meters%]" }, "selector": { - "options": "Selector Options", + "options": "Selector options", "types": { "action": "Action", "area": "Area", @@ -480,7 +480,7 @@ "color_temp": "Color temperature", "condition": "Condition", "date": "Date", - "datetime": "Date and Time", + "datetime": "Date and time", "device": "Device", "duration": "Duration", "entity": "Entity", @@ -490,7 +490,7 @@ "media": "Media", "number": "Number", "object": "Object", - "color_rgb": "RGB Color", + "color_rgb": "RGB color", "select": "Select", "state": "State", "target": "Target", @@ -498,7 +498,7 @@ "text": "Text", "theme": "Theme", "time": "Time", - "manual": "Manual Entry" + "manual": "Manual entry" } }, "template": { @@ -857,7 +857,7 @@ "cyan": "Cyan", "teal": "Teal", "green": "Green", - "light-green": "Light Green", + "light-green": "Light green", "lime": "Lime", "yellow": "Yellow", "amber": "Amber", @@ -1045,7 +1045,7 @@ "podcast": "Podcast", "season": "Season", "track": "Track", - "tv_show": "TV Show", + "tv_show": "TV show", "url": "URL", "video": "Video" }, @@ -2935,7 +2935,7 @@ "name": "Name", "description": "Description", "tag_id": "Tag ID", - "tag_id_placeholder": "Autogenerated if left empty", + "tag_id_placeholder": "Auto-generated if left empty", "delete": "Delete", "update": "Update", "create": "Create", @@ -4490,14 +4490,14 @@ "trace_no_longer_available": "Chosen trace is no longer available", "enter_downloaded_trace": "Enter downloaded trace", "tabs": { - "details": "Step Details", - "timeline": "Trace Timeline", + "details": "Step details", + "timeline": "Trace timeline", "logbook": "Related logbook entries", - "automation_config": "Automation Config", - "step_config": "Step Config", - "changed_variables": "Changed Variables", - "blueprint_config": "Blueprint Config", - "script_config": "Script Config" + "automation_config": "Automation config", + "step_config": "Step config", + "changed_variables": "Changed variables", + "blueprint_config": "Blueprint config", + "script_config": "Script config" }, "path": { "choose": "Select a step on the left for more information.", @@ -4544,7 +4544,7 @@ "caption": "Blueprints", "description": "Manage blueprints", "overview": { - "header": "Blueprint Editor", + "header": "Blueprint editor", "introduction": "The blueprint configuration allows you to import and manage your blueprints.", "learn_more": "Learn more about using blueprints", "headers": { @@ -4601,14 +4601,14 @@ "override_description": "Importing it will override the existing blueprint. If the updated blueprint is not compatible, it can break your automations. Automations will have to be adjusted manually.", "error_no_url": "Please enter the blueprint address.", "unsupported_blueprint": "This blueprint is not supported", - "file_name": "Blueprint Path" + "file_name": "Blueprint path" } }, "script": { "caption": "Scripts", "description": "Execute a sequence of actions", "picker": { - "header": "Script Editor", + "header": "Script editor", "introduction": "The script editor allows you to create and edit scripts. Please follow the link below to read the instructions to make sure that you have configured Home Assistant correctly.", "learn_more": "Learn more about scripts", "no_scripts": "We couldn't find any scripts", @@ -4684,7 +4684,7 @@ "field_delete_confirm_title": "Delete field?", "field_delete_confirm_text": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm_text%]", "header": "Script: {name}", - "default_name": "New Script", + "default_name": "New script", "modes": { "label": "[%key:ui::panel::config::automation::editor::modes::label%]", "learn_more": "[%key:ui::panel::config::automation::editor::modes::learn_more%]", @@ -4729,7 +4729,7 @@ "description": "Capture device states and easily recall them later", "activated": "Activated scene {name}.", "picker": { - "header": "Scene Editor", + "header": "Scene editor", "introduction": "The scene editor allows you to create and edit scenes. Please follow the link below to read the instructions to make sure that you have configured Home Assistant correctly.", "learn_more": "Learn more about scenes", "pick_scene": "Pick scene to edit", @@ -4993,7 +4993,7 @@ "other_home_assistant": "Other Home Assistant", "instance_name": "Instance name", "instance_version": "Instance version", - "ip_address": "IP Address", + "ip_address": "IP address", "connected_at": "Connected at", "obfuscated_ip": { "show": "Show IP address", @@ -5661,9 +5661,9 @@ }, "dhcp": { "title": "DHCP discovery", - "mac_address": "MAC Address", + "mac_address": "MAC address", "hostname": "Hostname", - "ip_address": "IP Address", + "ip_address": "IP address", "no_devices_found": "No recent DHCP requests found; no matching discoveries detected" }, "thread": { @@ -5722,7 +5722,7 @@ "name": "Name", "type": "Type", "port": "Port", - "ip_addresses": "IP Addresses", + "ip_addresses": "IP addresses", "properties": "Properties", "discovery_information": "Discovery information", "copy_to_clipboard": "Copy to clipboard", @@ -6354,7 +6354,7 @@ "title": "Door lock", "twist_assist": "Twist assist", "block_to_block": "Block to block", - "auto_relock_time": "Auto relock time", + "auto_relock_time": "Autorelock time", "hold_release_time": "Hold and release time", "operation_type": "Operation type", "operation_types": { @@ -6525,13 +6525,13 @@ "prefix": "Subnet prefix", "add_address": "Add address", "gateway": "Gateway address", - "dns_server": "DNS Server", - "add_dns_server": "Add DNS Server", + "dns_server": "DNS server", + "add_dns_server": "Add DNS server", "custom_dns": "Custom", "unsaved": "You have unsaved changes, these will get lost if you change tabs, do you want to continue?", "failed_to_change": "Failed to change network settings", "hostname": { - "title": "Host name", + "title": "Hostname", "description": "The name your instance will have on your network", "failed_to_set_hostname": "Setting hostname failed" } @@ -6548,9 +6548,9 @@ }, "network_adapter": "Network adapter", "network_adapter_info": "Configure which network adapters integrations will use. A restart is required for these settings to apply.", - "ip_information": "IP Information", + "ip_information": "IP information", "adapter": { - "auto_configure": "Auto configure", + "auto_configure": "Autoconfigure", "detected": "Detected", "adapter": "Adapter" } @@ -6559,7 +6559,7 @@ "caption": "Storage", "description": "{percent_used} used - {free_space} free", "used_space": "Used space", - "emmc_lifetime_used": "eMMC Lifetime Used", + "emmc_lifetime_used": "eMMC lifetime used", "disk_metrics": "Disk metrics", "datadisk": { "title": "Move data disk", @@ -6910,7 +6910,7 @@ }, "edit_view": { "header": "View configuration", - "header_name": "{name} View Configuration", + "header_name": "{name} view configuration", "add": "Add view", "background": { "settings": "Background settings", @@ -7026,7 +7026,7 @@ }, "edit_card": { "header": "Card configuration", - "typed_header": "{type} Card configuration", + "typed_header": "{type} card configuration", "pick_card": "Add to dashboard", "pick_card_title": "Which card would you like to add to {name}", "toggle_editor": "Toggle editor", @@ -7096,7 +7096,7 @@ "move_card": { "header": "Choose a view to move the card to", "strategy_error_title": "Impossible to move the card", - "strategy_error_text_strategy": "Moving a card to an auto generated view is not supported.", + "strategy_error_text_strategy": "Moving a card to an auto-generated view is not supported.", "success": "Card moved successfully", "error": "Error while moving card" }, @@ -7519,13 +7519,13 @@ "geo_location_sources": "Geolocation sources", "no_geo_location_sources": "No geolocation sources available", "appearance": "Appearance", - "theme_mode": "Theme Mode", + "theme_mode": "Theme mode", "theme_modes": { "auto": "Auto", "light": "Light", "dark": "Dark" }, - "default_zoom": "Default Zoom", + "default_zoom": "Default zoom", "source": "Source", "description": "The Map card that allows you to display entities on a map." }, @@ -7573,7 +7573,7 @@ "picture-elements": { "name": "Picture elements", "description": "The Picture elements card is one of the most versatile types of cards. The cards allow you to position icons or text and even actions! On an image based on coordinates.", - "card_options": "Card Options", + "card_options": "Card options", "elements": "Elements", "new_element": "Add new element", "confirm_delete_element": "Are you sure you want to delete the {type} element?", @@ -7622,14 +7622,14 @@ "integration_not_loaded": "This card requires the `todo` integration to be set up.", "hide_completed": "Hide completed items", "hide_create": "Hide 'Add item' field", - "display_order": "Display Order", + "display_order": "Display order", "sort_modes": { "none": "Default", "manual": "Manual", "alpha_asc": "Alphabetical (A-Z)", "alpha_desc": "Alphabetical (Z-A)", - "duedate_asc": "Due Date (Soonest First)", - "duedate_desc": "Due Date (Latest First)" + "duedate_asc": "Due date (Soonest first)", + "duedate_desc": "Due date (Latest first)" } }, "thermostat": { @@ -8231,14 +8231,14 @@ "confirm_delete_title": "Delete long-lived access token?", "confirm_delete_text": "Are you sure you want to delete the long-lived access token for {name}?", "delete_failed": "Failed to delete the access token.", - "create": "Create Token", + "create": "Create token", "create_failed": "Failed to create the access token.", "name": "Name", "prompt_name": "Give the token a name", "prompt_copy_token": "Copy your access token. It will not be shown again.", "empty_state": "You have no long-lived access tokens yet.", "qr_code_image": "QR code for token {name}", - "generate_qr_code": "Generate QR Code" + "generate_qr_code": "Generate QR code" } }, "todo": { @@ -8395,7 +8395,7 @@ "hdmi_input": "HDMI input", "hdmi_switcher": "HDMI switcher", "volume": "Volume", - "total_tv_time": "Total TV Time", + "total_tv_time": "Total TV time", "turn_tv_off": "Turn television off", "air": "Air" }, @@ -9076,7 +9076,7 @@ }, "capability": { "stage": { - "title": "Add-on Stage", + "title": "Add-on stage", "description": "Add-ons can have one of three stages:\n\n{icon_stable} **Stable**: These are add-ons ready to be used in production.\n\n{icon_experimental} **Experimental**: These may contain bugs, and may be unfinished.\n\n{icon_deprecated} **Deprecated**: These add-ons will no longer receive any updates." }, "rating": { @@ -9158,8 +9158,8 @@ "description": "This will restart the add-on if it crashes" }, "auto_update": { - "title": "Auto update", - "description": "Auto update the add-on when there is a new version available" + "title": "Autoupdate", + "description": "Autoupdate the add-on when there is a new version available" }, "ingress_panel": { "title": "Show in sidebar", @@ -9270,7 +9270,7 @@ "addons": "Add-ons", "dashboard": "Dashboard", "backups": "Backups", - "store": "Add-on Store", + "store": "Add-on store", "system": "System" }, "my": { @@ -9360,7 +9360,7 @@ "hostname": "Hostname", "change_hostname": "Change hostname", "new_hostname": "Please enter a new hostname:", - "ip_address": "IP Address", + "ip_address": "IP address", "change": "Change", "operating_system": "Operating system", "docker_version": "Docker version", @@ -9412,7 +9412,7 @@ "confirm_password": "Confirm encryption key", "password_protection": "Password protection", "enter_password": "Please enter a password.", - "passwords_not_matching": "The passwords does not match", + "passwords_not_matching": "The passwords do not match", "backup_already_running": "A backup or restore is already running. Creating a new backup is currently not possible, try again later.", "confirm_restore_partial_backup_title": "Restore partial backup", "confirm_restore_partial_backup_text": "The backup will be restored. Depending on the size of the backup, this can take up to 45 min. Home Assistant needs to shutdown and the restore progress is running in the background. If it succeeds, Home Assistant will automatically start again.", From 05a9f69c9e360de89aafc0bf65ce1edb45c8e83d Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:59:15 +0200 Subject: [PATCH 04/16] Pass area control service calls through hass (#25986) Connection logging --- src/common/entity/group_entities.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/entity/group_entities.ts b/src/common/entity/group_entities.ts index 32f9634af5..26237a699c 100644 --- a/src/common/entity/group_entities.ts +++ b/src/common/entity/group_entities.ts @@ -1,4 +1,4 @@ -import { callService, type HassEntity } from "home-assistant-js-websocket"; +import type { HassEntity } from "home-assistant-js-websocket"; import { computeStateDomain } from "./compute_state_domain"; import { isUnavailableState, UNAVAILABLE } from "../../data/entity"; import type { HomeAssistant } from "../../types"; @@ -62,7 +62,7 @@ export const toggleGroupEntities = ( const entitiesIds = states.map((stateObj) => stateObj.entity_id); - callService(hass.connection, domain, service, { + hass.callService(domain, service, { entity_id: entitiesIds, }); }; From 6300bfb200e53424840b7c6de4ff107912734b73 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Sun, 29 Jun 2025 17:16:09 +0200 Subject: [PATCH 05/16] Fix grammar of Light, Sensor and Tile card descriptions (#25988) * Fix grammar of Light, Sensor and Entity card descriptions * Capitalize "Tile card" as a name * Apply same change to Entity badge description --- 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 e9d1e039b2..a6505cf6e4 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7431,7 +7431,7 @@ }, "light": { "name": "Light", - "description": "The Light card allows you to change the brightness of the light." + "description": "The Light card allows you to change the brightness of a light." }, "generic": { "alt_text": "Alternative text", @@ -7612,7 +7612,7 @@ "none": "None", "line": "Line" }, - "description": "The Sensor card gives you a quick overview of your sensors state with an optional graph to visualize change over time.", + "description": "The Sensor card gives you a quick overview of a sensor's state with an optional graph to visualize change over time.", "limit_min": "Minimum value", "limit_max": "Maximum value" }, @@ -7639,7 +7639,7 @@ }, "tile": { "name": "Tile", - "description": "The tile card gives you a quick overview of your entity. The card allows you to toggle the entity, show the More info dialog or trigger custom actions.", + "description": "The Tile card gives you a quick overview of an entity. The card allows you to toggle the entity, show the More info dialog or trigger custom actions.", "color": "Color", "color_helper": "Inactive state (e.g. off, closed) will not be colored.", "icon_tap_action": "Icon tap behavior", @@ -7693,7 +7693,7 @@ "badge": { "entity": { "name": "Entity", - "description": "The Entity badge gives you a quick overview of your entity.", + "description": "The Entity badge gives you a quick overview of an entity.", "color": "[%key:ui::panel::lovelace::editor::card::tile::color%]", "color_helper": "[%key:ui::panel::lovelace::editor::card::tile::color_helper%]", "show_entity_picture": "Show entity picture", From b16087d5b577902dc86df220b9b16e01b6be3aca Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:16:59 +0200 Subject: [PATCH 06/16] Fix fullscreen yaml editor (transparency background) (#25989) Fix fullscreen editor (transparency background) --- src/components/ha-code-editor.ts | 6 +++--- src/resources/codemirror.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ha-code-editor.ts b/src/components/ha-code-editor.ts index be3eb9d283..5cb78c7193 100644 --- a/src/components/ha-code-editor.ts +++ b/src/components/ha-code-editor.ts @@ -559,9 +559,9 @@ export class HaCodeEditor extends ReactiveElement { right: 8px; z-index: 1; color: var(--secondary-text-color); - background-color: var(--card-background-color); + background-color: var(--secondary-background-color); border-radius: 50%; - opacity: 0.6; + opacity: 0.9; transition: opacity 0.2s; --mdc-icon-button-size: 32px; --mdc-icon-size: 18px; @@ -591,7 +591,7 @@ export class HaCodeEditor extends ReactiveElement { z-index: 9999 !important; background-color: var( --code-editor-background-color, - var(--mdc-text-field-fill-color, whitesmoke) + var(--card-background-color) ) !important; margin: 0 !important; padding-top: var(--safe-area-inset-top) !important; diff --git a/src/resources/codemirror.ts b/src/resources/codemirror.ts index 0999859c22..a5b8de8bb9 100644 --- a/src/resources/codemirror.ts +++ b/src/resources/codemirror.ts @@ -51,7 +51,7 @@ export const haTheme = EditorView.theme({ "&": { color: "var(--primary-text-color)", backgroundColor: - "var(--code-editor-background-color, var(--mdc-text-field-fill-color, whitesmoke))", + "var(--code-editor-background-color, var(--card-background-color))", borderRadius: "var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0px 0px", caretColor: "var(--secondary-text-color)", From 3e45821fd03197ab21865b52573220184379b792 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 30 Jun 2025 18:09:42 +0200 Subject: [PATCH 07/16] Allow to re-order floors in areas dashboard (#26002) * Allow to re-order floors in areas dashboard * Move drag handle to right * Improve typings * Only show drag handle if there is at least 2 floors --- .../ha-areas-floors-display-editor.ts | 208 +++++++++++------- src/components/ha-items-display-editor.ts | 38 ++-- .../areas/areas-dashboard-strategy.ts | 4 + .../areas/areas-overview-view-strategy.ts | 20 +- .../hui-areas-dashboard-strategy-editor.ts | 19 +- .../areas/helpers/areas-strategy-helper.ts | 25 ++- src/translations/en.json | 2 +- 7 files changed, 199 insertions(+), 117 deletions(-) diff --git a/src/components/ha-areas-floors-display-editor.ts b/src/components/ha-areas-floors-display-editor.ts index cd56b94ffc..5b372f932f 100644 --- a/src/components/ha-areas-floors-display-editor.ts +++ b/src/components/ha-areas-floors-display-editor.ts @@ -1,14 +1,15 @@ -import { mdiTextureBox } from "@mdi/js"; +import { mdiDrag, mdiTextureBox } from "@mdi/js"; import type { TemplateResult } from "lit"; import { LitElement, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; +import { repeat } from "lit/directives/repeat"; import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; import { computeFloorName } from "../common/entity/compute_floor_name"; import { getAreaContext } from "../common/entity/context/get_area_context"; -import { stringCompare } from "../common/string/compare"; import { areaCompare } from "../data/area_registry"; import type { FloorRegistryEntry } from "../data/floor_registry"; +import { getFloors } from "../panels/lovelace/strategies/areas/helpers/areas-strategy-helper"; import type { HomeAssistant } from "../types"; import "./ha-expansion-panel"; import "./ha-floor-icon"; @@ -17,9 +18,14 @@ import type { DisplayItem, DisplayValue } from "./ha-items-display-editor"; import "./ha-svg-icon"; import "./ha-textfield"; -export interface AreasDisplayValue { - hidden?: string[]; - order?: string[]; +export interface AreasFloorsDisplayValue { + areas_display?: { + hidden?: string[]; + order?: string[]; + }; + floors_display?: { + order?: string[]; + }; } const UNASSIGNED_FLOOR = "__unassigned__"; @@ -30,12 +36,10 @@ export class HaAreasFloorsDisplayEditor extends LitElement { @property() public label?: string; - @property({ attribute: false }) public value?: AreasDisplayValue; + @property({ attribute: false }) public value?: AreasFloorsDisplayValue; @property() public helper?: string; - @property({ type: Boolean }) public expanded = false; - @property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public required = false; @@ -44,55 +48,78 @@ export class HaAreasFloorsDisplayEditor extends LitElement { public showNavigationButton = false; protected render(): TemplateResult { - const groupedItems = this._groupedItems(this.hass.areas, this.hass.floors); + const groupedAreasItems = this._groupedAreasItems( + this.hass.areas, + this.hass.floors + ); - const filteredFloors = this._sortedFloors(this.hass.floors).filter( + const filteredFloors = this._sortedFloors( + this.hass.floors, + this.value?.floors_display?.order + ).filter( (floor) => // Only include floors that have areas assigned to them - groupedItems[floor.floor_id]?.length > 0 + groupedAreasItems[floor.floor_id]?.length > 0 ); const value: DisplayValue = { - order: this.value?.order ?? [], - hidden: this.value?.hidden ?? [], + order: this.value?.areas_display?.order ?? [], + hidden: this.value?.areas_display?.hidden ?? [], }; + const canReorderFloors = + filteredFloors.filter((floor) => floor.floor_id !== UNASSIGNED_FLOOR) + .length > 1; + return html` - ${this.label}` : nothing} + - - ${filteredFloors.map((floor, _, array) => { - const noFloors = - array.length === 1 && floor.floor_id === UNASSIGNED_FLOOR; - return html` -
- ${noFloors - ? nothing - : html`
- -

${computeFloorName(floor)}

-
`} -
+
+ ${repeat( + filteredFloors, + (floor) => floor.floor_id, + (floor: FloorRegistryEntry) => html` + + + ${floor.floor_id === UNASSIGNED_FLOOR || !canReorderFloors + ? nothing + : html` + + `} -
-
- `; - })} - + + ` + )} +
+
`; } - private _groupedItems = memoizeOne( + private _groupedAreasItems = memoizeOne( ( hassAreas: HomeAssistant["areas"], // update items if floors change @@ -116,7 +143,6 @@ export class HaAreasFloorsDisplayEditor extends LitElement { label: area.name, icon: area.icon ?? undefined, iconPath: mdiTextureBox, - description: floor?.name, }); return acc; @@ -128,18 +154,17 @@ export class HaAreasFloorsDisplayEditor extends LitElement { ); private _sortedFloors = memoizeOne( - (hassFloors: HomeAssistant["floors"]): FloorRegistryEntry[] => { - const floors = Object.values(hassFloors).sort((floorA, floorB) => { - if (floorA.level !== floorB.level) { - return (floorA.level ?? 0) - (floorB.level ?? 0); - } - return stringCompare(floorA.name, floorB.name); - }); + ( + hassFloors: HomeAssistant["floors"], + order: string[] | undefined + ): FloorRegistryEntry[] => { + const floors = getFloors(hassFloors, order); + const noFloors = floors.length === 0; floors.push({ floor_id: UNASSIGNED_FLOOR, - name: this.hass.localize( - "ui.panel.lovelace.strategy.areas.others_areas" - ), + name: noFloors + ? this.hass.localize("ui.panel.lovelace.strategy.areas.areas") + : this.hass.localize("ui.panel.lovelace.strategy.areas.other_areas"), icon: null, level: null, aliases: [], @@ -150,17 +175,43 @@ export class HaAreasFloorsDisplayEditor extends LitElement { } ); - private async _areaDisplayChanged(ev) { + private _floorMoved(ev: CustomEvent) { ev.stopPropagation(); - const value = ev.detail.value as DisplayValue; - const currentFloorId = ev.currentTarget.floorId; + const newIndex = ev.detail.newIndex; + const oldIndex = ev.detail.oldIndex; + const floorIds = this._sortedFloors( + this.hass.floors, + this.value?.floors_display?.order + ).map((floor) => floor.floor_id); + const newOrder = [...floorIds]; + const movedFloorId = newOrder.splice(oldIndex, 1)[0]; + newOrder.splice(newIndex, 0, movedFloorId); + const newValue: AreasFloorsDisplayValue = { + areas_display: this.value?.areas_display, + floors_display: { + order: newOrder, + }, + }; + if (newValue.floors_display?.order?.length === 0) { + delete newValue.floors_display.order; + } + fireEvent(this, "value-changed", { value: newValue }); + } - const floorIds = this._sortedFloors(this.hass.floors).map( - (floor) => floor.floor_id - ); + private async _areaDisplayChanged(ev: CustomEvent<{ value: DisplayValue }>) { + ev.stopPropagation(); + const value = ev.detail.value; + const currentFloorId = (ev.currentTarget as any).floorId; - const oldHidden = this.value?.hidden ?? []; - const oldOrder = this.value?.order ?? []; + const floorIds = this._sortedFloors( + this.hass.floors, + this.value?.floors_display?.order + ).map((floor) => floor.floor_id); + + const oldAreaDisplay = this.value?.areas_display ?? {}; + + const oldHidden = oldAreaDisplay?.hidden ?? []; + const oldOrder = oldAreaDisplay?.order ?? []; const newHidden: string[] = []; const newOrder: string[] = []; @@ -187,37 +238,27 @@ export class HaAreasFloorsDisplayEditor extends LitElement { } } - const newValue: AreasDisplayValue = { - hidden: newHidden, - order: newOrder, + const newValue: AreasFloorsDisplayValue = { + areas_display: { + hidden: newHidden, + order: newOrder, + }, + floors_display: this.value?.floors_display, }; - if (newValue.hidden?.length === 0) { - delete newValue.hidden; + if (newValue.areas_display?.hidden?.length === 0) { + delete newValue.areas_display.hidden; } - if (newValue.order?.length === 0) { - delete newValue.order; + if (newValue.areas_display?.order?.length === 0) { + delete newValue.areas_display.order; } - this.value = newValue; + if (newValue.floors_display?.order?.length === 0) { + delete newValue.floors_display.order; + } + fireEvent(this, "value-changed", { value: newValue }); } static styles = css` - .floor .header p { - margin: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - flex: 1; - } - .floor .header { - margin: 16px 0 8px 0; - padding: 0 8px; - display: flex; - flex-direction: row; - align-items: center; - gap: 8px; - } ha-expansion-panel { margin-bottom: 8px; --expansion-panel-summary-padding: 0 16px; @@ -225,6 +266,11 @@ export class HaAreasFloorsDisplayEditor extends LitElement { ha-expansion-panel [slot="leading-icon"] { margin-inline-end: 16px; } + label { + display: block; + font-weight: var(--ha-font-weight-bold); + margin-bottom: 8px; + } `; } diff --git a/src/components/ha-items-display-editor.ts b/src/components/ha-items-display-editor.ts index e87ecfaef0..91d55820bf 100644 --- a/src/components/ha-items-display-editor.ts +++ b/src/components/ha-items-display-editor.ts @@ -122,22 +122,6 @@ export class HaItemDisplayEditor extends LitElement { ${description ? html`${description}` : nothing} - ${isVisible && !disableSorting - ? html` - - ` - : html``} ${!showIcon ? nothing : icon @@ -162,6 +146,9 @@ export class HaItemDisplayEditor extends LitElement { ${this.actionsRenderer(item)} ` : nothing} + ${this.showNavigationButton + ? html`` + : nothing} - ${this.showNavigationButton - ? html` ` - : nothing} + ${isVisible && !disableSorting + ? html` + + ` + : html``} `; } diff --git a/src/panels/lovelace/strategies/areas/areas-dashboard-strategy.ts b/src/panels/lovelace/strategies/areas/areas-dashboard-strategy.ts index 9ec7bc9486..6154719ec6 100644 --- a/src/panels/lovelace/strategies/areas/areas-dashboard-strategy.ts +++ b/src/panels/lovelace/strategies/areas/areas-dashboard-strategy.ts @@ -22,6 +22,9 @@ export interface AreasDashboardStrategyConfig { hidden?: string[]; order?: string[]; }; + floors_display?: { + order?: string[]; + }; areas_options?: Record; } @@ -84,6 +87,7 @@ export class AreasDashboardStrategy extends ReactiveElement { type: "areas-overview", areas_display: config.areas_display, areas_options: config.areas_options, + floors_display: config.floors_display, } satisfies AreasViewStrategyConfig, }, ...areaViews, diff --git a/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts b/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts index 9c47f38ddd..9d2d75e2d2 100644 --- a/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts +++ b/src/panels/lovelace/strategies/areas/areas-overview-view-strategy.ts @@ -1,6 +1,5 @@ import { ReactiveElement } from "lit"; import { customElement } from "lit/decorators"; -import { stringCompare } from "../../../../common/string/compare"; import { floorDefaultIcon } from "../../../../components/ha-floor-icon"; import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/section"; import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view"; @@ -9,7 +8,11 @@ import { getAreaControlEntities } from "../../card-features/hui-area-controls-ca import { AREA_CONTROLS, type AreaControl } from "../../card-features/types"; import type { AreaCardConfig, HeadingCardConfig } from "../../cards/types"; import type { EntitiesDisplay } from "./area-view-strategy"; -import { computeAreaPath, getAreas } from "./helpers/areas-strategy-helper"; +import { + computeAreaPath, + getAreas, + getFloors, +} from "./helpers/areas-strategy-helper"; const UNASSIGNED_FLOOR = "__unassigned__"; @@ -23,6 +26,9 @@ export interface AreasViewStrategyConfig { hidden?: string[]; order?: string[]; }; + floors_display?: { + order?: string[]; + }; areas_options?: Record; } @@ -38,19 +44,13 @@ export class AreasOverviewViewStrategy extends ReactiveElement { config.areas_display?.order ); - const floors = Object.values(hass.floors); - floors.sort((floorA, floorB) => { - if (floorA.level !== floorB.level) { - return (floorA.level ?? 0) - (floorB.level ?? 0); - } - return stringCompare(floorA.name, floorB.name); - }); + const floors = getFloors(hass.floors, config.floors_display?.order); const floorSections = [ ...floors, { floor_id: UNASSIGNED_FLOOR, - name: hass.localize("ui.panel.lovelace.strategy.areas.others_areas"), + name: hass.localize("ui.panel.lovelace.strategy.areas.other_areas"), level: null, icon: null, }, diff --git a/src/panels/lovelace/strategies/areas/editor/hui-areas-dashboard-strategy-editor.ts b/src/panels/lovelace/strategies/areas/editor/hui-areas-dashboard-strategy-editor.ts index 73e7d890bb..96448afc83 100644 --- a/src/panels/lovelace/strategies/areas/editor/hui-areas-dashboard-strategy-editor.ts +++ b/src/panels/lovelace/strategies/areas/editor/hui-areas-dashboard-strategy-editor.ts @@ -1,10 +1,12 @@ import { mdiThermometerWater } from "@mdi/js"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-areas-display-editor"; import type { AreasDisplayValue } from "../../../../../components/ha-areas-display-editor"; import "../../../../../components/ha-areas-floors-display-editor"; +import type { AreasFloorsDisplayValue } from "../../../../../components/ha-areas-floors-display-editor"; import "../../../../../components/ha-entities-display-editor"; import "../../../../../components/ha-icon"; import "../../../../../components/ha-icon-button"; @@ -126,7 +128,7 @@ export class HuiAreasDashboardStrategyEditor `; } - const value = this._config.areas_display; + const value = this._areasFloorsDisplayValue(this._config); return html` ({ + areas_display: config.areas_display, + floors_display: config.floors_display, + }) + ); + private _editArea(ev: Event): void { ev.stopPropagation(); const area = (ev.currentTarget! as any).area as AreaRegistryEntry; @@ -163,11 +172,11 @@ export class HuiAreasDashboardStrategyEditor this._area = ev.detail.value; } - private _areasDisplayChanged(ev: CustomEvent): void { - const value = ev.detail.value as AreasDisplayValue; + private _areasFloorsDisplayChanged(ev: CustomEvent): void { + const value = ev.detail.value as AreasFloorsDisplayValue; const newConfig: AreasDashboardStrategyConfig = { ...this._config!, - areas_display: value, + ...value, }; fireEvent(this, "config-changed", { config: newConfig }); 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 cbebee032b..79582d02fa 100644 --- a/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts +++ b/src/panels/lovelace/strategies/areas/helpers/areas-strategy-helper.ts @@ -3,9 +3,13 @@ import { computeStateName } from "../../../../../common/entity/compute_state_nam import type { EntityFilterFunc } from "../../../../../common/entity/entity_filter"; import { generateEntityFilter } from "../../../../../common/entity/entity_filter"; import { stripPrefixFromEntityName } from "../../../../../common/entity/strip_prefix_from_entity_name"; -import { orderCompare } from "../../../../../common/string/compare"; +import { + orderCompare, + stringCompare, +} from "../../../../../common/string/compare"; import type { AreaRegistryEntry } from "../../../../../data/area_registry"; import { areaCompare } from "../../../../../data/area_registry"; +import type { FloorRegistryEntry } from "../../../../../data/floor_registry"; import type { LovelaceCardConfig } from "../../../../../data/lovelace/config/card"; import type { HomeAssistant } from "../../../../../types"; import { supportsAlarmModesCardFeature } from "../../../card-features/hui-alarm-modes-card-feature"; @@ -290,4 +294,23 @@ export const getAreas = ( return sortedAreas; }; +export const getFloors = ( + entries: HomeAssistant["floors"], + floorsOrder?: string[] +): FloorRegistryEntry[] => { + const floors = Object.values(entries); + const compare = orderCompare(floorsOrder || []); + + return floors.sort((floorA, floorB) => { + const order = compare(floorA.floor_id, floorB.floor_id); + if (order !== 0) { + return order; + } + if (floorA.level !== floorB.level) { + return (floorA.level ?? 0) - (floorB.level ?? 0); + } + return stringCompare(floorA.name, floorB.name); + }); +}; + export const computeAreaPath = (areaId: string): string => `areas-${areaId}`; diff --git a/src/translations/en.json b/src/translations/en.json index a6505cf6e4..97c31b937f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6685,7 +6685,7 @@ "actions": "Actions", "others": "Others" }, - "others_areas": "Other areas", + "other_areas": "Other areas", "areas": "Areas" } }, From 2e223e637b45b211acb94ee41921ef81b991d5b4 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 30 Jun 2025 17:44:50 +0200 Subject: [PATCH 08/16] Improve device row in integration page (#26005) Improve device row in config entry page --- .../ha-config-entry-device-row.ts | 197 +++++++++--------- src/translations/en.json | 1 + 2 files changed, 104 insertions(+), 94 deletions(-) diff --git a/src/panels/config/integrations/ha-config-entry-device-row.ts b/src/panels/config/integrations/ha-config-entry-device-row.ts index f2654f61e3..6783240f46 100644 --- a/src/panels/config/integrations/ha-config-entry-device-row.ts +++ b/src/panels/config/integrations/ha-config-entry-device-row.ts @@ -1,9 +1,9 @@ import { - mdiCogOutline, mdiDelete, mdiDevices, mdiDotsVertical, mdiPencil, + mdiShapeOutline, mdiStopCircleOutline, mdiTransitConnectionVariant, } from "@mdi/js"; @@ -58,111 +58,118 @@ class HaConfigEntryDeviceRow extends LitElement { area ? area.name : undefined, ].filter(Boolean); - return html` - -
${computeDeviceNameDisplay(device, this.hass)} + return html` + +
${computeDeviceNameDisplay(device, this.hass)}
${supportingText.join(" • ")} ${supportingText.length && entities.length ? " • " : nothing} - ${ - entities.length - ? this.narrow - ? this.hass.localize( - "ui.panel.config.integrations.config_entry.entities", - { count: entities.length } - ) - : html`${this.hass.localize( - "ui.panel.config.integrations.config_entry.entities", - { count: entities.length } - )}` - : nothing - } - ${ - !this.narrow - ? html` - ` - : nothing - } - + ${!this.narrow + ? html` ` + : nothing}
- ${ - !this.narrow - ? html`` - : nothing - } - - + ${!this.narrow + ? html`` + : nothing} + + - ${ - this.narrow - ? html` - - ${this.hass.localize( - "ui.panel.config.integrations.config_entry.device.configure" - )} - ` - : nothing - } - - - - ${ - device.disabled_by && device.disabled_by !== "user" - ? this.hass.localize( - "ui.dialogs.device-registry-detail.enabled_cause", - { - type: this.hass.localize( - `ui.dialogs.device-registry-detail.type.${ - device.entry_type || "device" - }` - ), - cause: this.hass.localize( - `config_entry.disabled_by.${device.disabled_by}` - ), - } - ) - : device.disabled_by - ? this.hass.localize( - "ui.panel.config.integrations.config_entry.device.enable" - ) - : this.hass.localize( - "ui.panel.config.integrations.config_entry.device.disable" - ) - } - - - - ${ - this.entry.supports_remove_device - ? html` + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.device.edit" + )} + ` + : nothing} + ${entities.length + ? html` + - + ${this.hass.localize( - "ui.panel.config.integrations.config_entry.device.delete" + `ui.panel.config.integrations.config_entry.entities`, + { count: entities.length } )} - ` - : nothing - } + + + ` + : nothing} + + + + ${device.disabled_by && device.disabled_by !== "user" + ? this.hass.localize( + "ui.dialogs.device-registry-detail.enabled_cause", + { + type: this.hass.localize( + `ui.dialogs.device-registry-detail.type.${ + device.entry_type || "device" + }` + ), + cause: this.hass.localize( + `config_entry.disabled_by.${device.disabled_by}` + ), + } + ) + : device.disabled_by + ? this.hass.localize( + "ui.panel.config.integrations.config_entry.device.enable" + ) + : this.hass.localize( + "ui.panel.config.integrations.config_entry.device.disable" + )} + + ${this.entry.supports_remove_device + ? html` + + ${this.hass.localize( + "ui.panel.config.integrations.config_entry.device.delete" + )} + ` + : nothing}
`; } @@ -170,7 +177,7 @@ class HaConfigEntryDeviceRow extends LitElement { private _getEntities = (): EntityRegistryEntry[] => this.entities?.filter((entity) => entity.device_id === this.device.id); - private _handleConfigureDevice(ev: MouseEvent) { + private _handleEditDevice(ev: MouseEvent) { ev.stopPropagation(); // Prevent triggering the click handler on the list item showDeviceRegistryDetailDialog(this, { device: this.device, @@ -295,6 +302,8 @@ class HaConfigEntryDeviceRow extends LitElement { } ha-md-list-item { --md-list-item-leading-space: 56px; + --md-ripple-hover-color: transparent; + --md-ripple-pressed-color: transparent; } .disabled { opacity: 0.5; diff --git a/src/translations/en.json b/src/translations/en.json index 97c31b937f..e3c4613e11 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5383,6 +5383,7 @@ "confirm_disable_title": "Disable device?", "confirm_disable_message": "Are you sure you want to disable {name} and all of its entities?", "configure": "Configure device", + "edit": "Edit device", "delete": "Remove device" }, "devices": "{count} {count, plural,\n one {device}\n other {devices}\n}", From 6e84fee7913dd915590b702d75fd795ba5347021 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 30 Jun 2025 18:10:08 +0200 Subject: [PATCH 09/16] Do not display quality scale for custom integrations (#26006) --- src/panels/config/integrations/ha-config-integration-page.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panels/config/integrations/ha-config-integration-page.ts b/src/panels/config/integrations/ha-config-integration-page.ts index 4b11f37d86..744b3fd181 100644 --- a/src/panels/config/integrations/ha-config-integration-page.ts +++ b/src/panels/config/integrations/ha-config-integration-page.ts @@ -432,7 +432,8 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) { )} ` : nothing} - ${this._manifest?.quality_scale && + ${this._manifest?.is_built_in && + this._manifest.quality_scale && Object.keys(QUALITY_SCALE_MAP).includes( this._manifest.quality_scale ) From 57da4d34994ccf70a2d3076b4a014dc400980146 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 30 Jun 2025 18:15:37 +0200 Subject: [PATCH 10/16] Fix object selector not displayed (#26007) --- src/components/ha-selector/ha-selector-object.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/ha-selector/ha-selector-object.ts b/src/components/ha-selector/ha-selector-object.ts index bca45148b3..626699c4fe 100644 --- a/src/components/ha-selector/ha-selector-object.ts +++ b/src/components/ha-selector/ha-selector-object.ts @@ -122,11 +122,7 @@ export class HaObjectSelector extends LitElement { } protected render() { - if (!this.selector.object) { - return nothing; - } - - if (this.selector.object.fields) { + if (this.selector.object?.fields) { if (this.selector.object.multiple) { const items = ensureArray(this.value ?? []); return html` From d0737082a564046035c166059e41f470b80e84e5 Mon Sep 17 00:00:00 2001 From: Ezra Freedman <38084742+ezra-freedman@users.noreply.github.com> Date: Tue, 1 Jul 2025 02:48:13 -0400 Subject: [PATCH 11/16] Fix translation in the integration page for entities (#26009) add call to localize --- src/panels/config/integrations/ha-config-entry-row.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/panels/config/integrations/ha-config-entry-row.ts b/src/panels/config/integrations/ha-config-entry-row.ts index 41e843c0c4..37b1020096 100644 --- a/src/panels/config/integrations/ha-config-entry-row.ts +++ b/src/panels/config/integrations/ha-config-entry-row.ts @@ -154,7 +154,10 @@ class HaConfigEntryRow extends LitElement { statusLine.push( html`${entities.length} entities${this.hass.localize( + "ui.panel.config.integrations.config_entry.entities", + { count: entities.length } + )}` ); } From 93837f01f74db1e0a87e08e4dcee671be0d54c71 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 1 Jul 2025 14:32:54 +0200 Subject: [PATCH 12/16] Avoid selector to take to much space in action calls (#26014) --- src/components/ha-settings-row.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ha-settings-row.ts b/src/components/ha-settings-row.ts index c7994e8d57..fa946d81f8 100644 --- a/src/components/ha-settings-row.ts +++ b/src/components/ha-settings-row.ts @@ -89,6 +89,7 @@ export class HaSettingsRow extends LitElement { display: var(--settings-row-content-display, flex); justify-content: flex-end; flex: 1; + min-width: 0; padding: 16px 0; } .content ::slotted(*) { From 96bbfe8a93e7cfec6e9031addd57153a0381295f Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 1 Jul 2025 14:33:36 +0200 Subject: [PATCH 13/16] Add dashboard title to strategy editor (#26015) --- .../dialogs/dialog-dashboard-strategy-editor.ts | 3 +++ .../dialogs/show-dialog-dashboard-strategy-editor.ts | 1 + src/panels/lovelace/hui-root.ts | 1 + 3 files changed, 5 insertions(+) diff --git a/src/panels/lovelace/editor/dashboard-strategy-editor/dialogs/dialog-dashboard-strategy-editor.ts b/src/panels/lovelace/editor/dashboard-strategy-editor/dialogs/dialog-dashboard-strategy-editor.ts index b9d5a40aed..c57efcc38e 100644 --- a/src/panels/lovelace/editor/dashboard-strategy-editor/dialogs/dialog-dashboard-strategy-editor.ts +++ b/src/panels/lovelace/editor/dashboard-strategy-editor/dialogs/dialog-dashboard-strategy-editor.ts @@ -144,6 +144,9 @@ class DialogDashboardStrategyEditor extends LitElement { .path=${mdiClose} > ${title} + ${this._params.title + ? html`${this._params.title}` + : nothing} void; takeControl: () => void; deleteDashboard: () => Promise; diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 6f3d727d4d..1a6f09f7a7 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -782,6 +782,7 @@ class HUIRoot extends LitElement { showDashboardStrategyEditorDialog(this, { config: this.lovelace!.rawConfig, + title: this.panel ? getPanelTitle(this.hass, this.panel) : undefined, saveConfig: this.lovelace!.saveConfig, takeControl: () => { showSaveDialog(this, { From e0b32ea78908df497d52914e34745f779b27715a Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 1 Jul 2025 14:34:19 +0200 Subject: [PATCH 14/16] Increase target area in tile card and area card (#26017) --- src/components/ha-control-button-group.ts | 1 - .../common/card-feature-styles.ts | 3 --- .../hui-area-controls-card-feature.ts | 23 +++++++++++++++++-- .../card-features/hui-card-feature.ts | 8 ++++++- .../card-features/hui-card-features.ts | 1 + 5 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/components/ha-control-button-group.ts b/src/components/ha-control-button-group.ts index fa9d0717c1..5c59d0801f 100644 --- a/src/components/ha-control-button-group.ts +++ b/src/components/ha-control-button-group.ts @@ -26,7 +26,6 @@ export class HaControlButtonGroup extends LitElement { .container { display: flex; flex-direction: row; - justify-content: var(--control-button-group-alignment, start); width: 100%; height: 100%; } diff --git a/src/panels/lovelace/card-features/common/card-feature-styles.ts b/src/panels/lovelace/card-features/common/card-feature-styles.ts index 15a6cebcd1..799570cc99 100644 --- a/src/panels/lovelace/card-features/common/card-feature-styles.ts +++ b/src/panels/lovelace/card-features/common/card-feature-styles.ts @@ -25,9 +25,6 @@ export const cardFeatureStyles = css` flex-basis: 20px; --control-button-padding: 0px; } - ha-control-button-group[no-stretch] > ha-control-button { - max-width: 48px; - } ha-control-button { --control-button-focus-color: var(--feature-color); } diff --git a/src/panels/lovelace/card-features/hui-area-controls-card-feature.ts b/src/panels/lovelace/card-features/hui-area-controls-card-feature.ts index 441684a477..705f02855a 100644 --- a/src/panels/lovelace/card-features/hui-area-controls-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-area-controls-card-feature.ts @@ -1,5 +1,6 @@ import type { HassEntity } from "home-assistant-js-websocket"; import { css, html, LitElement, nothing } from "lit"; +import { classMap } from "lit/directives/class-map"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; @@ -229,7 +230,11 @@ class HuiAreaControlsCardFeature } return html` - + ${displayControls.map((control) => { const button = AREA_CONTROLS_BUTTONS[control]; @@ -292,8 +297,22 @@ class HuiAreaControlsCardFeature return [ cardFeatureStyles, css` + :host { + pointer-events: none !important; + display: flex; + flex-direction: row; + justify-content: flex-end; + } ha-control-button-group { - --control-button-group-alignment: flex-end; + pointer-events: auto; + width: 100%; + } + ha-control-button-group.no-stretch { + width: auto; + max-width: 100%; + } + ha-control-button-group.no-stretch > ha-control-button { + width: 48px; } ha-control-button { --active-color: var(--state-active-color); diff --git a/src/panels/lovelace/card-features/hui-card-feature.ts b/src/panels/lovelace/card-features/hui-card-feature.ts index fa92cc89ae..9dae99634c 100644 --- a/src/panels/lovelace/card-features/hui-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-card-feature.ts @@ -1,4 +1,4 @@ -import { LitElement, html, nothing } from "lit"; +import { LitElement, css, html, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import type { HomeAssistant } from "../../../types"; import type { HuiErrorCard } from "../cards/hui-error-card"; @@ -56,6 +56,12 @@ export class HuiCardFeature extends LitElement { } return html`${element}`; } + + static styles = css` + :host > * { + pointer-events: auto; + } + `; } declare global { diff --git a/src/panels/lovelace/card-features/hui-card-features.ts b/src/panels/lovelace/card-features/hui-card-features.ts index 138e639657..21227fe0fa 100644 --- a/src/panels/lovelace/card-features/hui-card-features.ts +++ b/src/panels/lovelace/card-features/hui-card-features.ts @@ -46,6 +46,7 @@ export class HuiCardFeatures extends LitElement { --feature-height: 42px; --feature-border-radius: 12px; --feature-button-spacing: 12px; + pointer-events: none; position: relative; width: 100%; display: flex; From 61f1c8cbd47760008b1d17bad3b9f6bc78a5deb2 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 1 Jul 2025 14:22:55 +0200 Subject: [PATCH 15/16] Force narrow style for action, condition and trigger in blueprint (#26018) --- src/panels/config/blueprint/blueprint-generic-editor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/blueprint/blueprint-generic-editor.ts b/src/panels/config/blueprint/blueprint-generic-editor.ts index 97d6ad7442..844b6f5abb 100644 --- a/src/panels/config/blueprint/blueprint-generic-editor.ts +++ b/src/panels/config/blueprint/blueprint-generic-editor.ts @@ -173,6 +173,7 @@ export abstract class HaBlueprintGenericEditor extends LitElement { .content=${value?.description} > ${html` Date: Tue, 1 Jul 2025 15:02:51 +0200 Subject: [PATCH 16/16] Bumped version to 20250701.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 46d2199bb2..360873cc72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250627.0" +version = "20250701.0" license = "Apache-2.0" license-files = ["LICENSE*"] description = "The Home Assistant frontend"