diff --git a/src/common/search/search-input.ts b/src/common/search/search-input.ts new file mode 100644 index 0000000000..deb36885b7 --- /dev/null +++ b/src/common/search/search-input.ts @@ -0,0 +1,83 @@ +import { TemplateResult, html } from "lit-html"; +import { + css, + CSSResult, + customElement, + LitElement, + property, +} from "lit-element"; +import { fireEvent } from "../dom/fire_event"; +import "@polymer/iron-icon/iron-icon"; +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-icon-button/paper-icon-button"; +import "@material/mwc-button"; + +@customElement("search-input") +class SearchInput extends LitElement { + @property() private filter?: string; + + protected render(): TemplateResult | void { + return html` +
+ + + ${this.filter && + html` + + `} + +
+ `; + } + + private async _filterChanged(value: string) { + fireEvent(this, "value-changed", { value: String(value) }); + } + + private async _filterInputChanged(e) { + this._filterChanged(e.target.value); + } + + private async _clearSearch() { + this._filterChanged(""); + } + + static get styles(): CSSResult { + return css` + paper-input { + flex: 1 1 auto; + margin: 0 16px; + } + .search-container { + display: inline-flex; + width: 100%; + align-items: center; + } + .prefix { + margin: 8px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "search-input": SearchInput; + } +} diff --git a/src/dialogs/config-flow/step-flow-pick-handler.ts b/src/dialogs/config-flow/step-flow-pick-handler.ts index ef64b0798a..0fbe0ba597 100644 --- a/src/dialogs/config-flow/step-flow-pick-handler.ts +++ b/src/dialogs/config-flow/step-flow-pick-handler.ts @@ -1,10 +1,11 @@ import { - LitElement, - TemplateResult, - html, css, - customElement, CSSResult, + customElement, + html, + LitElement, + property, + TemplateResult, } from "lit-element"; import "@polymer/paper-spinner/paper-spinner-lite"; import "@polymer/paper-item/paper-item"; @@ -12,23 +13,62 @@ import "@polymer/paper-item/paper-item-body"; import { HomeAssistant } from "../../types"; import { createConfigFlow } from "../../data/config_entries"; import { fireEvent } from "../../common/dom/fire_event"; +import memoizeOne from "memoize-one"; +import * as Fuse from "fuse.js"; + import "../../components/ha-icon-next"; +import "../../common/search/search-input"; + +interface HandlerObj { + name: string; + slug: string; +} @customElement("step-flow-pick-handler") class StepFlowPickHandler extends LitElement { - public hass!: HomeAssistant; - public handlers!: string[]; + @property() public hass!: HomeAssistant; + @property() public handlers!: string[]; + @property() private filter?: string; + + private _getHandlers = memoizeOne((h: string[], filter?: string) => { + const handlers: HandlerObj[] = h.map((handler) => { + return { + name: this.hass.localize(`component.${handler}.config.title`), + slug: handler, + }; + }); + + if (filter) { + const options: Fuse.FuseOptions = { + keys: ["name", "slug"], + caseSensitive: false, + minMatchCharLength: 2, + threshold: 0.2, + }; + const fuse = new Fuse(handlers, options); + return fuse.search(filter); + } + return handlers.sort((a, b) => + a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1 + ); + }); protected render(): TemplateResult | void { + const handlers = this._getHandlers(this.handlers, this.filter); + return html`

${this.hass.localize("ui.panel.config.integrations.new")}

- ${this.handlers.map( - (handler) => + + ${handlers.map( + (handler: HandlerObj) => html` - ${this.hass.localize(`component.${handler}.config.title`)} + ${handler.name} @@ -38,15 +78,20 @@ class StepFlowPickHandler extends LitElement { `; } + private async _filterChanged(e) { + this.filter = e.detail.value; + } + private async _handlerPicked(ev) { fireEvent(this, "flow-update", { - stepPromise: createConfigFlow(this.hass, ev.currentTarget.handler), + stepPromise: createConfigFlow(this.hass, ev.currentTarget.handler.slug), }); } static get styles(): CSSResult { return css` h2 { + margin-bottom: 2px; padding-left: 16px; } div {