From c293cf56f6f70818d5548b4346418137d327937d Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Tue, 10 Feb 2026 16:33:49 +0100 Subject: [PATCH] Migrate from ha-md-menu to ha-dropdown (#29548) --- src/components/ha-icon-overflow-menu.ts | 1 - src/components/ha-md-divider.ts | 22 -- src/components/ha-md-menu-item.ts | 52 ---- src/components/ha-md-menu.ts | 47 --- .../add-automation-element-dialog.ts | 11 +- .../config/automation/ha-automation-picker.ts | 2 +- src/panels/config/automation/styles.ts | 6 - .../config/backup/ha-config-backup-backups.ts | 77 +++-- .../statistics/developer-tools-statistics.ts | 269 +++++++++--------- src/panels/config/labels/ha-config-labels.ts | 104 ++++--- src/panels/config/scene/ha-scene-dashboard.ts | 1 - src/panels/config/script/ha-script-picker.ts | 1 - 12 files changed, 275 insertions(+), 318 deletions(-) delete mode 100644 src/components/ha-md-divider.ts delete mode 100644 src/components/ha-md-menu-item.ts delete mode 100644 src/components/ha-md-menu.ts diff --git a/src/components/ha-icon-overflow-menu.ts b/src/components/ha-icon-overflow-menu.ts index 9d28730246..97d221378d 100644 --- a/src/components/ha-icon-overflow-menu.ts +++ b/src/components/ha-icon-overflow-menu.ts @@ -9,7 +9,6 @@ import type { HomeAssistant } from "../types"; import "./ha-dropdown"; import "./ha-dropdown-item"; import "./ha-icon-button"; -import "./ha-md-divider"; import "./ha-svg-icon"; import "./ha-tooltip"; diff --git a/src/components/ha-md-divider.ts b/src/components/ha-md-divider.ts deleted file mode 100644 index 111c059e96..0000000000 --- a/src/components/ha-md-divider.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Divider } from "@material/web/divider/internal/divider"; -import { styles } from "@material/web/divider/internal/divider-styles"; -import { css } from "lit"; -import { customElement } from "lit/decorators"; - -@customElement("ha-md-divider") -export class HaMdDivider extends Divider { - static override styles = [ - styles, - css` - :host { - --md-divider-color: var(--divider-color); - } - `, - ]; -} - -declare global { - interface HTMLElementTagNameMap { - "ha-md-divider": HaMdDivider; - } -} diff --git a/src/components/ha-md-menu-item.ts b/src/components/ha-md-menu-item.ts deleted file mode 100644 index 92727524a6..0000000000 --- a/src/components/ha-md-menu-item.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { MenuItemEl } from "@material/web/menu/internal/menuitem/menu-item"; -import { styles } from "@material/web/menu/internal/menuitem/menu-item-styles"; -import { css } from "lit"; -import { customElement, property } from "lit/decorators"; - -@customElement("ha-md-menu-item") -export class HaMdMenuItem extends MenuItemEl { - @property({ attribute: false }) clickAction?: (item?: HTMLElement) => void; - - static override styles = [ - styles, - css` - :host { - --ha-icon-display: block; - --md-sys-color-primary: var(--primary-text-color); - --md-sys-color-on-primary: var(--primary-text-color); - --md-sys-color-secondary: var(--secondary-text-color); - --md-sys-color-surface: var(--card-background-color); - --md-sys-color-on-surface: var(--primary-text-color); - --md-sys-color-on-surface-variant: var(--secondary-text-color); - --md-sys-color-secondary-container: rgba( - var(--rgb-primary-color), - 0.15 - ); - --md-sys-color-on-secondary-container: var(--text-primary-color); - --mdc-icon-size: 16px; - - --md-sys-color-on-primary-container: var(--primary-text-color); - --md-sys-color-on-secondary-container: var(--primary-text-color); - --md-menu-item-label-text-font: Roboto, sans-serif; - } - :host(.warning) { - --md-menu-item-label-text-color: var(--error-color); - --md-menu-item-leading-icon-color: var(--error-color); - } - ::slotted([slot="headline"]) { - text-wrap: nowrap; - } - :host([disabled]) { - opacity: 1; - --md-menu-item-label-text-color: var(--disabled-text-color); - --md-menu-item-leading-icon-color: var(--disabled-text-color); - } - `, - ]; -} - -declare global { - interface HTMLElementTagNameMap { - "ha-md-menu-item": HaMdMenuItem; - } -} diff --git a/src/components/ha-md-menu.ts b/src/components/ha-md-menu.ts deleted file mode 100644 index 919d02b1eb..0000000000 --- a/src/components/ha-md-menu.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Menu } from "@material/web/menu/internal/menu"; -import { styles } from "@material/web/menu/internal/menu-styles"; -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 { HaMdMenuItem } from "./ha-md-menu-item"; - -@customElement("ha-md-menu") -export class HaMdMenu extends Menu { - 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 HaMdMenuItem).clickAction?.(ev.detail.initiator); - } - - static override styles = [ - styles, - css` - :host { - --md-sys-color-surface-container: var(--card-background-color); - } - `, - ]; -} - -declare global { - interface HTMLElementTagNameMap { - "ha-md-menu": HaMdMenu; - } - - interface HTMLElementEventMap { - "close-menu": CloseMenuEvent; - } -} diff --git a/src/panels/config/automation/add-automation-element-dialog.ts b/src/panels/config/automation/add-automation-element-dialog.ts index 2dfc1f17ad..30a964a2d6 100644 --- a/src/panels/config/automation/add-automation-element-dialog.ts +++ b/src/panels/config/automation/add-automation-element-dialog.ts @@ -1,3 +1,4 @@ +import "@home-assistant/webawesome/dist/components/divider/divider"; import { consume } from "@lit/context"; import { mdiAppleKeyboardCommand, @@ -42,7 +43,6 @@ import "../../../components/ha-icon"; import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button-prev"; import "../../../components/ha-icon-next"; -import "../../../components/ha-md-divider"; import "../../../components/ha-md-list"; import "../../../components/ha-md-list-item"; import type { PickerComboBoxItem } from "../../../components/ha-picker-combo-box"; @@ -657,10 +657,7 @@ class DialogAddAutomationElement .path=${mdiPlus} > - ` + ` : nothing} ${collections.map( (collection, index) => html` @@ -2177,8 +2174,8 @@ class DialogAddAutomationElement width: var(--ha-space-6); } - ha-md-list-item.paste { - border-bottom: 1px solid var(--ha-color-border-neutral-quiet); + wa-divider { + --spacing: 0; } ha-svg-icon.plus { diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index bbab5c388e..b263b0fa4d 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -740,7 +740,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) { ${this.hass.localize("ui.panel.config.automation.editor.show_trace")} - + ${this.hass.localize("ui.panel.config.automation.picker.duplicate")} diff --git a/src/panels/config/automation/styles.ts b/src/panels/config/automation/styles.ts index 9928d664d7..12d69d07ef 100644 --- a/src/panels/config/automation/styles.ts +++ b/src/panels/config/automation/styles.ts @@ -40,9 +40,6 @@ export const rowStyles = css` .warning ul { margin: 4px 0; } - ha-md-menu-item > ha-svg-icon { - --mdc-icon-size: 24px; - } ha-tooltip { cursor: default; } @@ -272,7 +269,4 @@ export const overflowStyles = css` display: none; } } - ha-md-menu-item { - --mdc-icon-size: 24px; - } `; diff --git a/src/panels/config/backup/ha-config-backup-backups.ts b/src/panels/config/backup/ha-config-backup-backups.ts index 4dfde25e77..b4ea092db1 100644 --- a/src/panels/config/backup/ha-config-backup-backups.ts +++ b/src/panels/config/backup/ha-config-backup-backups.ts @@ -26,15 +26,16 @@ import type { } from "../../../components/data-table/ha-data-table"; import "../../../components/ha-button"; import "../../../components/ha-dropdown"; +import type { + HaDropdown, + HaDropdownSelectEvent, +} from "../../../components/ha-dropdown"; import "../../../components/ha-dropdown-item"; import "../../../components/ha-fab"; import "../../../components/ha-filter-states"; import "../../../components/ha-icon"; import "../../../components/ha-icon-next"; import "../../../components/ha-icon-overflow-menu"; -import "../../../components/ha-md-menu"; -import type { HaMdMenu } from "../../../components/ha-md-menu"; -import "../../../components/ha-md-menu-item"; import "../../../components/ha-spinner"; import "../../../components/ha-svg-icon"; import type { @@ -73,7 +74,6 @@ import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup" import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup"; import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup"; import { downloadBackup } from "./helper/download_backup"; -import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown"; interface BackupRow extends DataTableRowData, BackupContent { formatted_type: string; @@ -123,7 +123,11 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { @query("hass-tabs-subpage-data-table", true) private _dataTable!: HaTabsSubpageDataTable; - @query("#overflow-menu") private _overflowMenu?: HaMdMenu; + @query("#overflow-menu") private _overflowMenu?: HaDropdown; + + private _openingOverflow = false; + + private _overflowBackup?: BackupRow; public connectedCallback() { super.connectedCallback(); @@ -287,12 +291,27 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { return; } - if (this._overflowMenu.open) { - this._overflowMenu.close(); + if (this._overflowMenu.anchorElement === ev.target) { + this._overflowMenu.anchorElement = undefined; return; } + this._openingOverflow = true; this._overflowMenu.anchorElement = ev.target; - this._overflowMenu.show(); + this._overflowBackup = ev.target.backup; + this._overflowMenu.open = true; + }; + + private _overflowMenuOpened = () => { + this._openingOverflow = false; + }; + + private _overflowMenuClosed = () => { + // changing the anchorElement triggers a close event, ignore it + if (this._openingOverflow || !this._overflowMenu) { + return; + } + + this._overflowMenu.anchorElement = undefined; }; private _handleGroupingChanged(ev: CustomEvent) { @@ -477,16 +496,21 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { ` : nothing} - - - + + + ${this.hass.localize("ui.common.download")} - - - + + + ${this.hass.localize("ui.common.delete")} - - + + `; } @@ -556,16 +580,29 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { navigate(`/config/backup/details/${id}`); } - private _downloadBackup = async (ev): Promise => { - const backup = ev.parentElement.anchorElement.backup; + private _handleOverflowAction = (ev: HaDropdownSelectEvent) => { + const action = ev.detail.item.value; + + if (action === "download") { + this._downloadBackup(); + return; + } + + if (action === "delete") { + this._deleteBackup(); + } + }; + + private _downloadBackup = async (): Promise => { + const backup = this._overflowBackup; if (!backup) { return; } downloadBackup(this.hass, this, backup, this.config); }; - private _deleteBackup = async (ev): Promise => { - const backup = ev.parentElement.anchorElement.backup; + private _deleteBackup = async (): Promise => { + const backup = this._overflowBackup; if (!backup) { return; } diff --git a/src/panels/config/developer-tools/statistics/developer-tools-statistics.ts b/src/panels/config/developer-tools/statistics/developer-tools-statistics.ts index 4c1236b101..01c7724775 100644 --- a/src/panels/config/developer-tools/statistics/developer-tools-statistics.ts +++ b/src/panels/config/developer-tools/statistics/developer-tools-statistics.ts @@ -10,10 +10,10 @@ import { mdiUnfoldMoreHorizontal, } from "@mdi/js"; +import "@home-assistant/webawesome/dist/components/divider/divider"; import type { HassEntity } from "home-assistant-js-websocket"; import { css, type CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; import type { HASSDomEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event"; @@ -28,15 +28,11 @@ import type { SortingDirection, } from "../../../../components/data-table/ha-data-table"; import { showDataTableSettingsDialog } from "../../../../components/data-table/show-dialog-data-table-settings"; -import "@home-assistant/webawesome/dist/components/divider/divider"; import "../../../../components/ha-button"; import "../../../../components/ha-dialog"; import "../../../../components/ha-dropdown"; +import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown"; import "../../../../components/ha-dropdown-item"; -import type { HaMdMenu } from "../../../../components/ha-md-menu"; -import "../../../../components/ha-md-menu-item"; -import "../../../../components/ha-md-menu"; -import "../../../../components/ha-md-divider"; import "../../../../components/search-input-outlined"; import type { StatisticsMetaData, @@ -112,20 +108,8 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) { @query("ha-data-table", true) private _dataTable!: HaDataTable; - @query("#group-by-menu") private _groupByMenu!: HaMdMenu; - - @query("#sort-by-menu") private _sortByMenu!: HaMdMenu; - @query("search-input-outlined") private _searchInput!: HTMLElement; - private _toggleGroupBy() { - this._groupByMenu.open = !this._groupByMenu.open; - } - - private _toggleSortBy() { - this._sortByMenu.open = !this._sortByMenu.open; - } - protected firstUpdated() { this._validateStatistics(); } @@ -278,37 +262,106 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) { const sortByMenu = Object.values(columns).find((col) => col.sortable) ? html` - - - + + + + + ${Object.entries(columns).map(([id, column]) => + column.sortable + ? html` + + ${this._sortColumn === id + ? html` + + ` + : nothing} + ${column.title || column.label} + + ` + : nothing + )} + ` : nothing; const groupByMenu = Object.values(columns).find((col) => col.groupable) ? html` - - + + + + ${Object.entries(columns).map(([id, column]) => + column.groupable + ? html` + + ${column.title || column.label} + + ` + : nothing + )} + + ${localize("ui.components.subpage-data-table.dont_group_by")} + + + + + ${localize( + "ui.components.subpage-data-table.collapse_all_groups" + )} + + + + ${localize("ui.components.subpage-data-table.expand_all_groups")} + + ` : nothing; @@ -417,6 +470,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) { .hiddenColumns=${this.hiddenColumns} @row-click=${this._rowClicked} @selection-changed=${this._handleSelectionChanged} + @sorting-changed=${this._handleTableSortingChanged} > ${!this.narrow ? html` @@ -434,82 +488,6 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) { `} - - ${Object.entries(columns).map(([id, column]) => - column.groupable - ? html` - - ${column.title || column.label} - - ` - : nothing - )} - - ${localize("ui.components.subpage-data-table.dont_group_by")} - - - - - ${localize("ui.components.subpage-data-table.collapse_all_groups")} - - - - ${localize("ui.components.subpage-data-table.expand_all_groups")} - - - - ${Object.entries(columns).map(([id, column]) => - column.sortable - ? html` - - ${this._sortColumn === id - ? html` - - ` - : nothing} - ${column.title || column.label} - - ` - : nothing - )} - `; } @@ -526,8 +504,17 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) { this._selected = ev.detail.value; } - private _handleSortBy(ev) { - const columnId = ev.currentTarget.value; + private _handleTableSortingChanged( + ev: CustomEvent<{ column: string; direction: SortingDirection }> + ) { + const { column, direction } = ev.detail; + this._sortColumn = column; + this._sortDirection = direction; + } + + private _handleSortBy(ev: HaDropdownSelectEvent) { + ev.preventDefault(); // keep dropdown open + const columnId = ev.detail.item.value; if (!this._sortDirection || this._sortColumn !== columnId) { this._sortDirection = "asc"; } else if (this._sortDirection === "asc") { @@ -538,11 +525,29 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) { this._sortColumn = columnId; } - private _handleGroupBy(ev) { - this._setGroupColumn(ev.currentTarget.value); - } + private _handleOverflowGroupBy = (ev: HaDropdownSelectEvent) => { + const action = ev.detail.item.value; - private _setGroupColumn(columnId: string) { + if (!action) { + return; + } + + switch (action) { + case "collapse_all": + this._collapseAllGroups(); + return; + case "expand_all": + this._expandAllGroups(); + return; + case "none": + this._setGroupColumn(); + return; + default: + this._setGroupColumn(action); + } + }; + + private _setGroupColumn(columnId?: string) { this._groupColumn = columnId; } @@ -797,11 +802,19 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) { --dialog-content-padding: 0; } - #sort-by-anchor, - #group-by-anchor, ha-dropdown ha-assist-chip { --md-assist-chip-trailing-space: 8px; } + + ha-dropdown-item.selected { + font-weight: var(--ha-font-weight-medium); + color: var(--primary-color); + background-color: var(--ha-color-fill-primary-quiet-resting); + --icon-primary-color: var(--primary-color); + } + ha-dropdown-item.selected:hover { + background-color: var(--ha-color-fill-primary-quiet-hover); + } `, ]; } diff --git a/src/panels/config/labels/ha-config-labels.ts b/src/panels/config/labels/ha-config-labels.ts index 26ddd16871..895d6575c7 100644 --- a/src/panels/config/labels/ha-config-labels.ts +++ b/src/panels/config/labels/ha-config-labels.ts @@ -1,3 +1,4 @@ +import "@home-assistant/webawesome/dist/components/divider/divider"; import { mdiDelete, mdiDevices, @@ -20,13 +21,16 @@ import type { RowClickedEvent, SortingChangedEvent, } from "../../../components/data-table/ha-data-table"; +import "../../../components/ha-dropdown"; +import type { + HaDropdown, + HaDropdownSelectEvent, +} from "../../../components/ha-dropdown"; +import "../../../components/ha-dropdown-item"; import "../../../components/ha-fab"; import "../../../components/ha-icon"; import "../../../components/ha-icon-button"; import { renderLabelColorBadge } from "../../../components/ha-label-picker"; -import "../../../components/ha-md-menu"; -import type { HaMdMenu } from "../../../components/ha-md-menu"; -import "../../../components/ha-md-menu-item"; import "../../../components/ha-svg-icon"; import type { LabelRegistryEntry, @@ -44,12 +48,12 @@ import { } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-tabs-subpage-data-table"; import type { HomeAssistant, Route } from "../../../types"; -import { configSections } from "../ha-panel-config"; -import { showLabelDetailDialog } from "./show-dialog-label-detail"; import { getCreatedAtTableColumn, getModifiedAtTableColumn, } from "../common/data-table-columns"; +import { configSections } from "../ha-panel-config"; +import { showLabelDetailDialog } from "./show-dialog-label-detail"; @customElement("ha-config-labels") export class HaConfigLabels extends LitElement { @@ -93,10 +97,12 @@ export class HaConfigLabels extends LitElement { }) private _activeHiddenColumns?: string[]; - @query("#overflow-menu") private _overflowMenu?: HaMdMenu; + @query("#overflow-menu") private _overflowMenu?: HaDropdown; private _overflowLabel!: LabelRegistryEntry; + private _openingOverflow = false; + private _columns = memoizeOne((localize: LocalizeFunc, narrow: boolean) => { const columns: DataTableColumnContainer = { icon: { @@ -172,13 +178,27 @@ export class HaConfigLabels extends LitElement { return; } - if (this._overflowMenu.open) { - this._overflowMenu.close(); + if (this._overflowMenu.anchorElement === ev.target) { + this._overflowMenu.anchorElement = undefined; return; } - this._overflowLabel = ev.target.selected; + this._openingOverflow = true; this._overflowMenu.anchorElement = ev.target; - this._overflowMenu.show(); + this._overflowLabel = ev.target.selected; + this._overflowMenu.open = true; + }; + + private _overflowMenuOpened = () => { + this._openingOverflow = false; + }; + + private _overflowMenuClosed = () => { + // changing the anchorElement triggers a close event, ignore it + if (this._openingOverflow || !this._overflowMenu) { + return; + } + + this._overflowMenu.anchorElement = undefined; }; protected firstUpdated(changedProperties: PropertyValues) { @@ -224,32 +244,30 @@ export class HaConfigLabels extends LitElement { - - - + + + ${this.hass.localize("ui.panel.config.entities.caption")} - - - + + + ${this.hass.localize("ui.panel.config.devices.caption")} - - - + + + ${this.hass.localize("ui.panel.config.automation.caption")} - - - - + + + + ${this.hass.localize("ui.common.delete")} - - + + `; } @@ -341,6 +359,28 @@ export class HaConfigLabels extends LitElement { } } + private _handleOverflowAction = (ev: HaDropdownSelectEvent) => { + const action = ev.detail.item.value; + + if (!action) { + return; + } + switch (action) { + case "navigate-entities": + this._navigateEntities(); + break; + case "navigate-devices": + this._navigateDevices(); + break; + case "navigate-automations": + this._navigateAutomations(); + break; + case "remove": + this._handleRemoveLabelClick(); + break; + } + }; + private _navigateEntities = () => { navigate( `/config/entities?historyBack=1&label=${this._overflowLabel.label_id}` diff --git a/src/panels/config/scene/ha-scene-dashboard.ts b/src/panels/config/scene/ha-scene-dashboard.ts index 4dd27b9cf9..525e482fd5 100644 --- a/src/panels/config/scene/ha-scene-dashboard.ts +++ b/src/panels/config/scene/ha-scene-dashboard.ts @@ -52,7 +52,6 @@ import "../../../components/ha-filter-labels"; import "../../../components/ha-filter-voice-assistants"; import "../../../components/ha-icon-button"; import "../../../components/ha-icon-overflow-menu"; -import "../../../components/ha-md-divider"; import "../../../components/ha-state-icon"; import "../../../components/ha-sub-menu"; import "../../../components/ha-svg-icon"; diff --git a/src/panels/config/script/ha-script-picker.ts b/src/panels/config/script/ha-script-picker.ts index e657ae6caa..4a2bba4cba 100644 --- a/src/panels/config/script/ha-script-picker.ts +++ b/src/panels/config/script/ha-script-picker.ts @@ -54,7 +54,6 @@ import "../../../components/ha-filter-labels"; import "../../../components/ha-filter-voice-assistants"; import "../../../components/ha-icon-button"; import "../../../components/ha-icon-overflow-menu"; -import "../../../components/ha-md-divider"; import "../../../components/ha-sub-menu"; import "../../../components/ha-svg-icon"; import "../../../components/ha-tooltip";