diff --git a/demo/public/assets/sections/images/media_player_family_room.jpg b/demo/public/assets/sections/images/media_player_family_room.jpg new file mode 100644 index 000000000000..e73833aa3051 Binary files /dev/null and b/demo/public/assets/sections/images/media_player_family_room.jpg differ diff --git a/demo/src/configs/sections/entities.ts b/demo/src/configs/sections/entities.ts index 1900624b8304..1bdb06f8a9ad 100644 --- a/demo/src/configs/sections/entities.ts +++ b/demo/src/configs/sections/entities.ts @@ -1,7 +1,7 @@ import { convertEntities } from "../../../../src/fake_data/entity"; import { DemoConfig } from "../types"; -export const demoEntitiesSections: DemoConfig["entities"] = () => +export const demoEntitiesSections: DemoConfig["entities"] = (localize) => convertEntities({ "cover.living_room_garden_shutter": { entity_id: "cover.living_room_garden_shutter", @@ -113,11 +113,30 @@ export const demoEntitiesSections: DemoConfig["entities"] = () => }, "media_player.living_room_nest_mini": { entity_id: "media_player.living_room_nest_mini", - state: "off", + state: "on", attributes: { device_class: "speaker", - friendly_name: "Living room Nest Mini", - supported_features: 152461, + volume_level: 0.18, + is_volume_muted: false, + media_content_type: "music", + media_duration: 300, + media_position: 0, + media_position_updated_at: new Date( + // 23 seconds in + new Date().getTime() - 23000 + ).toISOString(), + media_title: "I Wasn't Born To Follow", + media_artist: "The Byrds", + media_album_name: "The Notorious Byrd Brothers", + source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"], + shuffle: false, + night_sound: false, + speech_enhance: false, + friendly_name: localize( + "ui.panel.page-demo.config.sections.entities.media_player.living_room_nest_mini" + ), + entity_picture: "/assets/sections/images/media_player_family_room.jpg", + supported_features: 64063, }, }, "cover.kitchen_shutter": { @@ -168,8 +187,27 @@ export const demoEntitiesSections: DemoConfig["entities"] = () => state: "on", attributes: { device_class: "speaker", - friendly_name: "Kitchen Nest Audio", - supported_features: 152461, + volume_level: 0.18, + is_volume_muted: false, + media_content_type: "music", + media_duration: 300, + media_position: 0, + media_position_updated_at: new Date( + // 23 seconds in + new Date().getTime() - 23000 + ).toISOString(), + media_title: "I Wasn't Born To Follow", + media_artist: "The Byrds", + media_album_name: "The Notorious Byrd Brothers", + source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"], + shuffle: false, + night_sound: false, + speech_enhance: false, + friendly_name: localize( + "ui.panel.page-demo.config.sections.entities.media_player.kitchen_nest_audio" + ), + entity_picture: "/assets/sections/images/media_player_family_room.jpg", + supported_features: 64063, }, }, "binary_sensor.tesla_wall_connector_vehicle_connected": { @@ -333,8 +371,28 @@ export const demoEntitiesSections: DemoConfig["entities"] = () => entity_id: "media_player.study_nest_hub", state: "off", attributes: { - friendly_name: "Study Nest Hub", - supported_features: 152461, + device_class: "speaker", + volume_level: 0.18, + is_volume_muted: false, + media_content_type: "music", + media_duration: 300, + media_position: 0, + media_position_updated_at: new Date( + // 23 seconds in + new Date().getTime() - 23000 + ).toISOString(), + media_title: "I Wasn't Born To Follow", + media_artist: "The Byrds", + media_album_name: "The Notorious Byrd Brothers", + source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"], + shuffle: false, + night_sound: false, + speech_enhance: false, + friendly_name: localize( + "ui.panel.page-demo.config.sections.entities.media_player.study_nest_hub" + ), + entity_picture: "/assets/sections/images/media_player_family_room.jpg", + supported_features: 64063, }, }, "sensor.standing_desk_height": { diff --git a/demo/src/configs/sections/lovelace.ts b/demo/src/configs/sections/lovelace.ts index 36b4abb2fe9c..f09c0595c6da 100644 --- a/demo/src/configs/sections/lovelace.ts +++ b/demo/src/configs/sections/lovelace.ts @@ -1,7 +1,7 @@ import { isFrontpageEmbed } from "../../util/is_frontpage"; import { DemoConfig } from "../types"; -export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ +export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ title: "Home Assistant Demo", views: [ { @@ -14,7 +14,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ ? [] : [ { - title: "Welcome 👋", + title: `${localize("ui.panel.page-demo.config.sections.titles.welcome")} 👋`, cards: [{ type: "custom:ha-demo-card" }], }, ]), @@ -53,10 +53,9 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ { type: "tile", entity: "media_player.living_room_nest_mini", - name: "Nest Mini", }, ], - title: "🛋️ Living room ", + title: `🛋️ ${localize("ui.panel.page-demo.config.sections.titles.living_room")} `, }, { type: "grid", @@ -89,10 +88,9 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ { type: "tile", entity: "media_player.kitchen_nest_audio", - name: "Nest Audio", }, ], - title: "👩‍🍳 Kitchen", + title: `👩‍🍳 ${localize("ui.panel.page-demo.config.sections.titles.kitchen")}`, }, { type: "grid", @@ -134,7 +132,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ color: "dark-grey", }, ], - title: "⚡️ Energy", + title: `⚡️ ${localize("ui.panel.page-demo.config.sections.titles.energy")}`, }, { type: "grid", @@ -171,7 +169,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ state_content: ["preset_mode", "current_temperature"], }, ], - title: "🌤️ Climate", + title: `🌤️ ${localize("ui.panel.page-demo.config.sections.titles.climate")}`, }, { type: "grid", @@ -189,7 +187,6 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ { type: "tile", entity: "media_player.study_nest_hub", - name: "Nest Hub", }, { type: "tile", @@ -199,7 +196,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ icon: "mdi:desk", }, ], - title: "🧑‍💻 Study", + title: `🧑‍💻 ${localize("ui.panel.page-demo.config.sections.titles.study")}`, }, { type: "grid", @@ -233,7 +230,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ name: "Illuminance", }, ], - title: "🌳 Outdoor", + title: `🌳 ${localize("ui.panel.page-demo.config.sections.titles.outdoor")}`, }, { type: "grid", @@ -263,7 +260,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ icon: "mdi:home-assistant", }, ], - title: "🎉 Updates", + title: `🎉 ${localize("ui.panel.page-demo.config.sections.titles.updates")}`, }, ], }, diff --git a/demo/src/stubs/entities.ts b/demo/src/stubs/entities.ts index 132515a014ad..8c863f9a3bb6 100644 --- a/demo/src/stubs/entities.ts +++ b/demo/src/stubs/entities.ts @@ -1,5 +1,55 @@ import { convertEntities } from "../../../src/fake_data/entity"; +export const mapEntities = () => + convertEntities({ + "zone.home": { + entity_id: "zone.home", + state: "zoning", + attributes: { + hidden: true, + latitude: 52.3631339, + longitude: 4.8903147, + radius: 200, + friendly_name: "Home", + icon: "hademo:home", + }, + }, + "zone.uva": { + entity_id: "zone.buckhead", + state: "zoning", + attributes: { + hidden: true, + radius: 400, + friendly_name: "UvA", + icon: "hademo:school", + latitude: 52.3558182, + longitude: 4.9535376, + }, + }, + "person.arsaboo": { + entity_id: "person.arsaboo", + state: "not_home", + attributes: { + radius: 50, + friendly_name: "Arsaboo", + latitude: 52.3579946, + longitude: 4.8664597, + entity_picture: "/assets/arsaboo/images/arsaboo.jpg", + }, + }, + "person.melody": { + entity_id: "person.melody", + state: "not_home", + attributes: { + radius: 50, + friendly_name: "Melody", + latitude: 52.3408927, + longitude: 4.8711073, + entity_picture: "/assets/arsaboo/images/melody.jpg", + }, + }, + }); + export const energyEntities = () => convertEntities({ "sensor.grid_fossil_fuel_percentage": { diff --git a/demo/src/stubs/lovelace.ts b/demo/src/stubs/lovelace.ts index 6896de37ef09..8712f51a66a2 100644 --- a/demo/src/stubs/lovelace.ts +++ b/demo/src/stubs/lovelace.ts @@ -7,16 +7,25 @@ import { } from "../configs/demo-configs"; import "../custom-cards/cast-demo-row"; import "../custom-cards/ha-demo-card"; +import { mapEntities } from "./entities"; export const mockLovelace = ( hass: MockHomeAssistant, localizePromise: Promise ) => { - hass.mockWS("lovelace/config", () => - Promise.all([selectedDemoConfig, localizePromise]).then( + hass.mockWS("lovelace/config", ({ url_path }) => { + if (url_path === "map") { + hass.addEntities(mapEntities()); + return { + strategy: { + type: "map", + }, + }; + } + return Promise.all([selectedDemoConfig, localizePromise]).then( ([config, localize]) => config.lovelace(localize) - ) - ); + ); + }); hass.mockWS("lovelace/config/save", () => Promise.resolve()); hass.mockWS("lovelace/resources", () => Promise.resolve([])); diff --git a/gallery/src/pages/Text/remove-delete-add-create.markdown b/gallery/src/pages/Text/remove-delete-add-create.markdown index 89c61db6383a..3b0bb85c3676 100644 --- a/gallery/src/pages/Text/remove-delete-add-create.markdown +++ b/gallery/src/pages/Text/remove-delete-add-create.markdown @@ -3,13 +3,16 @@ title: When to use remove, delete, add and create subtitle: The difference between remove/delete and add/create. --- -# Remove vs Delete +# Removing or deleting content -Remove and Delete are quite similar, but can be frustrating if used inconsistently. +_Remove_ and _Delete_ are quite similar, but can be frustrating if used inconsistently. + +- Remove refers to an action that can be restored or reapplied. +- Delete refers to a permanent, non-recoverable action. ## Remove -Take away and set aside, but kept in existence. +The term _Remove_ should always be used when an item/setting or content is to be removed or disassociated, but the action can be reversed or reapplied. For example: @@ -22,7 +25,7 @@ For example: ## Delete -Erase, rendered nonexistent or nonrecoverable. +The term _Delete_ should always be used to refer to any action that will cause the permanent deletion of an item/setting or content. For example: diff --git a/package.json b/package.json index 292983f4ad45..baf9e0afd94f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@codemirror/legacy-modes": "6.4.0", "@codemirror/search": "6.5.6", "@codemirror/state": "6.4.1", - "@codemirror/view": "6.28.2", + "@codemirror/view": "6.28.3", "@egjs/hammerjs": "2.0.17", "@formatjs/intl-datetimeformat": "6.12.5", "@formatjs/intl-displaynames": "6.6.8", @@ -157,7 +157,7 @@ "@babel/preset-typescript": "7.24.7", "@bundle-stats/plugin-webpack-filter": "4.13.3", "@koa/cors": "5.0.0", - "@lokalise/node-api": "12.5.0", + "@lokalise/node-api": "12.6.0", "@octokit/auth-oauth-device": "7.1.1", "@octokit/plugin-retry": "7.1.1", "@octokit/rest": "21.0.0", @@ -185,8 +185,8 @@ "@types/tar": "6.1.13", "@types/ua-parser-js": "0.7.39", "@types/webspeechapi": "0.0.29", - "@typescript-eslint/eslint-plugin": "7.14.1", - "@typescript-eslint/parser": "7.14.1", + "@typescript-eslint/eslint-plugin": "7.15.0", + "@typescript-eslint/parser": "7.15.0", "@web/dev-server": "0.1.38", "@web/dev-server-rollup": "0.4.1", "babel-loader": "9.1.3", @@ -237,7 +237,7 @@ "terser-webpack-plugin": "5.3.10", "transform-async-modules-webpack-plugin": "1.1.1", "ts-lit-plugin": "2.0.2", - "typescript": "5.5.2", + "typescript": "5.5.3", "webpack": "5.92.1", "webpack-cli": "5.1.4", "webpack-dev-server": "5.0.4", diff --git a/pyproject.toml b/pyproject.toml index 6aa93e24f7a8..402d7c3b98b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20240703.0" +version = "20240705.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index bd1507e2eee0..1819256bab0c 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -34,6 +34,7 @@ import type { HaCheckbox } from "../ha-checkbox"; import "../ha-svg-icon"; import "../search-input"; import { filterData, sortData } from "./sort-filter"; +import { LocalizeFunc } from "../../common/translations/localize"; export interface RowClickedEvent { id: string; @@ -110,6 +111,8 @@ const UNDEFINED_GROUP_KEY = "zzzzz_undefined"; export class HaDataTable extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; + @property({ attribute: false }) public localizeFunc?: LocalizeFunc; + @property({ type: Boolean }) public narrow = false; @property({ type: Object }) public columns: DataTableColumnContainer = {}; @@ -317,6 +320,8 @@ export class HaDataTable extends LitElement { ); protected render() { + const localize = this.localizeFunc || this.hass.localize; + const columns = this._sortedColumns(this.columns, this.columnOrder); const renderRow = (row: DataTableRowData, index: number) => @@ -436,7 +441,7 @@ export class HaDataTable extends LitElement {
${this.noDataText || - this.hass.localize("ui.components.data-table.no-data")} + localize("ui.components.data-table.no-data")}
@@ -619,6 +624,8 @@ export class HaDataTable extends LitElement { return; } + const localize = this.localizeFunc || this.hass.localize; + if (this.appendRow || this.hasFab || this.groupColumn) { let items = [...data]; @@ -672,7 +679,7 @@ export class HaDataTable extends LitElement { > ${groupName === UNDEFINED_GROUP_KEY - ? this.hass.localize("ui.components.data-table.ungrouped") + ? localize("ui.components.data-table.ungrouped") : groupName || ""} `, }); diff --git a/src/components/ha-grid-size-picker.ts b/src/components/ha-grid-size-picker.ts index 453a24cb8d30..6ae6b7b37d75 100644 --- a/src/components/ha-grid-size-picker.ts +++ b/src/components/ha-grid-size-picker.ts @@ -10,7 +10,7 @@ import { HomeAssistant } from "../types"; import { conditionalClamp } from "../common/number/clamp"; type GridSizeValue = { - rows?: number; + rows?: number | "auto"; columns?: number; }; @@ -47,6 +47,16 @@ export class HaGridSizeEditor extends LitElement { this.columnMin !== undefined && this.columnMin === this.columnMax; const disabledRows = this.rowMin !== undefined && this.rowMin === this.rowMax; + + const autoHeight = this._localValue?.rows === "auto"; + + const rowMin = this.rowMin ?? 1; + const rowMax = this.rowMax ?? this.rows; + const columnMin = this.columnMin ?? 1; + const columnMax = this.columnMax ?? this.columns; + const rowValue = autoHeight ? rowMin : this._localValue?.rows; + const columnValue = this._localValue?.columns; + return html`
+
@@ -215,10 +226,6 @@ export class HaGridSizeEditor extends LitElement { opacity: 0.2; cursor: pointer; } - .preview .cell[disabled] { - opacity: 0.05; - cursor: initial; - } .selected { pointer-events: none; } diff --git a/src/components/ha-menu-item.ts b/src/components/ha-menu-item.ts index b6b133c68115..2f2914859640 100644 --- a/src/components/ha-menu-item.ts +++ b/src/components/ha-menu-item.ts @@ -1,9 +1,11 @@ import { MdMenuItem } from "@material/web/menu/menu-item"; import { css } from "lit"; -import { customElement } from "lit/decorators"; +import { customElement, property } from "lit/decorators"; @customElement("ha-menu-item") export class HaMenuItem extends MdMenuItem { + @property({ attribute: false }) clickAction?: (item?: HTMLElement) => void; + static override styles = [ ...super.styles, css` diff --git a/src/components/ha-menu.ts b/src/components/ha-menu.ts index 06c2d5f91def..06e1de186329 100644 --- a/src/components/ha-menu.ts +++ b/src/components/ha-menu.ts @@ -1,9 +1,30 @@ import { MdMenu } from "@material/web/menu/menu"; +import type { CloseMenuEvent } from "@material/web/menu/menu"; +import { + CloseReason, + KeydownCloseKey, +} from "@material/web/menu/internal/controllers/shared"; import { css } from "lit"; import { customElement } from "lit/decorators"; +import type { HaMenuItem } from "./ha-menu-item"; @customElement("ha-menu") export class HaMenu extends MdMenu { + connectedCallback(): void { + super.connectedCallback(); + this.addEventListener("close-menu", this._handleCloseMenu); + } + + private _handleCloseMenu(ev: CloseMenuEvent) { + if ( + ev.detail.reason.kind === CloseReason.KEYDOWN && + ev.detail.reason.key === KeydownCloseKey.ESCAPE + ) { + return; + } + (ev.detail.initiator as HaMenuItem).clickAction?.(ev.detail.initiator); + } + static override styles = [ ...super.styles, css` @@ -18,4 +39,8 @@ declare global { interface HTMLElementTagNameMap { "ha-menu": HaMenu; } + + interface HTMLElementEventMap { + "close-menu": CloseMenuEvent; + } } diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 33413df92be3..2e629bd5e2bd 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -325,7 +325,7 @@ export class MoreInfoDialog extends LitElement { > ` : nothing} - ${isAdmin + ${!__DEMO__ && isAdmin ? html` ${this.hass.localize("ui.dialogs.more_info_control.history")}
- ${this.hass.localize( - "ui.dialogs.more_info_control.show_more" - )} + ${__DEMO__ + ? nothing + : html`${this.hass.localize( + "ui.dialogs.more_info_control.show_more" + )}`}
${this._error ? html`
${this._error}
` diff --git a/src/fake_data/demo_panels.ts b/src/fake_data/demo_panels.ts index 319aa4997827..dbf70b4b1df8 100644 --- a/src/fake_data/demo_panels.ts +++ b/src/fake_data/demo_panels.ts @@ -66,10 +66,10 @@ export const demoPanels: Panels = { // url_path: "history", // }, map: { - component_name: "map", + component_name: "lovelace", icon: "hass:tooltip-account", title: "map", - config: null, + config: { mode: "storage" }, url_path: "map", }, energy: { diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index 4f062182a2dc..615fb2d5d056 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -430,6 +430,7 @@ export class HaTabsSubpageDataTable extends LitElement { : ""}
- ${this.hass.localize( - "ui.components.subpage-data-table.show_results", - { number: this.data.length } - )} + ${localize("ui.components.subpage-data-table.show_results", { + number: this.data.length, + })}
` diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index 3c0f0d10df8c..7bef54309e4b 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -68,6 +68,7 @@ import "../../../components/ha-icon-overflow-menu"; import "../../../components/ha-menu"; import type { HaMenu } from "../../../components/ha-menu"; import "../../../components/ha-menu-item"; +import type { HaMenuItem } from "../../../components/ha-menu-item"; import "../../../components/ha-sub-menu"; import "../../../components/ha-svg-icon"; import { createAreaRegistryEntry } from "../../../data/area_registry"; @@ -826,7 +827,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { - + - +
${this.hass.localize( @@ -844,7 +845,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { )}
- +
${this.hass.localize( @@ -852,13 +853,13 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { )}
- +
${this.hass.localize("ui.panel.config.automation.editor.run")}
- +
${this.hass.localize( @@ -867,13 +868,13 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
- +
${this.hass.localize("ui.panel.config.automation.picker.duplicate")}
- + - +
${this.hass.localize("ui.panel.config.automation.picker.delete")} @@ -1055,28 +1056,32 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { this._applyFilters(); } - private _showInfo(ev) { - const automation = ev.currentTarget.parentElement.anchorElement.automation; + private _showInfo = (item: HaMenuItem) => { + const automation = ((item.parentElement as HaMenu)!.anchorElement as any)! + .automation; fireEvent(this, "hass-more-info", { entityId: automation.entity_id }); - } + }; - private _showSettings(ev) { - const automation = ev.currentTarget.parentElement.anchorElement.automation; + private _showSettings = (item: HaMenuItem) => { + const automation = ((item.parentElement as HaMenu)!.anchorElement as any)! + .automation; fireEvent(this, "hass-more-info", { entityId: automation.entity_id, view: "settings", }); - } + }; - private _runActions(ev) { - const automation = ev.currentTarget.parentElement.anchorElement.automation; + private _runActions = (item: HaMenuItem) => { + const automation = ((item.parentElement as HaMenu)!.anchorElement as any)! + .automation; triggerAutomationActions(this.hass, automation.entity_id); - } + }; - private _editCategory(ev) { - const automation = ev.currentTarget.parentElement.anchorElement.automation; + private _editCategory = (item: HaMenuItem) => { + const automation = ((item.parentElement as HaMenu)!.anchorElement as any)! + .automation; const entityReg = this._entityReg.find( (reg) => reg.entity_id === automation.entity_id @@ -1096,10 +1101,11 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { scope: "automation", entityReg, }); - } + }; - private _showTrace(ev) { - const automation = ev.currentTarget.parentElement.anchorElement.automation; + private _showTrace = (item: HaMenuItem) => { + const automation = ((item.parentElement as HaMenu)!.anchorElement as any)! + .automation; if (!automation.attributes.id) { showAlertDialog(this, { @@ -1112,19 +1118,21 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { navigate( `/config/automation/trace/${encodeURIComponent(automation.attributes.id)}` ); - } + }; - private async _toggle(ev): Promise { - const automation = ev.currentTarget.parentElement.anchorElement.automation; + private _toggle = async (item: HaMenuItem): Promise => { + const automation = ((item.parentElement as HaMenu)!.anchorElement as any)! + .automation; const service = automation.state === "off" ? "turn_on" : "turn_off"; await this.hass.callService("automation", service, { entity_id: automation.entity_id, }); - } + }; - private async _deleteConfirm(ev) { - const automation = ev.currentTarget.parentElement.anchorElement.automation; + private _deleteConfirm = async (item: HaMenuItem) => { + const automation = ((item.parentElement as HaMenu)!.anchorElement as any)! + .automation; showConfirmationDialog(this, { title: this.hass.localize( @@ -1139,7 +1147,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { confirm: () => this._delete(automation), destructive: true, }); - } + }; private async _delete(automation) { try { @@ -1159,8 +1167,9 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { } } - private async _duplicate(ev) { - const automation = ev.currentTarget.parentElement.anchorElement.automation; + private _duplicate = async (item: HaMenuItem) => { + const automation = ((item.parentElement as HaMenu)!.anchorElement as any)! + .automation; try { const config = await fetchAutomationFileConfig( @@ -1184,7 +1193,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { ), }); } - } + }; private _showHelp() { showAlertDialog(this, { diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 7b3849ec665a..1a9809b9b68a 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -1,6 +1,5 @@ import { consume } from "@lit-labs/context"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; -import "@material/mwc-list/mwc-list-item"; import { mdiCog, mdiDelete, @@ -10,8 +9,6 @@ import { mdiPencil, mdiPlusCircle, } from "@mdi/js"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; import { CSSResultGroup, LitElement, @@ -24,7 +21,7 @@ import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import memoizeOne from "memoize-one"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; -import { SENSOR_ENTITIES, ASSIST_ENTITIES } from "../../../common/const"; +import { ASSIST_ENTITIES, SENSOR_ENTITIES } from "../../../common/const"; import { computeDomain } from "../../../common/entity/compute_domain"; import { computeStateDomain } from "../../../common/entity/compute_state_domain"; import { computeStateName } from "../../../common/entity/compute_state_name"; @@ -77,7 +74,6 @@ import type { HomeAssistant } from "../../../types"; import { brandsUrl } from "../../../util/brands-url"; import { fileDownload } from "../../../util/file_download"; import "../../logbook/ha-logbook"; -import "../ha-config-section"; import "./device-detail/ha-device-entities-card"; import "./device-detail/ha-device-info-card"; import "./device-detail/ha-device-via-devices-card"; @@ -665,269 +661,235 @@ export class HaConfigDevicePage extends LitElement { ` : ""; - return html` - - - + +
+
+ ${area + ? html`` + : ""} +
+ ${battery && + (batteryDomain === "binary_sensor" || !isNaN(battery.state as any)) + ? html` +
+ ${batteryDomain === "sensor" + ? this.hass.formatEntityState(battery) + : nothing} + +
+ ` + : ""} + ${integrations.length + ? html` + ${domainToName( + ` + : ""} +
+
+
+ ${this._deviceAlerts?.length + ? html` +
+ ${this._deviceAlerts.map( + (alert) => html` + + ${alert.text} + + ` )} - > -
-
- ${ - area - ? html`` - : "" - } -
- ${ - battery && - (batteryDomain === "binary_sensor" || - !isNaN(battery.state as any)) - ? html` -
- ${batteryDomain === "sensor" - ? this.hass.formatEntityState(battery) - : nothing} - -
- ` - : "" - } - ${ - integrations.length - ? html` - ${domainToName( - ` - : "" - } -
-
-
- ${ - this._deviceAlerts?.length - ? html` -
- ${this._deviceAlerts.map( - (alert) => html` - - ${alert.text} - - ` + ` + : ""} + + ${deviceInfo} + ${firstDeviceAction || actions.length + ? html` +
+
+ - ` - : "" - } - - ${deviceInfo} - ${ - firstDeviceAction || actions.length - ? html` -
- + + ${actions.length + ? html` + + + ${actions.map((deviceAction) => { + const listItem = html` - ${firstDeviceAction!.label} - ${firstDeviceAction!.icon + ${deviceAction.label} + ${deviceAction.icon ? html` ` : ""} - ${firstDeviceAction!.trailingIcon + ${deviceAction.trailingIcon ? html` ` : ""} - - -
- - ${actions.length - ? html` - - `; + return deviceAction.href + ? html` - ${actions.map((deviceAction) => { - const listItem = html` - ${deviceAction.label} - ${deviceAction.icon - ? html` - - ` - : ""} - ${deviceAction.trailingIcon - ? html` - - ` - : ""} - `; - return deviceAction.href - ? html`${listItem} - ` - : listItem; - })} - - ` - : ""} -
- ` - : "" - } - - ${!this.narrow ? [automationCard, sceneCard, scriptCard] : ""} -
-
- ${( - [ - "control", - "sensor", - "notify", - "event", - "assist", - "config", - "diagnostic", - ] as const - ).map((category) => - // Make sure we render controls if no other cards will be rendered - entitiesByCategory[category].length > 0 || - (entities.length === 0 && category === "control") - ? html` - - - ` - : "" - )} - -
-
- ${this.narrow ? [automationCard, sceneCard, scriptCard] : ""} - ${ - isComponentLoaded(this.hass, "logbook") - ? html` - -

- ${this.hass.localize("panel.logbook")} -

- -
- ` - : "" - } -
-
- - `; + >${listItem} + ` + : listItem; + })} + + ` + : ""} +
+ ` + : ""} + + ${!this.narrow ? [automationCard, sceneCard, scriptCard] : ""} +
+
+ ${( + [ + "control", + "sensor", + "notify", + "event", + "assist", + "config", + "diagnostic", + ] as const + ).map((category) => + // Make sure we render controls if no other cards will be rendered + entitiesByCategory[category].length > 0 || + (entities.length === 0 && category === "control") + ? html` + + + ` + : "" + )} + +
+
+ ${this.narrow ? [automationCard, sceneCard, scriptCard] : ""} + ${isComponentLoaded(this.hass, "logbook") + ? html` + +

+ ${this.hass.localize("panel.logbook")} +

+ +
+ ` + : ""} +
+
+ `; } private async _getDiagnosticButtons(requestId: number): Promise { diff --git a/src/panels/config/tags/ha-config-tags.ts b/src/panels/config/tags/ha-config-tags.ts index 98ae4252f802..933490adfe3c 100644 --- a/src/panels/config/tags/ha-config-tags.ts +++ b/src/panels/config/tags/ha-config-tags.ts @@ -301,12 +301,13 @@ export class HaConfigTags extends SubscribeMixin(LitElement) { private async _removeTag(selectedTag: Tag) { if ( !(await showConfirmationDialog(this, { - title: this.hass!.localize("ui.panel.config.tag.confirm_remove_title"), - text: this.hass.localize("ui.panel.config.tag.confirm_remove", { + title: this.hass!.localize("ui.panel.config.tag.confirm_delete_title"), + text: this.hass.localize("ui.panel.config.tag.confirm_delete", { tag: selectedTag.name || selectedTag.id, }), dismissText: this.hass!.localize("ui.common.cancel"), - confirmText: this.hass!.localize("ui.common.remove"), + confirmText: this.hass!.localize("ui.common.delete"), + destructive: true, })) ) { return false; diff --git a/src/panels/lovelace/cards/energy/hui-energy-date-selection-card.ts b/src/panels/lovelace/cards/energy/hui-energy-date-selection-card.ts index d6ab8eb4929a..0b2ff5fa817b 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-date-selection-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-date-selection-card.ts @@ -1,17 +1,17 @@ import { - html, - LitElement, - nothing, - css, CSSResultGroup, + LitElement, PropertyValues, + css, + html, + nothing, } from "lit"; import { customElement, property, state } from "lit/decorators"; import { HomeAssistant } from "../../../../types"; +import { hasConfigChanged } from "../../common/has-changed"; import "../../components/hui-energy-period-selector"; -import { LovelaceCard } from "../../types"; +import { LovelaceCard, LovelaceLayoutOptions } from "../../types"; import { EnergyCardBaseConfig } from "../types"; -import { hasConfigChanged } from "../../common/has-changed"; @customElement("hui-energy-date-selection-card") export class HuiEnergyDateSelectionCard @@ -26,6 +26,13 @@ export class HuiEnergyDateSelectionCard return 1; } + public getLayoutOptions(): LovelaceLayoutOptions { + return { + grid_rows: 1, + grid_columns: 4, + }; + } + public setConfig(config: EnergyCardBaseConfig): void { this._config = config; } @@ -57,6 +64,13 @@ export class HuiEnergyDateSelectionCard static get styles(): CSSResultGroup { return css` + :host { + ha-card { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + } .padded { padding-left: 16px !important; padding-inline-start: 16px !important; diff --git a/src/panels/lovelace/cards/hui-entity-card.ts b/src/panels/lovelace/cards/hui-entity-card.ts index 42861e70fe4e..3e06cf7bf197 100644 --- a/src/panels/lovelace/cards/hui-entity-card.ts +++ b/src/panels/lovelace/cards/hui-entity-card.ts @@ -8,6 +8,7 @@ import { PropertyValues, } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; @@ -73,6 +74,8 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { @property({ attribute: false }) public hass?: HomeAssistant; + @property() public layout?: string; + @state() private _config?: EntityCardConfig; private _footerElement?: HuiErrorCard | LovelaceHeaderFooter; @@ -132,8 +135,15 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { const colored = stateObj && this.getStateColor(stateObj, this._config); + const fixedFooter = + this.layout === "grid" || this._footerElement !== undefined; + return html` - +
${name}
@@ -188,7 +198,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { ` : ""}
- ${this._footerElement} + `; } @@ -309,6 +319,16 @@ export class HuiEntityCard extends LitElement implements LovelaceCard { font-size: 18px; color: var(--secondary-text-color); } + + .with-fixed-footer { + justify-content: flex-start; + } + .with-fixed-footer .footer { + position: absolute; + right: 0; + left: 0; + bottom: 0; + } `, ]; } diff --git a/src/panels/lovelace/cards/hui-iframe-card.ts b/src/panels/lovelace/cards/hui-iframe-card.ts index 36cdea605ec3..48113e02d6d8 100644 --- a/src/panels/lovelace/cards/hui-iframe-card.ts +++ b/src/panels/lovelace/cards/hui-iframe-card.ts @@ -129,6 +129,8 @@ export class HuiIframeCard extends LitElement implements LovelaceCard { overflow: hidden; width: 100%; height: 100%; + display: flex; + flex-direction: column; } #root { diff --git a/src/panels/lovelace/cards/hui-tile-card.ts b/src/panels/lovelace/cards/hui-tile-card.ts index 19a05e3bb4ab..ae8c8a99c0b8 100644 --- a/src/panels/lovelace/cards/hui-tile-card.ts +++ b/src/panels/lovelace/cards/hui-tile-card.ts @@ -123,19 +123,21 @@ export class HuiTileCard extends LitElement implements LovelaceCard { public getLayoutOptions(): LovelaceLayoutOptions { const grid_columns = 2; + let grid_min_columns = 2; let grid_rows = 1; if (this._config?.features?.length) { const featureHeight = Math.ceil((this._config.features.length * 2) / 3); grid_rows += featureHeight; } if (this._config?.vertical) { - grid_rows!++; + grid_rows++; + grid_min_columns = 1; } return { grid_columns, grid_rows, grid_min_rows: grid_rows, - grid_min_columns: grid_columns, + grid_min_columns, }; } diff --git a/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts b/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts index ab1bebf00fcc..cfbbd31c3aec 100644 --- a/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts +++ b/src/panels/lovelace/editor/card-editor/ha-grid-layout-slider.ts @@ -378,6 +378,9 @@ export class HaGridLayoutSlider extends LitElement { :host(:disabled) .handle:after { background: var(--disabled-color); } + :host(:disabled) .active { + background: var(--disabled-color); + } .pressed .handle { transition: none; } diff --git a/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts b/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts index 5a5690727ce6..ac32343f75e9 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-layout-editor.ts @@ -11,8 +11,10 @@ import "../../../../components/ha-button-menu"; import "../../../../components/ha-grid-size-picker"; import "../../../../components/ha-icon-button"; import "../../../../components/ha-list-item"; +import "../../../../components/ha-settings-row"; import "../../../../components/ha-slider"; import "../../../../components/ha-svg-icon"; +import "../../../../components/ha-switch"; import "../../../../components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { LovelaceCardConfig } from "../../../../data/lovelace/config/card"; @@ -61,11 +63,13 @@ export class HuiCardLayoutEditor extends LitElement { this._defaultLayoutOptions ); + const sizeValue = this._gridSizeValue(options); + return html`

${this.hass.localize( - `ui.panel.lovelace.editor.edit_card.layout.explanation` + "ui.panel.lovelace.editor.edit_card.layout.explanation" )}

": - version: 5.5.2 - resolution: "typescript@patch:typescript@npm%3A5.5.2#optional!builtin::version=5.5.2&hash=379a07" +"typescript@patch:typescript@npm%3A5.5.3#optional!builtin": + version: 5.5.3 + resolution: "typescript@patch:typescript@npm%3A5.5.3#optional!builtin::version=5.5.3&hash=379a07" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/ac3145f65cf9e72ab29f2196e05d5816b355dc1a9195b9f010d285182a12457cfacd068be2dd22c877f88ebc966ac6e0e83f51c8586412b16499a27e3670ff4b + checksum: 10/7cf7acb78a80f749b82842f2ffe01e90e7b3e709a6f4268588e0b7599c41dca1059be217f47778fe1a380bfaf60933021ef20d002c426d4d7745e1b36c11467b languageName: node linkType: hard