Add multi select component to ha-form (#4247)

* Add multi select component

* Apply suggestions from code review

Co-Authored-By: Ian Richardson <iantrich@gmail.com>

* Comments

* update

* Fix

* Refactor to dropdown menu

Co-authored-by: Ian Richardson <iantrich@gmail.com>
This commit is contained in:
Bram Kragten 2020-02-17 14:13:09 +01:00 committed by GitHub
parent 24e4b0b772
commit 8f9a6bd544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 193 additions and 17 deletions

View File

@ -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`
<paper-menu-button horizontal-align="right" vertical-offset="8">
<div class="dropdown-trigger" slot="dropdown-trigger">
<paper-ripple></paper-ripple>
<paper-input
id="input"
type="text"
readonly
value=${this.data
.map((value) => this.schema.options![value] || value)
.join(", ")}
label=${this.label}
input-role="button"
input-aria-haspopup="listbox"
autocomplete="off"
>
<iron-icon
icon="paper-dropdown-menu:arrow-drop-down"
suffix
slot="suffix"
></iron-icon>
</paper-input>
</div>
<paper-listbox
multi
slot="dropdown-content"
attr-for-selected="item-value"
.selectedValues=${this.data}
@selected-items-changed=${this._valueChanged}
@iron-select=${this._onSelect}
>
${// 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`
<paper-icon-item .itemValue=${value}>
<paper-checkbox
.checked=${this.data.includes(value)}
slot="item-icon"
></paper-checkbox>
${this._optionLabel(item)}
</paper-icon-item>
`;
})}
</paper-listbox>
</paper-menu-button>
`;
}
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;
}
}

View File

@ -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`
<paper-item .itemValue=${this._optionValue(item)}>
${this._optionLabel(item)}
</paper-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 {

View File

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

View File

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

View File

@ -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(

View File

@ -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) =>