Render target picker combo-box in overlaying menu surface (#14970)

This commit is contained in:
Bram Kragten 2023-02-20 17:32:52 +01:00 committed by GitHub
parent cabbbcf9f3
commit 5f9f51f92d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 146 additions and 120 deletions

View File

@ -214,7 +214,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
if (!devEntities || !devEntities.length) { if (!devEntities || !devEntities.length) {
return false; return false;
} }
return deviceEntityLookup[device.id].some((entity) => { return devEntities.some((entity) => {
const stateObj = this.hass.states[entity.entity_id]; const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) { if (!stateObj) {
return false; return false;

View File

@ -4,7 +4,10 @@ import { fireEvent } from "../../common/dom/fire_event";
import { PolymerChangedEvent } from "../../polymer-types"; import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import "./ha-device-picker"; import "./ha-device-picker";
import type { HaDevicePickerDeviceFilterFunc } from "./ha-device-picker"; import type {
HaDevicePickerDeviceFilterFunc,
HaDevicePickerEntityFilterFunc,
} from "./ha-device-picker";
@customElement("ha-devices-picker") @customElement("ha-devices-picker")
class HaDevicesPicker extends LitElement { class HaDevicesPicker extends LitElement {
@ -44,6 +47,8 @@ class HaDevicesPicker extends LitElement {
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc; @property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
@property() public entityFilter?: HaDevicePickerEntityFilterFunc;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.hass) { if (!this.hass) {
return html``; return html``;
@ -59,6 +64,7 @@ class HaDevicesPicker extends LitElement {
.curValue=${entityId} .curValue=${entityId}
.hass=${this.hass} .hass=${this.hass}
.deviceFilter=${this.deviceFilter} .deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains} .includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains} .excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses} .includeDeviceClasses=${this.includeDeviceClasses}
@ -76,8 +82,10 @@ class HaDevicesPicker extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.helper=${this.helper} .helper=${this.helper}
.deviceFilter=${this.deviceFilter} .deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains} .includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains} .excludeDomains=${this.excludeDomains}
.excludeDevices=${currentDevices}
.includeDeviceClasses=${this.includeDeviceClasses} .includeDeviceClasses=${this.includeDeviceClasses}
.label=${this.pickDeviceLabel} .label=${this.pickDeviceLabel}
.disabled=${this.disabled} .disabled=${this.disabled}

View File

@ -233,6 +233,9 @@ export class HaAreaPicker extends LitElement {
}); });
inputEntities = inputEntities!.filter((entity) => { inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id]; const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return entityFilter!(stateObj); return entityFilter!(stateObj);
}); });
} }

View File

@ -13,6 +13,7 @@ import { HassEntity, HassServiceTarget } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit"; import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
import { ensureArray } from "../common/array/ensure-array"; import { ensureArray } from "../common/array/ensure-array";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { computeDomain } from "../common/entity/compute_domain"; import { computeDomain } from "../common/entity/compute_domain";
@ -31,6 +32,8 @@ import "./ha-area-picker";
import "./ha-icon-button"; import "./ha-icon-button";
import "./ha-input-helper-text"; import "./ha-input-helper-text";
import "./ha-svg-icon"; import "./ha-svg-icon";
import { stopPropagation } from "../common/dom/stop_propagation";
import "@material/mwc-menu/mwc-menu-surface";
@customElement("ha-target-picker") @customElement("ha-target-picker")
export class HaTargetPicker extends LitElement { export class HaTargetPicker extends LitElement {
@ -64,28 +67,21 @@ export class HaTargetPicker extends LitElement {
@property({ type: Boolean, reflect: true }) public disabled = false; @property({ type: Boolean, reflect: true }) public disabled = false;
@property({ type: Boolean }) public horizontal = false; @property({ type: Boolean }) public addOnTop = false;
@state() private _addMode?: "area_id" | "entity_id" | "device_id"; @state() private _addMode?: "area_id" | "entity_id" | "device_id";
@query("#input") private _inputElement?; @query("#input") private _inputElement?;
@query(".add-container", true) private _addContainer?: HTMLDivElement;
private _opened = false;
protected render() { protected render() {
return html` if (this.addOnTop) {
${this.horizontal return html` ${this._renderChips()} ${this._renderItems()} `;
? html` }
<div class="horizontal-container"> return html` ${this._renderItems()} ${this._renderChips()} `;
${this._renderChips()} ${this._renderPicker()}
</div>
${this._renderItems()}
`
: html`
<div>
${this._renderItems()} ${this._renderPicker()}
${this._renderChips()}
</div>
`}
`;
} }
private _renderItems() { private _renderItems() {
@ -132,7 +128,7 @@ export class HaTargetPicker extends LitElement {
private _renderChips() { private _renderChips() {
return html` return html`
<div class="mdc-chip-set"> <div class="mdc-chip-set add-container">
<div <div
class="mdc-chip area_id add" class="mdc-chip area_id add"
.type=${"area_id"} .type=${"area_id"}
@ -193,6 +189,7 @@ export class HaTargetPicker extends LitElement {
</span> </span>
</span> </span>
</div> </div>
${this._renderPicker()}
</div> </div>
${this.helper ${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>` ? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
@ -200,11 +197,8 @@ export class HaTargetPicker extends LitElement {
`; `;
} }
private async _showPicker(ev) { private _showPicker(ev) {
this._addMode = ev.currentTarget.type; this._addMode = ev.currentTarget.type;
await this.updateComplete;
await this._inputElement?.focus();
await this._inputElement?.open();
} }
private _renderChip( private _renderChip(
@ -239,7 +233,7 @@ export class HaTargetPicker extends LitElement {
</span> </span>
${type === "entity_id" ${type === "entity_id"
? "" ? ""
: html` <span role="gridcell"> : html`<span role="gridcell">
<ha-icon-button <ha-icon-button
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing" class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1" tabindex="-1"
@ -282,61 +276,72 @@ export class HaTargetPicker extends LitElement {
} }
private _renderPicker() { private _renderPicker() {
switch (this._addMode) { if (!this._addMode) {
case "area_id": return html``;
return html`
<ha-area-picker
.hass=${this.hass}
id="input"
.type=${"area_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}
no-add
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
.excludeAreas=${ensureArray(this.value?.area_id)}
@value-changed=${this._targetPicked}
></ha-area-picker>
`;
case "device_id":
return html`
<ha-device-picker
.hass=${this.hass}
id="input"
.type=${"device_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
.excludeDevices=${ensureArray(this.value?.device_id)}
@value-changed=${this._targetPicked}
></ha-device-picker>
`;
case "entity_id":
return html`
<ha-entity-picker
.hass=${this.hass}
id="input"
.type=${"entity_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
.excludeEntities=${ensureArray(this.value?.entity_id)}
@value-changed=${this._targetPicked}
allow-custom-entity
></ha-entity-picker>
`;
} }
return html``; return html`<mwc-menu-surface
open
.anchor=${this._addContainer}
.corner=${"BOTTOM_START"}
@closed=${this._onClosed}
@opened=${this._onOpened}
@opened-changed=${this._openedChanged}
@input=${stopPropagation}
>${this._addMode === "area_id"
? html`
<ha-area-picker
.hass=${this.hass}
id="input"
.type=${"area_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}
no-add
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
.excludeAreas=${ensureArray(this.value?.area_id)}
@value-changed=${this._targetPicked}
@click=${this._preventDefault}
></ha-area-picker>
`
: this._addMode === "device_id"
? html`
<ha-device-picker
.hass=${this.hass}
id="input"
.type=${"device_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
.excludeDevices=${ensureArray(this.value?.device_id)}
@value-changed=${this._targetPicked}
@click=${this._preventDefault}
></ha-device-picker>
`
: html`
<ha-entity-picker
.hass=${this.hass}
id="input"
.type=${"entity_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
.excludeEntities=${ensureArray(this.value?.entity_id)}
@value-changed=${this._targetPicked}
@click=${this._preventDefault}
allow-custom-entity
></ha-entity-picker>
`}</mwc-menu-surface
>`;
} }
private _targetPicked(ev) { private _targetPicked(ev) {
@ -347,7 +352,6 @@ export class HaTargetPicker extends LitElement {
const value = ev.detail.value; const value = ev.detail.value;
const target = ev.currentTarget; const target = ev.currentTarget;
target.value = ""; target.value = "";
this._addMode = undefined;
if ( if (
this.value && this.value &&
this.value[target.type] && this.value[target.type] &&
@ -454,6 +458,31 @@ export class HaTargetPicker extends LitElement {
return undefined; return undefined;
} }
private _onClosed(ev) {
ev.stopPropagation();
ev.target.open = true;
}
private async _onOpened() {
if (!this._addMode) {
return;
}
await this._inputElement?.focus();
await this._inputElement?.open();
this._opened = true;
}
private _openedChanged(ev: ComboBoxLightOpenedChangedEvent) {
if (this._opened && !ev.detail.value) {
this._opened = false;
this._addMode = undefined;
}
}
private _preventDefault(ev: Event) {
ev.preventDefault();
}
private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean { private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean {
const devEntities = Object.values(this.hass.entities).filter( const devEntities = Object.values(this.hass.entities).filter(
(entity) => entity.device_id === device.id (entity) => entity.device_id === device.id
@ -555,12 +584,6 @@ export class HaTargetPicker extends LitElement {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
${unsafeCSS(chipStyles)} ${unsafeCSS(chipStyles)}
.horizontal-container {
display: flex;
flex-wrap: wrap;
min-height: 56px;
align-items: center;
}
.mdc-chip { .mdc-chip {
color: var(--primary-text-color); color: var(--primary-text-color);
} }
@ -573,6 +596,10 @@ export class HaTargetPicker extends LitElement {
.mdc-chip.add { .mdc-chip.add {
color: rgba(0, 0, 0, 0.87); color: rgba(0, 0, 0, 0.87);
} }
.add-container {
position: relative;
display: inline-flex;
}
.mdc-chip:not(.add) { .mdc-chip:not(.add) {
cursor: default; cursor: default;
} }
@ -644,6 +671,15 @@ export class HaTargetPicker extends LitElement {
opacity: var(--light-disabled-opacity); opacity: var(--light-disabled-opacity);
pointer-events: none; pointer-events: none;
} }
mwc-menu-surface {
--mdc-menu-min-width: 100%;
}
ha-entity-picker,
ha-device-picker,
ha-area-picker {
display: block;
width: 100%;
}
`; `;
} }
} }

View File

@ -156,7 +156,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
</app-header> </app-header>
<div class="flex content"> <div class="flex content">
<div class="filters flex layout horizontal narrow-wrap"> <div class="filters">
<ha-date-range-picker <ha-date-range-picker
.hass=${this.hass} .hass=${this.hass}
?disabled=${this._isLoading} ?disabled=${this._isLoading}
@ -169,7 +169,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
.hass=${this.hass} .hass=${this.hass}
.value=${this._targetPickerValue} .value=${this._targetPickerValue}
.disabled=${this._isLoading} .disabled=${this._isLoading}
horizontal addOnTop
@value-changed=${this._targetsChanged} @value-changed=${this._targetsChanged}
></ha-target-picker> ></ha-target-picker>
</div> </div>
@ -510,18 +510,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
height: 100%; height: 100%;
} }
:host([narrow]) .narrow-wrap {
flex-wrap: wrap;
}
.horizontal {
align-items: center;
}
:host(:not([narrow])) .selector-padding {
padding-left: 32px;
}
.progress-wrapper { .progress-wrapper {
position: relative; position: relative;
} }
@ -529,11 +517,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
.filters { .filters {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
padding: 8px 16px 0; margin-top: 16px;
}
:host([narrow]) .filters {
flex-wrap: wrap;
} }
ha-date-range-picker { ha-date-range-picker {
@ -544,11 +528,15 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
direction: var(--direction); direction: var(--direction);
} }
:host([narrow]) ha-date-range-picker { @media all and (max-width: 1025px) {
margin-right: 0; .filters {
margin-inline-end: 0; flex-direction: column;
margin-inline-start: initial; }
direction: var(--direction); ha-date-range-picker {
margin-right: 0;
margin-inline-end: 0;
width: 100%;
}
} }
ha-circular-progress { ha-circular-progress {
@ -558,17 +546,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
ha-entity-picker {
display: inline-block;
flex-grow: 1;
max-width: 400px;
}
:host([narrow]) ha-entity-picker {
max-width: none;
width: 100%;
}
.start-search { .start-search {
padding-top: 16px; padding-top: 16px;
text-align: center; text-align: center;

View File

@ -249,6 +249,7 @@ export class HaPanelLogbook extends LitElement {
margin-inline-start: initial; margin-inline-start: initial;
max-width: 100%; max-width: 100%;
direction: var(--direction); direction: var(--direction);
margin-bottom: -5px;
} }
:host([narrow]) ha-date-range-picker { :host([narrow]) ha-date-range-picker {
@ -256,6 +257,7 @@ export class HaPanelLogbook extends LitElement {
margin-inline-end: 0; margin-inline-end: 0;
margin-inline-start: initial; margin-inline-start: initial;
direction: var(--direction); direction: var(--direction);
margin-bottom: 8px;
} }
.filters { .filters {