Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/device/ha-device-picker.ts
Expand Up @@ -214,7 +214,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
return devEntities.some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
Expand Down
10 changes: 9 additions & 1 deletion src/components/device/ha-devices-picker.ts
Expand Up @@ -4,7 +4,10 @@ import { fireEvent } from "../../common/dom/fire_event";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "./ha-device-picker";
import type { HaDevicePickerDeviceFilterFunc } from "./ha-device-picker";
import type {
HaDevicePickerDeviceFilterFunc,
HaDevicePickerEntityFilterFunc,
} from "./ha-device-picker";

@customElement("ha-devices-picker")
class HaDevicesPicker extends LitElement {
Expand Down Expand Up @@ -44,6 +47,8 @@ class HaDevicesPicker extends LitElement {

@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;

@property() public entityFilter?: HaDevicePickerEntityFilterFunc;

protected render(): TemplateResult {
if (!this.hass) {
return html``;
Expand All @@ -59,6 +64,7 @@ class HaDevicesPicker extends LitElement {
.curValue=${entityId}
.hass=${this.hass}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
Expand All @@ -76,8 +82,10 @@ class HaDevicesPicker extends LitElement {
.hass=${this.hass}
.helper=${this.helper}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains}
.excludeDevices=${currentDevices}
.includeDeviceClasses=${this.includeDeviceClasses}
.label=${this.pickDeviceLabel}
.disabled=${this.disabled}
Expand Down
3 changes: 3 additions & 0 deletions src/components/ha-area-picker.ts
Expand Up @@ -233,6 +233,9 @@ export class HaAreaPicker extends LitElement {
});
inputEntities = inputEntities!.filter((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return entityFilter!(stateObj);
});
}
Expand Down
202 changes: 119 additions & 83 deletions src/components/ha-target-picker.ts
Expand Up @@ -13,6 +13,7 @@ import { HassEntity, HassServiceTarget } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
import { customElement, property, query, state } from "lit/decorators";
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 { fireEvent } from "../common/dom/fire_event";
import { computeDomain } from "../common/entity/compute_domain";
Expand All @@ -31,6 +32,8 @@ import "./ha-area-picker";
import "./ha-icon-button";
import "./ha-input-helper-text";
import "./ha-svg-icon";
import { stopPropagation } from "../common/dom/stop_propagation";
import "@material/mwc-menu/mwc-menu-surface";

@customElement("ha-target-picker")
export class HaTargetPicker extends LitElement {
Expand Down Expand Up @@ -64,28 +67,21 @@ export class HaTargetPicker extends LitElement {

@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";

@query("#input") private _inputElement?;

@query(".add-container", true) private _addContainer?: HTMLDivElement;

private _opened = false;

protected render() {
return html`
${this.horizontal
? html`
<div class="horizontal-container">
${this._renderChips()} ${this._renderPicker()}
</div>
${this._renderItems()}
`
: html`
<div>
${this._renderItems()} ${this._renderPicker()}
${this._renderChips()}
</div>
`}
`;
if (this.addOnTop) {
return html` ${this._renderChips()} ${this._renderItems()} `;
}
return html` ${this._renderItems()} ${this._renderChips()} `;
}

private _renderItems() {
Expand Down Expand Up @@ -132,7 +128,7 @@ export class HaTargetPicker extends LitElement {

private _renderChips() {
return html`
<div class="mdc-chip-set">
<div class="mdc-chip-set add-container">
<div
class="mdc-chip area_id add"
.type=${"area_id"}
Expand Down Expand Up @@ -193,18 +189,16 @@ export class HaTargetPicker extends LitElement {
</span>
</span>
</div>
${this._renderPicker()}
</div>
${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
: ""}
`;
}

private async _showPicker(ev) {
private _showPicker(ev) {
this._addMode = ev.currentTarget.type;
await this.updateComplete;
await this._inputElement?.focus();
await this._inputElement?.open();
}

private _renderChip(
Expand Down Expand Up @@ -239,7 +233,7 @@ export class HaTargetPicker extends LitElement {
</span>
${type === "entity_id"
? ""
: html` <span role="gridcell">
: html`<span role="gridcell">
<ha-icon-button
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
Expand Down Expand Up @@ -282,61 +276,72 @@ export class HaTargetPicker extends LitElement {
}

private _renderPicker() {
switch (this._addMode) {
case "area_id":
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>
`;
if (!this._addMode) {
return html``;
}
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) {
Expand All @@ -347,7 +352,6 @@ export class HaTargetPicker extends LitElement {
const value = ev.detail.value;
const target = ev.currentTarget;
target.value = "";
this._addMode = undefined;
if (
this.value &&
this.value[target.type] &&
Expand Down Expand Up @@ -454,6 +458,31 @@ export class HaTargetPicker extends LitElement {
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 {
const devEntities = Object.values(this.hass.entities).filter(
(entity) => entity.device_id === device.id
Expand Down Expand Up @@ -555,12 +584,6 @@ export class HaTargetPicker extends LitElement {
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(chipStyles)}
.horizontal-container {
display: flex;
flex-wrap: wrap;
min-height: 56px;
align-items: center;
}
.mdc-chip {
color: var(--primary-text-color);
}
Expand All @@ -573,6 +596,10 @@ export class HaTargetPicker extends LitElement {
.mdc-chip.add {
color: rgba(0, 0, 0, 0.87);
}
.add-container {
position: relative;
display: inline-flex;
}
.mdc-chip:not(.add) {
cursor: default;
}
Expand Down Expand Up @@ -644,6 +671,15 @@ export class HaTargetPicker extends LitElement {
opacity: var(--light-disabled-opacity);
pointer-events: none;
}
mwc-menu-surface {
--mdc-menu-min-width: 100%;
}
ha-entity-picker,
ha-device-picker,
ha-area-picker {
display: block;
width: 100%;
}
`;
}
}
Expand Down