From 8f9a6bd544f3939316fa973f8078522e5d6413e0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 17 Feb 2020 14:13:09 +0100 Subject: [PATCH] Add multi select component to ha-form (#4247) * Add multi select component * Apply suggestions from code review Co-Authored-By: Ian Richardson * Comments * update * Fix * Refactor to dropdown menu Co-authored-by: Ian Richardson --- .../ha-form/ha-form-multi_select.ts | 157 ++++++++++++++++++ src/components/ha-form/ha-form-select.ts | 22 ++- src/components/ha-form/ha-form.ts | 11 +- src/data/data_entry_flow.ts | 10 +- .../show-dialog-data-entry-flow.ts | 4 +- src/dialogs/config-flow/step-flow-form.ts | 6 +- 6 files changed, 193 insertions(+), 17 deletions(-) create mode 100644 src/components/ha-form/ha-form-multi_select.ts diff --git a/src/components/ha-form/ha-form-multi_select.ts b/src/components/ha-form/ha-form-multi_select.ts new file mode 100644 index 0000000000..2f21740a14 --- /dev/null +++ b/src/components/ha-form/ha-form-multi_select.ts @@ -0,0 +1,157 @@ +import "@polymer/paper-checkbox/paper-checkbox"; +import "@polymer/paper-menu-button/paper-menu-button"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-item/paper-icon-item"; +import "@polymer/paper-listbox/paper-listbox"; +import "@polymer/paper-ripple/paper-ripple"; +import { + customElement, + html, + LitElement, + property, + query, + TemplateResult, + CSSResult, + css, +} from "lit-element"; +import { fireEvent } from "../../common/dom/fire_event"; +import { + HaFormElement, + HaFormMultiSelectData, + HaFormMultiSelectSchema, +} from "./ha-form"; + +@customElement("ha-form-multi_select") +export class HaFormMultiSelect extends LitElement implements HaFormElement { + @property() public schema!: HaFormMultiSelectSchema; + @property() public data!: HaFormMultiSelectData; + @property() public label!: string; + @property() public suffix!: string; + @property() private _init = false; + @query("paper-menu-button") private _input?: HTMLElement; + + public focus(): void { + if (this._input) { + this._input.focus(); + } + } + + protected render(): TemplateResult { + const options = Array.isArray(this.schema.options) + ? this.schema.options + : Object.entries(this.schema.options!); + + return html` + + + + ${// TS doesn't work with union array types https://github.com/microsoft/TypeScript/issues/36390 + // @ts-ignore + options.map((item: string | [string, string]) => { + const value = this._optionValue(item); + return html` + + + ${this._optionLabel(item)} + + `; + })} + + + `; + } + + protected firstUpdated() { + this.updateComplete.then(() => { + const input = (this.shadowRoot?.querySelector("paper-input") + ?.inputElement as any)?.inputElement; + if (input) { + input.style.textOverflow = "ellipsis"; + } + }); + } + + private _optionValue(item: string | string[]): string { + return Array.isArray(item) ? item[0] : item; + } + + private _optionLabel(item: string | string[]): string { + return Array.isArray(item) ? item[1] || item[0] : item; + } + + private _onSelect(ev: Event) { + ev.stopPropagation(); + } + + private _valueChanged(ev: CustomEvent): void { + if (!ev.detail.value || !this._init) { + // ignore first call because that is the init of the component + this._init = true; + return; + } + + fireEvent( + this, + "value-changed", + { + value: ev.detail.value.map((element) => element.itemValue), + }, + { bubbles: false } + ); + } + + static get styles(): CSSResult { + return css` + paper-menu-button { + display: block; + padding: 0; + --paper-item-icon-width: 34px; + } + paper-ripple { + top: 12px; + left: 0px; + bottom: 8px; + right: 0px; + } + paper-input { + text-overflow: ellipsis; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-form-multi_select": HaFormMultiSelect; + } +} diff --git a/src/components/ha-form/ha-form-select.ts b/src/components/ha-form/ha-form-select.ts index d0a871af01..f650ac3bfd 100644 --- a/src/components/ha-form/ha-form-select.ts +++ b/src/components/ha-form/ha-form-select.ts @@ -5,6 +5,8 @@ import { property, TemplateResult, query, + CSSResult, + css, } from "lit-element"; import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./ha-form"; import { fireEvent } from "../../common/dom/fire_event"; @@ -36,8 +38,10 @@ export class HaFormSelect extends LitElement implements HaFormElement { .selected=${this.data} @selected-item-changed=${this._valueChanged} > - ${this.schema.options!.map( - (item) => html` + ${// TS doesn't work with union array types https://github.com/microsoft/TypeScript/issues/36390 + // @ts-ignore + this.schema.options!.map( + (item: string | [string, string]) => html` ${this._optionLabel(item)} @@ -48,12 +52,12 @@ export class HaFormSelect extends LitElement implements HaFormElement { `; } - private _optionValue(item) { + private _optionValue(item: string | [string, string]) { return Array.isArray(item) ? item[0] : item; } - private _optionLabel(item) { - return Array.isArray(item) ? item[1] : item; + private _optionLabel(item: string | [string, string]) { + return Array.isArray(item) ? item[1] || item[0] : item; } private _valueChanged(ev: CustomEvent) { @@ -64,6 +68,14 @@ export class HaFormSelect extends LitElement implements HaFormElement { value: ev.detail.value.itemValue, }); } + + static get styles(): CSSResult { + return css` + paper-dropdown-menu { + display: block; + } + `; + } } declare global { diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index 5925dee226..24102dff1a 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -12,6 +12,7 @@ import "./ha-form-integer"; import "./ha-form-float"; import "./ha-form-boolean"; import "./ha-form-select"; +import "./ha-form-multi_select"; import "./ha-form-positive_time_period_dict"; import { fireEvent } from "../../common/dom/fire_event"; import { dynamicElement } from "../../common/dom/dynamic-element-directive"; @@ -22,6 +23,7 @@ export type HaFormSchema = | HaFormFloatSchema | HaFormBooleanSchema | HaFormSelectSchema + | HaFormMultiSelectSchema | HaFormTimeSchema; export interface HaFormBaseSchema { @@ -41,7 +43,12 @@ export interface HaFormIntegerSchema extends HaFormBaseSchema { export interface HaFormSelectSchema extends HaFormBaseSchema { type: "select"; - options?: string[]; + options?: string[] | Array<[string, string]>; +} + +export interface HaFormMultiSelectSchema extends HaFormBaseSchema { + type: "multi_select"; + options?: { [key: string]: string } | string[] | Array<[string, string]>; } export interface HaFormFloatSchema extends HaFormBaseSchema { @@ -71,6 +78,7 @@ export type HaFormData = | HaFormFloatData | HaFormBooleanData | HaFormSelectData + | HaFormMultiSelectData | HaFormTimeData; export type HaFormStringData = string; @@ -78,6 +86,7 @@ export type HaFormIntegerData = number; export type HaFormFloatData = number; export type HaFormBooleanData = boolean; export type HaFormSelectData = string; +export type HaFormMultiSelectData = string[]; export interface HaFormTimeData { hours?: number; minutes?: number; diff --git a/src/data/data_entry_flow.ts b/src/data/data_entry_flow.ts index 7f2e8ab894..dd2d09363f 100644 --- a/src/data/data_entry_flow.ts +++ b/src/data/data_entry_flow.ts @@ -1,3 +1,5 @@ +import { HaFormSchema } from "../components/ha-form/ha-form"; + export interface DataEntryFlowProgressedEvent { type: "data_entry_flow_progressed"; data: { @@ -7,12 +9,6 @@ export interface DataEntryFlowProgressedEvent { }; } -export interface FieldSchema { - name: string; - default?: any; - optional: boolean; -} - export interface DataEntryFlowProgress { flow_id: string; handler: string; @@ -27,7 +23,7 @@ export interface DataEntryFlowStepForm { flow_id: string; handler: string; step_id: string; - data_schema: FieldSchema[]; + data_schema: HaFormSchema[]; errors: { [key: string]: string }; description_placeholders: { [key: string]: string }; } diff --git a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts index 623732856a..490a6becc0 100644 --- a/src/dialogs/config-flow/show-dialog-data-entry-flow.ts +++ b/src/dialogs/config-flow/show-dialog-data-entry-flow.ts @@ -7,8 +7,8 @@ import { DataEntryFlowStepForm, DataEntryFlowStep, DataEntryFlowStepAbort, - FieldSchema, } from "../../data/data_entry_flow"; +import { HaFormSchema } from "../../components/ha-form/ha-form"; export interface FlowConfig { loadDevicesAndAreas: boolean; @@ -45,7 +45,7 @@ export interface FlowConfig { renderShowFormStepFieldLabel( hass: HomeAssistant, step: DataEntryFlowStepForm, - field: FieldSchema + field: HaFormSchema ): string; renderShowFormStepFieldError( diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index 172cf333a9..adf6a4ea5c 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -18,8 +18,10 @@ import "../../resources/ha-style"; import { HomeAssistant } from "../../types"; import { fireEvent } from "../../common/dom/fire_event"; import { configFlowContentStyles } from "./styles"; -import { DataEntryFlowStepForm, FieldSchema } from "../../data/data_entry_flow"; +import { DataEntryFlowStepForm } from "../../data/data_entry_flow"; import { FlowConfig } from "./show-dialog-data-entry-flow"; +// tslint:disable-next-line +import { HaFormSchema } from "../../components/ha-form/ha-form"; @customElement("step-flow-form") class StepFlowForm extends LitElement { @@ -176,7 +178,7 @@ class StepFlowForm extends LitElement { this._stepData = ev.detail.value; } - private _labelCallback = (field: FieldSchema): string => + private _labelCallback = (field: HaFormSchema): string => this.flowConfig.renderShowFormStepFieldLabel(this.hass, this.step, field); private _errorCallback = (error: string) =>