import "@home-assistant/webawesome/dist/components/popover/popover"; import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize"; import { mdiPlaylistPlus } from "@mdi/js"; import { css, html, LitElement, nothing, type CSSResultGroup } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { tinykeys } from "tinykeys"; import { fireEvent } from "../common/dom/fire_event"; import type { HomeAssistant } from "../types"; import "./ha-bottom-sheet"; import "./ha-button"; import "./ha-combo-box-item"; import "./ha-icon-button"; import "./ha-input-helper-text"; import "./ha-picker-combo-box"; import type { HaPickerComboBox, PickerComboBoxItem, PickerComboBoxSearchFn, } from "./ha-picker-combo-box"; import "./ha-picker-field"; import type { PickerValueRenderer } from "./ha-picker-field"; import "./ha-svg-icon"; @customElement("ha-generic-picker") export class HaGenericPicker extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; // eslint-disable-next-line lit/no-native-attributes @property({ type: Boolean }) public autofocus = false; @property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public required = false; @property({ type: Boolean, attribute: "allow-custom-value" }) public allowCustomValue; @property() public label?: string; @property() public value?: string; @property() public helper?: string; @property() public placeholder?: string; @property({ type: String, attribute: "search-label" }) public searchLabel?: string; @property({ attribute: "hide-clear-icon", type: Boolean }) public hideClearIcon = false; @property({ attribute: false, type: Array }) public getItems?: () => PickerComboBoxItem[]; @property({ attribute: false, type: Array }) public getAdditionalItems?: (searchString?: string) => PickerComboBoxItem[]; @property({ attribute: false }) public rowRenderer?: RenderItemFunction; @property({ attribute: false }) public valueRenderer?: PickerValueRenderer; @property({ attribute: false }) public searchFn?: PickerComboBoxSearchFn; @property({ attribute: "not-found-label", type: String }) public notFoundLabel?: string; @property({ attribute: "popover-placement" }) public popoverPlacement: | "bottom" | "top" | "left" | "right" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end" = "bottom-start"; /** If set picker shows an add button instead of textbox when value isn't set */ @property({ attribute: "add-button-label" }) public addButtonLabel?: string; @query(".container") private _containerElement?: HTMLDivElement; @query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox; @state() private _opened = false; @state() private _pickerWrapperOpen = false; @state() private _popoverWidth = 0; @state() private _openedNarrow = false; private _narrow = false; // helper to set new value after closing picker, to avoid flicker private _newValue?: string; private _unsubscribeTinyKeys?: () => void; protected render() { return html` ${this.label ? html`` : nothing}
${this.addButtonLabel && !this.value ? html` ${this.addButtonLabel} ` : html` `}
${!this._openedNarrow && (this._pickerWrapperOpen || this._opened) ? html` ${this._renderComboBox()} ` : this._pickerWrapperOpen || this._opened ? html` ${this._renderComboBox(true)} ` : nothing}
${this._renderHelper()} `; } private _renderComboBox(dialogMode = false) { if (!this._opened) { return nothing; } return html` `; } private _renderHelper() { return this.helper ? html`${this.helper}` : nothing; } private _dialogOpened = () => { this._opened = true; requestAnimationFrame(() => { this._comboBox?.focus(); }); }; private _hidePicker(ev) { ev.stopPropagation(); if (this._newValue) { fireEvent(this, "value-changed", { value: this._newValue }); this._newValue = undefined; } this._opened = false; this._pickerWrapperOpen = false; this._unsubscribeTinyKeys?.(); } private _valueChanged(ev: CustomEvent) { ev.stopPropagation(); const value = ev.detail.value; if (!value) { return; } this._pickerWrapperOpen = false; this._newValue = value; } private _clear(e) { e.stopPropagation(); this._setValue(undefined); } private _setValue(value: string | undefined) { this.value = value; fireEvent(this, "value-changed", { value }); } public async open(ev?: Event) { ev?.stopPropagation(); if (this.disabled) { return; } this._openedNarrow = this._narrow; this._popoverWidth = this._containerElement?.offsetWidth || 250; this._pickerWrapperOpen = true; this._unsubscribeTinyKeys = tinykeys(this, { Escape: this._handleEscClose, }); } connectedCallback() { super.connectedCallback(); this._handleResize(); window.addEventListener("resize", this._handleResize); } public disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("resize", this._handleResize); this._unsubscribeTinyKeys?.(); } private _handleResize = () => { this._narrow = window.matchMedia("(max-width: 870px)").matches || window.matchMedia("(max-height: 500px)").matches; if (!this._openedNarrow && this._pickerWrapperOpen) { this._popoverWidth = this._containerElement?.offsetWidth || 250; } }; private _handleEscClose = (ev: KeyboardEvent) => { ev.stopPropagation(); }; static get styles(): CSSResultGroup { return [ css` .container { position: relative; display: block; } label[disabled] { color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.6)); } label { display: block; margin: 0 0 8px; } ha-input-helper-text { display: block; margin: var(--ha-space-2) 0 0; } wa-popover { --wa-space-l: var(--ha-space-0); } wa-popover::part(body) { width: max(var(--body-width), 250px); max-width: max(var(--body-width), 250px); max-height: 500px; height: 70vh; overflow: hidden; } @media (max-height: 1000px) { wa-popover::part(body) { max-height: 400px; } } @media (max-height: 1000px) { wa-popover::part(body) { max-height: 400px; } } ha-bottom-sheet { --ha-bottom-sheet-height: 90vh; --ha-bottom-sheet-height: calc(100dvh - var(--ha-space-12)); --ha-bottom-sheet-max-height: var(--ha-bottom-sheet-height); --ha-bottom-sheet-max-width: 600px; --ha-bottom-sheet-padding: var(--ha-space-0); --ha-bottom-sheet-surface-background: var(--card-background-color); --ha-bottom-sheet-border-radius: var(--ha-border-radius-2xl); } ha-picker-field.opened { --mdc-text-field-idle-line-color: var(--primary-color); } `, ]; } } declare global { interface HTMLElementTagNameMap { "ha-generic-picker": HaGenericPicker; } }