diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts
index 1dd2e4274c..c6db3a0e54 100644
--- a/src/components/device/ha-device-picker.ts
+++ b/src/components/device/ha-device-picker.ts
@@ -113,7 +113,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
@internalProperty() private _opened?: boolean;
- @query("ha-combo-box", true) private _comboBox!: HaComboBox;
+ @query("ha-combo-box", true) public comboBox!: HaComboBox;
private _init = false;
@@ -242,11 +242,11 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
);
public open() {
- this._comboBox?.open();
+ this.comboBox?.open();
}
public focus() {
- this._comboBox?.focus();
+ this.comboBox?.focus();
}
public hassSubscribe(): UnsubscribeFunc[] {
@@ -269,7 +269,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
(changedProps.has("_opened") && this._opened)
) {
this._init = true;
- (this._comboBox as any).items = this._getDevices(
+ (this.comboBox as any).items = this._getDevices(
this.devices!,
this.areas!,
this.entities!,
diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts
index 10a9f9881e..45096550a4 100644
--- a/src/components/ha-area-picker.ts
+++ b/src/components/ha-area-picker.ts
@@ -127,7 +127,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
@internalProperty() private _opened?: boolean;
- @query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
+ @query("vaadin-combo-box-light", true) public comboBox!: HTMLElement;
private _init = false;
@@ -319,7 +319,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
(changedProps.has("_opened") && this._opened)
) {
this._init = true;
- (this._comboBox as any).items = this._getAreas(
+ (this.comboBox as any).items = this._getAreas(
this._areas!,
this._devices!,
this._entities!,
diff --git a/src/components/ha-button-menu.ts b/src/components/ha-button-menu.ts
index 5aafc86d78..de50f7bd42 100644
--- a/src/components/ha-button-menu.ts
+++ b/src/components/ha-button-menu.ts
@@ -1,4 +1,3 @@
-import "@material/mwc-button";
import "@material/mwc-menu";
import type { Corner, Menu } from "@material/mwc-menu";
import {
@@ -11,8 +10,6 @@ import {
query,
TemplateResult,
} from "lit-element";
-import "./ha-icon-button";
-
@customElement("ha-button-menu")
export class HaButtonMenu extends LitElement {
@property() public corner: Corner = "TOP_START";
diff --git a/src/components/ha-button-related-filter-menu.ts b/src/components/ha-button-related-filter-menu.ts
new file mode 100644
index 0000000000..fa2c580735
--- /dev/null
+++ b/src/components/ha-button-related-filter-menu.ts
@@ -0,0 +1,163 @@
+import "@material/mwc-icon-button";
+import type { Corner } from "@material/mwc-menu";
+import { mdiFilterVariant } from "@mdi/js";
+import {
+ css,
+ CSSResult,
+ customElement,
+ html,
+ internalProperty,
+ LitElement,
+ property,
+ TemplateResult,
+} from "lit-element";
+import "@material/mwc-menu/mwc-menu-surface";
+import { fireEvent } from "../common/dom/fire_event";
+import { findRelated, RelatedResult } from "../data/search";
+import type { HomeAssistant } from "../types";
+import "./ha-svg-icon";
+import "./ha-area-picker";
+import "./device/ha-device-picker";
+
+declare global {
+ // for fire event
+ interface HASSDomEvents {
+ "related-changed": {
+ value?: FilterValue;
+ items?: RelatedResult;
+ filter?: string;
+ };
+ }
+}
+
+interface FilterValue {
+ area?: string;
+ device?: string;
+}
+
+@customElement("ha-button-related-filter-menu")
+export class HaRelatedFilterButtonMenu extends LitElement {
+ @property() public hass!: HomeAssistant;
+
+ @property() public corner: Corner = "TOP_START";
+
+ @property({ type: Boolean, reflect: true }) public narrow = false;
+
+ @property({ type: Boolean }) public disabled = false;
+
+ @property({ attribute: false }) public value?: FilterValue;
+
+ @internalProperty() private _open = false;
+
+ protected render(): TemplateResult {
+ return html`
+
+
+
+
+
+
+
+ `;
+ }
+
+ private _handleClick(): void {
+ if (this.disabled) {
+ return;
+ }
+ this._open = true;
+ }
+
+ private _onClosed(): void {
+ this._open = false;
+ }
+
+ private async _devicePicked(ev: CustomEvent) {
+ const deviceId = ev.detail.value;
+ if (!deviceId) {
+ fireEvent(this, "related-changed", { value: undefined });
+ return;
+ }
+ const filter = this.hass.localize(
+ "ui.components.related-filter-menu.filtered_by_device",
+ "device_name",
+ (ev.currentTarget as any).comboBox.selectedItem.name
+ );
+ const items = await findRelated(this.hass, "device", deviceId);
+
+ fireEvent(this, "related-changed", {
+ value: { device: deviceId },
+ filter,
+ items,
+ });
+ }
+
+ private async _areaPicked(ev: CustomEvent) {
+ const areaId = ev.detail.value;
+ if (!areaId) {
+ fireEvent(this, "related-changed", { value: undefined });
+ return;
+ }
+ const filter = this.hass.localize(
+ "ui.components.related-filter-menu.filtered_by_area",
+ "area_name",
+ (ev.currentTarget as any).comboBox.selectedItem.name
+ );
+ const items = await findRelated(this.hass, "area", areaId);
+ fireEvent(this, "related-changed", {
+ value: { area: areaId },
+ filter,
+ items,
+ });
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ :host {
+ display: inline-block;
+ position: relative;
+ }
+ :host([narrow]) {
+ position: static;
+ }
+ ha-area-picker,
+ ha-device-picker {
+ display: block;
+ width: 300px;
+ padding: 4px 16px;
+ box-sizing: border-box;
+ }
+ :host([narrow]) ha-area-picker,
+ :host([narrow]) ha-device-picker {
+ width: 100%;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-button-related-filter-menu": HaRelatedFilterButtonMenu;
+ }
+}
diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts
index 6f6acd3346..19f89bd410 100644
--- a/src/components/ha-combo-box.ts
+++ b/src/components/ha-combo-box.ts
@@ -86,6 +86,10 @@ export class HaComboBox extends LitElement {
});
}
+ public get selectedItem() {
+ return (this._comboBox as any).selectedItem;
+ }
+
protected render(): TemplateResult {
return html`
) {
+ private _filterChanged(ev: PolymerChangedEvent) {
// @ts-ignore
- fireEvent(this, ev.type, ev.detail);
+ fireEvent(this, ev.type, ev.detail, { composed: false });
}
private _valueChanged(ev: PolymerChangedEvent) {
diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts
index a7a4df3e61..2f611f859c 100644
--- a/src/panels/config/automation/ha-automation-picker.ts
+++ b/src/panels/config/automation/ha-automation-picker.ts
@@ -5,6 +5,7 @@ import {
CSSResult,
customElement,
html,
+ internalProperty,
LitElement,
property,
TemplateResult,
@@ -20,6 +21,7 @@ import { DataTableColumnContainer } from "../../../components/data-table/ha-data
import "../../../components/entity/ha-entity-toggle";
import "../../../components/ha-fab";
import "../../../components/ha-svg-icon";
+import "../../../components/ha-button-related-filter-menu";
import {
AutomationEntity,
triggerAutomationActions,
@@ -45,14 +47,33 @@ class HaAutomationPicker extends LitElement {
@property() public automations!: AutomationEntity[];
- private _automations = memoizeOne((automations: AutomationEntity[]) => {
- return automations.map((automation) => {
- return {
- ...automation,
- name: computeStateName(automation),
- };
- });
- });
+ @property() private _activeFilters?: string[];
+
+ @internalProperty() private _filteredAutomations?: string[] | null;
+
+ @internalProperty() private _filterValue?;
+
+ private _automations = memoizeOne(
+ (
+ automations: AutomationEntity[],
+ filteredAutomations?: string[] | null
+ ) => {
+ if (filteredAutomations === null) {
+ return [];
+ }
+ return (filteredAutomations
+ ? automations.filter((automation) =>
+ filteredAutomations!.includes(automation.entity_id)
+ )
+ : automations
+ ).map((automation) => {
+ return {
+ ...automation,
+ name: computeStateName(automation),
+ };
+ });
+ }
+ );
private _columns = memoizeOne(
(narrow: boolean, _locale): DataTableColumnContainer => {
@@ -192,17 +213,27 @@ class HaAutomationPicker extends LitElement {
back-path="/config"
.route=${this.route}
.tabs=${configSections.automation}
+ .activeFilters=${this._activeFilters}
.columns=${this._columns(this.narrow, this.hass.locale)}
- .data=${this._automations(this.automations)}
- id="entity_id"
+ .data=${this._automations(this.automations, this._filteredAutomations)}
.noDataText=${this.hass.localize(
"ui.panel.config.automation.picker.no_automations"
)}
+ @clear-filter=${this._clearFilter}
hasFab
>
+
+