col.sortable)
? html`
-
+
column.sortable
? html`
-
${this._sortColumn === id
@@ -292,17 +289,17 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
`
: nothing}
${column.title || column.label}
-
+
`
: nothing
)}
-
+
`
: nothing;
const groupByMenu = Object.values(this.columns).find((col) => col.groupable)
? html`
-
+
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;
@@ -399,7 +394,7 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
"ui.components.subpage-data-table.exit_selection_mode"
)}
>
-
+
-
-
- ${localize("ui.components.subpage-data-table.select_all")}
-
-
-
-
- ${localize(
- "ui.components.subpage-data-table.select_none"
- )}
-
-
-
-
-
- ${localize(
- "ui.components.subpage-data-table.exit_selection_mode"
- )}
-
-
-
+
+ ${localize("ui.components.subpage-data-table.select_all")}
+
+
+ ${localize("ui.components.subpage-data-table.select_none")}
+
+
+
+ ${localize(
+ "ui.components.subpage-data-table.exit_selection_mode"
+ )}
+
+
${this.selected !== undefined
? html`
${localize("ui.components.subpage-data-table.selected", {
@@ -612,10 +590,10 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
this._sortColumn = this._sortDirection ? ev.detail.column : undefined;
}
- private _handleSortBy(ev) {
- if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") return;
+ private _handleSortBy(ev: CustomEvent<{ item: HaDropdownItem }>) {
+ ev.preventDefault(); // keep the dropdown open
- const columnId = ev.currentTarget.value;
+ const columnId = ev.detail.item.value;
if (!this._sortDirection || this._sortColumn !== columnId) {
this._sortDirection = "asc";
} else if (this._sortDirection === "asc") {
@@ -631,9 +609,24 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
});
}
- private _handleGroupBy = (item) => {
- this._setGroupColumn(item.value);
- };
+ private _handleGroupBy(ev: CustomEvent<{ item: HaDropdownItem }>) {
+ const group = ev.detail.item.value;
+
+ if (group === "reset") {
+ this._setGroupColumn("");
+ return;
+ }
+ if (group === "collapse_all") {
+ this._collapseAllGroups();
+ return;
+ }
+ if (group === "expand_all") {
+ this._expandAllGroups();
+ return;
+ }
+
+ this._setGroupColumn(group);
+ }
private _setGroupColumn(columnId: string) {
this._groupColumn = columnId;
@@ -669,6 +662,26 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
this._selectMode = true;
}
+ private _handleSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
+ const action = ev.detail.item.value;
+
+ if (!action) {
+ return;
+ }
+
+ switch (action) {
+ case "all":
+ this._selectAll();
+ break;
+ case "none":
+ this._selectNone();
+ break;
+ case "disable_select_mode":
+ this._disableSelectMode();
+ break;
+ }
+ }
+
private _disableSelectMode = () => {
this._selectMode = false;
this._dataTable.clearSelection();
@@ -902,9 +915,17 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
flex-direction: column;
}
- ha-md-button-menu ha-assist-chip {
+ ha-dropdown ha-assist-chip {
--md-assist-chip-trailing-space: 8px;
}
+
+ ha-dropdown-item.selected {
+ border: 1px solid var(--primary-color);
+ 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);
+ }
`;
}
diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts
index 28c5929e3a..58786db526 100644
--- a/src/panels/config/devices/ha-config-devices-dashboard.ts
+++ b/src/panels/config/devices/ha-config-devices-dashboard.ts
@@ -1,7 +1,7 @@
+import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiCancel,
- mdiChevronRight,
mdiDelete,
mdiDotsVertical,
mdiMenuDown,
@@ -43,6 +43,9 @@ import "../../../components/data-table/ha-data-table-labels";
import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-alert";
import "../../../components/ha-check-list-item";
+import "../../../components/ha-dropdown";
+import "../../../components/ha-dropdown-item";
+import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-fab";
import "../../../components/ha-filter-devices";
import "../../../components/ha-filter-floor-areas";
@@ -50,8 +53,6 @@ import "../../../components/ha-filter-integrations";
import "../../../components/ha-filter-labels";
import "../../../components/ha-filter-states";
import "../../../components/ha-icon-button";
-import "../../../components/ha-md-divider";
-import "../../../components/ha-md-menu-item";
import "../../../components/ha-sub-menu";
import { createAreaRegistryEntry } from "../../../data/area/area_registry";
import type { ConfigEntry, SubEntry } from "../../../data/config_entries";
@@ -702,6 +703,70 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
];
}
+ private _renderAreaItems = (slot = "") =>
+ html`${Object.values(this.hass.areas).map(
+ (area) =>
+ html`
+ ${area.icon
+ ? html``
+ : html``}
+ ${area.name}
+ `
+ )}
+
+ ${this.hass.localize(
+ "ui.panel.config.devices.picker.bulk_actions.no_area"
+ )}
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.devices.picker.bulk_actions.add_area"
+ )}
+ `;
+
+ private _renderLabelItems = (slot = "") =>
+ html`${this._labels?.map((label) => {
+ const color = label.color ? computeCssColor(label.color) : undefined;
+ const selected = this._selected.every((deviceId) =>
+ this.hass.devices[deviceId]?.labels.includes(label.label_id)
+ );
+ const partial =
+ !selected &&
+ this._selected.some((deviceId) =>
+ this.hass.devices[deviceId]?.labels.includes(label.label_id)
+ );
+ return html`
+
+
+ ${label.icon
+ ? html``
+ : nothing}
+ ${label.name}
+
+ `;
+ })}
+
+
+ ${this.hass.localize("ui.panel.config.labels.add_label")}
+ `;
+
protected render(): TemplateResult {
const { devicesOutput } = this._devicesAndFilterDomains(
this.hass.devices,
@@ -718,77 +783,6 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
(this._sizeController.value && this._sizeController.value < 700) ||
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
- const areaItems = html`${Object.values(this.hass.areas).map(
- (area) =>
- html`
- ${area.icon
- ? html``
- : html``}
- ${area.name}
- `
- )}
-
-
- ${this.hass.localize(
- "ui.panel.config.devices.picker.bulk_actions.no_area"
- )}
-
-
-
-
-
- ${this.hass.localize(
- "ui.panel.config.devices.picker.bulk_actions.add_area"
- )}
-
- `;
-
- const labelItems = html`${this._labels?.map((label) => {
- const color = label.color ? computeCssColor(label.color) : undefined;
- const selected = this._selected.every((deviceId) =>
- this.hass.devices[deviceId]?.labels.includes(label.label_id)
- );
- const partial =
- !selected &&
- this._selected.some((deviceId) =>
- this.hass.devices[deviceId]?.labels.includes(label.label_id)
- );
- return html`
-
-
- ${label.icon
- ? html``
- : nothing}
- ${label.name}
-
- `;
- })}
-
-
-
- ${this.hass.localize("ui.panel.config.labels.add_label")}
-
`;
-
return html`
${!this.narrow
- ? html`
+ ? html`
- ${labelItems}
-
+ ${this._renderLabelItems()}
+
${areasInOverflow
? nothing
- : html`
+ : html`
- ${areaItems}
- `}`
+ ${this._renderAreaItems()}
+ `}`
: nothing}
-
+
${this.narrow
? html``}
${this.narrow
- ? html`
-
-
- ${this.hass.localize(
- "ui.panel.config.automation.picker.bulk_actions.add_label"
- )}
-
-
-
- ${labelItems}
- `
+ ? html`
+ ${this.hass.localize(
+ "ui.panel.config.automation.picker.bulk_actions.add_label"
+ )}
+ ${this._renderLabelItems("submenu")}
+ `
: nothing}
${areasInOverflow
- ? html`
-
-
- ${this.hass.localize(
- "ui.panel.config.devices.picker.bulk_actions.move_area"
- )}
-
-
-
- ${areaItems}
-
- `
+ ? html`
+ ${this.hass.localize(
+ "ui.panel.config.devices.picker.bulk_actions.move_area"
+ )}
+ ${this._renderAreaItems("submenu")}
+
+ `
: nothing}
-
-
-
- ${this.hass.localize(
- "ui.panel.config.devices.picker.bulk_actions.delete_selected.button"
- )}
-
-
-
+
+ ${this.hass.localize(
+ "ui.panel.config.devices.picker.bulk_actions.delete_selected.button"
+ )}
+
+
`;
}
@@ -1094,12 +1076,22 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
this._selected = ev.detail.value;
}
- private _handleBulkArea = (item) => {
- const area = item.value;
- this._bulkAddArea(area);
- };
+ private _handleBulkArea(ev: CustomEvent<{ item: HaDropdownItem }>) {
+ const area = ev.detail.item.value;
- private async _bulkAddArea(area: string) {
+ if (area === "area_create") {
+ this._bulkCreateArea();
+ return;
+ }
+ if (area === "area_no") {
+ this._bulkAddArea(null);
+ return;
+ }
+
+ this._bulkAddArea(area.substring(5));
+ }
+
+ private async _bulkAddArea(area: string | null) {
const promises: Promise[] = [];
this._selected.forEach((deviceId) => {
promises.push(
@@ -1134,10 +1126,20 @@ ${rejected
});
};
- private async _handleBulkLabel(ev) {
- const label = ev.currentTarget.value;
- const action = ev.currentTarget.action;
- this._bulkLabel(label, action);
+ private async _handleBulkLabel(ev: CustomEvent<{ item: HaDropdownItem }>) {
+ const label = ev.detail.item.value;
+
+ if (label === "label_create") {
+ this._bulkCreateLabel();
+ return;
+ }
+
+ if (!label) {
+ return;
+ }
+
+ const action = (ev.detail.item as any).action;
+ this._bulkLabel(label.substring(6), action);
}
private async _bulkLabel(label: string, action: "add" | "remove") {
@@ -1251,6 +1253,27 @@ ${rejected
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
+ private _handleBulkAction(ev: CustomEvent<{ item: HaDropdownItem }>) {
+ const action = ev.detail.item.value;
+
+ if (!action) {
+ return;
+ }
+
+ if (action === "delete_selected") {
+ this._deleteSelected();
+ }
+
+ if (action.startsWith("label_")) {
+ this._handleBulkLabel(ev);
+ return;
+ }
+
+ if (action.startsWith("area_")) {
+ this._handleBulkArea(ev);
+ }
+ }
+
static get styles(): CSSResultGroup {
return [
css`
@@ -1273,7 +1296,7 @@ ${rejected
ha-assist-chip {
--ha-assist-chip-container-shape: 10px;
}
- ha-md-button-menu ha-assist-chip {
+ ha-dropdown ha-assist-chip {
--md-assist-chip-trailing-space: 8px;
}
ha-label {
diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts
index d63c26cb6c..dcd6d25695 100644
--- a/src/panels/config/entities/ha-config-entities.ts
+++ b/src/panels/config/entities/ha-config-entities.ts
@@ -1,3 +1,4 @@
+import "@home-assistant/webawesome/dist/components/divider/divider";
import { consume } from "@lit/context";
import {
mdiAlertCircle,
@@ -57,6 +58,9 @@ import type {
import "../../../components/data-table/ha-data-table-labels";
import "../../../components/ha-alert";
import "../../../components/ha-check-list-item";
+import "../../../components/ha-dropdown";
+import "../../../components/ha-dropdown-item";
+import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
import "../../../components/ha-filter-devices";
import "../../../components/ha-filter-domains";
import "../../../components/ha-filter-floor-areas";
@@ -66,8 +70,6 @@ import "../../../components/ha-filter-states";
import "../../../components/ha-filter-voice-assistants";
import "../../../components/ha-icon";
import "../../../components/ha-icon-button";
-import "../../../components/ha-md-divider";
-import "../../../components/ha-md-menu-item";
import "../../../components/ha-sub-menu";
import "../../../components/ha-svg-icon";
import "../../../components/ha-tooltip";
@@ -87,6 +89,7 @@ import type {
import { updateEntityRegistryEntry } from "../../../data/entity/entity_registry";
import type { EntitySources } from "../../../data/entity/entity_sources";
import { fetchEntitySourcesWithCache } from "../../../data/entity/entity_sources";
+import { getEntityVoiceAssistantsIds } from "../../../data/expose";
import { HELPERS_CRUD } from "../../../data/helpers_crud";
import type { IntegrationManifest } from "../../../data/integration";
import {
@@ -116,12 +119,11 @@ import { isHelperDomain } from "../helpers/const";
import "../integrations/ha-integration-overflow-menu";
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
-import { getEntityVoiceAssistantsIds } from "../../../data/expose";
-import { getAvailableAssistants } from "../voice-assistants/expose/available-assistants";
import {
- getAssistantsTableColumn,
getAssistantsSortableKey,
+ getAssistantsTableColumn,
} from "../voice-assistants/expose/assistants-table-column";
+import { getAvailableAssistants } from "../voice-assistants/expose/available-assistants";
export interface StateEntity extends Omit<
EntityRegistryEntry,
@@ -788,6 +790,44 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
];
}
+ private _renderLabelItems = (slot = "") =>
+ html`${this._labels?.map((label) => {
+ const color = label.color ? computeCssColor(label.color) : undefined;
+ const selected = this._selected.every((entityId) =>
+ this.hass.entities[entityId]?.labels.includes(label.label_id)
+ );
+ const partial =
+ !selected &&
+ this._selected.some((entityId) =>
+ this.hass.entities[entityId]?.labels.includes(label.label_id)
+ );
+ return html`
+
+
+ ${label.icon
+ ? html``
+ : nothing}
+ ${label.name}
+
+ `;
+ })}
+
+
+ ${this.hass.localize("ui.panel.config.labels.add_label")}
+ `;
+
protected render() {
if (!this.hass || this._entities === undefined) {
return html` `;
@@ -812,53 +852,13 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
[...filteredDomains][0]
);
- const labelItems = html` ${this._labels?.map((label) => {
- const color = label.color ? computeCssColor(label.color) : undefined;
- const selected = this._selected.every((entityId) =>
- this.hass.entities[entityId]?.labels.includes(label.label_id)
- );
- const partial =
- !selected &&
- this._selected.some((entityId) =>
- this.hass.entities[entityId]?.labels.includes(label.label_id)
- );
- return html`
-
-
- ${label.icon
- ? html``
- : nothing}
- ${label.name}
-
- `;
- })}
-
-
-
- ${this.hass.localize("ui.panel.config.labels.add_label")}
-
`;
-
return html`
- Array.isArray(filter)
- ? filter.length
- : filter &&
- Object.values(filter).some((val) =>
- Array.isArray(val) ? val.length : val
- )
- ).length
- }
+ .filters=${Object.values(this._filters).filter((filter) =>
+ Array.isArray(filter)
+ ? filter.length
+ : filter &&
+ Object.values(filter).some((val) =>
+ Array.isArray(val) ? val.length : val
+ )
+ ).length}
selectable
.selected=${this._selected.length}
.initialGroupColumn=${this._activeGrouping ?? "device_full"}
@@ -904,157 +902,125 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
slot="toolbar-icon"
>
-
-${
- !this.narrow
- ? html`
-
-
-
- ${labelItems}
- `
- : nothing
-}
-
- ${
- this.narrow
- ? html`
-
- `
- : html``
- }
-
- ${
- this.narrow
- ? html`
-
-
- ${this.hass.localize(
+ ${!this.narrow
+ ? html`
+
-
-
- ${labelItems}
-
- `
- : nothing
- }
-
-
-
-
- ${this.hass.localize(
- "ui.panel.config.entities.picker.enable_selected.button"
- )}
-
-
-
-
-
- ${this.hass.localize(
- "ui.panel.config.entities.picker.disable_selected.button"
- )}
-
-
-
-
-
-
-
- ${this.hass.localize(
- "ui.panel.config.entities.picker.unhide_selected.button"
- )}
-
-
-
-
-
- ${this.hass.localize(
- "ui.panel.config.entities.picker.hide_selected.button"
- )}
-
-
-
-
-
-
-
-
- ${this.hass.localize(
- "ui.panel.config.entities.picker.restore_entity_id_selected.button"
- )}
-
-
-
-
-
-
-
-
- ${this.hass.localize(
- "ui.panel.config.entities.picker.delete_selected.button"
- )}
-
-
-
-
- ${
- Array.isArray(this._filters.config_entry) &&
- this._filters.config_entry.length
- ? html`
- ${this.hass.localize(
- "ui.panel.config.entities.picker.filtering_by_config_entry"
+ >
+
+
+ ${this._renderLabelItems()}
+ `
+ : nothing}
+
+ ${this.narrow
+ ? html` entry.entry_id === this._filters.config_entry![0]
- )?.title || this._filters.config_entry[0]}${this._filters
- .config_entry.length === 1 &&
- Array.isArray(this._filters.sub_entry) &&
- this._filters.sub_entry.length
- ? html` (${this._subEntries?.find(
- (entry) =>
- entry.subentry_id === this._filters.sub_entry![0]
- )?.title || this._filters.sub_entry[0]})`
- : nothing}
- `
- : nothing
- }
+ slot="trigger"
+ >
+
+ `
+ : html``}
+ ${this.narrow
+ ? html`
+ ${this.hass.localize(
+ "ui.panel.config.automation.picker.bulk_actions.add_label"
+ )}
+
+ ${this._renderLabelItems("submenu")}
+
+ `
+ : nothing}
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.entities.picker.enable_selected.button"
+ )}
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.entities.picker.disable_selected.button"
+ )}
+
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.entities.picker.unhide_selected.button"
+ )}
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.entities.picker.hide_selected.button"
+ )}
+
+
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.entities.picker.restore_entity_id_selected.button"
+ )}
+
+
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.entities.picker.delete_selected.button"
+ )}
+
+
+ ${Array.isArray(this._filters.config_entry) &&
+ this._filters.config_entry.length
+ ? html`
+ ${this.hass.localize(
+ "ui.panel.config.entities.picker.filtering_by_config_entry"
+ )}
+ ${this._entries?.find(
+ (entry) => entry.entry_id === this._filters.config_entry![0]
+ )?.title || this._filters.config_entry[0]}${this._filters
+ .config_entry.length === 1 &&
+ Array.isArray(this._filters.sub_entry) &&
+ this._filters.sub_entry.length
+ ? html` (${this._subEntries?.find(
+ (entry) => entry.subentry_id === this._filters.sub_entry![0]
+ )?.title || this._filters.sub_entry[0]})`
+ : nothing}
+ `
+ : nothing}
- ${
- includeAddDeviceFab
- ? html`
-
- `
- : nothing
- }
+ ${includeAddDeviceFab
+ ? html`
+
+ `
+ : nothing}
`;
}
@@ -1396,10 +1358,24 @@ ${
this._clearSelection();
};
- private async _handleBulkLabel(ev) {
- const label = ev.currentTarget.value;
- const action = ev.currentTarget.action;
- await this._bulkLabel(label, action);
+ private async _handleBulkLabel(ev: CustomEvent<{ item: HaDropdownItem }>) {
+ ev.preventDefault(); // Prevent the dropdown from closing
+
+ const label = ev.detail.item.value;
+
+ if (!label) {
+ return;
+ }
+
+ if (label === "label_create") {
+ this._bulkCreateLabel();
+ return;
+ }
+
+ const labelId = label.substring(6);
+
+ const action = (ev.detail.item as any).action;
+ await this._bulkLabel(labelId, action);
}
private async _bulkLabel(label: string, action: "add" | "remove") {
@@ -1590,6 +1566,39 @@ ${rejected
this._activeHiddenColumns = ev.detail.hiddenColumns;
}
+ private _handleBulkAction(ev: CustomEvent<{ item: HaDropdownItem }>) {
+ const action = ev.detail.item.value;
+
+ if (!action) {
+ return;
+ }
+
+ switch (action) {
+ case "enable_selected":
+ this._enableSelected();
+ return;
+ case "disable_selected":
+ this._disableSelected();
+ return;
+ case "unhide_selected":
+ this._unhideSelected();
+ return;
+ case "hide_selected":
+ this._hideSelected();
+ return;
+ case "restore_entity_id_selected":
+ this._restoreEntityIdSelected();
+ return;
+ case "delete_selected":
+ this._removeSelected();
+ return;
+ }
+
+ if (action.startsWith("label_")) {
+ this._handleBulkLabel(ev);
+ }
+ }
+
static get styles(): CSSResultGroup {
return [
haStyle,
@@ -1659,7 +1668,7 @@ ${rejected
ha-assist-chip {
--ha-assist-chip-container-shape: 10px;
}
- ha-md-button-menu ha-assist-chip {
+ ha-dropdown ha-assist-chip {
--md-assist-chip-trailing-space: 8px;
}
ha-label {