From 8d2c716fbe7f819ecb97fcb3f31c2bb2ac2668ea Mon Sep 17 00:00:00 2001 From: Yosi Levy Date: Fri, 28 Jan 2022 17:45:12 +0200 Subject: [PATCH 001/174] Fix config card rtl issues --- src/panels/config/info/ha-config-info.ts | 6 +----- src/panels/config/info/integrations-card.ts | 1 + src/panels/config/info/system-health-card.ts | 4 ++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index 943f6aa329..74b10c529f 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -134,7 +134,7 @@ class HaConfigInfo extends LitElement { : ""}

-
+
Date: Thu, 3 Feb 2022 16:30:37 -0800 Subject: [PATCH 002/174] Remove optional field from ha-form schema type (#11538) --- gallery/src/pages/components/ha-form.ts | 15 ++------------- src/components/ha-form/ha-form-integer.ts | 6 +++--- src/components/ha-form/ha-form-select.ts | 4 ++-- src/components/ha-form/ha-form-string.ts | 2 +- src/components/ha-form/types.ts | 1 - src/dialogs/config-flow/step-flow-form.ts | 5 +++-- 6 files changed, 11 insertions(+), 22 deletions(-) diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index 324eb3a58d..c25ff1067f 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -50,13 +50,11 @@ const SCHEMAS: { { type: "boolean", name: "bool", - optional: true, default: false, }, { type: "integer", name: "int", - optional: true, default: 10, }, { @@ -67,7 +65,6 @@ const SCHEMAS: { { type: "string", name: "string", - optional: true, default: "Default", }, { @@ -77,7 +74,6 @@ const SCHEMAS: { ["other", "other"], ], name: "select", - optional: true, default: "default", }, { @@ -87,7 +83,6 @@ const SCHEMAS: { other: "Other", }, name: "multi", - optional: true, default: ["default"], }, { @@ -108,7 +103,6 @@ const SCHEMAS: { { type: "integer", name: "int with default", - optional: true, default: 10, }, { @@ -122,7 +116,6 @@ const SCHEMAS: { { type: "integer", name: "int range optional", - optional: true, valueMin: 0, valueMax: 10, }, @@ -148,7 +141,6 @@ const SCHEMAS: { ["other", "Other"], ], name: "select optional", - optional: true, }, { type: "select", @@ -161,7 +153,6 @@ const SCHEMAS: { ["option", "1000"], ], name: "select many otions", - optional: true, default: "default", }, ], @@ -190,7 +181,6 @@ const SCHEMAS: { option: "1000", }, name: "multi many otions", - optional: true, default: ["default"], }, ], @@ -239,11 +229,10 @@ const SCHEMAS: { valueMin: 1, valueMax: 65535, name: "port", - optional: true, default: 80, }, - { type: "string", name: "path", optional: true, default: "/" }, - { type: "boolean", name: "ssl", optional: true, default: false }, + { type: "string", name: "path", default: "/" }, + { type: "boolean", name: "ssl", default: false }, ], }, ]; diff --git a/src/components/ha-form/ha-form-integer.ts b/src/components/ha-form/ha-form-integer.ts index e8ec34f13f..d4d52fec23 100644 --- a/src/components/ha-form/ha-form-integer.ts +++ b/src/components/ha-form/ha-form-integer.ts @@ -45,7 +45,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
${this.label}
- ${this.schema.optional + ${!this.schema.required ? html`
@@ -100,7 +100,7 @@ export class HaFormInteger extends LitElement implements HaFormElement { return this.data; } - if (this.schema.optional) { + if (!this.schema.required) { return this.schema.valueMin || 0; } diff --git a/src/components/ha-form/ha-form-select.ts b/src/components/ha-form/ha-form-select.ts index 2df5d03242..bbe561ff40 100644 --- a/src/components/ha-form/ha-form-select.ts +++ b/src/components/ha-form/ha-form-select.ts @@ -29,7 +29,7 @@ export class HaFormSelect extends LitElement implements HaFormElement { } protected render(): TemplateResult { - if (!this.schema.optional && this.schema.options!.length < 6) { + if (this.schema.required && this.schema.options!.length < 6) { return html`
${this.label} @@ -59,7 +59,7 @@ export class HaFormSelect extends LitElement implements HaFormElement { @closed=${stopPropagation} @selected=${this._valueChanged} > - ${this.schema.optional + ${!this.schema.required ? html`` : ""} ${this.schema.options!.map( diff --git a/src/components/ha-form/ha-form-string.ts b/src/components/ha-form/ha-form-string.ts index fc787e4e63..22085ab2a5 100644 --- a/src/components/ha-form/ha-form-string.ts +++ b/src/components/ha-form/ha-form-string.ts @@ -89,7 +89,7 @@ export class HaFormString extends LitElement implements HaFormElement { if (this.data === value) { return; } - if (value === "" && this.schema.optional) { + if (value === "" && !this.schema.required) { value = undefined; } fireEvent(this, "value-changed", { diff --git a/src/components/ha-form/types.ts b/src/components/ha-form/types.ts index c52ba0b947..86122bebc1 100644 --- a/src/components/ha-form/types.ts +++ b/src/components/ha-form/types.ts @@ -15,7 +15,6 @@ export interface HaFormBaseSchema { name: string; default?: HaFormData; required?: boolean; - optional?: boolean; description?: { suffix?: string; suggested_value?: HaFormData }; } diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index a1969dc460..3519540633 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -103,12 +103,13 @@ class StepFlowForm extends LitElement { const allRequiredInfoFilledIn = stepData === undefined ? // If no data filled in, just check that any field is required - this.step.data_schema.find((field) => !field.optional) === undefined + this.step.data_schema.find((field) => field.required) === undefined : // If data is filled in, make sure all required fields are stepData && this.step.data_schema.every( (field) => - field.optional || !["", undefined].includes(stepData![field.name]) + !field.required || + !["", undefined].includes(stepData![field.name]) ); if (!allRequiredInfoFilledIn) { From f47440083e8208514f01d7da3ba048488b4ec67d Mon Sep 17 00:00:00 2001 From: Kuba Wolanin Date: Fri, 4 Feb 2022 11:02:09 +0100 Subject: [PATCH 003/174] Add entity id autocompletion to YAML code editors (#11099) --- package.json | 19 +-- src/components/ha-code-editor.ts | 119 +++++++++++---- .../ha-selector/ha-selector-object.ts | 1 + src/components/ha-service-control.ts | 1 + src/components/ha-yaml-editor.ts | 5 + .../action/ha-automation-action-row.ts | 1 + .../types/ha-automation-action-event.ts | 1 + .../ha-automation-condition-editor.ts | 1 + .../config/automation/ha-automation-editor.ts | 1 + .../trigger/ha-automation-trigger-row.ts | 1 + .../types/ha-automation-trigger-event.ts | 1 + .../mqtt/mqtt-config-panel.ts | 2 + .../zha/dialog-zha-device-zigbee-info.ts | 2 +- src/panels/config/script/ha-script-editor.ts | 1 + .../service/developer-tools-service.ts | 1 + .../template/developer-tools-template.ts | 2 + .../card-editor/hui-dialog-suggest-card.ts | 1 + .../lovelace/editor/hui-dialog-save-config.ts | 1 + .../lovelace/editor/hui-element-editor.ts | 2 + src/panels/lovelace/hui-editor.ts | 1 + src/resources/codemirror.ts | 46 +++++- yarn.lock | 139 +++++++++++------- 22 files changed, 251 insertions(+), 98 deletions(-) diff --git a/package.json b/package.json index 2e31be62fd..9eeac473fb 100644 --- a/package.json +++ b/package.json @@ -22,17 +22,18 @@ "license": "Apache-2.0", "dependencies": { "@braintree/sanitize-url": "^5.0.2", - "@codemirror/commands": "^0.19.5", - "@codemirror/gutter": "^0.19.4", - "@codemirror/highlight": "^0.19.6", - "@codemirror/history": "^0.19.0", + "@codemirror/autocomplete": "^0.19.12", + "@codemirror/commands": "^0.19.8", + "@codemirror/gutter": "^0.19.9", + "@codemirror/highlight": "^0.19.7", + "@codemirror/history": "^0.19.2", "@codemirror/legacy-modes": "^0.19.0", "@codemirror/rectangular-selection": "^0.19.1", - "@codemirror/search": "^0.19.2", - "@codemirror/state": "^0.19.4", - "@codemirror/stream-parser": "^0.19.2", - "@codemirror/text": "^0.19.5", - "@codemirror/view": "^0.19.15", + "@codemirror/search": "^0.19.6", + "@codemirror/state": "^0.19.6", + "@codemirror/stream-parser": "^0.19.5", + "@codemirror/text": "^0.19.6", + "@codemirror/view": "^0.19.40", "@formatjs/intl-datetimeformat": "^4.2.5", "@formatjs/intl-getcanonicallocales": "^1.8.0", "@formatjs/intl-locale": "^2.4.40", diff --git a/src/components/ha-code-editor.ts b/src/components/ha-code-editor.ts index 7ea60707c3..2ec8a2e627 100644 --- a/src/components/ha-code-editor.ts +++ b/src/components/ha-code-editor.ts @@ -1,8 +1,16 @@ +import type { + Completion, + CompletionContext, + CompletionResult, +} from "@codemirror/autocomplete"; import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view"; +import { HassEntities } from "home-assistant-js-websocket"; import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; import { loadCodeMirror } from "../resources/codemirror.ondemand"; +import { HomeAssistant } from "../types"; declare global { interface HASSDomEvents { @@ -24,10 +32,15 @@ export class HaCodeEditor extends ReactiveElement { @property() public mode = "yaml"; + public hass?: HomeAssistant; + @property({ type: Boolean }) public autofocus = false; @property({ type: Boolean }) public readOnly = false; + @property({ type: Boolean, attribute: "autocomplete-entities" }) + public autocompleteEntities = false; + @property() public error = false; @state() private _value = ""; @@ -110,43 +123,92 @@ export class HaCodeEditor extends ReactiveElement { private async _load(): Promise { this._loadedCodeMirror = await loadCodeMirror(); + const extensions = [ + this._loadedCodeMirror.lineNumbers(), + this._loadedCodeMirror.EditorState.allowMultipleSelections.of(true), + this._loadedCodeMirror.history(), + this._loadedCodeMirror.highlightSelectionMatches(), + this._loadedCodeMirror.highlightActiveLine(), + this._loadedCodeMirror.drawSelection(), + this._loadedCodeMirror.rectangularSelection(), + this._loadedCodeMirror.keymap.of([ + ...this._loadedCodeMirror.defaultKeymap, + ...this._loadedCodeMirror.searchKeymap, + ...this._loadedCodeMirror.historyKeymap, + ...this._loadedCodeMirror.tabKeyBindings, + saveKeyBinding, + ] as KeyBinding[]), + this._loadedCodeMirror.langCompartment.of(this._mode), + this._loadedCodeMirror.theme, + this._loadedCodeMirror.Prec.fallback( + this._loadedCodeMirror.highlightStyle + ), + this._loadedCodeMirror.readonlyCompartment.of( + this._loadedCodeMirror.EditorView.editable.of(!this.readOnly) + ), + this._loadedCodeMirror.EditorView.updateListener.of((update) => + this._onUpdate(update) + ), + ]; + + if (!this.readOnly && this.autocompleteEntities && this.hass) { + extensions.push( + this._loadedCodeMirror.autocompletion({ + override: [this._entityCompletions.bind(this)], + maxRenderedOptions: 10, + }) + ); + } this.codemirror = new this._loadedCodeMirror.EditorView({ state: this._loadedCodeMirror.EditorState.create({ doc: this._value, - extensions: [ - this._loadedCodeMirror.lineNumbers(), - this._loadedCodeMirror.EditorState.allowMultipleSelections.of(true), - this._loadedCodeMirror.history(), - this._loadedCodeMirror.highlightSelectionMatches(), - this._loadedCodeMirror.highlightActiveLine(), - this._loadedCodeMirror.drawSelection(), - this._loadedCodeMirror.rectangularSelection(), - this._loadedCodeMirror.keymap.of([ - ...this._loadedCodeMirror.defaultKeymap, - ...this._loadedCodeMirror.searchKeymap, - ...this._loadedCodeMirror.historyKeymap, - ...this._loadedCodeMirror.tabKeyBindings, - saveKeyBinding, - ] as KeyBinding[]), - this._loadedCodeMirror.langCompartment.of(this._mode), - this._loadedCodeMirror.theme, - this._loadedCodeMirror.Prec.fallback( - this._loadedCodeMirror.highlightStyle - ), - this._loadedCodeMirror.readonlyCompartment.of( - this._loadedCodeMirror.EditorView.editable.of(!this.readOnly) - ), - this._loadedCodeMirror.EditorView.updateListener.of((update) => - this._onUpdate(update) - ), - ], + extensions, }), root: this.shadowRoot!, parent: this.shadowRoot!, }); } + private _getStates = memoizeOne((states: HassEntities): Completion[] => { + if (!states) { + return []; + } + const options = Object.keys(states).map((key) => ({ + type: "variable", + label: key, + detail: states[key].attributes.friendly_name, + info: `State: ${states[key].state}`, + })); + + return options; + }); + + private _entityCompletions( + context: CompletionContext + ): CompletionResult | null | Promise { + const entityWord = context.matchBefore(/[a-z_]{3,}\./); + + if ( + !entityWord || + (entityWord.from === entityWord.to && !context.explicit) + ) { + return null; + } + + const states = this._getStates(this.hass!.states); + + if (!states || !states.length) { + return null; + } + + return { + from: Number(entityWord.from), + options: states, + span: /^\w*.\w*$/, + }; + } + private _blockKeyboardShortcuts() { this.addEventListener("keydown", (ev) => ev.stopPropagation()); } @@ -163,10 +225,9 @@ export class HaCodeEditor extends ReactiveElement { fireEvent(this, "value-changed", { value: this._value }); } - // Only Lit 2.0 will use this static get styles(): CSSResultGroup { return css` - :host(.error-state) div.cm-wrap .cm-gutters { + :host(.error-state) .cm-gutters { border-color: var(--error-state-color, red); } `; diff --git a/src/components/ha-selector/ha-selector-object.ts b/src/components/ha-selector/ha-selector-object.ts index e003f28251..0fa42b9588 100644 --- a/src/components/ha-selector/ha-selector-object.ts +++ b/src/components/ha-selector/ha-selector-object.ts @@ -18,6 +18,7 @@ export class HaObjectSelector extends LitElement { protected render() { return html`): boolean => { @@ -18,6 +19,8 @@ const isEmpty = (obj: Record): boolean => { @customElement("ha-yaml-editor") export class HaYamlEditor extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + @property() public value?: any; @property({ attribute: false }) public yamlSchema: Schema = DEFAULT_SCHEMA; @@ -56,8 +59,10 @@ export class HaYamlEditor extends LitElement { return html` ${this.label ? html`

${this.label}

` : ""} diff --git a/src/panels/config/automation/action/types/ha-automation-action-event.ts b/src/panels/config/automation/action/types/ha-automation-action-event.ts index 92b64fe634..26ebb424c9 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-event.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-event.ts @@ -49,6 +49,7 @@ export class HaEventAction extends LitElement implements ActionElement { @value-changed=${this._eventChanged} > diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 97f7cc3999..14da9659ef 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -269,6 +269,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { ` : ``} diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index b8f69ee4c2..15dbd9779a 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -177,6 +177,7 @@ export default class HaAutomationTriggerRow extends LitElement { )} diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts index 6889796957..6804fb8787 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts @@ -33,6 +33,7 @@ export class HaEventTrigger extends LitElement implements TriggerElement { @value-changed=${this._valueChanged} > ${this.hass.localize("ui.panel.config.mqtt.payload")}

diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 1f0219c0c0..75f57c0c84 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -398,6 +398,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { ` : ``} diff --git a/src/panels/developer-tools/service/developer-tools-service.ts b/src/panels/developer-tools/service/developer-tools-service.ts index 71ffb9f8d3..a833fc3b17 100644 --- a/src/panels/developer-tools/service/developer-tools-service.ts +++ b/src/panels/developer-tools/service/developer-tools-service.ts @@ -100,6 +100,7 @@ class HaPanelDevService extends LitElement { @value-changed=${this._serviceChanged} > ` diff --git a/src/panels/developer-tools/template/developer-tools-template.ts b/src/panels/developer-tools/template/developer-tools-template.ts index d67ddf31a1..74a5b8027d 100644 --- a/src/panels/developer-tools/template/developer-tools-template.ts +++ b/src/panels/developer-tools/template/developer-tools-template.ts @@ -128,9 +128,11 @@ class HaPanelDevTemplate extends LitElement {

diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts index 142e82b234..57ea84cb90 100755 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts @@ -85,6 +85,7 @@ export class HuiDialogSuggestCard extends LitElement { ? html`
diff --git a/src/panels/lovelace/editor/hui-dialog-save-config.ts b/src/panels/lovelace/editor/hui-dialog-save-config.ts index 5b2a00fc09..da724a1adc 100644 --- a/src/panels/lovelace/editor/hui-dialog-save-config.ts +++ b/src/panels/lovelace/editor/hui-dialog-save-config.ts @@ -113,6 +113,7 @@ export class HuiSaveConfig extends LitElement implements HassDialog { )}

`} diff --git a/src/panels/lovelace/editor/hui-element-editor.ts b/src/panels/lovelace/editor/hui-element-editor.ts index 88996fa23f..a1bcb849a2 100644 --- a/src/panels/lovelace/editor/hui-element-editor.ts +++ b/src/panels/lovelace/editor/hui-element-editor.ts @@ -197,6 +197,8 @@ export abstract class HuiElementEditor extends LitElement { ul > li": { + padding: "4px 8px", + }, + + "& .cm-tooltip-autocomplete ul li[aria-selected]": { + background: "var(--primary-color)", + color: "var(--text-primary-color)", + }, + + ".cm-completionIcon": { + display: "none", + }, + + ".cm-completionDetail": { + fontFamily: "Roboto", + color: "var(--secondary-text-color)", + }, + + "li[aria-selected] .cm-completionDetail": { + color: "var(--text-primary-color)", + }, + + "& .cm-completionInfo.cm-completionInfo-right": { + left: "calc(100% + 4px)", + }, + + "& .cm-tooltip.cm-completionInfo": { + padding: "4px 8px", + marginTop: "-5px", + }, + ".cm-selectionMatch": { backgroundColor: "rgba(var(--rgb-primary-color), 0.1)", }, @@ -131,7 +173,7 @@ export const theme = EditorView.theme({ "1px solid var(--paper-input-container-color, var(--secondary-text-color))", paddingRight: "1px", }, - "&.cm-focused cm-gutters": { + "&.cm-focused .cm-gutters": { borderRight: "2px solid var(--paper-input-container-focus-color, var(--primary-color))", paddingRight: "0", diff --git a/yarn.lock b/yarn.lock index 4c4a088a29..1a35f14b62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1315,52 +1315,66 @@ __metadata: languageName: node linkType: hard -"@codemirror/commands@npm:^0.19.5": - version: 0.19.5 - resolution: "@codemirror/commands@npm:0.19.5" +"@codemirror/autocomplete@npm:^0.19.12": + version: 0.19.12 + resolution: "@codemirror/autocomplete@npm:0.19.12" + dependencies: + "@codemirror/language": ^0.19.0 + "@codemirror/state": ^0.19.4 + "@codemirror/text": ^0.19.2 + "@codemirror/tooltip": ^0.19.12 + "@codemirror/view": ^0.19.0 + "@lezer/common": ^0.15.0 + checksum: f57dfe7b911e9dd928a589d72d487c84f74281e3a899120f14e857a48f4c9af109ae1df9f7e0e5959c77aedcfeafa74a428c832d1cd8cb0adde2d3e2daf6fec8 + languageName: node + linkType: hard + +"@codemirror/commands@npm:^0.19.8": + version: 0.19.8 + resolution: "@codemirror/commands@npm:0.19.8" dependencies: "@codemirror/language": ^0.19.0 "@codemirror/matchbrackets": ^0.19.0 "@codemirror/state": ^0.19.2 - "@codemirror/text": ^0.19.0 - "@codemirror/view": ^0.19.0 + "@codemirror/text": ^0.19.6 + "@codemirror/view": ^0.19.22 "@lezer/common": ^0.15.0 - checksum: 6f826cd20620e2540ae0385e3a8bcf19aac4e2b56b9f5a527581c92926bc311f91c6cbfb33ac17006f149d56aa60bf300e45b730f446ff47c1f7b297dc7f8137 + checksum: 296f7564e71c07680da0ade9b73db67d68dd845ede22d2ba9a2a83f8b3a5429953ccd22a11d96cf2543425d416a15d1f026fb5c74a2e80522cf1779cc7cd13ff languageName: node linkType: hard -"@codemirror/gutter@npm:^0.19.4": - version: 0.19.4 - resolution: "@codemirror/gutter@npm:0.19.4" +"@codemirror/gutter@npm:^0.19.9": + version: 0.19.9 + resolution: "@codemirror/gutter@npm:0.19.9" dependencies: "@codemirror/rangeset": ^0.19.0 "@codemirror/state": ^0.19.0 - "@codemirror/view": ^0.19.0 - checksum: 5e3ed8c8aad85411651dd752ad5e634c918e47b271dcf74206181bee27acd6cc01c495a921abcdd614076e535c66387973104a7bd34c67d0655c4c4d1b951cc4 + "@codemirror/view": ^0.19.23 + checksum: 948e4bdeddfdd2f824412aa8a2cc43915444e948c310ee113faca4a988e98b6b02bea72f8849481adf82a5021b00d6a8ee2bdf0b105864de0e8aa417b41a9ed1 languageName: node linkType: hard -"@codemirror/highlight@npm:^0.19.0, @codemirror/highlight@npm:^0.19.6": - version: 0.19.6 - resolution: "@codemirror/highlight@npm:0.19.6" +"@codemirror/highlight@npm:^0.19.0, @codemirror/highlight@npm:^0.19.7": + version: 0.19.7 + resolution: "@codemirror/highlight@npm:0.19.7" dependencies: "@codemirror/language": ^0.19.0 "@codemirror/rangeset": ^0.19.0 - "@codemirror/state": ^0.19.0 + "@codemirror/state": ^0.19.3 "@codemirror/view": ^0.19.0 "@lezer/common": ^0.15.0 style-mod: ^4.0.0 - checksum: 833ffe5aea7269ab782649059a6e41c68f7be3f5a49c3824dcf9a22b1efdc33ac081239fbc79fbdbdc9063800f155f16b435e31441598a9748f31c20a6a301b4 + checksum: 8be9d2d900501b483aa108fbd58e4cc628d01b6b5150e4f0242c1e779fd20b930f69c2da8d2eb5468712e01135808f900e44500c76fb0a838538c69c9aa31a96 languageName: node linkType: hard -"@codemirror/history@npm:^0.19.0": - version: 0.19.0 - resolution: "@codemirror/history@npm:0.19.0" +"@codemirror/history@npm:^0.19.2": + version: 0.19.2 + resolution: "@codemirror/history@npm:0.19.2" dependencies: - "@codemirror/state": ^0.19.0 + "@codemirror/state": ^0.19.2 "@codemirror/view": ^0.19.0 - checksum: 768b62110065eaccffca9baf83b1049a0a7a69520caf2ddf75d52eba7fed87fa666e0e2cd34cce432723b189ca03aac4aadc6afc29075b4e1195f29d6aec298f + checksum: c9d794289ea0b493b11a24df487a8de14afb7f8aef502bfaa9a8dda48e01c172c769ae76209743e4cb2d5937df0e64bea1295f07722b571a858d7417b21cc4f8 languageName: node linkType: hard @@ -1408,12 +1422,12 @@ __metadata: languageName: node linkType: hard -"@codemirror/rangeset@npm:^0.19.0": - version: 0.19.1 - resolution: "@codemirror/rangeset@npm:0.19.1" +"@codemirror/rangeset@npm:^0.19.0, @codemirror/rangeset@npm:^0.19.5": + version: 0.19.6 + resolution: "@codemirror/rangeset@npm:0.19.6" dependencies: "@codemirror/state": ^0.19.0 - checksum: 6337b1d3b20e6a13bf67eea9d6c39f36a280598949ac138b465d32fe6d56c12d029105ce1fe530540379fc7c0437b548c0caaa18be01b34731228ec6cba228fa + checksum: f7b9ff54ac514a5c67dea1689c7f227906b46643007da76e93045ea163bd863c823a35ded4d33ba8ab1d085cb562c67134b2bf9165ffc14a9f44fbf3d85afa43 languageName: node linkType: hard @@ -1428,32 +1442,32 @@ __metadata: languageName: node linkType: hard -"@codemirror/search@npm:^0.19.2": - version: 0.19.2 - resolution: "@codemirror/search@npm:0.19.2" +"@codemirror/search@npm:^0.19.6": + version: 0.19.6 + resolution: "@codemirror/search@npm:0.19.6" dependencies: "@codemirror/panel": ^0.19.0 "@codemirror/rangeset": ^0.19.0 - "@codemirror/state": ^0.19.2 + "@codemirror/state": ^0.19.3 "@codemirror/text": ^0.19.0 - "@codemirror/view": ^0.19.0 + "@codemirror/view": ^0.19.34 crelt: ^1.0.5 - checksum: 67555a427fb7a9cb35266ff629f9cec0e3b5654f6813957b452e09504349ea2e021d8bb88bf6adef6908c95c7a3e850148fe0130f044486fb1ed527793592865 + checksum: 1313b389b1f7b0282ab988d338fcadbd9025765d2e85d7de90dec43477241b1f31b4ab118506c2ff1821086256f3c50a570baa4a1abdfd1909c79d0f34f3776b languageName: node linkType: hard -"@codemirror/state@npm:^0.19.0, @codemirror/state@npm:^0.19.2, @codemirror/state@npm:^0.19.3, @codemirror/state@npm:^0.19.4": - version: 0.19.4 - resolution: "@codemirror/state@npm:0.19.4" +"@codemirror/state@npm:^0.19.0, @codemirror/state@npm:^0.19.2, @codemirror/state@npm:^0.19.3, @codemirror/state@npm:^0.19.4, @codemirror/state@npm:^0.19.6": + version: 0.19.6 + resolution: "@codemirror/state@npm:0.19.6" dependencies: "@codemirror/text": ^0.19.0 - checksum: 2babd90a62c5f65dd4aaf7955c9c52fae4a46ad75c50a540eb95f2f31b6648873ea73c84d6c0a676d4eab335a4bce511a75fce656144758d04ca62b9a2b644c4 + checksum: 65bee46d76c0b55b10ed4818cbb77267a6c75dff3c8cc04e83056a79a1d36e79d7b8bf750d4695238ac28fe792d6329939fd725839f8314eee34146941cae344 languageName: node linkType: hard -"@codemirror/stream-parser@npm:^0.19.0, @codemirror/stream-parser@npm:^0.19.2": - version: 0.19.2 - resolution: "@codemirror/stream-parser@npm:0.19.2" +"@codemirror/stream-parser@npm:^0.19.0, @codemirror/stream-parser@npm:^0.19.5": + version: 0.19.5 + resolution: "@codemirror/stream-parser@npm:0.19.5" dependencies: "@codemirror/highlight": ^0.19.0 "@codemirror/language": ^0.19.0 @@ -1461,27 +1475,37 @@ __metadata: "@codemirror/text": ^0.19.0 "@lezer/common": ^0.15.0 "@lezer/lr": ^0.15.0 - checksum: dc51e52c2f17198009d6faac3d49679c46785ffea0a854ec0bb3e3d3143be726888cdee207e8c4bd35edee86c6fb226c06862ce6a67d40649cd952174086a2a0 + checksum: 3a1edef98def985e31f9d1be3669bebc7bb14c41d0e69bd23e57868b67c1f473b7713cba1c45638e4453faf99e84888df2d4a3ebb183ea1db9795a367fde93bc languageName: node linkType: hard -"@codemirror/text@npm:^0.19.0, @codemirror/text@npm:^0.19.4, @codemirror/text@npm:^0.19.5": - version: 0.19.5 - resolution: "@codemirror/text@npm:0.19.5" - checksum: e8ff270e705fe7f9adb3c479845569f6bfb3d8033b70cb1da530f37d9088160713b7cda2b60106fa03df5dd82cd8bcde45662450dd42854006303101a0296892 +"@codemirror/text@npm:^0.19.0, @codemirror/text@npm:^0.19.2, @codemirror/text@npm:^0.19.4, @codemirror/text@npm:^0.19.6": + version: 0.19.6 + resolution: "@codemirror/text@npm:0.19.6" + checksum: 685e46c1f0114a216081b7a070460e1b0db9c51b0a2b361e9ed90e5ea2ed89d86a7a834b76f7c63b27fd192809d9414e7a15e0d186bd15cdb5d4f85639d434f0 languageName: node linkType: hard -"@codemirror/view@npm:^0.19.0, @codemirror/view@npm:^0.19.15": - version: 0.19.15 - resolution: "@codemirror/view@npm:0.19.15" +"@codemirror/tooltip@npm:^0.19.12": + version: 0.19.13 + resolution: "@codemirror/tooltip@npm:0.19.13" dependencies: - "@codemirror/rangeset": ^0.19.0 + "@codemirror/state": ^0.19.0 + "@codemirror/view": ^0.19.0 + checksum: 00e0554510aa6545efb201ce9a7925d13122c78455429ec26c220ff6c9de480728e16ad3bb7e451ceee1c1e1968e2c7168c38f7ffb64b5e096b4a504fe135494 + languageName: node + linkType: hard + +"@codemirror/view@npm:^0.19.0, @codemirror/view@npm:^0.19.22, @codemirror/view@npm:^0.19.23, @codemirror/view@npm:^0.19.34, @codemirror/view@npm:^0.19.40": + version: 0.19.40 + resolution: "@codemirror/view@npm:0.19.40" + dependencies: + "@codemirror/rangeset": ^0.19.5 "@codemirror/state": ^0.19.3 "@codemirror/text": ^0.19.0 style-mod: ^4.0.0 w3c-keyname: ^2.2.4 - checksum: 7864fc4b5ba6d490a4b1b3416a6ef93108a70cc28a6261bbf34c484358d64d739c80c723aa3d0c8344ccb437aeea39020f7e5a6b10ae4438ed8d12df20a7e568 + checksum: bf3356a15a2bd24bdea7097483f055b5bef9ae20508639bb37d0bc33439824f093d348736d0e0da5b3e076f2fc6662437d9b5795e0668325bd6329f3f0bbd50f languageName: node linkType: hard @@ -9050,17 +9074,18 @@ fsevents@^1.2.7: "@babel/preset-env": ^7.15.6 "@babel/preset-typescript": ^7.15.0 "@braintree/sanitize-url": ^5.0.2 - "@codemirror/commands": ^0.19.5 - "@codemirror/gutter": ^0.19.4 - "@codemirror/highlight": ^0.19.6 - "@codemirror/history": ^0.19.0 + "@codemirror/autocomplete": ^0.19.12 + "@codemirror/commands": ^0.19.8 + "@codemirror/gutter": ^0.19.9 + "@codemirror/highlight": ^0.19.7 + "@codemirror/history": ^0.19.2 "@codemirror/legacy-modes": ^0.19.0 "@codemirror/rectangular-selection": ^0.19.1 - "@codemirror/search": ^0.19.2 - "@codemirror/state": ^0.19.4 - "@codemirror/stream-parser": ^0.19.2 - "@codemirror/text": ^0.19.5 - "@codemirror/view": ^0.19.15 + "@codemirror/search": ^0.19.6 + "@codemirror/state": ^0.19.6 + "@codemirror/stream-parser": ^0.19.5 + "@codemirror/text": ^0.19.6 + "@codemirror/view": ^0.19.40 "@formatjs/intl-datetimeformat": ^4.2.5 "@formatjs/intl-getcanonicallocales": ^1.8.0 "@formatjs/intl-locale": ^2.4.40 From 0046252e32a233b8046074509ed675d6dcbf5f87 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 4 Feb 2022 03:47:21 -0800 Subject: [PATCH 004/174] Add selectors to ha-form (#11534) --- build-scripts/bundle.js | 8 ++- build-scripts/webpack.js | 5 +- .../src/components/demo-black-white-row.ts | 1 + gallery/src/pages/components/ha-form.ts | 60 ++++++++++++++++++- src/components/ha-form/ha-form.ts | 25 ++++++-- src/components/ha-form/types.ts | 16 ++++- 6 files changed, 104 insertions(+), 11 deletions(-) diff --git a/build-scripts/bundle.js b/build-scripts/bundle.js index 2325d80bca..61dc06f74c 100644 --- a/build-scripts/bundle.js +++ b/build-scripts/bundle.js @@ -10,7 +10,7 @@ module.exports.ignorePackages = ({ latestBuild }) => [ ]; // Files from NPM packages that we should replace with empty file -module.exports.emptyPackages = ({ latestBuild }) => +module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) => [ // Contains all color definitions for all material color sets. // We don't use it @@ -28,6 +28,11 @@ module.exports.emptyPackages = ({ latestBuild }) => ), // This polyfill is loaded in workers to support ES5, filter it out. latestBuild && require.resolve("proxy-polyfill/src/index.js"), + // Icons in supervisor conflict with icons in HA so we don't load. + isHassioBuild && + require.resolve( + path.resolve(paths.polymer_dir, "src/components/ha-icon.ts") + ), ].filter(Boolean); module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ @@ -196,6 +201,7 @@ module.exports.config = { publicPath: publicPath(latestBuild, paths.hassio_publicPath), isProdBuild, latestBuild, + isHassioBuild: true, defineOverlay: { __SUPERVISOR__: true, }, diff --git a/build-scripts/webpack.js b/build-scripts/webpack.js index bc07a7ec9f..185ef3aa65 100644 --- a/build-scripts/webpack.js +++ b/build-scripts/webpack.js @@ -30,6 +30,7 @@ const createWebpackConfig = ({ isProdBuild, latestBuild, isStatsBuild, + isHassioBuild, dontHash, }) => { if (!dontHash) { @@ -117,7 +118,9 @@ const createWebpackConfig = ({ }, }), new webpack.NormalModuleReplacementPlugin( - new RegExp(bundle.emptyPackages({ latestBuild }).join("|")), + new RegExp( + bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|") + ), path.resolve(paths.polymer_dir, "src/util/empty.js") ), !isProdBuild && new LogStartCompilePlugin(), diff --git a/gallery/src/components/demo-black-white-row.ts b/gallery/src/components/demo-black-white-row.ts index 92a4beeffe..549f10554c 100644 --- a/gallery/src/components/demo-black-white-row.ts +++ b/gallery/src/components/demo-black-white-row.ts @@ -3,6 +3,7 @@ import { html, LitElement, css, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element"; import { fireEvent } from "../../../src/common/dom/fire_event"; +import "../../../src/components/ha-card"; @customElement("demo-black-white-row") class DemoBlackWhiteRow extends LitElement { diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index c25ff1067f..a1be3cdc0c 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -1,11 +1,17 @@ /* eslint-disable lit/no-template-arrow */ import "@material/mwc-button"; import { LitElement, TemplateResult, html } from "lit"; -import { customElement } from "lit/decorators"; +import { customElement, state } from "lit/decorators"; import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data"; import type { HaFormSchema } from "../../../../src/components/ha-form/types"; import "../../../../src/components/ha-form/ha-form"; import "../../components/demo-black-white-row"; +import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; +import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry"; +import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; +import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; +import { provideHass } from "../../../../src/fake_data/provide_hass"; +import { HomeAssistant } from "../../../../src/types"; const SCHEMAS: { title: string; @@ -14,6 +20,44 @@ const SCHEMAS: { schema: HaFormSchema[]; data?: Record; }[] = [ + { + title: "Selectors", + translations: { + addon: "Addon", + entity: "Entity", + device: "Device", + area: "Area", + target: "Target", + number: "Number", + boolean: "Boolean", + time: "Time", + action: "Action", + text: "Text", + text_multiline: "Text Multiline", + object: "Object", + select: "Select", + }, + schema: [ + { name: "addon", selector: { addon: {} } }, + { name: "entity", selector: { entity: {} } }, + { name: "device", selector: { device: {} } }, + { name: "area", selector: { area: {} } }, + { name: "target", selector: { target: {} } }, + { name: "number", selector: { number: { min: 0, max: 10 } } }, + { name: "boolean", selector: { boolean: {} } }, + { name: "time", selector: { time: {} } }, + { name: "action", selector: { action: {} } }, + { name: "text", selector: { text: { multiline: false } } }, + { name: "text_multiline", selector: { text: { multiline: true } } }, + { name: "object", selector: { object: {} } }, + { + name: "select", + selector: { + select: { options: ["Everyone Home", "Some Home", "All gone"] }, + }, + }, + ], + }, { title: "Authentication", translations: { @@ -239,12 +283,25 @@ const SCHEMAS: { @customElement("demo-components-ha-form") class DemoHaForm extends LitElement { + @state() private hass!: HomeAssistant; + private data = SCHEMAS.map( ({ schema, data }) => data || computeInitialHaFormData(schema) ); private disabled = SCHEMAS.map(() => false); + constructor() { + super(); + const hass = provideHass(this); + hass.updateTranslations(null, "en"); + hass.updateTranslations("config", "en"); + mockEntityRegistry(hass); + mockDeviceRegistry(hass); + mockAreaRegistry(hass); + mockHassioSupervisor(hass); + } + protected render(): TemplateResult { return html` ${SCHEMAS.map((info, idx) => { @@ -267,6 +324,7 @@ class DemoHaForm extends LitElement { (slot) => html` (obj ? obj[item.name] : null); @customElement("ha-form") export class HaForm extends LitElement implements HaFormElement { + @property() public hass!: HomeAssistant; + @property() public data!: HaFormDataContainer; @property() public schema!: HaFormSchema[]; @@ -62,12 +66,21 @@ export class HaForm extends LitElement implements HaFormElement { ` : ""} - ${dynamicElement(`ha-form-${item.type}`, { - schema: item, - data: getValue(this.data, item), - label: this._computeLabel(item), - disabled: this.disabled, - })} + ${"selector" in item + ? html`` + : dynamicElement(`ha-form-${item.type}`, { + schema: item, + data: getValue(this.data, item), + label: this._computeLabel(item), + disabled: this.disabled, + })} `; })}
diff --git a/src/components/ha-form/types.ts b/src/components/ha-form/types.ts index 86122bebc1..d6cfc573ef 100644 --- a/src/components/ha-form/types.ts +++ b/src/components/ha-form/types.ts @@ -1,4 +1,5 @@ import type { LitElement } from "lit"; +import { Selector } from "../../data/selector"; import type { HaDurationData } from "../ha-duration-input"; export type HaFormSchema = @@ -9,13 +10,24 @@ export type HaFormSchema = | HaFormBooleanSchema | HaFormSelectSchema | HaFormMultiSelectSchema - | HaFormTimeSchema; + | HaFormTimeSchema + | HaFormSelector; export interface HaFormBaseSchema { name: string; + // This value is applied if no data is submitted for this field default?: HaFormData; required?: boolean; - description?: { suffix?: string; suggested_value?: HaFormData }; + description?: { + suffix?: string; + // This value will be set initially when form is loaded + suggested_value?: HaFormData; + }; +} + +export interface HaFormSelector extends HaFormBaseSchema { + type?: never; + selector: Selector; } export interface HaFormConstantSchema extends HaFormBaseSchema { From 8730c122fd54736da4fbb3fdd57fa9d8c6486a83 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 4 Feb 2022 17:22:37 +0100 Subject: [PATCH 005/174] Allow translate gas total (#11547) --- .../lovelace/cards/energy/hui-energy-sources-table-card.ts | 6 +++++- src/translations/en.json | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts index 5c1b21a992..136d4fd81c 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-sources-table-card.ts @@ -538,7 +538,11 @@ export class HuiEnergySourcesTableCard ${types.gas ? html` - Gas total + + ${this.hass.localize( + "ui.panel.lovelace.cards.energy.energy_sources_table.gas_total" + )} + diff --git a/src/translations/en.json b/src/translations/en.json index e804716cf5..034810a867 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3145,6 +3145,7 @@ }, "energy_sources_table": { "grid_total": "Grid total", + "gas_total": "Gas total", "source": "Source", "energy": "Energy", "cost": "Cost", From 9b97faa5e360ae9dceeacb3bb708477ae01f9736 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sat, 5 Feb 2022 00:46:09 +0100 Subject: [PATCH 006/174] Migrate combobox to mwc (#11546) --- package.json | 9 +- .../device/ha-area-devices-picker.ts | 2 +- src/components/device/ha-device-picker.ts | 66 +---- .../entity/ha-entity-attribute-picker.ts | 2 +- src/components/entity/ha-entity-picker.ts | 2 +- src/components/entity/ha-statistic-picker.ts | 90 ++---- src/components/entity/ha-statistics-picker.ts | 14 +- src/components/ha-addon-picker.ts | 40 +-- src/components/ha-area-picker.ts | 2 +- src/components/ha-combo-box.ts | 153 +++++++--- src/components/ha-date-input.ts | 2 +- src/components/ha-icon-picker.ts | 2 +- .../ha-selector/ha-selector-addon.ts | 8 +- src/components/ha-service-control.ts | 3 + src/components/ha-service-picker.ts | 40 +-- .../dialogs/dialog-energy-battery-settings.ts | 3 + .../dialogs/dialog-energy-device-settings.ts | 11 +- .../dialogs/dialog-energy-gas-settings.ts | 3 + .../dialog-energy-grid-flow-settings.ts | 3 + .../dialogs/dialog-energy-solar-settings.ts | 3 + .../hui-statistics-graph-card-editor.ts | 11 +- yarn.lock | 274 +++++++++--------- 22 files changed, 354 insertions(+), 389 deletions(-) diff --git a/package.json b/package.json index 9eeac473fb..7b3bf4a013 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@material/mwc-formfield": "0.25.3", "@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch", "@material/mwc-linear-progress": "0.25.3", - "@material/mwc-list": "0.25.3", + "@material/mwc-list": "^0.25.3", "@material/mwc-menu": "0.25.3", "@material/mwc-radio": "0.25.3", "@material/mwc-ripple": "0.25.3", @@ -88,8 +88,9 @@ "@polymer/paper-tooltip": "^3.0.1", "@polymer/polymer": "3.4.1", "@thomasloven/round-slider": "0.5.4", - "@vaadin/vaadin-combo-box": "^21.0.2", - "@vaadin/vaadin-date-picker": "^21.0.2", + "@vaadin/combo-box": "^22.0.4", + "@vaadin/date-picker": "^22.0.4", + "@vaadin/vaadin-themable-mixin": "^22.0.4", "@vibrant/color": "^3.2.1-alpha.1", "@vibrant/core": "^3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", @@ -112,7 +113,7 @@ "leaflet": "^1.7.1", "leaflet-draw": "^1.0.4", "lit": "^2.1.2", - "lit-vaadin-helpers": "^0.2.1", + "lit-vaadin-helpers": "^0.3.0", "marked": "^3.0.2", "memoize-one": "^5.2.1", "node-vibrant": "3.2.1-alpha.1", diff --git a/src/components/device/ha-area-devices-picker.ts b/src/components/device/ha-area-devices-picker.ts index f863bacd45..67e01a131d 100644 --- a/src/components/device/ha-area-devices-picker.ts +++ b/src/components/device/ha-area-devices-picker.ts @@ -4,7 +4,7 @@ import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-listbox/paper-listbox"; -import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; +import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index 3b13c963e1..1161505eeb 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -1,18 +1,9 @@ -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; +import "@material/mwc-list/mwc-list-item"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; -import { customElement, property, state, query } from "lit/decorators"; -import memoizeOne from "memoize-one"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; -import { mdiCheck } from "@mdi/js"; +import { customElement, property, query, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../common/dom/fire_event"; import { computeDomain } from "../../common/entity/compute_domain"; import { stringCompare } from "../../common/string/compare"; @@ -46,36 +37,12 @@ export type HaDevicePickerDeviceFilterFunc = ( device: DeviceRegistryEntry ) => boolean; -// eslint-disable-next-line lit/prefer-static-styles -const rowRenderer: ComboBoxLitRenderer = (item) => html` - - - - ${item.name} - ${item.area} - - `; +const rowRenderer: ComboBoxLitRenderer = (item) => html` + ${item.name} + ${item.area} +`; @customElement("ha-device-picker") export class HaDevicePicker extends SubscribeMixin(LitElement) { @@ -335,19 +302,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { fireEvent(this, "change"); }, 0); } - - static get styles(): CSSResultGroup { - return css` - paper-input > ha-icon-button { - --mdc-icon-button-size: 24px; - padding: 2px; - color: var(--secondary-text-color); - } - [hidden] { - display: none; - } - `; - } } declare global { diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts index d199c4f7b6..51b17fd53d 100644 --- a/src/components/entity/ha-entity-attribute-picker.ts +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -1,7 +1,7 @@ import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; -import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; +import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import { HassEntity } from "home-assistant-js-websocket"; import { css, diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index 016684e6c2..a455bb988a 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -2,7 +2,7 @@ import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item-body"; -import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; +import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import { HassEntity } from "home-assistant-js-websocket"; import { css, diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index 080a78a252..511b46353a 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -1,17 +1,8 @@ -import { mdiCheck } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item-body"; -import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; import { HassEntity } from "home-assistant-js-websocket"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -76,54 +67,24 @@ export class HaStatisticPicker extends LitElement { id: string; name: string; state?: HassEntity; - // eslint-disable-next-line lit/prefer-static-styles - }> = (item) => html` - - - ${item.state - ? html`` - : ""} - - ${item.name} - ${item.id === "" || item.id === "__missing" - ? html`${this.hass.localize( - "ui.components.statistic-picker.learn_more" - )}` - : item.id} - - `; + }> = (item) => html` + ${item.state + ? html`` + : ""} + ${item.name} + ${item.id === "" || item.id === "__missing" + ? html`${this.hass.localize( + "ui.components.statistic-picker.learn_more" + )}` + : item.id} + `; private _getStatistics = memoizeOne( ( @@ -293,19 +254,6 @@ export class HaStatisticPicker extends LitElement { fireEvent(this, "change"); }, 0); } - - static get styles(): CSSResultGroup { - return css` - paper-input > ha-icon-button { - --mdc-icon-button-size: 24px; - padding: 2px; - color: var(--secondary-text-color); - } - [hidden] { - display: none; - } - `; - } } declare global { diff --git a/src/components/entity/ha-statistics-picker.ts b/src/components/entity/ha-statistics-picker.ts index 46e10060d0..08242a5263 100644 --- a/src/components/entity/ha-statistics-picker.ts +++ b/src/components/entity/ha-statistics-picker.ts @@ -1,4 +1,4 @@ -import { html, LitElement, TemplateResult } from "lit"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import type { PolymerChangedEvent } from "../../polymer-types"; @@ -103,6 +103,18 @@ class HaStatisticsPicker extends LitElement { this._updateStatistics([...currentEntities, toAdd]); } + + static get styles(): CSSResultGroup { + return css` + :host { + width: 200px; + display: block; + } + ha-statistic-picker { + width: 100%; + } + `; + } } declare global { diff --git a/src/components/ha-addon-picker.ts b/src/components/ha-addon-picker.ts index c40d874c13..a28f188de9 100644 --- a/src/components/ha-addon-picker.ts +++ b/src/components/ha-addon-picker.ts @@ -1,4 +1,3 @@ -import { mdiCheck } from "@mdi/js"; import { html, LitElement, TemplateResult } from "lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; import { customElement, property, query, state } from "lit/decorators"; @@ -12,39 +11,12 @@ import { PolymerChangedEvent } from "../polymer-types"; import { HomeAssistant } from "../types"; import { HaComboBox } from "./ha-combo-box"; -// eslint-disable-next-line lit/prefer-static-styles -const rowRenderer: ComboBoxLitRenderer = (item) => html` - - - - ${item.name} - ${item.slug} - - `; +const rowRenderer: ComboBoxLitRenderer = ( + item +) => html` + ${item.name} + ${item.slug} +`; @customElement("ha-addon-picker") class HaAddonPicker extends LitElement { diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index ed658188df..b6504f9dc1 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -3,7 +3,7 @@ import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-listbox/paper-listbox"; -import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; +import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index afcd9c8911..dd2b5d0e27 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -1,25 +1,59 @@ +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-textfield/mwc-textfield"; import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; -import "@polymer/paper-listbox/paper-listbox"; -import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; +import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; +import type { ComboBoxLight } from "@vaadin/combo-box/vaadin-combo-box-light"; +import { registerStyles } from "@vaadin/vaadin-themable-mixin/register-styles"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; -import { customElement, property, query, state } from "lit/decorators"; +import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import { PolymerChangedEvent } from "../polymer-types"; import { HomeAssistant } from "../types"; import "./ha-icon-button"; -// eslint-disable-next-line lit/prefer-static-styles -const defaultRowRenderer: ComboBoxLitRenderer = (item) => html` - ${item}`; + :host([focused]:not([disabled])) { + background-color: rgba(0, 0, 0, 0.12); + } + :host([selected]:not([disabled])) { + background-color: transparent; + color: var(--mdc-theme-primary); + --mdc-ripple-color: var(--mdc-theme-primary); + --mdc-theme-text-primary-on-background: var(--mdc-theme-primary); + } + :host([selected]:not([disabled])):before { + background-color: var(--mdc-theme-primary); + opacity: 0.12; + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + :host([selected][focused]:not([disabled])):before { + opacity: 0.24; + } + :host(:hover:not([disabled])) { + background-color: transparent; + } + [part="content"] { + width: 100%; + } + [part="checkmark"] { + display: none; + } + ` +); + +const defaultRowRenderer: ComboBoxLitRenderer = (item) => + html`${item}`; @customElement("ha-combo-box") export class HaComboBox extends LitElement { @@ -46,24 +80,25 @@ export class HaComboBox extends LitElement { @property({ type: Boolean }) public disabled?: boolean; - @state() private _opened?: boolean; + @property({ type: Boolean, reflect: true, attribute: "opened" }) + private _opened?: boolean; - @query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement; + @query("vaadin-combo-box-light", true) private _comboBox!: ComboBoxLight; public open() { this.updateComplete.then(() => { - (this._comboBox as any)?.open(); + this._comboBox?.open(); }); } public focus() { this.updateComplete.then(() => { - this.shadowRoot?.querySelector("paper-input")?.focus(); + this._comboBox?.inputElement?.focus(); }); } public get selectedItem() { - return (this._comboBox as any).selectedItem; + return this._comboBox.selectedItem; } protected render(): TemplateResult { @@ -72,7 +107,7 @@ export class HaComboBox extends LitElement { .itemValuePath=${this.itemValuePath} .itemIdPath=${this.itemIdPath} .itemLabelPath=${this.itemLabelPath} - .value=${this.value} + .value=${this.value || ""} .items=${this.items} .filteredItems=${this.filteredItems} .allowCustomValue=${this.allowCustomValue} @@ -81,8 +116,9 @@ export class HaComboBox extends LitElement { @opened-changed=${this._openedChanged} @filter-changed=${this._filterChanged} @value-changed=${this._valueChanged} + attr-for-value="value" > -
`} > - ${this.value - ? html` - - ` - : ""} - - - + + ${this.value + ? html`` + : ""} + `; } @@ -119,8 +152,20 @@ export class HaComboBox extends LitElement { fireEvent(this, "value-changed", { value: undefined }); } + private _toggleOpen(ev: Event) { + if (this._opened) { + this._comboBox?.close(); + ev.stopPropagation(); + } else { + this._comboBox?.inputElement.focus(); + } + } + private _openedChanged(ev: PolymerChangedEvent) { - this._opened = ev.detail.value; + // delay this so we can handle click event before setting _opened + setTimeout(() => { + this._opened = ev.detail.value; + }, 0); // @ts-ignore fireEvent(this, ev.type, ev.detail); } @@ -141,11 +186,39 @@ export class HaComboBox extends LitElement { static get styles(): CSSResultGroup { return css` - paper-input > ha-icon-button { + :host { + display: block; + width: 100%; + margin-top: 4px; + } + vaadin-combo-box-light { + position: relative; + } + mwc-textfield { + width: 100%; + } + mwc-textfield > ha-icon-button { --mdc-icon-button-size: 24px; padding: 2px; color: var(--secondary-text-color); } + ha-svg-icon { + color: var(--input-dropdown-icon-color); + position: absolute; + cursor: pointer; + } + .toggle-button { + right: 12px; + top: -10px; + } + :host([opened]) .toggle-button { + color: var(--primary-color); + } + .clear-button { + --mdc-icon-size: 20px; + top: -7px; + right: 36px; + } `; } } diff --git a/src/components/ha-date-input.ts b/src/components/ha-date-input.ts index 852e4aa4c7..c83f5742c3 100644 --- a/src/components/ha-date-input.ts +++ b/src/components/ha-date-input.ts @@ -1,6 +1,6 @@ import { mdiCalendar } from "@mdi/js"; import "@polymer/paper-input/paper-input"; -import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker-light"; +import "@vaadin/date-picker/theme/material/vaadin-date-picker-light"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index 93da7a5d87..972a2f7a61 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -2,7 +2,7 @@ import { mdiCheck, mdiMenuDown, mdiMenuUp } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item-body"; -import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; +import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import { css, html, LitElement, TemplateResult } from "lit"; import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; import { customElement, property, query, state } from "lit/decorators"; diff --git a/src/components/ha-selector/ha-selector-addon.ts b/src/components/ha-selector/ha-selector-addon.ts index 30214933f1..47bb9c045b 100644 --- a/src/components/ha-selector/ha-selector-addon.ts +++ b/src/components/ha-selector/ha-selector-addon.ts @@ -1,4 +1,4 @@ -import { html, LitElement } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { AddonSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; @@ -22,6 +22,12 @@ export class HaAddonSelector extends LitElement { allow-custom-entity >`; } + + static styles = css` + ha-addon-picker { + width: 100%; + } + `; } declare global { diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index 3469982b77..5b9a8a6506 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -480,6 +480,9 @@ export class HaServiceControl extends LitElement { display: block; margin: var(--service-control-padding, 0 16px); } + ha-service-picker { + padding-top: 16px; + } ha-yaml-editor { padding: 16px 0; } diff --git a/src/components/ha-service-picker.ts b/src/components/ha-service-picker.ts index 196d2902c2..dab0015447 100644 --- a/src/components/ha-service-picker.ts +++ b/src/components/ha-service-picker.ts @@ -1,4 +1,3 @@ -import { mdiCheck } from "@mdi/js"; import { html, LitElement } from "lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; import { property, state } from "lit/decorators"; @@ -11,39 +10,12 @@ import "./ha-combo-box"; const rowRenderer: ComboBoxLitRenderer<{ service: string; name: string }> = ( item - // eslint-disable-next-line lit/prefer-static-styles -) => html` - - - - ${item.name} - ${item.name === item.service ? "" : item.service} - - `; +) => html` + ${item.name} + ${item.name === item.service ? "" : item.service} +`; class HaServicePicker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; diff --git a/src/panels/config/energy/dialogs/dialog-energy-battery-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-battery-settings.ts index 2f7477327b..394fcfcd65 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-battery-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-battery-settings.ts @@ -127,6 +127,9 @@ export class DialogEnergyBatterySettings ha-dialog { --mdc-dialog-max-width: 430px; } + ha-statistic-picker { + width: 100%; + } `, ]; } diff --git a/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts index e0ac123065..6cc3e0bc52 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-device-settings.ts @@ -1,5 +1,5 @@ import { mdiDevices } from "@mdi/js"; -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-dialog"; @@ -109,7 +109,14 @@ export class DialogEnergyDeviceSettings } static get styles(): CSSResultGroup { - return haStyleDialog; + return [ + haStyleDialog, + css` + ha-statistic-picker { + width: 100%; + } + `, + ]; } } diff --git a/src/panels/config/energy/dialogs/dialog-energy-gas-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-gas-settings.ts index fa5c9062f7..fea22bc68d 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-gas-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-gas-settings.ts @@ -294,6 +294,9 @@ export class DialogEnergyGasSettings ha-formfield { display: block; } + ha-statistic-picker { + width: 100%; + } .price-options { display: block; padding-left: 52px; diff --git a/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts index 0bb1ebf34e..4ae094ab52 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts @@ -301,6 +301,9 @@ export class DialogEnergyGridFlowSettings ha-formfield { display: block; } + ha-statistic-picker { + width: 100%; + } .price-options { display: block; padding-left: 52px; diff --git a/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts index 25f6073e85..cdd24d513f 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts @@ -248,6 +248,9 @@ export class DialogEnergySolarSettings ha-formfield { display: block; } + ha-statistic-picker { + width: 100%; + } .forecast-options { padding-left: 32px; } diff --git a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts index 163ea01bd3..89411e668c 100644 --- a/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-statistics-graph-card-editor.ts @@ -1,5 +1,5 @@ import "@polymer/paper-input/paper-input"; -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { array, @@ -284,7 +284,14 @@ export class HuiStatisticsGraphCardEditor } static get styles(): CSSResultGroup { - return configElementStyle; + return [ + configElementStyle, + css` + ha-statistics-picker { + width: 100%; + } + `, + ]; } } diff --git a/yarn.lock b/yarn.lock index 1a35f14b62..ab7da34845 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2491,7 +2491,7 @@ __metadata: languageName: node linkType: hard -"@material/mwc-list@npm:0.25.3, @material/mwc-list@npm:^0.25.3": +"@material/mwc-list@npm:^0.25.3": version: 0.25.3 resolution: "@material/mwc-list@npm:0.25.3" dependencies: @@ -3059,6 +3059,13 @@ __metadata: languageName: node linkType: hard +"@open-wc/dedupe-mixin@npm:^1.3.0": + version: 1.3.0 + resolution: "@open-wc/dedupe-mixin@npm:1.3.0" + checksum: 6c957d705a0ee96c46282b95267eab0365265e90109b2da95b51c5384b730c4823fb426f99a0cc10465826250bd6466199af80ad7257a4aacdd9bcb997b4cab3 + languageName: node + linkType: hard + "@open-wc/dev-server-hmr@npm:^0.0.2": version: 0.0.2 resolution: "@open-wc/dev-server-hmr@npm:0.0.2" @@ -3103,7 +3110,7 @@ __metadata: languageName: node linkType: hard -"@polymer/iron-a11y-keys-behavior@npm:^3.0.0, @polymer/iron-a11y-keys-behavior@npm:^3.0.0-pre.26": +"@polymer/iron-a11y-keys-behavior@npm:^3.0.0-pre.26": version: 3.0.1 resolution: "@polymer/iron-a11y-keys-behavior@npm:3.0.1" dependencies: @@ -3216,18 +3223,6 @@ __metadata: languageName: node linkType: hard -"@polymer/iron-list@npm:^3.0.0": - version: 3.0.2 - resolution: "@polymer/iron-list@npm:3.0.2" - dependencies: - "@polymer/iron-a11y-keys-behavior": ^3.0.0-pre.26 - "@polymer/iron-resizable-behavior": ^3.0.0-pre.26 - "@polymer/iron-scroll-target-behavior": ^3.0.0-pre.26 - "@polymer/polymer": ^3.0.0 - checksum: 6f3f9db546120776b4efedc3986c4399a4a28d3951cc6a1d5920e1d9b9eab26c6a92e2194dc4bdbb4dd148c727ac8aa20c7323a576a46600e6cd15bb0ad5ccce - languageName: node - linkType: hard - "@polymer/iron-media-query@npm:^3.0.0, @polymer/iron-media-query@npm:^3.0.0-pre.26": version: 3.0.1 resolution: "@polymer/iron-media-query@npm:3.0.1" @@ -4235,67 +4230,120 @@ __metadata: languageName: node linkType: hard -"@vaadin/vaadin-button@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-button@npm:21.0.2" +"@vaadin/button@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/button@npm:22.0.4" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-control-state-mixin": ^21.0.2 - "@vaadin/vaadin-element-mixin": ^21.0.2 - "@vaadin/vaadin-lumo-styles": ^21.0.2 - "@vaadin/vaadin-material-styles": ^21.0.2 - "@vaadin/vaadin-themable-mixin": ^21.0.2 - checksum: d95a682edc68a2cce6a08272e9cb0340517484ec39b3ced368390b6a171fb8f6ae19308d793af6947dc2525bf70f62c766ad7a50ed82eceff0699171b3b58768 + "@vaadin/component-base": ^22.0.4 + "@vaadin/vaadin-lumo-styles": ^22.0.4 + "@vaadin/vaadin-material-styles": ^22.0.4 + "@vaadin/vaadin-themable-mixin": ^22.0.4 + checksum: 70f8eeddd7c8d19eb859f2dd5f848e9b6b83f1f4bc8cb3d1fdc613f0637df9c04f0f91e49d23c4d390ce4ffa8a301ea77c364c29de278e8c32e5e7ccbcaf8f67 languageName: node linkType: hard -"@vaadin/vaadin-combo-box@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-combo-box@npm:21.0.2" +"@vaadin/combo-box@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/combo-box@npm:22.0.4" dependencies: - "@polymer/iron-a11y-announcer": ^3.0.0 - "@polymer/iron-list": ^3.0.0 + "@open-wc/dedupe-mixin": ^1.3.0 "@polymer/iron-resizable-behavior": ^3.0.0 "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-control-state-mixin": ^21.0.2 - "@vaadin/vaadin-element-mixin": ^21.0.2 - "@vaadin/vaadin-item": ^21.0.2 - "@vaadin/vaadin-lumo-styles": ^21.0.2 - "@vaadin/vaadin-material-styles": ^21.0.2 - "@vaadin/vaadin-overlay": ^21.0.2 - "@vaadin/vaadin-text-field": ^21.0.2 - "@vaadin/vaadin-themable-mixin": ^21.0.2 - checksum: 3c12f3c01d5c9b04efa011c57cee7ac3ec7d7f47d2d59a9ce71537f3a4f12177bcf4e049e03403c1f6693ef9a4717095dd7eb8435a83f4376bcf255a56a1aaa7 + "@vaadin/component-base": ^22.0.4 + "@vaadin/field-base": ^22.0.4 + "@vaadin/input-container": ^22.0.4 + "@vaadin/item": ^22.0.4 + "@vaadin/vaadin-lumo-styles": ^22.0.4 + "@vaadin/vaadin-material-styles": ^22.0.4 + "@vaadin/vaadin-overlay": ^22.0.4 + "@vaadin/vaadin-themable-mixin": ^22.0.4 + checksum: a3cde710d1187bba8e9e7eeb7f6397e6e1158befa3412c3aa684b3b45a1425cdee28d408d5e4dc4f586e1f6881db8850fc51c38f4658c4eb65c51a26a9623c56 languageName: node linkType: hard -"@vaadin/vaadin-control-state-mixin@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-control-state-mixin@npm:21.0.2" +"@vaadin/component-base@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/component-base@npm:22.0.4" dependencies: + "@open-wc/dedupe-mixin": ^1.3.0 "@polymer/polymer": ^3.0.0 - checksum: a6ed433dc8750afdff410c9046637f12ebbcd79f46c8a9d294ab4d3c18c1dd1c9b465b83c63671a804e10319123375f1e3ee55dfd5dd2b60f44f8593af7e2ee6 + "@vaadin/vaadin-development-mode-detector": ^2.0.0 + "@vaadin/vaadin-usage-statistics": ^2.1.0 + lit: ^2.0.0 + checksum: d18e7cebdd2928e33641ee035927540239e8a65d23521aca2e35d9992a44060d951e877af09088de0ffa88daa11f86a02b86a087cb29b51d9ada574009159509 languageName: node linkType: hard -"@vaadin/vaadin-date-picker@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-date-picker@npm:21.0.2" +"@vaadin/date-picker@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/date-picker@npm:22.0.4" dependencies: + "@open-wc/dedupe-mixin": ^1.3.0 "@polymer/iron-a11y-announcer": ^3.0.0 - "@polymer/iron-a11y-keys-behavior": ^3.0.0 "@polymer/iron-media-query": ^3.0.0 - "@polymer/iron-resizable-behavior": ^3.0.0 "@polymer/polymer": ^3.2.0 - "@vaadin/vaadin-button": ^21.0.2 - "@vaadin/vaadin-control-state-mixin": ^21.0.2 - "@vaadin/vaadin-element-mixin": ^21.0.2 - "@vaadin/vaadin-lumo-styles": ^21.0.2 - "@vaadin/vaadin-material-styles": ^21.0.2 - "@vaadin/vaadin-overlay": ^21.0.2 - "@vaadin/vaadin-text-field": ^21.0.2 - "@vaadin/vaadin-themable-mixin": ^21.0.2 - checksum: 0b1ee406540d59d3cc8bf37b47575adc9c2bc50e15f3e3adb1d95486d58c8b377b153f4f19c76258b27ea814188c024312b704961cf609120a7649943666552e + "@vaadin/button": ^22.0.4 + "@vaadin/component-base": ^22.0.4 + "@vaadin/field-base": ^22.0.4 + "@vaadin/input-container": ^22.0.4 + "@vaadin/vaadin-lumo-styles": ^22.0.4 + "@vaadin/vaadin-material-styles": ^22.0.4 + "@vaadin/vaadin-overlay": ^22.0.4 + "@vaadin/vaadin-themable-mixin": ^22.0.4 + checksum: bfc0bc3a9bec920d7a0b0d2b3968f6c5ca17cea752f37b7d707e8912b8c35e2780b1a78d4cdca1094519cb70554f95ee5ab69f212c677482935a7a4fc3b42a43 + languageName: node + linkType: hard + +"@vaadin/field-base@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/field-base@npm:22.0.4" + dependencies: + "@open-wc/dedupe-mixin": ^1.3.0 + "@polymer/polymer": ^3.0.0 + "@vaadin/component-base": ^22.0.4 + lit: ^2.0.0 + checksum: 4ca54ea3efd1bad2cea6ada97484e24f77f7ebb2ab5da7de5b9b7949d624b049d357c7f6f69206f1f10023b1711dc7b8bde9f6dfad774578efec00e1907c4763 + languageName: node + linkType: hard + +"@vaadin/icon@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/icon@npm:22.0.4" + dependencies: + "@polymer/polymer": ^3.0.0 + "@vaadin/component-base": ^22.0.4 + "@vaadin/vaadin-lumo-styles": ^22.0.4 + "@vaadin/vaadin-themable-mixin": ^22.0.4 + lit: ^2.0.0 + checksum: 65e5195a8eb6f8ce24471c3f52ffdd7edc49249e39922e945eb8f02fc2b277d8d527a1538241b9660ca568a87663912b1be9c82fa8a841f286e713630d52f3db + languageName: node + linkType: hard + +"@vaadin/input-container@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/input-container@npm:22.0.4" + dependencies: + "@polymer/polymer": ^3.0.0 + "@vaadin/component-base": ^22.0.4 + "@vaadin/vaadin-lumo-styles": ^22.0.4 + "@vaadin/vaadin-material-styles": ^22.0.4 + "@vaadin/vaadin-themable-mixin": ^22.0.4 + checksum: 718cb7d8f715427d9085feee8a0df987440511059c5bbfcaa80d63ecd989a693f8f50af9da0f483555396aece21b75eff280921eda7cf0b6358e67518e53ba85 + languageName: node + linkType: hard + +"@vaadin/item@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/item@npm:22.0.4" + dependencies: + "@open-wc/dedupe-mixin": ^1.3.0 + "@polymer/polymer": ^3.0.0 + "@vaadin/component-base": ^22.0.4 + "@vaadin/vaadin-lumo-styles": ^22.0.4 + "@vaadin/vaadin-material-styles": ^22.0.4 + "@vaadin/vaadin-themable-mixin": ^22.0.4 + checksum: ef8c253668852a129656e083149b3866327dfae8671e30bb1bf78b39f969bd1db74892f090c7632b64fd91c25334f0542ad9dc6848486609de4350da5e5ea44c languageName: node linkType: hard @@ -4306,100 +4354,49 @@ __metadata: languageName: node linkType: hard -"@vaadin/vaadin-element-mixin@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-element-mixin@npm:21.0.2" - dependencies: - "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-development-mode-detector": ^2.0.0 - "@vaadin/vaadin-usage-statistics": ^2.1.0 - checksum: 8d18ff9f1e430ff4ed7461fe948b171f78bf43ba2f263c7f7cc4f4161e0486c7ad8a8d57c8cbb9443883ee6fedfae84bbbb562e2e3bb2161d33625b2adde8ebe - languageName: node - linkType: hard - -"@vaadin/vaadin-icon@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-icon@npm:21.0.2" - dependencies: - "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-element-mixin": ^21.0.2 - "@vaadin/vaadin-lumo-styles": ^21.0.2 - "@vaadin/vaadin-themable-mixin": ^21.0.2 - lit: ^2.0.0 - checksum: 993cd6d8bb6f52cc7ba0e1958824c77a3d56f3a3537ded361412d971174e3bb080780c039487390f7807faebe19f17b56aa1ffc63ad7672cf575d02664ee5880 - languageName: node - linkType: hard - -"@vaadin/vaadin-item@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-item@npm:21.0.2" - dependencies: - "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-element-mixin": ^21.0.2 - "@vaadin/vaadin-lumo-styles": ^21.0.2 - "@vaadin/vaadin-material-styles": ^21.0.2 - "@vaadin/vaadin-themable-mixin": ^21.0.2 - checksum: b4ff73c0e4f6823f3eb06e3c79df4654e0485740d5928d5bbe95add0b70f4e7cf8abe03cf160c7fe9db2cd474689d0ba5ae3278e85de32bb31d8ab8f90b74e09 - languageName: node - linkType: hard - -"@vaadin/vaadin-lumo-styles@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-lumo-styles@npm:21.0.2" +"@vaadin/vaadin-lumo-styles@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/vaadin-lumo-styles@npm:22.0.4" dependencies: "@polymer/iron-icon": ^3.0.0 "@polymer/iron-iconset-svg": ^3.0.0 "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-icon": ^21.0.2 - "@vaadin/vaadin-themable-mixin": ^21.0.2 - checksum: 99058edf60d4989a9eeef4651ff4d95fd901dafd91314e9a7ce02656466f1cb304f0225da4259dada2eaf257ff83bffed8c24730444e45a1b21a303a1b9a2a00 + "@vaadin/icon": ^22.0.4 + "@vaadin/vaadin-themable-mixin": ^22.0.4 + checksum: 15e9becd675e0d12024fbdfaecedd03f55841f685932ff5cf8a2143641f895309f21b84f378f1fca3af538c3aba26dad81421f05a28f31ad7bb5550ede8a3aea languageName: node linkType: hard -"@vaadin/vaadin-material-styles@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-material-styles@npm:21.0.2" +"@vaadin/vaadin-material-styles@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/vaadin-material-styles@npm:22.0.4" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-themable-mixin": ^21.0.2 - checksum: 0d5c5aa82cdaa09bb153333bdbd272ad3d137de6046a0a5e69245cb01394f7fb7a2a98b26a6e1b36d38736d32e3134c05cb00b35e5b39b410b31fc78aa55ff0e + "@vaadin/vaadin-themable-mixin": ^22.0.4 + checksum: 0e341e03eab9641cc317a9cbf6d57e7d026539d7bc77159226625aad63a379f31cd70da5e02f10a3d79e9fe0cba1b058935beb2c5d98f146bfb9dd45d8634ee6 languageName: node linkType: hard -"@vaadin/vaadin-overlay@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-overlay@npm:21.0.2" +"@vaadin/vaadin-overlay@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/vaadin-overlay@npm:22.0.4" dependencies: "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-element-mixin": ^21.0.2 - "@vaadin/vaadin-lumo-styles": ^21.0.2 - "@vaadin/vaadin-material-styles": ^21.0.2 - "@vaadin/vaadin-themable-mixin": ^21.0.2 - checksum: a1d9556de019912bfc135bbf6e0b6062ac479047ed26d1db28a8255011826c94fa8df5c115d210d94e59c0f58bf444775422c92f8ff004bd2659720d4d33c8b3 + "@vaadin/component-base": ^22.0.4 + "@vaadin/vaadin-lumo-styles": ^22.0.4 + "@vaadin/vaadin-material-styles": ^22.0.4 + "@vaadin/vaadin-themable-mixin": ^22.0.4 + checksum: 1b012ff0beac7879da498cf50ee0974d4c3e5637ebea7f7834b9bfc45b9f02e80c61267794c0f3f4e1d4853aa2f35113e1d94bc187d9fe072f37a03eb99f3ab6 languageName: node linkType: hard -"@vaadin/vaadin-text-field@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-text-field@npm:21.0.2" +"@vaadin/vaadin-themable-mixin@npm:^22.0.4": + version: 22.0.4 + resolution: "@vaadin/vaadin-themable-mixin@npm:22.0.4" dependencies: - "@polymer/polymer": ^3.0.0 - "@vaadin/vaadin-control-state-mixin": ^21.0.2 - "@vaadin/vaadin-element-mixin": ^21.0.2 - "@vaadin/vaadin-lumo-styles": ^21.0.2 - "@vaadin/vaadin-material-styles": ^21.0.2 - "@vaadin/vaadin-themable-mixin": ^21.0.2 - checksum: 8b7e08298e8d04ec9f7fede5c1f92dc5503a41004f421c8dae7e4fe69715225e74328caa232ba50d8164f8d9bf635806056f58af9f897c4b9913a95594d0b117 - languageName: node - linkType: hard - -"@vaadin/vaadin-themable-mixin@npm:^21.0.2": - version: 21.0.2 - resolution: "@vaadin/vaadin-themable-mixin@npm:21.0.2" - dependencies: - "@polymer/polymer": ^3.0.0 + "@open-wc/dedupe-mixin": ^1.3.0 lit: ^2.0.0 - checksum: 1a3db436b2e08f5c68343380da03ac80e2d42cd108b152065c43e12acff3fc57ed595109b1e5a31efa092be7fc7f8f654342c29b8345e985dc89899154edfa8b + checksum: 0b2dce09626c92b85ff2d2ad48c8130239bf41fd95147a5fd4490cab4767f074ba9d1008d732d9e90b9219cb1ac0509f4a5fc8637eb5847d6d33e14b20552186 languageName: node linkType: hard @@ -9111,7 +9108,7 @@ fsevents@^1.2.7: "@material/mwc-formfield": 0.25.3 "@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch" "@material/mwc-linear-progress": 0.25.3 - "@material/mwc-list": 0.25.3 + "@material/mwc-list": ^0.25.3 "@material/mwc-menu": 0.25.3 "@material/mwc-radio": 0.25.3 "@material/mwc-ripple": 0.25.3 @@ -9160,8 +9157,9 @@ fsevents@^1.2.7: "@types/webspeechapi": ^0.0.29 "@typescript-eslint/eslint-plugin": ^4.32.0 "@typescript-eslint/parser": ^4.32.0 - "@vaadin/vaadin-combo-box": ^21.0.2 - "@vaadin/vaadin-date-picker": ^21.0.2 + "@vaadin/combo-box": ^22.0.4 + "@vaadin/date-picker": ^22.0.4 + "@vaadin/vaadin-themable-mixin": ^22.0.4 "@vibrant/color": ^3.2.1-alpha.1 "@vibrant/core": ^3.2.1-alpha.1 "@vibrant/quantizer-mmcq": ^3.2.1-alpha.1 @@ -9214,7 +9212,7 @@ fsevents@^1.2.7: lint-staged: ^11.1.2 lit: ^2.1.2 lit-analyzer: ^1.2.1 - lit-vaadin-helpers: ^0.2.1 + lit-vaadin-helpers: ^0.3.0 lodash.template: ^4.5.0 magic-string: ^0.25.7 map-stream: ^0.0.7 @@ -10820,12 +10818,12 @@ fsevents@^1.2.7: languageName: node linkType: hard -"lit-vaadin-helpers@npm:^0.2.1": - version: 0.2.1 - resolution: "lit-vaadin-helpers@npm:0.2.1" +"lit-vaadin-helpers@npm:^0.3.0": + version: 0.3.0 + resolution: "lit-vaadin-helpers@npm:0.3.0" dependencies: lit: ^2.0.0 - checksum: 140a0e7fbf9cdd8ffe780ffb0ff44227553e4764ae73408a93ed3ef0a6d996d7646e3dbf05a1624ae0ad9ad152d2f43adb4a78cf61cd4f4b6f86cbe4e2252bb4 + checksum: c96df23272442b3f6c38273721306eb650ea27876fade53ccfbb0158eef838865b1727b9657d35a80f55d94b1418106be63794adb064b79f35dfe3014fb435ff languageName: node linkType: hard From 45e6ec1ee22beb5409d1bc79829ce6433108eadc Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sat, 5 Feb 2022 01:19:36 +0100 Subject: [PATCH 007/174] New date picker (#11555) --- package.json | 2 +- src/components/ha-date-input.ts | 157 ++++++------------ src/components/ha-dialog-date-picker.ts | 106 ++++++++++++ .../controls/more-info-input_datetime.ts | 1 + .../hui-input-datetime-entity-row.ts | 1 + yarn.lock | 124 +++++++++----- 6 files changed, 245 insertions(+), 146 deletions(-) create mode 100644 src/components/ha-dialog-date-picker.ts diff --git a/package.json b/package.json index 7b3bf4a013..d8d7300978 100644 --- a/package.json +++ b/package.json @@ -89,13 +89,13 @@ "@polymer/polymer": "3.4.1", "@thomasloven/round-slider": "0.5.4", "@vaadin/combo-box": "^22.0.4", - "@vaadin/date-picker": "^22.0.4", "@vaadin/vaadin-themable-mixin": "^22.0.4", "@vibrant/color": "^3.2.1-alpha.1", "@vibrant/core": "^3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", "@vue/web-component-wrapper": "^1.2.0", "@webcomponents/webcomponentsjs": "^2.2.10", + "app-datepicker": "^5.0.1", "chart.js": "^3.3.2", "comlink": "^4.3.1", "core-js": "^3.15.2", diff --git a/src/components/ha-date-input.ts b/src/components/ha-date-input.ts index c83f5742c3..08c2590126 100644 --- a/src/components/ha-date-input.ts +++ b/src/components/ha-date-input.ts @@ -1,135 +1,76 @@ import { mdiCalendar } from "@mdi/js"; import "@polymer/paper-input/paper-input"; -import "@vaadin/date-picker/theme/material/vaadin-date-picker-light"; -import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; -import { customElement, property, query } from "lit/decorators"; +import { css, CSSResultGroup, html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { formatDateNumeric } from "../common/datetime/format_date"; import { fireEvent } from "../common/dom/fire_event"; +import { HomeAssistant } from "../types"; import "./ha-svg-icon"; -const i18n = { - monthNames: [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - weekdays: [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ], - weekdaysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - firstDayOfWeek: 0, - week: "Week", - calendar: "Calendar", - clear: "Clear", - today: "Today", - cancel: "Cancel", - formatTitle: (monthName, fullYear) => monthName + " " + fullYear, - formatDate: (d: { day: number; month: number; year: number }) => - [ - ("0000" + String(d.year)).slice(-4), - ("0" + String(d.month + 1)).slice(-2), - ("0" + String(d.day)).slice(-2), - ].join("-"), - parseDate: (text: string) => { - const parts = text.split("-"); - const today = new Date(); - let date; - let month = today.getMonth(); - let year = today.getFullYear(); - if (parts.length === 3) { - year = parseInt(parts[0]); - if (parts[0].length < 3 && year >= 0) { - year += year < 50 ? 2000 : 1900; - } - month = parseInt(parts[1]) - 1; - date = parseInt(parts[2]); - } else if (parts.length === 2) { - month = parseInt(parts[0]) - 1; - date = parseInt(parts[1]); - } else if (parts.length === 1) { - date = parseInt(parts[0]); - } +const loadDatePickerDialog = () => import("./ha-dialog-date-picker"); - if (date !== undefined) { - return { day: date, month, year }; - } - return undefined; - }, +export interface datePickerDialogParams { + value?: string; + min?: string; + max?: string; + locale?: string; + onChange: (value: string) => void; +} + +const showDatePickerDialog = ( + element: HTMLElement, + dialogParams: datePickerDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "ha-dialog-date-picker", + dialogImport: loadDatePickerDialog, + dialogParams, + }); }; @customElement("ha-date-input") export class HaDateInput extends LitElement { + @property({ attribute: false }) public locale!: HomeAssistant["locale"]; + @property() public value?: string; @property({ type: Boolean }) public disabled = false; @property() public label?: string; - @query("vaadin-date-picker-light", true) private _datePicker; - - private _inited = false; - - updated(changedProps: PropertyValues) { - if (changedProps.has("value")) { - this._datePicker.value = this.value; - this._inited = true; - } - } - render() { - return html` - - - - `; + + `; } - private _valueChanged(ev: CustomEvent) { - if ( - !this.value || - (this._inited && !this._compareStringDates(ev.detail.value, this.value)) - ) { - this.value = ev.detail.value; + private _openDialog() { + if (this.disabled) { + return; + } + showDatePickerDialog(this, { + min: "1970-01-01", + value: this.value, + onChange: (value) => this._valueChanged(value), + locale: this.locale.language, + }); + } + + private _valueChanged(value: string) { + if (this.value !== value) { + this.value = value; fireEvent(this, "change"); - fireEvent(this, "value-changed", { value: ev.detail.value }); + fireEvent(this, "value-changed", { value }); } } - private _compareStringDates(a: string, b: string): boolean { - const aParts = a.split("-"); - const bParts = b.split("-"); - let i = 0; - for (const aPart of aParts) { - if (Number(aPart) !== Number(bParts[i])) { - return false; - } - i++; - } - return true; - } - static get styles(): CSSResultGroup { return css` paper-input { diff --git a/src/components/ha-dialog-date-picker.ts b/src/components/ha-dialog-date-picker.ts new file mode 100644 index 0000000000..2cb229db7b --- /dev/null +++ b/src/components/ha-dialog-date-picker.ts @@ -0,0 +1,106 @@ +import "@material/mwc-button/mwc-button"; +import "app-datepicker"; +import { css, html, LitElement } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../common/dom/fire_event"; +import { haStyleDialog } from "../resources/styles"; +import { datePickerDialogParams } from "./ha-date-input"; +import "./ha-dialog"; + +@customElement("ha-dialog-date-picker") +export class HaDialogDatePicker extends LitElement { + @property() public value?: string; + + @property({ type: Boolean }) public disabled = false; + + @property() public label?: string; + + @state() private _params?: datePickerDialogParams; + + @state() private _value?: string; + + public showDialog(params: datePickerDialogParams): void { + this._params = params; + this._value = params.value; + } + + public closeDialog() { + this._params = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + render() { + if (!this._params) { + return html``; + } + return html` + + today + + cancel + + ok + `; + } + + private _valueChanged(ev: CustomEvent) { + this._value = ev.detail.value; + } + + private _setToday() { + this._value = new Date().toISOString().split("T")[0]; + } + + private _setValue() { + this._params?.onChange(this._value!); + this.closeDialog(); + } + + static styles = [ + haStyleDialog, + css` + ha-dialog { + --dialog-content-padding: 0; + --justify-action-buttons: space-between; + } + app-datepicker { + --app-datepicker-accent-color: var(--primary-color); + --app-datepicker-bg-color: transparent; + --app-datepicker-color: var(--primary-text-color); + --app-datepicker-disabled-day-color: var(--disabled-text-color); + --app-datepicker-focused-day-color: var(--text-primary-color); + --app-datepicker-focused-year-bg-color: var(--primary-color); + --app-datepicker-selector-color: var(--secondary-text-color); + --app-datepicker-separator-color: var(--divider-color); + --app-datepicker-weekday-color: var(--secondary-text-color); + } + app-datepicker::part(calendar-day):focus { + outline: none; + } + @media all and (min-width: 450px) { + ha-dialog { + --mdc-dialog-min-width: 300px; + } + } + @media all and (max-width: 450px), all and (max-height: 500px) { + app-datepicker { + width: 100%; + } + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-dialog-date-picker": HaDialogDatePicker; + } +} diff --git a/src/dialogs/more-info/controls/more-info-input_datetime.ts b/src/dialogs/more-info/controls/more-info-input_datetime.ts index af1e9aa6a1..4d1be5c70b 100644 --- a/src/dialogs/more-info/controls/more-info-input_datetime.ts +++ b/src/dialogs/more-info/controls/more-info-input_datetime.ts @@ -23,6 +23,7 @@ class MoreInfoInputDatetime extends LitElement { this.stateObj.attributes.has_date ? html` Date: Sat, 5 Feb 2022 04:16:01 +0100 Subject: [PATCH 008/174] Link via device on device page (#11554) Co-authored-by: Zack Barett --- .../device-detail/ha-device-info-card.ts | 63 ++++++++++--------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/src/panels/config/devices/device-detail/ha-device-info-card.ts b/src/panels/config/devices/device-detail/ha-device-info-card.ts index 19176d0fbe..ce8e553b06 100644 --- a/src/panels/config/devices/device-detail/ha-device-info-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-info-card.ts @@ -5,6 +5,7 @@ import { computeDeviceName, DeviceRegistryEntry, } from "../../../../data/device_registry"; +import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { loadDeviceRegistryDetailDialog } from "../device-registry-detail/show-dialog-device-registry-detail"; @@ -55,10 +56,13 @@ export class HaDeviceCard extends LitElement { "ui.panel.config.integrations.config_entry.via" )} ${this._computeDeviceName( - this.devices, - this.device.via_device_id - )}${this._computeDeviceName( + this.devices, + this.device.via_device_id + )}
` @@ -112,29 +116,32 @@ export class HaDeviceCard extends LitElement { } static get styles(): CSSResultGroup { - return css` - :host { - display: block; - } - ha-card { - flex: 1 0 100%; - min-width: 0; - } - .device { - width: 30%; - } - .area { - color: var(--primary-text-color); - } - .extra-info { - margin-top: 8px; - word-wrap: break-word; - } - .manuf, - .model { - color: var(--secondary-text-color); - word-wrap: break-word; - } - `; + return [ + haStyle, + css` + :host { + display: block; + } + ha-card { + flex: 1 0 100%; + min-width: 0; + } + .device { + width: 30%; + } + .area { + color: var(--primary-text-color); + } + .extra-info { + margin-top: 8px; + word-wrap: break-word; + } + .manuf, + .model { + color: var(--secondary-text-color); + word-wrap: break-word; + } + `, + ]; } } From 9be5a15c77d752ac6626becdb72acee6a659031d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 5 Feb 2022 10:44:48 -0600 Subject: [PATCH 009/174] Add integration_discovery to discovery sources (#11564) --- src/data/config_flow.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/config_flow.ts b/src/data/config_flow.ts index 92352fafc9..39019393c5 100644 --- a/src/data/config_flow.ts +++ b/src/data/config_flow.ts @@ -13,6 +13,7 @@ export const DISCOVERY_SOURCES = [ "ssdp", "zeroconf", "discovery", + "integration_discovery", "mqtt", "hassio", ]; From 04668ad809174dc0286baa2b31e7433dcdc5e891 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sun, 6 Feb 2022 23:26:42 +0100 Subject: [PATCH 010/174] Remember filter between navigation (#11565) --- src/components/data-table/ha-data-table.ts | 3 +++ src/layouts/hass-tabs-subpage-data-table.ts | 3 +++ src/panels/config/devices/ha-config-devices-dashboard.ts | 3 ++- src/panels/config/entities/ha-config-entities.ts | 3 ++- src/panels/config/integrations/ha-config-integrations.ts | 3 ++- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index b2d669105b..af06549c80 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -571,6 +571,9 @@ export class HaDataTable extends LitElement { } private _handleSearchChange(ev: CustomEvent): void { + if (this.filter) { + return; + } this._debounceSearch(ev.detail.value); } diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index b92f35e2e9..c291eab335 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -234,6 +234,9 @@ export class HaTabsSubpageDataTable extends LitElement { } private _handleSearchChange(ev: CustomEvent) { + if (this.filter === ev.detail.value) { + return; + } this.filter = ev.detail.value; fireEvent(this, "search-changed", { value: this.filter }); } diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index 8b541f711a..76b3c5d87f 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -67,7 +67,7 @@ export class HaConfigDeviceDashboard extends LitElement { @state() private _showDisabled = false; - @state() private _filter = ""; + @state() private _filter: string = history.state?.filter || ""; @state() private _numHiddenDevices = 0; @@ -490,6 +490,7 @@ export class HaConfigDeviceDashboard extends LitElement { private _handleSearchChange(ev: CustomEvent) { this._filter = ev.detail.value; + history.replaceState({ filter: this._filter }, ""); } private _clearFilter() { diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index fb91132d8c..7c03fd89f3 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -109,7 +109,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { @state() private _showReadOnly = true; - @state() private _filter = ""; + @state() private _filter: string = history.state?.filter || ""; @state() private _numHiddenEntities = 0; @@ -711,6 +711,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { private _handleSearchChange(ev: CustomEvent) { this._filter = ev.detail.value; + history.replaceState({ filter: this._filter }, ""); } private _handleSelectionChanged( diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 686eeaaa60..7aa599d8a2 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -137,7 +137,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { window.location.hash.substring(1) ); - @state() private _filter?: string; + @state() private _filter: string = history.state?.filter || ""; @state() private _diagnosticHandlers?: Record; @@ -613,6 +613,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { private _handleSearchChange(ev: CustomEvent) { this._filter = ev.detail.value; + history.replaceState({ filter: this._filter }, ""); } private async _highlightEntry() { From 4092f7f75d7dae2f7e0f35d702f98bd8eba2c084 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sun, 6 Feb 2022 23:29:28 +0100 Subject: [PATCH 011/174] Convert selectors to MWC (#11543) --- gallery/src/pages/components/ha-selector.ts | 9 +++- package.json | 1 + src/components/ha-form/ha-form-integer.ts | 2 +- .../ha-selector/ha-selector-number.ts | 21 ++++----- .../ha-selector/ha-selector-select.ts | 47 ++++++++----------- .../ha-selector/ha-selector-target.ts | 5 -- .../ha-selector/ha-selector-text.ts | 38 +++++++++------ yarn.lock | 15 +++++- 8 files changed, 77 insertions(+), 61 deletions(-) diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index b25790a299..e2cd0425ec 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -48,10 +48,15 @@ const SCHEMAS: { boolean: { name: "Boolean", selector: { boolean: {} } }, time: { name: "Time", selector: { time: {} } }, action: { name: "Action", selector: { action: {} } }, - text: { name: "Text", selector: { text: { multiline: false } } }, + text: { + name: "Text", + selector: { text: { multiline: false } }, + }, text_multiline: { name: "Text multiline", - selector: { text: { multiline: true } }, + selector: { + text: { multiline: true }, + }, }, object: { name: "Object", selector: { object: {} } }, select: { diff --git a/package.json b/package.json index d8d7300978..6b536e78ff 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "@material/mwc-switch": "0.25.3", "@material/mwc-tab": "0.25.3", "@material/mwc-tab-bar": "0.25.3", + "@material/mwc-textarea": "^0.25.3", "@material/mwc-textfield": "0.25.3", "@material/mwc-top-app-bar-fixed": "^0.25.3", "@material/top-app-bar": "14.0.0-canary.261f2db59.0", diff --git a/src/components/ha-form/ha-form-integer.ts b/src/components/ha-form/ha-form-integer.ts index d4d52fec23..3a684a3d40 100644 --- a/src/components/ha-form/ha-form-integer.ts +++ b/src/components/ha-form/ha-form-integer.ts @@ -25,7 +25,7 @@ export class HaFormInteger extends LitElement implements HaFormElement { @property({ type: Boolean }) public disabled = false; - @query("paper-input ha-slider") private _input?: HTMLElement; + @query("mwc-textfield ha-slider") private _input?: HTMLElement; private _lastValue?: HaFormIntegerData; diff --git a/src/components/ha-selector/ha-selector-number.ts b/src/components/ha-selector/ha-selector-number.ts index 244f97e2ce..6b2652b5b9 100644 --- a/src/components/ha-selector/ha-selector-number.ts +++ b/src/components/ha-selector/ha-selector-number.ts @@ -1,4 +1,3 @@ -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -6,6 +5,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import { NumberSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; import "../ha-slider"; +import "@material/mwc-textfield/mwc-textfield"; @customElement("ha-selector-number") export class HaNumberSelector extends LitElement { @@ -36,27 +36,23 @@ export class HaNumberSelector extends LitElement { > ` : ""} - - ${this.selector.number.unit_of_measurement - ? html`
- ${this.selector.number.unit_of_measurement} -
` - : ""} -
`; + `; } private get _value() { @@ -94,6 +90,9 @@ export class HaNumberSelector extends LitElement { ha-slider { flex: 1; } + mwc-textfield { + width: 70px; + } .single { flex: 1; } diff --git a/src/components/ha-selector/ha-selector-select.ts b/src/components/ha-selector/ha-selector-select.ts index 450a6d7e6a..eaaca5a6ea 100644 --- a/src/components/ha-selector/ha-selector-select.ts +++ b/src/components/ha-selector/ha-selector-select.ts @@ -1,9 +1,11 @@ import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; +import { stopPropagation } from "../../common/dom/stop_propagation"; import { SelectSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; -import "../ha-paper-dropdown-menu"; +import "@material/mwc-select/mwc-select"; +import "@material/mwc-list/mwc-list-item"; @customElement("ha-selector-select") export class HaSelectSelector extends LitElement { @@ -18,46 +20,37 @@ export class HaSelectSelector extends LitElement { @property({ type: Boolean }) public disabled = false; protected render() { - return html` - - ${this.selector.select.options.map( - (item: string) => html` - ${item} - ` - )} - - `; + ${this.selector.select.options.map( + (item: string) => html` + ${item} + ` + )} + `; } private _valueChanged(ev) { - if (this.disabled || !ev.detail.value) { + ev.stopPropagation(); + if (this.disabled || !ev.target.value) { return; } fireEvent(this, "value-changed", { - value: ev.detail.value.itemValue, + value: ev.target.value, }); } static get styles(): CSSResultGroup { return css` - ha-paper-dropdown-menu { + mwc-select { width: 100%; - min-width: 200px; - display: block; - } - paper-listbox { - min-width: 200px; - } - paper-item { - cursor: pointer; } `; } diff --git a/src/components/ha-selector/ha-selector-target.ts b/src/components/ha-selector/ha-selector-target.ts index 6ed0251665..4d81be67df 100644 --- a/src/components/ha-selector/ha-selector-target.ts +++ b/src/components/ha-selector/ha-selector-target.ts @@ -1,8 +1,3 @@ -import "@material/mwc-list/mwc-list"; -import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-tab-bar/mwc-tab-bar"; -import "@material/mwc-tab/mwc-tab"; -import "@polymer/paper-input/paper-input"; import { HassEntity, HassServiceTarget, diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts index 25cddf58d8..f0a093de79 100644 --- a/src/components/ha-selector/ha-selector-text.ts +++ b/src/components/ha-selector/ha-selector-text.ts @@ -1,10 +1,10 @@ -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-input/paper-textarea"; -import { html, LitElement } from "lit"; +import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { StringSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; +import "@material/mwc-textfield/mwc-textfield"; +import "@material/mwc-textarea/mwc-textarea"; @customElement("ha-selector-text") export class HaTextSelector extends LitElement { @@ -22,25 +22,26 @@ export class HaTextSelector extends LitElement { protected render() { if (this.selector.text?.multiline) { - return html``; + required + >`; } - return html``; + @input=${this._handleChange} + .label=${this.label || ""} + required + >`; } private _handleChange(ev) { @@ -50,6 +51,15 @@ export class HaTextSelector extends LitElement { } fireEvent(this, "value-changed", { value }); } + + static get styles(): CSSResultGroup { + return css` + mwc-textfield, + mwc-textarea { + width: 100%; + } + `; + } } declare global { diff --git a/yarn.lock b/yarn.lock index 307a488dc3..8d3c0ef776 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2665,7 +2665,19 @@ __metadata: languageName: node linkType: hard -"@material/mwc-textfield@npm:0.25.3": +"@material/mwc-textarea@npm:^0.25.3": + version: 0.25.3 + resolution: "@material/mwc-textarea@npm:0.25.3" + dependencies: + "@material/mwc-base": ^0.25.3 + "@material/mwc-textfield": ^0.25.3 + lit: ^2.0.0 + tslib: ^2.0.1 + checksum: 918e28b72f7c687c481cd9ee00f652cd6c212d37cd281197cb02c87f04153792c5a60a86276e82f44684e7c7b4947e0e2e5fceaa08fc075a030ea769c1501d8e + languageName: node + linkType: hard + +"@material/mwc-textfield@npm:0.25.3, @material/mwc-textfield@npm:^0.25.3": version: 0.25.3 resolution: "@material/mwc-textfield@npm:0.25.3" dependencies: @@ -9133,6 +9145,7 @@ fsevents@^1.2.7: "@material/mwc-switch": 0.25.3 "@material/mwc-tab": 0.25.3 "@material/mwc-tab-bar": 0.25.3 + "@material/mwc-textarea": ^0.25.3 "@material/mwc-textfield": 0.25.3 "@material/mwc-top-app-bar-fixed": ^0.25.3 "@material/top-app-bar": 14.0.0-canary.261f2db59.0 From d05f807b9df66d69a8a8b3829509459b9b26161f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Sun, 6 Feb 2022 23:31:46 +0100 Subject: [PATCH 012/174] Covert area picker to combo-box (#11562) --- src/components/ha-area-picker.ts | 154 +++++++++++-------------------- src/components/ha-combo-box.ts | 4 +- src/translations/en.json | 1 + 3 files changed, 57 insertions(+), 102 deletions(-) diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index b6504f9dc1..bfb39ac3da 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -1,19 +1,6 @@ -import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; -import "@polymer/paper-listbox/paper-listbox"; -import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; -import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import memoizeOne from "memoize-one"; @@ -41,38 +28,18 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin"; import { PolymerChangedEvent } from "../polymer-types"; import { HomeAssistant } from "../types"; import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker"; +import type { HaComboBox } from "./ha-combo-box"; +import "./ha-combo-box"; import "./ha-icon-button"; import "./ha-svg-icon"; const rowRenderer: ComboBoxLitRenderer = ( item - // eslint-disable-next-line lit/prefer-static-styles -) => html` - - - ${item.name} - `; +) => html` + ${item.name} +`; @customElement("ha-area-picker") export class HaAreaPicker extends SubscribeMixin(LitElement) { @@ -125,7 +92,9 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { @state() private _opened?: boolean; - @query("vaadin-combo-box-light", true) public comboBox!: HTMLElement; + @query("ha-combo-box", true) public comboBox!: HaComboBox; + + private _filter?: string; private _init = false; @@ -145,13 +114,13 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { public open() { this.updateComplete.then(() => { - (this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open(); + this.comboBox?.open(); }); } public focus() { this.updateComplete.then(() => { - this.shadowRoot?.querySelector("paper-input")?.focus(); + this.comboBox?.focus(); }); } @@ -339,52 +308,25 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { return html``; } return html` - - - ${this.value - ? html` - - ` - : ""} - - - - + `; } @@ -392,9 +334,29 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { this._areas?.find((area) => area.area_id === areaId) ); - private _clearValue(ev: Event) { - ev.stopPropagation(); - this._setValue(""); + private _filterChanged(ev: CustomEvent): void { + this._filter = ev.detail.value; + if (!this._filter) { + this.comboBox.filteredItems = this.comboBox.items; + return; + } + // @ts-ignore + if (!this.noAdd && this.comboBox._comboBox.filteredItems?.length === 0) { + this.comboBox.filteredItems = [ + { + area_id: "add_new_suggestion", + name: this.hass.localize( + "ui.components.area-picker.add_new_sugestion", + { name: this._filter } + ), + picture: null, + }, + ]; + } else { + this.comboBox.filteredItems = this.comboBox.items?.filter((item) => + item.name.toLowerCase().includes(this._filter!.toLowerCase()) + ); + } } private get _value() { @@ -406,9 +368,10 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { } private _areaChanged(ev: PolymerChangedEvent) { + ev.stopPropagation(); const newValue = ev.detail.value; - if (newValue !== "add_new") { + if (!["add_new_suggestion", "add_new"].includes(newValue)) { if (newValue !== this._value) { this._setValue(newValue); } @@ -425,6 +388,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { inputLabel: this.hass.localize( "ui.components.area-picker.add_dialog.name" ), + defaultValue: + newValue === "add_new_suggestion" ? this._filter : undefined, confirm: async (name) => { if (!name) { return; @@ -445,6 +410,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { this.entityFilter, this.noAdd ); + await this.updateComplete; + await this.comboBox.updateComplete; this._setValue(area.area_id); } catch (err: any) { showAlertDialog(this, { @@ -465,19 +432,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { fireEvent(this, "change"); }, 0); } - - static get styles(): CSSResultGroup { - return css` - paper-input > ha-icon-button { - --mdc-icon-button-size: 24px; - padding: 2px; - color: var(--secondary-text-color); - } - [hidden] { - display: none; - } - `; - } } declare global { diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index dd2b5d0e27..c4717b4d33 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -63,9 +63,9 @@ export class HaComboBox extends LitElement { @property() public value?: string; - @property() public items?: []; + @property() public items?: any[]; - @property() public filteredItems?: []; + @property() public filteredItems?: any[]; @property({ attribute: "allow-custom-value", type: Boolean }) public allowCustomValue?: boolean; diff --git a/src/translations/en.json b/src/translations/en.json index 034810a867..42eb7db2f6 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -401,6 +401,7 @@ "clear": "Clear", "show_areas": "Show areas", "area": "Area", + "add_new_sugestion": "Add new area ''{name}''", "add_new": "Add new area…", "no_areas": "You don't have any areas", "no_match": "No matching areas found", From 76af6e48cd01de4b0258bac686ad0903f55f5be7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Feb 2022 10:59:11 +0100 Subject: [PATCH 013/174] Convert entity picker to ha-combo (#11560) * Convert entity picker to ha-combo * Update ha-entity-picker.ts * Handle empty better * Clear value when no device/area/entity --- src/components/device/ha-device-picker.ts | 13 +- src/components/entity/ha-entity-picker.ts | 174 +++++++------------ src/components/entity/ha-statistic-picker.ts | 2 +- src/components/ha-area-picker.ts | 10 +- src/components/ha-selector/ha-selector.ts | 6 +- src/translations/en.json | 1 + 6 files changed, 77 insertions(+), 129 deletions(-) diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index 1161505eeb..d1a7d2b55a 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -38,7 +38,7 @@ export type HaDevicePickerDeviceFilterFunc = ( ) => boolean; const rowRenderer: ComboBoxLitRenderer = (item) => html` ${item.name} ${item.area} @@ -105,7 +105,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { if (!devices.length) { return [ { - id: "", + id: "no_devices", area: "", name: this.hass.localize("ui.components.device-picker.no_devices"), }, @@ -201,7 +201,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { if (!outputDevices.length) { return [ { - id: "", + id: "no_devices", area: "", name: this.hass.localize("ui.components.device-picker.no_match"), }, @@ -270,7 +270,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { .renderer=${rowRenderer} .disabled=${this.disabled} item-value-path="id" - item-id-path="id" item-label-path="name" @opened-changed=${this._openedChanged} @value-changed=${this._deviceChanged} @@ -284,7 +283,11 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) { private _deviceChanged(ev: PolymerChangedEvent) { ev.stopPropagation(); - const newValue = ev.detail.value; + let newValue = ev.detail.value; + + if (newValue === "no_devices") { + newValue = ""; + } if (newValue !== this._value) { this._setValue(newValue); diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index a455bb988a..d24f6fbac4 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -1,25 +1,16 @@ -import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-icon-item"; -import "@polymer/paper-item/paper-item-body"; -import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; +import "@material/mwc-list/mwc-list-item"; import { HassEntity } from "home-assistant-js-websocket"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; -import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; -import { customElement, property, query } from "lit/decorators"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; +import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../common/dom/fire_event"; import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; import { PolymerChangedEvent } from "../../polymer-types"; import { HomeAssistant } from "../../types"; +import "../ha-combo-box"; +import type { HaComboBox } from "../ha-combo-box"; import "../ha-icon-button"; import "../ha-svg-icon"; import "./state-badge"; @@ -27,35 +18,15 @@ import "./state-badge"; export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean; // eslint-disable-next-line lit/prefer-static-styles -const rowRenderer: ComboBoxLitRenderer = (item) => html` - - - - - ${computeStateName(item)} - ${item.entity_id} - - `; - +const rowRenderer: ComboBoxLitRenderer = + (item) => + html` + ${item.state + ? html`` + : ""} + ${item.friendly_name} + ${item.entity_id} + `; @customElement("ha-entity-picker") export class HaEntityPicker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -107,19 +78,19 @@ export class HaEntityPicker extends LitElement { @property({ type: Boolean }) public hideClearIcon = false; - @property({ type: Boolean }) private _opened = false; + @state() private _opened = false; - @query("vaadin-combo-box-light", true) private comboBox!: HTMLElement; + @query("ha-combo-box", true) public comboBox!: HaComboBox; public open() { this.updateComplete.then(() => { - (this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open(); + this.comboBox?.open(); }); } public focus() { this.updateComplete.then(() => { - this.shadowRoot?.querySelector("paper-input")?.focus(); + this.comboBox?.focus(); }); } @@ -144,6 +115,27 @@ export class HaEntityPicker extends LitElement { } let entityIds = Object.keys(hass.states); + if (!entityIds.length) { + return [ + { + entity_id: "", + state: "", + last_changed: "", + last_updated: "", + context: { id: "", user_id: null }, + friendly_name: this.hass!.localize( + "ui.components.entity.entity-picker.no_entities" + ), + attributes: { + friendly_name: this.hass!.localize( + "ui.components.entity.entity-picker.no_entities" + ), + icon: "mdi:magnify", + }, + }, + ]; + } + if (includeDomains) { entityIds = entityIds.filter((eid) => includeDomains.includes(computeDomain(eid)) @@ -156,7 +148,10 @@ export class HaEntityPicker extends LitElement { ); } - states = entityIds.sort().map((key) => hass!.states[key]); + states = entityIds.sort().map((key) => ({ + ...hass!.states[key], + friendly_name: computeStateName(hass!.states[key]) || key, + })); if (includeDeviceClasses) { states = states.filter( @@ -196,6 +191,9 @@ export class HaEntityPicker extends LitElement { last_changed: "", last_updated: "", context: { id: "", user_id: null }, + friendly_name: this.hass!.localize( + "ui.components.entity.entity-picker.no_match" + ), attributes: { friendly_name: this.hass!.localize( "ui.components.entity.entity-picker.no_match" @@ -241,64 +239,25 @@ export class HaEntityPicker extends LitElement { protected render(): TemplateResult { return html` - - -
- ${this.value && !this.hideClearIcon - ? html` - - ` - : ""} - - -
-
-
+ `; } - private _clearValue(ev: Event) { - ev.stopPropagation(); - this._setValue(""); - } - private get _value() { return this.value || ""; } @@ -308,6 +267,7 @@ export class HaEntityPicker extends LitElement { } private _valueChanged(ev: PolymerChangedEvent) { + ev.stopPropagation(); const newValue = ev.detail.value; if (newValue !== this._value) { this._setValue(newValue); @@ -317,9 +277,9 @@ export class HaEntityPicker extends LitElement { private _filterChanged(ev: CustomEvent): void { const filterString = ev.detail.value.toLowerCase(); (this.comboBox as any).filteredItems = this._states.filter( - (state) => - state.entity_id.toLowerCase().includes(filterString) || - computeStateName(state).toLowerCase().includes(filterString) + (entityState) => + entityState.entity_id.toLowerCase().includes(filterString) || + computeStateName(entityState).toLowerCase().includes(filterString) ); } @@ -330,22 +290,6 @@ export class HaEntityPicker extends LitElement { fireEvent(this, "change"); }, 0); } - - static get styles(): CSSResultGroup { - return css` - .suffix { - display: flex; - } - ha-icon-button { - --mdc-icon-button-size: 24px; - padding: 0px 2px; - color: var(--secondary-text-color); - } - [hidden] { - display: none; - } - `; - } } declare global { diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index 511b46353a..6fc0f34a1f 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -67,7 +67,7 @@ export class HaStatisticPicker extends LitElement { id: string; name: string; state?: HassEntity; - }> = (item) => html` + }> = (item) => html` ${item.state ? html`` : ""} diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index bfb39ac3da..a671893737 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -139,7 +139,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { if (!areas.length) { return [ { - area_id: "", + area_id: "no_areas", name: this.hass.localize("ui.components.area-picker.no_areas"), picture: null, }, @@ -263,7 +263,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { if (!outputAreas.length) { outputAreas = [ { - area_id: "", + area_id: "no_areas", name: this.hass.localize("ui.components.area-picker.no_match"), picture: null, }, @@ -369,7 +369,11 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) { private _areaChanged(ev: PolymerChangedEvent) { ev.stopPropagation(); - const newValue = ev.detail.value; + let newValue = ev.detail.value; + + if (newValue === "no_areas") { + newValue = ""; + } if (!["add_new_suggestion", "add_new"].includes(newValue)) { if (newValue !== this._value) { diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index d426ec0b49..e3eda4a3df 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -31,11 +31,7 @@ export class HaSelector extends LitElement { @property({ type: Boolean }) public disabled = false; public focus() { - const input = this.shadowRoot!.getElementById("selector"); - if (!input) { - return; - } - (input as HTMLElement).focus(); + this.shadowRoot!.getElementById("selector")?.focus(); } private get _type() { diff --git a/src/translations/en.json b/src/translations/en.json index 42eb7db2f6..ed8ff943bf 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -358,6 +358,7 @@ "entity": "Entity", "edit": "Edit", "clear": "Clear", + "no_entities": "You don't have any entities", "no_match": "No matching entities found", "show_entities": "Show entities" }, From 354ea88984487a39de4c1c476c1cd4e24804bd8a Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Feb 2022 15:08:54 +0100 Subject: [PATCH 014/174] Update links on info page (#11590) --- src/panels/config/info/ha-config-info.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index 74b10c529f..a2459d1565 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -89,24 +89,20 @@ class HaConfigInfo extends LitElement { Python 3, + Lit, + ${this.hass.localize("ui.panel.config.info.icons_by")} Polymer, ${this.hass.localize("ui.panel.config.info.icons_by")} - Google ${this.hass.localize("ui.common.and")} MaterialDesignIcons.comMaterial Design Icons.

From ca8d31c6bbcff2ad26dd587ea5686e8f9002435f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Feb 2022 17:04:37 +0100 Subject: [PATCH 015/174] Migrate (input) select entities to mwc (#11591) --- .../hui-input-select-entity-row.ts | 70 ++++----------- .../entity-rows/hui-select-entity-row.ts | 90 ++++++------------- src/state-summary/state-card-input_select.ts | 62 +++++-------- src/state-summary/state-card-select.ts | 79 ++++++---------- 4 files changed, 94 insertions(+), 207 deletions(-) diff --git a/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts index 55f384a6c9..30fd573dbe 100644 --- a/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts @@ -1,5 +1,5 @@ -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, @@ -11,8 +11,6 @@ import { import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeStateName } from "../../../common/entity/compute_state_name"; -import "../../../components/entity/state-badge"; -import "../../../components/ha-paper-dropdown-menu"; import { UNAVAILABLE_STATES } from "../../../data/entity"; import { forwardHaptic } from "../../../data/haptics"; import { @@ -67,75 +65,43 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow { .config=${this._config} hideName > - - - ${stateObj.attributes.options - ? stateObj.attributes.options.map( - (option) => html` ${option} ` - ) - : ""} - - + ${stateObj.attributes.options + ? stateObj.attributes.options.map( + (option) => + html`${option}` + ) + : ""} + `; } - protected updated(changedProps: PropertyValues) { - super.updated(changedProps); - - if (!this.hass || !this._config) { - return; - } - - const stateObj = this.hass.states[this._config.entity] as - | InputSelectEntity - | undefined; - - if (!stateObj) { - return; - } - - // Update selected after rendering the items or else it won't work in Firefox - if (stateObj.attributes.options) { - this.shadowRoot!.querySelector("paper-listbox")!.selected = - stateObj.attributes.options.indexOf(stateObj.state); - } - } - static get styles(): CSSResultGroup { return css` hui-generic-entity-row { display: flex; align-items: center; } - ha-paper-dropdown-menu { - margin-left: 16px; - flex: 1; - } - paper-item { - cursor: pointer; - min-width: 200px; - } - .pointer { - cursor: pointer; - } - state-badge:focus { - outline: none; - background: var(--divider-color); - border-radius: 100%; + mwc-select { + width: 100%; } `; } private _selectedChanged(ev): void { const stateObj = this.hass!.states[this._config!.entity]; - const option = ev.target.selectedItem.innerText.trim(); + const option = ev.target.value; if (option === stateObj.state) { return; } diff --git a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts index 5aa6f103fd..f6bc9ec867 100644 --- a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts @@ -1,5 +1,5 @@ -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, @@ -11,8 +11,6 @@ import { import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeStateName } from "../../../common/entity/compute_state_name"; -import "../../../components/entity/state-badge"; -import "../../../components/ha-paper-dropdown-menu"; import { UNAVAILABLE } from "../../../data/entity"; import { forwardHaptic } from "../../../data/haptics"; import { SelectEntity, setSelectOption } from "../../../data/select"; @@ -64,86 +62,52 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow { .config=${this._config} hideName > - - - ${stateObj.attributes.options - ? stateObj.attributes.options.map( - (option) => - html` - ${(stateObj.attributes.device_class && - this.hass!.localize( - `component.select.state.${stateObj.attributes.device_class}.${option}` - )) || + ${stateObj.attributes.options + ? stateObj.attributes.options.map( + (option) => + html` + ${(stateObj.attributes.device_class && this.hass!.localize( - `component.select.state._.${option}` - ) || - option} - ` - ) - : ""} - - + `component.select.state.${stateObj.attributes.device_class}.${option}` + )) || + this.hass!.localize( + `component.select.state._.${option}` + ) || + option} + + ` + ) + : ""} + `; } - protected updated(changedProps: PropertyValues) { - super.updated(changedProps); - - if (!this.hass || !this._config) { - return; - } - - const stateObj = this.hass.states[this._config.entity] as - | SelectEntity - | undefined; - - if (!stateObj) { - return; - } - - // Update selected after rendering the items or else it won't work in Firefox - if (stateObj.attributes.options) { - this.shadowRoot!.querySelector("paper-listbox")!.selected = - stateObj.attributes.options.indexOf(stateObj.state); - } - } - static get styles(): CSSResultGroup { return css` hui-generic-entity-row { display: flex; align-items: center; } - ha-paper-dropdown-menu { - margin-left: 16px; - flex: 1; - } - paper-item { - cursor: pointer; - min-width: 200px; - } - .pointer { - cursor: pointer; - } - state-badge:focus { - outline: none; - background: var(--divider-color); - border-radius: 100%; + mwc-select { + width: 100%; } `; } private _selectedChanged(ev): void { const stateObj = this.hass!.states[this._config!.entity]; - const option = ev.target.selectedItem.option; + const option = ev.target.value; if (option === stateObj.state) { return; } diff --git a/src/state-summary/state-card-input_select.ts b/src/state-summary/state-card-input_select.ts index 91b47eede3..c992975a82 100644 --- a/src/state-summary/state-card-input_select.ts +++ b/src/state-summary/state-card-input_select.ts @@ -1,21 +1,12 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; -import "@polymer/paper-item/paper-item"; -import type { PaperItemElement } from "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { stopPropagation } from "../common/dom/stop_propagation"; import { computeStateName } from "../common/entity/compute_state_name"; import "../components/entity/state-badge"; +import { UNAVAILABLE_STATES } from "../data/entity"; import { InputSelectEntity, setInputSelectOption } from "../data/input_select"; -import type { PolymerIronSelectEvent } from "../polymer-types"; import type { HomeAssistant } from "../types"; @customElement("state-card-input_select") @@ -27,32 +18,25 @@ class StateCardInputSelect extends LitElement { protected render(): TemplateResult { return html` - - - ${this.stateObj.attributes.options.map( - (option) => html` ${option} ` - )} - - + ${this.stateObj.attributes.options.map( + (option) => + html`${option}` + )} + `; } - protected updated(changedProps: PropertyValues) { - super.updated(changedProps); - // Update selected after rendering the items or else it won't work in Firefox - this.shadowRoot!.querySelector("paper-listbox")!.selected = - this.stateObj.attributes.options.indexOf(this.stateObj.state); - } - - private async _selectedOptionChanged( - ev: PolymerIronSelectEvent - ) { - const option = ev.detail.item.innerText.trim(); + private async _selectedOptionChanged(ev) { + const option = ev.target.value; if (option === this.stateObj.state) { return; } @@ -62,7 +46,7 @@ class StateCardInputSelect extends LitElement { static get styles(): CSSResultGroup { return css` :host { - display: block; + display: flex; } state-badge { @@ -70,14 +54,8 @@ class StateCardInputSelect extends LitElement { margin-top: 10px; } - paper-dropdown-menu-light { - display: block; - margin-left: 53px; - } - - paper-item { - cursor: pointer; - min-width: 200px; + mwc-select { + width: 100%; } `; } diff --git a/src/state-summary/state-card-select.ts b/src/state-summary/state-card-select.ts index 8541cac41b..1aae15ad6c 100644 --- a/src/state-summary/state-card-select.ts +++ b/src/state-summary/state-card-select.ts @@ -1,18 +1,11 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { stopPropagation } from "../common/dom/stop_propagation"; import { computeStateName } from "../common/entity/compute_state_name"; import "../components/entity/state-badge"; +import { UNAVAILABLE } from "../data/entity"; import { SelectEntity, setSelectOption } from "../data/select"; import type { HomeAssistant } from "../types"; @@ -25,42 +18,34 @@ class StateCardSelect extends LitElement { protected render(): TemplateResult { return html` - - - ${this.stateObj.attributes.options.map( - (option) => - html` - ${(this.stateObj.attributes.device_class && - this.hass.localize( - `component.select.state.${this.stateObj.attributes.device_class}.${option}` - )) || - this.hass.localize(`component.select.state._.${option}`) || - option} - ` - )} - - + ${this.stateObj.attributes.options.map( + (option) => + html` + + ${(this.stateObj.attributes.device_class && + this.hass.localize( + `component.select.state.${this.stateObj.attributes.device_class}.${option}` + )) || + this.hass.localize(`component.select.state._.${option}`) || + option} + + ` + )} + `; } - protected updated(changedProps: PropertyValues) { - super.updated(changedProps); - if (!changedProps.has("stateObj")) { - return; - } - // Update selected after rendering the items or else it won't work in Firefox - this.shadowRoot!.querySelector("paper-listbox")!.selected = - this.stateObj.attributes.options.indexOf(this.stateObj.state); - } - private _selectedOptionChanged(ev) { - const option = ev.target.selectedItem.option; + const option = ev.target.value; if (option === this.stateObj.state) { return; } @@ -70,7 +55,7 @@ class StateCardSelect extends LitElement { static get styles(): CSSResultGroup { return css` :host { - display: block; + display: flex; } state-badge { @@ -78,14 +63,8 @@ class StateCardSelect extends LitElement { margin-top: 10px; } - paper-dropdown-menu-light { - display: block; - margin-left: 53px; - } - - paper-item { - cursor: pointer; - min-width: 200px; + mwc-select { + width: 100%; } `; } From e72a4e4a209af159ca3b385c808fa6f83e982dad Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Feb 2022 17:06:04 +0100 Subject: [PATCH 016/174] Convert HaFormSchemas to use selectors (#11589) --- gallery/src/pages/components/ha-selector.ts | 6 ++- .../registries/dialog-hassio-registries.ts | 9 ++-- .../ha-selector/ha-selector-text.ts | 53 +++++++++++++++---- src/data/selector.ts | 17 +++++- src/onboarding/onboarding-create-user.ts | 16 ++++-- .../zha/zha-config-dashboard.ts | 2 +- .../dialog-ha-mfa-module-setup-flow.ts | 2 +- 7 files changed, 82 insertions(+), 23 deletions(-) diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index e2cd0425ec..ce015a3756 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -50,7 +50,11 @@ const SCHEMAS: { action: { name: "Action", selector: { action: {} } }, text: { name: "Text", - selector: { text: { multiline: false } }, + selector: { text: {} }, + }, + password: { + name: "Password", + selector: { text: { type: "password" } }, }, text_multiline: { name: "Text multiline", diff --git a/hassio/src/dialogs/registries/dialog-hassio-registries.ts b/hassio/src/dialogs/registries/dialog-hassio-registries.ts index 1eb0072a3f..5f7d1967ae 100644 --- a/hassio/src/dialogs/registries/dialog-hassio-registries.ts +++ b/hassio/src/dialogs/registries/dialog-hassio-registries.ts @@ -19,22 +19,21 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import type { HomeAssistant } from "../../../../src/types"; import { RegistriesDialogParams } from "./show-dialog-registries"; -const SCHEMA = [ +const SCHEMA: HaFormSchema[] = [ { - type: "string", name: "registry", required: true, + selector: { text: {} }, }, { - type: "string", name: "username", required: true, + selector: { text: {} }, }, { - type: "string", name: "password", required: true, - format: "password", + selector: { text: { type: "password" } }, }, ]; diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts index f0a093de79..d9a1f87bfc 100644 --- a/src/components/ha-selector/ha-selector-text.ts +++ b/src/components/ha-selector/ha-selector-text.ts @@ -1,10 +1,12 @@ +import "@material/mwc-textarea/mwc-textarea"; +import "@material/mwc-textfield/mwc-textfield"; +import { mdiEye, mdiEyeOff } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { StringSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; -import "@material/mwc-textfield/mwc-textfield"; -import "@material/mwc-textarea/mwc-textarea"; +import "../ha-icon-button"; @customElement("ha-selector-text") export class HaTextSelector extends LitElement { @@ -20,6 +22,8 @@ export class HaTextSelector extends LitElement { @property({ type: Boolean }) public disabled = false; + @state() private _unmaskedPassword = false; + protected render() { if (this.selector.text?.multiline) { return html``; } return html``; + .value=${this.value || ""} + .placeholder=${this.placeholder || ""} + .disabled=${this.disabled} + .type=${this._unmaskedPassword ? "text" : this.selector.text?.type} + @input=${this._handleChange} + .label=${this.label || ""} + .suffix=${this.selector.text?.type === "password" + ? // reserve some space for the icon. + html`

` + : this.selector.text?.suffix} + required + > + ${this.selector.text?.type === "password" + ? html`` + : ""}`; + } + + private _toggleUnmaskedPassword(): void { + this._unmaskedPassword = !this._unmaskedPassword; } private _handleChange(ev) { @@ -54,10 +75,22 @@ export class HaTextSelector extends LitElement { static get styles(): CSSResultGroup { return css` + :host { + display: block; + position: relative; + } mwc-textfield, mwc-textarea { width: 100%; } + ha-icon-button { + position: absolute; + top: 16px; + right: 16px; + --mdc-icon-button-size: 24px; + --mdc-icon-size: 20px; + color: var(--secondary-text-color); + } `; } } diff --git a/src/data/selector.ts b/src/data/selector.ts index 3bb2999877..2ea7e0c2fe 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -95,7 +95,22 @@ export interface ActionSelector { export interface StringSelector { text: { - multiline: boolean; + multiline?: boolean; + type?: + | "number" + | "text" + | "search" + | "tel" + | "url" + | "email" + | "password" + | "date" + | "month" + | "week" + | "time" + | "datetime-local" + | "color"; + suffix?: string; }; } diff --git a/src/onboarding/onboarding-create-user.ts b/src/onboarding/onboarding-create-user.ts index c511d34a46..1855141a60 100644 --- a/src/onboarding/onboarding-create-user.ts +++ b/src/onboarding/onboarding-create-user.ts @@ -18,10 +18,18 @@ import { onboardUserStep } from "../data/onboarding"; import { PolymerChangedEvent } from "../polymer-types"; const CREATE_USER_SCHEMA: HaFormSchema[] = [ - { type: "string", name: "name", required: true }, - { type: "string", name: "username", required: true }, - { type: "string", name: "password", required: true }, - { type: "string", name: "password_confirm", required: true }, + { name: "name", required: true, selector: { text: {} } }, + { name: "username", required: true, selector: { text: {} } }, + { + name: "password", + required: true, + selector: { text: { type: "password" } }, + }, + { + name: "password_confirm", + required: true, + selector: { text: { type: "password" } }, + }, ]; @customElement("onboarding-create-user") diff --git a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts index 8983508074..710328c269 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts @@ -106,7 +106,7 @@ class ZHAConfigDashboard extends LitElement { ${this._configuration ? Object.entries(this._configuration.schemas).map( - ([section, schema]) => html` html`` : this._step.type === "form" - ? html` Date: Mon, 7 Feb 2022 17:17:32 +0100 Subject: [PATCH 017/174] Fix number selector (#11585) --- src/components/ha-selector/ha-selector-number.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ha-selector/ha-selector-number.ts b/src/components/ha-selector/ha-selector-number.ts index 6b2652b5b9..9f4d29a765 100644 --- a/src/components/ha-selector/ha-selector-number.ts +++ b/src/components/ha-selector/ha-selector-number.ts @@ -62,9 +62,9 @@ export class HaNumberSelector extends LitElement { private _handleInputChange(ev) { ev.stopPropagation(); const value = - ev.detail.value === "" || isNaN(ev.detail.value) + ev.target.value === "" || isNaN(ev.target.value) ? undefined - : Number(ev.detail.value); + : Number(ev.target.value); if (this.value === value) { return; } From 869fa91ae5bc9f7038320587dcdfc8e2d6919eda Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Feb 2022 17:22:08 +0100 Subject: [PATCH 018/174] Convert entity-attribute picker to ha-combo-box (#11587) --- .../entity/ha-entity-attribute-picker.ts | 148 +++--------------- src/components/ha-combo-box.ts | 12 +- .../condition/ha-automation-condition-row.ts | 2 +- .../trigger/ha-automation-trigger-row.ts | 2 +- .../config-elements/hui-entity-card-editor.ts | 2 +- .../hui-weather-forecast-card-editor.ts | 3 +- src/panels/lovelace/editor/types.ts | 6 +- 7 files changed, 37 insertions(+), 138 deletions(-) diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts index 51b17fd53d..291d30da5e 100644 --- a/src/components/entity/ha-entity-attribute-picker.ts +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -1,54 +1,14 @@ -import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import { HassEntity } from "home-assistant-js-websocket"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; -import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query } from "lit/decorators"; -import { fireEvent } from "../../common/dom/fire_event"; import { formatAttributeName } from "../../data/entity_attributes"; import { PolymerChangedEvent } from "../../polymer-types"; import { HomeAssistant } from "../../types"; -import "../ha-icon-button"; -import "../ha-svg-icon"; -import "./state-badge"; +import "../ha-combo-box"; +import type { HaComboBox } from "../ha-combo-box"; export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean; -// eslint-disable-next-line lit/prefer-static-styles -const rowRenderer: ComboBoxLitRenderer = (item) => html` - - ${formatAttributeName(item)}`; - @customElement("ha-entity-attribute-picker") class HaEntityAttributePicker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -68,7 +28,7 @@ class HaEntityAttributePicker extends LitElement { @property({ type: Boolean }) private _opened = false; - @query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement; + @query("ha-combo-box", true) private _comboBox!: HaComboBox; protected shouldUpdate(changedProps: PropertyValues) { return !(!changedProps.has("_opened") && this._opened); @@ -78,7 +38,10 @@ class HaEntityAttributePicker extends LitElement { if (changedProps.has("_opened") && this._opened) { const state = this.entityId ? this.hass.states[this.entityId] : undefined; (this._comboBox as any).items = state - ? Object.keys(state.attributes) + ? Object.keys(state.attributes).map((key) => ({ + value: key, + label: formatAttributeName(key), + })) : []; } } @@ -89,100 +52,31 @@ class HaEntityAttributePicker extends LitElement { } return html` - - -
- ${this.value - ? html` - - ` - : ""} - - -
-
-
+ `; } - private _clearValue(ev: Event) { - ev.stopPropagation(); - this._setValue(""); - } - - private get _value() { - return this.value; - } - private _openedChanged(ev: PolymerChangedEvent) { this._opened = ev.detail.value; } private _valueChanged(ev: PolymerChangedEvent) { - const newValue = ev.detail.value; - if (newValue !== this._value) { - this._setValue(newValue); - } - } - - private _setValue(value: string) { - this.value = value; - setTimeout(() => { - fireEvent(this, "value-changed", { value }); - fireEvent(this, "change"); - }, 0); - } - - static get styles(): CSSResultGroup { - return css` - .suffix { - display: flex; - } - ha-icon-button { - --mdc-icon-button-size: 24px; - padding: 0px 2px; - color: var(--secondary-text-color); - } - [hidden] { - display: none; - } - `; + this.value = ev.detail.value; } } diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index c4717b4d33..6a44d33a6f 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -52,9 +52,6 @@ registerStyles( ` ); -const defaultRowRenderer: ComboBoxLitRenderer = (item) => - html`${item}`; - @customElement("ha-combo-box") export class HaComboBox extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -112,7 +109,7 @@ export class HaComboBox extends LitElement { .filteredItems=${this.filteredItems} .allowCustomValue=${this.allowCustomValue} .disabled=${this.disabled} - ${comboBoxRenderer(this.renderer || defaultRowRenderer)} + ${comboBoxRenderer(this.renderer || this._defaultRowRenderer)} @opened-changed=${this._openedChanged} @filter-changed=${this._filterChanged} @value-changed=${this._valueChanged} @@ -147,6 +144,13 @@ export class HaComboBox extends LitElement { `; } + private _defaultRowRenderer: ComboBoxLitRenderer< + string | Record + > = (item) => + html` + ${this.itemLabelPath ? item[this.itemLabelPath] : item} + `; + private _clearValue(ev: Event) { ev.stopPropagation(); fireEvent(this, "value-changed", { value: undefined }); diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 3e792abd18..b36118b680 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -24,7 +24,7 @@ export const handleChangeEvent = ( ev: CustomEvent ) => { ev.stopPropagation(); - const name = (ev.target as any)?.name; + const name = (ev.currentTarget as any)?.name; if (!name) { return; } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 15dbd9779a..f62a33fd7a 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -57,7 +57,7 @@ export interface TriggerElement extends LitElement { export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => { ev.stopPropagation(); - const name = (ev.target as any)?.name; + const name = (ev.currentTarget as any)?.name; if (!name) { return; } diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts index 845743d300..df9cb0e627 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts @@ -175,7 +175,7 @@ export class HuiEntityCardEditor if (!this._config || !this.hass) { return; } - const target = ev.target! as EditorTarget; + const target = ev.currentTarget! as EditorTarget; if ( this[`_${target.configValue}`] === target.value || diff --git a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts index 106010c441..cb1a53bede 100644 --- a/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-weather-forecast-card-editor.ts @@ -199,7 +199,8 @@ export class HuiWeatherForecastCardEditor if (!this._config || !this.hass) { return; } - const target = ev.target! as EditorTarget; + const target = ev.currentTarget! as EditorTarget; + if (this[`_${target.configValue}`] === target.value) { return; } diff --git a/src/panels/lovelace/editor/types.ts b/src/panels/lovelace/editor/types.ts index 73f2b3fe65..821c2dabcb 100644 --- a/src/panels/lovelace/editor/types.ts +++ b/src/panels/lovelace/editor/types.ts @@ -38,12 +38,12 @@ export interface ConfigError { message: string; } -export interface EntitiesEditorEvent { - detail?: { +export interface EntitiesEditorEvent extends CustomEvent { + detail: { entities?: EntityConfig[]; item?: any; }; - target?: EventTarget; + target: EventTarget | null; } export interface EditorTarget extends EventTarget { From 2cb37820dfc59dce612524718e23b71049b5cce9 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Feb 2022 17:43:49 +0100 Subject: [PATCH 019/174] Convert icon picker to ha-combobox (#11586) Co-authored-by: Zack --- src/components/ha-combo-box.ts | 32 ++++++--- src/components/ha-icon-picker.ts | 108 +++++++++++-------------------- src/components/ha-textfield.ts | 27 +++++++- 3 files changed, 84 insertions(+), 83 deletions(-) diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index 6a44d33a6f..5dd0c0527f 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-textfield/mwc-textfield"; import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import type { ComboBoxLight } from "@vaadin/combo-box/vaadin-combo-box-light"; @@ -11,6 +10,7 @@ import { fireEvent } from "../common/dom/fire_event"; import { PolymerChangedEvent } from "../polymer-types"; import { HomeAssistant } from "../types"; import "./ha-icon-button"; +import "./ha-textfield"; registerStyles( "vaadin-combo-box-item", @@ -54,12 +54,22 @@ registerStyles( @customElement("ha-combo-box") export class HaComboBox extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; + @property({ attribute: false }) public hass?: HomeAssistant; @property() public label?: string; @property() public value?: string; + @property() public placeholder?: string; + + @property() public validationMessage?: string; + + @property({ attribute: "error-message" }) public errorMessage?: string; + + @property({ type: Boolean }) public invalid?: boolean; + + @property({ type: Boolean }) public icon?: boolean; + @property() public items?: any[]; @property() public filteredItems?: any[]; @@ -115,27 +125,33 @@ export class HaComboBox extends LitElement { @value-changed=${this._valueChanged} attr-for-value="value" > -
`} + .icon=${this.icon} + .invalid=${this.invalid} > - + + ${this.value ? html`` : ""} ha-icon-button { + ha-textfield > ha-icon-button { --mdc-icon-button-size: 24px; padding: 2px; color: var(--secondary-text-color); diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index 972a2f7a61..2d7c1002c5 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -1,16 +1,13 @@ -import { mdiCheck, mdiMenuDown, mdiMenuUp } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-icon-item"; -import "@polymer/paper-item/paper-item-body"; -import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import { css, html, LitElement, TemplateResult } from "lit"; -import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; +import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; import { customIcons } from "../data/custom_icons"; import { PolymerChangedEvent } from "../polymer-types"; +import { HomeAssistant } from "../types"; +import "./ha-combo-box"; +import type { HaComboBox } from "./ha-combo-box"; import "./ha-icon"; -import "./ha-icon-button"; type IconItem = { icon: string; @@ -19,35 +16,17 @@ type IconItem = { let iconItems: IconItem[] = []; // eslint-disable-next-line lit/prefer-static-styles -const rowRenderer: ComboBoxLitRenderer = (item) => html` - - - - - ${item.icon} - `; +const rowRenderer: ComboBoxLitRenderer = (item) => html` + + ${item.icon} +`; @customElement("ha-icon-picker") export class HaIconPicker extends LitElement { + @property() public hass?: HomeAssistant; + @property() public value?: string; @property() public label?: string; @@ -64,51 +43,40 @@ export class HaIconPicker extends LitElement { @state() private _opened = false; - @query("vaadin-combo-box-light", true) private comboBox!: HTMLElement; + @query("ha-combo-box", true) private comboBox!: HaComboBox; protected render(): TemplateResult { return html` - - - ${this._value || this.placeholder - ? html` - - - ` - : this.fallbackPath - ? html`` - : ""} - - - + ${this._value || this.placeholder + ? html` + + + ` + : this.fallbackPath + ? html`` + : ""} + `; } @@ -150,6 +118,7 @@ export class HaIconPicker extends LitElement { } private _valueChanged(ev: PolymerChangedEvent) { + ev.stopPropagation(); this._setValue(ev.detail.value); } @@ -158,7 +127,7 @@ export class HaIconPicker extends LitElement { fireEvent( this, "value-changed", - { value }, + { value: this._value }, { bubbles: false, composed: false, @@ -211,11 +180,6 @@ export class HaIconPicker extends LitElement { *[slot="prefix"] { margin-right: 8px; } - paper-input > ha-icon-button { - --mdc-icon-button-size: 24px; - padding: 2px; - color: var(--secondary-text-color); - } `; } } diff --git a/src/components/ha-textfield.ts b/src/components/ha-textfield.ts index 55daec0349..c5e0e21a90 100644 --- a/src/components/ha-textfield.ts +++ b/src/components/ha-textfield.ts @@ -1,10 +1,31 @@ import { TextField } from "@material/mwc-textfield"; -import { TemplateResult, html } from "lit"; -import { customElement } from "lit/decorators"; +import { TemplateResult, html, PropertyValues } from "lit"; +import { customElement, property } from "lit/decorators"; @customElement("ha-textfield") export class HaTextField extends TextField { - override renderIcon(_icon: string, isTrailingIcon = false): TemplateResult { + @property({ type: Boolean }) public invalid?: boolean; + + @property({ attribute: "error-message" }) public errorMessage?: string; + + override updated(changedProperties: PropertyValues) { + super.updated(changedProperties); + if ( + (changedProperties.has("invalid") && + (this.invalid || changedProperties.get("invalid") !== undefined)) || + changedProperties.has("errorMessage") + ) { + this.setCustomValidity( + this.invalid ? this.errorMessage || "Invalid" : "" + ); + this.reportValidity(); + } + } + + protected override renderIcon( + _icon: string, + isTrailingIcon = false + ): TemplateResult { const type = isTrailingIcon ? "trailing" : "leading"; return html` From 236fa14ec34b5b4b19a624b1ae4c01418011abec Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Feb 2022 17:52:44 +0100 Subject: [PATCH 020/174] Convert area-devices picker (#11588) --- .../device/ha-area-devices-picker.ts | 133 +++--------------- 1 file changed, 18 insertions(+), 115 deletions(-) diff --git a/src/components/device/ha-area-devices-picker.ts b/src/components/device/ha-area-devices-picker.ts index 67e01a131d..6ce007e441 100644 --- a/src/components/device/ha-area-devices-picker.ts +++ b/src/components/device/ha-area-devices-picker.ts @@ -1,20 +1,7 @@ import "@material/mwc-button/mwc-button"; -import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; -import "@polymer/paper-listbox/paper-listbox"; -import "@vaadin/combo-box/theme/material/vaadin-combo-box-light"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; -import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers"; +import { html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../common/dom/fire_event"; @@ -50,36 +37,12 @@ interface AreaDevices { devices: string[]; } -// eslint-disable-next-line lit/prefer-static-styles -const rowRenderer: ComboBoxLitRenderer = (item) => html` - - - -
${item.name}
-
${item.devices.length} devices
-
-
`; +const rowRenderer: ComboBoxLitRenderer = ( + item +) => html` + ${item.name} + ${item.devices.length} devices +`; @customElement("ha-area-devices-picker") export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) { @@ -117,9 +80,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) { @property({ type: Array, attribute: "include-device-classes" }) public includeDeviceClasses?: string[]; - @property({ type: Boolean }) - private _opened?: boolean; - @state() private _areaPicker = true; @state() private _devices?: DeviceRegistryEntry[]; @@ -302,71 +262,30 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) { `; } return html` - - -
- ${this.value - ? html` ` - : ""} - ${areas.length > 0 - ? html` - - ` - : ""} -
-
-
- Choose individual devices + + + Choose individual devices + `; } - private _clearValue(ev: Event) { - ev.stopPropagation(); - this._setValue([]); - } - private get _value() { return this.value || []; } - private _openedChanged(ev: PolymerChangedEvent) { - this._opened = ev.detail.value; - } - private async _switchPicker() { this._areaPicker = !this._areaPicker; } @@ -398,22 +317,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) { fireEvent(this, "change"); }, 0); } - - static get styles(): CSSResultGroup { - return css` - .suffix { - display: flex; - } - ha-icon-button { - --mdc-icon-button-size: 24px; - padding: 0px 2px; - color: var(--secondary-text-color); - } - [hidden] { - display: none; - } - `; - } } declare global { From 09d46dac617d4dd0d3d283be288774374a30743c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Feb 2022 17:53:23 +0100 Subject: [PATCH 021/174] Convert device automation picker to mwc (#11592) Co-authored-by: Zack --- .../device/ha-device-automation-picker.ts | 105 +++++++----------- 1 file changed, 42 insertions(+), 63 deletions(-) diff --git a/src/components/device/ha-device-automation-picker.ts b/src/components/device/ha-device-automation-picker.ts index e4cc705dce..007f40e77c 100644 --- a/src/components/device/ha-device-automation-picker.ts +++ b/src/components/device/ha-device-automation-picker.ts @@ -1,7 +1,5 @@ -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; -import "@polymer/paper-listbox/paper-listbox"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; @@ -10,7 +8,6 @@ import { deviceAutomationsEqual, } from "../../data/device_automation"; import { HomeAssistant } from "../../types"; -import "../ha-paper-dropdown-menu"; const NO_AUTOMATION_KEY = "NO_AUTOMATION"; const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION"; @@ -67,14 +64,12 @@ export abstract class HaDeviceAutomationPicker< this._createNoAutomation = createNoAutomation; } - private get _key() { - if ( - !this.value || - deviceAutomationsEqual( - this._createNoAutomation(this.deviceId), - this.value - ) - ) { + private get _value() { + if (!this.value) { + return ""; + } + + if (!this._automations.length) { return NO_AUTOMATION_KEY; } @@ -93,42 +88,32 @@ export abstract class HaDeviceAutomationPicker< if (this._renderEmpty) { return html``; } + const value = this._value; return html` - - - - - ${this._automations.map( - (automation, idx) => html` - - ${this._localizeDeviceAutomation(this.hass, automation)} - - ` - )} - - + ${value === NO_AUTOMATION_KEY + ? html` + ${this.NO_AUTOMATION_TEXT} + ` + : ""} + ${value === UNKNOWN_AUTOMATION_KEY + ? html` + ${this.UNKNOWN_AUTOMATION_TEXT} + ` + : ""} + ${this._automations.map( + (automation, idx) => html` + + ${this._localizeDeviceAutomation(this.hass, automation)} + + ` + )} + `; } @@ -138,14 +123,6 @@ export abstract class HaDeviceAutomationPicker< if (changedProps.has("deviceId")) { this._updateDeviceInfo(); } - - // The value has changed, force the listbox to update - if (changedProps.has("value") || changedProps.has("_renderEmpty")) { - const listbox = this.shadowRoot!.querySelector("paper-listbox")!; - if (listbox) { - listbox._selectSelected(this._key); - } - } } private async _updateDeviceInfo() { @@ -168,9 +145,16 @@ export abstract class HaDeviceAutomationPicker< } private _automationChanged(ev) { - if (ev.detail.item.automation) { - this._setValue(ev.detail.item.automation); + const value = ev.target.value; + if (!value || [UNKNOWN_AUTOMATION_KEY, NO_AUTOMATION_KEY].includes(value)) { + return; } + const [deviceId, idx] = value.split("_"); + const automation = this._automations[idx]; + if (automation.device_id !== deviceId) { + return; + } + this._setValue(automation); } private _setValue(automation: T) { @@ -183,14 +167,9 @@ export abstract class HaDeviceAutomationPicker< static get styles(): CSSResultGroup { return css` - ha-paper-dropdown-menu { + mwc-select { width: 100%; - } - paper-listbox { - min-width: 200px; - } - paper-item { - cursor: pointer; + margin-top: 4px; } `; } From e9ec2da91766d10b47be0b7477f9da7db99cb519 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Feb 2022 18:58:36 +0100 Subject: [PATCH 022/174] Fix clearing device in device action (#11594) --- .../action/types/ha-automation-action-device_id.ts | 5 +++++ .../condition/types/ha-automation-condition-device.ts | 5 +++++ .../automation/trigger/types/ha-automation-trigger-device.ts | 5 +++++ src/panels/developer-tools/state/developer-tools-state.js | 2 +- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/panels/config/automation/action/types/ha-automation-action-device_id.ts b/src/panels/config/automation/action/types/ha-automation-action-device_id.ts index 1a4565f870..5aa1cecbf2 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-device_id.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-device_id.ts @@ -107,6 +107,11 @@ export class HaDeviceAction extends LitElement { private _devicePicked(ev) { ev.stopPropagation(); this._deviceId = ev.target.value; + if (this._deviceId === undefined) { + fireEvent(this, "value-changed", { + value: HaDeviceAction.defaultConfig, + }); + } } private _deviceActionPicked(ev) { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-device.ts b/src/panels/config/automation/condition/types/ha-automation-condition-device.ts index 74277a4680..b158625876 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-device.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-device.ts @@ -111,6 +111,11 @@ export class HaDeviceCondition extends LitElement { private _devicePicked(ev) { ev.stopPropagation(); this._deviceId = ev.target.value; + if (this._deviceId === undefined) { + fireEvent(this, "value-changed", { + value: { ...HaDeviceCondition.defaultConfig, condition: "device" }, + }); + } } private _deviceConditionPicked(ev) { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts index 6dc84a01f2..43ddbeeb14 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts @@ -111,6 +111,11 @@ export class HaDeviceTrigger extends LitElement { private _devicePicked(ev) { ev.stopPropagation(); this._deviceId = ev.target.value; + if (this._deviceId === undefined) { + fireEvent(this, "value-changed", { + value: { ...HaDeviceTrigger.defaultConfig, platform: "device" }, + }); + } } private _deviceTriggerPicked(ev) { diff --git a/src/panels/developer-tools/state/developer-tools-state.js b/src/panels/developer-tools/state/developer-tools-state.js index 58a4d9ad58..e90e0365ce 100644 --- a/src/panels/developer-tools/state/developer-tools-state.js +++ b/src/panels/developer-tools/state/developer-tools-state.js @@ -400,7 +400,7 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) { } entityIdChanged() { - if (this._entityId === "") { + if (!this._entityId) { this._entity = undefined; this._state = ""; this._stateAttributes = ""; From 2d33327d882782177fe4a66b4bd9b1742f4a724d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Feb 2022 21:33:09 +0100 Subject: [PATCH 023/174] dark mode fixes (#11595) --- src/components/ha-combo-box.ts | 2 +- src/components/ha-icon-picker.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index 5dd0c0527f..c80a131dca 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -19,7 +19,7 @@ registerStyles( padding: 0; } :host([focused]:not([disabled])) { - background-color: rgba(0, 0, 0, 0.12); + background-color: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.12); } :host([selected]:not([disabled])) { background-color: transparent; diff --git a/src/components/ha-icon-picker.ts b/src/components/ha-icon-picker.ts index 2d7c1002c5..57ec368cdc 100644 --- a/src/components/ha-icon-picker.ts +++ b/src/components/ha-icon-picker.ts @@ -174,6 +174,7 @@ export class HaIconPicker extends LitElement { return css` ha-icon, ha-svg-icon { + color: var(--primary-text-color); position: relative; bottom: 2px; } From 6a51e2aaad293d65c8b24dee5797d254a98e366e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 7 Feb 2022 23:18:52 +0100 Subject: [PATCH 024/174] Only show stable add-ons in the store if not advanced mode (#11596) --- hassio/src/addon-store/hassio-addon-repository.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hassio/src/addon-store/hassio-addon-repository.ts b/hassio/src/addon-store/hassio-addon-repository.ts index 49da9c3c21..43e9822df2 100644 --- a/hassio/src/addon-store/hassio-addon-repository.ts +++ b/hassio/src/addon-store/hassio-addon-repository.ts @@ -42,7 +42,9 @@ class HassioAddonRepositoryEl extends LitElement { const repo = this.repo; let _addons = this.addons; if (!this.hass.userData?.showAdvanced) { - _addons = _addons.filter((addon) => !addon.advanced); + _addons = _addons.filter( + (addon) => !addon.advanced && addon.stage === "stable" + ); } const addons = this._getAddons(_addons, this.filter); From 9eea17b793b2e007ab408f71c3577e222ec1808b Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 7 Feb 2022 17:45:16 -0600 Subject: [PATCH 025/174] Convert Automation Action Choose to HA Form (#11597) * Convert Auatomation Action Choose to HA Form * remove log * Remove Import --- .../action/types/ha-automation-action-choose.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts index f342512745..48ff2b68f7 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -10,8 +10,8 @@ import { Condition } from "../../../../../data/automation"; import { Action, ChooseAction } from "../../../../../data/script"; import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; -import "../ha-automation-action"; import { ActionElement } from "../ha-automation-action-row"; +import "../../../../../components/ha-form/ha-form"; @customElement("ha-automation-action-choose") export class HaChooseAction extends LitElement implements ActionElement { @@ -61,12 +61,13 @@ export class HaChooseAction extends LitElement implements ActionElement { "ui.panel.config.automation.editor.actions.type.choose.sequence" )}: - + > ` )} @@ -107,7 +108,7 @@ export class HaChooseAction extends LitElement implements ActionElement { private _actionChanged(ev: CustomEvent) { ev.stopPropagation(); - const value = ev.detail.value as Action[]; + const value = ev.detail.value.sequence as Action[]; const index = (ev.target as any).idx; const choose = this.action.choose ? [...ensureArray(this.action.choose)] From 4ef5f3af89d3a88b80679d497205aaedc0e1fa16 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 9 Feb 2022 16:50:48 +0100 Subject: [PATCH 026/174] Replace checkboxes in list items with `check-list-item` (#11610) Co-authored-by: Paulus Schoutsen --- src/components/ha-check-list-item.ts | 22 +++ src/components/ha-checkbox.ts | 18 ++- src/components/ha-dialog.ts | 148 +++++++++--------- src/components/ha-form/ha-form-float.ts | 18 +-- src/components/ha-form/ha-form-integer.ts | 20 +-- .../ha-form/ha-form-multi_select.ts | 83 +++++++--- src/components/ha-form/ha-form-string.ts | 18 +-- src/components/ha-formfield.ts | 34 ++-- src/components/ha-qr-scanner.ts | 16 +- src/components/ha-radio.ts | 18 ++- .../ha-selector/ha-selector-number.ts | 14 +- .../ha-selector/ha-selector-text.ts | 8 +- src/components/ha-slider.js | 2 +- src/components/ha-switch.ts | 59 ++++--- src/components/ha-textfield.ts | 16 +- .../config/cloud/account/cloud-google-pref.ts | 12 +- .../forgot-password/cloud-forgot-password.ts | 12 +- src/panels/config/cloud/login/cloud-login.ts | 16 +- .../config/cloud/register/cloud-register.ts | 16 +- .../devices/ha-config-devices-dashboard.ts | 12 +- .../config/entities/ha-config-entities.ts | 30 ++-- .../integrations/ha-config-integrations.ts | 16 +- .../zwave_js/dialog-zwave_js-add-node.ts | 5 - 23 files changed, 330 insertions(+), 283 deletions(-) create mode 100644 src/components/ha-check-list-item.ts diff --git a/src/components/ha-check-list-item.ts b/src/components/ha-check-list-item.ts new file mode 100644 index 0000000000..5b237208c9 --- /dev/null +++ b/src/components/ha-check-list-item.ts @@ -0,0 +1,22 @@ +import { css } from "lit"; +import { CheckListItemBase } from "@material/mwc-list/mwc-check-list-item-base"; +import { styles } from "@material/mwc-list/mwc-control-list-item.css"; +import { customElement } from "lit/decorators"; + +@customElement("ha-check-list-item") +export class HaCheckListItem extends CheckListItemBase { + static override styles = [ + styles, + css` + :host { + --mdc-theme-secondary: var(--primary-color); + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-check-list-item": HaCheckListItem; + } +} diff --git a/src/components/ha-checkbox.ts b/src/components/ha-checkbox.ts index b3f5a1a2f4..0e33ee2a18 100644 --- a/src/components/ha-checkbox.ts +++ b/src/components/ha-checkbox.ts @@ -1,12 +1,18 @@ -import { Checkbox } from "@material/mwc-checkbox"; +import { CheckboxBase } from "@material/mwc-checkbox/mwc-checkbox-base"; +import { styles } from "@material/mwc-checkbox/mwc-checkbox.css"; +import { css } from "lit"; import { customElement } from "lit/decorators"; @customElement("ha-checkbox") -export class HaCheckbox extends Checkbox { - public firstUpdated() { - super.firstUpdated(); - this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)"); - } +export class HaCheckbox extends CheckboxBase { + static override styles = [ + styles, + css` + :host { + --mdc-theme-secondary: var(--primary-color); + } + `, + ]; } declare global { diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index 34614bedc6..693bae6367 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -1,6 +1,7 @@ -import { Dialog } from "@material/mwc-dialog"; +import { DialogBase } from "@material/mwc-dialog/mwc-dialog-base"; +import { styles } from "@material/mwc-dialog/mwc-dialog.css"; import { mdiClose } from "@mdi/js"; -import { css, CSSResultGroup, html, TemplateResult } from "lit"; +import { css, html, TemplateResult } from "lit"; import { customElement } from "lit/decorators"; import { computeRTLDirection } from "../common/util/compute_rtl"; import type { HomeAssistant } from "../types"; @@ -21,8 +22,7 @@ export const createCloseHeading = ( `; @customElement("ha-dialog") -// @ts-expect-error -export class HaDialog extends Dialog { +export class HaDialog extends DialogBase { public scrollToPos(x: number, y: number) { this.contentElement?.scrollTo(x, y); } @@ -31,77 +31,75 @@ export class HaDialog extends Dialog { return html` ${super.renderHeading()} `; } - protected static get styles(): CSSResultGroup { - return [ - Dialog.styles, - css` - .mdc-dialog { - --mdc-dialog-scroll-divider-color: var(--divider-color); - z-index: var(--dialog-z-index, 7); - -webkit-backdrop-filter: var(--dialog-backdrop-filter, none); - backdrop-filter: var(--dialog-backdrop-filter, none); - } - .mdc-dialog__actions { - justify-content: var(--justify-action-buttons, flex-end); - padding-bottom: max(env(safe-area-inset-bottom), 8px); - } - .mdc-dialog__actions span:nth-child(1) { - flex: var(--secondary-action-button-flex, unset); - } - .mdc-dialog__actions span:nth-child(2) { - flex: var(--primary-action-button-flex, unset); - } - .mdc-dialog__container { - align-items: var(--vertial-align-dialog, center); - } - .mdc-dialog__title::before { - display: block; - height: 20px; - } - .mdc-dialog .mdc-dialog__content { - position: var(--dialog-content-position, relative); - padding: var(--dialog-content-padding, 20px 24px); - } - :host([hideactions]) .mdc-dialog .mdc-dialog__content { - padding-bottom: max( - var(--dialog-content-padding, 20px), - env(safe-area-inset-bottom) - ); - } - .mdc-dialog .mdc-dialog__surface { - position: var(--dialog-surface-position, relative); - top: var(--dialog-surface-top); - min-height: var(--mdc-dialog-min-height, auto); - border-radius: var( - --ha-dialog-border-radius, - var(--ha-card-border-radius, 4px) - ); - } - :host([flexContent]) .mdc-dialog .mdc-dialog__content { - display: flex; - flex-direction: column; - } - .header_button { - position: absolute; - right: 16px; - top: 10px; - text-decoration: none; - color: inherit; - } - .header_title { - margin-right: 40px; - } - [dir="rtl"].header_button { - right: auto; - left: 16px; - } - [dir="rtl"].header_title { - margin-left: 40px; - margin-right: 0px; - } - `, - ]; - } + static override styles = [ + styles, + css` + .mdc-dialog { + --mdc-dialog-scroll-divider-color: var(--divider-color); + z-index: var(--dialog-z-index, 7); + -webkit-backdrop-filter: var(--dialog-backdrop-filter, none); + backdrop-filter: var(--dialog-backdrop-filter, none); + } + .mdc-dialog__actions { + justify-content: var(--justify-action-buttons, flex-end); + padding-bottom: max(env(safe-area-inset-bottom), 8px); + } + .mdc-dialog__actions span:nth-child(1) { + flex: var(--secondary-action-button-flex, unset); + } + .mdc-dialog__actions span:nth-child(2) { + flex: var(--primary-action-button-flex, unset); + } + .mdc-dialog__container { + align-items: var(--vertial-align-dialog, center); + } + .mdc-dialog__title::before { + display: block; + height: 20px; + } + .mdc-dialog .mdc-dialog__content { + position: var(--dialog-content-position, relative); + padding: var(--dialog-content-padding, 20px 24px); + } + :host([hideactions]) .mdc-dialog .mdc-dialog__content { + padding-bottom: max( + var(--dialog-content-padding, 20px), + env(safe-area-inset-bottom) + ); + } + .mdc-dialog .mdc-dialog__surface { + position: var(--dialog-surface-position, relative); + top: var(--dialog-surface-top); + min-height: var(--mdc-dialog-min-height, auto); + border-radius: var( + --ha-dialog-border-radius, + var(--ha-card-border-radius, 4px) + ); + } + :host([flexContent]) .mdc-dialog .mdc-dialog__content { + display: flex; + flex-direction: column; + } + .header_button { + position: absolute; + right: 16px; + top: 10px; + text-decoration: none; + color: inherit; + } + .header_title { + margin-right: 40px; + } + [dir="rtl"].header_button { + right: auto; + left: 16px; + } + [dir="rtl"].header_title { + margin-left: 40px; + margin-right: 0px; + } + `, + ]; } declare global { diff --git a/src/components/ha-form/ha-form-float.ts b/src/components/ha-form/ha-form-float.ts index 7cae936ef7..ce28ee7cae 100644 --- a/src/components/ha-form/ha-form-float.ts +++ b/src/components/ha-form/ha-form-float.ts @@ -1,21 +1,21 @@ -import "@material/mwc-textfield"; -import type { TextField } from "@material/mwc-textfield"; import { css, html, LitElement, TemplateResult, PropertyValues } from "lit"; import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; +import type { HaTextField } from "../ha-textfield"; +import "../ha-textfield"; import { HaFormElement, HaFormFloatData, HaFormFloatSchema } from "./types"; @customElement("ha-form-float") export class HaFormFloat extends LitElement implements HaFormElement { - @property() public schema!: HaFormFloatSchema; + @property({ attribute: false }) public schema!: HaFormFloatSchema; - @property() public data!: HaFormFloatData; + @property({ attribute: false }) public data!: HaFormFloatData; @property() public label!: string; @property({ type: Boolean }) public disabled = false; - @query("mwc-textfield") private _input?: HTMLElement; + @query("ha-textfield") private _input?: HaTextField; public focus() { if (this._input) { @@ -25,7 +25,7 @@ export class HaFormFloat extends LitElement implements HaFormElement { protected render(): TemplateResult { return html` - + > `; } @@ -46,7 +46,7 @@ export class HaFormFloat extends LitElement implements HaFormElement { } private _valueChanged(ev: Event) { - const source = ev.target as TextField; + const source = ev.target as HaTextField; const rawValue = source.value.replace(",", "."); let value: number | undefined; @@ -81,7 +81,7 @@ export class HaFormFloat extends LitElement implements HaFormElement { :host([own-margin]) { margin-bottom: 5px; } - mwc-textfield { + ha-textfield { display: block; } `; diff --git a/src/components/ha-form/ha-form-integer.ts b/src/components/ha-form/ha-form-integer.ts index 3a684a3d40..5873eff158 100644 --- a/src/components/ha-form/ha-form-integer.ts +++ b/src/components/ha-form/ha-form-integer.ts @@ -1,6 +1,3 @@ -import "@material/mwc-textfield"; -import type { TextField } from "@material/mwc-textfield"; -import type { Slider } from "@material/mwc-slider"; import { css, CSSResultGroup, @@ -14,18 +11,21 @@ import { fireEvent } from "../../common/dom/fire_event"; import { HaCheckbox } from "../ha-checkbox"; import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types"; import "../ha-slider"; +import { HaTextField } from "../ha-textfield"; @customElement("ha-form-integer") export class HaFormInteger extends LitElement implements HaFormElement { - @property() public schema!: HaFormIntegerSchema; + @property({ attribute: false }) public schema!: HaFormIntegerSchema; - @property() public data?: HaFormIntegerData; + @property({ attribute: false }) public data?: HaFormIntegerData; @property() public label?: string; @property({ type: Boolean }) public disabled = false; - @query("mwc-textfield ha-slider") private _input?: HTMLElement; + @query("ha-textfield ha-slider") private _input?: + | HaTextField + | HTMLInputElement; private _lastValue?: HaFormIntegerData; @@ -70,7 +70,7 @@ export class HaFormInteger extends LitElement implements HaFormElement { } return html` - + > `; } @@ -138,7 +138,7 @@ export class HaFormInteger extends LitElement implements HaFormElement { } private _valueChanged(ev: Event) { - const source = ev.target as TextField | Slider; + const source = ev.target as HaTextField | HTMLInputElement; const rawValue = source.value; let value: number | undefined; @@ -172,7 +172,7 @@ export class HaFormInteger extends LitElement implements HaFormElement { ha-slider { flex: 1; } - mwc-textfield { + ha-textfield { display: block; } `; diff --git a/src/components/ha-form/ha-form-multi_select.ts b/src/components/ha-form/ha-form-multi_select.ts index 3023a1a15f..4777eeb76b 100644 --- a/src/components/ha-form/ha-form-multi_select.ts +++ b/src/components/ha-form/ha-form-multi_select.ts @@ -1,25 +1,27 @@ +import "@material/mwc-select/mwc-select"; import { mdiMenuDown, mdiMenuUp } from "@mdi/js"; -import "@material/mwc-textfield"; -import "@material/mwc-formfield"; import { css, CSSResultGroup, html, LitElement, - TemplateResult, PropertyValues, + TemplateResult, } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import "../ha-button-menu"; +import { HaCheckListItem } from "../ha-check-list-item"; +import "../ha-checkbox"; +import type { HaCheckbox } from "../ha-checkbox"; +import "../ha-formfield"; import "../ha-svg-icon"; +import "../ha-textfield"; import { HaFormElement, HaFormMultiSelectData, HaFormMultiSelectSchema, } from "./types"; -import "../ha-checkbox"; -import type { HaCheckbox } from "../ha-checkbox"; function optionValue(item: string | string[]): string { return Array.isArray(item) ? item[0] : item; @@ -57,23 +59,23 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement { : Object.entries(this.schema.options); const data = this.data || []; - const renderedOptions = options.map((item: string | [string, string]) => { - const value = optionValue(item); - return html` - - - - `; - }); - // We will just render all checkboxes. if (options.length < SHOW_ALL_ENTRIES_LIMIT) { - return html`
${this.label}${renderedOptions}
`; + return html`
+ ${this.label}${options.map((item: string | [string, string]) => { + const value = optionValue(item); + return html` + + + + `; + })} +
`; } return html` @@ -83,8 +85,10 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement { corner="BOTTOM_START" @opened=${this._handleOpen} @closed=${this._handleClose} + multi + activatable > - + > - ${renderedOptions} + ${options.map((item: string | [string, string]) => { + const value = optionValue(item); + const selected = data.includes(value); + return html` + ${optionLabel(item)} + `; + })} `; } @@ -105,7 +122,7 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement { protected firstUpdated() { this.updateComplete.then(() => { const { formElement, mdcRoot } = - this.shadowRoot?.querySelector("mwc-textfield") || ({} as any); + this.shadowRoot?.querySelector("ha-textfield") || ({} as any); if (formElement) { formElement.style.textOverflow = "ellipsis"; } @@ -125,9 +142,23 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement { } } + private _selectedChanged(ev: CustomEvent): void { + ev.stopPropagation(); + if (ev.detail.source === "property") { + return; + } + this._handleValueChanged( + (ev.target as HaCheckListItem).value, + ev.detail.selected + ); + } + private _valueChanged(ev: CustomEvent): void { const { value, checked } = ev.target as HaCheckbox; + this._handleValueChanged(value, checked); + } + private _handleValueChanged(value, checked: boolean): void { let newValue: string[]; if (checked) { @@ -171,11 +202,11 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement { display: block; cursor: pointer; } - mwc-formfield { + ha-formfield { display: block; padding-right: 16px; } - mwc-textfield { + ha-textfield { display: block; pointer-events: none; } diff --git a/src/components/ha-form/ha-form-string.ts b/src/components/ha-form/ha-form-string.ts index 22085ab2a5..e82a96519e 100644 --- a/src/components/ha-form/ha-form-string.ts +++ b/src/components/ha-form/ha-form-string.ts @@ -1,17 +1,17 @@ import { mdiEye, mdiEyeOff } from "@mdi/js"; -import "@material/mwc-textfield"; -import type { TextField } from "@material/mwc-textfield"; import { css, CSSResultGroup, html, LitElement, - TemplateResult, PropertyValues, + TemplateResult, } from "lit"; -import { customElement, property, state, query } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import "../ha-icon-button"; +import "../ha-textfield"; +import type { HaTextField } from "../ha-textfield"; import type { HaFormElement, HaFormStringData, @@ -32,7 +32,7 @@ export class HaFormString extends LitElement implements HaFormElement { @state() private _unmaskedPassword = false; - @query("mwc-textfield") private _input?: HTMLElement; + @query("ha-textfield") private _input?: HaTextField; public focus(): void { if (this._input) { @@ -45,7 +45,7 @@ export class HaFormString extends LitElement implements HaFormElement { this.schema.name.includes(field) ); return html` - + > ${isPassword ? html`

${this.localize("ui.components.qr-scanner.manual_input")}

- + > ${this.localize("ui.common.submit")} @@ -161,7 +161,7 @@ class HaQrScanner extends LitElement { private _manualKeyup(ev: KeyboardEvent) { if (ev.key === "Enter") { - this._qrCodeScanned((ev.target as TextField).value); + this._qrCodeScanned((ev.target as HaTextField).value); } } @@ -199,7 +199,7 @@ class HaQrScanner extends LitElement { display: flex; align-items: center; } - mwc-textfield { + ha-textfield { flex: 1; margin-right: 8px; } diff --git a/src/components/ha-radio.ts b/src/components/ha-radio.ts index a2fb2b5929..f551d4fedc 100644 --- a/src/components/ha-radio.ts +++ b/src/components/ha-radio.ts @@ -1,12 +1,18 @@ -import { Radio } from "@material/mwc-radio"; +import { RadioBase } from "@material/mwc-radio/mwc-radio-base"; +import { styles } from "@material/mwc-radio/mwc-radio.css"; +import { css } from "lit"; import { customElement } from "lit/decorators"; @customElement("ha-radio") -export class HaRadio extends Radio { - public firstUpdated() { - super.firstUpdated(); - this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)"); - } +export class HaRadio extends RadioBase { + static override styles = [ + styles, + css` + :host { + --mdc-theme-secondary: var(--primary-color); + } + `, + ]; } declare global { diff --git a/src/components/ha-selector/ha-selector-number.ts b/src/components/ha-selector/ha-selector-number.ts index 9f4d29a765..75d050e03c 100644 --- a/src/components/ha-selector/ha-selector-number.ts +++ b/src/components/ha-selector/ha-selector-number.ts @@ -5,7 +5,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import { NumberSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; import "../ha-slider"; -import "@material/mwc-textfield/mwc-textfield"; +import "../ha-textfield"; @customElement("ha-selector-number") export class HaNumberSelector extends LitElement { @@ -36,7 +36,7 @@ export class HaNumberSelector extends LitElement { > ` : ""} - - `; + `; } private get _value() { - return this.value || 0; + return this.value ?? 0; } private _handleInputChange(ev) { @@ -90,10 +91,11 @@ export class HaNumberSelector extends LitElement { ha-slider { flex: 1; } - mwc-textfield { - width: 70px; + ha-textfield { + --ha-textfield-input-width: 40px; } .single { + --ha-textfield-input-width: unset; flex: 1; } `; diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts index d9a1f87bfc..7431c05833 100644 --- a/src/components/ha-selector/ha-selector-text.ts +++ b/src/components/ha-selector/ha-selector-text.ts @@ -1,5 +1,4 @@ import "@material/mwc-textarea/mwc-textarea"; -import "@material/mwc-textfield/mwc-textfield"; import { mdiEye, mdiEyeOff } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -7,6 +6,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import { StringSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; import "../ha-icon-button"; +import "../ha-textfield"; @customElement("ha-selector-text") export class HaTextSelector extends LitElement { @@ -38,7 +38,7 @@ export class HaTextSelector extends LitElement { required >`; } - return html`
` : this.selector.text?.suffix} required - > + > ${this.selector.text?.type === "password" ? html` { if (this.haptic) { forwardHaptic("light"); @@ -24,29 +20,30 @@ export class HaSwitch extends Switch { }); } - static get styles(): CSSResultGroup { - return [ - Switch.styles, - css` - .mdc-switch.mdc-switch--checked .mdc-switch__thumb { - background-color: var(--switch-checked-button-color); - border-color: var(--switch-checked-button-color); - } - .mdc-switch.mdc-switch--checked .mdc-switch__track { - background-color: var(--switch-checked-track-color); - border-color: var(--switch-checked-track-color); - } - .mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb { - background-color: var(--switch-unchecked-button-color); - border-color: var(--switch-unchecked-button-color); - } - .mdc-switch:not(.mdc-switch--checked) .mdc-switch__track { - background-color: var(--switch-unchecked-track-color); - border-color: var(--switch-unchecked-track-color); - } - `, - ]; - } + static override styles = [ + styles, + css` + :host { + --mdc-theme-secondary: var(--switch-checked-color); + } + .mdc-switch.mdc-switch--checked .mdc-switch__thumb { + background-color: var(--switch-checked-button-color); + border-color: var(--switch-checked-button-color); + } + .mdc-switch.mdc-switch--checked .mdc-switch__track { + background-color: var(--switch-checked-track-color); + border-color: var(--switch-checked-track-color); + } + .mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb { + background-color: var(--switch-unchecked-button-color); + border-color: var(--switch-unchecked-button-color); + } + .mdc-switch:not(.mdc-switch--checked) .mdc-switch__track { + background-color: var(--switch-unchecked-track-color); + border-color: var(--switch-unchecked-track-color); + } + `, + ]; } declare global { diff --git a/src/components/ha-textfield.ts b/src/components/ha-textfield.ts index c5e0e21a90..8ca6992f2d 100644 --- a/src/components/ha-textfield.ts +++ b/src/components/ha-textfield.ts @@ -1,9 +1,10 @@ -import { TextField } from "@material/mwc-textfield"; -import { TemplateResult, html, PropertyValues } from "lit"; +import { TextFieldBase } from "@material/mwc-textfield/mwc-textfield-base"; +import { styles } from "@material/mwc-textfield/mwc-textfield.css"; +import { TemplateResult, html, PropertyValues, css } from "lit"; import { customElement, property } from "lit/decorators"; @customElement("ha-textfield") -export class HaTextField extends TextField { +export class HaTextField extends TextFieldBase { @property({ type: Boolean }) public invalid?: boolean; @property({ attribute: "error-message" }) public errorMessage?: string; @@ -37,6 +38,15 @@ export class HaTextField extends TextField { `; } + + static override styles = [ + styles, + css` + .mdc-text-field__input { + width: var(--ha-textfield-input-width, 100%); + } + `, + ]; } declare global { diff --git a/src/panels/config/cloud/account/cloud-google-pref.ts b/src/panels/config/cloud/account/cloud-google-pref.ts index 7e605d165d..3e7646911d 100644 --- a/src/panels/config/cloud/account/cloud-google-pref.ts +++ b/src/panels/config/cloud/account/cloud-google-pref.ts @@ -1,12 +1,12 @@ import "@material/mwc-button"; -import "@material/mwc-textfield/mwc-textfield"; -import type { TextField } from "@material/mwc-textfield/mwc-textfield"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-alert"; import "../../../../components/ha-card"; import type { HaSwitch } from "../../../../components/ha-switch"; +import "../../../../components/ha-textfield"; +import type { HaTextField } from "../../../../components/ha-textfield"; import { CloudStatusLoggedIn, updateCloudPref } from "../../../../data/cloud"; import { syncCloudGoogleEntities } from "../../../../data/google_assistant"; import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box"; @@ -136,7 +136,7 @@ export class CloudGooglePref extends LitElement { ${this.hass.localize( "ui.panel.config.cloud.account.google.enter_pin_info" )} - + > `} @@ -229,7 +229,7 @@ export class CloudGooglePref extends LitElement { } private async _pinChanged(ev) { - const input = ev.target as TextField; + const input = ev.target as HaTextField; try { await updateCloudPref(this.hass, { [input.id]: input.value || null, @@ -260,7 +260,7 @@ export class CloudGooglePref extends LitElement { right: auto; left: 24px; } - mwc-textfield { + ha-textfield { width: 250px; display: block; margin-top: 8px; diff --git a/src/panels/config/cloud/forgot-password/cloud-forgot-password.ts b/src/panels/config/cloud/forgot-password/cloud-forgot-password.ts index a6b34b9054..003ef7ae66 100644 --- a/src/panels/config/cloud/forgot-password/cloud-forgot-password.ts +++ b/src/panels/config/cloud/forgot-password/cloud-forgot-password.ts @@ -1,11 +1,11 @@ -import "@material/mwc-textfield/mwc-textfield"; -import type { TextField } from "@material/mwc-textfield/mwc-textfield"; import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/buttons/ha-progress-button"; import "../../../../components/ha-alert"; import "../../../../components/ha-card"; +import type { HaTextField } from "../../../../components/ha-textfield"; +import "../../../../components/ha-textfield"; import { cloudForgotPassword } from "../../../../data/cloud"; import "../../../../layouts/hass-subpage"; import { haStyle } from "../../../../resources/styles"; @@ -23,7 +23,7 @@ export class CloudForgotPassword extends LitElement { @state() private _error?: string; - @query("#email", true) private _emailField!: TextField; + @query("#email", true) private _emailField!: HaTextField; protected render(): TemplateResult { return html` @@ -49,7 +49,7 @@ export class CloudForgotPassword extends LitElement { ${this._error ? html`${this._error}` : ""} - + >
${this._error}` : ""} - - + + >
- - ${this.hass!.localize( "ui.panel.config.devices.picker.filter.show_disabled" )} - + `; diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 7c03fd89f3..8c8bcec0d7 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -1,4 +1,3 @@ -import "@material/mwc-list/mwc-list-item"; import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item"; import { mdiAlertCircle, @@ -35,6 +34,7 @@ import type { import "../../../components/ha-button-menu"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; +import "../../../components/ha-check-list-item"; import { AreaRegistryEntry, subscribeAreaRegistry, @@ -586,45 +586,35 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { )} .path=${mdiFilterVariant} > - - ${this.hass!.localize( "ui.panel.config.entities.picker.filter.show_disabled" )} - - + - ${this.hass!.localize( "ui.panel.config.entities.picker.filter.show_unavailable" )} - - + - ${this.hass!.localize( "ui.panel.config.entities.picker.filter.show_readonly" )} - + `} ${includeZHAFab ? html` diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 7aa599d8a2..3c1879e1c5 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -1,5 +1,4 @@ import { ActionDetail } from "@material/mwc-list"; -import "@material/mwc-list/mwc-list-item"; import { mdiFilterVariant, mdiPlus } from "@mdi/js"; import Fuse from "fuse.js"; import type { UnsubscribeFunc } from "home-assistant-js-websocket"; @@ -26,6 +25,8 @@ import "../../../components/ha-checkbox"; import "../../../components/ha-fab"; import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; +import "../../../components/ha-check-list-item"; + import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { ConfigEntry, getConfigEntries } from "../../../data/config_entries"; import { @@ -308,21 +309,16 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { .path=${mdiFilterVariant} > - - + ${this.hass.localize( "ui.panel.config.integrations.ignore.show_ignored" )} - - - + + ${this.hass.localize( "ui.panel.config.integrations.disable.show_disabled" )} - + `; return html` diff --git a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts index 82a7ebda77..4c9603ba0a 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/dialog-zwave_js-add-node.ts @@ -1,5 +1,4 @@ import "@material/mwc-button/mwc-button"; -import "@material/mwc-textfield/mwc-textfield"; import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import type { PaperInputElement } from "@polymer/paper-input/paper-input"; @@ -811,10 +810,6 @@ class DialogZWaveJSAddNode extends LitElement { } } - mwc-textfield { - width: 100%; - } - ha-svg-icon { width: 68px; height: 48px; From 5435218187b5086e13bdb98fcab4c75749dc2e7e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 9 Feb 2022 17:58:37 +0100 Subject: [PATCH 027/174] Make textarea grow on input (#11618) --- .../ha-selector/ha-selector-text.ts | 11 ++-- src/components/ha-textarea.ts | 60 +++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 src/components/ha-textarea.ts diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts index 7431c05833..565ef8889e 100644 --- a/src/components/ha-selector/ha-selector-text.ts +++ b/src/components/ha-selector/ha-selector-text.ts @@ -1,4 +1,3 @@ -import "@material/mwc-textarea/mwc-textarea"; import { mdiEye, mdiEyeOff } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -6,6 +5,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import { StringSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; import "../ha-icon-button"; +import "../ha-textarea"; import "../ha-textfield"; @customElement("ha-selector-text") @@ -26,7 +26,7 @@ export class HaTextSelector extends LitElement { protected render() { if (this.selector.text?.multiline) { - return html``; + autogrow + >`; } return html` Date: Wed, 9 Feb 2022 18:20:25 +0100 Subject: [PATCH 028/174] Update lit-virtualizer (#11623) --- .../patches/@lit-labs/virtualizer/0.7.0.patch | 1536 ----------------- .../virtualizer/event-target-shim.patch | 18 +- package.json | 2 +- src/components/data-table/ha-data-table.ts | 187 +- src/dialogs/quick-bar/ha-quick-bar.ts | 65 +- src/panels/logbook/ha-logbook.ts | 32 +- yarn.lock | 22 +- 7 files changed, 166 insertions(+), 1696 deletions(-) delete mode 100644 .yarn/patches/@lit-labs/virtualizer/0.7.0.patch diff --git a/.yarn/patches/@lit-labs/virtualizer/0.7.0.patch b/.yarn/patches/@lit-labs/virtualizer/0.7.0.patch deleted file mode 100644 index d7c1a6d703..0000000000 --- a/.yarn/patches/@lit-labs/virtualizer/0.7.0.patch +++ /dev/null @@ -1,1536 +0,0 @@ -diff --git a/CHANGELOG.md b/CHANGELOG.md -index c5ea17be2de03bbfe82bcc30aa28f7035cc93da0..0a21c3a1d32f5b8a2068c89df83f1b84ce4abbda 100644 ---- a/CHANGELOG.md -+++ b/CHANGELOG.md -@@ -1,16 +1,24 @@ -+## [0.7.0] - 2021-05-26 -+### Changed -+- Made significant changes and improvements to TypeScript types (work ongoing) -+ -+### Fixed -+- Fixed scrolling issue on iOS ([#54](https://github.com/PolymerLabs/uni-virtualizer/issues/54)) -+ - ## [0.6.0] - 2021-05-01 - - This is a stopgap release to unblock migrations to Lit 2.0 --- In the near future: -- - Source will move to the Lit monorepo -- - Subsequent releases will likely be as `@lit-labs/virtualizer` -+- In the near future, source will move to the Lit monorepo - - ### Changed - - Migrated to Lit 2.x - - ## [0.5.0] - 2021-05-01 - ### Changed -+- Changed npm package to `@lit-labs/virtualizer` - - Significant refactoring - - Now emits custom events, access data from `detail` object -+- `layout` property is now required for both the `lit-virtualizer` -+ element and the `scroll()` directive - - ### Added - - Support for older browsers (IE11, legacy Edge) -diff --git a/README.md b/README.md -index 86e6610be14ed8f375074a628298e8e9c8c50823..0ddf6a9d89e1a73514e0d11e92b8d007f864f887 100644 ---- a/README.md -+++ b/README.md -@@ -182,7 +182,7 @@ const handleEvent = (e) => { - } - - const example = (contacts) => html` --
-+
- ${scroll({ - items: contacts, - renderItem: ({ mediumText }) => html`

${mediumText}

`, -diff --git a/lib/lit-virtualizer.d.ts b/lib/lit-virtualizer.d.ts -index d6a63bd658542fee62c36f8658fc3561ab61d2e8..4c93ff899f483d4824d24024b0803c8838150d0a 100644 ---- a/lib/lit-virtualizer.d.ts -+++ b/lib/lit-virtualizer.d.ts -@@ -1,5 +1,5 @@ - import { LitElement, TemplateResult } from 'lit'; --import { Type, Layout, LayoutConfig } from './uni-virtualizer/lib/layouts/Layout.js'; -+import { LayoutSpecifier, Layout, LayoutConstructor } from './uni-virtualizer/lib/layouts/Layout.js'; - /** - * A LitElement wrapper of the scroll directive. - * -@@ -7,19 +7,19 @@ import { Type, Layout, LayoutConfig } from './uni-virtualizer/lib/layouts/Layout - * Pass an items array, renderItem method, and scroll target as properties - * to the element. - */ --export declare class LitVirtualizer extends LitElement { -- renderItem: (item: Item, index?: number) => TemplateResult; -- items: Array; -+export declare class LitVirtualizer extends LitElement { -+ renderItem?: ((item: any, index?: number) => TemplateResult); -+ items: Array; - scrollTarget: Element | Window; -- keyFunction: (item: any) => any; -+ keyFunction: ((item: unknown) => unknown) | undefined; - private _layout; - private _scrollToIndex; - createRenderRoot(): this; - /** - * The method used for rendering each item. - */ -- set layout(layout: Layout | Type | LayoutConfig); -- get layout(): Layout | Type | LayoutConfig; -+ set layout(layout: Layout | LayoutConstructor | LayoutSpecifier | null); -+ get layout(): Layout | LayoutConstructor | LayoutSpecifier | null; - /** - * Scroll to the specified index, placing that item at the given position - * in the scroll view. -@@ -29,7 +29,7 @@ export declare class LitVirtualizer extends LitElement { - } - declare global { - interface HTMLElementTagNameMap { -- 'lit-virtualizer': LitVirtualizer; -+ 'lit-virtualizer': LitVirtualizer; - } - } - //# sourceMappingURL=lit-virtualizer.d.ts.map -\ No newline at end of file -diff --git a/lib/lit-virtualizer.d.ts.map b/lib/lit-virtualizer.d.ts.map -index 65ca47b388981b8454619ef71c1eade0c847b6da..763404320ecd414b07e98aee537f55aeacb223ba 100644 ---- a/lib/lit-virtualizer.d.ts.map -+++ b/lib/lit-virtualizer.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"lit-virtualizer.d.ts","sourceRoot":"","sources":["../src/lib/lit-virtualizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAKvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAErF;;;;;;GAMG;AACH,qBACa,cAAc,CAAC,IAAI,CAAE,SAAQ,UAAU;IAEhD,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,cAAc,CAAC;IAG3D,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAGnB,YAAY,EAAE,OAAO,GAAG,MAAM,CAAQ;IAGtC,WAAW,EAAE,CAAC,IAAI,EAAC,GAAG,KAAK,GAAG,CAAC;IAE/B,OAAO,CAAC,OAAO,CAAsC;IAErD,OAAO,CAAC,cAAc,CAAoC;IAE1D,gBAAgB;IAahB;;OAEG;IAWH,IACI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,YAAY,EAItD;IAED,IAAI,MAAM,IANS,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,YAAY,CAQtD;IAGD;;;OAGG;IACG,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAgB;IAO7D,MAAM,IAAI,cAAc;CAO3B;AAED,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,iBAAiB,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;KAC9C;CACJ"} -\ No newline at end of file -+{"version":3,"file":"lit-virtualizer.d.ts","sourceRoot":"","sources":["../src/lib/lit-virtualizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAKvD,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,yCAAyC,CAAC;AAErG;;;;;;GAMG;AACH,qBACa,cAAe,SAAQ,UAAU;IAE1C,UAAU,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,cAAc,CAAC,CAAC;IAG7D,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAM;IAG3B,YAAY,EAAE,OAAO,GAAG,MAAM,CAAQ;IAGtC,WAAW,EAAE,CAAC,CAAC,IAAI,EAAC,OAAO,KAAK,OAAO,CAAC,GAAG,SAAS,CAAa;IAEjE,OAAO,CAAC,OAAO,CAA6D;IAE5E,OAAO,CAAC,cAAc,CAAkD;IAExE,gBAAgB;IAahB;;OAEG;IAWH,IACI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,GAAG,eAAe,GAAG,IAAI,EAIrE;IAED,IAAI,MAAM,IAAI,MAAM,GAAG,iBAAiB,GAAG,eAAe,GAAG,IAAI,CAEhE;IAGD;;;OAGG;IACG,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAgB;IAO7D,MAAM,IAAI,cAAc;CAO3B;AAED,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,qBAAqB;QAC3B,iBAAiB,EAAE,cAAc,CAAC;KACrC;CACJ"} -\ No newline at end of file -diff --git a/lib/lit-virtualizer.js b/lib/lit-virtualizer.js -index d7846882e0c98212ee4be144728f26a4c7736fe8..7fb00024c2df2290559fc78db1ad13faf86a2c62 100644 ---- a/lib/lit-virtualizer.js -+++ b/lib/lit-virtualizer.js -@@ -1,4 +1,9 @@ --import { __decorate } from "tslib"; -+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { -+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; -+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); -+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; -+ return c > 3 && r && Object.defineProperty(target, key, r), r; -+}; - import { html, LitElement } from 'lit'; - import { customElement } from 'lit/decorators/custom-element.js'; - import { property } from 'lit/decorators/property.js'; -@@ -14,7 +19,11 @@ import { scrollerRef } from './uni-virtualizer/lib/VirtualScroller.js'; - let LitVirtualizer = class LitVirtualizer extends LitElement { - constructor() { - super(...arguments); -+ this.items = []; - this.scrollTarget = this; -+ this.keyFunction = undefined; -+ this._layout = null; -+ this._scrollToIndex = null; - } - createRenderRoot() { - return this; -@@ -83,3 +92,4 @@ LitVirtualizer = __decorate([ - customElement('lit-virtualizer') - ], LitVirtualizer); - export { LitVirtualizer }; -+//# sourceMappingURL=lit-virtualizer.js.map -\ No newline at end of file -diff --git a/lib/lit-virtualizer.js.map b/lib/lit-virtualizer.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..54c885bd148d6bae8f5de85e3417a97a99035d24 ---- /dev/null -+++ b/lib/lit-virtualizer.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"lit-virtualizer.js","sourceRoot":"","sources":["../src/lib/lit-virtualizer.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAkB,MAAM,KAAK,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,WAAW,EAAoB,MAAM,0CAA0C,CAAC;AAGzF;;;;;;GAMG;AAEH,IAAa,cAAc,GAA3B,MAAa,cAAe,SAAQ,UAAU;IAA9C;;QAKI,UAAK,GAAmB,EAAE,CAAC;QAG3B,iBAAY,GAAqB,IAAI,CAAC;QAGtC,gBAAW,GAA4C,SAAS,CAAC;QAEzD,YAAO,GAAwD,IAAI,CAAC;QAEpE,mBAAc,GAA6C,IAAI,CAAC;IA0D5E,CAAC;IAxDG,gBAAgB;QACZ,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,gBAAgB;IAChB,0BAA0B;IAC1B,IAAI;IAEJ,qBAAqB;IACrB,2BAA2B;IAC3B,gDAAgD;IAChD,IAAI;IAEJ;;OAEG;IACH,qBAAqB;IACrB,+BAA+B;IAC/B,IAAI;IACJ,+BAA+B;IAC/B,4CAA4C;IAC5C,yCAAyC;IACzC,gCAAgC;IAChC,QAAQ;IACR,IAAI;IAGJ,IAAI,MAAM,CAAC,MAA2D;QAClE,qDAAqD;QACrD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,aAAa,EAAE,CAAC;IACzB,CAAC;IAED,IAAI,MAAM;QACN,OAAQ,IAAyB,CAAC,WAAW,CAAE,CAAC,MAAM,CAAC;IAC3D,CAAC;IAGD;;;OAGG;IACH,KAAK,CAAC,aAAa,CAAC,KAAa,EAAE,WAAmB,OAAO;QACzD,IAAI,CAAC,cAAc,GAAG,EAAC,KAAK,EAAE,QAAQ,EAAC,CAAC;QACxC,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,MAAM,IAAI,CAAC,cAAc,CAAC;QAC1B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;IAC/B,CAAC;IAED,MAAM;QACF,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;QAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,OAAO,IAAI,CAAA;cACL,MAAM,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;SACzG,CAAC;IACN,CAAC;CACJ,CAAA;AAvEG;IADC,QAAQ,EAAE;kDACkD;AAG7D;IADC,QAAQ,CAAC,EAAC,SAAS,EAAE,KAAK,EAAC,CAAC;6CACF;AAG3B;IADC,QAAQ,CAAC,EAAC,SAAS,EAAE,KAAK,EAAC,CAAC;oDACS;AAGtC;IADC,QAAQ,EAAE;mDACsD;AAiCjE;IADC,QAAQ,CAAC,EAAC,SAAS,EAAC,KAAK,EAAC,CAAC;4CAK3B;AAhDQ,cAAc;IAD1B,aAAa,CAAC,iBAAiB,CAAC;GACpB,cAAc,CAyE1B;SAzEY,cAAc","sourcesContent":["import { html, LitElement, TemplateResult } from 'lit';\nimport { customElement } from 'lit/decorators/custom-element.js';\nimport { property } from 'lit/decorators/property.js';\nimport { scroll } from './scroll.js';\nimport { scrollerRef, ContainerElement } from './uni-virtualizer/lib/VirtualScroller.js';\nimport { LayoutSpecifier, Layout, LayoutConstructor } from './uni-virtualizer/lib/layouts/Layout.js';\n\n/**\n * A LitElement wrapper of the scroll directive.\n *\n * Import this module to declare the lit-virtualizer custom element.\n * Pass an items array, renderItem method, and scroll target as properties\n * to the element.\n */\n@customElement('lit-virtualizer')\nexport class LitVirtualizer extends LitElement {\n @property()\n renderItem?: ((item: any, index?: number) => TemplateResult);\n\n @property({attribute: false})\n items: Array = [];\n\n @property({attribute: false})\n scrollTarget: Element | Window = this;\n\n @property()\n keyFunction: ((item:unknown) => unknown) | undefined = undefined;\n\n private _layout: Layout | LayoutConstructor | LayoutSpecifier | null = null;\n\n private _scrollToIndex: {index: number, position: string} | null = null;\n \n createRenderRoot() {\n return this;\n }\n\n // get items() {\n // return this._items;\n // }\n\n // set items(items) {\n // this._items = items;\n // this._scroller.totalItems = items.length;\n // }\n\n /**\n * The method used for rendering each item.\n */\n // get renderItem() {\n // return this._renderItem;\n // }\n // set renderItem(renderItem) {\n // if (renderItem !== this.renderItem) {\n // this._renderItem = renderItem;\n // this.requestUpdate();\n // }\n // }\n\n @property({attribute:false})\n set layout(layout: Layout | LayoutConstructor | LayoutSpecifier | null) {\n // TODO (graynorton): Shouldn't have to set this here\n this._layout = layout;\n this.requestUpdate();\n }\n\n get layout(): Layout | LayoutConstructor | LayoutSpecifier | null {\n return (this as ContainerElement)[scrollerRef]!.layout;\n }\n \n \n /**\n * Scroll to the specified index, placing that item at the given position\n * in the scroll view.\n */\n async scrollToIndex(index: number, position: string = 'start') {\n this._scrollToIndex = {index, position};\n this.requestUpdate();\n await this.updateComplete;\n this._scrollToIndex = null;\n }\n\n render(): TemplateResult {\n const { items, renderItem, keyFunction, scrollTarget } = this;\n const layout = this._layout;\n return html`\n ${scroll({ items, renderItem, layout, keyFunction, scrollTarget, scrollToIndex: this._scrollToIndex })}\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'lit-virtualizer': LitVirtualizer;\n }\n}"]} -\ No newline at end of file -diff --git a/lib/scroll.d.ts b/lib/scroll.d.ts -index bca0558061c88063428fcb2e8468a86b31c31157..b75789ee00a5abbdae3a15a2bd2392a5b7a213bb 100644 ---- a/lib/scroll.d.ts -+++ b/lib/scroll.d.ts -@@ -1,19 +1,19 @@ - import { TemplateResult, ChildPart } from 'lit'; - import { PartInfo } from 'lit/directive.js'; - import { AsyncDirective } from 'lit/async-directive.js'; --import { Type, Layout, LayoutConfig } from './uni-virtualizer/lib/layouts/Layout.js'; --import { VirtualScroller } from './uni-virtualizer/lib/VirtualScroller.js'; -+import { Layout, LayoutConstructor, LayoutSpecifier } from './uni-virtualizer/lib/layouts/Layout.js'; -+import { VirtualScroller, ScrollToIndexValue } from './uni-virtualizer/lib/VirtualScroller.js'; - /** - * Configuration options for the scroll directive. - */ --interface ScrollConfig { -+interface ScrollConfig { - /** - * A function that returns a lit-html TemplateResult. It will be used - * to generate the DOM for each item in the virtual list. - */ -- renderItem?: (item: Item, index?: number) => TemplateResult; -- keyFunction?: (item: any) => any; -- layout?: Layout | Type | LayoutConfig; -+ renderItem?: (item: any, index?: number) => TemplateResult; -+ keyFunction?: (item: any) => unknown; -+ layout?: Layout | LayoutConstructor | LayoutSpecifier | null; - /** - * An element that receives scroll events for the virtual scroller. - */ -@@ -21,7 +21,7 @@ interface ScrollConfig { - /** - * The list of items to display via the renderItem function. - */ -- items?: Array; -+ items?: Array; - /** - * Limit for the number of items to display. Defaults to the length of the - * items array. -@@ -30,24 +30,23 @@ interface ScrollConfig { - /** - * Index and position of the item to scroll to. - */ -- scrollToIndex?: { -- index: number; -- position?: string; -- }; -+ scrollToIndex?: ScrollToIndexValue; - } -+export declare const defaultKeyFunction: (item: any) => any; -+export declare const defaultRenderItem: (item: any) => TemplateResult<1>; - declare class ScrollDirective extends AsyncDirective { -- container: HTMLElement; -- scroller: VirtualScroller; -+ container: HTMLElement | null; -+ scroller: VirtualScroller | null; - first: number; - last: number; - renderItem: (item: any, index?: number) => TemplateResult; -- keyFunction: (item: any) => any; -- items: Array; -+ keyFunction: (item: any) => unknown; -+ items: Array; - constructor(part: PartInfo); -- render(config?: ScrollConfig): unknown; -- update(part: ChildPart, [config]: [ScrollConfig]): unknown; -+ render(config?: ScrollConfig): unknown; -+ update(part: ChildPart, [config]: [ScrollConfig]): unknown; - private _initialize; - } --export declare const scroll: (config?: ScrollConfig) => import("lit/directive.js").DirectiveResult; -+export declare const scroll: (config?: ScrollConfig | undefined) => import("lit-html/directive").DirectiveResult; - export {}; - //# sourceMappingURL=scroll.d.ts.map -\ No newline at end of file -diff --git a/lib/scroll.d.ts.map b/lib/scroll.d.ts.map -index 64ae5c72b0d1342ef2018721bc93362b9ed1c9ff..5278da9ef97db92a43e22caa6a644990a194e313 100644 ---- a/lib/scroll.d.ts.map -+++ b/lib/scroll.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"scroll.d.ts","sourceRoot":"","sources":["../src/lib/scroll.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAW,SAAS,EAAE,MAAM,KAAK,CAAC;AACzD,OAAO,EAAa,QAAQ,EAAY,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,EAAE,eAAe,EAAoB,MAAM,0CAA0C,CAAC;AAE7F;;GAEG;AACH,UAAU,YAAY,CAAC,IAAI;IACvB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,cAAc,CAAC;IAE5D,WAAW,CAAC,EAAE,CAAC,IAAI,EAAC,GAAG,KAAK,GAAG,CAAC;IAGhC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC;IAE9C;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAEhC;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAEpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;CACpD;AAIH,cAAM,eAAgB,SAAQ,cAAc;IACxC,SAAS,EAAE,WAAW,CAAA;IACtB,QAAQ,EAAE,eAAe,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IAC/C,KAAK,EAAE,MAAM,CAAI;IACjB,IAAI,EAAE,MAAM,CAAK;IACjB,UAAU,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,cAAc,CAAA;IACzD,WAAW,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,CAAA;IAC/B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAA;gBAEL,IAAI,EAAE,QAAQ;IAO1B,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;IAclC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAetD,OAAO,CAAC,WAAW;CAiBtB;AAED,eAAO,MAAM,MAAM,wGAA6B,CAAC"} -\ No newline at end of file -+{"version":3,"file":"scroll.d.ts","sourceRoot":"","sources":["../src/lib/scroll.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAW,SAAS,EAAQ,MAAM,KAAK,CAAC;AAC/D,OAAO,EAAa,QAAQ,EAAY,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AACrG,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAE/F;;GAEG;AACH,UAAU,YAAY;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,cAAc,CAAC;IAE3D,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC;IAGrC,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,GAAG,eAAe,GAAG,IAAI,CAAC;IAE7D;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAEhC;;OAEG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAEnB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAEH,eAAO,MAAM,kBAAkB,SAAU,GAAG,QAAS,CAAC;AACtD,eAAO,MAAM,iBAAiB,SAAU,GAAG,sBAA2C,CAAC;AAEvF,cAAM,eAAgB,SAAQ,cAAc;IACxC,SAAS,EAAE,WAAW,GAAG,IAAI,CAAO;IACpC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAO;IACvC,KAAK,SAAI;IACT,IAAI,SAAK;IACT,UAAU,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,cAAc,CAAqB;IAC9E,WAAW,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAsB;IACzD,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAK;gBAEd,IAAI,EAAE,QAAQ;IAO1B,MAAM,CAAC,MAAM,CAAC,EAAE,YAAY;IAc5B,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC;IAehD,OAAO,CAAC,WAAW;CAiBtB;AAED,eAAO,MAAM,MAAM,6GAA6B,CAAC"} -\ No newline at end of file -diff --git a/lib/scroll.js b/lib/scroll.js -index 9a0a5b4133c8a00def065e2c611db3d93b7208b0..33fd6bb63b352c8a49b057cce761280af45a57b7 100644 ---- a/lib/scroll.js -+++ b/lib/scroll.js -@@ -1,22 +1,28 @@ --import { nothing } from 'lit'; -+import { nothing, html } from 'lit'; - import { directive, PartType } from 'lit/directive.js'; - import { AsyncDirective } from 'lit/async-directive.js'; - import { repeat } from 'lit/directives/repeat.js'; - import { VirtualScroller } from './uni-virtualizer/lib/VirtualScroller.js'; --const defaultKeyFunction = item => item; -+export const defaultKeyFunction = (item) => item; -+export const defaultRenderItem = (item) => html `${JSON.stringify(item, null, 2)}`; - class ScrollDirective extends AsyncDirective { - constructor(part) { - super(part); -+ this.container = null; -+ this.scroller = null; - this.first = 0; - this.last = -1; -+ this.renderItem = defaultRenderItem; -+ this.keyFunction = defaultKeyFunction; -+ this.items = []; - if (part.type !== PartType.CHILD) { - throw new Error('The scroll directive can only be used in child expressions'); - } - } - render(config) { - if (config) { -- this.renderItem = config.renderItem; -- this.keyFunction = config.keyFunction; -+ this.renderItem = config.renderItem || this.renderItem; -+ this.keyFunction = config.keyFunction || this.keyFunction; - } - const itemsToRender = []; - if (this.first >= 0 && this.last >= this.first) { -@@ -30,9 +36,9 @@ class ScrollDirective extends AsyncDirective { - var _a; - if (this.scroller || this._initialize(part, config)) { - const { scroller } = this; -- this.items = scroller.items = config.items; -+ this.items = scroller.items = config.items || []; - scroller.totalItems = config.totalItems || ((_a = config.items) === null || _a === void 0 ? void 0 : _a.length) || 0; -- scroller.layout = config.layout; -+ scroller.layout = config.layout || null; - scroller.scrollTarget = config.scrollTarget || this.container; - if (config.scrollToIndex) { - scroller.scrollToIndex = config.scrollToIndex; -@@ -60,3 +66,4 @@ class ScrollDirective extends AsyncDirective { - } - } - export const scroll = directive(ScrollDirective); -+//# sourceMappingURL=scroll.js.map -\ No newline at end of file -diff --git a/lib/scroll.js.map b/lib/scroll.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..4cf7e26beb7d9c52e8af93dc789f96a06756123a ---- /dev/null -+++ b/lib/scroll.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"scroll.js","sourceRoot":"","sources":["../src/lib/scroll.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,OAAO,EAAa,IAAI,EAAE,MAAM,KAAK,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAY,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAE,eAAe,EAAsB,MAAM,0CAA0C,CAAC;AAuC/F,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC;AACtD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAA,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;AAEvF,MAAM,eAAgB,SAAQ,cAAc;IASxC,YAAY,IAAc;QACtB,KAAK,CAAC,IAAI,CAAC,CAAC;QAThB,cAAS,GAAuB,IAAI,CAAA;QACpC,aAAQ,GAA2B,IAAI,CAAA;QACvC,UAAK,GAAG,CAAC,CAAA;QACT,SAAI,GAAG,CAAC,CAAC,CAAA;QACT,eAAU,GAAkD,iBAAiB,CAAC;QAC9E,gBAAW,GAA2B,kBAAkB,CAAC;QACzD,UAAK,GAAmB,EAAE,CAAA;QAItB,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,KAAK,EAAE;YAC9B,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;SACjF;IACL,CAAC;IAED,MAAM,CAAC,MAAqB;QACxB,IAAI,MAAM,EAAE;YACR,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC;YACvD,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC;SAC7D;QACD,MAAM,aAAa,GAAG,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE;YAC5C,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;gBAC7C,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;aACrC;SACJ;QACD,OAAO,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,WAAW,IAAI,kBAAkB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC1F,CAAC;IAED,MAAM,CAAC,IAAe,EAAE,CAAC,MAAM,CAAiB;;QAC5C,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;YACjD,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,QAAS,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;YAClD,QAAS,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,KAAI,MAAA,MAAM,CAAC,KAAK,0CAAE,MAAM,CAAA,IAAI,CAAC,CAAC;YACtE,QAAS,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC;YACzC,QAAS,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,IAAI,CAAC,SAAS,CAAC;YAC/D,IAAI,MAAM,CAAC,aAAa,EAAE;gBACtB,QAAS,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;aAClD;YACD,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;SAC9B;QACD,OAAO,OAAO,CAAC;IACnB,CAAC;IAEO,WAAW,CAAC,IAAe,EAAE,MAAoB;QACrD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAyB,CAAC;QAClE,IAAI,SAAS,IAAI,SAAS,CAAC,QAAQ,KAAK,CAAC,EAAE;YACvC,IAAI,CAAC,QAAQ,GAAG,IAAI,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;YACnD,SAAS,CAAC,gBAAgB,CAAC,cAAc,EAAE,CAAC,CAAQ,EAAE,EAAE;gBACpD,IAAI,CAAC,KAAK,GAAI,CAAiB,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC7C,IAAI,CAAC,IAAI,GAAI,CAAiB,CAAC,MAAM,CAAC,IAAI,CAAC;gBAC3C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YACjC,CAAC,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;SACf;QACD,oEAAoE;QACpE,wEAAwE;QACxE,yCAAyC;QACzC,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC1D,OAAO,KAAK,CAAC;IACjB,CAAC;CACJ;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,SAAS,CAAC,eAAe,CAAC,CAAC","sourcesContent":["import { TemplateResult, nothing, ChildPart, html } from 'lit';\nimport { directive, PartInfo, PartType } from 'lit/directive.js';\nimport { AsyncDirective } from 'lit/async-directive.js';\nimport { repeat } from 'lit/directives/repeat.js';\nimport { Layout, LayoutConstructor, LayoutSpecifier } from './uni-virtualizer/lib/layouts/Layout.js';\nimport { VirtualScroller, ScrollToIndexValue } from './uni-virtualizer/lib/VirtualScroller.js';\n\n/**\n * Configuration options for the scroll directive.\n */\ninterface ScrollConfig {\n /**\n * A function that returns a lit-html TemplateResult. It will be used\n * to generate the DOM for each item in the virtual list.\n */\n renderItem?: (item: any, index?: number) => TemplateResult;\n\n keyFunction?: (item: any) => unknown;\n \n // TODO (graynorton): Document...\n layout?: Layout | LayoutConstructor | LayoutSpecifier | null;\n \n /**\n * An element that receives scroll events for the virtual scroller.\n */\n scrollTarget?: Element | Window;\n \n /**\n * The list of items to display via the renderItem function.\n */\n items?: Array;\n \n /**\n * Limit for the number of items to display. Defaults to the length of the\n * items array.\n */\n totalItems?: number;\n \n /**\n * Index and position of the item to scroll to.\n */\n scrollToIndex?: ScrollToIndexValue;\n }\n \nexport const defaultKeyFunction = (item: any) => item;\nexport const defaultRenderItem = (item: any) => html`${JSON.stringify(item, null, 2)}`;\n\nclass ScrollDirective extends AsyncDirective {\n container: HTMLElement | null = null\n scroller: VirtualScroller | null = null\n first = 0\n last = -1\n renderItem: (item: any, index?: number) => TemplateResult = defaultRenderItem;\n keyFunction: (item: any) => unknown = defaultKeyFunction;\n items: Array = []\n\n constructor(part: PartInfo) {\n super(part);\n if (part.type !== PartType.CHILD) {\n throw new Error('The scroll directive can only be used in child expressions');\n }\n }\n \n render(config?: ScrollConfig) {\n if (config) {\n this.renderItem = config.renderItem || this.renderItem;\n this.keyFunction = config.keyFunction || this.keyFunction;\n }\n const itemsToRender = [];\n if (this.first >= 0 && this.last >= this.first) {\n for (let i = this.first; i < this.last + 1; i++) {\n itemsToRender.push(this.items[i]);\n } \n }\n return repeat(itemsToRender, this.keyFunction || defaultKeyFunction, this.renderItem);\n }\n\n update(part: ChildPart, [config]: [ScrollConfig]) {\n if (this.scroller || this._initialize(part, config)) {\n const { scroller } = this;\n this.items = scroller!.items = config.items || [];\n scroller!.totalItems = config.totalItems || config.items?.length || 0;\n scroller!.layout = config.layout || null;\n scroller!.scrollTarget = config.scrollTarget || this.container;\n if (config.scrollToIndex) {\n scroller!.scrollToIndex = config.scrollToIndex;\n }\n return this.render(config); \n }\n return nothing;\n }\n\n private _initialize(part: ChildPart, config: ScrollConfig) {\n const container = this.container = part.parentNode as HTMLElement;\n if (container && container.nodeType === 1) {\n this.scroller = new VirtualScroller({ container });\n container.addEventListener('rangeChanged', (e: Event) => {\n this.first = (e as CustomEvent).detail.first;\n this.last = (e as CustomEvent).detail.last;\n this.setValue(this.render());\n });\n return true;\n }\n // TODO (GN): This seems to be needed in the case where the `scroll`\n // directive is used within the `LitVirtualizer` element. Figure out why\n // and see if there's a cleaner solution.\n Promise.resolve().then(() => this.update(part, [config]));\n return false;\n }\n}\n\nexport const scroll = directive(ScrollDirective);"]} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/VirtualScroller.d.ts b/lib/uni-virtualizer/lib/VirtualScroller.d.ts -index 5806fc0ad4e5483f01ad7a1cf2451a31c1f0013a..70fd76efc606d2ff36d5635718ed3a172af6a43b 100644 ---- a/lib/uni-virtualizer/lib/VirtualScroller.d.ts -+++ b/lib/uni-virtualizer/lib/VirtualScroller.d.ts -@@ -1,18 +1,28 @@ --import { ItemBox, Type, Layout, LayoutConfig } from './layouts/Layout.js'; -+import { ItemBox, Layout, LayoutConstructor, LayoutSpecifier } from './layouts/Layout.js'; - export declare const scrollerRef: unique symbol; --declare global { -- interface Window { -- ShadyDOM?: any; -- } --} - export declare type RangeChangeEvent = { - first: number; - last: number; - firstVisible: number; - lastVisible: number; - }; --interface VirtualScrollerConfig { -- layout?: Layout | Type | LayoutConfig; -+interface ElementWithOptionalScrollerRef extends Element { -+ [scrollerRef]?: VirtualScroller; -+} -+interface ShadowRootWithOptionalScrollerRef extends ShadowRoot { -+ [scrollerRef]?: VirtualScroller; -+} -+declare type Container = ElementWithOptionalScrollerRef | ShadowRootWithOptionalScrollerRef; -+export declare type ContainerElement = ElementWithOptionalScrollerRef; -+declare type ChildMeasurements = { -+ [key: number]: ItemBox; -+}; -+export declare type ScrollToIndexValue = { -+ index: number; -+ position?: string; -+} | null; -+export interface VirtualScrollerConfig { -+ layout?: Layout | LayoutConstructor | LayoutSpecifier; - /** - * An element that receives scroll events for the virtual scroller. - */ -@@ -30,7 +40,7 @@ interface VirtualScrollerConfig { - * Extensions of this class must also override VirtualRepeater's DOM - * manipulation methods. - */ --export declare class VirtualScroller { -+export declare class VirtualScroller { - private _benchmarkStart; - /** - * Whether the layout should receive an updated viewport size on the next -@@ -70,7 +80,7 @@ export declare class VirtualScroller { - /** - * Containing element. Set by container. - */ -- protected _container: Element | ShadowRoot; -+ protected _container: Container | null; - /** - * The parent of all child nodes to be rendered. Set by container. - */ -@@ -80,11 +90,6 @@ export declare class VirtualScroller { - * restored when container is changed. - */ - private _containerInlineStyle; -- /** -- * Keep track of original container stylesheet, so it can be restored -- * when container is changed. -- */ -- private _containerStylesheet; - /** - * Size of the container. - */ -@@ -137,12 +142,10 @@ export declare class VirtualScroller { - * measured, and their dimensions passed to this callback. Use it to layout - * children as needed. - */ -- protected _measureCallback: (sizes: { -- [key: number]: ItemBox; -- }) => void; -- protected _measureChildOverride: (element: Element, item: object) => object; -+ protected _measureCallback: ((sizes: ChildMeasurements) => void) | null; -+ protected _measureChildOverride: ((element: Element, item: unknown) => ItemBox) | null; - constructor(config?: VirtualScrollerConfig); -- set items(items: any); -+ set items(items: Array | undefined); - /** - * The total number of items, regardless of the range, that can be rendered - * as child nodes. -@@ -152,15 +155,15 @@ export declare class VirtualScroller { - /** - * The parent of all child nodes to be rendered. - */ -- get container(): Element | ShadowRoot; -- set container(container: Element | ShadowRoot); -- get layout(): Layout | Type | LayoutConfig; -- set layout(layout: Layout | Type | LayoutConfig); -+ get container(): Container | null; -+ set container(container: Container | null); -+ get layout(): Layout | LayoutConstructor | LayoutSpecifier | null; -+ set layout(layout: Layout | LayoutConstructor | LayoutSpecifier | null); - startBenchmarking(): void; - stopBenchmarking(): { - timeElapsed: number; - virtualizationTime: number; -- }; -+ } | null; - private _measureChildren; - /** - * Returns the width, height, and margins of the given child. -@@ -177,19 +180,15 @@ export declare class VirtualScroller { - * Index and position of item to scroll to. The scroller will fix to that point - * until the user scrolls. - */ -- set scrollToIndex(newValue: { -- index: number; -- position?: string; -- }); -- protected _schedule(method: any): Promise; -+ set scrollToIndex(newValue: ScrollToIndexValue); -+ protected _schedule(method: Function): Promise; - _updateDOM(): Promise; - _updateLayout(): void; - private _handleScrollEvent; -- handleEvent(event: any): void; -+ handleEvent(event: CustomEvent): void; - private _initResizeObservers; -- private _applyContainerStyles; - private _createContainerSizer; -- get _children(): Array; -+ get _children(): Array; - private _updateView; - /** - * Styles the _sizer element or the container so that its size reflects the -diff --git a/lib/uni-virtualizer/lib/VirtualScroller.d.ts.map b/lib/uni-virtualizer/lib/VirtualScroller.d.ts.map -index fb02bc5f06fb90647f52463f7797a944ec8da8ba..917e1bf68689debf0f5402d5b5409b43302e2955 100644 ---- a/lib/uni-virtualizer/lib/VirtualScroller.d.ts.map -+++ b/lib/uni-virtualizer/lib/VirtualScroller.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"VirtualScroller.d.ts","sourceRoot":"","sources":["../../../src/lib/uni-virtualizer/lib/VirtualScroller.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAW,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnF,eAAO,MAAM,WAAW,eAAwB,CAAC;AAGjD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACZ,QAAQ,CAAC,EAAE,GAAG,CAAC;KAClB;CACF;AAsCD,oBAAY,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAGF,UAAU,qBAAqB;IAC7B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,YAAY,CAAC;IAE9C;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAEhC;;OAEG;IACH,SAAS,EAAE,OAAO,GAAG,UAAU,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,qBAAa,eAAe,CAAC,IAAI,EAAE,KAAK,SAAS,WAAW;IAC1D,OAAO,CAAC,eAAe,CAAQ;IAC/B;;;OAGG;IAGH,OAAO,CAAC,OAAO,CAAgB;IAE/B;;;OAGG;IACH,OAAO,CAAC,aAAa,CAAwB;IAE7C;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAqB;IAEnC;;;OAGG;IACH,OAAO,CAAC,WAAW,CAA4C;IAE/D;;;OAGG;IACH,OAAO,CAAC,UAAU,CAAqC;IAEvD;;OAEG;IACH,OAAO,CAAC,YAAY,CAA4C;IAGhE,OAAO,CAAC,kBAAkB,CAAa;IAEvC,OAAO,CAAC,aAAa,CAAoC;IAEzD,OAAO,CAAC,aAAa,CAAiB;IAEtC,OAAO,CAAC,aAAa,CAAiB;IAEtC,OAAO,CAAC,kBAAkB,CAAiB;IAE3C;;OAEG;IACH,SAAS,CAAC,UAAU,EAAE,OAAO,GAAG,UAAU,CAAQ;IAElD;;OAEG;IACH,OAAO,CAAC,iBAAiB,CAAiB;IAE1C;;;OAGG;IACH,OAAO,CAAC,qBAAqB,CAAQ;IAErC;;;OAGG;IACH,OAAO,CAAC,oBAAoB,CAAQ;IAEpC;;OAEG;IACH,OAAO,CAAC,cAAc,CAAyC;IAE/D;;OAEG;IACH,OAAO,CAAC,YAAY,CAAwB;IAE5C;;OAEG;IACH,OAAO,CAAC,WAAW,CAAwB;IAE3C,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,wBAAwB,CAAkB;IAClD,OAAO,CAAC,kBAAkB,CAAkB;IAI5C,OAAO,CAAC,aAAa,CAAgC;IAErD;;OAEG;IACH,OAAO,CAAC,cAAc,CAA4C;IAElE;;OAEG;IACH,OAAO,CAAC,MAAM,CAAmB;IAEjC;;OAEG;IACH,OAAO,CAAC,WAAW,CAAgB;IAEnC;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,CAAK;IAE7B;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,CAAK;IAE5B;;OAEG;IACH,OAAO,CAAC,aAAa,CAAS;IAE9B;;OAEG;IACH,OAAO,CAAC,YAAY,CAAS;IAE7B,SAAS,CAAC,UAAU,kBAAiB;IAErC;;;;OAIG;IACH,SAAS,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAC,KAAK,IAAI,CAAQ;IAE7E,SAAS,CAAC,qBAAqB,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAQ;gBAEvE,MAAM,CAAC,EAAE,qBAAqB;IAS1C,IAAI,KAAK,CAAC,KAAK,KAAA,EAMd;IAED;;;OAGG;IACH,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,UAAU,CAAC,GAAG,EAAE,MAAM,EAWzB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,GAAG,UAAU,CAEpC;IAED,IAAI,SAAS,CAAC,SAAS,EAAE,OAAO,GAAG,UAAU,EAoE5C;IAID,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,YAAY,CAEjD;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,YAAY,EAgEtD;IAID,iBAAiB;IAMjB,gBAAgB;;;;IAchB,OAAO,CAAC,gBAAgB;IAgBxB;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO;IAQxC;;;;OAIG;IACH,IAAI,YAAY,IAAI,OAAO,GAAG,MAAM,GAAG,IAAI,CAE1C;IACD,IAAI,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,EAyB/C;IAED;;;OAGG;IACH,IAAI,aAAa,CAAC,QAAQ,EAAE;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAC,EAG7D;cAEe,SAAS,CAAC,MAAM,KAAA,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C,UAAU;IA0BhB,aAAa;IAoBb,OAAO,CAAC,kBAAkB;IAc1B,WAAW,CAAC,KAAK,KAAA;YA4BH,oBAAoB;IAWlC,OAAO,CAAC,qBAAqB;IAqB7B,OAAO,CAAC,qBAAqB;IAgB7B,IAAI,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,CAW5B;IAED,OAAO,CAAC,WAAW;IAiDnB;;;OAGG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;YAqBX,YAAY;IAkB1B,OAAO,CAAC,mBAAmB;IAS3B;;;OAGG;IACH,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,iBAAiB;IAWzB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;YAMf,iBAAiB;IAc/B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,oBAAoB;CAO7B"} -\ No newline at end of file -+{"version":3,"file":"VirtualScroller.d.ts","sourceRoot":"","sources":["../../../src/lib/uni-virtualizer/lib/VirtualScroller.ts"],"names":[],"mappings":"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"} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/VirtualScroller.js b/lib/uni-virtualizer/lib/VirtualScroller.js -index deb122c252aca658b7aeae6edeee9f98de61ee2b..c2cf7431568224fddcd6af51f7352c5a6b116724 100644 ---- a/lib/uni-virtualizer/lib/VirtualScroller.js -+++ b/lib/uni-virtualizer/lib/VirtualScroller.js -@@ -1,28 +1,5 @@ - import getResizeObserver from './polyfillLoaders/ResizeObserver.js'; - export const scrollerRef = Symbol('scrollerRef'); --let nativeShadowDOM = 'attachShadow' in Element.prototype && (!('ShadyDOM' in window) || !window['ShadyDOM'].inUse); --const HOST_CLASSNAME = 'uni-virtualizer-host'; --let globalContainerStylesheet = null; --function containerStyles(hostSel, childSel) { -- return ` -- ${hostSel} { -- display: block; -- position: relative; -- contain: strict; -- height: 150px; -- overflow: auto; -- } -- ${childSel} { -- box-sizing: border-box; -- }`; --} --function attachGlobalContainerStylesheet() { -- if (!globalContainerStylesheet) { -- globalContainerStylesheet = document.createElement('style'); -- globalContainerStylesheet.textContent = containerStyles(`.${HOST_CLASSNAME}`, `.${HOST_CLASSNAME} > *`); -- document.head.appendChild(globalContainerStylesheet); -- } --} - /** - * Provides virtual scrolling boilerplate. - * -@@ -84,11 +61,6 @@ export class VirtualScroller { - * restored when container is changed. - */ - this._containerInlineStyle = null; -- /** -- * Keep track of original container stylesheet, so it can be restored -- * when container is changed. -- */ -- this._containerStylesheet = null; - /** - * Size of the container. - */ -@@ -128,6 +100,14 @@ export class VirtualScroller { - * Index of the last child in the range. - */ - this._last = 0; -+ /** -+ * Index of the first item intersecting the container element. -+ */ -+ this._firstVisible = 0; -+ /** -+ * Index of the last item intersecting the container element. -+ */ -+ this._lastVisible = 0; - this._scheduled = new WeakSet(); - /** - * Invoked at the end of each render cycle: children in the range are -@@ -143,7 +123,7 @@ export class VirtualScroller { - } - } - set items(items) { -- if (items !== this._items) { -+ if (Array.isArray(items) && items !== this._items) { - this._itemsChanged = true; - this._items = items; - this._schedule(this._updateLayout); -@@ -218,7 +198,16 @@ export class VirtualScroller { - this._containerElement = newEl; - if (newEl) { - this._containerInlineStyle = newEl.getAttribute('style') || null; -- this._applyContainerStyles(); -+ // https://github.com/PolymerLabs/uni-virtualizer/issues/104 -+ // Would rather set these CSS properties on the host using Shadow Root -+ // style scoping (and fall back to a global stylesheet where native -+ // Shadow DOM is not available), but this Mobile Safari bug is preventing -+ // that from working: https://bugs.webkit.org/show_bug.cgi?id=226195 -+ const style = newEl.style; -+ style.display = style.display || 'block'; -+ style.position = style.position || 'relative'; -+ style.overflow = style.overflow || 'auto'; -+ style.contain = style.contain || 'strict'; - if (newEl === this._scrollTarget) { - this._sizer = this._sizer || this._createContainerSizer(); - this._container.insertBefore(this._sizer, this._container.firstChild); -@@ -242,11 +231,12 @@ export class VirtualScroller { - if (this._layout === layout) { - return; - } -- let _layout, _config; -+ let _layout = null; -+ let _config = {}; - if (typeof layout === 'object') { - if (layout.type !== undefined) { - _layout = layout.type; -- delete layout.type; -+ // delete (layout as LayoutSpecifier).type; - } - _config = layout; - } -@@ -325,7 +315,7 @@ export class VirtualScroller { - const child = children[i]; - const idx = this._first + i; - if (this._itemsChanged || this._toBeMeasured.has(child)) { -- mm[idx] = fn.call(this, child, this._items[idx]); -+ mm[idx] = fn.call(this, child, this._items[idx] /*as unknown as object*/); - } - } - this._childMeasurements = mm; -@@ -438,7 +428,9 @@ export class VirtualScroller { - try { - window.performance.measure('uv-virtualizing', 'uv-start', 'uv-end'); - } -- catch (e) { } -+ catch (e) { -+ console.warn('Error measuring performance data: ', e); -+ } - window.performance.mark('uv-start'); - } - this._schedule(this._updateLayout); -@@ -479,26 +471,6 @@ export class VirtualScroller { - this._mutationObserver = new MutationObserver(this._observeMutations.bind(this)); - } - } -- _applyContainerStyles() { -- if (nativeShadowDOM) { -- if (this._containerStylesheet === null) { -- const sheet = (this._containerStylesheet = document.createElement('style')); -- sheet.textContent = containerStyles(':host', '::slotted(*)'); -- } -- const root = this._containerElement.shadowRoot || this._containerElement.attachShadow({ mode: 'open' }); -- const slot = root.querySelector('slot:not([name])'); -- root.appendChild(this._containerStylesheet); -- if (!slot) { -- root.appendChild(document.createElement('slot')); -- } -- } -- else { -- attachGlobalContainerStylesheet(); -- if (this._containerElement) { -- this._containerElement.classList.add(HOST_CLASSNAME); -- } -- } -- } - _createContainerSizer() { - const sizer = document.createElement('div'); - // When the scrollHeight is large, the height of this element might be -@@ -600,6 +572,7 @@ export class VirtualScroller { - if (child) { - const { top, left, width, height } = pos[key]; - child.style.position = 'absolute'; -+ child.style.boxSizing = 'border-box'; - child.style.transform = `translate(${left}px, ${top}px)`; - if (width !== undefined) { - child.style.width = width + 'px'; -@@ -683,7 +656,7 @@ export class VirtualScroller { - // this.requestRemeasure(); - } - _childrenSizeChanged(changes) { -- for (let change of changes) { -+ for (const change of changes) { - this._toBeMeasured.set(change.target, change.contentRect); - } - this._measureChildren(); -@@ -703,3 +676,4 @@ function getMarginValue(value) { - const float = value ? parseFloat(value) : NaN; - return Number.isNaN(float) ? 0 : float; - } -+//# sourceMappingURL=VirtualScroller.js.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/VirtualScroller.js.map b/lib/uni-virtualizer/lib/VirtualScroller.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..693e4099af0f40c1ce2589fec061afe13ae23bc2 ---- /dev/null -+++ b/lib/uni-virtualizer/lib/VirtualScroller.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"VirtualScroller.js","sourceRoot":"","sources":["../../../src/lib/uni-virtualizer/lib/VirtualScroller.ts"],"names":[],"mappings":"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","sourcesContent":["import getResizeObserver from './polyfillLoaders/ResizeObserver.js';\nimport { ItemBox, Margins, Layout, LayoutConstructor, LayoutSpecifier } from './layouts/Layout.js';\n\nexport const scrollerRef = Symbol('scrollerRef');\n\ninterface Range {\n first: number;\n last: number;\n num: number;\n remeasure: boolean;\n stable: boolean;\n firstVisible: number;\n lastVisible: number;\n}\n\nexport type RangeChangeEvent = {\n first: number;\n last: number;\n firstVisible: number;\n lastVisible: number;\n};\n\ninterface ElementWithOptionalScrollerRef extends Element {\n [scrollerRef]?: VirtualScroller\n}\n\ninterface ShadowRootWithOptionalScrollerRef extends ShadowRoot {\n [scrollerRef]?: VirtualScroller\n}\n\ntype Container = ElementWithOptionalScrollerRef | ShadowRootWithOptionalScrollerRef;\nexport type ContainerElement = ElementWithOptionalScrollerRef;\n\ntype VerticalScrollSize = {height: number};\ntype HorizontalScrollSize = {width: number};\ntype ScrollSize = VerticalScrollSize | HorizontalScrollSize;\n\ntype ChildMeasurements = {[key: number]: ItemBox};\n\nexport type ScrollToIndexValue = {index: number, position?: string} | null;\n\nexport interface VirtualScrollerConfig {\n layout?: Layout | LayoutConstructor | LayoutSpecifier;\n\n /**\n * An element that receives scroll events for the virtual scroller.\n */\n scrollTarget?: Element | Window;\n\n /**\n * The parent of all child nodes to be rendered.\n */\n container: Element | ShadowRoot;\n}\n\n/**\n * Provides virtual scrolling boilerplate.\n *\n * Extensions of this class must set container, layout, and scrollTarget.\n *\n * Extensions of this class must also override VirtualRepeater's DOM\n * manipulation methods.\n */\nexport class VirtualScroller {\n private _benchmarkStart: number | null = null;\n /**\n * Whether the layout should receive an updated viewport size on the next\n * render.\n */\n // private _needsUpdateView: boolean = false;\n\n private _layout: Layout | null = null;\n\n /**\n * The element that generates scroll events and defines the container\n * viewport. Set by scrollTarget.\n */\n private _scrollTarget: Element | null = null;\n\n /**\n * A sentinel element that sizes the container when it is a scrolling\n * element. This ensures the scroll bar accurately reflects the total\n * size of the list.\n */\n private _sizer: HTMLElement | null = null;\n\n /**\n * Layout provides these values, we set them on _render().\n * TODO @straversi: Can we find an XOR type, usable for the key here?\n */\n private _scrollSize: ScrollSize | null = null;\n\n /**\n * Difference between scroll target's current and required scroll offsets.\n * Provided by layout.\n */\n private _scrollErr: {left: number, top: number} | null = null;\n\n /**\n * A list of the positions (top, left) of the children in the current range.\n */\n private _childrenPos: Array<{top: number, left: number}> | null = null;\n\n // TODO: (graynorton): type\n private _childMeasurements: ChildMeasurements | null = null;\n\n private _toBeMeasured: Map = new Map();\n\n private _rangeChanged = true;\n\n private _itemsChanged = true;\n\n private _visibilityChanged = true;\n\n /**\n * Containing element. Set by container.\n */\n protected _container: Container | null = null;\n\n /**\n * The parent of all child nodes to be rendered. Set by container.\n */\n private _containerElement: ContainerElement | null = null;\n\n /**\n * Keep track of original inline style of the container, so it can be\n * restored when container is changed.\n */\n private _containerInlineStyle: string | null = null;\n\n /**\n * Size of the container.\n */\n private _containerSize: {width: number, height: number} | null = null;\n\n /**\n * Resize observer attached to container.\n */\n private _containerRO: ResizeObserver | null = null;\n\n /**\n * Resize observer attached to children.\n */\n private _childrenRO: ResizeObserver | null = null;\n\n private _mutationObserver: MutationObserver | null = null;\n private _mutationPromise: Promise | null = null;\n private _mutationPromiseResolver: Function | null = null;\n private _mutationsObserved = false;\n\n // TODO (graynorton): Rethink, per longer comment below\n\n private _loadListener = this._childLoaded.bind(this);\n\n /**\n * Index and position of item to scroll to.\n */\n private _scrollToIndex: ScrollToIndexValue = null;\n\n /**\n * Items to render. Set by items.\n */\n private _items: Array = [];\n\n /**\n * Total number of items to render. Set by totalItems.\n */\n private _totalItems: number | null = null;\n\n /**\n * Index of the first child in the range, not necessarily the first visible child.\n * TODO @straversi: Consider renaming these.\n */\n protected _first = 0;\n\n /**\n * Index of the last child in the range.\n */\n protected _last = 0;\n\n /**\n * Index of the first item intersecting the container element.\n */\n private _firstVisible = 0;\n\n /**\n * Index of the last item intersecting the container element.\n */\n private _lastVisible = 0;\n\n protected _scheduled = new WeakSet();\n\n /**\n * Invoked at the end of each render cycle: children in the range are\n * measured, and their dimensions passed to this callback. Use it to layout\n * children as needed.\n */\n protected _measureCallback: ((sizes: ChildMeasurements) => void) | null = null;\n\n protected _measureChildOverride: ((element: Element, item: unknown) => ItemBox) | null = null;\n\n constructor(config?: VirtualScrollerConfig) {\n this._first = -1;\n this._last = -1;\n\n if (config) {\n Object.assign(this, config);\n }\n }\n\n set items(items: Array | undefined) {\n if (Array.isArray(items) && items !== this._items) {\n this._itemsChanged = true;\n this._items = items;\n this._schedule(this._updateLayout);\n }\n }\n\n /**\n * The total number of items, regardless of the range, that can be rendered\n * as child nodes.\n */\n get totalItems(): number {\n return (this._totalItems === null ? this._items.length : this._totalItems);\n }\n\n set totalItems(num: number) {\n if (typeof num !== 'number' && num !== null) {\n throw new Error('New value must be a number.');\n }\n\n // TODO(valdrin) should we check if it is a finite number?\n // Technically, Infinity would break Layout, not VirtualRepeater.\n if (num !== this._totalItems) {\n this._totalItems = num;\n this._schedule(this._updateLayout);\n }\n }\n\n /**\n * The parent of all child nodes to be rendered.\n */\n get container(): Container | null {\n return this._container;\n }\n\n set container(container: Container | null) {\n if (container === this._container) {\n return;\n }\n\n if (this._container) {\n // Remove children from old container.\n // TODO (graynorton): Decide whether we'd rather fire an event to clear\n // the range and let the renderer take care of removing the DOM children\n this._children.forEach(child => child.parentNode!.removeChild(child));\n }\n\n this._container = container;\n\n this._schedule(this._updateLayout);\n\n this._initResizeObservers().then(() => {\n const oldEl = this._containerElement as HTMLElement;\n // Consider document fragments as shadowRoots.\n const newEl =\n (container && container.nodeType === Node.DOCUMENT_FRAGMENT_NODE) ?\n (container as ShadowRoot).host as HTMLElement :\n container as HTMLElement;\n if (oldEl === newEl) {\n return;\n }\n \n this._containerRO!.disconnect();\n this._containerSize = null;\n \n if (oldEl) {\n if (this._containerInlineStyle) {\n oldEl.setAttribute('style', this._containerInlineStyle!);\n } else {\n oldEl.removeAttribute('style');\n }\n this._containerInlineStyle = null;\n if (oldEl === this._scrollTarget) {\n oldEl.removeEventListener('scroll', this, {passive: true} as EventListenerOptions);\n this._sizer && this._sizer.remove();\n }\n oldEl.removeEventListener('load', this._loadListener, true);\n\n this._mutationObserver!.disconnect();\n } else {\n // First time container was setup, add listeners only now.\n addEventListener('scroll', this, {passive: true});\n }\n \n this._containerElement = newEl;\n \n if (newEl) {\n this._containerInlineStyle = newEl.getAttribute('style') || null;\n // https://github.com/PolymerLabs/uni-virtualizer/issues/104\n // Would rather set these CSS properties on the host using Shadow Root\n // style scoping (and fall back to a global stylesheet where native\n // Shadow DOM is not available), but this Mobile Safari bug is preventing\n // that from working: https://bugs.webkit.org/show_bug.cgi?id=226195\n const style = newEl.style as CSSStyleDeclaration & { contain: string };\n style.display = style.display || 'block';\n style.position = style.position || 'relative';\n style.overflow = style.overflow || 'auto';\n style.contain = style.contain || 'strict';\n if (newEl === this._scrollTarget) {\n this._sizer = this._sizer || this._createContainerSizer();\n this._container!.insertBefore(this._sizer, this._container!.firstChild);\n }\n this._schedule(this._updateLayout);\n this._containerRO!.observe(newEl);\n this._mutationObserver!.observe(newEl, { childList: true });\n this._mutationPromise = new Promise(resolve => this._mutationPromiseResolver = resolve);\n \n if (this._layout && this._layout.listenForChildLoadEvents) {\n newEl.addEventListener('load', this._loadListener, true);\n }\n }\n }); \n }\n\n // This will always actually return a layout instance,\n // but TypeScript wants the getter and setter types to be the same\n get layout(): Layout | LayoutConstructor | LayoutSpecifier | null {\n return this._layout;\n }\n\n set layout(layout: Layout | LayoutConstructor | LayoutSpecifier | null) {\n if (this._layout === layout) {\n return;\n }\n\n let _layout: LayoutConstructor | Layout | null = null;\n let _config: object = {};\n\n if (typeof layout === 'object') {\n if ((layout as LayoutSpecifier).type !== undefined) {\n _layout = (layout as LayoutSpecifier).type;\n // delete (layout as LayoutSpecifier).type;\n }\n _config = layout as object;\n }\n else {\n _layout = layout;\n }\n\n if (typeof _layout === 'function') {\n if (this._layout instanceof _layout) {\n if (_config) {\n this._layout!.config = _config;\n }\n return;\n }\n else {\n _layout = new _layout(_config);\n }\n }\n\n if (this._layout) {\n this._measureCallback = null;\n this._measureChildOverride = null;\n this._layout.removeEventListener('scrollsizechange', this);\n this._layout.removeEventListener('scrollerrorchange', this);\n this._layout.removeEventListener('itempositionchange', this);\n this._layout.removeEventListener('rangechange', this);\n delete this.container![scrollerRef];\n this.container!.removeEventListener('load', this._loadListener, true);\n // Reset container size so layout can get correct viewport size.\n if (this._containerElement) {\n this._sizeContainer(undefined);\n }\n }\n\n this._layout = _layout as Layout | null;\n\n if (this._layout) {\n if (this._layout.measureChildren && typeof this._layout.updateItemSizes === 'function') {\n if (typeof this._layout.measureChildren === 'function') {\n this._measureChildOverride = this._layout.measureChildren;\n }\n this._measureCallback = this._layout.updateItemSizes.bind(this._layout);\n }\n this._layout.addEventListener('scrollsizechange', this);\n this._layout.addEventListener('scrollerrorchange', this);\n this._layout.addEventListener('itempositionchange', this);\n this._layout.addEventListener('rangechange', this);\n this._container![scrollerRef] = this;\n if (this._layout.listenForChildLoadEvents) {\n this._container!.addEventListener('load', this._loadListener, true);\n }\n this._schedule(this._updateLayout);\n }\n }\n\n // TODO (graynorton): Rework benchmarking so that it has no API and\n // instead is always on except in production builds\n startBenchmarking() {\n if (this._benchmarkStart === null) {\n this._benchmarkStart = window.performance.now();\n }\n }\n\n stopBenchmarking() {\n if (this._benchmarkStart !== null) {\n const now = window.performance.now();\n const timeElapsed = now - this._benchmarkStart;\n const entries = performance.getEntriesByName('uv-virtualizing', 'measure');\n const virtualizationTime = entries\n .filter(e => e.startTime >= this._benchmarkStart! && e.startTime < now)\n .reduce((t, m) => t + m.duration, 0);\n this._benchmarkStart = null;\n return { timeElapsed, virtualizationTime };\n }\n return null;\n }\n\n private _measureChildren(): void {\n const mm: ChildMeasurements = {};\n const children = this._children;\n const fn = this._measureChildOverride || this._measureChild;\n for (let i = 0; i < children.length; i++) {\n const child = children[i];\n const idx = this._first + i;\n if (this._itemsChanged || this._toBeMeasured.has(child)) {\n mm[idx] = fn.call(this, child, this._items[idx] /*as unknown as object*/);\n }\n }\n this._childMeasurements = mm;\n this._schedule(this._updateLayout);\n this._toBeMeasured.clear();\n }\n\n /**\n * Returns the width, height, and margins of the given child.\n */\n _measureChild(element: Element): ItemBox {\n // offsetWidth doesn't take transforms in consideration, so we use\n // getBoundingClientRect which does.\n const {width, height} = element.getBoundingClientRect();\n return Object.assign({width, height}, getMargins(element));\n }\n\n\n /**\n * The element that generates scroll events and defines the container\n * viewport. The value `null` (default) corresponds to `window` as scroll\n * target.\n */\n get scrollTarget(): Element | Window | null {\n return this._scrollTarget;\n }\n set scrollTarget(target: Element | Window | null) {\n // Consider window as null.\n if (target === window) {\n target = null;\n }\n if (this._scrollTarget === target) {\n return;\n }\n this._sizeContainer(undefined);\n if (this._scrollTarget) {\n this._scrollTarget.removeEventListener('scroll', this, {passive: true} as EventListenerOptions);\n if (this._sizer && this._scrollTarget === this._containerElement) {\n this._sizer.remove();\n }\n }\n\n this._scrollTarget = target as (Element | null);\n\n if (target) {\n target.addEventListener('scroll', this, {passive: true});\n if (target === this._containerElement) {\n this._sizer = this._sizer || this._createContainerSizer();\n this._container!.insertBefore(this._sizer, this._container!.firstChild);\n }\n }\n }\n\n /**\n * Index and position of item to scroll to. The scroller will fix to that point\n * until the user scrolls.\n */\n set scrollToIndex(newValue: ScrollToIndexValue) {\n this._scrollToIndex = newValue;\n this._schedule(this._updateLayout);\n }\n\n protected async _schedule(method: Function): Promise {\n if (!this._scheduled.has(method)) {\n this._scheduled.add(method);\n await Promise.resolve();\n this._scheduled.delete(method);\n method.call(this);\n }\n }\n\n async _updateDOM() {\n const {_rangeChanged, _itemsChanged} = this;\n if (this._visibilityChanged) {\n this._notifyVisibility();\n this._visibilityChanged = false;\n }\n if (_rangeChanged || _itemsChanged) {\n this._notifyRange();\n this._rangeChanged = false;\n this._itemsChanged = false;\n await this._mutationPromise;\n }\n if (this._layout!.measureChildren) {\n this._children.forEach((child) => this._childrenRO!.observe(child));\n }\n this._positionChildren(this._childrenPos!);\n this._sizeContainer(this._scrollSize);\n if (this._scrollErr) {\n this._correctScrollError(this._scrollErr);\n this._scrollErr = null;\n }\n if (this._benchmarkStart && 'mark' in window.performance) {\n window.performance.mark('uv-end');\n }\n }\n\n _updateLayout() {\n this._layout!.totalItems = this._totalItems!;\n if (this._scrollToIndex !== null) {\n this._layout!.scrollToIndex(this._scrollToIndex.index, this._scrollToIndex!.position!);\n this._scrollToIndex = null;\n }\n this._updateView();\n if (this._childMeasurements !== null) {\n // If the layout has been changed, we may have measurements but no callback\n if (this._measureCallback) {\n this._measureCallback(this._childMeasurements);\n }\n this._childMeasurements = null;\n }\n this._layout!.reflowIfNeeded(this._itemsChanged);\n if (this._benchmarkStart && 'mark' in window.performance) {\n window.performance.mark('uv-end');\n }\n }\n\n private _handleScrollEvent() {\n if (this._benchmarkStart && 'mark' in window.performance) {\n try {\n window.performance.measure(\n 'uv-virtualizing',\n 'uv-start',\n 'uv-end'\n );\n } catch(e) {\n console.warn('Error measuring performance data: ', e);\n }\n window.performance.mark('uv-start');\n }\n this._schedule(this._updateLayout);\n }\n\n handleEvent(event: CustomEvent) {\n switch (event.type) {\n case 'scroll':\n if (!this._scrollTarget || event.target === this._scrollTarget) {\n this._handleScrollEvent();\n }\n break;\n case 'scrollsizechange':\n this._scrollSize = event.detail;\n this._schedule(this._updateDOM);\n break;\n case 'scrollerrorchange':\n this._scrollErr = event.detail;\n this._schedule(this._updateDOM);\n break;\n case 'itempositionchange':\n this._childrenPos = event.detail;\n this._schedule(this._updateDOM);\n break;\n case 'rangechange':\n this._adjustRange(event.detail);\n this._schedule(this._updateDOM);\n break;\n default:\n console.warn('event not handled', event);\n }\n }\n\n private async _initResizeObservers() {\n if (this._containerRO === null) {\n const ResizeObserver = await getResizeObserver();\n this._containerRO = new ResizeObserver(\n (entries: ResizeObserverEntry[]) => this._containerSizeChanged(entries[0].contentRect));\n this._childrenRO =\n new ResizeObserver(this._childrenSizeChanged.bind(this));\n this._mutationObserver = new MutationObserver(this._observeMutations.bind(this));\n }\n }\n\n private _createContainerSizer(): HTMLDivElement {\n const sizer = document.createElement('div');\n // When the scrollHeight is large, the height of this element might be\n // ignored. Setting content and font-size ensures the element has a size.\n Object.assign(sizer.style, {\n position: 'absolute',\n margin: '-2px 0 0 0',\n padding: 0,\n visibility: 'hidden',\n fontSize: '2px',\n });\n sizer.innerHTML = ' ';\n sizer.id = 'uni-virtualizer-spacer';\n return sizer;\n }\n\n get _children(): Array {\n const arr = [];\n let next = this.container!.firstElementChild as HTMLElement;\n while (next) {\n // Skip our spacer. TODO (graynorton): Feels a bit hacky. Anything better?\n if (next.id !== 'uni-virtualizer-spacer') {\n arr.push(next);\n }\n next = next.nextElementSibling as HTMLElement;\n }\n return arr;\n }\n\n private _updateView() {\n if (!this.container || !this._containerElement || !this._layout) {\n return;\n }\n let width, height, top, left;\n if (this._scrollTarget === this._containerElement && this._containerSize !== null) {\n width = this._containerSize.width;\n height = this._containerSize.height;\n left = this._containerElement.scrollLeft;\n top = this._containerElement.scrollTop;\n } else {\n const containerBounds = this._containerElement.getBoundingClientRect();\n const scrollBounds = this._scrollTarget ?\n this._scrollTarget.getBoundingClientRect() :\n {\n top: containerBounds.top + window.pageYOffset,\n left: containerBounds.left + window.pageXOffset,\n width: innerWidth,\n height: innerHeight\n };\n const scrollerWidth = scrollBounds.width;\n const scrollerHeight = scrollBounds.height;\n const xMin = Math.max(\n 0, Math.min(scrollerWidth, containerBounds.left - scrollBounds.left));\n const yMin = Math.max(\n 0, Math.min(scrollerHeight, containerBounds.top - scrollBounds.top));\n // TODO (graynorton): Direction is intended to be a layout-level concept, not a scroller-level concept,\n // so this feels like a factoring problem\n const xMax = this._layout.direction === 'vertical' ?\n Math.max(\n 0,\n Math.min(\n scrollerWidth, containerBounds.right - scrollBounds.left)) :\n scrollerWidth;\n const yMax = this._layout.direction === 'vertical' ?\n scrollerHeight :\n Math.max(\n 0,\n Math.min(\n scrollerHeight, containerBounds.bottom - scrollBounds.top));\n width = xMax - xMin;\n height = yMax - yMin;\n left = Math.max(0, -(containerBounds.left - scrollBounds.left));\n top = Math.max(0, -(containerBounds.top - scrollBounds.top));\n }\n this._layout.viewportSize = {width, height};\n this._layout.viewportScroll = {top, left};\n }\n\n /**\n * Styles the _sizer element or the container so that its size reflects the\n * total size of all items.\n */\n private _sizeContainer(size?: ScrollSize | null) {\n if (this._scrollTarget === this._containerElement) {\n const left = size && (size as HorizontalScrollSize).width ? (size as HorizontalScrollSize).width - 1 : 0;\n const top = size && (size as VerticalScrollSize).height ? (size as VerticalScrollSize).height - 1 : 0;\n if (this._sizer) {\n this._sizer.style.transform = `translate(${left}px, ${top}px)`;\n }\n } else {\n if (this._containerElement) {\n const style = (this._containerElement as HTMLElement).style;\n (style.minWidth as string | null) = size && (size as HorizontalScrollSize).width ? (size as HorizontalScrollSize).width + 'px' : null;\n (style.minHeight as string | null) = size && (size as VerticalScrollSize).height ? (size as VerticalScrollSize).height + 'px' : null; \n }\n }\n }\n\n /**\n * Sets the top and left transform style of the children from the values in\n * pos.\n */\n private _positionChildren(pos: Array<{top: number, left: number, width?: number, height?: number}>) {\n if (pos) {\n const children = this._children;\n Object.keys(pos).forEach((key) => {\n const idx = (key as unknown as number) - this._first;\n const child = children[idx];\n if (child) {\n const {top, left, width, height} = pos[key as unknown as number];\n child.style.position = 'absolute';\n child.style.boxSizing = 'border-box';\n child.style.transform = `translate(${left}px, ${top}px)`;\n if (width !== undefined) {\n child.style.width = width + 'px';\n }\n if (height !== undefined) {\n child.style.height = height + 'px';\n }\n }\n }); \n }\n }\n\n private async _adjustRange(range: Range) {\n const {_first, _last, _firstVisible, _lastVisible} = this;\n this._first = range.first;\n this._last = range.last;\n this._firstVisible = range.firstVisible;\n this._lastVisible = range.lastVisible;\n this._rangeChanged = (\n this._rangeChanged ||\n this._first !== _first ||\n this._last !== _last\n );\n this._visibilityChanged = (\n this._visibilityChanged ||\n this._firstVisible !== _firstVisible ||\n this._lastVisible !== _lastVisible\n );\n }\n\n private _correctScrollError(err: {top: number, left: number}) {\n if (this._scrollTarget) {\n this._scrollTarget.scrollTop -= err.top;\n this._scrollTarget.scrollLeft -= err.left;\n } else {\n window.scroll(window.pageXOffset - err.left, window.pageYOffset - err.top);\n }\n }\n\n /**\n * Emits a rangechange event with the current first, last, firstVisible, and\n * lastVisible.\n */\n private _notifyRange() {\n // TODO (graynorton): Including visibility here for backward compat, but \n // may decide to remove at some point. The rationale for separating is that\n // range change events are mainly intended for \"internal\" consumption by the\n // renderer, whereas visibility change events are mainly intended for \"external\"\n // consumption by application code.\n this._container!.dispatchEvent(\n new CustomEvent('rangeChanged', {detail:{\n first: this._first,\n last: this._last,\n firstVisible: this._firstVisible,\n lastVisible: this._lastVisible,\n }})\n );\n }\n\n private _notifyVisibility() {\n this._container!.dispatchEvent(\n new CustomEvent('visibilityChanged', {detail:{\n first: this._first,\n last: this._last,\n firstVisible: this._firstVisible,\n lastVisible: this._lastVisible,\n }})\n );\n }\n\n /**\n * Render and update the view at the next opportunity with the given\n * container size.\n */\n private _containerSizeChanged(size: {width: number, height: number}) {\n const {width, height} = size;\n this._containerSize = {width, height};\n this._schedule(this._updateLayout);\n }\n\n private async _observeMutations() {\n if (!this._mutationsObserved) {\n this._mutationsObserved = true;\n this._mutationPromiseResolver!();\n this._mutationPromise = new Promise(resolve => this._mutationPromiseResolver = resolve);\n this._mutationsObserved = false;\n }\n }\n\n // TODO (graynorton): Rethink how this works. Probably child loading is too specific\n // to have dedicated support for; might want some more generic lifecycle hooks for\n // layouts to use. Possibly handle measurement this way, too, or maybe that remains\n // a first-class feature?\n\n private _childLoaded() {\n // this.requestRemeasure();\n }\n\n private _childrenSizeChanged(changes: ResizeObserverEntry[]) {\n for (const change of changes) {\n this._toBeMeasured.set(change.target as HTMLElement, change.contentRect);\n }\n this._measureChildren();\n this._schedule(this._updateLayout);\n }\n}\n\nfunction getMargins(el: Element): Margins {\n const style = window.getComputedStyle(el);\n return {\n marginTop: getMarginValue(style.marginTop),\n marginRight: getMarginValue(style.marginRight),\n marginBottom: getMarginValue(style.marginBottom),\n marginLeft: getMarginValue(style.marginLeft),\n };\n}\n\nfunction getMarginValue(value: string): number {\n const float = value ? parseFloat(value) : NaN;\n return Number.isNaN(float) ? 0 : float;\n}"]} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout.d.ts b/lib/uni-virtualizer/lib/layouts/Layout.d.ts -index 42d282ba7ee5d75b7ba85afcf985c8ea47112ea4..425e2d89301a051c6eecb4e00b4dbdeec7032b2b 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout.d.ts -+++ b/lib/uni-virtualizer/lib/layouts/Layout.d.ts -@@ -8,7 +8,7 @@ export declare type Margins = { - marginBottom: number; - marginLeft: number; - }; --export declare type ItemBox = Size | Size & Margins; -+export declare type ItemBox = Size | (Size & Margins); - export declare type position = 'left' | 'top'; - export declare type Positions = { - left: number; -@@ -16,29 +16,28 @@ export declare type Positions = { - width?: number; - height?: number; - }; --export interface Type extends Function { -- new (...args: any[]): T; --} --export interface LayoutConfig { -- type?: Type; -+export declare type LayoutConstructor = new (config?: object) => Layout; -+export interface LayoutSpecifier { -+ type: LayoutConstructor; - } -+export declare type LayoutSpecifierFactory = (config?: object) => LayoutSpecifier; - export declare type ScrollDirection = 'vertical' | 'horizontal'; - /** - * Interface for layouts consumed by VirtualScroller or VirtualRepeater. - */ - export interface Layout { -- config: LayoutConfig; -+ config?: object; - totalItems: number; - direction: ScrollDirection; - viewportSize: Size; - viewportScroll: Positions; -- readonly measureChildren?: boolean | ((e: Element, i: object) => object); -+ readonly measureChildren?: boolean | ((e: Element, i: unknown) => ItemBox); - readonly listenForChildLoadEvents?: boolean; - updateItemSizes?: (sizes: { - [key: number]: ItemBox; - }) => void; -- addEventListener: any; -- removeEventListener: any; -+ addEventListener: Function; -+ removeEventListener: Function; - scrollToIndex: (index: number, position: string) => void; - /** - * Called by a VirtualRepeater or VirtualScroller when an update that -diff --git a/lib/uni-virtualizer/lib/layouts/Layout.d.ts.map b/lib/uni-virtualizer/lib/layouts/Layout.d.ts.map -index 470e5b26be4db353e4a9bafb815d41bcf208832f..13497a406bb9144e3f8b5c729bca351916c831bb 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout.d.ts.map -+++ b/lib/uni-virtualizer/lib/layouts/Layout.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAC3C,oBAAY,IAAI,GAAG;KAChB,GAAG,IAAI,SAAS,GAAG,MAAM;CAC3B,CAAC;AAEF,oBAAY,OAAO,GAAG;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAC;AAEF,oBAAY,OAAO,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC;AAE5C,oBAAY,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AACtC,oBAAY,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAC;AAEF,MAAM,WAAW,IAAI,CAAC,CAAC,CAAE,SAAQ,QAAQ;IACvC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;CACpB;AAED,oBAAY,eAAe,GAAG,UAAU,GAAG,YAAY,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,YAAY,CAAC;IAErB,UAAU,EAAE,MAAM,CAAC;IAEnB,SAAS,EAAE,eAAe,CAAC;IAE3B,YAAY,EAAE,IAAI,CAAC;IAEnB,cAAc,EAAE,SAAS,CAAC;IAE1B,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC;IAEzE,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAE5C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KACvB,KAAK,IAAI,CAAC;IAEX,gBAAgB,MAAC;IAEjB,mBAAmB,MAAC;IAEpB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CG;IACH,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1C"} -\ No newline at end of file -+{"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout.ts"],"names":[],"mappings":"AAAA,oBAAY,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAC3C,oBAAY,IAAI,GAAG;KAChB,GAAG,IAAI,SAAS,GAAG,MAAM;CAC3B,CAAC;AAEF,oBAAY,OAAO,GAAG;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAC;AAEF,oBAAY,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC;AAE9C,oBAAY,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AACtC,oBAAY,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAC;AAGF,oBAAY,iBAAiB,GAAG,KAAI,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,iBAAiB,CAAA;CACxB;AAED,oBAAY,sBAAsB,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,eAAe,CAAC;AAG1E,oBAAY,eAAe,GAAG,UAAU,GAAG,YAAY,CAAC;AAExD;;GAEG;AACF,MAAM,WAAW,MAAM;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,UAAU,EAAE,MAAM,CAAC;IAEnB,SAAS,EAAE,eAAe,CAAC;IAE3B,YAAY,EAAE,IAAI,CAAC;IAEnB,cAAc,EAAE,SAAS,CAAC;IAE1B,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC;IAE3E,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAE5C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KACvB,KAAK,IAAI,CAAC;IAEX,gBAAgB,EAAE,QAAQ,CAAC;IAE3B,mBAAmB,EAAE,QAAQ,CAAC;IAE9B,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6CG;IACH,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CAC1C"} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout.js b/lib/uni-virtualizer/lib/layouts/Layout.js -index cb0ff5c3b541f646105198ee23ac0fc3d805023e..215b2c9498b60901b4bf2dd7c4a18803a4e4562c 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout.js -+++ b/lib/uni-virtualizer/lib/layouts/Layout.js -@@ -1 +1,2 @@ - export {}; -+//# sourceMappingURL=Layout.js.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout.js.map b/lib/uni-virtualizer/lib/layouts/Layout.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..a95ae35dbf53896f9eba8ccbeeea0b06f9c8d3c0 ---- /dev/null -+++ b/lib/uni-virtualizer/lib/layouts/Layout.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"Layout.js","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout.ts"],"names":[],"mappings":"","sourcesContent":["export type dimension = 'height' | 'width';\nexport type Size = {\n [key in dimension]: number\n};\n\nexport type Margins = {\n marginTop: number,\n marginRight: number,\n marginBottom: number,\n marginLeft: number\n};\n\nexport type ItemBox = Size | (Size & Margins);\n\nexport type position = 'left' | 'top';\nexport type Positions = {\n left: number,\n top: number,\n width?: number,\n height?: number\n};\n\n\nexport type LayoutConstructor = new(config?: object) => Layout;\n\nexport interface LayoutSpecifier {\n type: LayoutConstructor\n}\n\nexport type LayoutSpecifierFactory = (config?: object) => LayoutSpecifier;\n\n\nexport type ScrollDirection = 'vertical' | 'horizontal';\n\n/**\n * Interface for layouts consumed by VirtualScroller or VirtualRepeater.\n */\n export interface Layout {\n config?: object;\n \n totalItems: number;\n\n direction: ScrollDirection;\n\n viewportSize: Size;\n\n viewportScroll: Positions;\n\n readonly measureChildren?: boolean | ((e: Element, i: unknown) => ItemBox);\n\n readonly listenForChildLoadEvents?: boolean;\n\n updateItemSizes?: (sizes: {\n [key: number]: ItemBox\n }) => void;\n\n addEventListener: Function;\n\n removeEventListener: Function;\n\n scrollToIndex: (index: number, position: string) => void;\n\n /**\n * Called by a VirtualRepeater or VirtualScroller when an update that\n * potentially affects layout has occurred. For example, a viewport size\n * change.\n *\n * The layout is in turn responsible for dispatching events, as necessary,\n * to the VirtualRepeater or VirtualScroller. Each of the following events\n * represents an update that should be determined during a reflow. Dispatch\n * each event at maximum once during a single reflow.\n *\n * Events that should be dispatched:\n * - scrollsizechange\n * Dispatch when the total length of all items in the scrolling direction,\n * including spacing, changes.\n * detail: {\n * 'height' | 'width': number\n * }\n * - rangechange\n * Dispatch when the range of children that should be displayed changes\n * (based on layout calculations and the size of the container) or when\n * the first or last item to intersect the container changes.\n * detail: {\n * first: number,\n * last: number,\n * num: number,\n * stable: boolean,\n * remeasure: boolean,\n * firstVisible: number,\n * lastVisible: number,\n * }\n * - itempositionchange\n * Dispatch when the child positions change, for example due to a range\n * change.\n * detail {\n * [number]: {\n * left: number,\n * top: number\n * }\n * }\n * - scrollerrorchange\n * Dispatch when the set viewportScroll offset is not what it should be.\n * detail {\n * height: number,\n * width: number,\n * }\n */\n reflowIfNeeded: (force: boolean) => void;\n}"]} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1d.d.ts b/lib/uni-virtualizer/lib/layouts/Layout1d.d.ts -index 843bab970bceb48331dbc28b30ec11c56f9940f2..cafaca7a6b9f0d7789eb17d2c753149dc5c99e60 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1d.d.ts -+++ b/lib/uni-virtualizer/lib/layouts/Layout1d.d.ts -@@ -1,10 +1,19 @@ --import { Layout1dBase } from './Layout1dBase.js'; -+import { Layout1dBase, Layout1dBaseConfig } from './Layout1dBase.js'; - import { ItemBox, Positions, Size } from './Layout.js'; - declare type ItemBounds = { - pos: number; - size: number; - }; --export declare class Layout1d extends Layout1dBase { -+declare type Layout1dConstructor = { -+ prototype: Layout1d; -+ new (config?: Layout1dBaseConfig): Layout1d; -+}; -+declare type Layout1dSpecifier = Layout1dBaseConfig & { -+ type: Layout1dConstructor; -+}; -+declare type Layout1dSpecifierFactory = (config?: Layout1dBaseConfig) => Layout1dSpecifier; -+export declare const layout1d: Layout1dSpecifierFactory; -+export declare class Layout1d extends Layout1dBase { - /** - * Indices of children mapped to their (position and length) in the scrolling - * direction. Used to keep track of children that are in range. -@@ -24,11 +33,11 @@ export declare class Layout1d extends Layout1dBase { - * jumping to any point of the scroll size. We choose it once and stick with - * it until stable. _first and _last are deduced around it. - */ -- _anchorIdx: number; -+ _anchorIdx: number | null; - /** - * Position in the scrolling direction of the anchor child. - */ -- _anchorPos: number; -+ _anchorPos: number | null; - /** - * Whether all children in range were in range during the previous reflow. - */ -@@ -47,7 +56,6 @@ export declare class Layout1d extends Layout1dBase { - private _tMeasured; - private _measureChildren; - _estimate: boolean; -- constructor(config: any); - get measureChildren(): boolean; - /** - * Determine the average size of all children represented in the sizes -@@ -62,13 +70,13 @@ export declare class Layout1d extends Layout1dBase { - */ - _updateItemSize(): void; - _getMetrics(idx: number): ItemBox; -- _getPhysicalItem(idx: number): ItemBounds; -+ _getPhysicalItem(idx: number): ItemBounds | undefined; - _getSize(idx: number): number | undefined; - /** - * Returns the position in the scrolling direction of the item at idx. - * Estimates it if the item at idx is not in the DOM. - */ -- _getPosition(idx: any): number; -+ _getPosition(idx: number): number; - _calculateAnchor(lower: number, upper: number): number; - _getAnchor(lower: number, upper: number): number; - /** -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1d.d.ts.map b/lib/uni-virtualizer/lib/layouts/Layout1d.d.ts.map -index 511045deb4569007c11e36a74ffdd4ca0dbb4931..16037434e706663ab3bc9fadb9e5c19090b5342f 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1d.d.ts.map -+++ b/lib/uni-virtualizer/lib/layouts/Layout1d.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"Layout1d.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1d.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAC,MAAM,aAAa,CAAC;AAErD,aAAK,UAAU,GAAG;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAAC;AAEF,qBAAa,QAAS,SAAQ,YAAY;IACxC;;;OAGG;IACH,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAa;IAEpD;;;OAGG;IACH,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAa;IAEvD;;OAEG;IACH,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAa;IAExC;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAQ;IAE1B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAQ;IAE1B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAQ;IAExB;;OAEG;IACH,eAAe,EAAE,OAAO,CAAS;IAEjC;;OAEG;IACH,OAAO,CAAC,UAAU,CAAa;IAE/B;;OAEG;IACH,OAAO,CAAC,UAAU,CAAa;IAE/B,OAAO,CAAC,gBAAgB,CAAQ;IAEhC,SAAS,EAAE,OAAO,CAAQ;gBAEd,MAAM,KAAA;IAIlB,IAAI,eAAe,YAElB;IAED;;;OAGG;IACH,eAAe,CAAC,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAC;IAmC/C;;;OAGG;IACH,eAAe;IAMf,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIjC,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU;IAIzC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKzC;;;OAGG;IACH,YAAY,CAAC,GAAG,KAAA,GAAG,MAAM;IAKzB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IActD,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAqDhD;;;OAGG;IACH,eAAe;IAQf;;OAEG;IACH,WAAW;IAeX,SAAS;IA+GT,eAAe,IAAI,MAAM;IAezB,iBAAiB;IAQjB,OAAO;IA0BP,iBAAiB;IAMjB;;OAEG;IACH,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;IAOxC;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAO/B,gBAAgB;IAKhB,UAAU;CAMX"} -\ No newline at end of file -+{"version":3,"file":"Layout1d.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1d.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAU,MAAM,aAAa,CAAC;AAE9D,aAAK,UAAU,GAAG;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAAC;AAEF,aAAK,mBAAmB,GAAG;IACzB,SAAS,EAAE,QAAQ,CAAC;IACpB,KAAI,MAAM,CAAC,EAAE,kBAAkB,GAAG,QAAQ,CAAA;CAC3C,CAAA;AAED,aAAK,iBAAiB,GAAG,kBAAkB,GAAG;IAC5C,IAAI,EAAE,mBAAmB,CAAA;CAC1B,CAAA;AAED,aAAK,wBAAwB,GAAG,CAAC,MAAM,CAAC,EAAE,kBAAkB,KAAK,iBAAiB,CAAC;AAEnF,eAAO,MAAM,QAAQ,EAAE,wBAEb,CAAC;AAGX,qBAAa,QAAS,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IAC5D;;;OAGG;IACH,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAa;IAEpD;;;OAGG;IACH,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAa;IAEvD;;OAEG;IACH,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAa;IAExC;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEjC;;OAEG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IAEjC;;OAEG;IACH,OAAO,UAAQ;IAEf;;OAEG;IACH,eAAe,UAAS;IAExB;;OAEG;IACH,OAAO,CAAC,UAAU,CAAK;IAEvB;;OAEG;IACH,OAAO,CAAC,UAAU,CAAK;IAEvB,OAAO,CAAC,gBAAgB,CAAQ;IAEhC,SAAS,UAAQ;IAUjB,IAAI,eAAe,YAElB;IAED;;;OAGG;IACH,eAAe,CAAC,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAC;IAmC/C;;;OAGG;IACH,eAAe;IAMf,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IASjC,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIrD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKzC;;;OAGG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAKjC,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IActD,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAqDhD;;;OAGG;IACH,eAAe;IAQf;;OAEG;IACH,WAAW;IAeX,SAAS;IA+GT,eAAe,IAAI,MAAM;IAezB,iBAAiB;IAQjB,OAAO;IA0BP,iBAAiB;IAMjB;;OAEG;IACH,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;IAOxC;;OAEG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAO/B,gBAAgB;IAKhB,UAAU;CAMX"} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1d.js b/lib/uni-virtualizer/lib/layouts/Layout1d.js -index 24e296295ce03663bdb90b19925ccb70e3586ddb..7a261b8b7d3a3bce975eef81a6558dc5ad561e66 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1d.js -+++ b/lib/uni-virtualizer/lib/layouts/Layout1d.js -@@ -1,7 +1,10 @@ - import { Layout1dBase } from './Layout1dBase.js'; -+export const layout1d = (config) => Object.assign({ -+ type: Layout1d -+}, config); - export class Layout1d extends Layout1dBase { -- constructor(config) { -- super(config); -+ constructor() { -+ super(...arguments); - /** - * Indices of children mapped to their (position and length) in the scrolling - * direction. Used to keep track of children that are in range. -@@ -45,6 +48,11 @@ export class Layout1d extends Layout1dBase { - this._measureChildren = true; - this._estimate = true; - } -+ // protected _defaultConfig: Layout1dBaseConfig = Object.assign({}, super._defaultConfig, { -+ // }) -+ // constructor(config: Layout1dConfig) { -+ // super(config); -+ // } - get measureChildren() { - return this._measureChildren; - } -@@ -54,7 +62,7 @@ export class Layout1d extends Layout1dBase { - */ - updateItemSizes(sizes) { - Object.keys(sizes).forEach((key) => { -- const metrics = sizes[key], mi = this._getMetrics(Number(key)), prevSize = mi[this._sizeDim]; -+ const metrics = sizes[Number(key)], mi = this._getMetrics(Number(key)), prevSize = mi[this._sizeDim]; - // TODO(valdrin) Handle margin collapsing. - // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing - mi.width = metrics.width + (metrics.marginLeft || 0) + -@@ -64,10 +72,10 @@ export class Layout1d extends Layout1dBase { - const size = mi[this._sizeDim]; - const item = this._getPhysicalItem(Number(key)); - if (item) { -- let delta; -+ let delta = 0; - if (size !== undefined) { - item.size = size; -- if (prevSize === undefined) { -+ if (prevSize === -1) { - delta = size; - this._nMeasured++; - } -@@ -93,7 +101,12 @@ export class Layout1d extends Layout1dBase { - Math.round(this._tMeasured / this._nMeasured); - } - _getMetrics(idx) { -- return (this._metrics[idx] = this._metrics[idx] || {}); -+ let metrics = this._metrics.get(idx); -+ if (metrics === undefined) { -+ metrics = { height: -1, width: -1 }; -+ this._metrics.set(idx, metrics); -+ } -+ return metrics; - } - _getPhysicalItem(idx) { - return this._newPhysicalItems.get(idx) || this._physicalItems.get(idx); -@@ -365,3 +378,4 @@ export class Layout1d extends Layout1dBase { - super._emitRange({ remeasure, stable }); - } - } -+//# sourceMappingURL=Layout1d.js.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1d.js.map b/lib/uni-virtualizer/lib/layouts/Layout1d.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..53e2375c71a8f5e1aa116c5d3152e392569607ef ---- /dev/null -+++ b/lib/uni-virtualizer/lib/layouts/Layout1d.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"Layout1d.js","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1d.ts"],"names":[],"mappings":"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","sourcesContent":["import {Layout1dBase, Layout1dBaseConfig} from './Layout1dBase.js';\nimport {ItemBox, Positions, Size, Margins} from './Layout.js';\n\ntype ItemBounds = {\n pos: number,\n size: number\n};\n\ntype Layout1dConstructor = {\n prototype: Layout1d,\n new(config?: Layout1dBaseConfig): Layout1d\n}\n\ntype Layout1dSpecifier = Layout1dBaseConfig & {\n type: Layout1dConstructor\n}\n\ntype Layout1dSpecifierFactory = (config?: Layout1dBaseConfig) => Layout1dSpecifier;\n\nexport const layout1d: Layout1dSpecifierFactory = (config?: Layout1dBaseConfig) => Object.assign({\n type: Layout1d\n}, config);\n\n\nexport class Layout1d extends Layout1dBase {\n /**\n * Indices of children mapped to their (position and length) in the scrolling\n * direction. Used to keep track of children that are in range.\n */\n _physicalItems: Map = new Map();\n\n /**\n * Used in tandem with _physicalItems to track children in range across\n * reflows.\n */\n _newPhysicalItems: Map = new Map();\n\n /**\n * Width and height of children by their index.\n */\n _metrics: Map = new Map();\n\n /**\n * anchorIdx is the anchor around which we reflow. It is designed to allow\n * jumping to any point of the scroll size. We choose it once and stick with\n * it until stable. _first and _last are deduced around it.\n */\n _anchorIdx: number | null = null;\n\n /**\n * Position in the scrolling direction of the anchor child.\n */\n _anchorPos: number | null = null;\n\n /**\n * Whether all children in range were in range during the previous reflow.\n */\n _stable = true;\n\n /**\n * Whether to remeasure children during the next reflow.\n */\n _needsRemeasure = false;\n\n /**\n * Number of children to lay out.\n */\n private _nMeasured = 0;\n\n /**\n * Total length in the scrolling direction of the laid out children.\n */\n private _tMeasured = 0;\n\n private _measureChildren = true;\n\n _estimate = true;\n\n // protected _defaultConfig: Layout1dBaseConfig = Object.assign({}, super._defaultConfig, {\n\n // })\n\n // constructor(config: Layout1dConfig) {\n // super(config);\n // }\n\n get measureChildren() {\n return this._measureChildren;\n }\n\n /**\n * Determine the average size of all children represented in the sizes\n * argument.\n */\n updateItemSizes(sizes: {[key: number]: ItemBox}) {\n Object.keys(sizes).forEach((key) => {\n const metrics = sizes[Number(key)], mi = this._getMetrics(Number(key)),\n prevSize = mi[this._sizeDim];\n\n // TODO(valdrin) Handle margin collapsing.\n // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Mastering_margin_collapsing\n mi.width = metrics.width + ((metrics as Margins).marginLeft || 0) +\n ((metrics as Margins).marginRight || 0);\n mi.height = metrics.height + ((metrics as Margins).marginTop || 0) +\n ((metrics as Margins).marginBottom || 0);\n\n const size = mi[this._sizeDim];\n const item = this._getPhysicalItem(Number(key));\n if (item) {\n let delta = 0;\n\n if (size !== undefined) {\n item.size = size;\n if (prevSize === -1) {\n delta = size;\n this._nMeasured++;\n } else {\n delta = size - prevSize;\n }\n }\n this._tMeasured = this._tMeasured + delta;\n }\n });\n if (this._nMeasured) {\n this._updateItemSize();\n this._scheduleReflow();\n }\n }\n\n /**\n * Set the average item size based on the total length and number of children\n * in range.\n */\n _updateItemSize() {\n // Keep integer values.\n this._itemSize[this._sizeDim] =\n Math.round(this._tMeasured / this._nMeasured);\n }\n\n _getMetrics(idx: number): ItemBox {\n let metrics = this._metrics.get(idx);\n if (metrics === undefined) {\n metrics = {height: -1, width: -1};\n this._metrics.set(idx, metrics);\n }\n return metrics;\n }\n\n _getPhysicalItem(idx: number): ItemBounds | undefined {\n return this._newPhysicalItems.get(idx) || this._physicalItems.get(idx);\n }\n\n _getSize(idx: number): number | undefined {\n const item = this._getPhysicalItem(idx);\n return item && item.size;\n }\n\n /**\n * Returns the position in the scrolling direction of the item at idx.\n * Estimates it if the item at idx is not in the DOM.\n */\n _getPosition(idx: number): number {\n const item = this._getPhysicalItem(idx);\n return item ? item.pos : (idx * (this._delta)) + this._spacing;\n }\n\n _calculateAnchor(lower: number, upper: number): number {\n if (lower === 0) {\n return 0;\n }\n if (upper > this._scrollSize - this._viewDim1) {\n return this._totalItems - 1;\n }\n return Math.max(\n 0,\n Math.min(\n this._totalItems - 1,\n Math.floor(((lower + upper) / 2) / this._delta)));\n }\n\n _getAnchor(lower: number, upper: number): number {\n if (this._physicalItems.size === 0) {\n return this._calculateAnchor(lower, upper);\n }\n if (this._first < 0) {\n console.error('_getAnchor: negative _first');\n return this._calculateAnchor(lower, upper);\n }\n if (this._last < 0) {\n console.error('_getAnchor: negative _last');\n return this._calculateAnchor(lower, upper);\n }\n\n const firstItem = this._getPhysicalItem(this._first),\n lastItem = this._getPhysicalItem(this._last),\n firstMin = firstItem!.pos, firstMax = firstMin + firstItem!.size,\n lastMin = lastItem!.pos, lastMax = lastMin + lastItem!.size;\n\n if (lastMax < lower) {\n // Window is entirely past physical items, calculate new anchor\n return this._calculateAnchor(lower, upper);\n }\n if (firstMin > upper) {\n // Window is entirely before physical items, calculate new anchor\n return this._calculateAnchor(lower, upper);\n }\n if (firstMin >= lower || firstMax >= lower) {\n // First physical item overlaps window, choose it\n return this._first;\n }\n if (lastMax <= upper || lastMin <= upper) {\n // Last physical overlaps window, choose it\n return this._last;\n }\n // Window contains a physical item, but not the first or last\n let maxIdx = this._last, minIdx = this._first;\n\n while (true) {\n const candidateIdx = Math.round((maxIdx + minIdx) / 2),\n candidate = this._physicalItems.get(candidateIdx),\n cMin = candidate!.pos, cMax = cMin + candidate!.size;\n\n if ((cMin >= lower && cMin <= upper) ||\n (cMax >= lower && cMax <= upper)) {\n return candidateIdx;\n } else if (cMax < lower) {\n minIdx = candidateIdx + 1;\n } else if (cMin > upper) {\n maxIdx = candidateIdx - 1;\n }\n }\n }\n\n /**\n * Updates _first and _last based on items that should be in the current\n * viewed range.\n */\n _getActiveItems() {\n if (this._viewDim1 === 0 || this._totalItems === 0) {\n this._clearItems();\n } else {\n this._getItems();\n }\n }\n\n /**\n * Sets the range to empty.\n */\n _clearItems() {\n this._first = -1;\n this._last = -1;\n this._physicalMin = 0;\n this._physicalMax = 0;\n const items = this._newPhysicalItems;\n this._newPhysicalItems = this._physicalItems;\n this._newPhysicalItems.clear();\n this._physicalItems = items;\n this._stable = true;\n }\n\n /*\n * Updates _first and _last based on items that should be in the given range.\n */\n _getItems() {\n const items = this._newPhysicalItems;\n let lower, upper;\n\n // The anchorIdx is the anchor around which we reflow. It is designed to\n // allow jumping to any point of the scroll size. We choose it once and\n // stick with it until stable. first and last are deduced around it.\n\n if (this._scrollToIndex >= 0) {\n // If we have a scrollToIndex, we anchor on the given\n // index and set the scroll position accordingly\n this._anchorIdx = this._scrollToIndex;\n this._anchorPos = this._getPosition(this._anchorIdx);\n this._scrollIfNeeded();\n lower = Math.max(0, this._scrollPosition - this._overhang);\n upper = Math.min(this._scrollSize, this._scrollPosition + this._viewDim1 + this._overhang);\n }\n else {\n // Otherwise, we find an appropriate index to anchor on\n // given the current scroll position\n upper = Math.min(\n this._scrollSize,\n this._scrollPosition + this._viewDim1 + this._overhang\n );\n lower = Math.max(0, upper - this._viewDim1 - (2 * this._overhang));\n\n if (this._anchorIdx === null || this._anchorPos === null) {\n this._anchorIdx = this._getAnchor(lower, upper);\n this._anchorPos = this._getPosition(this._anchorIdx); \n }\n }\n\n let anchorSize = this._getSize(this._anchorIdx);\n if (anchorSize === undefined) {\n anchorSize = this._itemDim1;\n }\n\n // Anchor might be outside bounds, so prefer correcting the error and keep\n // that anchorIdx.\n let anchorErr = 0;\n\n if (this._anchorPos + anchorSize + this._spacing < lower) {\n anchorErr = lower - (this._anchorPos + anchorSize + this._spacing);\n }\n\n if (this._anchorPos > upper) {\n anchorErr = upper - this._anchorPos;\n }\n\n if (anchorErr) {\n this._scrollPosition -= anchorErr;\n lower -= anchorErr;\n upper -= anchorErr;\n this._scrollError += anchorErr;\n }\n\n // TODO @straversi: If size is always itemDim1, then why keep track of it?\n items.set(this._anchorIdx, {pos: this._anchorPos, size: anchorSize});\n\n this._first = (this._last = this._anchorIdx);\n this._physicalMin = (this._physicalMax = this._anchorPos);\n\n this._stable = true;\n\n while (this._physicalMin > lower && this._first > 0) {\n let size = this._getSize(--this._first);\n if (size === undefined) {\n this._stable = false;\n size = this._itemDim1;\n }\n const pos = (this._physicalMin -= size + this._spacing);\n items.set(this._first, {pos, size});\n if (this._stable === false && this._estimate === false) {\n break;\n }\n }\n\n while (this._physicalMax < upper && this._last < this._totalItems) {\n let size = this._getSize(this._last);\n if (size === undefined) {\n this._stable = false;\n size = this._itemDim1;\n }\n items.set(this._last++, {pos: this._physicalMax, size});\n if (this._stable === false && this._estimate === false) {\n break;\n } else {\n this._physicalMax += size + this._spacing;\n }\n }\n\n this._last--;\n\n // This handles the cases where we were relying on estimated sizes.\n const extentErr = this._calculateError();\n if (extentErr) {\n this._physicalMin -= extentErr;\n this._physicalMax -= extentErr;\n this._anchorPos -= extentErr;\n this._scrollPosition -= extentErr;\n items.forEach((item) => item.pos -= extentErr);\n this._scrollError += extentErr;\n }\n\n if (this._stable) {\n this._newPhysicalItems = this._physicalItems;\n this._newPhysicalItems.clear();\n this._physicalItems = items;\n }\n }\n\n _calculateError(): number {\n if (this._first === 0) {\n return this._physicalMin;\n } else if (this._physicalMin <= 0) {\n return this._physicalMin - (this._first * this._delta);\n } else if (this._last === this._totalItems - 1) {\n return this._physicalMax - this._scrollSize;\n } else if (this._physicalMax >= this._scrollSize) {\n return (\n (this._physicalMax - this._scrollSize) +\n ((this._totalItems - 1 - this._last) * this._delta));\n }\n return 0;\n }\n\n _updateScrollSize() {\n // Reuse previously calculated physical max, as it might be higher than the\n // estimated size.\n super._updateScrollSize();\n this._scrollSize = Math.max(this._physicalMax, this._scrollSize);\n }\n\n // TODO: Can this be made to inherit from base, with proper hooks?\n _reflow() {\n const {_first, _last, _scrollSize} = this;\n\n this._updateScrollSize();\n this._getActiveItems();\n\n if (this._scrollSize !== _scrollSize) {\n this._emitScrollSize();\n }\n\n this._updateVisibleIndices();\n this._emitRange();\n if (this._first === -1 && this._last === -1) {\n this._resetReflowState();\n } else if (\n this._first !== _first || this._last !== _last ||\n this._needsRemeasure) {\n this._emitChildPositions();\n this._emitScrollError();\n } else {\n this._emitChildPositions();\n this._emitScrollError();\n this._resetReflowState();\n }\n }\n\n _resetReflowState() {\n this._anchorIdx = null;\n this._anchorPos = null;\n this._stable = true;\n }\n\n /**\n * Returns the top and left positioning of the item at idx.\n */\n _getItemPosition(idx: number): Positions {\n return {\n [this._positionDim]: this._getPosition(idx),\n [this._secondaryPositionDim]: 0,\n } as Positions;\n }\n\n /**\n * Returns the height and width of the item at idx.\n */\n _getItemSize(idx: number): Size {\n return {\n [this._sizeDim]: this._getSize(idx) || this._itemDim1,\n [this._secondarySizeDim]: this._itemDim2,\n } as Size;\n }\n\n _viewDim2Changed() {\n this._needsRemeasure = true;\n this._scheduleReflow();\n }\n\n _emitRange() {\n const remeasure = this._needsRemeasure;\n const stable = this._stable;\n this._needsRemeasure = false;\n super._emitRange({remeasure, stable});\n }\n}\n"]} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dBase.d.ts b/lib/uni-virtualizer/lib/layouts/Layout1dBase.d.ts -index 1ec5e4adbfbaa9ea3e158dd0ee59cdecf46b943c..35db11a215e139c4527c726fa219dc3fae044ddc 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dBase.d.ts -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dBase.d.ts -@@ -1,5 +1,12 @@ --import { Layout, Positions, ScrollDirection, Size, dimension, position, LayoutConfig } from './Layout.js'; --export declare abstract class Layout1dBase implements Layout { -+import { Layout, Positions, ScrollDirection, Size, dimension, position } from './Layout.js'; -+declare type UpdateVisibleIndicesOptions = { -+ emit?: boolean; -+}; -+export interface Layout1dBaseConfig { -+ direction?: ScrollDirection; -+ totalItems?: number; -+} -+export declare abstract class Layout1dBase implements Layout { - /** - * The last set viewport scroll position. - */ -@@ -100,11 +107,11 @@ export declare abstract class Layout1dBase implements Layout { - */ - protected _overhang: number; - private _eventTarget; -- protected _spacingChanged: any; -- protected static _defaultConfig: LayoutConfig; -- constructor(config: any); -- set config(config: LayoutConfig); -- get config(): LayoutConfig; -+ protected _spacingChanged: boolean; -+ protected _defaultConfig: C; -+ constructor(config?: C); -+ set config(config: C); -+ get config(): C; - /** - * Maximum index of children + 1, to help estimate total height of the scroll - * space. -@@ -139,15 +146,15 @@ export declare abstract class Layout1dBase implements Layout { - /** - * Perform a reflow if one has been scheduled. - */ -- reflowIfNeeded(force: any): void; -+ reflowIfNeeded(force: boolean): void; - /** - * Scroll to the child at the given index, and the given position within that - * child. - */ -- scrollToIndex(index: any, position?: string): void; -- dispatchEvent(...args: any[]): Promise; -- addEventListener(...args: any[]): Promise; -- removeEventListener(...args: any[]): Promise; -+ scrollToIndex(index: number, position?: string): void; -+ dispatchEvent(evt: Event): Promise; -+ addEventListener(type: string, listener: EventListener | EventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined): Promise; -+ removeEventListener(type: string, callback: EventListener | EventListenerObject | null, options?: boolean | EventListenerOptions | undefined): Promise; - /** - * Get the top and left positioning of the item at idx. - */ -@@ -156,7 +163,7 @@ export declare abstract class Layout1dBase implements Layout { - * Update _first and _last based on items that should be in the current - * range. - */ -- abstract _getActiveItems(): any; -+ abstract _getActiveItems(): void; - protected _itemDim2Changed(): void; - protected _viewDim2Changed(): void; - protected _updateLayout(): void; -@@ -189,7 +196,7 @@ export declare abstract class Layout1dBase implements Layout { - */ - protected _updateScrollSize(): void; - protected _scrollIfNeeded(): void; -- protected _emitRange(inProps?: any): void; -+ protected _emitRange(inProps?: unknown): void; - protected _emitScrollSize(): void; - protected _emitScrollError(): void; - /** -@@ -206,7 +213,8 @@ export declare abstract class Layout1dBase implements Layout { - * Find the indices of the first and last items to intersect the viewport. - * Emit a visibleindiceschange event when either index changes. - */ -- protected _updateVisibleIndices(options?: any): void; -+ protected _updateVisibleIndices(options?: UpdateVisibleIndicesOptions): void; - private _scrollPositionChanged; - } -+export {}; - //# sourceMappingURL=Layout1dBase.d.ts.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dBase.d.ts.map b/lib/uni-virtualizer/lib/layouts/Layout1dBase.d.ts.map -index 29480cad4fb2479f471e00315614dcfacb36b07b..96c99bef00039d9fb14dc10e76bef00b227b912a 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dBase.d.ts.map -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dBase.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"Layout1dBase.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dBase.ts"],"names":[],"mappings":"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"} -\ No newline at end of file -+{"version":3,"file":"Layout1dBase.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dBase.ts"],"names":[],"mappings":"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"} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dBase.js b/lib/uni-virtualizer/lib/layouts/Layout1dBase.js -index 94efc6df12444102eb08c9ed14dda66c8a1d7d72..995c860ad93bf52395ebef55532a9f7ae43e73fb 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dBase.js -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dBase.js -@@ -28,6 +28,14 @@ export class Layout1dBase { - * top of the viewport. Value is a proportion of the item size. - */ - this._scrollToAnchor = 0; -+ /** -+ * The index of the first item intersecting the viewport. -+ */ -+ this._firstVisible = 0; -+ /** -+ * The index of the last item intersecting the viewport. -+ */ -+ this._lastVisible = 0; - this._eventTargetPromise = (EventTarget().then((Ctor) => { - this._eventTarget = new Ctor(); - })); -@@ -96,19 +104,20 @@ export class Layout1dBase { - // TODO (graynorton): Probably want to make this something we calculate based - // on viewport size, item size, other factors, possibly still with a dial of some kind - this._overhang = 1000; -- if (config) { -- this.config = config; -- } -+ this._eventTarget = null; -+ this._spacingChanged = false; -+ this._defaultConfig = { -+ direction: 'vertical' -+ }; -+ this.config = config || this._defaultConfig; - } - set config(config) { -- Object.assign(this, Object.assign({}, this.constructor._defaultConfig, config)); -+ Object.assign(this, Object.assign({}, this._defaultConfig, config)); - } - get config() { -- const config = {}; -- for (let key in this.constructor._defaultConfig) { -- config[key] = this[key]; -- } -- return config; -+ return { -+ direction: this.direction -+ }; - } - /** - * Maximum index of children + 1, to help estimate total height of the scroll -@@ -241,17 +250,17 @@ export class Layout1dBase { - } - this._scheduleReflow(); - } -- async dispatchEvent(...args) { -+ async dispatchEvent(evt) { - await this._eventTargetPromise; -- this._eventTarget.dispatchEvent(...args); -+ this._eventTarget.dispatchEvent(evt); - } -- async addEventListener(...args) { -+ async addEventListener(type, listener, options) { - await this._eventTargetPromise; -- this._eventTarget.addEventListener(...args); -+ this._eventTarget.addEventListener(type, listener, options); - } -- async removeEventListener(...args) { -+ async removeEventListener(type, callback, options) { - await this._eventTargetPromise; -- this._eventTarget.removeEventListener(...args); -+ this._eventTarget.removeEventListener(type, callback, options); - } - _itemDim2Changed() { - // Override -@@ -449,4 +458,4 @@ export class Layout1dBase { - } - } - } --Layout1dBase._defaultConfig = {}; -+//# sourceMappingURL=Layout1dBase.js.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dBase.js.map b/lib/uni-virtualizer/lib/layouts/Layout1dBase.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..be6a74593fea802779d275aef73f85858458c307 ---- /dev/null -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dBase.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"Layout1dBase.js","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dBase.ts"],"names":[],"mappings":"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","sourcesContent":["import EventTarget from '../polyfillLoaders/EventTarget.js';\nimport {Layout, Positions, ScrollDirection, Size, dimension, position} from './Layout.js';\n\ntype UpdateVisibleIndicesOptions = {\n emit?: boolean\n}\n\nexport interface Layout1dBaseConfig {\n direction?: ScrollDirection,\n totalItems?: number\n}\n\nexport abstract class Layout1dBase implements Layout {\n /**\n * The last set viewport scroll position.\n */\n private _latestCoords: Positions = {left: 0, top: 0};\n\n /**\n * Scrolling direction.\n */\n private _direction: ScrollDirection = 'vertical';\n\n /**\n * Dimensions of the viewport.\n */\n private _viewportSize: Size = {width: 0, height: 0};\n\n /**\n * Flag for debouncing asynchnronous reflow requests.\n */\n private _pendingReflow = false;\n\n private _pendingLayoutUpdate = false;\n\n /**\n * Index of the item that has been scrolled to via the public API. When the\n * container is otherwise scrolled, this value is set back to -1.\n */\n protected _scrollToIndex = -1;\n\n /**\n * When a child is scrolled to, the offset from the top of the child and the\n * top of the viewport. Value is a proportion of the item size.\n */\n private _scrollToAnchor = 0;\n\n /**\n * The index of the first item intersecting the viewport.\n */\n private _firstVisible = 0;\n\n /**\n * The index of the last item intersecting the viewport.\n */\n private _lastVisible = 0;\n\n private _eventTargetPromise: Promise = (EventTarget().then((Ctor) => {\n this._eventTarget = new Ctor();\n }));\n\n /**\n * Pixel offset in the scroll direction of the first child.\n */\n protected _physicalMin = 0;\n\n /**\n * Pixel offset in the scroll direction of the last child.\n */\n protected _physicalMax = 0;\n\n /**\n * Index of the first child.\n */\n protected _first = -1;\n\n /**\n * Index of the last child.\n */\n protected _last = -1;\n\n /**\n * The _estimated_ size of a child.\n */\n protected _itemSize: Size = {width: 100, height: 100};\n\n /**\n * Space in pixels between children.\n */\n protected _spacing = 0;\n\n /**\n * Length in the scrolling direction.\n */\n protected _sizeDim: dimension = 'height';\n\n /**\n * Length in the non-scrolling direction.\n */\n protected _secondarySizeDim: dimension = 'width';\n\n /**\n * Position in the scrolling direction.\n */\n protected _positionDim: position = 'top';\n\n /**\n * Position in the non-scrolling direction.\n */\n protected _secondaryPositionDim: position = 'left';\n\n /**\n * Current scroll offset in pixels.\n */\n protected _scrollPosition = 0;\n\n /**\n * Difference between current scroll offset and scroll offset calculated due\n * to a reflow.\n */\n protected _scrollError = 0;\n\n /**\n * Total number of items that could possibly be displayed. Used to help\n * calculate the scroll size.\n */\n protected _totalItems = 0;\n\n /**\n * The total (estimated) length of all items in the scrolling direction.\n */\n protected _scrollSize = 1;\n\n /**\n * Number of pixels beyond the visible size of the container to still include\n * in the active range of items.\n */\n // TODO (graynorton): Probably want to make this something we calculate based\n // on viewport size, item size, other factors, possibly still with a dial of some kind\n protected _overhang = 1000;\n\n private _eventTarget: EventTarget | null = null;\n protected _spacingChanged = false;\n\n protected _defaultConfig: C = {\n direction: 'vertical'\n } as C\n\n constructor(config?: C) {\n this.config = config || this._defaultConfig;\n }\n\n set config(config: C) {\n Object.assign(this, Object.assign({}, this._defaultConfig, config));\n }\n\n get config(): C {\n return {\n direction: this.direction\n } as C;\n }\n\n /**\n * Maximum index of children + 1, to help estimate total height of the scroll\n * space.\n */\n get totalItems(): number {\n return this._totalItems;\n }\n set totalItems(num) {\n const _num = Number(num);\n if (_num !== this._totalItems) {\n this._totalItems = _num;\n this._scheduleReflow();\n }\n }\n\n /**\n * Primary scrolling direction.\n */\n get direction(): ScrollDirection {\n return this._direction;\n }\n set direction(dir) {\n // Force it to be either horizontal or vertical.\n dir = (dir === 'horizontal') ? dir : 'vertical';\n if (dir !== this._direction) {\n this._direction = dir;\n this._sizeDim = (dir === 'horizontal') ? 'width' : 'height';\n this._secondarySizeDim = (dir === 'horizontal') ? 'height' : 'width';\n this._positionDim = (dir === 'horizontal') ? 'left' : 'top';\n this._secondaryPositionDim = (dir === 'horizontal') ? 'top' : 'left';\n this._scheduleLayoutUpdate();\n }\n }\n\n /**\n * Estimate of the dimensions of a single child.\n */\n get itemSize(): Size {\n return this._itemSize;\n }\n set itemSize(dims) {\n const {_itemDim1, _itemDim2} = this;\n Object.assign(this._itemSize, dims);\n if (_itemDim1 !== this._itemDim1 || _itemDim2 !== this._itemDim2) {\n if (_itemDim2 !== this._itemDim2) {\n this._itemDim2Changed();\n } else {\n this._scheduleLayoutUpdate();\n }\n }\n }\n\n /**\n * Amount of space in between items.\n */\n get spacing(): number {\n return this._spacing;\n }\n set spacing(px) {\n const _px = Number(px);\n if (_px !== this._spacing) {\n this._spacing = _px;\n this._scheduleLayoutUpdate();\n }\n }\n\n /**\n * Height and width of the viewport.\n */\n get viewportSize(): Size {\n return this._viewportSize;\n }\n set viewportSize(dims) {\n const {_viewDim1, _viewDim2} = this;\n Object.assign(this._viewportSize, dims);\n if (_viewDim2 !== this._viewDim2) {\n this._viewDim2Changed();\n } else if (_viewDim1 !== this._viewDim1) {\n this._checkThresholds();\n }\n }\n\n /**\n * Scroll offset of the viewport.\n */\n get viewportScroll(): Positions {\n return this._latestCoords;\n }\n set viewportScroll(coords) {\n Object.assign(this._latestCoords, coords);\n const oldPos = this._scrollPosition;\n this._scrollPosition = this._latestCoords[this._positionDim];\n if (oldPos !== this._scrollPosition) {\n this._scrollPositionChanged(oldPos, this._scrollPosition);\n this._updateVisibleIndices({emit: true});\n }\n this._checkThresholds();\n }\n\n /**\n * Perform a reflow if one has been scheduled.\n */\n reflowIfNeeded(force: boolean) {\n if (force || this._pendingReflow) {\n this._pendingReflow = false;\n this._reflow();\n }\n }\n\n /**\n * Scroll to the child at the given index, and the given position within that\n * child.\n */\n scrollToIndex(index: number, position = 'start') {\n if (!Number.isFinite(index))\n return;\n index = Math.min(this.totalItems, Math.max(0, index));\n this._scrollToIndex = index;\n if (position === 'nearest') {\n position = index > this._first + this._num / 2 ? 'end' : 'start';\n }\n switch (position) {\n case 'start':\n this._scrollToAnchor = 0;\n break;\n case 'center':\n this._scrollToAnchor = 0.5;\n break;\n case 'end':\n this._scrollToAnchor = 1;\n break;\n default:\n throw new TypeError(\n 'position must be one of: start, center, end, nearest');\n }\n this._scheduleReflow();\n }\n\n async dispatchEvent(evt: Event) {\n await this._eventTargetPromise;\n this._eventTarget!.dispatchEvent(evt);\n }\n\n async addEventListener(type: string, listener: EventListener | EventListenerObject | null, options?: boolean | AddEventListenerOptions | undefined) {\n await this._eventTargetPromise;\n this._eventTarget!.addEventListener(type, listener, options);\n }\n\n async removeEventListener(type: string, callback: EventListener | EventListenerObject | null, options?: boolean | EventListenerOptions | undefined) {\n await this._eventTargetPromise;\n this._eventTarget!.removeEventListener(type, callback, options);\n }\n\n /**\n * Get the top and left positioning of the item at idx.\n */\n abstract _getItemPosition(idx: number): Positions;\n\n /**\n * Update _first and _last based on items that should be in the current\n * range.\n */\n abstract _getActiveItems(): void;\n\n protected _itemDim2Changed() {\n // Override\n }\n\n protected _viewDim2Changed() {\n // Override\n }\n\n protected _updateLayout() {\n // Override\n }\n\n protected _getItemSize(_idx: number): Size {\n return {\n [this._sizeDim]: this._itemDim1,\n [this._secondarySizeDim]: this._itemDim2,\n } as unknown as Size;\n }\n\n /**\n * The size of an item in the scrolling direction + space between items.\n */\n protected get _delta(): number {\n return this._itemDim1 + this._spacing;\n }\n\n /**\n * The height or width of an item, whichever corresponds to the scrolling direction.\n */\n protected get _itemDim1(): number {\n return this._itemSize[this._sizeDim];\n }\n\n /**\n * The height or width of an item, whichever does NOT correspond to the scrolling direction.\n */\n protected get _itemDim2(): number {\n return this._itemSize[this._secondarySizeDim];\n }\n\n /**\n * The height or width of the viewport, whichever corresponds to the scrolling direction.\n */\n protected get _viewDim1(): number {\n return this._viewportSize[this._sizeDim];\n }\n\n /**\n * The height or width of the viewport, whichever does NOT correspond to the scrolling direction.\n */\n protected get _viewDim2(): number {\n return this._viewportSize[this._secondarySizeDim];\n }\n\n protected _scheduleReflow() {\n this._pendingReflow = true;\n }\n\n protected _scheduleLayoutUpdate() {\n this._pendingLayoutUpdate = true;\n this._scheduleReflow();\n }\n\n protected _reflow() {\n const {_first, _last, _scrollSize} = this;\n\n if (this._pendingLayoutUpdate) {\n this._updateLayout();\n this._pendingLayoutUpdate = false;\n }\n this._updateScrollSize();\n this._getActiveItems();\n this._scrollIfNeeded();\n this._updateVisibleIndices();\n\n if (this._scrollSize !== _scrollSize) {\n this._emitScrollSize();\n }\n\n if (this._first === -1 && this._last === -1) {\n // TODO: have default empty object for emitRange instead\n this._emitRange();\n } else if (\n this._first !== _first || this._last !== _last ||\n this._spacingChanged) {\n // TODO: have default empty object for emitRange instead\n this._emitRange();\n this._emitChildPositions();\n }\n this._emitScrollError();\n }\n\n /**\n * Estimates the total length of all items in the scrolling direction, including spacing.\n */\n protected _updateScrollSize() {\n // Ensure we have at least 1px - this allows getting at least 1 item to be\n // rendered.\n this._scrollSize = Math.max(1, this._totalItems * this._delta);\n }\n\n protected _scrollIfNeeded() {\n if (this._scrollToIndex === -1) {\n return;\n }\n const index = this._scrollToIndex;\n const anchor = this._scrollToAnchor;\n const pos = this._getItemPosition(index)[this._positionDim];\n const size = this._getItemSize(index)[this._sizeDim];\n\n const curAnchorPos = this._scrollPosition + this._viewDim1 * anchor;\n const newAnchorPos = pos + size * anchor;\n // Ensure scroll position is an integer within scroll bounds.\n const scrollPosition = Math.floor(Math.min(\n this._scrollSize - this._viewDim1,\n Math.max(0, this._scrollPosition - curAnchorPos + newAnchorPos)));\n this._scrollError += this._scrollPosition - scrollPosition;\n this._scrollPosition = scrollPosition;\n }\n\n protected _emitRange(inProps: unknown = undefined) {\n const detail = Object.assign(\n {\n first: this._first,\n last: this._last,\n num: this._num,\n stable: true,\n firstVisible: this._firstVisible,\n lastVisible: this._lastVisible,\n },\n inProps);\n this.dispatchEvent(new CustomEvent('rangechange', {detail}));\n }\n\n protected _emitScrollSize() {\n const detail = {\n [this._sizeDim]: this._scrollSize,\n };\n this.dispatchEvent(new CustomEvent('scrollsizechange', {detail}));\n }\n\n protected _emitScrollError() {\n if (this._scrollError) {\n const detail = {\n [this._positionDim]: this._scrollError,\n [this._secondaryPositionDim]: 0,\n };\n this.dispatchEvent(new CustomEvent('scrollerrorchange', {detail}));\n this._scrollError = 0;\n }\n }\n\n /**\n * Get or estimate the top and left positions of items in the current range.\n * Emit an itempositionchange event with these positions.\n */\n protected _emitChildPositions() {\n const detail: {[key: number]: Positions} = {};\n for (let idx = this._first; idx <= this._last; idx++) {\n detail[idx] = this._getItemPosition(idx);\n }\n this.dispatchEvent(new CustomEvent('itempositionchange', {detail}));\n }\n\n /**\n * Number of items to display.\n */\n private get _num(): number {\n if (this._first === -1 || this._last === -1) {\n return 0;\n }\n return this._last - this._first + 1;\n }\n\n private _checkThresholds() {\n if (this._viewDim1 === 0 && this._num > 0) {\n this._scheduleReflow();\n } else {\n const min = Math.max(0, this._scrollPosition - this._overhang);\n const max = Math.min(\n this._scrollSize,\n this._scrollPosition + this._viewDim1 + this._overhang);\n if (this._physicalMin > min || this._physicalMax < max) {\n this._scheduleReflow();\n }\n }\n }\n\n /**\n * Find the indices of the first and last items to intersect the viewport.\n * Emit a visibleindiceschange event when either index changes.\n */\n protected _updateVisibleIndices(options?: UpdateVisibleIndicesOptions) {\n if (this._first === -1 || this._last === -1) return;\n\n let firstVisible = this._first;\n while (\n Math.round(\n this._getItemPosition(firstVisible)[this._positionDim] +\n this._getItemSize(firstVisible)[this._sizeDim]\n )\n <=\n Math.round (this._scrollPosition)\n ) {\n firstVisible++;\n }\n\n let lastVisible = this._last;\n while (\n Math.round(this._getItemPosition(lastVisible)[this._positionDim])\n >=\n Math.round(this._scrollPosition + this._viewDim1)\n ) {\n lastVisible--;\n }\n\n if (firstVisible !== this._firstVisible || lastVisible !== this._lastVisible) {\n this._firstVisible = firstVisible;\n this._lastVisible = lastVisible;\n if (options && options.emit) {\n this._emitRange();\n }\n }\n }\n\n private _scrollPositionChanged(oldPos: number, newPos: number) {\n // When both values are bigger than the max scroll position, keep the\n // current _scrollToIndex, otherwise invalidate it.\n const maxPos = this._scrollSize - this._viewDim1;\n if (oldPos < maxPos || newPos < maxPos) {\n this._scrollToIndex = -1;\n }\n }\n}\n"]} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dFlex.d.ts b/lib/uni-virtualizer/lib/layouts/Layout1dFlex.d.ts -index b4f6e54f539aef2a2ecd0399237e3d59a57a8a1f..e6aadd4b26ce97a862ca1051c6ecb08467347162 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dFlex.d.ts -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dFlex.d.ts -@@ -1,11 +1,14 @@ --import { Layout1dBase } from './Layout1dBase'; --import { ItemBox, Positions, Size, LayoutConfig, Type } from './Layout'; --export interface Layout1dFlexConfig extends LayoutConfig { -- type?: Type; -- direction?: "horizontal" | "vertical"; -+import { Layout1dBase, Layout1dBaseConfig } from './Layout1dBase'; -+import { ItemBox, Positions, Size } from './Layout'; -+interface Layout1dFlexConfig extends Layout1dBaseConfig { - spacing?: number; - idealSize?: number; - } -+declare type Layout1dFlexSpecifier = Layout1dFlexConfig & { -+ type: new (config?: Layout1dFlexConfig) => Layout1dFlex; -+}; -+declare type Layout1dFlexSpecifierFactory = (config?: Layout1dFlexConfig) => Layout1dFlexSpecifier; -+export declare const layout1dFlex: Layout1dFlexSpecifierFactory; - interface Rolumn { - _startIdx: number; - _endIdx: number; -@@ -21,35 +24,33 @@ interface Chunk { - /** - * TODO @straversi: document and test this Layout. - */ --export declare class Layout1dFlex extends Layout1dBase { -+export declare class Layout1dFlex extends Layout1dBase { - private _itemSizes; - private _chunkSize; - private _chunks; - private _aspectRatios; - private _numberOfAspectRatiosMeasured; -- protected _idealSize: number; -+ protected _idealSize: number | null; - protected _config: Layout1dFlexConfig; -- protected static _defaultConfig: Layout1dFlexConfig; -+ protected _defaultConfig: Layout1dFlexConfig; - listenForChildLoadEvents: boolean; -- measureChildren: ((e: Element, i: object) => object); -- set idealSize(px: number); -- get idealSize(): number; -+ /** -+ * TODO graynorton@ Don't hard-code Flickr - probably need a config option -+ */ -+ measureChildren: ((e: Element, i: unknown) => (ItemBox)); -+ set idealSize(px: number | null); -+ get idealSize(): number | null; - updateItemSizes(sizes: { - [key: number]: ItemBox; - }): void; - _newChunk(): { -- _rolumns: any[]; -- _itemPositions: any[]; -+ _rolumns: never[]; -+ _itemPositions: never[]; - _size: number; - _dirty: boolean; - }; -- _getChunk(idx: number | string): { -- _rolumns: any[]; -- _itemPositions: any[]; -- _size: number; -- _dirty: boolean; -- }; -- _recordAspectRatio(dims: any): void; -+ _getChunk(idx: number | string): Chunk; -+ _recordAspectRatio(dims: ItemBox): void; - _getRandomAspectRatio(): Size; - _viewDim2Changed(): void; - _getActiveItems(): void; -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dFlex.d.ts.map b/lib/uni-virtualizer/lib/layouts/Layout1dFlex.d.ts.map -index a899016165645dce6a8326b41733b024ed5d4e3d..de4bacef32254ea5dd92cfff32da4d1c83d31950 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dFlex.d.ts.map -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dFlex.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"Layout1dFlex.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dFlex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAC,MAAM,UAAU,CAAC;AAEtE,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,IAAI,CAAC,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IAC1B,SAAS,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,UAAU,MAAM;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,UAAU,KAAK;IACb,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACjC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAA;CAChB;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAC5C,OAAO,CAAC,UAAU,CAAmB;IAIrC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,6BAA6B,CAAa;IAClD,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,OAAO,EAAE,kBAAkB,CAAM;IAC3C,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,kBAAkB,CAIlD;IAED,wBAAwB,UAAQ;IAEhC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,CAKnD;IAED,IAAI,SAAS,CAAC,EAAE,QAAA,EAMf;IAED,IAAI,SAAS,WAEZ;IAED,eAAe,CAAC,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAC;IAoB/C,SAAS;;;;;;IAST,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;;;;;;IAI9B,kBAAkB,CAAC,IAAI,KAAA;IAavB,qBAAqB,IAAI,IAAI;IAa7B,gBAAgB;IAIhB,eAAe;IA2Bf,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;IAKxC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAM/B,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAStC,YAAY,CAAC,QAAQ,EAAE,MAAM;IA+D7B,aAAa,IAAI,IAAI;IAYrB,iBAAiB;CAOlB"} -\ No newline at end of file -+{"version":3,"file":"Layout1dFlex.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dFlex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,kBAAkB,EAAC,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAC,MAAM,UAAU,CAAC;AAElD,UAAU,kBAAmB,SAAQ,kBAAkB;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,aAAK,qBAAqB,GAAG,kBAAkB,GAAG;IAChD,IAAI,EAAE,KAAI,MAAM,CAAC,EAAE,kBAAkB,KAAK,YAAY,CAAA;CACvD,CAAA;AAED,aAAK,4BAA4B,GAAG,CAAC,MAAM,CAAC,EAAE,kBAAkB,KAAK,qBAAqB,CAAC;AAE3F,eAAO,MAAM,YAAY,EAAE,4BAEjB,CAAC;AAEX,UAAU,MAAM;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,UAAU,KAAK;IACb,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACjC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAA;CAChB;AAeD;;GAEG;AACH,qBAAa,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IAChE,OAAO,CAAC,UAAU,CAAmB;IAIrC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,aAAa,CAAoB;IACzC,OAAO,CAAC,6BAA6B,CAAa;IAClD,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IAC3C,SAAS,CAAC,OAAO,EAAE,kBAAkB,CAAM;IAC3C,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAGzC;IAEH,wBAAwB,UAAQ;IAElC;;OAEG;IACD,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAUvD;IAED,IAAI,SAAS,CAAC,EAAE,eAAA,EAMf;IAED,IAAI,SAAS,kBAEZ;IAED,eAAe,CAAC,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAC;IAqB/C,SAAS;;;;;;IAST,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAI9B,kBAAkB,CAAC,IAAI,EAAE,OAAO;IAahC,qBAAqB,IAAI,IAAI;IAa7B,gBAAgB;IAIhB,eAAe;IA2Bf,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;IAKxC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAM/B,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAStC,YAAY,CAAC,QAAQ,EAAE,MAAM;IA+D7B,aAAa,IAAI,IAAI;IAYrB,iBAAiB;CAOlB"} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dFlex.js b/lib/uni-virtualizer/lib/layouts/Layout1dFlex.js -index 567ee9bc28f6b65eff52d1d853d150f6ca99fb12..00b54b50b3e4977d706846e4f139a6e91f15c615 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dFlex.js -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dFlex.js -@@ -1,4 +1,7 @@ - import { Layout1dBase } from './Layout1dBase'; -+export const layout1dFlex = (config) => Object.assign({ -+ type: Layout1dFlex -+}, config); - /** - * TODO @straversi: document and test this Layout. - */ -@@ -6,16 +9,33 @@ export class Layout1dFlex extends Layout1dBase { - constructor() { - super(...arguments); - this._itemSizes = []; -+ // private _itemPositions: Array = []; -+ // private _rolumnStartIdx: Array = []; -+ // private _rolumnStartPos: Array = []; -+ this._chunkSize = null; - this._chunks = []; - this._aspectRatios = {}; - this._numberOfAspectRatiosMeasured = 0; -+ this._idealSize = null; - this._config = {}; -+ this._defaultConfig = Object.assign({}, super._defaultConfig, { -+ spacing: 0, -+ idealSize: 200 -+ }); - this.listenForChildLoadEvents = true; -+ /** -+ * TODO graynorton@ Don't hard-code Flickr - probably need a config option -+ */ - this.measureChildren = function (e, i) { -- return { -- width: i['o_width'] || e.naturalWidth || undefined, -- height: i['o_height'] || e.naturalHeight || undefined -- }; -+ const { naturalWidth, naturalHeight } = e; -+ if (naturalWidth !== undefined && naturalHeight != undefined) { -+ return { width: naturalWidth, height: naturalHeight }; -+ } -+ const { o_width, o_height } = i; -+ if (o_width !== undefined && o_height !== undefined) { -+ return { width: o_width, height: o_height }; -+ } -+ return { width: -1, height: -1 }; - }; - } - set idealSize(px) { -@@ -31,15 +51,16 @@ export class Layout1dFlex extends Layout1dBase { - updateItemSizes(sizes) { - let dirty; - Object.keys(sizes).forEach((key) => { -- const chunk = this._getChunk(key); -- const dims = sizes[key]; -- const prevDims = this._itemSizes[key]; -+ const n = Number(key); -+ const chunk = this._getChunk(n); -+ const dims = sizes[n]; -+ const prevDims = this._itemSizes[n]; - if (dims.width && dims.height) { - if (!prevDims || prevDims.width !== dims.width || prevDims.height !== dims.height) { - chunk._dirty = true; - dirty = true; -- this._itemSizes[Number(key)] = sizes[key]; -- this._recordAspectRatio(sizes[key]); -+ this._itemSizes[n] = sizes[n]; -+ this._recordAspectRatio(sizes[n]); - } - } - }); -@@ -121,7 +142,7 @@ export class Layout1dFlex extends Layout1dBase { - } - _getNaturalItemDims(idx) { - let itemDims = this._itemSizes[idx]; -- if (itemDims === undefined || itemDims.width === undefined || itemDims.height === undefined) { -+ if (itemDims === undefined || itemDims.width === -1 || itemDims.height === -1) { - itemDims = this._getRandomAspectRatio(); - } - return itemDims; -@@ -208,8 +229,4 @@ export class Layout1dFlex extends Layout1dBase { - // (this._spacing * 2); - } - } --Layout1dFlex._defaultConfig = { -- direction: 'vertical', -- spacing: 0, -- idealSize: 200 --}; -+//# sourceMappingURL=Layout1dFlex.js.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dFlex.js.map b/lib/uni-virtualizer/lib/layouts/Layout1dFlex.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..1d93aa6da4cc4a62ce585a3d6261549773c8d855 ---- /dev/null -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dFlex.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"Layout1dFlex.js","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dFlex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAqB,MAAM,gBAAgB,CAAC;AAchE,MAAM,CAAC,MAAM,YAAY,GAAiC,CAAC,MAA2B,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;IACvG,IAAI,EAAE,YAAY;CACnB,EAAE,MAAM,CAAC,CAAC;AA6BX;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,YAAgC;IAAlE;;QACU,eAAU,GAAgB,EAAE,CAAC;QACrC,iDAAiD;QACjD,+CAA+C;QAC/C,+CAA+C;QACvC,eAAU,GAAkB,IAAI,CAAC;QACjC,YAAO,GAAiB,EAAE,CAAC;QAC3B,kBAAa,GAAiB,EAAE,CAAC;QACjC,kCAA6B,GAAW,CAAC,CAAC;QACxC,eAAU,GAAkB,IAAI,CAAC;QACjC,YAAO,GAAuB,EAAE,CAAC;QACjC,mBAAc,GAAuB,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,cAAc,EAAE;YACrF,OAAO,EAAE,CAAC;YACV,SAAS,EAAE,GAAG;SACf,CAAC,CAAC;QAEH,6BAAwB,GAAG,IAAI,CAAC;QAElC;;WAEG;QACD,oBAAe,GAA4C,UAAU,CAAC,EAAE,CAAC;YACvE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,CAAqB,CAAC;YAC9D,IAAI,YAAY,KAAK,SAAS,IAAI,aAAa,IAAI,SAAS,EAAE;gBAC5D,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;aACvD;YACD,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAoB,CAAC;YACnD,IAAI,OAAO,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE;gBACnD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;aAC7C;YACD,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;QACnC,CAAC,CAAA;IA+MH,CAAC;IA7MC,IAAI,SAAS,CAAC,EAAE;QACd,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;QACvB,IAAI,GAAG,KAAK,IAAI,CAAC,UAAU,EAAE;YAC3B,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;YACtB,IAAI,CAAC,qBAAqB,EAAE,CAAC;SAC9B;IACH,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,eAAe,CAAC,KAA+B;QAC7C,IAAI,KAAK,CAAC;QACV,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YAC/B,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YACtB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAChC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YACpC,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE;gBAC7B,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE;oBACjF,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;oBACpB,KAAK,GAAG,IAAI,CAAC;oBACb,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;oBAC9B,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;iBACnC;aACF;QACL,CAAC,CAAC,CAAC;QACH,IAAI,KAAK,EAAE;YACT,IAAI,CAAC,qBAAqB,EAAE,CAAC;SAC9B;IACH,CAAC;IAED,SAAS;QACP,OAAO;YACL,CAAC,UAAU,CAAC,EAAE,EAAE;YAChB,cAAc,EAAE,EAAE;YAClB,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,KAAK;SACd,CAAA;IACH,CAAC;IAED,SAAS,CAAC,GAAoB;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,UAAW,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;IACtF,CAAC;IAED,kBAAkB,CAAC,IAAa;QAC9B,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE;YAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;YAC9D,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE;gBAC9B,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;aAC9B;iBACI;gBACH,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;aAChC;YACD,IAAI,CAAC,6BAA6B,EAAE,CAAC;SACtC;IACH,CAAC;IAED,qBAAqB;QACnB,IAAI,IAAI,CAAC,6BAA6B,KAAK,CAAC,EAAE;YAC5C,OAAO,EAAC,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAC,CAAC;SAC9B;QACD,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,6BAA6B,CAAC;QAC7D,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAChD,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE;YAClC,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;SACvC;QACD,OAAO,EAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAC,CAAC;IAClD,CAAC;IAEC,gBAAgB;QACd,IAAI,CAAC,qBAAqB,EAAE,CAAC;IAC/B,CAAC;IAED,eAAe;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QACxC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QACjG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAChB,IAAI,CAAC,WAAW,EAChB,SAAS,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClF,IAAI,GAAG,GAAG,YAAY,CAAC;QACvB,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE;YACxC,GAAG,EAAE,CAAC;SACT;QACD,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE;YACxC,GAAG,EAAE,CAAC;SACT;QACD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC;QAC5C,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC;QAClD,IAAI,SAAS,CAAC;QACd,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE;YACxG,GAAG,EAAE,CAAC;SACT;QACD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;QACzC,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;IAChC,CAAC;IAED,gBAAgB,CAAC,GAAW;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAChC,OAAO,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;IAED,YAAY,CAAC,GAAW;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,EAAC,KAAK,EAAE,MAAM,EAAC,GAAG,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAClD,OAAO,EAAC,KAAK,EAAE,MAAM,EAAS,CAAC;IACjC,CAAC;IAED,mBAAmB,CAAC,GAAW;QAC7B,IAAI,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,KAAK,KAAK,CAAC,CAAC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE;YAC7E,QAAQ,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;SACzC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAGD,YAAY,CAAC,QAAgB;QAC3B,MAAM,KAAK,GAAU,IAAI,CAAC,SAAS,EAAE,CAAC;QACtC,IAAI,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC7B,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,SAAS,GAAG,QAAQ,CAAC;QACzB,MAAM,YAAY,GAAG,CAAC,OAAe,EAAE,EAAE;YACrC,MAAM,MAAM,GAAG;gBACb,SAAS,EAAE,QAAQ;gBACnB,OAAO,EAAE,OAAO;gBAChB,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC,QAAQ;gBACnC,KAAK,EAAE,CAAC;aACT,CAAA;YACD,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC5B,IAAI,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC;YACjC,KAAK,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC,IAAI,OAAO,EAAE,CAAC,EAAE,EAAE;gBACtC,MAAM,GAAG,GAAG,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;gBACpC,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,KAAM,GAAG,SAAS,CAAC;gBACnC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,MAAO,GAAG,SAAS,CAAC;gBACrC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC;gBAClE,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC;gBAChE,YAAY,IAAI,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;aAChE;YACD,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAE,CAAC;QACjE,CAAC,CAAA;QACD,OAAO,GAAG,GAAG,IAAI,CAAC,UAAW,EAAE;YAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;YAC/C,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC;YAC/E,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACzC,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YACnD,MAAM,gBAAgB,GAAG,IAAI,CAAC,UAAW,GAAG,QAAQ,CAAC;YACrD,MAAM,WAAW,GAAG,gBAAgB,GAAG,QAAQ,CAAC;YAChD,MAAM,YAAY,GAAG,gBAAgB,GAAG,SAAS,CAAC;YAClD,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG;gBACxB,IAAI,EAAE,CAAC;gBACP,GAAG,EAAE,CAAC;gBACN,KAAK,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC;gBAC/D,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC;aACpE,CAAC;YACF,MAAM,KAAK,GAAG,cAAc,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,CAAC;YAC5D,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE;gBAC/C,4CAA4C;gBAC5C,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;gBACtB,QAAQ,GAAG,GAAG,CAAC;gBACf,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAW,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;gBAC3D,SAAS,GAAG,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,YAAY,CAAC;gBAClE,WAAW,GAAG,YAAY,CAAC;aAC9B;iBACI;gBACD,6BAA6B;gBAC7B,WAAW,IAAI,YAAY,CAAC;gBAC5B,SAAS,GAAG,KAAK,CAAC;aACrB;YACD,IAAI,GAAG,KAAK,IAAI,CAAC,UAAW,GAAG,CAAC,EAAE;gBAC9B,YAAY,CAAC,GAAG,CAAC,CAAC;aACrB;YACD,GAAG,EAAE,CAAC;SACP;QACD,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC7D,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC;QACtD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,aAAa;QACX,KAAI,yCAA0C,IAAI,CAAC,SAAS,KAAK,CAAC;YAAE,OAAO;QAC3E,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,UAAW,GAAG,IAAI,CAAC,UAAW,CAAC,CAAC,CAAC;QAC3G,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1C,wGAAwG;QACxG,eAAe;QACf,mDAAmD;QACnD,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACvC,oDAAoD;QACpD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;IAChC,CAAC;IAEC,iBAAiB;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,CAAC,WAAW,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7F,wDAAwD;QACxD,mEAAmE;QACnE,uBAAuB;IAC7B,CAAC;CACF","sourcesContent":["import {Layout1dBase, Layout1dBaseConfig} from './Layout1dBase';\nimport {ItemBox, Positions, Size} from './Layout';\n\ninterface Layout1dFlexConfig extends Layout1dBaseConfig {\n spacing?: number,\n idealSize?: number\n}\n\ntype Layout1dFlexSpecifier = Layout1dFlexConfig & {\n type: new(config?: Layout1dFlexConfig) => Layout1dFlex\n}\n\ntype Layout1dFlexSpecifierFactory = (config?: Layout1dFlexConfig) => Layout1dFlexSpecifier;\n\nexport const layout1dFlex: Layout1dFlexSpecifierFactory = (config?: Layout1dFlexConfig) => Object.assign({\n type: Layout1dFlex\n}, config);\n\ninterface Rolumn {\n _startIdx: number,\n _endIdx: number,\n _startPos: number,\n _size: number\n}\n\ninterface Chunk {\n _itemPositions: Array,\n _rolumns: Array,\n _size: number,\n _dirty: boolean\n}\n\ninterface AspectRatios {\n // conceptually, key is a number, but strictly speaking it's a string\n [key: string]: number\n}\n\n/**\n * TODO graynorton@ Don't hard-code Flickr - probably need a config option\n */\n interface FlickrImageData {\n o_width: number,\n o_height: number\n}\n\n/**\n * TODO @straversi: document and test this Layout.\n */\nexport class Layout1dFlex extends Layout1dBase {\n private _itemSizes: Array = [];\n // private _itemPositions: Array = [];\n // private _rolumnStartIdx: Array = [];\n // private _rolumnStartPos: Array = [];\n private _chunkSize: number | null = null;\n private _chunks: Array = [];\n private _aspectRatios: AspectRatios = {};\n private _numberOfAspectRatiosMeasured: number = 0;\n protected _idealSize: number | null = null;\n protected _config: Layout1dFlexConfig = {};\n protected _defaultConfig: Layout1dFlexConfig = Object.assign({}, super._defaultConfig, {\n spacing: 0,\n idealSize: 200\n });\n\n listenForChildLoadEvents = true;\n\n/**\n * TODO graynorton@ Don't hard-code Flickr - probably need a config option\n */\n measureChildren: ((e: Element, i: unknown) => (ItemBox)) = function (e, i) {\n const { naturalWidth, naturalHeight } = e as HTMLImageElement;\n if (naturalWidth !== undefined && naturalHeight != undefined) {\n return { width: naturalWidth, height: naturalHeight };\n }\n const { o_width, o_height } = i as FlickrImageData;\n if (o_width !== undefined && o_height !== undefined) {\n return { width: o_width, height: o_height };\n }\n return { width: -1, height: -1 };\n }\n\n set idealSize(px) {\n const _px = Number(px);\n if (_px !== this._idealSize) {\n this._idealSize = _px;\n this._scheduleLayoutUpdate();\n }\n }\n\n get idealSize() {\n return this._idealSize;\n }\n\n updateItemSizes(sizes: {[key: number]: ItemBox}) {\n let dirty;\n Object.keys(sizes).forEach((key) => {\n const n = Number(key);\n const chunk = this._getChunk(n);\n const dims = sizes[n];\n const prevDims = this._itemSizes[n];\n if (dims.width && dims.height) {\n if (!prevDims || prevDims.width !== dims.width || prevDims.height !== dims.height) {\n chunk._dirty = true;\n dirty = true;\n this._itemSizes[n] = sizes[n];\n this._recordAspectRatio(sizes[n]);\n }\n }\n });\n if (dirty) {\n this._scheduleLayoutUpdate();\n }\n }\n\n _newChunk() {\n return {\n ['_rolumns']: [],\n _itemPositions: [],\n _size: 0,\n _dirty: false \n }\n }\n\n _getChunk(idx: number | string) {\n return this._chunks[Math.floor(Number(idx) / this._chunkSize!)] || this._newChunk();\n }\n\n _recordAspectRatio(dims: ItemBox) {\n if (dims.width && dims.height) {\n const bucket = Math.round(dims.width / dims.height * 10) / 10;\n if (this._aspectRatios[bucket]) {\n this._aspectRatios[bucket]++;\n }\n else {\n this._aspectRatios[bucket] = 1;\n }\n this._numberOfAspectRatiosMeasured++; \n }\n }\n\n _getRandomAspectRatio(): Size {\n if (this._numberOfAspectRatiosMeasured === 0) {\n return {width: 1, height: 1};\n }\n const n = Math.random() * this._numberOfAspectRatiosMeasured;\n const buckets = Object.keys(this._aspectRatios);\n let i = -1, m = 0;\n while (m < n && i < buckets.length) {\n m += this._aspectRatios[buckets[++i]];\n }\n return {width: Number(buckets[i]), height: 1};\n}\n\n _viewDim2Changed() {\n this._scheduleLayoutUpdate();\n }\n\n _getActiveItems() {\n const chunk = this._getChunk(0);\n if (chunk._rolumns.length === 0) return;\n const scrollPos = Math.max(0, Math.min(this._scrollPosition, this._scrollSize - this._viewDim1));\n const min = Math.max(0, scrollPos - this._overhang);\n const max = Math.min(\n this._scrollSize,\n scrollPos + this._viewDim1 + this._overhang);\n const mid = (min + max) / 2;\n const estMidRolumn = Math.round((mid / this._scrollSize) * chunk._rolumns.length);\n let idx = estMidRolumn;\n while (chunk._rolumns[idx]._startPos < min) {\n idx++;\n }\n while (chunk._rolumns[idx]._startPos > min) {\n idx--;\n }\n this._first = chunk._rolumns[idx]._startIdx;\n this._physicalMin = chunk._rolumns[idx]._startPos;\n let rolumnMax;\n while ((rolumnMax = chunk._rolumns[idx]._startPos + chunk._rolumns[idx]._size + (this._spacing * 2)) < max) {\n idx++;\n }\n this._last = chunk._rolumns[idx]._endIdx;\n this._physicalMax = rolumnMax;\n }\n\n _getItemPosition(idx: number): Positions {\n const chunk = this._getChunk(0);\n return chunk._itemPositions[idx];\n }\n\n _getItemSize(idx: number): Size {\n const chunk = this._getChunk(0);\n const {width, height} = chunk._itemPositions[idx];\n return {width, height} as Size;\n }\n\n _getNaturalItemDims(idx: number): Size {\n let itemDims = this._itemSizes[idx];\n if (itemDims === undefined || itemDims.width === -1 || itemDims.height === -1) {\n itemDims = this._getRandomAspectRatio();\n }\n return itemDims;\n }\n\n\n _layOutChunk(startIdx: number) {\n const chunk: Chunk = this._newChunk();\n let startPos = this._spacing;\n let idx = 0;\n let rolumnSize2 = 0;\n let lastRatio = Infinity;\n const finishRolumn = (lastIdx: number) => {\n const rolumn = {\n _startIdx: startIdx,\n _endIdx: lastIdx,\n _startPos: startPos - this._spacing,\n _size: 0\n }\n chunk._rolumns.push(rolumn);\n let itemStartPos = this._spacing;\n for (let i = startIdx; i <= lastIdx; i++) {\n const pos = chunk._itemPositions[i];\n pos.width = pos.width! * lastRatio;\n pos.height = pos.height! * lastRatio;\n pos.left = this._positionDim === 'left' ? startPos : itemStartPos;\n pos.top = this._positionDim === 'top' ? startPos : itemStartPos;\n itemStartPos += pos[this._secondarySizeDim]! + this._spacing;\n }\n rolumn._size = chunk._itemPositions[lastIdx][this._sizeDim]!;\n }\n while (idx < this._chunkSize!) {\n const itemDims = this._getNaturalItemDims(idx);\n const availableSpace = this._viewDim2 - (this._spacing * (idx - startIdx + 2));\n const itemSize = itemDims[this._sizeDim];\n const itemSize2 = itemDims[this._secondarySizeDim];\n const idealScaleFactor = this._idealSize! / itemSize;\n const adjItemSize = idealScaleFactor * itemSize;\n const adjItemSize2 = idealScaleFactor * itemSize2;\n chunk._itemPositions[idx] = {\n left: 0,\n top: 0,\n width: (this._sizeDim === 'width' ? adjItemSize : adjItemSize2),\n height: (this._sizeDim === 'height' ? adjItemSize : adjItemSize2)\n };\n const ratio = availableSpace / (rolumnSize2 + adjItemSize2);\n if (Math.abs(1 - ratio) > Math.abs(1 - lastRatio)) {\n // rolumn is better without adding this item\n finishRolumn(idx - 1);\n startIdx = idx;\n startPos += (this._idealSize! * lastRatio) + this._spacing;\n lastRatio = (this._viewDim2 - (2 * this._spacing)) / adjItemSize2;\n rolumnSize2 = adjItemSize2;\n }\n else {\n // add this item and continue\n rolumnSize2 += adjItemSize2;\n lastRatio = ratio;\n }\n if (idx === this._chunkSize! - 1) {\n finishRolumn(idx);\n }\n idx++;\n }\n const lastRolumn = chunk._rolumns[chunk._rolumns.length - 1];\n chunk._size = lastRolumn._startPos + lastRolumn._size;\n return chunk; \n }\n\n _updateLayout(): void {\n if (/*this._rolumnStartIdx === undefined ||*/ this._viewDim2 === 0) return;\n this._chunkSize = Math.ceil(2 * (this._viewDim1 * this._viewDim2) / (this._idealSize! * this._idealSize!));\n console.log('chunkSize', this._chunkSize);\n // TODO: An odd place to do this, need to think through the logistics of getting size info to the layout\n // in all cases\n // this._itemSizes.length = 100;//this._totalItems;\n this._chunks[0] = this._layOutChunk(0);\n // TODO (graynorton): This is a hack to force reflow\n this._spacingChanged = true;\n}\n\n _updateScrollSize() {\n const chunk = this._chunks[0];\n this._scrollSize = !chunk || chunk._rolumns.length === 0 ? 1 : chunk._size + (2 * this._spacing);\n // chunk._rolumns[chunk._rolumns.length - 1]._startPos +\n // chunk._itemPositions[chunk._rolumns.length - 1][this._sizeDim] +\n // (this._spacing * 2);\n }\n}"]} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dGrid.d.ts b/lib/uni-virtualizer/lib/layouts/Layout1dGrid.d.ts -index c8f7a5c894287d49c6855481a653259422131db1..39633e64c311e105edf9bd9c8ed63e8ab73d26f5 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dGrid.d.ts -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dGrid.d.ts -@@ -2,9 +2,8 @@ import { Layout1dBase } from './Layout1dBase.js'; - /** - * TODO @straversi: document and test this Layout. - */ --export declare abstract class Layout1dGrid extends Layout1dBase { -- protected _rolumns: any; -- constructor(config: any); -+export declare abstract class Layout1dGrid extends Layout1dBase { -+ protected _rolumns: number; - _viewDim2Changed(): void; - _itemDim2Changed(): void; - _getActiveItems(): void; -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dGrid.d.ts.map b/lib/uni-virtualizer/lib/layouts/Layout1dGrid.d.ts.map -index 748cb292f3329dbbcebac625ab7955a6b8683e66..ed14c15459b9a49e0a8b7d28c09fde366ed3d32b 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dGrid.d.ts.map -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dGrid.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"Layout1dGrid.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAE/C;;GAEG;AACH,8BAAsB,YAAa,SAAQ,YAAY;IACrD,SAAS,CAAC,QAAQ,MAAC;gBAEP,MAAM,KAAA;IAKlB,gBAAgB;IAIhB,gBAAgB;IAIhB,eAAe;IAef,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC;IAQ1D,iBAAiB;CAIlB"} -\ No newline at end of file -+{"version":3,"file":"Layout1dGrid.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAE/C;;GAEG;AACH,8BAAsB,YAAY,CAAC,MAAM,CAAE,SAAQ,YAAY,CAAC,MAAM,CAAC;IACrE,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAK;IAO/B,gBAAgB;IAIhB,gBAAgB;IAIhB,eAAe;IAef,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC;IAQ1D,iBAAiB;CAIlB"} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dGrid.js b/lib/uni-virtualizer/lib/layouts/Layout1dGrid.js -index 24db275ef90173740b73488f8816fbf3344dff78..f9fdf72e948f74f384cb655887b12862afde5547 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dGrid.js -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dGrid.js -@@ -3,10 +3,14 @@ import { Layout1dBase } from './Layout1dBase.js'; - * TODO @straversi: document and test this Layout. - */ - export class Layout1dGrid extends Layout1dBase { -- constructor(config) { -- super(config); -+ constructor() { -+ super(...arguments); - this._rolumns = 1; - } -+ // constructor(config) { -+ // super(config); -+ // this._rolumns = 1; -+ // } - _viewDim2Changed() { - this._scheduleLayoutUpdate(); - } -@@ -36,3 +40,4 @@ export class Layout1dGrid extends Layout1dBase { - Math.max(1, Math.ceil(this._totalItems / this._rolumns) * this._delta + this._spacing); - } - } -+//# sourceMappingURL=Layout1dGrid.js.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dGrid.js.map b/lib/uni-virtualizer/lib/layouts/Layout1dGrid.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..d5b5318d4d7ba2efa45ae788e5ef20563707af2d ---- /dev/null -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dGrid.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"Layout1dGrid.js","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAE/C;;GAEG;AACH,MAAM,OAAgB,YAAqB,SAAQ,YAAoB;IAAvE;;QACY,aAAQ,GAAW,CAAC,CAAC;IA0CjC,CAAC;IAxCC,wBAAwB;IACxB,mBAAmB;IACnB,uBAAuB;IACvB,IAAI;IAEJ,gBAAgB;QACd,IAAI,CAAC,qBAAqB,EAAE,CAAC;IAC/B,CAAC;IAED,gBAAgB;QACd,IAAI,CAAC,qBAAqB,EAAE,CAAC;IAC/B,CAAC;IAED,eAAe;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAChB,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAEjD,IAAI,CAAC,MAAM,GAAG,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QACvC,IAAI,CAAC,KAAK;YACN,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;QACxE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;QAC3C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,gBAAgB,CAAC,GAAW;QAC1B,OAAO;YACL,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM;YAC9E,CAAC,IAAI,CAAC,qBAAqB,CAAC,EAAE,IAAI,CAAC,QAAQ;gBAC3C,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;SACrB,CAAC;IAC9C,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,WAAW;YACZ,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7F,CAAC;CACF","sourcesContent":["import {Layout1dBase} from './Layout1dBase.js';\n\n/**\n * TODO @straversi: document and test this Layout.\n */\nexport abstract class Layout1dGrid extends Layout1dBase {\n protected _rolumns: number = 1;\n\n // constructor(config) {\n // super(config);\n // this._rolumns = 1;\n // }\n\n _viewDim2Changed() {\n this._scheduleLayoutUpdate();\n }\n\n _itemDim2Changed() {\n this._scheduleLayoutUpdate();\n }\n\n _getActiveItems() {\n const min = Math.max(0, this._scrollPosition - this._overhang);\n const max = Math.min(\n this._scrollSize,\n this._scrollPosition + this._viewDim1 + this._overhang);\n const firstCow = Math.floor(min / this._delta);\n const lastCow = Math.ceil(max / this._delta) - 1;\n\n this._first = firstCow * this._rolumns;\n this._last =\n Math.min(((lastCow + 1) * this._rolumns) - 1, this._totalItems - 1);\n this._physicalMin = this._delta * firstCow;\n this._physicalMax = this._delta * (lastCow + 1);\n }\n\n _getItemPosition(idx: number): {top: number, left: number} {\n return {\n [this._positionDim]: this._spacing + Math.floor(idx / this._rolumns) * this._delta,\n [this._secondaryPositionDim]: this._spacing +\n ((idx % this._rolumns) * (this._spacing + this._itemDim2))\n } as unknown as {top: number, left: number};\n }\n\n _updateScrollSize() {\n this._scrollSize =\n Math.max(1, Math.ceil(this._totalItems / this._rolumns) * this._delta + this._spacing);\n }\n}\n"]} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.d.ts b/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.d.ts -index aeb4125400544350993c7c2b5c934b5931b9d163..4e5324c76353f7af1f7f4d8f1981ff2684b258ab 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.d.ts -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.d.ts -@@ -1,6 +1,7 @@ - import { Layout1dGrid } from './Layout1dGrid.js'; -+import { Layout1dBaseConfig } from './Layout1dBase.js'; - import { ItemBox } from './Layout'; --export declare class Layout1dNaturalSizeGrid extends Layout1dGrid { -+export declare class Layout1dNaturalSizeGrid extends Layout1dGrid { - updateItemSizes(sizes: { - [key: number]: ItemBox; - }): void; -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.d.ts.map b/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.d.ts.map -index 602b691726e6fb10dd3c8e430253267e8f0bafcf..5b45aac00d294a71c6a6a6ba4a6fb75ff4530a98 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.d.ts.map -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"Layout1dNaturalSizeGrid.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAC,OAAO,EAAC,MAAM,UAAU,CAAC;AAEjC,qBAAa,uBAAwB,SAAQ,YAAY;IACrD,eAAe,CAAC,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAC;IAQ7C,aAAa;CAYlB"} -\ No newline at end of file -+{"version":3,"file":"Layout1dNaturalSizeGrid.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAC,OAAO,EAAC,MAAM,UAAU,CAAC;AAEjC,qBAAa,uBAAwB,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,eAAe,CAAC,KAAK,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAC;IAQ7C,aAAa;CAYlB"} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.js b/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.js -index 7ba0132588ff7979f3b004188241fa8334abaead..b188b16d0fe9a2889e18fa40f2a18377682632a4 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.js -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.js -@@ -20,3 +20,4 @@ export class Layout1dNaturalSizeGrid extends Layout1dGrid { - this._spacingChanged = !(_spacing === this._spacing); - } - } -+//# sourceMappingURL=Layout1dNaturalSizeGrid.js.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.js.map b/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..cf506e8d0dcce673437abc94de413141f2cf4df8 ---- /dev/null -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"Layout1dNaturalSizeGrid.js","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dNaturalSizeGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAI/C,MAAM,OAAO,uBAAwB,SAAQ,YAAgC;IACzE,eAAe,CAAC,KAA+B;QAC3C,uCAAuC;QACvC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,IAAI,EAAE;YACR,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;SACtB;IACH,CAAC;IAED,aAAa;QACX,MAAM,EAAC,QAAQ,EAAC,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QACzE,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,EAAE;YACrB,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC/D,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;SACzB;aACI;YACH,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC;SACnB;QACD,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvD,CAAC;CACN","sourcesContent":["import {Layout1dGrid} from './Layout1dGrid.js';\nimport {Layout1dBaseConfig} from './Layout1dBase.js';\nimport {ItemBox} from './Layout';\n\nexport class Layout1dNaturalSizeGrid extends Layout1dGrid {\n updateItemSizes(sizes: {[key: number]: ItemBox}) {\n // Assume all items have the same size.\n const size = Object.values(sizes)[0];\n if (size) {\n this.itemSize = size;\n }\n }\n \n _updateLayout() {\n const {_spacing} = this;\n this._rolumns = Math.max(1, Math.floor(this._viewDim2 / this._itemDim2));\n if (this._rolumns > 1) {\n this._spacing = (this._viewDim2 % (this._rolumns * this._itemDim2)) /\n (this._rolumns + 1);\n }\n else {\n this._spacing = 0;\n }\n this._spacingChanged = !(_spacing === this._spacing);\n } \n}"]} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.d.ts b/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.d.ts -index 6aea3ca7ab177d3750be3c5e0179cff8930dbf0b..f7d46726678162fc91735c45111fce60687c1dca 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.d.ts -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.d.ts -@@ -1,10 +1,20 @@ -+import { Layout1dBaseConfig } from './Layout1dBase.js'; - import { Layout1dGrid } from './Layout1dGrid.js'; - import { Positions } from './Layout.js'; --export declare class Layout1dSquareGrid extends Layout1dGrid { -+interface Layout1dSquareGridConfig extends Layout1dBaseConfig { -+ spacing?: number; -+ idealSize?: number; -+} -+declare type Layout1dSquareGridSpecifier = Layout1dSquareGridConfig & { -+ type: new (config?: Layout1dSquareGridConfig) => Layout1dSquareGrid; -+}; -+declare type Layout1dSquareGridSpecifierFactory = (config?: Layout1dSquareGridConfig) => Layout1dSquareGridSpecifier; -+export declare const layout1dSquareGrid: Layout1dSquareGridSpecifierFactory; -+export declare class Layout1dSquareGrid extends Layout1dGrid { - protected _idealSize: number; -- constructor(config: any); -- set idealSize(px: any); -+ set idealSize(px: number); - _getItemPosition(idx: number): Positions; - _updateLayout(): void; - } -+export {}; - //# sourceMappingURL=Layout1dSquareGrid.d.ts.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.d.ts.map b/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.d.ts.map -index a283b0668bda6eac853d0d53ad7d675f770ecfad..9fcaf8aa40d8116114cd4785986408c7ff0b0feb 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.d.ts.map -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"Layout1dSquareGrid.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAEtC,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,SAAS,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEjB,MAAM,KAAA;IAOlB,IAAI,SAAS,CAAC,EAAE,KAAA,EAKf;IAED,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;IAIxC,aAAa;CASd"} -\ No newline at end of file -+{"version":3,"file":"Layout1dSquareGrid.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AAEtC,UAAU,wBAAyB,SAAQ,kBAAkB;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,aAAK,2BAA2B,GAAG,wBAAwB,GAAG;IAC5D,IAAI,EAAE,KAAI,MAAM,CAAC,EAAE,wBAAwB,KAAK,kBAAkB,CAAA;CACnE,CAAA;AAED,aAAK,kCAAkC,GAAG,CAAC,MAAM,CAAC,EAAE,wBAAwB,KAAK,2BAA2B,CAAC;AAE7G,eAAO,MAAM,kBAAkB,EAAE,kCAEvB,CAAC;AAEX,qBAAa,kBAAmB,SAAQ,YAAY,CAAC,wBAAwB,CAAC;IAC5E,SAAS,CAAC,UAAU,EAAE,MAAM,CAAO;IASnC,IAAI,SAAS,CAAC,EAAE,EAAE,MAAM,EAKvB;IAED,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS;IAIxC,aAAa;CASd"} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.js b/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.js -index 27636003a5baa3d56b60693222ed0e84561ff796..ce7b984d5b60e2ae09942df097049732d949390f 100644 ---- a/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.js -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.js -@@ -1,11 +1,18 @@ - import { Layout1dGrid } from './Layout1dGrid.js'; -+export const layout1dSquareGrid = (config) => Object.assign({ -+ type: Layout1dSquareGrid -+}, config); - export class Layout1dSquareGrid extends Layout1dGrid { -- constructor(config) { -- super(config); -- if (config.idealSize === undefined) { -- this._idealSize = 200; -- } -+ constructor() { -+ super(...arguments); -+ this._idealSize = 200; - } -+ // constructor(config) { -+ // super(config); -+ // if (config.idealSize === undefined) { -+ // this._idealSize = 200; -+ // } -+ // } - set idealSize(px) { - if (px !== this._idealSize) { - this._idealSize = px; -@@ -25,3 +32,4 @@ export class Layout1dSquareGrid extends Layout1dGrid { - } - } - } -+//# sourceMappingURL=Layout1dSquareGrid.js.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.js.map b/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..701d12281865e1459666458e9418a96696813f3b ---- /dev/null -+++ b/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"Layout1dSquareGrid.js","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/layouts/Layout1dSquareGrid.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAc/C,MAAM,CAAC,MAAM,kBAAkB,GAAuC,CAAC,MAAiC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;IACzH,IAAI,EAAE,kBAAkB;CACzB,EAAE,MAAM,CAAC,CAAC;AAEX,MAAM,OAAO,kBAAmB,SAAQ,YAAsC;IAA9E;;QACY,eAAU,GAAW,GAAG,CAAC;IA6BrC,CAAC;IA3BC,wBAAwB;IACxB,mBAAmB;IACnB,0CAA0C;IAC1C,+BAA+B;IAC/B,MAAM;IACN,IAAI;IAEJ,IAAI,SAAS,CAAC,EAAU;QACtB,IAAI,EAAE,KAAK,IAAI,CAAC,UAAU,EAAE;YAC1B,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;YACrB,IAAI,CAAC,qBAAqB,EAAE,CAAC;SAC9B;IACH,CAAC;IAED,gBAAgB,CAAC,GAAW;QAC1B,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACpE,CAAC;IAED,aAAa;QACX,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC;QAClD,IAAI,CAAC,QAAQ,GAAG,QAAQ,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChF,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC;QACzF,IAAI,OAAO,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE;YACpC,IAAI,CAAC,SAAS,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YACrD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;SAC7B;IACH,CAAC;CACF","sourcesContent":["import {Layout1dBaseConfig} from './Layout1dBase.js';\nimport {Layout1dGrid} from './Layout1dGrid.js';\nimport {Positions} from './Layout.js';\n\ninterface Layout1dSquareGridConfig extends Layout1dBaseConfig {\n spacing?: number,\n idealSize?: number\n}\n\ntype Layout1dSquareGridSpecifier = Layout1dSquareGridConfig & {\n type: new(config?: Layout1dSquareGridConfig) => Layout1dSquareGrid\n}\n\ntype Layout1dSquareGridSpecifierFactory = (config?: Layout1dSquareGridConfig) => Layout1dSquareGridSpecifier;\n\nexport const layout1dSquareGrid: Layout1dSquareGridSpecifierFactory = (config?: Layout1dSquareGridConfig) => Object.assign({\n type: Layout1dSquareGrid\n}, config);\n\nexport class Layout1dSquareGrid extends Layout1dGrid {\n protected _idealSize: number = 200;\n\n // constructor(config) {\n // super(config);\n // if (config.idealSize === undefined) {\n // this._idealSize = 200;\n // }\n // }\n\n set idealSize(px: number) {\n if (px !== this._idealSize) {\n this._idealSize = px;\n this._scheduleLayoutUpdate();\n }\n }\n\n _getItemPosition(idx: number): Positions {\n return Object.assign(super._getItemPosition(idx), this._itemSize);\n }\n\n _updateLayout() {\n const frolumns = this._viewDim2 / this._idealSize;\n this._rolumns = frolumns % 1 < 0.5 ? Math.floor(frolumns) : Math.ceil(frolumns);\n const adjSize = (this._viewDim2 - ((this._rolumns + 1) * this._spacing)) / this._rolumns;\n if (adjSize !== this._itemSize.width) {\n this._itemSize = { width: adjSize, height: adjSize };\n this._spacingChanged = true;\n }\n } \n}"]} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.d.ts b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.d.ts -index d9f205869ab7ecc5e93b11c8310941611bbc18fa..da9f9dc7cdee3c8af63f235aec6913cde5362f1d 100644 ---- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.d.ts -+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.d.ts -@@ -1,2 +1,6 @@ --export default function EventTarget(): Promise; -+interface EventTargetConstructor { -+ new (): EventTarget; -+} -+export default function EventTarget(): Promise; -+export {}; - //# sourceMappingURL=EventTarget.d.ts.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.d.ts.map b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.d.ts.map -index ff50f3f8186e7b5ffc8ccf6c656dd39a0d56d9f3..7e5f49dc04b97a08ca30dc564ae41bacf8baa143 100644 ---- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.d.ts.map -+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"EventTarget.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.ts"],"names":[],"mappings":"AAEA,wBAA8B,WAAW,iBAExC"} -\ No newline at end of file -+{"version":3,"file":"EventTarget.d.ts","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.ts"],"names":[],"mappings":"AAEA,UAAU,sBAAsB;IAC5B,QAAO,WAAW,CAAA;CACrB;AAKD,wBAA8B,WAAW,oCAExC"} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js -index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644 ---- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js -+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js -@@ -1,14 +1,15 @@ --let _ET, ET; -+let _ET; -+let ET; - export default async function EventTarget() { -- return ET || init(); -+ return ET || init(); - } - async function init() { -- _ET = window.EventTarget; -- try { -- new _ET(); -- } -- catch (_a) { -- _ET = (await import('event-target-shim')).EventTarget; -- } -- return (ET = _ET); -+ _ET = window.EventTarget; -+ try { -+ new _ET(); -+ } catch (_a) { -+ _ET = (await import("event-target-shim")).default.EventTarget; -+ } -+ return (ET = _ET); - } -+//# sourceMappingURL=EventTarget.js.map -diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js.map b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..4ce7a8b18f1f07910bae2b2fd08b8b9cddf2d9ca ---- /dev/null -+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"EventTarget.js","sourceRoot":"","sources":["../../../../src/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.ts"],"names":[],"mappings":"AAMA,IAAI,GAA+C,CAAC;AACpD,IAAI,EAA0B,CAAC;AAE/B,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,WAAW;IACrC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC;AACxB,CAAC;AAED,KAAK,UAAU,IAAI;IACf,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC;IACzB,IAAI;QACA,IAAI,GAAG,EAAE,CAAC;KACb;IACD,WAAM;QACF,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,WAAW,CAAC;KACzD;IACD,OAAO,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC;AACtB,CAAC","sourcesContent":["type EventTargetModule = typeof import('event-target-shim');\n\ninterface EventTargetConstructor {\n new(): EventTarget\n}\n\nlet _ET: EventTargetModule | EventTargetConstructor;\nlet ET: EventTargetConstructor;\n\nexport default async function EventTarget() {\n return ET || init();\n}\n\nasync function init() {\n _ET = window.EventTarget;\n try {\n new _ET();\n }\n catch {\n _ET = (await import('event-target-shim')).EventTarget;\n }\n return (ET = _ET);\n}"]} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/uni-virtualizer.d.ts b/lib/uni-virtualizer/uni-virtualizer.d.ts -index 60838ab88dbfc1aa5889ba3a24729ac7f7a668e3..4137a2752ade8a4a41893635ed94ea0ad572a2e7 100644 ---- a/lib/uni-virtualizer/uni-virtualizer.d.ts -+++ b/lib/uni-virtualizer/uni-virtualizer.d.ts -@@ -1,4 +1,4 @@ - export { VirtualScroller, RangeChangeEvent, scrollerRef } from './lib/VirtualScroller.js'; --export { Layout1d } from './lib/layouts/Layout1d.js'; -+export { Layout1d, layout1d } from './lib/layouts/Layout1d.js'; - export { Layout1dGrid } from './lib/layouts/Layout1dGrid.js'; - //# sourceMappingURL=uni-virtualizer.d.ts.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/uni-virtualizer.d.ts.map b/lib/uni-virtualizer/uni-virtualizer.d.ts.map -index d045b7d96beda8cc80bc3a3b82e22ff23131a44f..38e1246ce67e788109d6c9310210a601693b6f95 100644 ---- a/lib/uni-virtualizer/uni-virtualizer.d.ts.map -+++ b/lib/uni-virtualizer/uni-virtualizer.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"uni-virtualizer.d.ts","sourceRoot":"","sources":["../../src/lib/uni-virtualizer/uni-virtualizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC"} -\ No newline at end of file -+{"version":3,"file":"uni-virtualizer.d.ts","sourceRoot":"","sources":["../../src/lib/uni-virtualizer/uni-virtualizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC"} -\ No newline at end of file -diff --git a/lib/uni-virtualizer/uni-virtualizer.js b/lib/uni-virtualizer/uni-virtualizer.js -index abbfd723d9d0013a5afa3641ace55bd4b89be275..ed6b82aedaa51d4c38de726c0db6e21d01759f5f 100644 ---- a/lib/uni-virtualizer/uni-virtualizer.js -+++ b/lib/uni-virtualizer/uni-virtualizer.js -@@ -1,3 +1,4 @@ - export { VirtualScroller, scrollerRef } from './lib/VirtualScroller.js'; --export { Layout1d } from './lib/layouts/Layout1d.js'; -+export { Layout1d, layout1d } from './lib/layouts/Layout1d.js'; - export { Layout1dGrid } from './lib/layouts/Layout1dGrid.js'; -+//# sourceMappingURL=uni-virtualizer.js.map -\ No newline at end of file -diff --git a/lib/uni-virtualizer/uni-virtualizer.js.map b/lib/uni-virtualizer/uni-virtualizer.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..326d70cd4eedd257fb6fbb0078255e2882c49724 ---- /dev/null -+++ b/lib/uni-virtualizer/uni-virtualizer.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"uni-virtualizer.js","sourceRoot":"","sources":["../../src/lib/uni-virtualizer/uni-virtualizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAoB,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE1F,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC","sourcesContent":["export { VirtualScroller, RangeChangeEvent, scrollerRef } from './lib/VirtualScroller.js';\n\nexport { Layout1d, layout1d } from './lib/layouts/Layout1d.js';\nexport { Layout1dGrid } from './lib/layouts/Layout1dGrid.js';\n"]} -\ No newline at end of file -diff --git a/lit-virtualizer.d.ts b/lit-virtualizer.d.ts -index 1c857d4bc1aeb43b56a7923b990ddcd094b09be2..58204903e4356b38a34df0ba29fec6390d435078 100644 ---- a/lit-virtualizer.d.ts -+++ b/lit-virtualizer.d.ts -@@ -1,4 +1,4 @@ - export { scroll } from './lib/scroll.js'; --export { Layout1d, Layout1dGrid, RangeChangeEvent, scrollerRef } from './lib/uni-virtualizer/uni-virtualizer.js'; -+export { Layout1d, layout1d, Layout1dGrid, RangeChangeEvent, scrollerRef } from './lib/uni-virtualizer/uni-virtualizer.js'; - export { LitVirtualizer } from './lib/lit-virtualizer.js'; - //# sourceMappingURL=lit-virtualizer.d.ts.map -\ No newline at end of file -diff --git a/lit-virtualizer.d.ts.map b/lit-virtualizer.d.ts.map -index 3a1ccb65e6cefe6192415cdbb5e6dfa1f273233c..08ad361b84e378b6052924266a57fca5a48177b0 100644 ---- a/lit-virtualizer.d.ts.map -+++ b/lit-virtualizer.d.ts.map -@@ -1 +1 @@ --{"version":3,"file":"lit-virtualizer.d.ts","sourceRoot":"","sources":["src/lit-virtualizer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAC;AACjH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC"} -\ No newline at end of file -+{"version":3,"file":"lit-virtualizer.d.ts","sourceRoot":"","sources":["src/lit-virtualizer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAC3H,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC"} -\ No newline at end of file -diff --git a/lit-virtualizer.js b/lit-virtualizer.js -index 1774c8a7f63ab390fd6bb96ce2f58dccef32c211..ef3497547e82647ab7282927e83a32ed39456b4c 100644 ---- a/lit-virtualizer.js -+++ b/lit-virtualizer.js -@@ -1,4 +1,5 @@ - // export { repeat } from './lib/repeat.js'; - export { scroll } from './lib/scroll.js'; --export { Layout1d, Layout1dGrid, scrollerRef } from './lib/uni-virtualizer/uni-virtualizer.js'; -+export { Layout1d, layout1d, Layout1dGrid, scrollerRef } from './lib/uni-virtualizer/uni-virtualizer.js'; - export { LitVirtualizer } from './lib/lit-virtualizer.js'; -+//# sourceMappingURL=lit-virtualizer.js.map -\ No newline at end of file -diff --git a/lit-virtualizer.js.map b/lit-virtualizer.js.map -new file mode 100644 -index 0000000000000000000000000000000000000000..c0c3a0f6bb2af1571380e7f79e81a6affa787f18 ---- /dev/null -+++ b/lit-virtualizer.js.map -@@ -0,0 +1 @@ -+{"version":3,"file":"lit-virtualizer.js","sourceRoot":"","sources":["src/lit-virtualizer.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAoB,WAAW,EAAE,MAAM,0CAA0C,CAAC;AAC3H,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["// export { repeat } from './lib/repeat.js';\nexport { scroll } from './lib/scroll.js';\nexport { Layout1d, layout1d, Layout1dGrid, RangeChangeEvent, scrollerRef } from './lib/uni-virtualizer/uni-virtualizer.js';\nexport { LitVirtualizer } from './lib/lit-virtualizer.js';\n"]} -\ No newline at end of file -diff --git a/package.json b/package.json -index b9854ae5453193acdc822d0b06e02674733844a1..f1194c01e74606efba3403df51f2adbadd5b0da6 100644 ---- a/package.json -+++ b/package.json -@@ -45,8 +45,7 @@ - "rollup-plugin-node-resolve": "^4.2.3", - "rollup-plugin-terser": "^5.0.0", - "tachometer": "^0.4.7", -- "tslint": "^5.18.0", -- "typescript": "^4.0.2" -+ "typescript": "^4.1.3" - }, - "dependencies": { - "event-target-shim": "^5.0.1", diff --git a/.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch b/.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch index dfb0a686fe..b5b2421b48 100644 --- a/.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch +++ b/.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch @@ -1,11 +1,10 @@ -diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js -index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644 ---- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js -+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js -@@ -1,14 +1,15 @@ --let _ET, ET; -+let _ET; -+let ET; +diff --git a/polyfillLoaders/EventTarget.js b/polyfillLoaders/EventTarget.js +index 4e18ade7ba485849f17f28c94c42f0e0e01ac387..8f34f4f646c7f7becc208fb5a546c96034fc74dc 100644 +--- a/polyfillLoaders/EventTarget.js ++++ b/polyfillLoaders/EventTarget.js +@@ -6,16 +6,15 @@ + let _ET; + let ET; export default async function EventTarget() { - return ET || init(); + return ET || init(); @@ -26,4 +25,5 @@ index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc + _ET = (await import("event-target-shim")).default.EventTarget; + } + return (ET = _ET); - } \ No newline at end of file + } + //# sourceMappingURL=EventTarget.js.map \ No newline at end of file diff --git a/package.json b/package.json index 6b536e78ff..63c5df94ca 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@fullcalendar/daygrid": "5.9.0", "@fullcalendar/interaction": "5.9.0", "@fullcalendar/list": "5.9.0", - "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch", + "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch", "@material/chips": "14.0.0-canary.261f2db59.0", "@material/data-table": "14.0.0-canary.261f2db59.0", "@material/mwc-button": "0.25.3", diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index af06549c80..c05c968b4f 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -1,4 +1,3 @@ -import { Layout1d, scroll } from "@lit-labs/virtualizer"; import { mdiArrowDown, mdiArrowUp } from "@mdi/js"; import deepClone from "deep-clone-simple"; import { @@ -31,6 +30,7 @@ import type { HaCheckbox } from "../ha-checkbox"; import "../ha-svg-icon"; import { filterData, sortData } from "./sort-filter"; import { HomeAssistant } from "../../types"; +import "@lit-labs/virtualizer"; declare global { // for fire event @@ -337,111 +337,99 @@ export class HaDataTable extends LitElement {
` : html` -
- ${scroll({ - items: this._items, - layout: Layout1d, - renderItem: (row: DataTableRowData, index) => { - // not sure how this happens... - if (!row) { - return html``; - } - if (row.append) { - return html` -
${row.content}
- `; - } - if (row.empty) { - return html`
`; - } - return html` -
- ${this.selectable - ? html` -
- - -
- ` - : ""} - ${Object.entries(this.columns).map( - ([key, column]) => { - if (column.hidden) { - return ""; - } - return html` -
- ${column.template - ? column.template(row[key], row) - : row[key]} -
- `; - } - )} -
- `; - }, - })} -
+ .items=${this._items} + .renderItem=${this._renderRow} + > `} `; } + private _renderRow = ( + row: DataTableRowData, + index: number + ): TemplateResult => { + // not sure how this happens... + if (!row) { + return html``; + } + if (row.append) { + return html`
${row.content}
`; + } + if (row.empty) { + return html`
`; + } + return html` +
+ ${this.selectable + ? html` +
+ + +
+ ` + : ""} + ${Object.entries(this.columns).map(([key, column]) => { + if (column.hidden) { + return ""; + } + return html` +
+ ${column.template ? column.template(row[key], row) : row[key]} +
+ `; + })} +
+ `; + }; + private async _sortFilterData() { const startTime = new Date().getTime(); this.curRequest++; @@ -970,6 +958,9 @@ export class HaDataTable extends LitElement { .clickable { cursor: pointer; } + lit-virtualizer { + contain: size layout !important; + } `, ]; } diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 77e1ccd8df..c166918cc9 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -1,5 +1,4 @@ -import "../../components/ha-textfield"; -import { Layout1d, scroll } from "@lit-labs/virtualizer"; +import "@lit-labs/virtualizer"; import "@material/mwc-list/mwc-list"; import type { List } from "@material/mwc-list/mwc-list"; import { SingleSelectedEvent } from "@material/mwc-list/mwc-list-foundation"; @@ -13,7 +12,7 @@ import { mdiReload, mdiServerNetwork, } from "@mdi/js"; -import { css, html, LitElement } from "lit"; +import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { styleMap } from "lit/directives/style-map"; @@ -36,11 +35,12 @@ import "../../components/ha-chip"; import "../../components/ha-circular-progress"; import "../../components/ha-header-bar"; import "../../components/ha-icon-button"; +import "../../components/ha-textfield"; import { domainToName } from "../../data/integration"; import { getPanelNameTranslationKey } from "../../data/panel"; import { PageNavigation } from "../../layouts/hass-tabs-subpage"; import { configSections } from "../../panels/config/ha-panel-config"; -import { haStyleDialog } from "../../resources/styles"; +import { haStyleDialog, haStyleScrollbar } from "../../resources/styles"; import { HomeAssistant } from "../../types"; import { ConfirmationDialogParams, @@ -210,25 +210,24 @@ export class QuickBar extends LitElement { ` : html` - - ${scroll({ - items, - layout: Layout1d, - renderItem: (item: QuickBarItem, index) => - this._renderItem(item, index), - })} + + + `} ${this._hint ? html`
${this._hint}
` : ""} @@ -261,14 +260,14 @@ export class QuickBar extends LitElement { } } - private _renderItem(item: QuickBarItem, index?: number) { + private _renderItem = (item: QuickBarItem, index: number): TemplateResult => { if (!item) { return html``; } return isCommandItem(item) ? this._renderCommandItem(item, index) : this._renderEntityItem(item as EntityItem, index); - } + }; private _renderEntityItem(item: EntityItem, index?: number) { return html` @@ -431,16 +430,21 @@ export class QuickBar extends LitElement { private _handleListItemKeyDown(ev: KeyboardEvent) { const isSingleCharacter = ev.key.length === 1; - const isFirstListItem = - (ev.target as HTMLElement).getAttribute("index") === "0"; + const index = (ev.target as HTMLElement).getAttribute("index"); + const isFirstListItem = index === "0"; this._focusListElement = ev.target as ListItem; + if (ev.key === "ArrowDown") { + this._getItemAtIndex(Number(index) + 1)?.focus(); + } if (ev.key === "ArrowUp") { if (isFirstListItem) { this._filterInputField?.focus(); + } else { + this._getItemAtIndex(Number(index) - 1)?.focus(); } } if (ev.key === "Backspace" || isSingleCharacter) { - (ev.currentTarget as List).scrollTop = 0; + (ev.currentTarget as HTMLElement).scrollTop = 0; this._filterInputField?.focus(); } } @@ -683,6 +687,7 @@ export class QuickBar extends LitElement { static get styles() { return [ + haStyleScrollbar, haStyleDialog, css` .heading { @@ -780,6 +785,10 @@ export class QuickBar extends LitElement { display: flex; align-items: center; } + + lit-virtualizer { + contain: size layout !important; + } `, ]; } diff --git a/src/panels/logbook/ha-logbook.ts b/src/panels/logbook/ha-logbook.ts index bafff2cd9c..5c59dfa783 100644 --- a/src/panels/logbook/ha-logbook.ts +++ b/src/panels/logbook/ha-logbook.ts @@ -1,4 +1,4 @@ -import { Layout1d, scroll } from "@lit-labs/virtualizer"; +import "@lit-labs/virtualizer"; import { css, CSSResultGroup, @@ -97,12 +97,13 @@ class HaLogbook extends LitElement { @scroll=${this._saveScrollPos} > ${this.virtualize - ? scroll({ - items: this.entries, - layout: Layout1d, - renderItem: (item: LogbookEntry, index) => - this._renderLogbookItem(item, index), - }) + ? html` + ` : this.entries.map((item, index) => this._renderLogbookItem(item, index) )} @@ -110,11 +111,11 @@ class HaLogbook extends LitElement { `; } - private _renderLogbookItem( + private _renderLogbookItem = ( item: LogbookEntry, - index?: number - ): TemplateResult { - if (index === undefined) { + index: number + ): TemplateResult => { + if (!item || index === undefined) { return html``; } @@ -239,7 +240,7 @@ class HaLogbook extends LitElement { `; - } + }; @eventOptions({ passive: true }) private _saveScrollPos(e: Event) { @@ -362,10 +363,15 @@ class HaLogbook extends LitElement { max-height: var(--logbook-max-height); } - :host([virtualize]) .container { + .container, + lit-virtualizer { height: 100%; } + lit-virtualizer { + contain: size layout !important; + } + .narrow .entry { line-height: 1.5; padding: 8px; diff --git a/yarn.lock b/yarn.lock index 8d3c0ef776..b4a106053a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1960,25 +1960,25 @@ __metadata: languageName: node linkType: hard -"@lit-labs/virtualizer@0.6.0": - version: 0.6.0 - resolution: "@lit-labs/virtualizer@npm:0.6.0" +"@lit-labs/virtualizer@0.7.0-pre.2": + version: 0.7.0-pre.2 + resolution: "@lit-labs/virtualizer@npm:0.7.0-pre.2" dependencies: event-target-shim: ^5.0.1 - lit: ^2.0.0-rc.1 + lit: ^2.0.0 tslib: ^1.10.0 - checksum: c18a49c331a66f82a896a8de7d856277cc48dc4c9ca90df9513c1a73c887a36d1363178233a902d8d0fe5e1aa88f71302d68ac7a0cb1dc6ccbe593282d1cee72 + checksum: 88d5fffe73213c305160257e4e3a63a93087a90e63ca6640a81d156faacb68a8f84a82b94153b9364aeab3f5de45484acad1f8dcc2673e8464e394672358a3cc languageName: node linkType: hard -"@lit-labs/virtualizer@patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch::locator=home-assistant-frontend%40workspace%3A.": - version: 0.6.0 - resolution: "@lit-labs/virtualizer@patch:@lit-labs/virtualizer@npm%3A0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch::version=0.6.0&hash=0c4ec6&locator=home-assistant-frontend%40workspace%3A." +"@lit-labs/virtualizer@patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch::locator=home-assistant-frontend%40workspace%3A.": + version: 0.7.0-pre.2 + resolution: "@lit-labs/virtualizer@patch:@lit-labs/virtualizer@npm%3A0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch::version=0.7.0-pre.2&hash=cef165&locator=home-assistant-frontend%40workspace%3A." dependencies: event-target-shim: ^5.0.1 - lit: ^2.0.0-rc.1 + lit: ^2.0.0 tslib: ^1.10.0 - checksum: 66bed3149dce8a0099e567ec3f1d249fb1855bf0df41e644453d044debe5f686c8c1b2078d45b4de5c488e821119eae1734b040a2145862efec54c442b83ff0f + checksum: ea8bdd610530fc9b50dd327cc3b8c70d4e45300636d731969af7b9475047c91066fe7973e04ac91f80111c116c80a989f1507da7fbfc526f7c0dff3a54719a4d languageName: node linkType: hard @@ -9124,7 +9124,7 @@ fsevents@^1.2.7: "@fullcalendar/interaction": 5.9.0 "@fullcalendar/list": 5.9.0 "@koa/cors": ^3.1.0 - "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch" + "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch" "@material/chips": 14.0.0-canary.261f2db59.0 "@material/data-table": 14.0.0-canary.261f2db59.0 "@material/mwc-button": 0.25.3 From ed001fb10ba3cab6381c261ab6cf01bf5d059010 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 9 Feb 2022 18:20:56 +0100 Subject: [PATCH 029/174] Convert time inputs to Lit + mwc (#11609) --- src/common/datetime/create_duration_data.ts | 7 +- src/components/ha-base-time-input.ts | 308 +++++++++++ src/components/ha-duration-input.ts | 112 ++-- .../ha-selector/ha-selector-time.ts | 1 - src/components/ha-textfield.ts | 23 + src/components/ha-time-input.ts | 46 +- src/components/paper-time-input.js | 497 ------------------ .../controls/more-info-input_datetime.ts | 1 - .../hui-input-datetime-entity-row.ts | 1 - 9 files changed, 396 insertions(+), 600 deletions(-) create mode 100644 src/components/ha-base-time-input.ts delete mode 100644 src/components/paper-time-input.js diff --git a/src/common/datetime/create_duration_data.ts b/src/common/datetime/create_duration_data.ts index 92b3d01021..b198b5dbcb 100644 --- a/src/common/datetime/create_duration_data.ts +++ b/src/common/datetime/create_duration_data.ts @@ -1,5 +1,5 @@ -import { HaDurationData } from "../../components/ha-duration-input"; -import { ForDict } from "../../data/automation"; +import type { HaDurationData } from "../../components/ha-duration-input"; +import type { ForDict } from "../../data/automation"; export const createDurationData = ( duration: string | number | ForDict | undefined @@ -19,6 +19,9 @@ export const createDurationData = ( } return { seconds: duration }; } + if (!("days" in duration)) { + return duration; + } const { days, minutes, seconds, milliseconds } = duration; let hours = duration.hours || 0; hours = (hours || 0) + (days || 0) * 24; diff --git a/src/components/ha-base-time-input.ts b/src/components/ha-base-time-input.ts new file mode 100644 index 0000000000..e8ede0a78b --- /dev/null +++ b/src/components/ha-base-time-input.ts @@ -0,0 +1,308 @@ +import { LitElement, html, TemplateResult, css } from "lit"; +import { customElement, property } from "lit/decorators"; +import "@material/mwc-select/mwc-select"; +import "@material/mwc-list/mwc-list-item"; +import "./ha-textfield"; +import { fireEvent } from "../common/dom/fire_event"; +import { stopPropagation } from "../common/dom/stop_propagation"; + +export interface TimeChangedEvent { + hours: number; + minutes: number; + seconds: number; + milliseconds: number; + amPm?: "AM" | "PM"; +} + +@customElement("ha-base-time-input") +export class HaBaseTimeInput extends LitElement { + /** + * Label for the input + */ + @property() label?: string; + + /** + * auto validate time inputs + */ + @property({ type: Boolean }) autoValidate = false; + + /** + * 12 or 24 hr format + */ + @property({ type: Number }) format: 12 | 24 = 12; + + /** + * disables the inputs + */ + @property({ type: Boolean }) disabled = false; + + /** + * hour + */ + @property({ type: Number }) hours = 0; + + /** + * minute + */ + @property({ type: Number }) minutes = 0; + + /** + * second + */ + @property({ type: Number }) seconds = 0; + + /** + * milli second + */ + @property({ type: Number }) milliseconds = 0; + + /** + * Label for the hour input + */ + @property() hourLabel = ""; + + /** + * Label for the min input + */ + @property() minLabel = ""; + + /** + * Label for the sec input + */ + @property() secLabel = ""; + + /** + * Label for the milli sec input + */ + @property() millisecLabel = ""; + + /** + * show the sec field + */ + @property({ type: Boolean }) enableSecond = false; + + /** + * show the milli sec field + */ + @property({ type: Boolean }) enableMillisecond = false; + + /** + * limit hours input + */ + @property({ type: Boolean }) noHoursLimit = false; + + /** + * AM or PM + */ + @property() amPm: "AM" | "PM" = "AM"; + + /** + * Formatted time string + */ + @property() value?: string; + + protected render(): TemplateResult { + return html` + ${this.label ? html`` : ""} +
+ + + + + ${this.enableSecond + ? html` + ` + : ""} + ${this.enableMillisecond + ? html` + ` + : ""} + ${this.format === 24 + ? "" + : html` + AM + PM + `} +
+ `; + } + + private _valueChanged(ev) { + this[ev.target.name] = + ev.target.name === "amPm" ? ev.target.value : Number(ev.target.value); + const value: TimeChangedEvent = { + hours: this.hours, + minutes: this.minutes, + seconds: this.seconds, + milliseconds: this.milliseconds, + }; + if (this.format === 12) { + value.amPm = this.amPm; + } + fireEvent(this, "value-changed", { + value, + }); + } + + private _onFocus(ev) { + ev.target.select(); + } + + /** + * Format time fragments + */ + private _formatValue(value: number, padding = 2) { + return value.toString().padStart(padding, "0"); + } + + /** + * 24 hour format has a max hr of 23 + */ + private get _hourMax() { + if (this.noHoursLimit) { + return null; + } + if (this.format === 12) { + return 12; + } + return 23; + } + + static styles = css` + :host { + display: block; + } + .time-input-wrap { + display: flex; + border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0; + overflow: hidden; + position: relative; + } + ha-textfield { + width: 40px; + text-align: center; + --mdc-shape-small: 0; + --text-field-appearance: none; + --text-field-padding: 0 4px; + --text-field-suffix-padding-left: 2px; + --text-field-suffix-padding-right: 0; + --text-field-text-align: center; + } + ha-textfield.hasSuffix { + --text-field-padding: 0 0 0 4px; + } + ha-textfield:first-child { + --text-field-border-top-left-radius: var(--mdc-shape-medium); + } + ha-textfield:last-child { + --text-field-border-top-right-radius: var(--mdc-shape-medium); + } + mwc-select { + --mdc-shape-small: 0; + width: 85px; + } + label { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + font-family: var( + --mdc-typography-body2-font-family, + var(--mdc-typography-font-family, Roboto, sans-serif) + ); + font-size: var(--mdc-typography-body2-font-size, 0.875rem); + line-height: var(--mdc-typography-body2-line-height, 1.25rem); + font-weight: var(--mdc-typography-body2-font-weight, 400); + letter-spacing: var( + --mdc-typography-body2-letter-spacing, + 0.0178571429em + ); + text-decoration: var(--mdc-typography-body2-text-decoration, inherit); + text-transform: var(--mdc-typography-body2-text-transform, inherit); + color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87)); + padding-left: 4px; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-base-time-input": HaBaseTimeInput; + } +} diff --git a/src/components/ha-duration-input.ts b/src/components/ha-duration-input.ts index 3de83b1fd3..9ac4e72e2f 100644 --- a/src/components/ha-duration-input.ts +++ b/src/components/ha-duration-input.ts @@ -1,7 +1,8 @@ import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; -import "./paper-time-input"; +import "./ha-base-time-input"; +import type { TimeChangedEvent } from "./ha-base-time-input"; export interface HaDurationData { hours?: number; @@ -32,110 +33,69 @@ class HaDurationInput extends LitElement { protected render(): TemplateResult { return html` - + .hours=${this._hours} + .minutes=${this._minutes} + .seconds=${this._seconds} + .milliseconds=${this._milliseconds} + @value-changed=${this._durationChanged} + noHoursLimit + hourLabel="hh" + minLabel="mm" + secLabel="ss" + millisecLabel="ms" + > `; } private get _hours() { - return this.data && this.data.hours ? Number(this.data.hours) : 0; + return this.data?.hours ? Number(this.data.hours) : 0; } private get _minutes() { - return this.data && this.data.minutes ? Number(this.data.minutes) : 0; + return this.data?.minutes ? Number(this.data.minutes) : 0; } private get _seconds() { - return this.data && this.data.seconds ? Number(this.data.seconds) : 0; + return this.data?.seconds ? Number(this.data.seconds) : 0; } private get _milliseconds() { - return this.data && this.data.milliseconds - ? Number(this.data.milliseconds) - : 0; + return this.data?.milliseconds ? Number(this.data.milliseconds) : 0; } - private _parseDuration(value) { - return value.toString().padStart(2, "0"); - } + private _durationChanged(ev: CustomEvent<{ value: TimeChangedEvent }>) { + ev.stopPropagation(); + const value = { ...ev.detail.value }; - private _parseDurationMillisec(value) { - return value.toString().padStart(3, "0"); - } - - private _hourChanged(ev) { - this._durationChanged(ev, "hours"); - } - - private _minChanged(ev) { - this._durationChanged(ev, "minutes"); - } - - private _secChanged(ev) { - this._durationChanged(ev, "seconds"); - } - - private _millisecChanged(ev) { - this._durationChanged(ev, "milliseconds"); - } - - private _durationChanged(ev, unit) { - let value = Number(ev.detail.value); - - if (value === this[`_${unit}`]) { - return; + if (!this.enableMillisecond && !value.milliseconds) { + // @ts-ignore + delete value.milliseconds; + } else if (value.milliseconds > 999) { + value.seconds += Math.floor(value.milliseconds / 1000); + value.milliseconds %= 1000; } - let hours = this._hours; - let minutes = this._minutes; - - if (unit === "seconds" && value > 59) { - minutes += Math.floor(value / 60); - value %= 60; + if (value.seconds > 59) { + value.minutes += Math.floor(value.seconds / 60); + value.seconds %= 60; } - if (unit === "minutes" && value > 59) { - hours += Math.floor(value / 60); - value %= 60; + if (value.minutes > 59) { + value.hours += Math.floor(value.minutes / 60); + value.minutes %= 60; } - const newValue: HaDurationData = { - hours, - minutes, - seconds: this._seconds, - }; - - if (this.enableMillisecond || this._milliseconds) { - newValue.milliseconds = this._milliseconds; - } - - newValue[unit] = value; - fireEvent(this, "value-changed", { - value: newValue, + value, }); } } diff --git a/src/components/ha-selector/ha-selector-time.ts b/src/components/ha-selector/ha-selector-time.ts index f1a116d371..fb3b4e2b8d 100644 --- a/src/components/ha-selector/ha-selector-time.ts +++ b/src/components/ha-selector/ha-selector-time.ts @@ -22,7 +22,6 @@ export class HaTimeSelector extends LitElement { .value=${this.value} .locale=${this.hass.locale} .disabled=${this.disabled} - hide-label enable-second > `; diff --git a/src/components/ha-textfield.ts b/src/components/ha-textfield.ts index 8ca6992f2d..7aae676aa7 100644 --- a/src/components/ha-textfield.ts +++ b/src/components/ha-textfield.ts @@ -45,6 +45,29 @@ export class HaTextField extends TextFieldBase { .mdc-text-field__input { width: var(--ha-textfield-input-width, 100%); } + .mdc-text-field:not(.mdc-text-field--with-leading-icon) { + padding: var(--text-field-padding, 0px 16px); + } + .mdc-text-field__affix--suffix { + padding-left: var(--text-field-suffix-padding-left, 12px); + padding-right: var(--text-field-suffix-padding-right, 0px); + } + + input { + text-align: var(--text-field-text-align); + } + + /* Chrome, Safari, Edge, Opera */ + :host([no-spinner]) input::-webkit-outer-spin-button, + :host([no-spinner]) input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + /* Firefox */ + :host([no-spinner]) input[type="number"] { + -moz-appearance: textfield; + } `, ]; } diff --git a/src/components/ha-time-input.ts b/src/components/ha-time-input.ts index 1b14f40579..5efd5a13f3 100644 --- a/src/components/ha-time-input.ts +++ b/src/components/ha-time-input.ts @@ -2,12 +2,13 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { useAmPm } from "../common/datetime/use_am_pm"; import { fireEvent } from "../common/dom/fire_event"; -import "./paper-time-input"; +import "./ha-base-time-input"; import { FrontendLocaleData } from "../data/translation"; +import type { TimeChangedEvent } from "./ha-base-time-input"; @customElement("ha-time-input") export class HaTimeInput extends LitElement { - @property() public locale!: FrontendLocaleData; + @property({ attribute: false }) public locale!: FrontendLocaleData; @property() public value?: string; @@ -15,9 +16,6 @@ export class HaTimeInput extends LitElement { @property({ type: Boolean }) public disabled = false; - @property({ type: Boolean, attribute: "hide-label" }) public hideLabel = - false; - @property({ type: Boolean, attribute: "enable-second" }) public enableSecond = false; @@ -35,40 +33,44 @@ export class HaTimeInput extends LitElement { } return html` - = 12 ? "PM" : "AM")} .disabled=${this.disabled} - @change=${this._timeChanged} - @am-pm-changed=${this._timeChanged} - .hideLabel=${this.hideLabel} + @value-changed=${this._timeChanged} .enableSecond=${this.enableSecond} - > + > `; } - private _timeChanged(ev) { - let value = ev.target.value; + private _timeChanged(ev: CustomEvent<{ value: TimeChangedEvent }>) { + ev.stopPropagation(); + const eventValue = ev.detail.value; + const useAMPM = useAmPm(this.locale); - let hours = Number(ev.target.hour || 0); - if (value && useAMPM) { - if (ev.target.amPm === "PM" && hours < 12) { + let hours = eventValue.hours || 0; + if (eventValue && useAMPM) { + if (eventValue.amPm === "PM" && hours < 12) { hours += 12; } - if (ev.target.amPm === "AM" && hours === 12) { + if (eventValue.amPm === "AM" && hours === 12) { hours = 0; } - value = `${hours.toString().padStart(2, "0")}:${ev.target.min || "00"}:${ - ev.target.sec || "00" - }`; } + const value = `${hours.toString().padStart(2, "0")}:${ + eventValue.minutes ? eventValue.minutes.toString().padStart(2, "0") : "00" + }:${ + eventValue.seconds ? eventValue.seconds.toString().padStart(2, "0") : "00" + }`; + if (value === this.value) { return; } + this.value = value; fireEvent(this, "change"); fireEvent(this, "value-changed", { diff --git a/src/components/paper-time-input.js b/src/components/paper-time-input.js deleted file mode 100644 index 99e9c8de8f..0000000000 --- a/src/components/paper-time-input.js +++ /dev/null @@ -1,497 +0,0 @@ -/** -Adapted from paper-time-input from -https://github.com/ryanburns23/paper-time-input -MIT Licensed. Copyright (c) 2017 Ryan Burns - -`` Polymer element to accept a time with paper-input & paper-dropdown-menu -Inspired by the time input in google forms - -### Styling - -`` provides the following custom properties and mixins for styling: - -Custom property | Description | Default -----------------|-------------|---------- -`--paper-time-input-dropdown-ripple-color` | dropdown ripple color | `--primary-color` -`--paper-time-input-cotnainer` | Mixin applied to the inputs | `{}` -`--paper-time-dropdown-input-cotnainer` | Mixin applied to the dropdown input | `{}` -*/ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; - -export class PaperTimeInput extends PolymerElement { - static get template() { - return html` - - - -
- - - : - - - - - : - - - - - : - - - - - - - - - - AM - PM - - -
- `; - } - - static get properties() { - return { - /** - * Label for the input - */ - label: { - type: String, - value: "Time", - }, - /** - * auto validate time inputs - */ - autoValidate: { - type: Boolean, - value: true, - }, - /** - * hides the label - */ - hideLabel: { - type: Boolean, - value: false, - }, - /** - * float the input labels - */ - floatInputLabels: { - type: Boolean, - value: false, - }, - /** - * always float the input labels - */ - alwaysFloatInputLabels: { - type: Boolean, - value: false, - }, - /** - * 12 or 24 hr format - */ - format: { - type: Number, - value: 12, - }, - /** - * disables the inputs - */ - disabled: { - type: Boolean, - value: false, - }, - /** - * hour - */ - hour: { - type: String, - notify: true, - }, - /** - * minute - */ - min: { - type: String, - notify: true, - }, - /** - * second - */ - sec: { - type: String, - notify: true, - }, - /** - * milli second - */ - millisec: { - type: String, - notify: true, - }, - /** - * Label for the hour input - */ - hourLabel: { - type: String, - value: "", - }, - /** - * Label for the min input - */ - minLabel: { - type: String, - value: "", - }, - /** - * Label for the sec input - */ - secLabel: { - type: String, - value: "", - }, - /** - * Label for the milli sec input - */ - millisecLabel: { - type: String, - value: "", - }, - /** - * show the sec field - */ - enableSecond: { - type: Boolean, - value: false, - }, - /** - * show the milli sec field - */ - enableMillisecond: { - type: Boolean, - value: false, - }, - /** - * limit hours input - */ - noHoursLimit: { - type: Boolean, - value: false, - }, - /** - * AM or PM - */ - amPm: { - type: String, - notify: true, - value: "AM", - }, - /** - * Formatted time string - */ - value: { - type: String, - notify: true, - readOnly: true, - computed: "_computeTime(min, hour, sec, millisec, amPm)", - }, - }; - } - - /** - * Validate the inputs - * @return {boolean} - */ - validate() { - let valid = true; - // Validate hour & min fields - if (!this.$.hour.validate() || !this.$.min.validate()) { - valid = false; - } - // Validate second field - if (this.enableSecond && !this.$.sec.validate()) { - valid = false; - } - // Validate milli second field - if (this.enableMillisecond && !this.$.millisec.validate()) { - valid = false; - } - // Validate AM PM if 12 hour time - if (this.format === 12 && !this.$.dropdown.validate()) { - valid = false; - } - return valid; - } - - /** - * Create time string - */ - _computeTime(min, hour, sec, millisec, amPm) { - let str; - if ( - hour || - min || - (sec && this.enableSecond) || - (millisec && this.enableMillisecond) - ) { - hour = hour || "00"; - min = min || "00"; - sec = sec || "00"; - millisec = millisec || "000"; - str = hour + ":" + min; - // add sec field - if (this.enableSecond && sec) { - str = str + ":" + sec; - } - // add milli sec field - if (this.enableMillisecond && millisec) { - str = str + ":" + millisec; - } - // No ampm on 24 hr time - if (this.format === 12) { - str = str + " " + amPm; - } - } - - return str; - } - - _onFocus(ev) { - ev.target.inputElement.inputElement.select(); - } - - /** - * Format milli sec - */ - _formatMillisec() { - if (this.millisec.toString().length === 1) { - this.millisec = this.millisec.toString().padStart(3, "0"); - } - } - - /** - * Format sec - */ - _formatSec() { - if (this.sec.toString().length === 1) { - this.sec = this.sec.toString().padStart(2, "0"); - } - } - - /** - * Format min - */ - _formatMin() { - if (this.min.toString().length === 1) { - this.min = this.min.toString().padStart(2, "0"); - } - } - - /** - * Format hour - */ - _shouldFormatHour() { - if (this.format === 24 && this.hour.toString().length === 1) { - this.hour = this.hour.toString().padStart(2, "0"); - } - } - - /** - * 24 hour format has a max hr of 23 - */ - _computeHourMax(format) { - if (this.noHoursLimit) { - return null; - } - if (format === 12) { - return format; - } - return 23; - } - - _equal(n1, n2) { - return n1 === n2; - } - - _computeClassNames(hasSuffix) { - return hasSuffix ? " " : "no-suffix"; - } -} - -customElements.define("paper-time-input", PaperTimeInput); diff --git a/src/dialogs/more-info/controls/more-info-input_datetime.ts b/src/dialogs/more-info/controls/more-info-input_datetime.ts index 4d1be5c70b..a0f11eb726 100644 --- a/src/dialogs/more-info/controls/more-info-input_datetime.ts +++ b/src/dialogs/more-info/controls/more-info-input_datetime.ts @@ -43,7 +43,6 @@ class MoreInfoInputDatetime extends LitElement { : this.stateObj.state} .locale=${this.hass.locale} .disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)} - hide-label @value-changed=${this._timeChanged} @click=${this._stopEventPropagation} > diff --git a/src/panels/lovelace/entity-rows/hui-input-datetime-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-datetime-entity-row.ts index 21a8ada291..a727d2f67d 100644 --- a/src/panels/lovelace/entity-rows/hui-input-datetime-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-datetime-entity-row.ts @@ -72,7 +72,6 @@ class HuiInputDatetimeEntityRow extends LitElement implements LovelaceRow { : stateObj.state} .locale=${this.hass.locale} .disabled=${UNAVAILABLE_STATES.includes(stateObj.state)} - hide-label @value-changed=${this._timeChanged} @click=${this._stopEventPropagation} > From 4db943c5ffbc8e5cf949a086a8ecd7d54a90ac04 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Wed, 9 Feb 2022 13:02:03 -0500 Subject: [PATCH 030/174] Set initial focus for device, area, and entity dialogs (#11622) --- src/dialogs/more-info/ha-more-info-dialog.ts | 3 ++- src/panels/config/areas/dialog-area-registry-detail.ts | 1 + .../device-registry-detail/dialog-device-registry-detail.ts | 1 + src/panels/config/entities/dialog-entity-editor.ts | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 64082db266..dd33958584 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -164,6 +164,7 @@ export class MoreInfoDialog extends LitElement { .label=${this.hass.localize( "ui.dialogs.more_info_control.details" )} + dialogInitialFocus > -
+
${cache( this._currTabIndex === 0 ? html` diff --git a/src/panels/config/areas/dialog-area-registry-detail.ts b/src/panels/config/areas/dialog-area-registry-detail.ts index 9bbb35de18..3fadb0ea58 100644 --- a/src/panels/config/areas/dialog-area-registry-detail.ts +++ b/src/panels/config/areas/dialog-area-registry-detail.ts @@ -92,6 +92,7 @@ class DialogAreaDetail extends LitElement { "ui.panel.config.areas.editor.name_required" )} .invalid=${nameInvalid} + dialogInitialFocus > ${Object.entries(this._extraTabs).map( From d37d99223d1f9a08236343358727bd66bc61b662 Mon Sep 17 00:00:00 2001 From: Patrick ZAJDA Date: Wed, 9 Feb 2022 19:10:41 +0100 Subject: [PATCH 031/174] Add aria-label to table headers with no title (#11503) --- src/components/data-table/ha-data-table.ts | 2 ++ .../config/automation/ha-automation-picker.ts | 9 ++++++ .../devices/ha-config-devices-dashboard.ts | 3 ++ .../config/entities/ha-config-entities.ts | 3 ++ .../config/helpers/ha-config-helpers.ts | 6 ++++ .../ha-config-lovelace-dashboards.ts | 6 ++++ src/panels/config/scene/ha-scene-dashboard.ts | 8 +++++ src/panels/config/script/ha-script-picker.ts | 7 +++++ src/panels/config/tags/ha-config-tags.ts | 2 ++ src/panels/config/users/ha-config-users.ts | 3 ++ .../card-editor/hui-entity-picker-table.ts | 3 ++ src/translations/en.json | 29 +++++++++++++++---- 12 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index c05c968b4f..18d4d4a5a1 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -70,6 +70,7 @@ export interface DataTableSortColumnData { export interface DataTableColumnData extends DataTableSortColumnData { title: TemplateResult | string; + label?: TemplateResult | string; type?: "numeric" | "icon" | "icon-button" | "overflow-menu"; template?: (data: any, row: T) => TemplateResult | string; width?: string; @@ -294,6 +295,7 @@ export class HaDataTable extends LitElement { }; return html`
html` @@ -127,6 +130,9 @@ class HaAutomationPicker extends LitElement { `, }; columns.trigger = { + label: this.hass.localize( + "ui.panel.config.automation.picker.headers.trigger" + ), title: html` ${this.hass.localize("ui.card.automation.trigger")} @@ -146,6 +152,9 @@ class HaAutomationPicker extends LitElement { } columns.actions = { title: "", + label: this.hass.localize( + "ui.panel.config.automation.picker.headers.actions" + ), type: "overflow-menu", template: (_info, automation: any) => html` disabled_by diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 8c8bcec0d7..36afbb6092 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -168,6 +168,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { (narrow, _language, showDisabled): DataTableColumnContainer => ({ icon: { title: "", + label: this.hass.localize( + "ui.panel.config.entities.picker.headers.state_icon" + ), type: "icon", template: (_, entry: EntityRow) => html` icon @@ -88,6 +91,9 @@ export class HaConfigHelpers extends LitElement { }; columns.editable = { title: "", + label: this.hass.localize( + "ui.panel.config.helpers.picker.headers.editable" + ), type: "icon", template: (editable) => html` ${!editable diff --git a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts index 4ac61b267b..59c787b91c 100644 --- a/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts +++ b/src/panels/config/lovelace/dashboards/ha-config-lovelace-dashboards.ts @@ -53,6 +53,9 @@ export class HaConfigLovelaceDashboards extends LitElement { const columns: DataTableColumnContainer = { icon: { title: "", + label: this.hass.localize( + "ui.panel.config.lovelace.dashboards.picker.headers.icon" + ), type: "icon", template: (icon, dashboard) => icon @@ -161,6 +164,9 @@ export class HaConfigLovelaceDashboards extends LitElement { columns.url_path = { title: "", + label: this.hass.localize( + "ui.panel.config.lovelace.dashboards.picker.headers.url" + ), filterable: true, width: "100px", template: (urlPath) => diff --git a/src/panels/config/scene/ha-scene-dashboard.ts b/src/panels/config/scene/ha-scene-dashboard.ts index 53310c5e8c..07ca40e110 100644 --- a/src/panels/config/scene/ha-scene-dashboard.ts +++ b/src/panels/config/scene/ha-scene-dashboard.ts @@ -67,6 +67,9 @@ class HaSceneDashboard extends LitElement { (_language): DataTableColumnContainer => ({ activate: { title: "", + label: this.hass.localize( + "ui.panel.config.scene.picker.headers.activate" + ), type: "icon-button", template: (_toggle, scene) => html` @@ -82,6 +85,7 @@ class HaSceneDashboard extends LitElement { }, icon: { title: "", + label: this.hass.localize("ui.panel.config.scene.picker.headers.state"), type: "icon", template: (_, scene) => html` `, @@ -95,6 +99,9 @@ class HaSceneDashboard extends LitElement { }, info: { title: "", + label: this.hass.localize( + "ui.panel.config.scene.picker.headers.show_info" + ), type: "icon-button", template: (_info, scene) => html` html` html` @@ -84,6 +85,9 @@ class HaScriptPicker extends LitElement { }, icon: { title: "", + label: this.hass.localize( + "ui.panel.config.script.picker.headers.state" + ), type: "icon", template: (_icon, script) => html` `, @@ -124,6 +128,7 @@ class HaScriptPicker extends LitElement { } columns.info = { title: "", + label: this.hass.localize("ui.panel.config.script.picker.show_info"), type: "icon-button", template: (_info, script) => html` html` @@ -152,6 +158,7 @@ class HaScriptPicker extends LitElement { }; columns.edit = { title: "", + label: this.hass.localize("ui.panel.config.script.picker.edit_script"), type: "icon-button", template: (_info, script: any) => html` diff --git a/src/panels/config/tags/ha-config-tags.ts b/src/panels/config/tags/ha-config-tags.ts index 6b53cac199..26a0c6f1e8 100644 --- a/src/panels/config/tags/ha-config-tags.ts +++ b/src/panels/config/tags/ha-config-tags.ts @@ -61,6 +61,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) { const columns: DataTableColumnContainer = { icon: { title: "", + label: this.hass.localize("ui.panel.config.tag.headers.icon"), type: "icon", template: (_icon, tag) => html``, }, @@ -103,6 +104,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) { if (this._canWriteTags) { columns.write = { title: "", + label: this.hass.localize("ui.panel.config.tag.headers.write"), type: "icon-button", template: (_write, tag: any) => html` html` Date: Wed, 9 Feb 2022 23:01:05 +0100 Subject: [PATCH 032/174] Add loadCardHelpers to cast scope (#11616) --- cast/src/receiver/layout/hc-lovelace.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cast/src/receiver/layout/hc-lovelace.ts b/cast/src/receiver/layout/hc-lovelace.ts index 122ce510fd..3cb24f24cf 100644 --- a/cast/src/receiver/layout/hc-lovelace.ts +++ b/cast/src/receiver/layout/hc-lovelace.ts @@ -7,6 +7,9 @@ import "../../../../src/panels/lovelace/views/hui-view"; import { HomeAssistant } from "../../../../src/types"; import "./hc-launch-screen"; +(window as any).loadCardHelpers = () => + import("../../../../src/panels/lovelace/custom-card-helpers"); + @customElement("hc-lovelace") class HcLovelace extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; From 9c9bfa2b77341fa816555482f15b8ac94d27e8f8 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 9 Feb 2022 23:35:49 +0100 Subject: [PATCH 033/174] Update code editor to material 3 look (#11628) --- src/resources/codemirror.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/resources/codemirror.ts b/src/resources/codemirror.ts index ffd7d1d7f8..3b69e471b5 100644 --- a/src/resources/codemirror.ts +++ b/src/resources/codemirror.ts @@ -41,8 +41,10 @@ export const theme = EditorView.theme({ "&": { color: "var(--primary-text-color)", backgroundColor: - "var(--code-editor-background-color, var(--card-background-color))", + "var(--code-editor-background-color, var(--mdc-text-field-fill-color, whitesmoke))", "& ::selection": { backgroundColor: "rgba(var(--rgb-primary-color), 0.3)" }, + borderRadius: + "var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0px 0px", caretColor: "var(--secondary-text-color)", height: "var(--code-mirror-height, auto)", maxHeight: "var(--code-mirror-max-height, unset)", @@ -64,7 +66,10 @@ export const theme = EditorView.theme({ ".cm-scroller": { outline: "none" }, - ".cm-content": { caretColor: "var(--secondary-text-color)" }, + ".cm-content": { + caretColor: "var(--secondary-text-color)", + paddingTop: "16px", + }, ".cm-panels": { backgroundColor: "var(--primary-background-color)", @@ -166,7 +171,7 @@ export const theme = EditorView.theme({ ".cm-gutters": { backgroundColor: - "var(--paper-dialog-background-color, var(--primary-background-color))", + "var(--code-editor-gutter-color, var(--mdc-text-field-fill-color, whitesmoke))", color: "var(--paper-dialog-color, var(--secondary-text-color))", border: "none", borderRight: From dc27871189627c6c5bf17e01b48865ff6a20b8b0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 9 Feb 2022 23:36:16 +0100 Subject: [PATCH 034/174] Set button role on button card and handle enter and space (#11627) --- src/panels/lovelace/cards/hui-button-card.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts index a490f9277c..f96a1d9201 100644 --- a/src/panels/lovelace/cards/hui-button-card.ts +++ b/src/panels/lovelace/cards/hui-button-card.ts @@ -160,9 +160,13 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { hasHold: hasAction(this._config!.hold_action), hasDoubleClick: hasAction(this._config!.double_tap_action), })} + role="button" + aria-label=${this._config.name || + (stateObj ? computeStateName(stateObj) : "")} tabindex=${ifDefined( hasAction(this._config.tap_action) ? "0" : undefined )} + @keydown=${this._handleKeyDown} > ${this._config.show_icon ? html` @@ -230,6 +234,12 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { return this._ripple; }); + private _handleKeyDown(ev: KeyboardEvent) { + if (ev.key === "Enter" || ev.key === " ") { + handleAction(this, this.hass!, this._config!, "tap"); + } + } + @eventOptions({ passive: true }) private handleRippleActivate(evt?: Event) { this._rippleHandlers.startPress(evt); From 134ed7d303c9065243ac2fc86443de0336927117 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 10 Feb 2022 00:14:25 +0100 Subject: [PATCH 035/174] Only load ha-selector when needed (#11630) --- src/components/ha-form/ha-form.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index 100dadc2b7..15293808e8 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -1,4 +1,4 @@ -import { css, CSSResultGroup, html, LitElement } from "lit"; +import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import { dynamicElement } from "../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../common/dom/fire_event"; @@ -11,12 +11,13 @@ import "./ha-form-multi_select"; import "./ha-form-positive_time_period_dict"; import "./ha-form-select"; import "./ha-form-string"; -import "../ha-selector/ha-selector"; import { HaFormElement, HaFormDataContainer, HaFormSchema } from "./types"; import { HomeAssistant } from "../../types"; const getValue = (obj, item) => (obj ? obj[item.name] : null); +let selectorImported = false; + @customElement("ha-form") export class HaForm extends LitElement implements HaFormElement { @property() public hass!: HomeAssistant; @@ -46,6 +47,19 @@ export class HaForm extends LitElement implements HaFormElement { } } + willUpdate(changedProperties: PropertyValues) { + super.willUpdate(changedProperties); + if ( + !selectorImported && + changedProperties.has("schema") && + this.schema && + this.schema.some((item) => "selector" in item) + ) { + selectorImported = true; + import("../ha-selector/ha-selector"); + } + } + protected render() { return html`
From ce3b8544b985c9185cf7679f135943e9434e5a7c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 10 Feb 2022 00:15:00 +0100 Subject: [PATCH 036/174] Fix service control for older browsers (#11629) --- src/components/ha-service-control.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index 5b9a8a6506..9cac6ae6b9 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -135,7 +135,9 @@ export class HaServiceControl extends LitElement { let updatedDefaultValue = false; if (this._value && serviceData) { // Set mandatory bools without a default value to false - this._value.data ??= {}; + if (!this._value.data) { + this._value.data = {}; + } serviceData.fields.forEach((field) => { if ( field.selector && From a0aed9112c2d2aa92b8400aa9128f6efe56fb2bd Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 10 Feb 2022 00:18:44 +0100 Subject: [PATCH 037/174] Migrate a bunch of paper-dropdowns (#11626) --- src/components/data-table/ha-data-table.ts | 1 + src/components/ha-blueprint-picker.ts | 61 +++---- src/components/ha-combo-box.ts | 1 - src/components/user/ha-user-picker.ts | 66 +++---- src/components/user/ha-users-picker.ts | 2 +- .../config-flow/step-flow-create-entry.ts | 10 -- .../more-info/controls/more-info-climate.ts | 166 ++++++++---------- .../more-info/controls/more-info-fan.js | 45 +++-- .../controls/more-info-humidifier.ts | 57 +++--- .../more-info/controls/more-info-light.ts | 40 ++--- .../controls/more-info-media_player.ts | 80 ++++----- .../more-info/controls/more-info-remote.ts | 36 ++-- .../more-info/controls/more-info-vacuum.ts | 36 ++-- .../controls/more-info-water_heater.js | 49 +++--- .../types/ha-automation-action-repeat.ts | 50 +++--- .../automation/blueprint-automation-editor.ts | 1 - .../automation/dialog-new-automation.ts | 4 +- .../config/automation/ha-automation-editor.ts | 1 - .../automation/manual-automation-editor.ts | 40 ++--- .../config/cloud/account/cloud-tts-pref.ts | 78 ++++---- .../cloud/account/dialog-cloud-tts-try.ts | 80 +++++---- .../dialog-device-registry-detail.ts | 3 - .../config/entities/ha-config-entities.ts | 3 - .../config/helpers/ha-config-helpers.ts | 3 - .../config-elements/hui-glance-card-editor.ts | 4 +- .../hui-graph-footer-editor.ts | 1 - src/translations/en.json | 1 + 27 files changed, 412 insertions(+), 507 deletions(-) diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 18d4d4a5a1..372bfa4460 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -945,6 +945,7 @@ export class HaDataTable extends LitElement { } .scroller { height: calc(100% - 57px); + overflow: overlay !important; } .mdc-data-table__table.auto-height .scroller { diff --git a/src/components/ha-blueprint-picker.ts b/src/components/ha-blueprint-picker.ts index ba17709a76..bbe399e214 100644 --- a/src/components/ha-blueprint-picker.ts +++ b/src/components/ha-blueprint-picker.ts @@ -1,10 +1,10 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../common/dom/fire_event"; +import { stopPropagation } from "../common/dom/stop_propagation"; import { stringCompare } from "../common/string/compare"; import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint"; import { HomeAssistant } from "../types"; @@ -24,7 +24,11 @@ class HaBluePrintPicker extends LitElement { @property({ type: Boolean }) public disabled = false; public open() { - this.shadowRoot!.querySelector("paper-dropdown-menu-light")!.open(); + const select = this.shadowRoot?.querySelector("mwc-select"); + if (select) { + // @ts-expect-error + select.menuOpen = true; + } } private _processedBlueprints = memoizeOne((blueprints?: Blueprints) => { @@ -45,32 +49,29 @@ class HaBluePrintPicker extends LitElement { return html``; } return html` - - - - ${this.hass.localize( - "ui.components.blueprint-picker.select_blueprint" - )} - - ${this._processedBlueprints(this.blueprints).map( - (blueprint) => html` - - ${blueprint.name} - - ` + + ${this.hass.localize( + "ui.components.blueprint-picker.select_blueprint" )} - - + + ${this._processedBlueprints(this.blueprints).map( + (blueprint) => html` + + ${blueprint.name} + + ` + )} + `; } @@ -84,10 +85,10 @@ class HaBluePrintPicker extends LitElement { } private _blueprintChanged(ev) { - const newValue = ev.detail.item.dataset.blueprintPath; + const newValue = ev.target.value; if (newValue !== this.value) { - this.value = ev.detail.value; + this.value = newValue; setTimeout(() => { fireEvent(this, "value-changed", { value: newValue }); fireEvent(this, "change"); @@ -100,15 +101,11 @@ class HaBluePrintPicker extends LitElement { :host { display: inline-block; } - paper-dropdown-menu-light { + mwc-select { width: 100%; min-width: 200px; display: block; } - paper-item { - cursor: pointer; - min-width: 200px; - } `; } } diff --git a/src/components/ha-combo-box.ts b/src/components/ha-combo-box.ts index c80a131dca..f8f1bd5cde 100644 --- a/src/components/ha-combo-box.ts +++ b/src/components/ha-combo-box.ts @@ -209,7 +209,6 @@ export class HaComboBox extends LitElement { :host { display: block; width: 100%; - margin-top: 4px; } vaadin-combo-box-light { position: relative; diff --git a/src/components/user/ha-user-picker.ts b/src/components/user/ha-user-picker.ts index b12f1f20fd..f1ac638e0b 100644 --- a/src/components/user/ha-user-picker.ts +++ b/src/components/user/ha-user-picker.ts @@ -1,8 +1,3 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-icon-item"; -import "@polymer/paper-item/paper-item-body"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -11,6 +6,8 @@ import { stringCompare } from "../../common/string/compare"; import { fetchUsers, User } from "../../data/user"; import { HomeAssistant } from "../../types"; import "./ha-user-badge"; +import "@material/mwc-select/mwc-select"; +import "@material/mwc-list/mwc-list-item"; class HaUserPicker extends LitElement { public hass?: HomeAssistant; @@ -37,34 +34,31 @@ class HaUserPicker extends LitElement { protected render(): TemplateResult { return html` - - - - ${this.noUserLabel || - this.hass?.localize("ui.components.user-picker.no_user")} - - ${this._sortedUsers(this.users).map( - (user) => html` - - - ${user.name} - - ` - )} - - + ${this.users?.length === 0 + ? html` + ${this.noUserLabel || + this.hass?.localize("ui.components.user-picker.no_user")} + ` + : ""} + ${this._sortedUsers(this.users).map( + (user) => html` + + + ${user.name} + + ` + )} + `; } @@ -78,10 +72,10 @@ class HaUserPicker extends LitElement { } private _userChanged(ev) { - const newValue = ev.detail.item.dataset.userId; + const newValue = ev.target.value; if (newValue !== this.value) { - this.value = ev.detail.value; + this.value = newValue; setTimeout(() => { fireEvent(this, "value-changed", { value: newValue }); fireEvent(this, "change"); @@ -94,15 +88,9 @@ class HaUserPicker extends LitElement { :host { display: inline-block; } - paper-dropdown-menu-light { + mwc-list { display: block; } - paper-listbox { - min-width: 200px; - } - paper-icon-item { - cursor: pointer; - } `; } } diff --git a/src/components/user/ha-users-picker.ts b/src/components/user/ha-users-picker.ts index d9c4ff148a..ceb972d178 100644 --- a/src/components/user/ha-users-picker.ts +++ b/src/components/user/ha-users-picker.ts @@ -75,7 +75,7 @@ class HaUsersPickerLight extends LitElement { ) )} *:last-child { margin-left: auto; } - paper-dropdown-menu-light { - cursor: pointer; - } - paper-item { - cursor: pointer; - white-space: nowrap; - } @media all and (max-width: 450px), all and (max-height: 500px) { .device { width: 100%; diff --git a/src/dialogs/more-info/controls/more-info-climate.ts b/src/dialogs/more-info/controls/more-info-climate.ts index 6074e2be62..3512ba86a3 100644 --- a/src/dialogs/more-info/controls/more-info-climate.ts +++ b/src/dialogs/more-info/controls/more-info-climate.ts @@ -1,6 +1,3 @@ -import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, @@ -15,7 +12,6 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { supportsFeature } from "../../../common/entity/supports-feature"; import { computeRTLDirection } from "../../../common/util/compute_rtl"; import "../../../components/ha-climate-control"; -import "../../../components/ha-paper-dropdown-menu"; import "../../../components/ha-slider"; import "../../../components/ha-switch"; import { @@ -30,6 +26,9 @@ import { compareClimateHvacModes, } from "../../../data/climate"; import { HomeAssistant } from "../../../types"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; class MoreInfoClimate extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -169,109 +168,93 @@ class MoreInfoClimate extends LitElement {
- - - ${stateObj.attributes.hvac_modes - .concat() - .sort(compareClimateHvacModes) - .map( - (mode) => html` - - ${hass.localize(`component.climate.state._.${mode}`)} - - ` - )} - - + ${stateObj.attributes.hvac_modes + .concat() + .sort(compareClimateHvacModes) + .map( + (mode) => html` + + ${hass.localize(`component.climate.state._.${mode}`)} + + ` + )} +
${supportPresetMode && stateObj.attributes.preset_modes ? html`
- - - ${stateObj.attributes.preset_modes!.map( - (mode) => html` - - ${hass.localize( - `state_attributes.climate.preset_mode.${mode}` - ) || mode} - - ` - )} - - + ${stateObj.attributes.preset_modes!.map( + (mode) => html` + + ${hass.localize( + `state_attributes.climate.preset_mode.${mode}` + ) || mode} + + ` + )} +
` : ""} ${supportFanMode && stateObj.attributes.fan_modes ? html`
- - - ${stateObj.attributes.fan_modes!.map( - (mode) => html` - - ${hass.localize( - `state_attributes.climate.fan_mode.${mode}` - ) || mode} - - ` - )} - - + ${stateObj.attributes.fan_modes!.map( + (mode) => html` + + ${hass.localize( + `state_attributes.climate.fan_mode.${mode}` + ) || mode} + + ` + )} +
` : ""} ${supportSwingMode && stateObj.attributes.swing_modes ? html`
- - - ${stateObj.attributes.swing_modes!.map( - (mode) => html` - ${mode} - ` - )} - - + ${stateObj.attributes.swing_modes!.map( + (mode) => html` + ${mode} + ` + )} +
` : ""} @@ -366,7 +349,7 @@ class MoreInfoClimate extends LitElement { } private _handleFanmodeChanged(ev) { - const newVal = ev.detail.value; + const newVal = ev.target.value; this._callServiceHelper( this.stateObj!.attributes.fan_mode, newVal, @@ -376,14 +359,14 @@ class MoreInfoClimate extends LitElement { } private _handleOperationmodeChanged(ev) { - const newVal = ev.detail.value; + const newVal = ev.target.value; this._callServiceHelper(this.stateObj!.state, newVal, "set_hvac_mode", { hvac_mode: newVal, }); } private _handleSwingmodeChanged(ev) { - const newVal = ev.detail.value; + const newVal = ev.target.value; this._callServiceHelper( this.stateObj!.attributes.swing_mode, newVal, @@ -393,7 +376,7 @@ class MoreInfoClimate extends LitElement { } private _handlePresetmodeChanged(ev) { - const newVal = ev.detail.value || null; + const newVal = ev.target.value || null; this._callServiceHelper( this.stateObj!.attributes.preset_mode, newVal, @@ -444,12 +427,9 @@ class MoreInfoClimate extends LitElement { color: var(--primary-text-color); } - ha-paper-dropdown-menu { + mwc-select { width: 100%; - } - - paper-item { - cursor: pointer; + margin-top: 8px; } ha-slider { @@ -488,3 +468,9 @@ class MoreInfoClimate extends LitElement { } customElements.define("more-info-climate", MoreInfoClimate); + +declare global { + interface HTMLElementTagNameMap { + "more-info-climate": MoreInfoClimate; + } +} diff --git a/src/dialogs/more-info/controls/more-info-fan.js b/src/dialogs/more-info/controls/more-info-fan.js index 3bfa8ec2aa..3d712670c7 100644 --- a/src/dialogs/more-info/controls/more-info-fan.js +++ b/src/dialogs/more-info/controls/more-info-fan.js @@ -1,6 +1,4 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; @@ -10,11 +8,12 @@ import "../../../components/ha-attributes"; import "../../../components/ha-icon"; import "../../../components/ha-icon-button"; import "../../../components/ha-labeled-slider"; -import "../../../components/ha-paper-dropdown-menu"; import "../../../components/ha-switch"; import { SUPPORT_SET_SPEED } from "../../../data/fan"; import { EventsMixin } from "../../../mixins/events-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; /* * @appliesMixin EventsMixin @@ -38,13 +37,9 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) { display: block; } - ha-paper-dropdown-menu { + mwc-select { width: 100%; } - - paper-item { - cursor: pointer; - }
@@ -62,25 +57,21 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
- - - - - + [[item]] + +
@@ -180,7 +171,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) { presetModeChanged(ev) { const oldVal = this.stateObj.attributes.preset_mode; - const newVal = ev.detail.value; + const newVal = ev.target.value; if (!newVal || oldVal === newVal) return; @@ -190,6 +181,10 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) { }); } + stopPropagation(ev) { + ev.stopPropagation(); + } + percentageChanged(ev) { const oldVal = parseInt(this.stateObj.attributes.percentage, 10); const newVal = ev.target.value; diff --git a/src/dialogs/more-info/controls/more-info-humidifier.ts b/src/dialogs/more-info/controls/more-info-humidifier.ts index 8271b86703..3d6aa6e0c9 100644 --- a/src/dialogs/more-info/controls/more-info-humidifier.ts +++ b/src/dialogs/more-info/controls/more-info-humidifier.ts @@ -1,6 +1,3 @@ -import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, @@ -12,9 +9,9 @@ import { import { property } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../../common/dom/fire_event"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; import { computeRTLDirection } from "../../../common/util/compute_rtl"; -import "../../../components/ha-paper-dropdown-menu"; import "../../../components/ha-slider"; import "../../../components/ha-switch"; import { @@ -22,6 +19,8 @@ import { HUMIDIFIER_SUPPORT_MODES, } from "../../../data/humidifier"; import { HomeAssistant } from "../../../types"; +import "@material/mwc-list/mwc-list"; +import "@material/mwc-list/mwc-list-item"; class MoreInfoHumidifier extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -69,28 +68,24 @@ class MoreInfoHumidifier extends LitElement { ${supportModes ? html`
- - - ${stateObj.attributes.available_modes!.map( - (mode) => html` - - ${hass.localize( - `state_attributes.humidifier.mode.${mode}` - ) || mode} - - ` - )} - - + ${stateObj.attributes.available_modes!.map( + (mode) => html` + + ${hass.localize( + `state_attributes.humidifier.mode.${mode}` + ) || mode} + + ` + )} +
` : ""} @@ -124,7 +119,7 @@ class MoreInfoHumidifier extends LitElement { } private _handleModeChanged(ev) { - const newVal = ev.detail.value || null; + const newVal = ev.target.value || null; this._callServiceHelper( this.stateObj!.attributes.mode, newVal, @@ -175,14 +170,10 @@ class MoreInfoHumidifier extends LitElement { color: var(--primary-text-color); } - ha-paper-dropdown-menu { + mwc-select { width: 100%; } - paper-item { - cursor: pointer; - } - ha-slider { width: 100%; } @@ -207,3 +198,9 @@ class MoreInfoHumidifier extends LitElement { } customElements.define("more-info-humidifier", MoreInfoHumidifier); + +declare global { + interface HTMLElementTagNameMap { + "more-info-humidifier": MoreInfoHumidifier; + } +} diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index cc656c8e60..12c28a045b 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -1,6 +1,6 @@ +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { mdiPalette } from "@mdi/js"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, @@ -11,13 +11,13 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-attributes"; import "../../../components/ha-button-toggle-group"; import "../../../components/ha-color-picker"; import "../../../components/ha-icon-button"; import "../../../components/ha-labeled-slider"; -import "../../../components/ha-paper-dropdown-menu"; import { getLightCurrentModeRgbColor, LightColorModes, @@ -208,24 +208,22 @@ class MoreInfoLight extends LitElement { this.stateObj!.attributes.effect_list?.length ? html`
- - ${this.stateObj.attributes.effect_list.map( - (effect: string) => html` - ${effect} - ` - )} - - + ${this.stateObj.attributes.effect_list.map( + (effect: string) => html` + + ${effect} + + ` + )} + ` : ""} ` @@ -322,8 +320,8 @@ class MoreInfoLight extends LitElement { this._mode = ev.detail.value; } - private _effectChanged(ev: CustomEvent) { - const newVal = ev.detail.item.itemName; + private _effectChanged(ev) { + const newVal = ev.target.value; if (!newVal || this.stateObj!.attributes.effect === newVal) { return; diff --git a/src/dialogs/more-info/controls/more-info-media_player.ts b/src/dialogs/more-info/controls/more-info-media_player.ts index e836e3dd28..526445cf5d 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.ts +++ b/src/dialogs/more-info/controls/more-info-media_player.ts @@ -1,4 +1,6 @@ import "@material/mwc-button/mwc-button"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { mdiLoginVariant, mdiMusicNote, @@ -10,17 +12,15 @@ import { mdiVolumePlus, } from "@mdi/js"; import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; import { computeRTLDirection } from "../../../common/util/compute_rtl"; import "../../../components/ha-icon-button"; -import "../../../components/ha-svg-icon"; -import "../../../components/ha-paper-dropdown-menu"; import "../../../components/ha-slider"; +import "../../../components/ha-svg-icon"; import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog"; import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity"; import { @@ -139,23 +139,21 @@ class MoreInfoMediaPlayer extends LitElement { class="source-input" .path=${mdiLoginVariant} > - - - ${stateObj.attributes.source_list!.map( - (source) => - html` - ${source} - ` - )} - - + ${stateObj.attributes.source_list!.map( + (source) => + html` + ${source} + ` + )} +
` : ""} @@ -164,24 +162,20 @@ class MoreInfoMediaPlayer extends LitElement { ? html`
- - - ${stateObj.attributes.sound_mode_list.map( - (mode) => html` - ${mode} - ` - )} - - + ${stateObj.attributes.sound_mode_list.map( + (mode) => html` + ${mode} + ` + )} +
` : ""} @@ -242,15 +236,11 @@ class MoreInfoMediaPlayer extends LitElement { margin-top: 24px; } - .source-input ha-paper-dropdown-menu, - .sound-input ha-paper-dropdown-menu { + .source-input mwc-select, + .sound-input mwc-select { margin-left: 10px; flex-grow: 1; } - - paper-item { - cursor: pointer; - } `; } @@ -279,8 +269,8 @@ class MoreInfoMediaPlayer extends LitElement { }); } - private _handleSourceChanged(e: CustomEvent) { - const newVal = e.detail.item.itemName; + private _handleSourceChanged(e) { + const newVal = e.target.value; if (!newVal || this.stateObj!.attributes.source === newVal) { return; @@ -292,8 +282,8 @@ class MoreInfoMediaPlayer extends LitElement { }); } - private _handleSoundModeChanged(e: CustomEvent) { - const newVal = e.detail.item.itemName; + private _handleSoundModeChanged(e) { + const newVal = e.target.value; if (!newVal || this.stateObj?.attributes.sound_mode === newVal) { return; diff --git a/src/dialogs/more-info/controls/more-info-remote.ts b/src/dialogs/more-info/controls/more-info-remote.ts index 421537bf67..d952dfbeab 100644 --- a/src/dialogs/more-info/controls/more-info-remote.ts +++ b/src/dialogs/more-info/controls/more-info-remote.ts @@ -1,12 +1,12 @@ -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-attributes"; -import "../../../components/ha-paper-dropdown-menu"; import { RemoteEntity, REMOTE_SUPPORT_ACTIVITY } from "../../../data/remote"; import { HomeAssistant } from "../../../types"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-list/mwc-list"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; const filterExtraAttributes = "activity_list,current_activity"; @@ -26,24 +26,22 @@ class MoreInfoRemote extends LitElement { return html` ${supportsFeature(stateObj, REMOTE_SUPPORT_ACTIVITY) ? html` - - - ${stateObj.attributes.activity_list!.map( - (activity) => html` - ${activity} - ` - )} - - + ${stateObj.attributes.activity_list!.map( + (activity) => html` + ${activity} + ` + )} + ` : ""} @@ -55,9 +53,9 @@ class MoreInfoRemote extends LitElement { `; } - private handleActivityChanged(ev: CustomEvent) { + private handleActivityChanged(ev) { const oldVal = this.stateObj!.attributes.current_activity; - const newVal = ev.detail.item.itemName; + const newVal = ev.target.value; if (!newVal || oldVal === newVal) { return; diff --git a/src/dialogs/more-info/controls/more-info-vacuum.ts b/src/dialogs/more-info/controls/more-info-vacuum.ts index 73a3c12eb0..17eaa0a534 100644 --- a/src/dialogs/more-info/controls/more-info-vacuum.ts +++ b/src/dialogs/more-info/controls/more-info-vacuum.ts @@ -8,15 +8,13 @@ import { mdiStop, mdiTargetVariant, } from "@mdi/js"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-attributes"; import "../../../components/ha-icon"; import "../../../components/ha-icon-button"; -import "../../../components/ha-paper-dropdown-menu"; import { UNAVAILABLE } from "../../../data/entity"; import { VacuumEntity, @@ -31,6 +29,8 @@ import { VACUUM_SUPPORT_STOP, } from "../../../data/vacuum"; import { HomeAssistant } from "../../../types"; +import "@material/mwc-select/mwc-select"; +import "@material/mwc-list/mwc-list-item"; interface VacuumCommand { translationKey: string; @@ -173,25 +173,23 @@ class MoreInfoVacuum extends LitElement { ? html`
- - - ${stateObj.attributes.fan_speed_list!.map( - (mode) => html` - ${mode} - ` - )} - - + ${stateObj.attributes.fan_speed_list!.map( + (mode) => html` + ${mode} + ` + )} +
@@ -221,9 +219,9 @@ class MoreInfoVacuum extends LitElement { }); } - private handleFanSpeedChanged(ev: CustomEvent) { + private handleFanSpeedChanged(ev) { const oldVal = this.stateObj!.attributes.fan_speed; - const newVal = ev.detail.item.itemName; + const newVal = ev.target.value; if (!newVal || oldVal === newVal) { return; diff --git a/src/dialogs/more-info/controls/more-info-water_heater.js b/src/dialogs/more-info/controls/more-info-water_heater.js index 0a8c006006..d46bf1a178 100644 --- a/src/dialogs/more-info/controls/more-info-water_heater.js +++ b/src/dialogs/more-info/controls/more-info-water_heater.js @@ -1,6 +1,4 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { timeOut } from "@polymer/polymer/lib/utils/async"; import { Debouncer } from "@polymer/polymer/lib/utils/debounce"; import { html } from "@polymer/polymer/lib/utils/html-tag"; @@ -8,11 +6,12 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import { featureClassNames } from "../../../common/entity/feature_class_names"; import { supportsFeature } from "../../../common/entity/supports-feature"; -import "../../../components/ha-paper-dropdown-menu"; import "../../../components/ha-switch"; import "../../../components/ha-water_heater-control"; import { EventsMixin } from "../../../mixins/events-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; /* * @appliesMixin EventsMixin @@ -27,14 +26,10 @@ class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) { color: var(--primary-text-color); } - ha-paper-dropdown-menu { + mwc-select { width: 100%; } - paper-item { - cursor: pointer; - } - ha-water_heater-control.range-control-left, ha-water_heater-control.range-control-right { float: left; @@ -75,27 +70,23 @@ class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) { +
@@ -209,13 +200,17 @@ class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) { handleOperationmodeChanged(ev) { const oldVal = this.stateObj.attributes.operation_mode; - const newVal = ev.detail.value; + const newVal = ev.target.value; if (!newVal || oldVal === newVal) return; this.callServiceHelper("set_operation_mode", { operation_mode: newVal, }); } + stopPropagation(ev) { + ev.stopPropagation(); + } + callServiceHelper(service, data) { // We call stateChanged after a successful call to re-sync the inputs // with the state. It will be out of sync if our service call did not diff --git a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts index 07249aeaf8..6037160e47 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts @@ -1,7 +1,5 @@ import "@polymer/paper-input/paper-input"; -import type { PaperListboxElement } from "@polymer/paper-listbox"; -import "@polymer/paper-listbox/paper-listbox"; -import { CSSResultGroup, html, LitElement } from "lit"; +import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { @@ -35,31 +33,25 @@ export class HaRepeatAction extends LitElement implements ActionElement { const action = this.action.repeat; const type = getType(action); - const selected = type ? OPTIONS.indexOf(type) : -1; return html` - - - ${OPTIONS.map( - (opt) => html` - - ${this.hass.localize( - `ui.panel.config.automation.editor.actions.type.repeat.type.${opt}.label` - )} - - ` - )} - - + ${OPTIONS.map( + (opt) => html` + + ${this.hass.localize( + `ui.panel.config.automation.editor.actions.type.repeat.type.${opt}.label` + )} + + ` + )} + ${type === "count" ? html`` )}

- - - ${MODES.map( - (mode) => html` - - ${this.hass.localize( - `ui.panel.config.automation.editor.modes.${mode}` - ) || mode} - - ` - )} - - + ${MODES.map( + (mode) => html` + + ${this.hass.localize( + `ui.panel.config.automation.editor.modes.${mode}` + ) || mode} + + ` + )} + ${this.config.mode && MODES_MAX.includes(this.config.mode) ? html`
- - - ${languages.map( - ([key, label]) => - html`${label}` - )} - - + ${languages.map( + ([key, label]) => + html`${label}` + )} + - - - ${genders.map( - ([key, label]) => - html`${label}` - )} - - + + ${genders.map( + ([key, label]) => + html`${label}` + )} +
@@ -94,15 +88,13 @@ export class CloudTTSPref extends LitElement { `; } - protected firstUpdated(changedProps) { - super.firstUpdated(changedProps); - getCloudTTSInfo(this.hass).then((info) => { - this.ttsInfo = info; - }); - } - - protected updated(changedProps) { - super.updated(changedProps); + protected willUpdate(changedProps) { + super.willUpdate(changedProps); + if (!this.hasUpdated) { + getCloudTTSInfo(this.hass).then((info) => { + this.ttsInfo = info; + }); + } if (changedProps.has("cloudStatus")) { this.savingPreferences = false; } @@ -171,11 +163,11 @@ export class CloudTTSPref extends LitElement { } async _handleLanguageChange(ev) { - if (ev.detail.item.value === this.cloudStatus!.prefs.tts_default_voice[0]) { + if (ev.target.value === this.cloudStatus!.prefs.tts_default_voice[0]) { return; } this.savingPreferences = true; - const language = ev.detail.item.value; + const language = ev.target.value; const curGender = this.cloudStatus!.prefs.tts_default_voice[1]; const genders = this.getSupportedGenders(language, this.ttsInfo); @@ -200,12 +192,12 @@ export class CloudTTSPref extends LitElement { } async _handleGenderChange(ev) { - if (ev.detail.item.value === this.cloudStatus!.prefs.tts_default_voice[1]) { + if (ev.target.value === this.cloudStatus!.prefs.tts_default_voice[1]) { return; } this.savingPreferences = true; const language = this.cloudStatus!.prefs.tts_default_voice[0]; - const gender = ev.detail.item.value; + const gender = ev.target.value; try { await updateCloudPref(this.hass, { diff --git a/src/panels/config/cloud/account/dialog-cloud-tts-try.ts b/src/panels/config/cloud/account/dialog-cloud-tts-try.ts index a65c544e40..bb4bad9e49 100644 --- a/src/panels/config/cloud/account/dialog-cloud-tts-try.ts +++ b/src/panels/config/cloud/account/dialog-cloud-tts-try.ts @@ -1,18 +1,18 @@ import "@material/mwc-button"; +import "@material/mwc-select"; +import "@material/mwc-list/mwc-list-item"; import { mdiPlayCircleOutline, mdiRobot } from "@mdi/js"; -import "@polymer/paper-input/paper-textarea"; -import type { PaperTextareaElement } from "@polymer/paper-input/paper-textarea"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state, query } from "lit/decorators"; import { LocalStorage } from "../../../../common/decorators/local-storage"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { computeStateDomain } from "../../../../common/entity/compute_state_domain"; import { computeStateName } from "../../../../common/entity/compute_state_name"; import { supportsFeature } from "../../../../common/entity/supports-feature"; import { createCloseHeading } from "../../../../components/ha-dialog"; -import "../../../../components/ha-paper-dropdown-menu"; +import type { HaTextArea } from "../../../../components/ha-textarea"; +import "../../../../components/ha-textarea"; import { showAutomationEditor } from "../../../../data/automation"; import { SUPPORT_PLAY_MEDIA } from "../../../../data/media-player"; import { convertTextToSpeech } from "../../../../data/tts"; @@ -29,7 +29,7 @@ export class DialogTryTts extends LitElement { @state() private _params?: TryTtsDialogParams; - @query("#message") private _messageInput?: PaperTextareaElement; + @query("#message") private _messageInput?: HaTextArea; @LocalStorage("cloudTtsTryMessage", false, false) private _message!: string; @@ -61,7 +61,8 @@ export class DialogTryTts extends LitElement { )} >
- - + - - - - ${this.hass.localize( - "ui.panel.config.cloud.account.tts.dialog.target_browser" - )} - - ${Object.values(this.hass.states) - .filter( - (entity) => - computeStateDomain(entity) === "media_player" && - supportsFeature(entity, SUPPORT_PLAY_MEDIA) - ) - .map( - (entity) => html` - - ${computeStateName(entity)} - - ` - )} - - + + ${this.hass.localize( + "ui.panel.config.cloud.account.tts.dialog.target_browser" + )} + + ${Object.values(this.hass.states) + .filter( + (entity) => + computeStateDomain(entity) === "media_player" && + supportsFeature(entity, SUPPORT_PLAY_MEDIA) + ) + .map( + (entity) => html` + + ${computeStateName(entity)} + + ` + )} +
Date: Wed, 9 Feb 2022 17:45:31 -0600 Subject: [PATCH 038/174] Merged too fast for Bram :) Code improv (#11632) --- src/components/ha-form/ha-form.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index 15293808e8..619ce62286 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -52,8 +52,7 @@ export class HaForm extends LitElement implements HaFormElement { if ( !selectorImported && changedProperties.has("schema") && - this.schema && - this.schema.some((item) => "selector" in item) + this.schema?.some((item) => "selector" in item) ) { selectorImported = true; import("../ha-selector/ha-selector"); From 830b4490067a7d029f0c7da07096874e273e00a6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 9 Feb 2022 17:28:12 -0800 Subject: [PATCH 039/174] Add support for opening camera media source (#11633) Co-authored-by: Zack Barett --- src/data/camera.ts | 8 ++++ .../media-browser/ha-panel-media-browser.ts | 43 +++++++++++++------ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/data/camera.ts b/src/data/camera.ts index 22e936c7d4..d556578ae2 100644 --- a/src/data/camera.ts +++ b/src/data/camera.ts @@ -115,3 +115,11 @@ export const updateCameraPrefs = ( entity_id: entityId, ...prefs, }); + +const CAMERA_MEDIA_SOURCE_PREFIX = "media-source://camera/"; + +export const isCameraMediaSource = (mediaContentId: string) => + mediaContentId.startsWith(CAMERA_MEDIA_SOURCE_PREFIX); + +export const getEntityIdFromCameraMediaSource = (mediaContentId: string) => + mediaContentId.substring(CAMERA_MEDIA_SOURCE_PREFIX.length); diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 95931759cb..d6cfa92ffc 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -11,7 +11,7 @@ import { } from "lit"; import { customElement, property } from "lit/decorators"; import { LocalStorage } from "../../common/decorators/local-storage"; -import { HASSDomEvent } from "../../common/dom/fire_event"; +import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event"; import { navigate } from "../../common/navigate"; import "../../components/ha-menu-button"; import "../../components/ha-icon-button"; @@ -29,6 +29,10 @@ import type { HomeAssistant, Route } from "../../types"; import "./ha-bar-media-player"; import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog"; import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; +import { + getEntityIdFromCameraMediaSource, + isCameraMediaSource, +} from "../../data/camera"; @customElement("ha-panel-media-browser") class PanelMediaBrowser extends LitElement { @@ -181,23 +185,34 @@ class PanelMediaBrowser extends LitElement { media_content_id: item.media_content_id, media_content_type: item.media_content_type, }); - } else if (item.media_content_type.startsWith("audio/")) { + return; + } + + if (isCameraMediaSource(item.media_content_id)) { + fireEvent(this, "hass-more-info", { + entityId: getEntityIdFromCameraMediaSource(item.media_content_id), + }); + return; + } + + if (item.media_content_type.startsWith("audio/")) { await this.shadowRoot!.querySelector("ha-bar-media-player")!.playItem( item ); - } else { - const resolvedUrl: any = await resolveMediaSource( - this.hass, - item.media_content_id - ); - - showWebBrowserPlayMediaDialog(this, { - sourceUrl: resolvedUrl.url, - sourceType: resolvedUrl.mime_type, - title: item.title, - can_play: item.can_play, - }); + return; } + + const resolvedUrl: any = await resolveMediaSource( + this.hass, + item.media_content_id + ); + + showWebBrowserPlayMediaDialog(this, { + sourceUrl: resolvedUrl.url, + sourceType: resolvedUrl.mime_type, + title: item.title, + can_play: item.can_play, + }); } static get styles(): CSSResultGroup { From 92a9ed7080b0f68699200cfefda6af56d5d31e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 10 Feb 2022 10:29:42 +0100 Subject: [PATCH 040/174] Create error when trying to backup wile system in freeze (#11634) Co-authored-by: Bram Kragten --- .../src/update-available/update-available-card.ts | 14 +++++++------- src/translations/en.json | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/hassio/src/update-available/update-available-card.ts b/hassio/src/update-available/update-available-card.ts index 5012d61567..fce6b76927 100644 --- a/hassio/src/update-available/update-available-card.ts +++ b/hassio/src/update-available/update-available-card.ts @@ -192,13 +192,7 @@ class UpdateAvailableCard extends LitElement {
` : ""} - + ${this.supervisor.localize("common.update")}
@@ -360,8 +354,14 @@ class UpdateAvailableCard extends LitElement { } private async _update() { + if (this._shouldCreateBackup && this.supervisor.info.state === "freeze") { + this._error = this.supervisor.localize("backup.backup_already_running"); + return; + } + this._error = undefined; this._updating = true; + try { if (this._updateType === "addon") { await updateHassioAddon( diff --git a/src/translations/en.json b/src/translations/en.json index f716a7e9fb..6668462050 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4514,7 +4514,8 @@ "confirm_password": "Confirm backup password", "password_protection": "Password protection", "enter_password": "Please enter a password.", - "passwords_not_matching": "The passwords does not match" + "passwords_not_matching": "The passwords does not match", + "backup_already_running": "A backup or restore is already running, creating a new backup is currently not possible, try again later." }, "dialog": { "network": { From b053881cef183357e7f7e3da5d10a8a97e27efd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 10 Feb 2022 10:40:34 +0100 Subject: [PATCH 041/174] Add missing type to create device automation/script heading (#11635) Co-authored-by: Bram Kragten --- .../ha-device-automation-dialog.ts | 17 ++++++++++++----- .../show-dialog-device-automation.ts | 3 ++- .../config/devices/ha-config-device-page.ts | 7 +++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts b/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts index b42d2f069b..cef11fc0fe 100644 --- a/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts +++ b/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts @@ -60,18 +60,18 @@ export class DialogDeviceAutomation extends LitElement { return; } - const { deviceId, script } = this._params; + const { device, script } = this._params; - fetchDeviceActions(this.hass, deviceId).then((actions) => { + fetchDeviceActions(this.hass, device.id).then((actions) => { this._actions = actions; }); if (script) { return; } - fetchDeviceTriggers(this.hass, deviceId).then((triggers) => { + fetchDeviceTriggers(this.hass, device.id).then((triggers) => { this._triggers = triggers; }); - fetchDeviceConditions(this.hass, deviceId).then((conditions) => { + fetchDeviceConditions(this.hass, device.id).then((conditions) => { this._conditions = conditions; }); } @@ -88,7 +88,14 @@ export class DialogDeviceAutomation extends LitElement { .heading=${this.hass.localize( `ui.panel.config.devices.${ this._params.script ? "script" : "automation" - }.create` + }.create`, + { + type: this.hass.localize( + `ui.panel.config.devices.type.${ + this._params.device.entry_type || "device" + }` + ), + } )} >
diff --git a/src/panels/config/devices/device-detail/show-dialog-device-automation.ts b/src/panels/config/devices/device-detail/show-dialog-device-automation.ts index 93242cd742..4f00987bdf 100644 --- a/src/panels/config/devices/device-detail/show-dialog-device-automation.ts +++ b/src/panels/config/devices/device-detail/show-dialog-device-automation.ts @@ -1,7 +1,8 @@ import { fireEvent } from "../../../../common/dom/fire_event"; +import { DeviceRegistryEntry } from "../../../../data/device_registry"; export interface DeviceAutomationDialogParams { - deviceId: string; + device: DeviceRegistryEntry; script?: boolean; } diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 984acc04fa..5e966278e8 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -812,12 +812,15 @@ export class HaConfigDevicePage extends LitElement { } private _showScriptDialog() { - showDeviceAutomationDialog(this, { deviceId: this.deviceId, script: true }); + showDeviceAutomationDialog(this, { + device: this._device(this.deviceId, this.devices)!, + script: true, + }); } private _showAutomationDialog() { showDeviceAutomationDialog(this, { - deviceId: this.deviceId, + device: this._device(this.deviceId, this.devices)!, script: false, }); } From b0b3222b3329ab6637a5e6928b7ece58d5ccd211 Mon Sep 17 00:00:00 2001 From: Eric Severance Date: Thu, 10 Feb 2022 02:43:45 -0800 Subject: [PATCH 042/174] Generate random webhook_id and add copy button (#11568) Co-authored-by: Bram Kragten Co-authored-by: Zack Barett Co-authored-by: Paulus Schoutsen --- .../trigger/ha-automation-trigger-row.ts | 2 +- .../types/ha-automation-trigger-webhook.ts | 114 ++++++++++++++++-- src/translations/en.json | 4 +- 3 files changed, 108 insertions(+), 12 deletions(-) diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index f62a33fd7a..bda6105400 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -61,7 +61,7 @@ export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => { if (!name) { return; } - const newVal = ev.detail.value; + const newVal = (ev.target as any)?.value; if ((element.trigger[name] || "") === newVal) { return; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts index 4d90ec0219..fecd09535d 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-webhook.ts @@ -1,39 +1,133 @@ -import "@polymer/paper-input/paper-input"; -import { html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; -import { WebhookTrigger } from "../../../../../data/automation"; +import "../../../../../components/ha-icon-button"; +import "../../../../../components/ha-textfield"; +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { mdiContentCopy } from "@mdi/js"; +import { css, html, LitElement, PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import { slugify } from "../../../../../common/string/slugify"; +import { copyToClipboard } from "../../../../../common/util/copy-clipboard"; +import type { HaTextField } from "../../../../../components/ha-textfield"; +import { showToast } from "../../../../../util/toast"; +import { + WebhookTrigger, + AutomationConfig, +} from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; import { handleChangeEvent } from "../ha-automation-trigger-row"; +const DEFAULT_WEBHOOK_ID = ""; + @customElement("ha-automation-trigger-webhook") export class HaWebhookTrigger extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property() public trigger!: WebhookTrigger; + @state() private _config?: AutomationConfig; + + private _unsub?: UnsubscribeFunc; + public static get defaultConfig() { return { - webhook_id: "", + webhook_id: DEFAULT_WEBHOOK_ID, }; } + connectedCallback() { + super.connectedCallback(); + const details = { + callback: (config) => { + this._config = config; + }, + }; + fireEvent(this, "subscribe-automation-config", details); + this._unsub = (details as any).unsub; + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this._unsub) { + this._unsub(); + } + } + + private _generateWebhookId(): string { + // The webhook_id should be treated like a password. Generate a default + // value that would be hard for someone to guess. This generates a + // 144-bit random value. The output is a 24 character url-safe string. + const randomBytes = crypto.getRandomValues(new Uint8Array(18)); + const base64Str = btoa(String.fromCharCode(...randomBytes)); + const urlSafeId = base64Str.replace(/\+/g, "-").replace(/\//g, "_"); + + // Include the automation name to give the user context about what the + // webhook_id is used for. + const urlSafeAlias = slugify(this._config?.alias || "", "-"); + + return `${urlSafeAlias}-${urlSafeId}`; + } + + public willUpdate(changedProperties: PropertyValues) { + super.willUpdate(changedProperties); + if (changedProperties.has("trigger")) { + if (this.trigger.webhook_id === DEFAULT_WEBHOOK_ID) { + this.trigger.webhook_id = this._generateWebhookId(); + } + } + } + protected render() { const { webhook_id: webhookId } = this.trigger; + return html` - + .helper=${this.hass.localize( + "ui.panel.config.automation.editor.triggers.type.webhook.webhook_id_helper" + )} + .iconTrailing=${true} + .value=${webhookId || ""} + @input=${this._valueChanged} + > + + `; } private _valueChanged(ev: CustomEvent): void { handleChangeEvent(this, ev); } + + private async _copyUrl(ev): Promise { + const inputElement = ev.target.parentElement as HaTextField; + const url = this.hass.hassUrl(`/api/webhook/${inputElement.value}`); + + await copyToClipboard(url); + showToast(this, { + message: this.hass.localize("ui.common.copied_clipboard"), + }); + } + + static styles = css` + ha-textfield { + display: block; + } + + ha-textfield > ha-icon-button { + --mdc-icon-button-size: 24px; + --mdc-icon-size: 18px; + } + `; } declare global { diff --git a/src/translations/en.json b/src/translations/en.json index 6668462050..2583b4d528 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1678,8 +1678,10 @@ "seconds": "Seconds" }, "webhook": { + "copy_url": "Copy URL to Clipboard", "label": "Webhook", - "webhook_id": "Webhook ID" + "webhook_id": "Webhook ID", + "webhook_id_helper": "Treat this ID like a password: keep it secret, and make it hard to guess." }, "zone": { "label": "Zone", From 467a5169c05afb7336aff2fabd03aadea3aee21f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 10 Feb 2022 15:23:21 +0100 Subject: [PATCH 043/174] Migrate search bar to mwc (#11637) --- src/common/search/search-input.ts | 71 ++++++++----------- src/components/data-table/ha-data-table.ts | 5 +- src/dialogs/quick-bar/ha-quick-bar.ts | 1 - src/layouts/hass-tabs-subpage-data-table.ts | 64 ++++++++++------- .../devices/ha-config-devices-dashboard.ts | 2 +- .../config/entities/ha-config-entities.ts | 10 +-- .../config/helpers/ha-config-helpers.ts | 1 - .../integrations/ha-config-integrations.ts | 29 ++++---- src/panels/config/logs/ha-config-logs.ts | 19 ++--- .../editor/card-editor/hui-card-picker.ts | 4 +- .../card-editor/hui-dialog-create-card.ts | 5 ++ .../unused-entities/hui-unused-entities.ts | 1 - 12 files changed, 111 insertions(+), 101 deletions(-) diff --git a/src/common/search/search-input.ts b/src/common/search/search-input.ts index 08084af98a..f9806d60e0 100644 --- a/src/common/search/search-input.ts +++ b/src/common/search/search-input.ts @@ -1,17 +1,10 @@ import { mdiClose, mdiMagnify } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; -import type { PaperInputElement } from "@polymer/paper-input/paper-input"; -import { - css, - CSSResultGroup, - html, - LitElement, - PropertyValues, - TemplateResult, -} from "lit"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query } from "lit/decorators"; import "../../components/ha-icon-button"; import "../../components/ha-svg-icon"; +import "../../components/ha-textfield"; +import type { HaTextField } from "../../components/ha-textfield"; import { HomeAssistant } from "../../types"; import { fireEvent } from "../dom/fire_event"; @@ -21,12 +14,6 @@ class SearchInput extends LitElement { @property() public filter?: string; - @property({ type: Boolean, attribute: "no-label-float" }) - public noLabelFloat? = false; - - @property({ type: Boolean, attribute: "no-underline" }) - public noUnderline = false; - @property({ type: Boolean }) public autofocus = false; @@ -34,49 +21,42 @@ class SearchInput extends LitElement { public label?: string; public focus() { - this.shadowRoot!.querySelector("paper-input")!.focus(); + this._input?.focus(); } - @query("paper-input", true) private _input!: PaperInputElement; + @query("ha-textfield", true) private _input!: HaTextField; protected render(): TemplateResult { return html` - - - + + ${this.filter && html` `} - + `; } - protected updated(changedProps: PropertyValues) { - if ( - changedProps.has("noUnderline") && - (this.noUnderline || changedProps.get("noUnderline") !== undefined) - ) { - ( - this._input.inputElement!.parentElement!.shadowRoot!.querySelector( - "div.unfocused-line" - ) as HTMLElement - ).style.display = this.noUnderline ? "none" : "block"; - } - } - private async _filterChanged(value: string) { fireEvent(this, "value-changed", { value: String(value) }); } @@ -91,15 +71,24 @@ class SearchInput extends LitElement { static get styles(): CSSResultGroup { return css` + :host { + display: inline-flex; + } ha-svg-icon, ha-icon-button { color: var(--primary-text-color); } + ha-svg-icon { + outline: none; + } ha-icon-button { --mdc-icon-button-size: 24px; } - ha-svg-icon.prefix { - margin: 8px; + .clear-button { + --mdc-icon-size: 20px; + } + ha-textfield { + display: inherit; } `; } diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 372bfa4460..babc785d50 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -928,11 +928,10 @@ export class HaDataTable extends LitElement { } .table-header { border-bottom: 1px solid var(--divider-color); - padding: 0 16px; } search-input { - position: relative; - top: 2px; + display: block; + flex: 1; } slot[name="header"] { display: block; diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index c166918cc9..56bac512dd 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -24,7 +24,6 @@ import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; import { domainIcon } from "../../common/entity/domain_icon"; import { navigate } from "../../common/navigate"; -import "../../common/search/search-input"; import { caseInsensitiveStringCompare } from "../../common/string/compare"; import { fuzzyFilterSort, diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index c291eab335..c05530b856 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -159,28 +159,28 @@ export class HaTabsSubpageDataTable extends LitElement { const headerToolbar = html` - ${filterInfo - ? html`
- ${this.narrow - ? html`
- - - ${filterInfo} - -
` - : filterInfo} - - ${this.hass.localize("ui.components.data-table.clear")} - -
` - : ""}`; + +
+ ${filterInfo + ? html`
+ ${this.narrow + ? html`
+ + + ${filterInfo} + +
` + : filterInfo} + + ${this.hass.localize("ui.components.data-table.clear")} + +
` + : ""} +
`; return html` Date: Thu, 10 Feb 2022 15:24:00 +0100 Subject: [PATCH 044/174] fix data-table row handlers (#11638) --- src/components/data-table/ha-data-table.ts | 8 ++-- src/dialogs/quick-bar/ha-quick-bar.ts | 52 +++++++++++++--------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index babc785d50..d199040c5c 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -526,7 +526,7 @@ export class HaDataTable extends LitElement { } } - private _handleRowCheckboxClick(ev: Event) { + private _handleRowCheckboxClick = (ev: Event) => { const checkbox = ev.currentTarget as HaCheckbox; const rowId = (checkbox as any).rowId; @@ -539,16 +539,16 @@ export class HaDataTable extends LitElement { this._checkedRows = this._checkedRows.filter((row) => row !== rowId); } this._checkedRowsChanged(); - } + }; - private _handleRowClick(ev: Event) { + private _handleRowClick = (ev: Event) => { const target = ev.target as HTMLElement; if (["HA-CHECKBOX", "MWC-BUTTON"].includes(target.tagName)) { return; } const rowId = (ev.currentTarget as any).rowId; fireEvent(this, "row-click", { id: rowId }, { bubbles: false }); - } + }; private _checkedRowsChanged() { // force scroller to update, change it's items diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 56bac512dd..337855d71b 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -1,7 +1,5 @@ import "@lit-labs/virtualizer"; import "@material/mwc-list/mwc-list"; -import type { List } from "@material/mwc-list/mwc-list"; -import { SingleSelectedEvent } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import type { ListItem } from "@material/mwc-list/mwc-list-item"; import { @@ -123,18 +121,28 @@ export class QuickBar extends LitElement { fireEvent(this, "dialog-closed", { dialog: this.localName }); } + private _getItems = memoizeOne( + (commandMode: boolean, commandItems, entityItems, filter: string) => { + const items = commandMode ? commandItems : entityItems; + + if (items && filter && filter !== " ") { + return this._filterItems(items, filter); + } + return items; + } + ); + protected render() { if (!this._opened) { return html``; } - let items: QuickBarItem[] | undefined = this._commandMode - ? this._commandItems - : this._entityItems; - - if (items && this._filter && this._filter !== " ") { - items = this._filterItems(items, this._filter); - } + const items: QuickBarItem[] | undefined = this._getItems( + this._commandMode, + this._commandItems, + this._entityItems, + this._filter + ); return html` ` : html` - + { From 0eeed8519327cb8c1cb7b24d152acfae68ad01ac Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 10 Feb 2022 15:24:47 +0100 Subject: [PATCH 045/174] Bunch of fixes and cleanup (#11636) --- hassio/src/system/hassio-core-info.ts | 10 -- hassio/src/system/hassio-host-info.ts | 10 -- src/components/entity/ha-entities-picker.ts | 8 +- src/components/entity/ha-statistics-picker.ts | 2 + src/components/ha-check-list-item.ts | 4 +- src/components/ha-selector/ha-selector.ts | 2 +- src/components/ha-service-control.ts | 5 +- src/components/ha-service-picker.ts | 2 +- .../media-player/ha-media-player-browse.ts | 1 - .../action/ha-automation-action-row.ts | 9 +- .../types/ha-automation-action-choose.ts | 1 - .../types/ha-automation-action-device_id.ts | 9 +- .../ha-automation-condition-editor.ts | 15 ++- .../trigger/ha-automation-trigger-row.ts | 3 + .../integration-panels/zha/zha-device-card.ts | 1 - .../zha/zha-device-pairing-status-card.ts | 1 - .../resources/ha-config-lovelace-resources.ts | 1 - src/panels/config/scene/ha-scene-editor.ts | 6 ++ src/panels/config/users/dialog-user-detail.ts | 1 + .../service/developer-tools-service.ts | 35 +++---- .../lovelace/components/hui-action-editor.ts | 91 +++++++++---------- .../lovelace/components/hui-entity-editor.ts | 10 +- .../components/hui-theme-select-editor.ts | 52 +++++------ .../config-elements/config-elements-style.ts | 7 ++ .../hui-entities-card-editor.ts | 1 - .../hui-header-footer-editor.ts | 1 - .../editor/hui-entities-card-row-editor.ts | 11 ++- 27 files changed, 163 insertions(+), 136 deletions(-) diff --git a/hassio/src/system/hassio-core-info.ts b/hassio/src/system/hassio-core-info.ts index dc3589246e..df078742d7 100644 --- a/hassio/src/system/hassio-core-info.ts +++ b/hassio/src/system/hassio-core-info.ts @@ -205,16 +205,6 @@ class HassioCoreInfo extends LitElement { color: var(--secondary-text-color); --mdc-menu-min-width: 200px; } - @media (min-width: 563px) { - paper-listbox { - max-height: 150px; - overflow: auto; - } - } - paper-item { - cursor: pointer; - min-height: 35px; - } mwc-list-item ha-svg-icon { color: var(--secondary-text-color); } diff --git a/hassio/src/system/hassio-host-info.ts b/hassio/src/system/hassio-host-info.ts index ff51c8903b..bd743fe8b2 100644 --- a/hassio/src/system/hassio-host-info.ts +++ b/hassio/src/system/hassio-host-info.ts @@ -440,16 +440,6 @@ class HassioHostInfo extends LitElement { color: var(--secondary-text-color); --mdc-menu-min-width: 200px; } - @media (min-width: 563px) { - paper-listbox { - max-height: 150px; - overflow: auto; - } - } - paper-item { - cursor: pointer; - min-height: 35px; - } mwc-list-item ha-svg-icon { color: var(--secondary-text-color); } diff --git a/src/components/entity/ha-entities-picker.ts b/src/components/entity/ha-entities-picker.ts index 19c20886fa..deac6335ca 100644 --- a/src/components/entity/ha-entities-picker.ts +++ b/src/components/entity/ha-entities-picker.ts @@ -1,5 +1,5 @@ import type { HassEntity } from "home-assistant-js-websocket"; -import { html, LitElement, TemplateResult } from "lit"; +import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { isValidEntityId } from "../../common/entity/valid_entity_id"; @@ -145,6 +145,12 @@ class HaEntitiesPickerLight extends LitElement { this._updateEntities([...currentEntities, toAdd]); } + + static override styles = css` + ha-entity-picker { + margin-top: 8px; + } + `; } declare global { diff --git a/src/components/entity/ha-statistics-picker.ts b/src/components/entity/ha-statistics-picker.ts index 08242a5263..aa25e45803 100644 --- a/src/components/entity/ha-statistics-picker.ts +++ b/src/components/entity/ha-statistics-picker.ts @@ -111,7 +111,9 @@ class HaStatisticsPicker extends LitElement { display: block; } ha-statistic-picker { + display: block; width: 100%; + margin-top: 8px; } `; } diff --git a/src/components/ha-check-list-item.ts b/src/components/ha-check-list-item.ts index 5b237208c9..63f5827f67 100644 --- a/src/components/ha-check-list-item.ts +++ b/src/components/ha-check-list-item.ts @@ -1,12 +1,14 @@ import { css } from "lit"; import { CheckListItemBase } from "@material/mwc-list/mwc-check-list-item-base"; -import { styles } from "@material/mwc-list/mwc-control-list-item.css"; +import { styles as controlStyles } from "@material/mwc-list/mwc-control-list-item.css"; +import { styles } from "@material/mwc-list/mwc-list-item.css"; import { customElement } from "lit/decorators"; @customElement("ha-check-list-item") export class HaCheckListItem extends CheckListItemBase { static override styles = [ styles, + controlStyles, css` :host { --mdc-theme-secondary: var(--primary-color); diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index e3eda4a3df..c73c5b0805 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -31,7 +31,7 @@ export class HaSelector extends LitElement { @property({ type: Boolean }) public disabled = false; public focus() { - this.shadowRoot!.getElementById("selector")?.focus(); + this.shadowRoot?.getElementById("selector")?.focus(); } private get _type() { diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index 9cac6ae6b9..badc1bb488 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -67,7 +67,7 @@ export class HaServiceControl extends LitElement { @query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor; - protected updated(changedProperties: PropertyValues) { + protected willUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("value")) { return; } @@ -482,9 +482,6 @@ export class HaServiceControl extends LitElement { display: block; margin: var(--service-control-padding, 0 16px); } - ha-service-picker { - padding-top: 16px; - } ha-yaml-editor { padding: 16px 0; } diff --git a/src/components/ha-service-picker.ts b/src/components/ha-service-picker.ts index dab0015447..a25c61a25a 100644 --- a/src/components/ha-service-picker.ts +++ b/src/components/ha-service-picker.ts @@ -10,7 +10,7 @@ import "./ha-combo-box"; const rowRenderer: ComboBoxLitRenderer<{ service: string; name: string }> = ( item -) => html` +) => html` ${item.name} ${item.name === item.service ? "" : item.service} - - ${this._yamlMode - ? html` - ` - : html`
+ + ${this._yamlMode + ? html`
+ + +
` + : html`
`} + class="card-content" + > + `} +
diff --git a/src/panels/lovelace/components/hui-action-editor.ts b/src/panels/lovelace/components/hui-action-editor.ts index 7dd7787daf..114233e2ec 100644 --- a/src/panels/lovelace/components/hui-action-editor.ts +++ b/src/panels/lovelace/components/hui-action-editor.ts @@ -1,13 +1,8 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-input/paper-textarea"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; -import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../common/dom/fire_event"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; import "../../../components/ha-help-tooltip"; import "../../../components/ha-service-control"; import { @@ -62,32 +57,30 @@ export class HuiActionEditor extends LitElement { return html` ${this.config?.action === "navigate" ? html` - + @input=${this._valueChanged} + > ` : ""} ${this.config?.action === "url" ? html` - + @input=${this._valueChanged} + > ` : ""} ${this.config?.action === "call-service" @@ -132,23 +125,17 @@ export class HuiActionEditor extends LitElement { `; } - private _actionPicked(ev: CustomEvent): void { + private _actionPicked(ev): void { ev.stopPropagation(); if (!this.hass) { return; } - const item = ev.detail.item; - const value = item.value; + const value = ev.target.value; if (this.config?.action === value) { return; } if (value === "default") { fireEvent(this, "value-changed", { value: undefined }); - if (this.config?.action) { - ( - this.shadowRoot!.querySelector("paper-listbox") as PaperListboxElement - ).select(this.config.action); - } return; } @@ -173,13 +160,13 @@ export class HuiActionEditor extends LitElement { }); } - private _valueChanged(ev: CustomEvent): void { + private _valueChanged(ev): void { ev.stopPropagation(); if (!this.hass) { return; } const target = ev.target! as EditorTarget; - const value = ev.detail.value; + const value = ev.target.value; if (this[`_${target.configValue}`] === value) { return; } @@ -205,7 +192,19 @@ export class HuiActionEditor extends LitElement { static get styles(): CSSResultGroup { return css` .dropdown { - display: flex; + position: relative; + } + ha-help-tooltip { + position: absolute; + right: 40px; + top: 16px; + } + mwc-select, + ha-textfield { + width: 100%; + } + ha-textfield { + margin-top: 8px; } ha-service-control { --service-control-padding: 0; diff --git a/src/panels/lovelace/components/hui-entity-editor.ts b/src/panels/lovelace/components/hui-entity-editor.ts index ff81d8ec67..f08bc02f05 100644 --- a/src/panels/lovelace/components/hui-entity-editor.ts +++ b/src/panels/lovelace/components/hui-entity-editor.ts @@ -78,6 +78,7 @@ export class HuiEntityEditor extends LitElement { )}
@@ -170,7 +171,7 @@ export class HuiEntityEditor extends LitElement { const index = (ev.target as any).index; const newConfigEntities = this.entities!.concat(); - if (value === "") { + if (value === "" || value === undefined) { newConfigEntities.splice(index, 1); } else { newConfigEntities[index] = { @@ -186,6 +187,13 @@ export class HuiEntityEditor extends LitElement { return [ sortableStyles, css` + ha-entity-picker { + margin-top: 8px; + } + .add-entity { + display: block; + margin-left: 31px; + } .entity { display: flex; align-items: center; diff --git a/src/panels/lovelace/components/hui-theme-select-editor.ts b/src/panels/lovelace/components/hui-theme-select-editor.ts index f20b6dd033..60a3746e8c 100644 --- a/src/panels/lovelace/components/hui-theme-select-editor.ts +++ b/src/panels/lovelace/components/hui-theme-select-editor.ts @@ -1,11 +1,11 @@ import "@material/mwc-button"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import { HomeAssistant } from "../../../types"; +import "@material/mwc-select/mwc-select"; +import "@material/mwc-list/mwc-list-item"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; @customElement("hui-theme-select-editor") export class HuiThemeSelectEditor extends LitElement { @@ -17,53 +17,47 @@ export class HuiThemeSelectEditor extends LitElement { protected render(): TemplateResult { return html` - - ${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.no_theme" + )} - ${this.hass!.localize( - "ui.panel.lovelace.editor.card.generic.no_theme" - )} - ${Object.keys(this.hass!.themes.themes) - .sort() - .map( - (theme) => - html` ${theme} ` - )} - - + ${Object.keys(this.hass!.themes.themes) + .sort() + .map( + (theme) => + html` ${theme} ` + )} + `; } static get styles(): CSSResultGroup { return css` - paper-dropdown-menu { + mwc-select { width: 100%; } - paper-item { - cursor: pointer; - } `; } private _changed(ev): void { - if (!this.hass || ev.target.selected === "") { + if (!this.hass || ev.target.value === "") { return; } - this.value = ev.target.selected === "remove" ? "" : ev.target.selected; + this.value = ev.target.value === "remove" ? "" : ev.target.selected; fireEvent(this, "value-changed", { value: this.value }); } } diff --git a/src/panels/lovelace/editor/config-elements/config-elements-style.ts b/src/panels/lovelace/editor/config-elements/config-elements-style.ts index 0e61bc76fd..dead5f483c 100644 --- a/src/panels/lovelace/editor/config-elements/config-elements-style.ts +++ b/src/panels/lovelace/editor/config-elements/config-elements-style.ts @@ -19,4 +19,11 @@ export const configElementStyle = css` .suffix { margin: 0 8px; } + hui-theme-select-editor, + hui-action-editor, + mwc-select, + ha-textfield, + ha-icon-picker { + margin-top: 8px; + } `; diff --git a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts index 46ff0b41a6..0869bb9264 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts @@ -1,7 +1,6 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { diff --git a/src/panels/lovelace/editor/header-footer-editor/hui-header-footer-editor.ts b/src/panels/lovelace/editor/header-footer-editor/hui-header-footer-editor.ts index 76b7032a8f..efe181ea57 100644 --- a/src/panels/lovelace/editor/header-footer-editor/hui-header-footer-editor.ts +++ b/src/panels/lovelace/editor/header-footer-editor/hui-header-footer-editor.ts @@ -1,6 +1,5 @@ import { mdiClose, mdiPencil, mdiPlus } from "@mdi/js"; import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; diff --git a/src/panels/lovelace/editor/hui-entities-card-row-editor.ts b/src/panels/lovelace/editor/hui-entities-card-row-editor.ts index e9e8f5d92c..23ff1df094 100644 --- a/src/panels/lovelace/editor/hui-entities-card-row-editor.ts +++ b/src/panels/lovelace/editor/hui-entities-card-row-editor.ts @@ -126,6 +126,7 @@ export class HuiEntitiesCardRowEditor extends LitElement { )}
@@ -226,7 +227,7 @@ export class HuiEntitiesCardRowEditor extends LitElement { const index = (ev.target as any).index; const newConfigEntities = this.entities!.concat(); - if (value === "") { + if (value === "" || value === undefined) { newConfigEntities.splice(index, 1); } else { newConfigEntities[index] = { @@ -253,6 +254,14 @@ export class HuiEntitiesCardRowEditor extends LitElement { return [ sortableStyles, css` + ha-entity-picker { + margin-top: 8px; + } + .add-entity { + display: block; + margin-left: 31px; + margin-right: 71px; + } .entity { display: flex; align-items: center; From cefa2ee183d38120afc5603c7bd37aca56900c0c Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 10 Feb 2022 09:26:28 -0600 Subject: [PATCH 046/174] State Trigger -> HA Form (#11631) Co-authored-by: Bram Kragten --- src/components/ha-base-time-input.ts | 15 ++-- src/components/ha-form/ha-form.ts | 2 +- .../ha-selector/ha-selector-duration.ts | 37 ++++++++ .../ha-selector/ha-selector-text.ts | 6 +- src/components/ha-selector/ha-selector.ts | 4 + src/data/selector.ts | 5 ++ .../types/ha-automation-trigger-state.ts | 87 ++++++++----------- 7 files changed, 97 insertions(+), 59 deletions(-) create mode 100644 src/components/ha-selector/ha-selector-duration.ts diff --git a/src/components/ha-base-time-input.ts b/src/components/ha-base-time-input.ts index e8ede0a78b..27b72e42f2 100644 --- a/src/components/ha-base-time-input.ts +++ b/src/components/ha-base-time-input.ts @@ -26,6 +26,11 @@ export class HaBaseTimeInput extends LitElement { */ @property({ type: Boolean }) autoValidate = false; + /** + * determines if inputs are required + */ + @property({ type: Boolean }) public required?: boolean; + /** * 12 or 24 hr format */ @@ -115,7 +120,7 @@ export class HaBaseTimeInput extends LitElement { @input=${this._valueChanged} @focus=${this._onFocus} no-spinner - required + .required=${this.required} .autoValidate=${this.autoValidate} maxlength="2" .max=${this._hourMax} @@ -135,7 +140,7 @@ export class HaBaseTimeInput extends LitElement { @focus=${this._onFocus} name="minutes" no-spinner - required + .required=${this.required} .autoValidate=${this.autoValidate} maxlength="2" max="59" @@ -156,7 +161,7 @@ export class HaBaseTimeInput extends LitElement { @focus=${this._onFocus} name="seconds" no-spinner - required + .required=${this.required} .autoValidate=${this.autoValidate} maxlength="2" max="59" @@ -177,7 +182,7 @@ export class HaBaseTimeInput extends LitElement { @focus=${this._onFocus} name="milliseconds" no-spinner - required + .required=${this.required} .autoValidate=${this.autoValidate} maxlength="3" max="999" @@ -189,7 +194,7 @@ export class HaBaseTimeInput extends LitElement { ${this.format === 24 ? "" : html`` : dynamicElement(`ha-form-${item.type}`, { schema: item, diff --git a/src/components/ha-selector/ha-selector-duration.ts b/src/components/ha-selector/ha-selector-duration.ts new file mode 100644 index 0000000000..1471750d90 --- /dev/null +++ b/src/components/ha-selector/ha-selector-duration.ts @@ -0,0 +1,37 @@ +import "../ha-duration-input"; +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { DurationSelector } from "../../data/selector"; +import { HomeAssistant } from "../../types"; + +@customElement("ha-selector-duration") +export class HaTimeDuration extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public selector!: DurationSelector; + + @property() public value?: string; + + @property() public label?: string; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean }) public required = true; + + protected render() { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-duration": HaTimeDuration; + } +} diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts index 565ef8889e..fa1d48ff63 100644 --- a/src/components/ha-selector/ha-selector-text.ts +++ b/src/components/ha-selector/ha-selector-text.ts @@ -22,6 +22,8 @@ export class HaTextSelector extends LitElement { @property({ type: Boolean }) public disabled = false; + @property({ type: Boolean }) public required = true; + @state() private _unmaskedPassword = false; protected render() { @@ -35,7 +37,7 @@ export class HaTextSelector extends LitElement { autocapitalize="none" autocomplete="off" spellcheck="false" - required + .required=${this.required} autogrow >`; } @@ -50,7 +52,7 @@ export class HaTextSelector extends LitElement { ? // reserve some space for the icon. html`
` : this.selector.text?.suffix} - required + .required=${this.required} > ${this.selector.text?.type === "password" ? html` - - - - + .computeLabel=${this._computeLabelCallback} + > `; } private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); + ev.stopPropagation(); + const newTrigger = ev.detail.value; + + Object.keys(newTrigger).forEach((key) => + newTrigger[key] === undefined || newTrigger[key] === "" + ? delete newTrigger[key] + : {} + ); + + fireEvent(this, "value-changed", { value: newTrigger }); + } + + private _computeLabelCallback(schema: HaFormSchema): string { + return this.hass.localize( + schema.name === "entity_id" + ? "ui.components.entity.entity-picker.entity" + : `ui.panel.config.automation.editor.triggers.type.state.${schema.name}` + ); } } From d7a5921e7b2e0b62cc285c8a509b76df438bfe38 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 10 Feb 2022 09:54:01 -0800 Subject: [PATCH 047/174] Allow uploading media (#11615) * Allow uploading media * Update path * Use current item we already have * Update src/panels/media-browser/ha-panel-media-browser.ts Co-authored-by: Bram Kragten * Use alert dialog and use button for add media Co-authored-by: Bram Kragten --- .../media-player/ha-media-player-browse.ts | 13 +++ src/data/media_source.ts | 26 ++++++ .../media-browser/ha-panel-media-browser.ts | 80 ++++++++++++++++--- src/translations/en.json | 4 + 4 files changed, 110 insertions(+), 13 deletions(-) diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 6ed5c04a6f..939cb78b82 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -114,6 +114,19 @@ export class HaMediaPlayerBrowse extends LitElement { } } + public async refresh() { + const currentId = this.navigateIds[this.navigateIds.length - 1]; + try { + this._currentItem = await this._fetchData( + this.entityId, + currentId.media_content_id, + currentId.media_content_type + ); + } catch (err) { + this._setError(err); + } + } + public play(): void { if (this._currentItem?.can_play) { this._runAction(this._currentItem); diff --git a/src/data/media_source.ts b/src/data/media_source.ts index 759be2a7d3..dfcdbb37ab 100644 --- a/src/data/media_source.ts +++ b/src/data/media_source.ts @@ -23,3 +23,29 @@ export const browseLocalMediaPlayer = ( type: "media_source/browse_media", media_content_id: mediaContentId, }); + +export const isLocalMediaSourceContentId = (mediaId: string) => + mediaId.startsWith("media-source://media_source"); + +export const uploadLocalMedia = async ( + hass: HomeAssistant, + media_content_id: string, + file: File +) => { + const fd = new FormData(); + fd.append("media_content_id", media_content_id); + fd.append("file", file); + const resp = await hass.fetchWithAuth( + "/api/media_source/local_source/upload", + { + method: "POST", + body: fd, + } + ); + if (resp.status === 413) { + throw new Error("Uploaded image is too large"); + } else if (resp.status !== 200) { + throw new Error("Unknown error"); + } + return resp.json(); +}; diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index d6cfa92ffc..ff996a122f 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -1,6 +1,7 @@ -import { mdiArrowLeft } from "@mdi/js"; +import { mdiArrowLeft, mdiUpload } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; +import "@material/mwc-button"; import { css, CSSResultGroup, @@ -9,20 +10,28 @@ import { PropertyValues, TemplateResult, } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { LocalStorage } from "../../common/decorators/local-storage"; import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event"; import { navigate } from "../../common/navigate"; import "../../components/ha-menu-button"; import "../../components/ha-icon-button"; +import "../../components/ha-svg-icon"; import "../../components/media-player/ha-media-player-browse"; -import type { MediaPlayerItemId } from "../../components/media-player/ha-media-player-browse"; +import type { + HaMediaPlayerBrowse, + MediaPlayerItemId, +} from "../../components/media-player/ha-media-player-browse"; import { BROWSER_PLAYER, MediaPickedEvent, MediaPlayerItem, } from "../../data/media-player"; -import { resolveMediaSource } from "../../data/media_source"; +import { + isLocalMediaSourceContentId, + resolveMediaSource, + uploadLocalMedia, +} from "../../data/media_source"; import "../../layouts/ha-app-layout"; import { haStyle } from "../../resources/styles"; import type { HomeAssistant, Route } from "../../types"; @@ -43,7 +52,7 @@ class PanelMediaBrowser extends LitElement { @property() public route!: Route; - @property() _currentItem?: MediaPlayerItem; + @state() _currentItem?: MediaPlayerItem; private _navigateIds: MediaPlayerItemId[] = [ { @@ -55,6 +64,8 @@ class PanelMediaBrowser extends LitElement { @LocalStorage("mediaBrowseEntityId", true, false) private _entityId = BROWSER_PLAYER; + @query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse; + protected render(): TemplateResult { return html` @@ -73,15 +84,28 @@ class PanelMediaBrowser extends LitElement { .narrow=${this.narrow} > `} -
-
- ${!this._currentItem - ? this.hass.localize( - "ui.components.media-browser.media-player-browser" - ) - : this._currentItem.title} -
+
+ ${!this._currentItem + ? this.hass.localize( + "ui.components.media-browser.media-player-browser" + ) + : this._currentItem.title}
+ ${this._currentItem && + isLocalMediaSourceContentId( + this._currentItem.media_content_id || "" + ) + ? html` + + + + ` + : ""} { + try { + await uploadLocalMedia( + this.hass, + this._currentItem!.media_content_id!, + input.files![0] + ); + } catch (err: any) { + showAlertDialog(this, { + text: this.hass.localize( + "ui.components.media-browser.file_management.upload_failed", + { + reason: err.message || err, + } + ), + }); + return; + } + await this._browser.refresh(); + }); + input.click(); + } + static get styles(): CSSResultGroup { return [ haStyle, @@ -237,6 +287,10 @@ class PanelMediaBrowser extends LitElement { left: 0; right: 0; } + + ha-svg-icon[slot="icon"] { + vertical-align: middle; + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index 2583b4d528..547d8ff918 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -524,6 +524,10 @@ "no_local_media_found": "No local media found", "no_media_folder": "It looks like you have not yet created a media directory.", "setup_local_help": "Check the {documentation} on how to setup local media.", + "file_management": { + "upload_failed": "Upload failed: {reason}", + "add_media": "Add Media" + }, "class": { "album": "Album", "app": "App", From fca7d2c5b0975bc64e87ab84e7cd4851ad86ab63 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 10 Feb 2022 13:56:25 -0600 Subject: [PATCH 048/174] Add Attribute Picker as a selector - add to state trigger (#11641) Co-authored-by: Paulus Schoutsen --- gallery/src/pages/components/ha-form.ts | 7 +++- gallery/src/pages/components/ha-selector.ts | 5 +++ src/components/ha-form/ha-form.ts | 1 + .../ha-selector/ha-selector-attribute.ts | 38 +++++++++++++++++++ src/components/ha-selector/ha-selector.ts | 3 +- src/data/selector.ts | 9 +++++ .../types/ha-automation-trigger-state.ts | 14 ++++++- 7 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 src/components/ha-selector/ha-selector-attribute.ts diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index a1be3cdc0c..a49cf9f131 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -40,7 +40,12 @@ const SCHEMAS: { schema: [ { name: "addon", selector: { addon: {} } }, { name: "entity", selector: { entity: {} } }, - { name: "device", selector: { device: {} } }, + { + name: "Attribute", + selector: { attribute: { entity_id: "" } }, + }, + { name: "Device", selector: { device: {} } }, + { name: "Duration", selector: { duration: {} } }, { name: "area", selector: { area: {} } }, { name: "target", selector: { target: {} } }, { name: "number", selector: { number: { min: 0, max: 10 } } }, diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index ce015a3756..b4fc18f118 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -21,7 +21,12 @@ const SCHEMAS: { name: "One of each", input: { entity: { name: "Entity", selector: { entity: {} } }, + attribute: { + name: "Attribute", + selector: { attribute: { entity_id: "" } }, + }, device: { name: "Device", selector: { device: {} } }, + duration: { name: "Duration", selector: { duration: {} } }, addon: { name: "Addon", selector: { addon: {} } }, area: { name: "Area", selector: { area: {} } }, target: { name: "Target", selector: { target: {} } }, diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index 884db9f6a9..bc93ca4242 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -81,6 +81,7 @@ export class HaForm extends LitElement implements HaFormElement { : ""} ${"selector" in item ? html` + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-attribute": HaSelectorAttribute; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index a41184a2de..145991228f 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -6,8 +6,10 @@ import { HomeAssistant } from "../../types"; import "./ha-selector-action"; import "./ha-selector-addon"; import "./ha-selector-area"; +import "./ha-selector-attribute"; import "./ha-selector-boolean"; import "./ha-selector-device"; +import "./ha-selector-duration"; import "./ha-selector-entity"; import "./ha-selector-number"; import "./ha-selector-object"; @@ -15,7 +17,6 @@ import "./ha-selector-select"; import "./ha-selector-target"; import "./ha-selector-text"; import "./ha-selector-time"; -import "./ha-selector-duration"; @customElement("ha-selector") export class HaSelector extends LitElement { diff --git a/src/data/selector.ts b/src/data/selector.ts index eac4404399..4ed8f9dd7b 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -1,7 +1,9 @@ export type Selector = | AddonSelector + | AttributeSelector | EntitySelector | DeviceSelector + | DurationSelector | AreaSelector | TargetSelector | NumberSelector @@ -11,6 +13,7 @@ export type Selector = | StringSelector | ObjectSelector | SelectSelector; + export interface EntitySelector { entity: { integration?: string; @@ -19,6 +22,12 @@ export interface EntitySelector { }; } +export interface AttributeSelector { + attribute: { + entity_id: string; + }; +} + export interface DeviceSelector { device: { integration?: string; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts index 5b909c1ace..d50304561a 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts @@ -9,6 +9,7 @@ import { string, union, } from "superstruct"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { hasTemplate } from "../../../../../common/string/has-template"; import { StateTrigger } from "../../../../../data/automation"; @@ -33,6 +34,7 @@ const stateTriggerStruct = assign( const SCHEMA = [ { name: "entity_id", selector: { entity: {} } }, + { name: "attribute", selector: { attribute: { entity_id: "" } } }, { name: "from", required: false, selector: { text: {} } }, { name: "to", required: false, selector: { text: {} } }, { name: "for", required: false, selector: { duration: {} } }, @@ -48,6 +50,15 @@ export class HaStateTrigger extends LitElement implements TriggerElement { return { entity_id: "" }; } + private _schema = memoizeOne((entityId) => { + const schema = [...SCHEMA]; + schema[1] = { + name: "attribute", + selector: { attribute: { entity_id: entityId } }, + }; + return schema; + }); + public shouldUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("trigger")) { return true; @@ -81,12 +92,13 @@ export class HaStateTrigger extends LitElement implements TriggerElement { const trgFor = createDurationData(this.trigger.for); const data = { ...this.trigger, ...{ for: trgFor } }; + const schema = this._schema(this.trigger.entity_id); return html` From ce9f83e9a24ed6a9260489fb8e4482d55045fc7d Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 10 Feb 2022 16:11:29 -0600 Subject: [PATCH 049/174] Time Pattern to HA Form (#11648) --- .../ha-automation-trigger-time_pattern.ts | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern.ts index b1b0a74f4c..dbb91a0567 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern.ts @@ -1,12 +1,16 @@ -import "@polymer/paper-input/paper-input"; import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; -import { TimePatternTrigger } from "../../../../../data/automation"; -import { HomeAssistant } from "../../../../../types"; -import { - handleChangeEvent, - TriggerElement, -} from "../ha-automation-trigger-row"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import type { TimePatternTrigger } from "../../../../../data/automation"; +import type { HomeAssistant } from "../../../../../types"; +import type { TriggerElement } from "../ha-automation-trigger-row"; + +const SCHEMA: HaFormSchema[] = [ + { name: "hours", selector: { text: {} } }, + { name: "minutes", selector: { text: {} } }, + { name: "seconds", selector: { text: {} } }, +]; @customElement("ha-automation-trigger-time_pattern") export class HaTimePatternTrigger extends LitElement implements TriggerElement { @@ -19,38 +23,27 @@ export class HaTimePatternTrigger extends LitElement implements TriggerElement { } protected render() { - const { hours, minutes, seconds } = this.trigger; return html` - - - + > `; } private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); + ev.stopPropagation(); + const newTrigger = ev.detail.value; + fireEvent(this, "value-changed", { value: newTrigger }); } + + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.time_pattern.${schema.name}` + ); } declare global { From ac90bb70883975b3001ea290b0d2c108bee1e925 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 10 Feb 2022 16:11:45 -0600 Subject: [PATCH 050/174] MQTT Trigger to Ha-Form (#11643) Co-authored-by: Paulus Schoutsen --- .../types/ha-automation-trigger-mqtt.ts | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt.ts index e3e997771f..95941c85b2 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt.ts @@ -1,12 +1,15 @@ -import "@polymer/paper-input/paper-input"; import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; import { MqttTrigger } from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; -import { - handleChangeEvent, - TriggerElement, -} from "../ha-automation-trigger-row"; +import type { TriggerElement } from "../ha-automation-trigger-row"; + +const SCHEMA: HaFormSchema[] = [ + { name: "topic", required: true, selector: { text: {} } }, + { name: "payload", selector: { text: {} } }, +]; @customElement("ha-automation-trigger-mqtt") export class HaMQTTTrigger extends LitElement implements TriggerElement { @@ -19,30 +22,27 @@ export class HaMQTTTrigger extends LitElement implements TriggerElement { } protected render() { - const { topic, payload } = this.trigger; return html` - - + > `; } private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); + ev.stopPropagation(); + const newTrigger = ev.detail.value; + fireEvent(this, "value-changed", { value: newTrigger }); } + + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.mqtt.${schema.name}` + ); } declare global { From 76f574f875613a0a4d490658197500d37468e064 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 10 Feb 2022 16:11:57 -0600 Subject: [PATCH 051/174] Convert Sun to Ha Form (#11647) Co-authored-by: Paulus Schoutsen --- .../types/ha-automation-trigger-sun.ts | 103 +++++++----------- 1 file changed, 40 insertions(+), 63 deletions(-) diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts index b996cbdcbf..a6efcf9d95 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts @@ -1,16 +1,12 @@ -import "@polymer/paper-input/paper-input"; -import { css, html, LitElement } from "lit"; +import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import "../../../../../components/ha-radio"; -import "../../../../../components/ha-formfield"; -import type { HaRadio } from "../../../../../components/ha-radio"; import type { SunTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; -import { - handleChangeEvent, - TriggerElement, -} from "../ha-automation-trigger-row"; +import type { TriggerElement } from "../ha-automation-trigger-row"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; @customElement("ha-automation-trigger-sun") export class HaSunTrigger extends LitElement implements TriggerElement { @@ -18,6 +14,29 @@ export class HaSunTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public trigger!: SunTrigger; + private _schema = memoizeOne((localize: LocalizeFunc) => [ + { + name: "event", + type: "select", + required: true, + options: [ + [ + "sunrise", + localize( + "ui.panel.config.automation.editor.triggers.type.sun.sunrise" + ), + ], + [ + "sunset", + localize( + "ui.panel.config.automation.editor.triggers.type.sun.sunset" + ), + ], + ], + }, + { name: "offset", selector: { text: {} } }, + ]); + public static get defaultConfig() { return { event: "sunrise" as SunTrigger["event"], @@ -26,69 +45,27 @@ export class HaSunTrigger extends LitElement implements TriggerElement { } protected render() { - const { offset, event } = this.trigger; return html` - - - + > `; } private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); - } - - private _radioGroupPicked(ev) { ev.stopPropagation(); - fireEvent(this, "value-changed", { - value: { - ...this.trigger, - event: (ev.target as HaRadio).value, - }, - }); + const newTrigger = ev.detail.value; + fireEvent(this, "value-changed", { value: newTrigger }); } - static styles = css` - label { - display: flex; - align-items: center; - } - `; + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.sun.${schema.name}` + ); } declare global { From 9912d427f2fee43f7cff7f785f6962b9f938b2ee Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 10 Feb 2022 16:12:01 -0600 Subject: [PATCH 052/174] Geo Location Trigger to HA - Form (#11644) Co-authored-by: Paulus Schoutsen --- .../ha-automation-trigger-geo_location.ts | 118 ++++++------------ 1 file changed, 41 insertions(+), 77 deletions(-) diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location.ts index ffb16e28ec..3b8dcb8e71 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location.ts @@ -1,13 +1,12 @@ -import { css, html, LitElement } from "lit"; +import "../../../../../components/ha-form/ha-form"; +import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import "../../../../../components/entity/ha-entity-picker"; -import type { HaRadio } from "../../../../../components/ha-radio"; +import { HaFormSchema } from "../../../../../components/ha-form/types"; import type { GeoLocationTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; -import { handleChangeEvent } from "../ha-automation-trigger-row"; - -const includeDomains = ["zone"]; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; @customElement("ha-automation-trigger-geo_location") export class HaGeolocationTrigger extends LitElement { @@ -15,6 +14,30 @@ export class HaGeolocationTrigger extends LitElement { @property({ attribute: false }) public trigger!: GeoLocationTrigger; + private _schema = memoizeOne((localize: LocalizeFunc) => [ + { name: "source", selector: { text: {} } }, + { name: "zone", selector: { entity: { domain: "zone" } } }, + { + name: "event", + type: "select", + required: true, + options: [ + [ + "enter", + localize( + "ui.panel.config.automation.editor.triggers.type.geo_location.enter" + ), + ], + [ + "leave", + localize( + "ui.panel.config.automation.editor.triggers.type.geo_location.leave" + ), + ], + ], + }, + ]); + public static get defaultConfig() { return { source: "", @@ -24,86 +47,27 @@ export class HaGeolocationTrigger extends LitElement { } protected render() { - const { source, zone, event } = this.trigger; - return html` - - - + .computeLabel=${this._computeLabelCallback} + @value-changed=${this._valueChanged} + > `; } private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); - } - - private _zonePicked(ev: CustomEvent) { ev.stopPropagation(); - fireEvent(this, "value-changed", { - value: { ...this.trigger, zone: ev.detail.value }, - }); + const newTrigger = ev.detail.value; + fireEvent(this, "value-changed", { value: newTrigger }); } - private _radioGroupPicked(ev: CustomEvent) { - ev.stopPropagation(); - fireEvent(this, "value-changed", { - value: { - ...this.trigger, - event: (ev.target as HaRadio).value, - }, - }); - } - - static styles = css` - label { - display: flex; - align-items: center; - } - `; + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.geo_location.${schema.name}` + ); } declare global { From ed84ce9692439a5f7420b4d63268d6a0c0cdbe4b Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 10 Feb 2022 16:12:12 -0600 Subject: [PATCH 053/174] HA Trigger to HA Form (#11645) Co-authored-by: Paulus Schoutsen --- .../ha-automation-trigger-homeassistant.ts | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant.ts index c660b8f132..fceb9bd515 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant.ts @@ -1,11 +1,12 @@ +import "../../../../../components/ha-form/ha-form"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import type { HaRadio } from "../../../../../components/ha-radio"; import type { HassTrigger } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; -import "../../../../../components/ha-formfield"; -import "../../../../../components/ha-radio"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; @customElement("ha-automation-trigger-homeassistant") export class HaHassTrigger extends LitElement { @@ -13,6 +14,28 @@ export class HaHassTrigger extends LitElement { @property({ attribute: false }) public trigger!: HassTrigger; + private _schema = memoizeOne((localize: LocalizeFunc) => [ + { + name: "event", + type: "select", + required: true, + options: [ + [ + "start", + localize( + "ui.panel.config.automation.editor.triggers.type.homeassistant.start" + ), + ], + [ + "shutdown", + localize( + "ui.panel.config.automation.editor.triggers.type.homeassistant.shutdown" + ), + ], + ], + }, + ]); + public static get defaultConfig() { return { event: "start" as HassTrigger["event"], @@ -20,50 +43,28 @@ export class HaHassTrigger extends LitElement { } protected render() { - const { event } = this.trigger; return html` - + `; } - private _radioGroupPicked(ev) { + private _valueChanged(ev: CustomEvent): void { ev.stopPropagation(); - fireEvent(this, "value-changed", { - value: { - ...this.trigger, - event: (ev.target as HaRadio).value, - }, - }); + const newTrigger = ev.detail.value; + fireEvent(this, "value-changed", { value: newTrigger }); } + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.geo_location.${schema.name}` + ); + static styles = css` label { display: flex; From 099fa706a0476d0f84eb4bf222ea0719aaa14a80 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Fri, 11 Feb 2022 07:05:07 -0600 Subject: [PATCH 054/174] Make HA Form set required to false for selectors (#11649) --- src/components/ha-form/ha-form.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index bc93ca4242..af97f2dffb 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -87,7 +87,7 @@ export class HaForm extends LitElement implements HaFormElement { .value=${getValue(this.data, item)} .label=${this._computeLabel(item)} .disabled=${this.disabled} - .required=${item.required} + .required=${item.required || false} >` : dynamicElement(`ha-form-${item.type}`, { schema: item, From db7cac578230b4b7669922e7801dce5a7805156d Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Fri, 11 Feb 2022 10:31:45 -0600 Subject: [PATCH 055/174] Fix Lovelace Empty Menu when not advanced or admin (#11660) --- src/panels/lovelace/hui-root.ts | 285 +++++++++++++++++--------------- 1 file changed, 156 insertions(+), 129 deletions(-) diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index ccd4ee08fb..d2c2a1a6b7 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -276,137 +276,153 @@ class HUIRoot extends LitElement { > ` : ""} - - - ${this.narrow && - this._conversation(this.hass.config.components) - ? html` - + - ${this.hass!.localize( - "ui.panel.lovelace.menu.start_conversation" - )} - - - ` - : ""} - ${this._yamlMode - ? html` - - ${this.hass!.localize("ui.common.refresh")} - - - - ${this.hass!.localize( - "ui.panel.lovelace.unused_entities.title" - )} - - - ` - : ""} - ${(this.hass.panels.lovelace?.config as LovelacePanelConfig) - ?.mode === "yaml" - ? html` - - ${this.hass!.localize( - "ui.panel.lovelace.menu.reload_resources" - )} - - - ` - : ""} - ${this.hass!.user?.is_admin && !this.hass!.config.safe_mode - ? html` - - ${this.hass!.localize( - "ui.panel.lovelace.menu.configure_ui" - )} - - - ` - : ""} - ${this._editMode - ? html` - - - ${this.hass!.localize( - "ui.panel.lovelace.menu.help" - )} - - - - ` - : ""} - + .path=${mdiDotsVertical} + > + ${this.narrow && + this._conversation(this.hass.config.components) + ? html` + + ${this.hass!.localize( + "ui.panel.lovelace.menu.start_conversation" + )} + + + ` + : ""} + ${this._yamlMode + ? html` + + ${this.hass!.localize( + "ui.common.refresh" + )} + + + + ${this.hass!.localize( + "ui.panel.lovelace.unused_entities.title" + )} + + + ` + : ""} + ${( + this.hass.panels.lovelace + ?.config as LovelacePanelConfig + )?.mode === "yaml" + ? html` + + ${this.hass!.localize( + "ui.panel.lovelace.menu.reload_resources" + )} + + + ` + : ""} + ${this.hass!.user?.is_admin && + !this.hass!.config.safe_mode + ? html` + + ${this.hass!.localize( + "ui.panel.lovelace.menu.configure_ui" + )} + + + ` + : ""} + ${this._editMode + ? html` + + + ${this.hass!.localize( + "ui.panel.lovelace.menu.help" + )} + + + + ` + : ""} + + ` + : ""} `} ${this._editMode @@ -621,6 +637,17 @@ class HUIRoot extends LitElement { return this.shadowRoot!.getElementById("view") as HTMLDivElement; } + private get _showButtonMenu(): boolean { + return ( + (this.narrow && this._conversation(this.hass.config.components)) || + this._editMode || + (this.hass!.user?.is_admin && !this.hass!.config.safe_mode) || + (this.hass.panels.lovelace?.config as LovelacePanelConfig)?.mode === + "yaml" || + this._yamlMode + ); + } + private _handleRefresh(ev: CustomEvent): void { if (!shouldHandleRequestSelectedEvent(ev)) { return; From 35cc2911185889d32bc760517df1caf4bd76b79c Mon Sep 17 00:00:00 2001 From: Shay Levy Date: Fri, 11 Feb 2022 18:42:22 +0200 Subject: [PATCH 056/174] Add support for media player assumed state (#11642) --- src/data/media-player.ts | 40 +++++++++--- .../hui-media-player-entity-row.ts | 63 ++++++++++++++++--- 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 48f3351dbb..6328b4aafe 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -261,8 +261,10 @@ export const computeMediaControls = ( }); } + const assumedState = stateObj.attributes.assumed_state === true; + if ( - (state === "playing" || state === "paused") && + (state === "playing" || state === "paused" || assumedState) && supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK) ) { buttons.push({ @@ -272,14 +274,15 @@ export const computeMediaControls = ( } if ( - (state === "playing" && + !assumedState && + ((state === "playing" && (supportsFeature(stateObj, SUPPORT_PAUSE) || supportsFeature(stateObj, SUPPORT_STOP))) || - ((state === "paused" || state === "idle") && - supportsFeature(stateObj, SUPPORT_PLAY)) || - (state === "on" && - (supportsFeature(stateObj, SUPPORT_PLAY) || - supportsFeature(stateObj, SUPPORT_PAUSE))) + ((state === "paused" || state === "idle") && + supportsFeature(stateObj, SUPPORT_PLAY)) || + (state === "on" && + (supportsFeature(stateObj, SUPPORT_PLAY) || + supportsFeature(stateObj, SUPPORT_PAUSE)))) ) { buttons.push({ icon: @@ -299,8 +302,29 @@ export const computeMediaControls = ( }); } + if (assumedState && supportsFeature(stateObj, SUPPORT_PLAY)) { + buttons.push({ + icon: mdiPlay, + action: "media_play", + }); + } + + if (assumedState && supportsFeature(stateObj, SUPPORT_PAUSE)) { + buttons.push({ + icon: mdiPause, + action: "media_pause", + }); + } + + if (assumedState && supportsFeature(stateObj, SUPPORT_STOP)) { + buttons.push({ + icon: mdiStop, + action: "media_stop", + }); + } + if ( - (state === "playing" || state === "paused") && + (state === "playing" || state === "paused" || assumedState) && supportsFeature(stateObj, SUPPORT_NEXT_TRACK) ) { buttons.push({ diff --git a/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts b/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts index 5935905dbf..f9e40bea12 100644 --- a/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-media-player-entity-row.ts @@ -110,10 +110,11 @@ class HuiMediaPlayerEntityRow extends LitElement implements LovelaceRow { const entityState = stateObj.state; const controlButton = this._computeControlButton(stateObj); + const assumedState = stateObj.attributes.assumed_state === true; const buttons = html` ${!this._narrow && - entityState === "playing" && + (entityState === "playing" || assumedState) && supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK) ? html` ` : ""} - ${(entityState === "playing" && + ${!assumedState && + ((entityState === "playing" && (supportsFeature(stateObj, SUPPORT_PAUSE) || supportsFeature(stateObj, SUPPORT_STOP))) || - ((entityState === "paused" || entityState === "idle") && - supportsFeature(stateObj, SUPPORT_PLAY)) || - (entityState === "on" && - (supportsFeature(stateObj, SUPPORT_PLAY) || - supportsFeature(stateObj, SUPPORT_PAUSE))) + ((entityState === "paused" || entityState === "idle") && + supportsFeature(stateObj, SUPPORT_PLAY)) || + (entityState === "on" && + (supportsFeature(stateObj, SUPPORT_PLAY) || + supportsFeature(stateObj, SUPPORT_PAUSE)))) ? html` ` : ""} - ${entityState === "playing" && + ${assumedState && supportsFeature(stateObj, SUPPORT_PLAY) + ? html` + + ` + : ""} + ${assumedState && supportsFeature(stateObj, SUPPORT_PAUSE) + ? html` + + ` + : ""} + ${assumedState && supportsFeature(stateObj, SUPPORT_STOP) + ? html` + + ` + : ""} + ${(entityState === "playing" || assumedState) && supportsFeature(stateObj, SUPPORT_NEXT_TRACK) ? html` Date: Fri, 11 Feb 2022 19:34:50 +0100 Subject: [PATCH 057/174] Improve search and filters on mobile + fix close button in search field (#11662) Co-authored-by: Zack --- src/common/search/search-input.ts | 34 +++--- .../ha-button-related-filter-menu.ts | 12 +- src/components/ha-textfield.ts | 8 ++ src/layouts/hass-tabs-subpage-data-table.ts | 89 ++++++++++----- src/layouts/hass-tabs-subpage.ts | 1 + .../integrations/ha-config-integrations.ts | 108 +++++++++++------- src/panels/config/logs/ha-config-logs.ts | 4 +- 7 files changed, 163 insertions(+), 93 deletions(-) diff --git a/src/common/search/search-input.ts b/src/common/search/search-input.ts index f9806d60e0..ae1d48811a 100644 --- a/src/common/search/search-input.ts +++ b/src/common/search/search-input.ts @@ -14,6 +14,9 @@ class SearchInput extends LitElement { @property() public filter?: string; + @property({ type: Boolean }) + public suffix = false; + @property({ type: Boolean }) public autofocus = false; @@ -33,7 +36,7 @@ class SearchInput extends LitElement { .label=${this.label || "Search"} .value=${this.filter || ""} .icon=${true} - .iconTrailing=${this.filter} + .iconTrailing=${this.filter || this.suffix} @input=${this._filterInputChanged} > @@ -43,16 +46,18 @@ class SearchInput extends LitElement { .path=${mdiMagnify} > - ${this.filter && - html` - - `} +
+ ${this.filter && + html` + + `} + +
`; } @@ -81,15 +86,16 @@ class SearchInput extends LitElement { ha-svg-icon { outline: none; } - ha-icon-button { - --mdc-icon-button-size: 24px; - } .clear-button { --mdc-icon-size: 20px; } ha-textfield { display: inherit; } + .trailing { + display: flex; + align-items: center; + } `; } } diff --git a/src/components/ha-button-related-filter-menu.ts b/src/components/ha-button-related-filter-menu.ts index fb65469e12..9eb28c7bb7 100644 --- a/src/components/ha-button-related-filter-menu.ts +++ b/src/components/ha-button-related-filter-menu.ts @@ -73,6 +73,7 @@ export class HaRelatedFilterButtonMenu extends LitElement { .hass=${this.hass} .value=${this.value?.area} no-add + .excludeDomains=${this.excludeDomains} @value-changed=${this._areaPicked} > - -
- ${filterInfo - ? html`
- ${this.narrow - ? html`
- - - ${filterInfo} - -
` - : filterInfo} - - ${this.hass.localize("ui.components.data-table.clear")} - -
` - : ""} -
`; + .hass=${this.hass} + .filter=${this.filter} + .suffix=${!this.narrow} + @value-changed=${this._handleSearchChange} + .label=${this.searchLabel || + this.hass.localize("ui.components.data-table.search")} + > + ${!this.narrow + ? html`
+ ${filterInfo + ? html`
+ ${filterInfo} + + ${this.hass.localize("ui.components.data-table.clear")} + +
` + : ""} + +
` + : ""} + `; return html` -
+
+ ${this.narrow + ? html`
+ ${this.numHidden || + this.activeFilters + ? html`${this.numHidden || "!"}` + : ""} +
` + : ""} +
${this.narrow ? html`
@@ -267,6 +272,12 @@ export class HaTabsSubpageDataTable extends LitElement { align-items: center; color: var(--secondary-text-color); } + search-input { + --mdc-text-field-fill-color: var(--sidebar-background-color); + --mdc-text-field-idle-line-color: var(--divider-color); + --text-field-overflow: visible; + z-index: 5; + } .table-header search-input { display: block; position: absolute; @@ -276,15 +287,16 @@ export class HaTabsSubpageDataTable extends LitElement { } .search-toolbar search-input { display: block; + width: 100%; color: var(--secondary-text-color); - --mdc-text-field-fill-color: transparant; - --mdc-text-field-idle-line-color: var(--divider-color); --mdc-ripple-color: transparant; } .filters { + --mdc-text-field-fill-color: initial; + --mdc-text-field-idle-line-color: initial; + --text-field-overflow: initial; display: flex; justify-content: flex-end; - width: 100%; margin-right: 8px; } .active-filters { @@ -295,6 +307,7 @@ export class HaTabsSubpageDataTable extends LitElement { padding: 2px 2px 2px 8px; margin-left: 4px; font-size: 14px; + width: max-content; } .active-filters ha-svg-icon { color: var(--primary-color); @@ -313,6 +326,24 @@ export class HaTabsSubpageDataTable extends LitElement { left: 0; content: ""; } + .badge { + min-width: 20px; + box-sizing: border-box; + border-radius: 50%; + font-weight: 400; + background-color: var(--primary-color); + line-height: 20px; + text-align: center; + padding: 0px 4px; + color: var(--text-primary-color); + position: absolute; + right: 0; + top: 4px; + font-size: 0.65em; + } + .filter-menu { + position: relative; + } `; } } diff --git a/src/layouts/hass-tabs-subpage.ts b/src/layouts/hass-tabs-subpage.ts index 0054e2dbbf..1c749ad090 100644 --- a/src/layouts/hass-tabs-subpage.ts +++ b/src/layouts/hass-tabs-subpage.ts @@ -272,6 +272,7 @@ class HassTabsSubpage extends LitElement { ha-menu-button, ha-icon-button-arrow-prev, ::slotted([slot="toolbar-icon"]) { + display: flex; flex-shrink: 0; pointer-events: auto; color: var(--sidebar-icon-color); diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 4dc224c2e7..3c8dbe1f1e 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -297,29 +297,35 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { this._filter ); - const filterMenu = html` - - - - ${this.hass.localize( - "ui.panel.config.integrations.ignore.show_ignored" - )} - - - ${this.hass.localize( - "ui.panel.config.integrations.disable.show_disabled" - )} - - `; + + + + ${this.hass.localize( + "ui.panel.config.integrations.ignore.show_ignored" + )} + + + ${this.hass.localize( + "ui.panel.config.integrations.disable.show_disabled" + )} + + + ${!this._showDisabled && this.narrow && disabledCount + ? html`${disabledCount}` + : ""} +
`; return html` - ${!this._showDisabled && disabledCount - ? html`
- ${this.hass.localize( - "ui.panel.config.integrations.disable.disabled_integrations", - { number: disabledCount } - )} - + ${!this._showDisabled && disabledCount + ? html`
+ ${this.hass.localize( + "ui.panel.config.integrations.disable.disabled_integrations", + { number: disabledCount } )} - > -
` - : ""} - ${filterMenu} + +
` + : ""} + ${filterMenu} +
`} @@ -683,13 +687,15 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { .empty-message h1 { margin-bottom: 0; } - + search-input { + --mdc-text-field-fill-color: var(--sidebar-background-color); + --mdc-text-field-idle-line-color: var(--divider-color); + --text-field-overflow: visible; + } search-input.header { display: block; color: var(--secondary-text-color); margin-left: 8px; - --mdc-text-field-fill-color: transparant; - --mdc-text-field-idle-line-color: var(--divider-color); --mdc-ripple-color: transparant; } .search { @@ -717,6 +723,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { align-items: center; padding: 2px 2px 2px 8px; font-size: 14px; + width: max-content; } .active-filters mwc-button { margin-left: 8px; @@ -732,6 +739,21 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { left: 0; content: ""; } + .badge { + min-width: 20px; + box-sizing: border-box; + border-radius: 50%; + font-weight: 400; + background-color: var(--primary-color); + line-height: 20px; + text-align: center; + padding: 0px 4px; + color: var(--text-primary-color); + position: absolute; + right: 14px; + top: 8px; + font-size: 0.65em; + } `, ]; } diff --git a/src/panels/config/logs/ha-config-logs.ts b/src/panels/config/logs/ha-config-logs.ts index 603fef1777..7161e055fd 100644 --- a/src/panels/config/logs/ha-config-logs.ts +++ b/src/panels/config/logs/ha-config-logs.ts @@ -105,10 +105,10 @@ export class HaConfigLogs extends LitElement { } search-input { display: block; + --mdc-text-field-fill-color: var(--sidebar-background-color); + --mdc-text-field-idle-line-color: var(--divider-color); } search-input.header { - --mdc-text-field-fill-color: transparant; - --mdc-text-field-idle-line-color: var(--divider-color); --mdc-ripple-color: transparant; } .content { From ee1fd3e8653faf444d6112e93b327e39ca2d57cb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 11 Feb 2022 10:49:16 -0800 Subject: [PATCH 058/174] Allow adding Zigbee/Zwave device (#11650) --- .../config-flow/step-flow-pick-handler.ts | 130 ++++++++++++------ src/translations/en.json | 2 + 2 files changed, 91 insertions(+), 41 deletions(-) diff --git a/src/dialogs/config-flow/step-flow-pick-handler.ts b/src/dialogs/config-flow/step-flow-pick-handler.ts index 38257e66aa..cd0652bcc2 100644 --- a/src/dialogs/config-flow/step-flow-pick-handler.ts +++ b/src/dialogs/config-flow/step-flow-pick-handler.ts @@ -1,5 +1,4 @@ -import "@polymer/paper-item/paper-icon-item"; -import "@polymer/paper-item/paper-item-body"; +import "@material/mwc-list/mwc-list-item"; import Fuse from "fuse.js"; import { css, @@ -12,12 +11,16 @@ import { import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import memoizeOne from "memoize-one"; +import { isComponentLoaded } from "../../common/config/is_component_loaded"; import { fireEvent } from "../../common/dom/fire_event"; +import { navigate } from "../../common/navigate"; import "../../common/search/search-input"; import { caseInsensitiveStringCompare } from "../../common/string/compare"; import { LocalizeFunc } from "../../common/translations/localize"; import "../../components/ha-icon-next"; +import { getConfigEntries } from "../../data/config_entries"; import { domainToName } from "../../data/integration"; +import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node"; import { HomeAssistant } from "../../types"; import { brandsUrl } from "../../util/brands-url"; import { documentationUrl } from "../../util/documentation-url"; @@ -26,6 +29,7 @@ import { configFlowContentStyles } from "./styles"; interface HandlerObj { name: string; slug: string; + is_add?: boolean; } declare global { @@ -77,6 +81,17 @@ class StepFlowPickHandler extends LitElement { protected render(): TemplateResult { const handlers = this._getHandlers(); + const addDeviceRows: HandlerObj[] = ["zha", "zwave_js"] + .filter((domain) => isComponentLoaded(this.hass, domain)) + .map((domain) => ({ + name: this.hass.localize( + `ui.panel.config.integrations.add_${domain}_device` + ), + slug: domain, + is_add: true, + })) + .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)); + return html`

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

+ ${addDeviceRows.length + ? html` + ${addDeviceRows.map((handler) => this._renderRow(handler))} +
+ ` + : ""} ${handlers.length - ? handlers.map( - (handler: HandlerObj) => - html` - - - - ${handler.name} - - - ` - ) + ? handlers.map((handler) => this._renderRow(handler)) : html`

${this.hass.localize( @@ -144,6 +143,31 @@ class StepFlowPickHandler extends LitElement { `; } + private _renderRow(handler: HandlerObj) { + return html` + + + ${handler.name} + ${handler.is_add ? "" : html``} + + `; + } + public willUpdate(changedProps: PropertyValues): void { if (this._filter === undefined && this.initialFilter !== undefined) { this._filter = this.initialFilter; @@ -161,20 +185,17 @@ class StepFlowPickHandler extends LitElement { protected updated(changedProps) { super.updated(changedProps); - // Store the width and height so that when we search, box doesn't jump - const div = this.shadowRoot!.querySelector("div")!; - if (!this._width) { - const width = div.clientWidth; - if (width) { - this._width = width; - } - } - if (!this._height) { - const height = div.clientHeight; - if (height) { - this._height = height; - } + if (!changedProps.has("handlers")) { + return; } + // Wait until list item initialized + const firstListItem = this.shadowRoot!.querySelector("mwc-list-item")!; + firstListItem.updateComplete.then(() => { + // Store the width and height so that when we search, box doesn't jump + const div = this.shadowRoot!.querySelector("div.container")!; + this._width = div.clientWidth; + this._height = div.clientHeight; + }); } private _getHandlers() { @@ -190,8 +211,31 @@ class StepFlowPickHandler extends LitElement { } private async _handlerPicked(ev) { + const handler: HandlerObj = ev.currentTarget.handler; + + if (handler.is_add) { + if (handler.slug === "zwave_js") { + const entries = await getConfigEntries(this.hass); + const entry = entries.find((ent) => ent.domain === "zwave_js"); + + if (!entry) { + return; + } + + showZWaveJSAddNodeDialog(this, { + entry_id: entry.entry_id, + }); + } else if (handler.slug === "zha") { + navigate("/config/zha/add"); + } + + // This closes dialog. + fireEvent(this, "flow-update"); + return; + } + fireEvent(this, "handler-picked", { - handler: ev.currentTarget.handler.slug, + handler: handler.slug, }); } @@ -219,7 +263,11 @@ class StepFlowPickHandler extends LitElement { } search-input { display: block; - margin: -12px 16px 0; + margin-top: 8px; + } + .divider { + margin: 8px 0; + border-top: 1px solid var(--divider-color); } ha-icon-next { margin-right: 8px; diff --git a/src/translations/en.json b/src/translations/en.json index 547d8ff918..cafd39ed58 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2478,6 +2478,8 @@ "rename_dialog": "Edit the name of this config entry", "rename_input_label": "Entry name", "search": "Search integrations", + "add_zwave_js_device": "Add Z-Wave device", + "add_zha_device": "Add Zigbee device", "disable": { "show_disabled": "Show disabled integrations", "disabled_integrations": "{number} disabled", From fb66d224aec1414b03290c8e58dd4ecbdcccff0c Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Fri, 11 Feb 2022 15:39:33 -0600 Subject: [PATCH 059/174] Numerical State to HA-Form (#11646) Co-authored-by: Paulus Schoutsen --- .../ha-automation-trigger-numeric_state.ts | 109 ++++++++---------- 1 file changed, 51 insertions(+), 58 deletions(-) diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts index 218bb8273b..1a43407438 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts @@ -1,15 +1,13 @@ -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-input/paper-textarea"; +import "../../../../../components/ha-form/ha-form"; import { html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { hasTemplate } from "../../../../../common/string/has-template"; -import "../../../../../components/entity/ha-entity-picker"; -import { NumericStateTrigger } from "../../../../../data/automation"; -import { HomeAssistant } from "../../../../../types"; -import { handleChangeEvent } from "../ha-automation-trigger-row"; -import "../../../../../components/ha-duration-input"; +import type { NumericStateTrigger } from "../../../../../data/automation"; +import type { HomeAssistant } from "../../../../../types"; @customElement("ha-automation-trigger-numeric_state") export class HaNumericStateTrigger extends LitElement { @@ -17,6 +15,22 @@ export class HaNumericStateTrigger extends LitElement { @property() public trigger!: NumericStateTrigger; + private _schema = memoizeOne((entityId): HaFormSchema[] => [ + { name: "entity_id", selector: { entity: {} } }, + { + name: "attribute", + selector: { attribute: { entity_id: entityId } }, + }, + { name: "above", required: false, selector: { text: {} } }, + { name: "below", required: false, selector: { text: {} } }, + { + name: "value_template", + required: false, + selector: { text: { multiline: true } }, + }, + { name: "for", required: false, selector: { duration: {} } }, + ]); + public willUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("trigger")) { return; @@ -38,67 +52,46 @@ export class HaNumericStateTrigger extends LitElement { } public render() { - const { value_template, entity_id, attribute, below, above } = this.trigger; const trgFor = createDurationData(this.trigger.for); + const data = { ...this.trigger, for: trgFor }; + const schema = this._schema(this.trigger.entity_id); + return html` - - - - - - + .computeLabel=${this._computeLabelCallback} + > `; } private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); + ev.stopPropagation(); + const newTrigger = ev.detail.value; + fireEvent(this, "value-changed", { value: newTrigger }); } + + private _computeLabelCallback = (schema: HaFormSchema): string => { + switch (schema.name) { + case "entity_id": + return this.hass.localize("ui.components.entity.entity-picker.entity"); + case "attribute": + return this.hass.localize( + "ui.components.entity.entity-attribute-picker.attribute" + ); + case "for": + return this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.state.for` + ); + default: + return this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.numeric_state.${schema.name}` + ); + } + }; } declare global { From e1c07f109c13b3664edbd01cd3c1b7fd81048adb Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 11 Feb 2022 23:24:29 +0100 Subject: [PATCH 060/174] Filter fixes (#11664) --- .../ha-button-related-filter-menu.ts | 20 ++++++- .../config-flow/step-flow-pick-handler.ts | 60 +++++++++---------- src/dialogs/more-info/ha-more-info-dialog.ts | 4 +- src/layouts/hass-tabs-subpage-data-table.ts | 21 +++++-- .../integrations/ha-config-integrations.ts | 21 +++++-- 5 files changed, 83 insertions(+), 43 deletions(-) diff --git a/src/components/ha-button-related-filter-menu.ts b/src/components/ha-button-related-filter-menu.ts index 9eb28c7bb7..bc0a7c26b3 100644 --- a/src/components/ha-button-related-filter-menu.ts +++ b/src/components/ha-button-related-filter-menu.ts @@ -4,6 +4,7 @@ import { mdiFilterVariant } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; +import { stopPropagation } from "../common/dom/stop_propagation"; import { computeStateName } from "../common/entity/compute_state_name"; import { computeDeviceName } from "../data/device_registry"; import { findRelated, RelatedResult } from "../data/search"; @@ -65,6 +66,7 @@ export class HaRelatedFilterButtonMenu extends LitElement { .fullwidth=${this.narrow} .corner=${this.corner} @closed=${this._onClosed} + @input=${stopPropagation} > `; @@ -110,7 +115,12 @@ export class HaRelatedFilterButtonMenu extends LitElement { this._open = false; } + private _preventDefault(ev) { + ev.preventDefault(); + } + private async _entityPicked(ev: CustomEvent) { + ev.stopPropagation(); const entityId = ev.detail.value; if (!entityId) { fireEvent(this, "related-changed", { value: undefined }); @@ -130,6 +140,7 @@ export class HaRelatedFilterButtonMenu extends LitElement { } private async _devicePicked(ev: CustomEvent) { + ev.stopPropagation(); const deviceId = ev.detail.value; if (!deviceId) { fireEvent(this, "related-changed", { value: undefined }); @@ -153,6 +164,7 @@ export class HaRelatedFilterButtonMenu extends LitElement { } private async _areaPicked(ev: CustomEvent) { + ev.stopPropagation(); const areaId = ev.detail.value; if (!areaId) { fireEvent(this, "related-changed", { value: undefined }); @@ -176,7 +188,7 @@ export class HaRelatedFilterButtonMenu extends LitElement { :host { display: inline-block; position: relative; - --mdc-menu-min-width: 200px; + --mdc-menu-min-width: 250px; } ha-area-picker, ha-device-picker, @@ -186,6 +198,12 @@ export class HaRelatedFilterButtonMenu extends LitElement { padding: 4px 16px; box-sizing: border-box; } + ha-area-picker { + padding-top: 16px; + } + ha-entity-picker { + padding-bottom: 16px; + } :host([narrow]) ha-area-picker, :host([narrow]) ha-device-picker, :host([narrow]) ha-entity-picker { diff --git a/src/dialogs/config-flow/step-flow-pick-handler.ts b/src/dialogs/config-flow/step-flow-pick-handler.ts index cd0652bcc2..d40b8dad64 100644 --- a/src/dialogs/config-flow/step-flow-pick-handler.ts +++ b/src/dialogs/config-flow/step-flow-pick-handler.ts @@ -1,3 +1,4 @@ +import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list-item"; import Fuse from "fuse.js"; import { @@ -5,8 +6,8 @@ import { CSSResultGroup, html, LitElement, - TemplateResult, PropertyValues, + TemplateResult, } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; @@ -102,8 +103,7 @@ class StepFlowPickHandler extends LitElement { .label=${this.hass.localize("ui.panel.config.integrations.search")} @keypress=${this._maybeSubmit} > -

this._renderRow(handler))} -
+ ` : ""} ${handlers.length @@ -139,7 +139,7 @@ class StepFlowPickHandler extends LitElement { >.

`} -
+ `; } @@ -169,10 +169,26 @@ class StepFlowPickHandler extends LitElement { } public willUpdate(changedProps: PropertyValues): void { + super.willUpdate(changedProps); if (this._filter === undefined && this.initialFilter !== undefined) { this._filter = this.initialFilter; } - super.willUpdate(changedProps); + if (this.initialFilter !== undefined && this._filter === "") { + this.initialFilter = undefined; + this._filter = ""; + this._width = undefined; + this._height = undefined; + } else if ( + this.hasUpdated && + changedProps.has("_filter") && + (!this._width || !this._height) + ) { + // Store the width and height so that when we search, box doesn't jump + const boundingRect = + this.shadowRoot!.querySelector("mwc-list")!.getBoundingClientRect(); + this._width = boundingRect.width; + this._height = boundingRect.height; + } } protected firstUpdated(changedProps) { @@ -183,21 +199,6 @@ class StepFlowPickHandler extends LitElement { ); } - protected updated(changedProps) { - super.updated(changedProps); - if (!changedProps.has("handlers")) { - return; - } - // Wait until list item initialized - const firstListItem = this.shadowRoot!.querySelector("mwc-list-item")!; - firstListItem.updateComplete.then(() => { - // Store the width and height so that when we search, box doesn't jump - const div = this.shadowRoot!.querySelector("div.container")!; - this._width = div.clientWidth; - this._height = div.clientHeight; - }); - } - private _getHandlers() { return this._filterHandlers( this.handlers, @@ -263,31 +264,26 @@ class StepFlowPickHandler extends LitElement { } search-input { display: block; - margin-top: 8px; - } - .divider { - margin: 8px 0; - border-top: 1px solid var(--divider-color); + margin: 16px 16px 0; } ha-icon-next { margin-right: 8px; } - div { + mwc-list { overflow: auto; max-height: 600px; } + .divider { + border-bottom-color: var(--divider-color); + } h2 { padding-right: 66px; } @media all and (max-height: 900px) { - div { + mwc-list { max-height: calc(100vh - 134px); } } - paper-icon-item { - cursor: pointer; - margin-bottom: 4px; - } p { text-align: center; padding: 16px; diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index dd33958584..2198a69a9e 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -338,7 +338,9 @@ export class MoreInfoDialog extends LitElement { flex-shrink: 0; display: block; } - + .content { + outline: none; + } @media all and (max-width: 450px), all and (max-height: 500px) { ha-header-bar { --mdc-theme-primary: var(--app-header-background-color); diff --git a/src/layouts/hass-tabs-subpage-data-table.ts b/src/layouts/hass-tabs-subpage-data-table.ts index 3d22846a62..1e44c0fafd 100644 --- a/src/layouts/hass-tabs-subpage-data-table.ts +++ b/src/layouts/hass-tabs-subpage-data-table.ts @@ -164,7 +164,11 @@ export class HaTabsSubpageDataTable extends LitElement { this.hass.localize("ui.components.data-table.search")} > ${!this.narrow - ? html`
+ ? html`
${filterInfo ? html`
${filterInfo} @@ -194,10 +198,10 @@ export class HaTabsSubpageDataTable extends LitElement {
${this.narrow ? html`
- ${this.numHidden || - this.activeFilters + ${this.numHidden || this.activeFilters ? html`${this.numHidden || "!"}` : ""} +
` : ""}
@@ -238,6 +242,10 @@ export class HaTabsSubpageDataTable extends LitElement { `; } + private _preventDefault(ev) { + ev.preventDefault(); + } + private _handleSearchChange(ev: CustomEvent) { if (this.filter === ev.detail.value) { return; @@ -292,12 +300,14 @@ export class HaTabsSubpageDataTable extends LitElement { --mdc-ripple-color: transparant; } .filters { - --mdc-text-field-fill-color: initial; - --mdc-text-field-idle-line-color: initial; + --mdc-text-field-fill-color: var(--input-fill-color); + --mdc-text-field-idle-line-color: var(--input-idle-line-color); + --mdc-shape-small: 4px; --text-field-overflow: initial; display: flex; justify-content: flex-end; margin-right: 8px; + color: var(--primary-text-color); } .active-filters { color: var(--primary-text-color); @@ -308,6 +318,7 @@ export class HaTabsSubpageDataTable extends LitElement { margin-left: 4px; font-size: 14px; width: max-content; + cursor: initial; } .active-filters ha-svg-icon { color: var(--primary-color); diff --git a/src/panels/config/integrations/ha-config-integrations.ts b/src/panels/config/integrations/ha-config-integrations.ts index 3c8dbe1f1e..b8fb03c40d 100644 --- a/src/panels/config/integrations/ha-config-integrations.ts +++ b/src/panels/config/integrations/ha-config-integrations.ts @@ -300,10 +300,14 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { const filterMenu = html`
+ ${!this._showDisabled && this.narrow && disabledCount + ? html`${disabledCount}` + : ""} - ${!this._showDisabled && this.narrow && disabledCount - ? html`${disabledCount}` - : ""}
`; return html` @@ -362,7 +363,11 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { )} > ${!this._showDisabled && disabledCount - ? html`
+ ? html`
${this.hass.localize( "ui.panel.config.integrations.disable.disabled_integrations", { number: disabledCount } @@ -507,6 +512,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { `; } + private _preventDefault(ev) { + ev.preventDefault(); + } + private _loadConfigEntries() { getConfigEntries(this.hass).then((configEntries) => { this._configEntries = configEntries @@ -724,6 +733,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { padding: 2px 2px 2px 8px; font-size: 14px; width: max-content; + cursor: initial; } .active-filters mwc-button { margin-left: 8px; @@ -754,6 +764,9 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) { top: 8px; font-size: 0.65em; } + ha-button-menu { + color: var(--primary-text-color); + } `, ]; } From bef6591548607616d70a3cc1110fd3b8b120977d Mon Sep 17 00:00:00 2001 From: Michael <35783820+mib1185@users.noreply.github.com> Date: Sat, 12 Feb 2022 07:30:19 +0100 Subject: [PATCH 061/174] Add WORKSPACE_DIRECTORY environment variable to devcontainer and script.core (#11477) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joakim Sørensen --- .devcontainer/devcontainer.json | 3 +++ script/core | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 977620c9dd..746fa30697 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,6 +16,9 @@ "runem.lit-plugin", "ms-python.vscode-pylance" ], + "containerEnv": { + "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" + }, "settings": { "terminal.integrated.shell.linux": "/bin/bash", "files.eol": "\n", diff --git a/script/core b/script/core index 00d663cab2..79ba38a348 100755 --- a/script/core +++ b/script/core @@ -4,6 +4,8 @@ # Stop on errors set -e +WD="${WORKSPACE_DIRECTORY:=/workspaces/frontend}" + if [ -z "${DEVCONTAINER}" ]; then echo "This task should only run inside a devcontainer, for local install HA Core in a venv." exit 1 @@ -16,9 +18,9 @@ if [ -z $(which hass) ]; then git+git://github.com/home-assistant/home-assistant.git@dev fi -if [ ! -d "/workspaces/frontend/config" ]; then +if [ ! -d "${WD}/config" ]; then echo "Creating default configuration." - mkdir -p "/workspaces/frontend/config"; + mkdir -p "${WD}/config"; hass --script ensure_config -c config echo "demo: @@ -26,24 +28,24 @@ logger: default: info logs: homeassistant.components.frontend: debug -" >> /workspaces/frontend/config/configuration.yaml +" >> "${WD}/config/configuration.yaml" if [ ! -z "${HASSIO}" ]; then echo " # frontend: -# development_repo: /workspaces/frontend +# development_repo: ${WD} hassio: - development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml + development_repo: ${WD}" >> "${WD}/config/configuration.yaml" else echo " frontend: - development_repo: /workspaces/frontend + development_repo: ${WD} # hassio: -# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml +# development_repo: ${WD}" >> "${WD}/config/configuration.yaml" fi fi -hass -c /workspaces/frontend/config +hass -c "${WD}/config" From d86a18b80bbabccea1ab757e78dfb5c94db45073 Mon Sep 17 00:00:00 2001 From: lintaba Date: Sat, 12 Feb 2022 23:00:50 +0100 Subject: [PATCH 062/174] hotfix history view on missing state (#11663) Co-authored-by: Paulus Schoutsen --- src/common/entity/compute_state_display.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index f8bf892d8a..7c6955e084 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -19,6 +19,9 @@ export const computeStateDisplay = ( if (compareState === UNKNOWN || compareState === UNAVAILABLE) { return localize(`state.default.${compareState}`); } + if (compareState === "") { + return localize(`state.default.${UNKNOWN}`); + } // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` if (isNumericState(stateObj)) { @@ -123,7 +126,11 @@ export const computeStateDisplay = ( domain === "scene" || (domain === "sensor" && stateObj.attributes.device_class === "timestamp") ) { - return formatDateTime(new Date(compareState), locale); + try { + return formatDateTime(new Date(compareState), locale); + } catch (_err) { + return compareState; + } } return ( From a8c1fdd21edb26857779b81810e97f0977ec8ef9 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 12 Feb 2022 20:21:26 -0800 Subject: [PATCH 063/174] Improve robustness of hls media player (#11672) --- src/components/ha-hls-player.ts | 85 +++++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 25 deletions(-) diff --git a/src/components/ha-hls-player.ts b/src/components/ha-hls-player.ts index fd16491efa..0be5f97535 100644 --- a/src/components/ha-hls-player.ts +++ b/src/components/ha-hls-player.ts @@ -43,6 +43,8 @@ class HaHLSPlayer extends LitElement { @state() private _error?: string; + @state() private _errorIsFatal = false; + private _hlsPolyfillInstance?: HlsLite; private _exoPlayer = false; @@ -53,6 +55,7 @@ class HaHLSPlayer extends LitElement { super.connectedCallback(); HaHLSPlayer.streamCount += 1; if (this.hasUpdated) { + this._resetError(); this._startHls(); } } @@ -64,16 +67,23 @@ class HaHLSPlayer extends LitElement { } protected render(): TemplateResult { - if (this._error) { - return html`${this._error}`; - } return html` - + ${this._error + ? html` + ${this._error} + ` + : ""} + ${!this._errorIsFatal + ? html`` + : ""} `; } @@ -87,12 +97,11 @@ class HaHLSPlayer extends LitElement { } this._cleanUp(); + this._resetError(); this._startHls(); } private async _startHls(): Promise { - this._error = undefined; - const masterPlaylistPromise = fetch(this.url); const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min")) @@ -110,8 +119,8 @@ class HaHLSPlayer extends LitElement { } if (!hlsSupported) { - this._error = this.hass.localize( - "ui.components.media-browser.video_not_supported" + this._setFatalError( + this.hass.localize("ui.components.media-browser.video_not_supported") ); return; } @@ -219,9 +228,16 @@ class HaHLSPlayer extends LitElement { this._hlsPolyfillInstance = hls; hls.attachMedia(videoEl); hls.on(Hls.Events.MEDIA_ATTACHED, () => { + this._resetError(); hls.loadSource(url); }); - hls.on(Hls.Events.ERROR, (_, data: any) => { + hls.on(Hls.Events.FRAG_LOADED, (_event, _data: any) => { + this._resetError(); + }); + hls.on(Hls.Events.ERROR, (_event, data: any) => { + // Some errors are recovered automatically by the hls player itself, and the others handled + // in this function require special actions to recover. Errors retried in this function + // are done with backoff to not cause unecessary failures. if (!data.fatal) { return; } @@ -241,22 +257,22 @@ class HaHLSPlayer extends LitElement { error += " (" + data.response.code + ")"; } } - this._error = error; - return; + this._setRetryableError(error); + break; } case Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT: - this._error = "Timeout while starting stream"; - return; + this._setRetryableError("Timeout while starting stream"); + break; default: - this._error = "Unknown stream network error (" + data.details + ")"; - return; + this._setRetryableError("Stream network error"); + break; } - this._error = "Error with media stream contents (" + data.details + ")"; + hls.startLoad(); } else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) { - this._error = "Error with media stream contents (" + data.details + ")"; + this._setRetryableError("Error with media stream contents"); + hls.recoverMediaError(); } else { - this._error = - "Unknown error with stream (" + data.type + ", " + data.details + ")"; + this._setFatalError("Error playing stream"); } }); } @@ -284,6 +300,21 @@ class HaHLSPlayer extends LitElement { } } + private _resetError() { + this._error = undefined; + this._errorIsFatal = false; + } + + private _setFatalError(errorMessage: string) { + this._error = errorMessage; + this._errorIsFatal = true; + } + + private _setRetryableError(errorMessage: string) { + this._error = errorMessage; + this._errorIsFatal = false; + } + static get styles(): CSSResultGroup { return css` :host, @@ -296,10 +327,14 @@ class HaHLSPlayer extends LitElement { max-height: var(--video-max-height, calc(100vh - 97px)); } - ha-alert { + .fatal { display: block; padding: 100px 16px; } + + .retry { + display: block; + } `; } } From db33c38e214ffbd675a9465f8cd0aab04826251b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 13 Feb 2022 11:26:12 -0800 Subject: [PATCH 064/174] Revert compute state display show empty string as unknown (#11677) --- src/common/entity/compute_state_display.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/common/entity/compute_state_display.ts b/src/common/entity/compute_state_display.ts index 7c6955e084..3838218ea4 100644 --- a/src/common/entity/compute_state_display.ts +++ b/src/common/entity/compute_state_display.ts @@ -19,9 +19,6 @@ export const computeStateDisplay = ( if (compareState === UNKNOWN || compareState === UNAVAILABLE) { return localize(`state.default.${compareState}`); } - if (compareState === "") { - return localize(`state.default.${UNKNOWN}`); - } // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` if (isNumericState(stateObj)) { From 7f90ffa82f91ffb81fc826d5574a999a71c2fb2e Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Sun, 13 Feb 2022 16:02:48 -0500 Subject: [PATCH 065/174] Set initial focus for some more dialogs (#11676) --- .../dialog-config-entry-system-options.ts | 1 + .../mqtt/dialog-mqtt-device-debug-info.ts | 1 + src/panels/config/logs/dialog-system-log-detail.ts | 3 ++- src/panels/config/users/dialog-add-user.ts | 2 ++ .../statistics/dialog-statistics-fix-units-changed.ts | 1 + .../dialog-statistics-fix-unsupported-unit-meta.ts | 6 +++++- 6 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts index f370a9b00c..aaced030d6 100644 --- a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts +++ b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts @@ -83,6 +83,7 @@ class DialogConfigEntrySystemOptions extends LitElement { .checked=${!this._disableNewEntities} @change=${this._disableNewEntitiesChanged} .disabled=${this._submitting} + dialogInitialFocus > ${this._allowUpdatePolling() diff --git a/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts b/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts index dd85ebe90f..4b0bd660a7 100644 --- a/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts +++ b/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts @@ -71,6 +71,7 @@ class DialogMQTTDeviceDebugInfo extends LitElement { diff --git a/src/panels/config/logs/dialog-system-log-detail.ts b/src/panels/config/logs/dialog-system-log-detail.ts index abda08186a..2765ffa532 100644 --- a/src/panels/config/logs/dialog-system-log-detail.ts +++ b/src/panels/config/logs/dialog-system-log-detail.ts @@ -104,7 +104,7 @@ class DialogSystemLogDetail extends LitElement { )} ` : ""} -
+

Logger: ${item.name}
Source: ${item.source.join(":")} @@ -227,6 +227,7 @@ class DialogSystemLogDetail extends LitElement { } .contents { padding: 16px; + outline: none; } .error { color: var(--error-color); diff --git a/src/panels/config/users/dialog-add-user.ts b/src/panels/config/users/dialog-add-user.ts index 65d11ac262..70761bbdd7 100644 --- a/src/panels/config/users/dialog-add-user.ts +++ b/src/panels/config/users/dialog-add-user.ts @@ -111,6 +111,7 @@ export class DialogAddUser extends LitElement { .errorMessage=${this.hass.localize("ui.common.error_required")} @value-changed=${this._handleValueChanged} @blur=${this._maybePopulateUsername} + dialogInitialFocus >` : ""} - + Fix From 806b1296b0315fe17584719602a6596533ce6bf6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 06:33:21 -0800 Subject: [PATCH 066/174] Limit types of media that can be uploaded to local media (#11683) --- src/panels/media-browser/ha-panel-media-browser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index ff996a122f..f3b541f7a3 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -242,6 +242,7 @@ class PanelMediaBrowser extends LitElement { private async _startUpload() { const input = document.createElement("input"); input.type = "file"; + input.accept = "audio/*,video/*,image/*"; input.addEventListener("change", async () => { try { await uploadLocalMedia( From 63c9b3f8309ec621e741f29abcccdd4a4e907220 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 14 Feb 2022 09:21:46 -0600 Subject: [PATCH 067/174] Don't show toggle always on more info (#11640) --- src/common/entity/can_toggle_domain.ts | 2 +- src/common/entity/can_toggle_state.ts | 22 ++++++++++++++++++--- test/common/entity/can_toggle_state_test.ts | 8 ++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/common/entity/can_toggle_domain.ts b/src/common/entity/can_toggle_domain.ts index d1c4a08b7c..df87bfcf7a 100644 --- a/src/common/entity/can_toggle_domain.ts +++ b/src/common/entity/can_toggle_domain.ts @@ -1,4 +1,4 @@ -import { HomeAssistant } from "../../types"; +import type { HomeAssistant } from "../../types"; export const canToggleDomain = (hass: HomeAssistant, domain: string) => { const services = hass.services[domain]; diff --git a/src/common/entity/can_toggle_state.ts b/src/common/entity/can_toggle_state.ts index f0480b60ff..afc8dd9f9c 100644 --- a/src/common/entity/can_toggle_state.ts +++ b/src/common/entity/can_toggle_state.ts @@ -1,14 +1,30 @@ -import { HassEntity } from "home-assistant-js-websocket"; -import { HomeAssistant } from "../../types"; +import type { HassEntity } from "home-assistant-js-websocket"; +import type { HomeAssistant } from "../../types"; import { canToggleDomain } from "./can_toggle_domain"; import { computeStateDomain } from "./compute_state_domain"; import { supportsFeature } from "./supports-feature"; export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => { const domain = computeStateDomain(stateObj); + if (domain === "group") { - return stateObj.state === "on" || stateObj.state === "off"; + if ( + stateObj.attributes?.entity_id?.some((entity) => { + const entityStateObj = hass.states[entity]; + if (!entityStateObj) { + return false; + } + + const entityDomain = computeStateDomain(entityStateObj); + return canToggleDomain(hass, entityDomain); + }) + ) { + return stateObj.state === "on" || stateObj.state === "off"; + } + + return false; } + if (domain === "climate") { return supportsFeature(stateObj, 4096); } diff --git a/test/common/entity/can_toggle_state_test.ts b/test/common/entity/can_toggle_state_test.ts index b35bb04c63..93bd3849a6 100644 --- a/test/common/entity/can_toggle_state_test.ts +++ b/test/common/entity/can_toggle_state_test.ts @@ -10,6 +10,10 @@ describe("canToggleState", () => { turn_off: null, }, }, + states: { + "light.bla": { entity_id: "light.bla" }, + "light.test": { entity_id: "light.test" }, + }, }; it("Detects lights toggle", () => { @@ -24,7 +28,11 @@ describe("canToggleState", () => { const stateObj: any = { entity_id: "group.bla", state: "on", + attributes: { + entity_id: ["light.bla", "light.test"], + }, }; + assert.isTrue(canToggleState(hass, stateObj)); }); From a321432175edad523245da6f59b563c560f415d2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 07:50:44 -0800 Subject: [PATCH 068/174] Add TTS to media browser (#11679) --- .../media-player/ha-browse-media-tts.ts | 230 ++++++++++++++++ .../media-player/ha-media-player-browse.ts | 259 +++++++++--------- src/data/cloud.ts | 7 - src/data/cloud/tts.ts | 70 +++++ src/data/tts.ts | 8 + .../config/cloud/account/cloud-tts-pref.ts | 77 ++---- .../media-browser/ha-panel-media-browser.ts | 16 +- src/translations/en.json | 12 + 8 files changed, 476 insertions(+), 203 deletions(-) create mode 100644 src/components/media-player/ha-browse-media-tts.ts create mode 100644 src/data/cloud/tts.ts diff --git a/src/components/media-player/ha-browse-media-tts.ts b/src/components/media-player/ha-browse-media-tts.ts new file mode 100644 index 0000000000..8bec663a5d --- /dev/null +++ b/src/components/media-player/ha-browse-media-tts.ts @@ -0,0 +1,230 @@ +import "@material/mwc-select"; +import "@material/mwc-list/mwc-list-item"; +import { css, html, LitElement, PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../common/dom/fire_event"; +import { fetchCloudStatus, updateCloudPref } from "../../data/cloud"; +import { + CloudTTSInfo, + getCloudTTSInfo, + getCloudTtsLanguages, + getCloudTtsSupportedGenders, +} from "../../data/cloud/tts"; +import { MediaPlayerBrowseAction } from "../../data/media-player"; +import { HomeAssistant } from "../../types"; +import "../ha-textarea"; +import { buttonLinkStyle } from "../../resources/styles"; +import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; +import { LocalStorage } from "../../common/decorators/local-storage"; + +@customElement("ha-browse-media-tts") +class BrowseMediaTTS extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public item; + + @property() public action!: MediaPlayerBrowseAction; + + @state() private _cloudDefaultOptions?: [string, string]; + + @state() private _cloudOptions?: [string, string]; + + @state() private _cloudTTSInfo?: CloudTTSInfo; + + @LocalStorage("cloudTtsTryMessage", false, false) private _message!: string; + + protected render() { + return html` + + + ${this._cloudDefaultOptions ? this._renderCloudOptions() : ""} +

+ ${this._cloudDefaultOptions && + (this._cloudDefaultOptions![0] !== this._cloudOptions![0] || + this._cloudDefaultOptions![1] !== this._cloudOptions![1]) + ? html` + + ` + : html``} + +
+ `; + } + + private _renderCloudOptions() { + const languages = this.getLanguages(this._cloudTTSInfo); + const selectedVoice = this._cloudOptions!; + const genders = this.getSupportedGenders( + selectedVoice[0], + this._cloudTTSInfo, + this.hass.localize + ); + + return html` +
+ + ${languages.map( + ([key, label]) => + html`${label}` + )} + + + + ${genders.map( + ([key, label]) => + html`${label}` + )} + +
+ `; + } + + protected override willUpdate(changedProps: PropertyValues): void { + super.willUpdate(changedProps); + + if (changedProps.has("message")) { + return; + } + + // Re-rendering can reset message because textarea content is newer than local storage. + // But we don't want to write every keystroke to local storage. + // So instead we just do it when we're going to render. + const message = this.shadowRoot!.querySelector("ha-textarea")?.value; + if (message !== undefined && message !== this._message) { + this._message = message; + } + } + + async _handleLanguageChange(ev) { + if (ev.target.value === this._cloudOptions![0]) { + return; + } + this._cloudOptions = [ev.target.value, this._cloudOptions![1]]; + } + + async _handleGenderChange(ev) { + if (ev.target.value === this._cloudOptions![1]) { + return; + } + this._cloudOptions = [this._cloudOptions![0], ev.target.value]; + } + + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + + if (changedProps.has("item")) { + if (this.isCloudItem && !this._cloudTTSInfo) { + getCloudTTSInfo(this.hass).then((info) => { + this._cloudTTSInfo = info; + }); + fetchCloudStatus(this.hass).then((status) => { + if (status.logged_in) { + this._cloudDefaultOptions = status.prefs.tts_default_voice; + this._cloudOptions = { ...this._cloudDefaultOptions }; + } + }); + } + } + } + + private getLanguages = memoizeOne(getCloudTtsLanguages); + + private getSupportedGenders = memoizeOne(getCloudTtsSupportedGenders); + + private get isCloudItem(): boolean { + return this.item.media_content_id === "media-source://tts/cloud"; + } + + private async _ttsClicked(): Promise { + const message = this.shadowRoot!.querySelector("ha-textarea")!.value; + this._message = message; + const item = { ...this.item }; + const query = new URLSearchParams(); + query.append("message", message); + if (this._cloudOptions) { + query.append("language", this._cloudOptions[0]); + query.append("gender", this._cloudOptions[1]); + } + item.media_content_id += `?${query.toString()}`; + item.can_play = true; + fireEvent(this, "media-picked", { item }); + } + + private async _storeDefaults() { + const oldDefaults = this._cloudDefaultOptions!; + this._cloudDefaultOptions = [...this._cloudOptions!]; + try { + await updateCloudPref(this.hass, { + tts_default_voice: this._cloudDefaultOptions, + }); + } catch (err: any) { + this._cloudDefaultOptions = oldDefaults; + showAlertDialog(this, { + text: this.hass.localize( + "ui.panel.media-browser.tts.faild_to_store_defaults", + { error: err.message || err } + ), + }); + } + } + + static override styles = [ + buttonLinkStyle, + css` + :host { + margin: 16px auto; + padding: 0 8px; + display: flex; + flex-direction: column; + max-width: 400px; + } + .cloud-options { + margin-top: 16px; + display: flex; + justify-content: space-between; + } + .cloud-options mwc-select { + width: 48%; + } + + .actions { + display: flex; + justify-content: space-between; + margin-top: 16px; + } + button.link { + color: var(--primary-color); + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-browse-media-tts": BrowseMediaTTS; + } +} diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 939cb78b82..27e2274169 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -49,6 +49,8 @@ import "../ha-icon-button"; import "../ha-svg-icon"; import "../ha-fab"; import { browseLocalMediaPlayer } from "../../data/media_source"; +import { isTTSMediaSource } from "../../data/tts"; +import "./ha-browse-media-tts"; declare global { interface HASSDomEvents { @@ -246,131 +248,16 @@ export class HaMediaPlayerBrowse extends LitElement { ${this._renderError(this._error)}
` - : currentItem.children?.length - ? childrenMediaClass.layout === "grid" - ? html` -
- ${currentItem.children.map( - (child) => html` -
- -
- ${child.thumbnail - ? html` -
- ` - : html` -
- -
- `} - ${child.can_play - ? html` - - ` - : ""} -
-
- ${child.title} - ${child.title} -
-
-
- ` - )} -
- ` - : html` - - ${currentItem.children.map( - (child) => html` - -
- -
- ${child.title} -
-
  • - ` - )} -
    - ` - : html` + : isTTSMediaSource(currentItem.media_content_id) + ? html` + + ` + : !currentItem.children?.length + ? html`
    ${this.hass.localize( "ui.components.media-browser.no_items" @@ -400,6 +287,128 @@ export class HaMediaPlayerBrowse extends LitElement { : ""}
    ` + : childrenMediaClass.layout === "grid" + ? html` +
    + ${currentItem.children.map( + (child) => html` +
    + +
    + ${child.thumbnail + ? html` +
    + ` + : html` +
    + +
    + `} + ${child.can_play + ? html` + + ` + : ""} +
    +
    + ${child.title} + ${child.title} +
    +
    +
    + ` + )} +
    + ` + : html` + + ${currentItem.children.map( + (child) => html` + +
    + +
    + ${child.title} +
    +
  • + ` + )} +
    + ` }
    diff --git a/src/data/cloud.ts b/src/data/cloud.ts index 4679d97a75..8f4f14b72b 100644 --- a/src/data/cloud.ts +++ b/src/data/cloud.ts @@ -186,10 +186,3 @@ export const updateCloudAlexaEntityConfig = ( entity_id: entityId, ...values, }); - -export interface CloudTTSInfo { - languages: Array<[string, string]>; -} - -export const getCloudTTSInfo = (hass: HomeAssistant) => - hass.callWS({ type: "cloud/tts/info" }); diff --git a/src/data/cloud/tts.ts b/src/data/cloud/tts.ts new file mode 100644 index 0000000000..02fe969e21 --- /dev/null +++ b/src/data/cloud/tts.ts @@ -0,0 +1,70 @@ +import { caseInsensitiveStringCompare } from "../../common/string/compare"; +import { LocalizeFunc } from "../../common/translations/localize"; +import { translationMetadata } from "../../resources/translations-metadata"; +import { HomeAssistant } from "../../types"; + +export interface CloudTTSInfo { + languages: Array<[string, string]>; +} + +export const getCloudTTSInfo = (hass: HomeAssistant) => + hass.callWS({ type: "cloud/tts/info" }); + +export const getCloudTtsLanguages = (info?: CloudTTSInfo) => { + const languages: Array<[string, string]> = []; + + if (!info) { + return languages; + } + + const seen = new Set(); + for (const [lang] of info.languages) { + if (seen.has(lang)) { + continue; + } + seen.add(lang); + + let label = lang; + + if (lang in translationMetadata.translations) { + label = translationMetadata.translations[lang].nativeName; + } else { + const [langFamily, dialect] = lang.split("-"); + if (langFamily in translationMetadata.translations) { + label = `${translationMetadata.translations[langFamily].nativeName}`; + + if (langFamily.toLowerCase() !== dialect.toLowerCase()) { + label += ` (${dialect})`; + } + } + } + + languages.push([lang, label]); + } + return languages.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1])); +}; + +export const getCloudTtsSupportedGenders = ( + language: string, + info: CloudTTSInfo | undefined, + localize: LocalizeFunc +) => { + const genders: Array<[string, string]> = []; + + if (!info) { + return genders; + } + + for (const [curLang, gender] of info.languages) { + if (curLang === language) { + genders.push([ + gender, + localize(`ui.panel.media-browser.tts.gender_${gender}`) || + localize(`ui.panel.config.cloud.account.tts.${gender}`) || + gender, + ]); + } + } + + return genders.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1])); +}; diff --git a/src/data/tts.ts b/src/data/tts.ts index e4469ffb24..f4d0d1346b 100644 --- a/src/data/tts.ts +++ b/src/data/tts.ts @@ -10,3 +10,11 @@ export const convertTextToSpeech = ( options?: Record; } ) => hass.callApi<{ url: string; path: string }>("POST", "tts_get_url", data); + +const TTS_MEDIA_SOURCE_PREFIX = "media-source://tts/"; + +export const isTTSMediaSource = (mediaContentId: string) => + mediaContentId.startsWith(TTS_MEDIA_SOURCE_PREFIX); + +export const getProviderFromTTSMediaSource = (mediaContentId: string) => + mediaContentId.substring(TTS_MEDIA_SOURCE_PREFIX.length); diff --git a/src/panels/config/cloud/account/cloud-tts-pref.ts b/src/panels/config/cloud/account/cloud-tts-pref.ts index 95e68745f2..b92a9e45d7 100644 --- a/src/panels/config/cloud/account/cloud-tts-pref.ts +++ b/src/panels/config/cloud/account/cloud-tts-pref.ts @@ -5,18 +5,17 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { caseInsensitiveStringCompare } from "../../../../common/string/compare"; import "../../../../components/ha-card"; import "../../../../components/ha-svg-icon"; import "../../../../components/ha-switch"; +import { CloudStatusLoggedIn, updateCloudPref } from "../../../../data/cloud"; import { - CloudStatusLoggedIn, CloudTTSInfo, getCloudTTSInfo, - updateCloudPref, -} from "../../../../data/cloud"; + getCloudTtsLanguages, + getCloudTtsSupportedGenders, +} from "../../../../data/cloud/tts"; import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box"; -import { translationMetadata } from "../../../../resources/translations-metadata"; import type { HomeAssistant } from "../../../../types"; import { showTryTtsDialog } from "./show-dialog-cloud-tts-try"; @@ -37,7 +36,11 @@ export class CloudTTSPref extends LitElement { const languages = this.getLanguages(this.ttsInfo); const defaultVoice = this.cloudStatus.prefs.tts_default_voice; - const genders = this.getSupportedGenders(defaultVoice[0], this.ttsInfo); + const genders = this.getSupportedGenders( + defaultVoice[0], + this.ttsInfo, + this.hass.localize + ); return html` { - const languages: Array<[string, string]> = []; + private getLanguages = memoizeOne(getCloudTtsLanguages); - if (!info) { - return languages; - } - - const seen = new Set(); - for (const [lang] of info.languages) { - if (seen.has(lang)) { - continue; - } - seen.add(lang); - - let label = lang; - - if (lang in translationMetadata.translations) { - label = translationMetadata.translations[lang].nativeName; - } else { - const [langFamily, dialect] = lang.split("-"); - if (langFamily in translationMetadata.translations) { - label = `${translationMetadata.translations[langFamily].nativeName}`; - - if (langFamily.toLowerCase() !== dialect.toLowerCase()) { - label += ` (${dialect})`; - } - } - } - - languages.push([lang, label]); - } - return languages.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1])); - }); - - private getSupportedGenders = memoizeOne( - (language: string, info?: CloudTTSInfo) => { - const genders: Array<[string, string]> = []; - - if (!info) { - return genders; - } - - for (const [curLang, gender] of info.languages) { - if (curLang === language) { - genders.push([ - gender, - this.hass.localize(`ui.panel.config.cloud.account.tts.${gender}`) || - gender, - ]); - } - } - - return genders.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1])); - } - ); + private getSupportedGenders = memoizeOne(getCloudTtsSupportedGenders); private _openTryDialog() { showTryTtsDialog(this, { @@ -170,7 +121,11 @@ export class CloudTTSPref extends LitElement { const language = ev.target.value; const curGender = this.cloudStatus!.prefs.tts_default_voice[1]; - const genders = this.getSupportedGenders(language, this.ttsInfo); + const genders = this.getSupportedGenders( + language, + this.ttsInfo, + this.hass.localize + ); const newGender = genders.find((item) => item[0] === curGender) ? curGender : genders[0][0]; diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index f3b541f7a3..0ab9f870c4 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -219,18 +219,18 @@ class PanelMediaBrowser extends LitElement { return; } - if (item.media_content_type.startsWith("audio/")) { + const resolvedUrl = await resolveMediaSource( + this.hass, + item.media_content_id + ); + + if (resolvedUrl.mime_type.startsWith("audio/")) { await this.shadowRoot!.querySelector("ha-bar-media-player")!.playItem( item ); return; } - const resolvedUrl: any = await resolveMediaSource( - this.hass, - item.media_content_id - ); - showWebBrowserPlayMediaDialog(this, { sourceUrl: resolvedUrl.url, sourceType: resolvedUrl.mime_type, @@ -270,10 +270,6 @@ class PanelMediaBrowser extends LitElement { return [ haStyle, css` - :host { - --mdc-theme-primary: var(--app-header-text-color); - } - ha-media-player-browse { height: calc(100vh - (100px + var(--header-height))); } diff --git a/src/translations/en.json b/src/translations/en.json index cafd39ed58..5bcc4ce4e8 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3689,6 +3689,18 @@ "media-browser": { "error": { "player_not_exist": "Media player {name} does not exist" + }, + "tts": { + "message": "Message", + "example_message": "Hello {name}, you can play any text on any supported media player!", + "language": "Language", + "gender": "Gender", + "gender_male": "Male", + "gender_female": "Female", + "action_play": "Say", + "action_pick": "Select", + "set_as_default": "Set as default options", + "faild_to_store_defaults": "Failed to store defaults: {error}" } }, "map": { From 2ac0ad1d9882c4adf671d4d40fc590d5b34d7ccb Mon Sep 17 00:00:00 2001 From: kpine Date: Mon, 14 Feb 2022 08:06:03 -0800 Subject: [PATCH 069/174] Omit Device info and actions for connected controller nodes (#11673) --- src/data/zwave_js.ts | 1 + .../zwave_js/ha-device-actions-zwave_js.ts | 74 +++++++++----- .../zwave_js/ha-device-info-zwave_js.ts | 98 ++++++++++--------- 3 files changed, 103 insertions(+), 70 deletions(-) diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index 4940cd2d92..ab02e4acf8 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -126,6 +126,7 @@ export interface ZWaveJSNodeStatus { is_routing: boolean | null; zwave_plus_version: number | null; highest_security_class: SecurityClass | null; + is_controller_node: boolean; } export interface ZwaveJSNodeMetadata { diff --git a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts index 2cd06cbea6..5d9a4205e6 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-actions-zwave_js.ts @@ -10,8 +10,10 @@ import { import { customElement, property, state } from "lit/decorators"; import { DeviceRegistryEntry } from "../../../../../../data/device_registry"; import { + fetchZwaveNodeStatus, getZwaveJsIdentifiersFromDevice, ZWaveJSNodeIdentifiers, + ZWaveJSNodeStatus, } from "../../../../../../data/zwave_js"; import { haStyle } from "../../../../../../resources/styles"; import { HomeAssistant } from "../../../../../../types"; @@ -29,43 +31,67 @@ export class HaDeviceActionsZWaveJS extends LitElement { @state() private _nodeId?: number; + @state() private _node?: ZWaveJSNodeStatus; + protected updated(changedProperties: PropertyValues) { if (changedProperties.has("device")) { - this._entryId = this.device.config_entries[0]; - const identifiers: ZWaveJSNodeIdentifiers | undefined = getZwaveJsIdentifiersFromDevice(this.device); if (!identifiers) { return; } this._nodeId = identifiers.node_id; + this._entryId = this.device.config_entries[0]; + + this._fetchNodeDetails(); } } + protected async _fetchNodeDetails() { + if (!this._nodeId || !this._entryId) { + return; + } + + this._node = await fetchZwaveNodeStatus( + this.hass, + this._entryId, + this._nodeId + ); + } + protected render(): TemplateResult { + if (!this._node) { + return html``; + } return html` - - - ${this.hass.localize( - "ui.panel.config.zwave_js.device_info.device_config" - )} - - - - ${this.hass.localize( - "ui.panel.config.zwave_js.device_info.reinterview_device" - )} - - - ${this.hass.localize("ui.panel.config.zwave_js.device_info.heal_node")} - - - ${this.hass.localize( - "ui.panel.config.zwave_js.device_info.remove_failed" - )} - + ${!this._node.is_controller_node + ? html` + + + ${this.hass.localize( + "ui.panel.config.zwave_js.device_info.device_config" + )} + + + + ${this.hass.localize( + "ui.panel.config.zwave_js.device_info.reinterview_device" + )} + + + ${this.hass.localize( + "ui.panel.config.zwave_js.device_info.heal_node" + )} + + + ${this.hass.localize( + "ui.panel.config.zwave_js.device_info.remove_failed" + )} + + ` + : ""} `; } diff --git a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-info-zwave_js.ts b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-info-zwave_js.ts index dc24356fff..32a732662f 100644 --- a/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-info-zwave_js.ts +++ b/src/panels/config/devices/device-detail/integration-elements/zwave_js/ha-device-info-zwave_js.ts @@ -103,52 +103,58 @@ export class HaDeviceInfoZWaveJS extends LitElement { ${this.hass.localize("ui.panel.config.zwave_js.common.node_id")}: ${this._node.node_id}
    -
    - ${this.hass.localize( - "ui.panel.config.zwave_js.device_info.node_status" - )}: - ${this.hass.localize( - `ui.panel.config.zwave_js.node_status.${ - nodeStatus[this._node.status] - }` - )} -
    -
    - ${this.hass.localize( - "ui.panel.config.zwave_js.device_info.node_ready" - )}: - ${this._node.ready - ? this.hass.localize("ui.common.yes") - : this.hass.localize("ui.common.no")} -
    -
    - ${this.hass.localize( - "ui.panel.config.zwave_js.device_info.highest_security" - )}: - ${this._node.highest_security_class !== null - ? this.hass.localize( - `ui.panel.config.zwave_js.security_classes.${ - SecurityClass[this._node.highest_security_class] - }.title` - ) - : this._node.is_secure === false - ? this.hass.localize( - "ui.panel.config.zwave_js.security_classes.none.title" - ) - : this.hass.localize("ui.panel.config.zwave_js.device_info.unknown")} -
    -
    - ${this.hass.localize( - "ui.panel.config.zwave_js.device_info.zwave_plus" - )}: - ${this._node.zwave_plus_version - ? this.hass.localize( - "ui.panel.config.zwave_js.device_info.zwave_plus_version", - "version", - this._node.zwave_plus_version - ) - : this.hass.localize("ui.common.no")} -
    + ${!this._node.is_controller_node + ? html` +
    + ${this.hass.localize( + "ui.panel.config.zwave_js.device_info.node_status" + )}: + ${this.hass.localize( + `ui.panel.config.zwave_js.node_status.${ + nodeStatus[this._node.status] + }` + )} +
    +
    + ${this.hass.localize( + "ui.panel.config.zwave_js.device_info.node_ready" + )}: + ${this._node.ready + ? this.hass.localize("ui.common.yes") + : this.hass.localize("ui.common.no")} +
    +
    + ${this.hass.localize( + "ui.panel.config.zwave_js.device_info.highest_security" + )}: + ${this._node.highest_security_class !== null + ? this.hass.localize( + `ui.panel.config.zwave_js.security_classes.${ + SecurityClass[this._node.highest_security_class] + }.title` + ) + : this._node.is_secure === false + ? this.hass.localize( + "ui.panel.config.zwave_js.security_classes.none.title" + ) + : this.hass.localize( + "ui.panel.config.zwave_js.device_info.unknown" + )} +
    +
    + ${this.hass.localize( + "ui.panel.config.zwave_js.device_info.zwave_plus" + )}: + ${this._node.zwave_plus_version + ? this.hass.localize( + "ui.panel.config.zwave_js.device_info.zwave_plus_version", + "version", + this._node.zwave_plus_version + ) + : this.hass.localize("ui.common.no")} +
    + ` + : ""} `; } From 460b9003fccda2bd6a9dbb8ec16b4ed525873afb Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 14 Feb 2022 11:27:29 -0600 Subject: [PATCH 070/174] Script Editor to Ha Form (#11601) Co-authored-by: Bram Kragten --- build-scripts/bundle.js | 4 + gallery/src/pages/components/ha-form.ts | 6 + gallery/src/pages/components/ha-selector.ts | 1 + src/components/ha-form/ha-form.ts | 22 +- .../ha-selector/ha-selector-icon.ts | 39 ++ .../ha-selector/ha-selector-select.ts | 16 +- src/components/ha-selector/ha-selector.ts | 4 + src/data/selector.ts | 15 +- src/panels/config/script/ha-script-editor.ts | 425 ++++++++++-------- 9 files changed, 339 insertions(+), 193 deletions(-) create mode 100644 src/components/ha-selector/ha-selector-icon.ts diff --git a/build-scripts/bundle.js b/build-scripts/bundle.js index 61dc06f74c..2205196266 100644 --- a/build-scripts/bundle.js +++ b/build-scripts/bundle.js @@ -33,6 +33,10 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) => require.resolve( path.resolve(paths.polymer_dir, "src/components/ha-icon.ts") ), + isHassioBuild && + require.resolve( + path.resolve(paths.polymer_dir, "src/components/ha-icon-picker.ts") + ), ].filter(Boolean); module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index a49cf9f131..dac1320a37 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -61,6 +61,12 @@ const SCHEMAS: { select: { options: ["Everyone Home", "Some Home", "All gone"] }, }, }, + { + name: "icon", + selector: { + icon: {}, + }, + }, ], }, { diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index b4fc18f118..60890a3e4d 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -72,6 +72,7 @@ const SCHEMAS: { name: "Select", selector: { select: { options: ["Option 1", "Option 2"] } }, }, + icon: { name: "Icon", selector: { icon: {} } }, }, }, ]; diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index af97f2dffb..610d2d2de5 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -32,7 +32,12 @@ export class HaForm extends LitElement implements HaFormElement { @property() public computeError?: (schema: HaFormSchema, error) => string; - @property() public computeLabel?: (schema: HaFormSchema) => string; + @property() public computeLabel?: ( + schema: HaFormSchema, + data?: HaFormDataContainer + ) => string; + + @property() public computeHelper?: (schema: HaFormSchema) => string; public focus() { const root = this.shadowRoot?.querySelector(".root"); @@ -71,6 +76,7 @@ export class HaForm extends LitElement implements HaFormElement { : ""} ${this.schema.map((item) => { const error = getValue(this.error, item); + return html` ${error ? html` @@ -85,14 +91,15 @@ export class HaForm extends LitElement implements HaFormElement { .hass=${this.hass} .selector=${item.selector} .value=${getValue(this.data, item)} - .label=${this._computeLabel(item)} + .label=${this._computeLabel(item, this.data)} .disabled=${this.disabled} + .helper=${this._computeHelper(item)} .required=${item.required || false} >` : dynamicElement(`ha-form-${item.type}`, { schema: item, data: getValue(this.data, item), - label: this._computeLabel(item), + label: this._computeLabel(item, this.data), disabled: this.disabled, })} `; @@ -107,6 +114,7 @@ export class HaForm extends LitElement implements HaFormElement { root.addEventListener("value-changed", (ev) => { ev.stopPropagation(); const schema = (ev.target as HaFormElement).schema as HaFormSchema; + fireEvent(this, "value-changed", { value: { ...this.data, [schema.name]: ev.detail.value }, }); @@ -114,14 +122,18 @@ export class HaForm extends LitElement implements HaFormElement { return root; } - private _computeLabel(schema: HaFormSchema) { + private _computeLabel(schema: HaFormSchema, data: HaFormDataContainer) { return this.computeLabel - ? this.computeLabel(schema) + ? this.computeLabel(schema, data) : schema ? schema.name : ""; } + private _computeHelper(schema: HaFormSchema) { + return this.computeHelper ? this.computeHelper(schema) : ""; + } + private _computeError(error, schema: HaFormSchema | HaFormSchema[]) { return this.computeError ? this.computeError(error, schema) : error; } diff --git a/src/components/ha-selector/ha-selector-icon.ts b/src/components/ha-selector/ha-selector-icon.ts new file mode 100644 index 0000000000..046a612d5e --- /dev/null +++ b/src/components/ha-selector/ha-selector-icon.ts @@ -0,0 +1,39 @@ +import "../ha-icon-picker"; +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import { HomeAssistant } from "../../types"; +import { IconSelector } from "../../data/selector"; +import { fireEvent } from "../../common/dom/fire_event"; + +@customElement("ha-selector-icon") +export class HaIconSelector extends LitElement { + @property() public hass!: HomeAssistant; + + @property() public selector!: IconSelector; + + @property() public value?: string; + + @property() public label?: string; + + @property({ type: Boolean, reflect: true }) public disabled = false; + + protected render() { + return html` + + `; + } + + private _valueChanged(ev: CustomEvent) { + fireEvent(this, "value-changed", { value: ev.detail.value }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-icon": HaIconSelector; + } +} diff --git a/src/components/ha-selector/ha-selector-select.ts b/src/components/ha-selector/ha-selector-select.ts index eaaca5a6ea..5c1cc3e91b 100644 --- a/src/components/ha-selector/ha-selector-select.ts +++ b/src/components/ha-selector/ha-selector-select.ts @@ -2,7 +2,7 @@ import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { stopPropagation } from "../../common/dom/stop_propagation"; -import { SelectSelector } from "../../data/selector"; +import { SelectOption, SelectSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; import "@material/mwc-select/mwc-select"; import "@material/mwc-list/mwc-list-item"; @@ -17,6 +17,8 @@ export class HaSelectSelector extends LitElement { @property() public label?: string; + @property() public helper?: string; + @property({ type: Boolean }) public disabled = false; protected render() { @@ -25,15 +27,17 @@ export class HaSelectSelector extends LitElement { naturalMenuWidth .label=${this.label} .value=${this.value} + .helper=${this.helper} .disabled=${this.disabled} @closed=${stopPropagation} @selected=${this._valueChanged} > - ${this.selector.select.options.map( - (item: string) => html` - ${item} - ` - )} + ${this.selector.select.options.map((item: string | SelectOption) => { + const value = typeof item === "object" ? item.value : item; + const label = typeof item === "object" ? item.label : item; + + return html`${label}`; + })} `; } diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 145991228f..53aaa6a841 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -17,6 +17,7 @@ import "./ha-selector-select"; import "./ha-selector-target"; import "./ha-selector-text"; import "./ha-selector-time"; +import "./ha-selector-icon"; @customElement("ha-selector") export class HaSelector extends LitElement { @@ -28,6 +29,8 @@ export class HaSelector extends LitElement { @property() public label?: string; + @property() public helper?: string; + @property() public placeholder?: any; @property({ type: Boolean }) public disabled = false; @@ -52,6 +55,7 @@ export class HaSelector extends LitElement { placeholder: this.placeholder, disabled: this.disabled, required: this.required, + helper: this.helper, id: "selector", })} `; diff --git a/src/data/selector.ts b/src/data/selector.ts index 4ed8f9dd7b..458a84235a 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -12,7 +12,8 @@ export type Selector = | ActionSelector | StringSelector | ObjectSelector - | SelectSelector; + | SelectSelector + | IconSelector; export interface EntitySelector { entity: { @@ -133,8 +134,18 @@ export interface ObjectSelector { object: {}; } +export interface SelectOption { + value: string; + label: string; +} + export interface SelectSelector { select: { - options: string[]; + options: string[] | SelectOption[]; }; } + +export interface IconSelector { + // eslint-disable-next-line @typescript-eslint/ban-types + icon: {}; +} diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 75f57c0c84..c4ce09a6b0 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -1,4 +1,4 @@ -import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; +import type { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import { mdiCheck, @@ -9,8 +9,6 @@ import { } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; -import { PaperListboxElement } from "@polymer/paper-listbox"; import { css, CSSResultGroup, @@ -21,6 +19,7 @@ import { } from "lit"; import { property, state, query } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; +import memoizeOne from "memoize-one"; import { computeObjectId } from "../../../common/entity/compute_object_id"; import { navigate } from "../../../common/navigate"; import { slugify } from "../../../common/string/slugify"; @@ -29,8 +28,12 @@ import { copyToClipboard } from "../../../common/util/copy-clipboard"; import "../../../components/ha-button-menu"; import "../../../components/ha-card"; import "../../../components/ha-fab"; +import type { + HaFormDataContainer, + HaFormSchema, + HaFormSelector, +} from "../../../components/ha-form/types"; import "../../../components/ha-icon-button"; -import "../../../components/ha-icon-picker"; import "../../../components/ha-svg-icon"; import "../../../components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../components/ha-yaml-editor"; @@ -49,10 +52,9 @@ import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box import "../../../layouts/ha-app-layout"; import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin"; import { haStyle } from "../../../resources/styles"; -import { HomeAssistant, Route } from "../../../types"; +import type { HomeAssistant, Route } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import { showToast } from "../../../util/toast"; -import "../automation/action/ha-automation-action"; import { HaDeviceAction } from "../automation/action/types/ha-automation-action-device_id"; import "../ha-config-section"; import { configSections } from "../ha-panel-config"; @@ -83,7 +85,91 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { @query("ha-yaml-editor", true) private _editor?: HaYamlEditor; + private _schema = memoizeOne( + (hasID: boolean, useBluePrint?: boolean, currentMode?: string) => { + const schema: HaFormSchema[] = [ + { + name: "alias", + selector: { + text: { + type: "text", + }, + }, + }, + { + name: "icon", + selector: { + icon: {}, + }, + }, + ]; + + if (!hasID) { + schema.push({ + name: "id", + selector: { + text: {}, + }, + }); + } + + if (!useBluePrint) { + schema.push({ + name: "mode", + selector: { + select: { + options: MODES.map((mode) => ({ + label: ` + ${ + this.hass.localize( + `ui.panel.config.script.editor.modes.${mode}` + ) || mode + } + `, + value: mode, + })), + }, + }, + }); + } + + if (currentMode && MODES_MAX.includes(currentMode)) { + schema.push({ + name: "max", + selector: { + text: { + type: "number", + }, + }, + }); + } + + return schema; + } + ); + protected render(): TemplateResult { + if (!this._config) { + return html``; + } + + const schema = this._schema( + !!this.scriptEntityId, + "use_blueprint" in this._config, + this._config.mode + ); + + const data = { + mode: MODES[0], + max: + this._config.mode && MODES_MAX.includes(this._config.mode) + ? 10 + : undefined, + icon: undefined, + ...this._config, + id: this._entityId, + }; + return html` ${this.hass.localize("ui.panel.config.automation.editor.edit_ui")} ${this._mode === "gui" - ? html` ` + ? html` + + ` : ``} ${this.hass.localize("ui.panel.config.automation.editor.edit_yaml")} ${this._mode === "yaml" - ? html` ` + ? html` + + ` : ``} @@ -173,16 +263,14 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { ${this.narrow - ? html` ${this._config?.alias} ` + ? html`${this._config?.alias}` : ""}
    - ${this._errors - ? html`
    ${this._errors}
    ` - : ""} + ${this._errors ? html`
    ${this._errors}
    ` : ""} ${this._mode === "gui" ? html`
    - - - - - ${!this.scriptEntityId - ? html` - ` - : ""} - ${"use_blueprint" in this._config - ? "" - : html`

    - ${this.hass.localize( - "ui.panel.config.script.editor.modes.description", - "documentation_link", - html`${this.hass.localize( - "ui.panel.config.script.editor.modes.documentation" - )}` - )} -

    - - - ${MODES.map( - (mode) => html` - - ${this.hass.localize( - `ui.panel.config.script.editor.modes.${mode}` - ) || mode} - - ` - )} - - - ${this._config.mode && - MODES_MAX.includes(this._config.mode) - ? html` - ` - : html``} `} + >
    ${this.scriptEntityId ? html` @@ -328,47 +335,51 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { ${"use_blueprint" in this._config - ? html`` - : html` - - ${this.hass.localize( - "ui.panel.config.script.editor.sequence" - )} - - -

    - ${this.hass.localize( - "ui.panel.config.script.editor.sequence_sentence" - )} -

    - - ${this.hass.localize( - "ui.panel.config.script.editor.link_available_actions" - )} - -
    - -
    `} + .narrow=${this.narrow} + .isWide=${this.isWide} + .config=${this._config} + @value-changed=${this._configChanged} + > + ` + : html` + + + ${this.hass.localize( + "ui.panel.config.script.editor.sequence" + )} + + +

    + ${this.hass.localize( + "ui.panel.config.script.editor.sequence_sentence" + )} +

    + + ${this.hass.localize( + "ui.panel.config.script.editor.link_available_actions" + )} + +
    + +
    + `} ` : ""}
    @@ -495,7 +506,50 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { } } - private async _runScript(ev) { + private _computeLabelCallback = ( + schema: HaFormSelector, + data: HaFormDataContainer + ): string => { + switch (schema.name) { + case "mode": + return this.hass.localize("ui.panel.config.script.editor.modes.label"); + case "max": + return this.hass.localize( + `ui.panel.config.script.editor.max.${data.mode}` + ); + default: + return this.hass.localize( + `ui.panel.config.script.editor.${schema.name}` + ); + } + }; + + private _computeHelperCallback = ( + schema: HaFormSelector + ): string | undefined => { + if (schema.name === "mode") { + return this.hass.localize( + "ui.panel.config.script.editor.modes.description", + "documentation_link", + html` + ${this.hass.localize( + "ui.panel.config.script.editor.modes.documentation" + )} + ` + ); + } + return undefined; + }; + + private async _runScript(ev: CustomEvent) { ev.stopPropagation(); await triggerScript(this.hass, this.scriptEntityId as string); showToast(this, { @@ -507,14 +561,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { }); } - private _modeChanged(ev: CustomEvent) { - const mode = ((ev.target as PaperListboxElement)?.selectedItem as any) - ?.mode; - - if (mode === this._config!.mode) { - return; - } - + private _modeChanged(mode) { this._config = { ...this._config!, mode }; if (!MODES_MAX.includes(mode)) { delete this._config.max; @@ -522,23 +569,23 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { this._dirty = true; } - private _aliasChanged(ev: CustomEvent) { + private _aliasChanged(alias: string) { if (this.scriptEntityId || this._entityId) { return; } - const aliasSlugify = slugify((ev.target as any).value); + const aliasSlugify = slugify(alias); let id = aliasSlugify; let i = 2; while (this.hass.states[`script.${id}`]) { id = `${aliasSlugify}_${i}`; i++; } + this._entityId = id; } - private _idChanged(ev: CustomEvent) { - ev.stopPropagation(); - this._entityId = (ev.target as any).value; + private _idChanged(id: string) { + this._entityId = id; if (this.hass.states[`script.${this._entityId}`]) { this._idError = true; } else { @@ -548,24 +595,39 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { private _valueChanged(ev: CustomEvent) { ev.stopPropagation(); - const target = ev.target as any; - const name = target.name; - if (!name) { - return; - } - let newVal = ev.detail.value; - if (target.type === "number") { - newVal = Number(newVal); - } - if ((this._config![name] || "") === newVal) { - return; - } - if (!newVal) { - delete this._config![name]; - this._config = { ...this._config! }; - } else { - this._config = { ...this._config!, [name]: newVal }; + const values = ev.detail.value as any; + + for (const key of Object.keys(values)) { + if (key === "sequence") { + continue; + } + + const value = values[key]; + + if (value === this._config![key]) { + continue; + } + + switch (key) { + case "id": + this._idChanged(value); + return; + case "alias": + this._aliasChanged(value); + break; + case "mode": + this._modeChanged(value); + return; + } + + if (values[key] === undefined) { + delete this._config![key]; + this._config = { ...this._config! }; + } else { + this._config = { ...this._config!, [key]: value }; + } } + this._dirty = true; } @@ -575,7 +637,10 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { } private _sequenceChanged(ev: CustomEvent): void { - this._config = { ...this._config!, sequence: ev.detail.value as Action[] }; + this._config = { + ...this._config!, + sequence: ev.detail.value as Action[], + }; this._errors = undefined; this._dirty = true; } From 523afe2f6f0564607f1cf4025974bc25279db4b3 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 14 Feb 2022 20:08:18 +0100 Subject: [PATCH 071/174] Another round of paper-dropdown -> mwc-select conversion (#11674) * Another round of paper-dropdown -> mwc-select conversion * ha-pick-language-row -> Lit * Update hui-view-editor.ts * Cleanup imports * hassio * Add explicit imports --- .../addon-view/config/hassio-addon-audio.ts | 75 ++++++------ hassio/src/dashboard/hassio-update.ts | 1 - .../datadisk/dialog-hassio-datadisk.ts | 26 ++-- hassio/src/system/hassio-supervisor-log.ts | 34 ++---- src/components/entity/ha-statistic-picker.ts | 2 - src/components/ha-paper-dropdown-menu.ts | 28 ----- .../media-player/ha-media-player-browse.ts | 1 - .../more-info/controls/more-info-light.ts | 4 - .../more-info/controls/more-info-remote.ts | 10 +- .../more-info/controls/more-info-vacuum.ts | 3 - .../condition/ha-automation-condition-row.ts | 1 - .../types/ha-automation-condition-trigger.ts | 41 +++---- .../types/ha-automation-trigger-tag.ts | 42 +++---- .../config/cloud/account/cloud-remote-pref.ts | 1 - .../config/cloud/account/cloud-webhooks.ts | 2 - .../config/cloud/register/cloud-register.ts | 3 - .../entities/entity-registry-settings.ts | 44 +++---- src/panels/config/ha-form-style.js | 4 - src/panels/config/ha-panel-config.ts | 2 - .../config/helpers/dialog-helper-detail.ts | 2 +- .../zha/zha-config-dashboard.ts | 2 - .../zwave_js/zwave_js-logs.ts | 32 ++--- .../zwave_js/zwave_js-node-config.ts | 41 +++---- .../dialog-lovelace-resource-detail.ts | 82 ++++++------- .../resources/ha-config-lovelace-resources.ts | 2 - .../hui-alarm-panel-card-editor.ts | 24 ++-- .../hui-calendar-card-editor.ts | 45 ++++--- .../hui-conditional-card-editor.ts | 59 +++++----- .../hui-entities-card-editor.ts | 2 - .../hui-generic-entity-row-editor.ts | 59 +++++----- .../hui-picture-entity-card-editor.ts | 30 ++--- .../hui-picture-glance-card-editor.ts | 30 ++--- .../config-elements/hui-sensor-card-editor.ts | 26 ++-- .../hui-statistics-graph-card-editor.ts | 58 ++++----- .../hui-header-footer-editor.ts | 1 - .../select-view/hui-dialog-select-view.ts | 67 +++++------ .../editor/view-editor/hui-view-editor.ts | 35 +++--- .../view-editor/hui-view-visibility-editor.ts | 13 +- src/panels/profile/ha-panel-profile.ts | 2 - src/panels/profile/ha-pick-dashboard-row.ts | 53 ++++----- src/panels/profile/ha-pick-language-row.js | 111 ------------------ src/panels/profile/ha-pick-language-row.ts | 87 ++++++++++++++ .../profile/ha-pick-number-format-row.ts | 63 +++++----- src/panels/profile/ha-pick-theme-row.ts | 36 +++--- src/panels/profile/ha-pick-time-format-row.ts | 51 ++++---- 45 files changed, 590 insertions(+), 747 deletions(-) delete mode 100644 src/components/ha-paper-dropdown-menu.ts delete mode 100644 src/panels/profile/ha-pick-language-row.js create mode 100644 src/panels/profile/ha-pick-language-row.ts diff --git a/hassio/src/addon-view/config/hassio-addon-audio.ts b/hassio/src/addon-view/config/hassio-addon-audio.ts index 0851a1269b..c9629589ae 100644 --- a/hassio/src/addon-view/config/hassio-addon-audio.ts +++ b/hassio/src/addon-view/config/hassio-addon-audio.ts @@ -1,7 +1,6 @@ import "@material/mwc-button"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; +import "@material/mwc-select"; +import "@material/mwc-list/mwc-list-item"; import { css, CSSResultGroup, @@ -11,7 +10,7 @@ import { TemplateResult, } from "lit"; import { customElement, property, state } from "lit/decorators"; -import "web-animations-js/web-animations-next-lite.min"; +import { stopPropagation } from "../../../../src/common/dom/stop_propagation"; import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-card"; @@ -58,48 +57,44 @@ class HassioAddonAudio extends LitElement { ? html`${this._error}` : ""} - - - ${this._inputDevices && - this._inputDevices.map( - (item) => html` - - ${item.name} - - ` - )} - - - html` + + ${item.name} + + ` + )} + + - - ${this._outputDevices && - this._outputDevices.map( - (item) => html` - ${item.name} - ` - )} - - + ${this._outputDevices && + this._outputDevices.map( + (item) => html` + ${item.name} + ` + )} +
    @@ -138,12 +133,12 @@ class HassioAddonAudio extends LitElement { } private _setInputDevice(ev): void { - const device = ev.detail.item.getAttribute("device"); + const device = ev.target.value; this._selectedInput = device; } private _setOutputDevice(ev): void { - const device = ev.detail.item.getAttribute("device"); + const device = ev.target.value; this._selectedOutput = device; } diff --git a/hassio/src/dashboard/hassio-update.ts b/hassio/src/dashboard/hassio-update.ts index 2f8321344d..fbdb6f9aa1 100644 --- a/hassio/src/dashboard/hassio-update.ts +++ b/hassio/src/dashboard/hassio-update.ts @@ -148,7 +148,6 @@ export class HassioUpdate extends LitElement { } ha-settings-row { padding: 0; - --paper-item-body-two-line-min-height: 32px; } `, ]; diff --git a/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts b/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts index f91c6362f7..d903651898 100644 --- a/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts +++ b/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts @@ -1,6 +1,5 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -90,18 +89,19 @@ class HassioDatadiskDialog extends LitElement { )}

    - - - ${this.devices.map( - (device) => html`${device}` - )} - - + ${this.devices.map( + (device) => + html`${device}` + )} + ` : this.devices === undefined ? this.dialogParams.supervisor.localize( @@ -130,8 +130,8 @@ class HassioDatadiskDialog extends LitElement { `; } - private _select_device(event) { - this.selectedDevice = event.detail.value; + private _select_device(ev) { + this.selectedDevice = ev.target.value; } private async _moveDatadisk() { diff --git a/hassio/src/system/hassio-supervisor-log.ts b/hassio/src/system/hassio-supervisor-log.ts index 09f518703c..6000b5b9aa 100644 --- a/hassio/src/system/hassio-supervisor-log.ts +++ b/hassio/src/system/hassio-supervisor-log.ts @@ -1,7 +1,4 @@ import "@material/mwc-button"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import "../../../src/components/buttons/ha-progress-button"; @@ -73,24 +70,19 @@ class HassioSupervisorLog extends LitElement { : ""} ${this.hass.userData?.showAdvanced ? html` - - - ${logProviders.map( - (provider) => html` - - ${provider.name} - - ` - )} - - + ${logProviders.map( + (provider) => html` + + ${provider.name} + + ` + )} + ` : ""} @@ -110,7 +102,7 @@ class HassioSupervisorLog extends LitElement { } private async _setLogProvider(ev): Promise { - const provider = ev.detail.item.getAttribute("provider"); + const provider = ev.target.value; this._selectedLogProvider = provider; this._loadData(); } @@ -153,7 +145,7 @@ class HassioSupervisorLog extends LitElement { pre { white-space: pre-wrap; } - paper-dropdown-menu { + mwc-select { padding: 0 2%; width: 96%; } diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index 6fc0f34a1f..a3965023a0 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -1,6 +1,4 @@ import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-icon-item"; -import "@polymer/paper-item/paper-item-body"; import { HassEntity } from "home-assistant-js-websocket"; import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; diff --git a/src/components/ha-paper-dropdown-menu.ts b/src/components/ha-paper-dropdown-menu.ts deleted file mode 100644 index 72f2f6b15c..0000000000 --- a/src/components/ha-paper-dropdown-menu.ts +++ /dev/null @@ -1,28 +0,0 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import { PolymerElement } from "@polymer/polymer"; -import { Constructor } from "../types"; - -const paperDropdownClass = customElements.get( - "paper-dropdown-menu" -) as Constructor; - -// patches paper drop down to properly support RTL - https://github.com/PolymerElements/paper-dropdown-menu/issues/183 -export class HaPaperDropdownClass extends paperDropdownClass { - public ready() { - super.ready(); - // wait to check for direction since otherwise direction is wrong even though top level is RTL - setTimeout(() => { - if (window.getComputedStyle(this).direction === "rtl") { - this.style.textAlign = "right"; - } - }, 100); - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-paper-dropdown-menu": HaPaperDropdownClass; - } -} - -customElements.define("ha-paper-dropdown-menu", HaPaperDropdownClass); diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 27e2274169..6117286c59 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -2,7 +2,6 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list-item"; import { mdiPlay, mdiPlus } from "@mdi/js"; -import "@polymer/paper-item/paper-item"; import "@polymer/paper-tooltip/paper-tooltip"; import { css, diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index 12c28a045b..08b53c1825 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -615,10 +615,6 @@ class MoreInfoLight extends LitElement { color: var(--secondary-text-color); } - paper-item { - cursor: pointer; - } - hr { border-color: var(--divider-color); border-bottom: none; diff --git a/src/dialogs/more-info/controls/more-info-remote.ts b/src/dialogs/more-info/controls/more-info-remote.ts index d952dfbeab..dbdc2fd0c9 100644 --- a/src/dialogs/more-info/controls/more-info-remote.ts +++ b/src/dialogs/more-info/controls/more-info-remote.ts @@ -1,4 +1,4 @@ -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-attributes"; @@ -66,14 +66,6 @@ class MoreInfoRemote extends LitElement { activity: newVal, }); } - - static get styles(): CSSResultGroup { - return css` - paper-item { - cursor: pointer; - } - `; - } } declare global { diff --git a/src/dialogs/more-info/controls/more-info-vacuum.ts b/src/dialogs/more-info/controls/more-info-vacuum.ts index 17eaa0a534..44ef096299 100644 --- a/src/dialogs/more-info/controls/more-info-vacuum.ts +++ b/src/dialogs/more-info/controls/more-info-vacuum.ts @@ -241,9 +241,6 @@ class MoreInfoVacuum extends LitElement { .status-subtitle { color: var(--secondary-text-color); } - paper-item { - cursor: pointer; - } .flex-horizontal { display: flex; flex-direction: row; diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index b36118b680..bee7358190 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -1,7 +1,6 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import { mdiDotsVertical } from "@mdi/js"; -import "@polymer/paper-item/paper-item"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts index 14adc8d117..ea867147bd 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts @@ -1,4 +1,5 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -48,41 +49,35 @@ export class HaTriggerCondition extends LitElement { "ui.panel.config.automation.editor.conditions.type.trigger.no_triggers" ); } - return html` - - ${ensureArray(this._triggers).map((trigger) => - trigger.id - ? html` - - ${trigger.id} - - ` - : "" - )} - - `; + ${ensureArray(this._triggers).map((trigger) => + trigger.id + ? html` + + ${trigger.id} + + ` + : "" + )} + `; } private _automationUpdated(config?: AutomationConfig) { this._triggers = config?.trigger; } - private _triggerPicked(ev: CustomEvent) { + private _triggerPicked(ev) { ev.stopPropagation(); - if (!ev.detail.value) { + if (!ev.target.value) { return; } - const newTrigger = ev.detail.value.dataset.triggerId; + const newTrigger = ev.target.value; if (this.condition.id === newTrigger) { return; } diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts index ce891e3a57..90772dccfe 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts @@ -1,13 +1,14 @@ +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select"; import "@polymer/paper-input/paper-input"; import { html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import { caseInsensitiveStringCompare } from "../../../../../common/string/compare"; import { TagTrigger } from "../../../../../data/automation"; import { fetchTags, Tag } from "../../../../../data/tag"; import { HomeAssistant } from "../../../../../types"; import { TriggerElement } from "../ha-automation-trigger-row"; -import "../../../../../components/ha-paper-dropdown-menu"; -import { caseInsensitiveStringCompare } from "../../../../../common/string/compare"; @customElement("ha-automation-trigger-tag") export class HaTagTrigger extends LitElement implements TriggerElement { @@ -29,27 +30,22 @@ export class HaTagTrigger extends LitElement implements TriggerElement { protected render() { const { tag_id } = this.trigger; return html` - - - ${this._tags.map( - (tag) => html` - - ${tag.name || tag.id} - - ` - )} - - + ${this._tags.map( + (tag) => html` + + ${tag.name || tag.id} + + ` + )} + `; } @@ -64,8 +60,14 @@ export class HaTagTrigger extends LitElement implements TriggerElement { fireEvent(this, "value-changed", { value: { ...this.trigger, - tag_id: ev.detail.item.tag.id, + tag_id: ev.target.value, }, }); } } + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-trigger-tag": HaTagTrigger; + } +} diff --git a/src/panels/config/cloud/account/cloud-remote-pref.ts b/src/panels/config/cloud/account/cloud-remote-pref.ts index aeadcf80ed..2f1eb91417 100644 --- a/src/panels/config/cloud/account/cloud-remote-pref.ts +++ b/src/panels/config/cloud/account/cloud-remote-pref.ts @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "@polymer/paper-item/paper-item-body"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; diff --git a/src/panels/config/cloud/account/cloud-webhooks.ts b/src/panels/config/cloud/account/cloud-webhooks.ts index 6f31ea5b52..a18e9e038f 100644 --- a/src/panels/config/cloud/account/cloud-webhooks.ts +++ b/src/panels/config/cloud/account/cloud-webhooks.ts @@ -1,5 +1,3 @@ -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../../../common/config/is_component_loaded"; diff --git a/src/panels/config/cloud/register/cloud-register.ts b/src/panels/config/cloud/register/cloud-register.ts index 58dd7e60ed..11a0c8d243 100644 --- a/src/panels/config/cloud/register/cloud-register.ts +++ b/src/panels/config/cloud/register/cloud-register.ts @@ -266,9 +266,6 @@ export class CloudRegister extends LitElement { a { color: var(--primary-color); } - paper-item { - cursor: pointer; - } h1 { margin: 0; } diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index a62338b472..f4c40e335f 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -1,6 +1,7 @@ import "@material/mwc-button/mwc-button"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import "@polymer/paper-input/paper-input"; -import type { PaperItemElement } from "@polymer/paper-item/paper-item"; import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, @@ -17,7 +18,6 @@ import { domainIcon } from "../../../common/entity/domain_icon"; import "../../../components/ha-area-picker"; import "../../../components/ha-expansion-panel"; import "../../../components/ha-icon-picker"; -import "../../../components/ha-paper-dropdown-menu"; import "../../../components/ha-switch"; import type { HaSwitch } from "../../../components/ha-switch"; import { @@ -158,28 +158,23 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { > ${OVERRIDE_DEVICE_CLASSES[domain]?.includes(this._deviceClass) || (domain === "cover" && this.entry.original_device_class === null) - ? html` - - ${OVERRIDE_DEVICE_CLASSES[domain].map( - (deviceClass: string) => html` - - ${this.hass.localize( - `ui.dialogs.entity_registry.editor.device_classes.${domain}.${deviceClass}` - )} - - ` - )} - - ` + ${OVERRIDE_DEVICE_CLASSES[domain].map( + (deviceClass: string) => html` + + ${this.hass.localize( + `ui.dialogs.entity_registry.editor.device_classes.${domain}.${deviceClass}` + )} + + ` + )} + ` : ""} ): void { + private _deviceClassChanged(ev): void { this._error = undefined; - if (ev.detail.value === null) { - return; - } - this._deviceClass = (ev.detail.value as any).itemValue; + this._deviceClass = ev.target.value; } private _areaPicked(ev: CustomEvent) { @@ -425,7 +417,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { padding-bottom: max(env(safe-area-inset-bottom), 8px); background-color: var(--mdc-theme-surface, #fff); } - ha-paper-dropdown-menu { + mwc-select { width: 100%; } ha-switch { diff --git a/src/panels/config/ha-form-style.js b/src/panels/config/ha-form-style.js index df3756b47b..71cf999aab 100644 --- a/src/panels/config/ha-form-style.js +++ b/src/panels/config/ha-form-style.js @@ -22,10 +22,6 @@ documentContainer.innerHTML = ` @apply --layout-vertical; @apply --layout-start; } - - paper-dropdown-menu.form-control { - margin: -9px 0; - } `; diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index 6511ac47d2..c938ca0975 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -21,8 +21,6 @@ import { mdiTools, mdiViewDashboard, } from "@mdi/js"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; import { PolymerElement } from "@polymer/polymer"; import { PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; diff --git a/src/panels/config/helpers/dialog-helper-detail.ts b/src/panels/config/helpers/dialog-helper-detail.ts index b94e865f02..eace117eb7 100644 --- a/src/panels/config/helpers/dialog-helper-detail.ts +++ b/src/panels/config/helpers/dialog-helper-detail.ts @@ -2,7 +2,7 @@ import "@material/mwc-button/mwc-button"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-tooltip/paper-tooltip"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state, query } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { dynamicElement } from "../../../common/dom/dynamic-element-directive"; diff --git a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts index 710328c269..93ec82bc7f 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-config-dashboard.ts @@ -1,7 +1,5 @@ import "@material/mwc-button/mwc-button"; import { mdiFolderMultipleOutline, mdiLan, mdiNetwork, mdiPlus } from "@mdi/js"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; import { css, CSSResultGroup, diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts index 6b0d850953..542a492e8f 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts @@ -1,6 +1,6 @@ +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { mdiDownload } from "@mdi/js"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-listbox/paper-listbox"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultArray, html, LitElement } from "lit"; import { customElement, property, query, state } from "lit/decorators"; @@ -77,26 +77,20 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
    ${this._logConfig ? html` - - - Error - Warn - Info - Verbose - Debug - Silly - - + Error + Warn + Info + Verbose + Debug + Silly + ` : ""}
    @@ -142,7 +136,7 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) { if (ev.target === undefined || this._logConfig === undefined) { return; } - const selected = ev.target.selected; + const selected = ev.target.value; if (this._logConfig.level === selected) { return; } diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts index 3dcd84f9aa..4cf083893d 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts @@ -1,13 +1,12 @@ import "@material/mwc-button/mwc-button"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { mdiCheckCircle, mdiCircle, mdiCloseCircle, mdiProgressClock, } from "@mdi/js"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, @@ -287,26 +286,20 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) { return html` ${labelAndDescription}
    - - - ${Object.entries(item.metadata.states).map( - ([key, entityState]) => html` - ${entityState} - ` - )} - - + ${Object.entries(item.metadata.states).map( + ([key, entityState]) => html` + ${entityState} + ` + )} +
    `; } @@ -351,12 +344,12 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) { if (ev.target === undefined || this._config![ev.target.key] === undefined) { return; } - if (this._config![ev.target.key].value === ev.target.selected) { + if (this._config![ev.target.key].value === ev.target.value) { return; } this.setResult(ev.target.key, undefined); - this._updateConfigParameter(ev.target, Number(ev.target.selected)); + this._updateConfigParameter(ev.target, Number(ev.target.value)); } private debouncedUpdate = debounce((target, value) => { @@ -462,7 +455,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) { } .flex .config-label, - .flex paper-dropdown-menu { + .flex mwc-select { flex: 1; } diff --git a/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts b/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts index 9c80495f75..ed04677669 100644 --- a/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts +++ b/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts @@ -1,11 +1,8 @@ import "@material/mwc-button/mwc-button"; import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { createCloseHeading } from "../../../../components/ha-dialog"; -import "../../../../components/ha-paper-dropdown-menu"; import { LovelaceResource, LovelaceResourcesMutableParams, @@ -14,6 +11,9 @@ import { PolymerChangedEvent } from "../../../../polymer-types"; import { haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; +import { stopPropagation } from "../../../../common/dom/stop_propagation"; const detectResourceType = (url: string) => { const ext = url.split(".").pop() || ""; @@ -102,48 +102,44 @@ export class DialogLovelaceResourceDetail extends LitElement { dialogInitialFocus >

    - - - - ${this.hass!.localize( - "ui.panel.config.lovelace.resources.types.module" - )} - - ${this._type === "js" - ? html` - - ${this.hass!.localize( - "ui.panel.config.lovelace.resources.types.js" - )} - - ` - : ""} - - ${this.hass!.localize( - "ui.panel.config.lovelace.resources.types.css" - )} - - ${this._type === "html" - ? html` - - ${this.hass!.localize( - "ui.panel.config.lovelace.resources.types.html" - )} - - ` - : ""} - - + + ${this.hass!.localize( + "ui.panel.config.lovelace.resources.types.module" + )} + + ${this._type === "js" + ? html` + + ${this.hass!.localize( + "ui.panel.config.lovelace.resources.types.js" + )} + + ` + : ""} + + ${this.hass!.localize( + "ui.panel.config.lovelace.resources.types.css" + )} + + ${this._type === "html" + ? html` + + ${this.hass!.localize( + "ui.panel.config.lovelace.resources.types.html" + )} + + ` + : ""} +
    ${this._params.resource @@ -185,8 +181,8 @@ export class DialogLovelaceResourceDetail extends LitElement { } } - private _typeChanged(ev: CustomEvent) { - this._type = ev.detail.item.getAttribute("type"); + private _typeChanged(ev) { + this._type = ev.target.value; } private async _updateResource() { diff --git a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts index 9c29684522..b8a9367e06 100644 --- a/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts +++ b/src/panels/config/lovelace/resources/ha-config-lovelace-resources.ts @@ -1,6 +1,4 @@ import { mdiPlus } from "@mdi/js"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-item/paper-icon-item"; import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoize from "memoize-one"; diff --git a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts index c843f2b52b..c4c974715a 100644 --- a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts @@ -1,11 +1,11 @@ +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { mdiClose } from "@mdi/js"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { array, assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { stopPropagation } from "../../../../common/dom/stop_propagation"; import "../../../../components/entity/ha-entity-picker"; import "../../../../components/ha-svg-icon"; import { HomeAssistant } from "../../../../types"; @@ -109,18 +109,20 @@ export class HuiAlarmPanelCardEditor
    ` )} - - - ${states.map( - (entityState) => html` ${entityState} ` - )} - - + ${states.map( + (entityState) => + html`${entityState} ` + )} + - - - ${views.map( - (view) => html` - ${this.hass!.localize( - `ui.panel.lovelace.editor.card.calendar.views.${view}` - )} - - ` - )} - - + ${views.map( + (view) => html` + ${this.hass!.localize( + `ui.panel.lovelace.editor.card.calendar.views.${view}` + )} + + ` + )} +
    - - - ${this.hass!.localize( - "ui.panel.lovelace.editor.card.conditional.state_equal" - )} - ${this.hass!.localize( - "ui.panel.lovelace.editor.card.conditional.state_not_equal" - )} - - + + + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.conditional.state_equal" + )} + + + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.conditional.state_not_equal" + )} + + @@ -274,9 +277,9 @@ export class HuiConditionalCardEditor } const conditions = [...this._config.conditions]; if (target.configValue === "entity" && target.value === "") { - conditions.splice(target.index, 1); + conditions.splice(target.idx, 1); } else { - const condition = { ...conditions[target.index] }; + const condition = { ...conditions[target.idx] }; if (target.configValue === "entity") { condition.entity = target.value; } else if (target.configValue === "state") { @@ -286,7 +289,7 @@ export class HuiConditionalCardEditor condition.state = target.value; } } else if (target.configValue === "invert") { - if (target.selected === 1) { + if (target.value === "true") { if (condition.state) { condition.state_not = condition.state; delete condition.state; @@ -296,7 +299,7 @@ export class HuiConditionalCardEditor delete condition.state_not; } } - conditions[target.index] = condition; + conditions[target.idx] = condition; } this._config = { ...this._config, conditions }; fireEvent(this, "config-changed", { config: this._config }); @@ -321,7 +324,7 @@ export class HuiConditionalCardEditor display: flex; align-items: flex-end; } - .condition .state paper-dropdown-menu { + .condition .state mwc-select { margin-right: 16px; } .condition .state paper-input { diff --git a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts index 0869bb9264..d0558373bd 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts @@ -1,6 +1,4 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { diff --git a/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts b/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts index 083920c9b5..bc1b534701 100644 --- a/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts @@ -1,3 +1,5 @@ +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import "@polymer/paper-input/paper-input"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -99,37 +101,34 @@ export class HuiGenericEntityRowEditor @value-changed=${this._valueChanged} >
    - - + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.entities.secondary_info_values.none" + )} - ${this.hass!.localize( - "ui.panel.lovelace.editor.card.entities.secondary_info_values.none" - )} - ${Object.keys(SecondaryInfoValues).map((info) => { - if ( - !("domains" in SecondaryInfoValues[info]) || - ("domains" in SecondaryInfoValues[info] && - SecondaryInfoValues[info].domains!.includes(domain)) - ) { - return html` - ${this.hass!.localize( - `ui.panel.lovelace.editor.card.entities.secondary_info_values.${info}` - )} - `; - } - return ""; - })} - - + ${Object.keys(SecondaryInfoValues).map((info) => { + if ( + !("domains" in SecondaryInfoValues[info]) || + ("domains" in SecondaryInfoValues[info] && + SecondaryInfoValues[info].domains!.includes(domain)) + ) { + return html` + + ${this.hass!.localize( + `ui.panel.lovelace.editor.card.entities.secondary_info_values.${info}` + )} + + `; + } + return ""; + })} +
    `; } diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts index fbdffb08df..aa1ed1eb7b 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts @@ -1,11 +1,11 @@ -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { assert, boolean, object, optional, string, assign } from "superstruct"; +import { assert, assign, boolean, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import "../../../../components/ha-formfield"; import "../../../../components/ha-switch"; @@ -17,9 +17,9 @@ import "../../components/hui-entity-editor"; import "../../components/hui-theme-select-editor"; import { LovelaceCardEditor } from "../../types"; import { actionConfigStruct } from "../structs/action-struct"; +import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { EditorTarget } from "../types"; import { configElementStyle } from "./config-elements-style"; -import { baseLovelaceCardConfig } from "../structs/base-card-struct"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -155,22 +155,24 @@ export class HuiPictureEntityCardEditor allow-custom-entity >
    - - - ${views.map((view) => html` ${view} `)} - - + ${views.map( + (view) => + html`${view} ` + )} +
    - - - ${views.map((view) => html` ${view} `)} - - + ${views.map( + (view) => + html`${view} ` + )} + - - - ${graphs.map((graph) => html`${graph}`)} - - + ${graphs.map( + (graph) => + html`${graph}` + )} +
    - - - ${periods.map( - (period) => - html` - ${this.hass!.localize( - `ui.panel.lovelace.editor.card.statistics-graph.periods.${period}` - )} - ` - )} - - + ${periods.map( + (period) => + html` + ${this.hass!.localize( + `ui.panel.lovelace.editor.card.statistics-graph.periods.${period}` + )} + ` + )} + ${this._params.allowDashboardChange - ? html` - - - Default - - ${this._dashboards.map((dashboard) => { - if (!this.hass.user!.is_admin && dashboard.require_admin) { - return ""; - } - return html` - ${dashboard.title} - `; - })} - - ` + Default + + ${this._dashboards.map((dashboard) => { + if (!this.hass.user!.is_admin && dashboard.require_admin) { + return ""; + } + return html` + ${dashboard.title} + `; + })} + ` : ""} ${this._config ? this._config.views.length > 1 @@ -111,7 +108,7 @@ export class HuiDialogSelectView extends LitElement { ${this._config.views.map( (view, idx) => html` icon) + .graphic=${this._config?.views.some(({ icon }) => icon) ? "icon" : null} @click=${this._viewChanged} @@ -142,8 +139,8 @@ export class HuiDialogSelectView extends LitElement { this._params!.dashboards || (await fetchDashboards(this.hass)); } - private async _dashboardChanged(ev: CustomEvent) { - let urlPath: string | null = ev.detail.item.urlPath; + private async _dashboardChanged(ev) { + let urlPath: string | null = ev.target.value; if (urlPath === this._urlPath) { return; } @@ -181,7 +178,7 @@ export class HuiDialogSelectView extends LitElement { return [ haStyleDialog, css` - ha-paper-dropdown-menu { + mwc-select { width: 100%; } `, diff --git a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts index 40da6d5d4e..411b7906ef 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts @@ -1,7 +1,10 @@ +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { slugify } from "../../../../common/string/slugify"; import "../../../../components/ha-formfield"; import "../../../../components/ha-icon-picker"; @@ -121,26 +124,24 @@ export class HuiViewEditor extends LitElement { .configValue=${"theme"} @value-changed=${this._valueChanged} > - - - ${[DEFAULT_VIEW_LAYOUT, SIDEBAR_VIEW_LAYOUT, PANEL_VIEW_LAYOUT].map( - (type) => html` - ${this.hass.localize( - `ui.panel.lovelace.editor.edit_view.types.${type}` - )} - ` - )} - - + ${[DEFAULT_VIEW_LAYOUT, SIDEBAR_VIEW_LAYOUT, PANEL_VIEW_LAYOUT].map( + (type) => html` + ${this.hass.localize( + `ui.panel.lovelace.editor.edit_view.types.${type}` + )} + ` + )} +
    `; } @@ -166,7 +167,7 @@ export class HuiViewEditor extends LitElement { } private _typeChanged(ev): void { - const selected = ev.target.selected; + const selected = ev.target.value; if (selected === "") { return; } diff --git a/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts index 143aa75ecf..d4bb565835 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts @@ -1,5 +1,5 @@ -import "@polymer/paper-item/paper-icon-item"; -import "@polymer/paper-item/paper-item-body"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, @@ -68,19 +68,20 @@ export class HuiViewVisibilityEditor extends LitElement {

    ${this._sortedUsers(this._users).map( (user) => html` - + - ${user.name} + ${user.name} - + ` )} `; diff --git a/src/panels/profile/ha-panel-profile.ts b/src/panels/profile/ha-panel-profile.ts index 4d9b82c42c..a1c7b66ad3 100644 --- a/src/panels/profile/ha-panel-profile.ts +++ b/src/panels/profile/ha-panel-profile.ts @@ -1,8 +1,6 @@ import "@material/mwc-button"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property, state } from "lit/decorators"; diff --git a/src/panels/profile/ha-pick-dashboard-row.ts b/src/panels/profile/ha-pick-dashboard-row.ts index 544c7296cc..9fa0dc057e 100644 --- a/src/panels/profile/ha-pick-dashboard-row.ts +++ b/src/panels/profile/ha-pick-dashboard-row.ts @@ -1,12 +1,11 @@ -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import "../../components/ha-paper-dropdown-menu"; import "../../components/ha-settings-row"; import { fetchDashboards, LovelaceDashboard } from "../../data/lovelace"; import { setDefaultPanel } from "../../data/panel"; import { HomeAssistant } from "../../types"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; @customElement("ha-pick-dashboard-row") class HaPickDashboardRow extends LitElement { @@ -30,36 +29,30 @@ class HaPickDashboardRow extends LitElement { ${this.hass.localize("ui.panel.profile.dashboard.description")} - - - ${this.hass.localize( - "ui.panel.profile.dashboard.default_dashboard_label" - )} - ${this._dashboards.map((dashboard) => { - if (!this.hass.user!.is_admin && dashboard.require_admin) { - return ""; - } - return html` - ${dashboard.title} - `; - })} - - + + ${this.hass.localize( + "ui.panel.profile.dashboard.default_dashboard_label" + )} + + ${this._dashboards.map((dashboard) => { + if (!this.hass.user!.is_admin && dashboard.require_admin) { + return ""; + } + return html` + + ${dashboard.title} + + `; + })} + `; } @@ -68,8 +61,8 @@ class HaPickDashboardRow extends LitElement { this._dashboards = await fetchDashboards(this.hass); } - private _dashboardChanged(ev: CustomEvent) { - const urlPath = ev.detail.item.getAttribute("url-path"); + private _dashboardChanged(ev) { + const urlPath = ev.target.value; if (!urlPath || urlPath === this.hass.defaultPanel) { return; } diff --git a/src/panels/profile/ha-pick-language-row.js b/src/panels/profile/ha-pick-language-row.js deleted file mode 100644 index 7cd49a7304..0000000000 --- a/src/panels/profile/ha-pick-language-row.js +++ /dev/null @@ -1,111 +0,0 @@ -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../components/ha-paper-dropdown-menu"; -import "../../components/ha-settings-row"; -import { EventsMixin } from "../../mixins/events-mixin"; -import LocalizeMixin from "../../mixins/localize-mixin"; - -/* - * @appliesMixin LocalizeMixin - * @appliesMixin EventsMixin - */ -class HaPickLanguageRow extends LocalizeMixin(EventsMixin(PolymerElement)) { - static get template() { - return html` - - - [[localize('ui.panel.profile.language.header')]] - - [[localize('ui.panel.profile.language.link_promo')]] - - - - - - - - `; - } - - static get properties() { - return { - hass: Object, - narrow: Boolean, - languageSelection: { - type: String, - observer: "languageSelectionChanged", - }, - languages: { - type: Array, - computed: "computeLanguages(hass)", - }, - }; - } - - static get observers() { - return ["setLanguageSelection(language)"]; - } - - computeLanguages(hass) { - if (!hass || !hass.translationMetadata) { - return []; - } - const translations = hass.translationMetadata.translations; - return Object.keys(translations).map((key) => ({ - key, - ...translations[key], - })); - } - - setLanguageSelection(language) { - this.languageSelection = language; - } - - languageSelectionChanged(newVal) { - // Only fire event if language was changed. This prevents select updates when - // responding to hass changes. - if (newVal !== this.hass.language) { - this.fire("hass-language-select", newVal); - } - } - - ready() { - super.ready(); - if (this.hass && this.hass.locale && this.hass.locale.language) { - this.setLanguageSelection(this.hass.locale.language); - } - } -} - -customElements.define("ha-pick-language-row", HaPickLanguageRow); diff --git a/src/panels/profile/ha-pick-language-row.ts b/src/panels/profile/ha-pick-language-row.ts new file mode 100644 index 0000000000..ced951b463 --- /dev/null +++ b/src/panels/profile/ha-pick-language-row.ts @@ -0,0 +1,87 @@ +import { css, html, LitElement, PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../common/dom/fire_event"; +import "../../components/ha-settings-row"; +import { HomeAssistant, Translation } from "../../types"; +import "@material/mwc-select/mwc-select"; +import "@material/mwc-list/mwc-list-item"; + +@customElement("ha-pick-language-row") +export class HaPickLanguageRow extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public narrow!: boolean; + + @state() private _languages: (Translation & { key: string })[] = []; + + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + this._computeLanguages(); + } + + protected render() { + return html` + + ${this.hass.localize("ui.panel.profile.language.header")} + + ${this.hass.localize("ui.panel.profile.language.link_promo")} + + + ${this._languages.map( + (language) => html` + ${language.nativeName} + ` + )} + + + `; + } + + private _computeLanguages() { + if (!this.hass.translationMetadata?.translations) { + return; + } + this._languages = Object.keys( + this.hass.translationMetadata.translations + ).map((key) => ({ + key, + ...this.hass.translationMetadata.translations[key], + })); + } + + private _languageSelectionChanged(ev) { + // Only fire event if language was changed. This prevents select updates when + // responding to hass changes. + if (ev.target.value !== this.hass.language) { + fireEvent(this, "hass-language-select", ev.target.value); + } + } + + static styles = css` + a { + color: var(--primary-color); + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-pick-language-row": HaPickLanguageRow; + } +} diff --git a/src/panels/profile/ha-pick-number-format-row.ts b/src/panels/profile/ha-pick-number-format-row.ts index 13bd937c46..f32ee02dce 100644 --- a/src/panels/profile/ha-pick-number-format-row.ts +++ b/src/panels/profile/ha-pick-number-format-row.ts @@ -1,11 +1,10 @@ -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { formatNumber } from "../../common/number/format_number"; import "../../components/ha-card"; -import "../../components/ha-paper-dropdown-menu"; import "../../components/ha-settings-row"; import { NumberFormat } from "../../data/translation"; import { HomeAssistant } from "../../types"; @@ -25,47 +24,39 @@ class NumberFormatRow extends LitElement { ${this.hass.localize("ui.panel.profile.number_format.description")} - - - ${Object.values(NumberFormat).map((format) => { - const formattedNumber = formatNumber(1234567.89, { - ...this.hass.locale, - number_format: format, - }); - const value = this.hass.localize( - `ui.panel.profile.number_format.formats.${format}` - ); - const twoLine = value.slice(value.length - 2) !== "89"; // Display explicit number formats on one line - return html` - - -
    ${value}
    - ${twoLine - ? html`
    ${formattedNumber}
    ` - : ""} -
    -
    - `; - })} -
    -
    + ${Object.values(NumberFormat).map((format) => { + const formattedNumber = formatNumber(1234567.89, { + ...this.hass.locale, + number_format: format, + }); + const value = this.hass.localize( + `ui.panel.profile.number_format.formats.${format}` + ); + const twoLine = value.slice(value.length - 2) !== "89"; // Display explicit number formats on one line + return html` + + ${value} + ${twoLine + ? html`${formattedNumber}` + : ""} + + `; + })} + `; } - private async _handleFormatSelection(ev: CustomEvent) { - fireEvent(this, "hass-number-format-select", ev.detail.item.format); + private async _handleFormatSelection(ev) { + fireEvent(this, "hass-number-format-select", ev.target.value); } } diff --git a/src/panels/profile/ha-pick-theme-row.ts b/src/panels/profile/ha-pick-theme-row.ts index 7f55bb8bb8..214cc20515 100644 --- a/src/panels/profile/ha-pick-theme-row.ts +++ b/src/panels/profile/ha-pick-theme-row.ts @@ -1,7 +1,5 @@ import "@material/mwc-button/mwc-button"; import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, @@ -13,7 +11,6 @@ import { import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-formfield"; -import "../../components/ha-paper-dropdown-menu"; import "../../components/ha-radio"; import type { HaRadio } from "../../components/ha-radio"; import "../../components/ha-settings-row"; @@ -23,6 +20,8 @@ import { } from "../../resources/ha-style"; import { HomeAssistant } from "../../types"; import { documentationUrl } from "../../util/documentation-url"; +import "@material/mwc-select/mwc-select"; +import "@material/mwc-list/mwc-list-item"; @customElement("ha-pick-theme-row") export class HaPickThemeRow extends LitElement { @@ -63,22 +62,17 @@ export class HaPickThemeRow extends LitElement { ${this.hass.localize("ui.panel.profile.themes.link_promo")} - - - ${this._themeNames.map( - (theme) => html`${theme}` - )} - - + ${this._themeNames.map( + (theme) => + html`${theme}` + )} + ${curTheme === "default" || this._supportsModeSelection(curTheme) ? html`
    @@ -91,7 +85,7 @@ export class HaPickThemeRow extends LitElement { @change=${this._handleDarkMode} name="dark_mode" value="auto" - ?checked=${themeSettings?.dark === undefined} + .checked=${themeSettings?.dark === undefined} > @@ -116,7 +110,7 @@ export class HaPickThemeRow extends LitElement { @change=${this._handleDarkMode} name="dark_mode" value="dark" - ?checked=${themeSettings?.dark === true} + .checked=${themeSettings?.dark === true} > @@ -195,8 +189,8 @@ export class HaPickThemeRow extends LitElement { fireEvent(this, "settheme", { dark }); } - private _handleThemeSelection(ev: CustomEvent) { - const theme = ev.detail.item.theme; + private _handleThemeSelection(ev) { + const theme = ev.target.value; if (theme === "Backend-selected") { if (this.hass.selectedTheme?.theme) { fireEvent(this, "settheme", { diff --git a/src/panels/profile/ha-pick-time-format-row.ts b/src/panels/profile/ha-pick-time-format-row.ts index 686c186d17..5f68076f2a 100644 --- a/src/panels/profile/ha-pick-time-format-row.ts +++ b/src/panels/profile/ha-pick-time-format-row.ts @@ -1,14 +1,13 @@ -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { formatTime } from "../../common/datetime/format_time"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-card"; -import "../../components/ha-paper-dropdown-menu"; import "../../components/ha-settings-row"; import { TimeFormat } from "../../data/translation"; import { HomeAssistant } from "../../types"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; @customElement("ha-pick-time-format-row") class TimeFormatRow extends LitElement { @@ -26,42 +25,34 @@ class TimeFormatRow extends LitElement { ${this.hass.localize("ui.panel.profile.time_format.description")} - - - ${Object.values(TimeFormat).map((format) => { - const formattedTime = formatTime(date, { - ...this.hass.locale, - time_format: format, - }); - const value = this.hass.localize( - `ui.panel.profile.time_format.formats.${format}` - ); - return html` - -
    ${value}
    -
    ${formattedTime}
    -
    -
    `; - })} -
    -
    + ${Object.values(TimeFormat).map((format) => { + const formattedTime = formatTime(date, { + ...this.hass.locale, + time_format: format, + }); + const value = this.hass.localize( + `ui.panel.profile.time_format.formats.${format}` + ); + return html` + ${value} + ${formattedTime} + `; + })} + `; } - private async _handleFormatSelection(ev: CustomEvent) { - fireEvent(this, "hass-time-format-select", ev.detail.item.format); + private async _handleFormatSelection(ev) { + fireEvent(this, "hass-time-format-select", ev.target.value); } } From fc654d86c6fa51e130d062f0f356a55853bc98d1 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 14 Feb 2022 22:33:12 +0100 Subject: [PATCH 072/174] hassio fixes (#11688) --- hassio/src/addon-store/hassio-addon-store.ts | 13 ++++----- .../addon-view/config/hassio-addon-audio.ts | 27 +++++++++++-------- .../hardware/dialog-hassio-hardware.ts | 2 +- hassio/src/system/hassio-supervisor-log.ts | 4 +-- .../update-available/update-available-card.ts | 1 - 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/hassio/src/addon-store/hassio-addon-store.ts b/hassio/src/addon-store/hassio-addon-store.ts index b83d282787..2e071dc145 100644 --- a/hassio/src/addon-store/hassio-addon-store.ts +++ b/hassio/src/addon-store/hassio-addon-store.ts @@ -221,13 +221,14 @@ class HassioAddonStore extends LitElement { margin-top: 24px; } .search { - padding: 0 16px; - background: var(--sidebar-background-color); - border-bottom: 1px solid var(--divider-color); + position: sticky; + top: 0; + z-index: 2; } - .search search-input { - position: relative; - top: 2px; + search-input { + display: block; + --mdc-text-field-fill-color: var(--sidebar-background-color); + --mdc-text-field-idle-line-color: var(--divider-color); } .advanced { padding: 12px; diff --git a/hassio/src/addon-view/config/hassio-addon-audio.ts b/hassio/src/addon-view/config/hassio-addon-audio.ts index c9629589ae..ff8480609b 100644 --- a/hassio/src/addon-view/config/hassio-addon-audio.ts +++ b/hassio/src/addon-view/config/hassio-addon-audio.ts @@ -56,8 +56,8 @@ class HassioAddonAudio extends LitElement { ${this._error ? html`${this._error}` : ""} - - - ${this._inputDevices && - this._inputDevices.map( + ${this._inputDevices.map( (item) => html` ${item.name} ` )} - - `} + ${this._outputDevices && + html` - ${this._outputDevices && - this._outputDevices.map( + ${this._outputDevices.map( (item) => html` ${item.name} ` )} - + `}
    @@ -121,12 +120,18 @@ class HassioAddonAudio extends LitElement { .card-actions { text-align: right; } + mwc-select { + width: 100%; + } + mwc-select:last-child { + margin-top: 8px; + } `, ]; } - protected update(changedProperties: PropertyValues): void { - super.update(changedProperties); + protected willUpdate(changedProperties: PropertyValues): void { + super.willUpdate(changedProperties); if (changedProperties.has("addon")) { this._addonChanged(); } diff --git a/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts b/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts index ca94af7c8d..5575658d5e 100755 --- a/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts +++ b/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts @@ -178,7 +178,7 @@ class HassioHardwareDialog extends LitElement { padding: 0.2em 0.4em; } search-input { - margin: 0 16px; + margin: 8px 16px 0; display: block; } .device-property { diff --git a/hassio/src/system/hassio-supervisor-log.ts b/hassio/src/system/hassio-supervisor-log.ts index 6000b5b9aa..65cb0f8a66 100644 --- a/hassio/src/system/hassio-supervisor-log.ts +++ b/hassio/src/system/hassio-supervisor-log.ts @@ -146,8 +146,8 @@ class HassioSupervisorLog extends LitElement { white-space: pre-wrap; } mwc-select { - padding: 0 2%; - width: 96%; + width: 100%; + margin-bottom: 4px; } `, ]; diff --git a/hassio/src/update-available/update-available-card.ts b/hassio/src/update-available/update-available-card.ts index fce6b76927..a5a2bc81a2 100644 --- a/hassio/src/update-available/update-available-card.ts +++ b/hassio/src/update-available/update-available-card.ts @@ -10,7 +10,6 @@ import { import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../src/common/dom/fire_event"; -import "../../../src/common/search/search-input"; import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/ha-alert"; import "../../../src/components/ha-button-menu"; From 92db272759c28c04f7c4c687b6e7f907b77023e0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 14 Feb 2022 23:56:50 +0100 Subject: [PATCH 073/174] Dont exclude domain for area and device (#11689) --- src/components/ha-button-related-filter-menu.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/ha-button-related-filter-menu.ts b/src/components/ha-button-related-filter-menu.ts index bc0a7c26b3..7dcd892eb5 100644 --- a/src/components/ha-button-related-filter-menu.ts +++ b/src/components/ha-button-related-filter-menu.ts @@ -75,7 +75,6 @@ export class HaRelatedFilterButtonMenu extends LitElement { .hass=${this.hass} .value=${this.value?.area} no-add - .excludeDomains=${this.excludeDomains} @value-changed=${this._areaPicked} @click=${this._preventDefault} > @@ -85,7 +84,6 @@ export class HaRelatedFilterButtonMenu extends LitElement { )} .hass=${this.hass} .value=${this.value?.device} - .excludeDomains=${this.excludeDomains} @value-changed=${this._devicePicked} @click=${this._preventDefault} > From 520896a3c2d04a06c7560f67f57fbbc04e6f8df2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 15:21:17 -0800 Subject: [PATCH 074/174] Try to keep the browsing stack when changing players in media panel (#11681) --- .../media-player/ha-media-player-browse.ts | 54 ++++++++++++++----- .../media-browser/ha-bar-media-player.ts | 9 +++- .../media-browser/ha-panel-media-browser.ts | 35 ++++++++---- 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 6117286c59..97b8963919 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -54,7 +54,14 @@ import "./ha-browse-media-tts"; declare global { interface HASSDomEvents { "media-picked": MediaPickedEvent; - "media-browsed": { ids: MediaPlayerItemId[]; current?: MediaPlayerItem }; + "media-browsed": { + // Items of the new browse stack + ids: MediaPlayerItemId[]; + // Current fetched item for this browse stack + current?: MediaPlayerItem; + // If the new stack should replace the old stack + replace?: boolean; + }; } } @@ -433,8 +440,8 @@ export class HaMediaPlayerBrowse extends LitElement { if (changedProps.has("entityId")) { this._setError(undefined); - } - if (!changedProps.has("navigateIds")) { + } else if (!changedProps.has("navigateIds")) { + // Neither entity ID or navigateIDs changed, nothing to fetch return; } @@ -443,6 +450,7 @@ export class HaMediaPlayerBrowse extends LitElement { const oldNavigateIds = changedProps.get("navigateIds") as | this["navigateIds"] | undefined; + const navigateIds = this.navigateIds; // We're navigating. Reset the shizzle. this._content?.scrollTo(0, 0); @@ -451,11 +459,9 @@ export class HaMediaPlayerBrowse extends LitElement { const oldParentItem = this._parentItem; this._currentItem = undefined; this._parentItem = undefined; - const currentId = this.navigateIds[this.navigateIds.length - 1]; + const currentId = navigateIds[navigateIds.length - 1]; const parentId = - this.navigateIds.length > 1 - ? this.navigateIds[this.navigateIds.length - 2] - : undefined; + navigateIds.length > 1 ? navigateIds[navigateIds.length - 2] : undefined; let currentProm: Promise | undefined; let parentProm: Promise | undefined; @@ -464,9 +470,9 @@ export class HaMediaPlayerBrowse extends LitElement { if ( // Check if we navigated to a child oldNavigateIds && - this.navigateIds.length > oldNavigateIds.length && + navigateIds.length === oldNavigateIds.length + 1 && oldNavigateIds.every((oldVal, idx) => { - const curVal = this.navigateIds[idx]; + const curVal = navigateIds[idx]; return ( curVal.media_content_id === oldVal.media_content_id && curVal.media_content_type === oldVal.media_content_type @@ -477,8 +483,8 @@ export class HaMediaPlayerBrowse extends LitElement { } else if ( // Check if we navigated to a parent oldNavigateIds && - this.navigateIds.length < oldNavigateIds.length && - this.navigateIds.every((curVal, idx) => { + navigateIds.length === oldNavigateIds.length - 1 && + navigateIds.every((curVal, idx) => { const oldVal = oldNavigateIds[idx]; return ( curVal.media_content_id === oldVal.media_content_id && @@ -501,11 +507,33 @@ export class HaMediaPlayerBrowse extends LitElement { (item) => { this._currentItem = item; fireEvent(this, "media-browsed", { - ids: this.navigateIds, + ids: navigateIds, current: item, }); }, - (err) => this._setError(err) + (err) => { + // When we change entity ID, we will first try to see if the new entity is + // able to resolve the new path. If that results in an error, browse the root. + const isNewEntityWithSamePath = + oldNavigateIds && + changedProps.has("entityId") && + navigateIds.length === oldNavigateIds.length && + oldNavigateIds.every( + (oldItem, idx) => + navigateIds[idx].media_content_id === oldItem.media_content_id && + navigateIds[idx].media_content_type === oldItem.media_content_type + ); + if (isNewEntityWithSamePath) { + fireEvent(this, "media-browsed", { + ids: [ + { media_content_id: undefined, media_content_type: undefined }, + ], + replace: true, + }); + } else { + this._setError(err); + } + } ); // Fetch parent if (!parentProm && parentId !== undefined) { diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index 65a02e1fc6..1dee8318f1 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -25,7 +25,6 @@ import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; import { domainIcon } from "../../common/entity/domain_icon"; import { supportsFeature } from "../../common/entity/supports-feature"; -import { navigate } from "../../common/navigate"; import "../../components/ha-button-menu"; import "../../components/ha-icon-button"; import { UNAVAILABLE_STATES } from "../../data/entity"; @@ -47,6 +46,12 @@ import type { HomeAssistant } from "../../types"; import "../lovelace/components/hui-marquee"; import { BrowserMediaPlayer } from "./browser-media-player"; +declare global { + interface HASSDomEvents { + "player-picked": { entityId: string }; + } +} + @customElement("ha-bar-media-player") class BarMediaPlayer extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -399,7 +404,7 @@ class BarMediaPlayer extends LitElement { private _selectPlayer(ev: CustomEvent): void { const entityId = (ev.currentTarget as any).player; - navigate(`/media-browser/${entityId}`, { replace: true }); + fireEvent(this, "player-picked", { entityId }); } static get styles(): CSSResultGroup { diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 0ab9f870c4..7a2590b903 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -43,6 +43,16 @@ import { isCameraMediaSource, } from "../../data/camera"; +const createMediaPanelUrl = (entityId: string, items: MediaPlayerItemId[]) => { + let path = `/media-browser/${entityId}`; + for (const item of items.slice(1)) { + path += + "/" + + encodeURIComponent(`${item.media_content_type},${item.media_content_id}`); + } + return path; +}; + @customElement("ha-panel-media-browser") class PanelMediaBrowser extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -120,6 +130,7 @@ class PanelMediaBrowser extends LitElement { .hass=${this.hass} .entityId=${this._entityId} .narrow=${this.narrow} + @player-picked=${this._playerPicked} > `; } @@ -179,7 +190,9 @@ class PanelMediaBrowser extends LitElement { } private _goBack() { - history.back(); + navigate( + createMediaPanelUrl(this._entityId, this._navigateIds.slice(0, -1)) + ); } private _mediaBrowsed(ev: { detail: HASSDomEvents["media-browsed"] }) { @@ -188,15 +201,9 @@ class PanelMediaBrowser extends LitElement { return; } - let path = ""; - for (const item of ev.detail.ids.slice(1)) { - path += - "/" + - encodeURIComponent( - `${item.media_content_type},${item.media_content_id}` - ); - } - navigate(`/media-browser/${this._entityId}${path}`); + navigate(createMediaPanelUrl(this._entityId, ev.detail.ids), { + replace: ev.detail.replace, + }); } private async _mediaPicked( @@ -239,6 +246,14 @@ class PanelMediaBrowser extends LitElement { }); } + private _playerPicked(ev) { + const entityId: string = ev.detail.entityId; + if (entityId === this._entityId) { + return; + } + navigate(createMediaPanelUrl(entityId, this._navigateIds)); + } + private async _startUpload() { const input = document.createElement("input"); input.type = "file"; From 8bb2374b1bf00c175c2b8a4d1868868b35157eb2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 15:25:23 -0800 Subject: [PATCH 075/174] Allow uploading multiple files (#11687) --- .../media-player/ha-media-player-browse.ts | 53 ++++++----- .../media-browser/ha-panel-media-browser.ts | 94 ++++++++++++++----- src/translations/en.json | 4 +- 3 files changed, 101 insertions(+), 50 deletions(-) diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 97b8963919..be644eacca 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -1,7 +1,7 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list-item"; -import { mdiPlay, mdiPlus } from "@mdi/js"; +import { mdiArrowUpRight, mdiPlay, mdiPlus } from "@mdi/js"; import "@polymer/paper-tooltip/paper-tooltip"; import { css, @@ -265,32 +265,25 @@ export class HaMediaPlayerBrowse extends LitElement { : !currentItem.children?.length ? html`
    - ${this.hass.localize( - "ui.components.media-browser.no_items" - )} -
    ${currentItem.media_content_id === "media-source://media_source/local/." - ? html`
    ${this.hass.localize( - "ui.components.media-browser.learn_adding_local_media", - "documentation", - html` + + + + + ${this.hass.localize( + "ui.components.media-browser.file_management.highlight_button" )} - target="_blank" - rel="noreferrer" - >${this.hass.localize( - "ui.components.media-browser.documentation" - )}` - )} -
    - ${this.hass.localize( - "ui.components.media-browser.local_media_files" - )}` - : ""} + +
    + ` + : this.hass.localize( + "ui.components.media-browser.no_items" + )}
    ` : childrenMediaClass.layout === "grid" @@ -772,6 +765,18 @@ export class HaMediaPlayerBrowse extends LitElement { padding-left: 32px; } + .highlight-add-button { + display: flex; + flex-direction: row-reverse; + margin-right: 48px; + } + + .highlight-add-button ha-svg-icon { + position: relative; + top: -0.5em; + margin-left: 8px; + } + .content { overflow-y: auto; box-sizing: border-box; diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 7a2590b903..ad47077d0f 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -15,6 +15,7 @@ import { LocalStorage } from "../../common/decorators/local-storage"; import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event"; import { navigate } from "../../common/navigate"; import "../../components/ha-menu-button"; +import "../../components/ha-circular-progress"; import "../../components/ha-icon-button"; import "../../components/ha-svg-icon"; import "../../components/media-player/ha-media-player-browse"; @@ -64,6 +65,8 @@ class PanelMediaBrowser extends LitElement { @state() _currentItem?: MediaPlayerItem; + @state() _uploading = 0; + private _navigateIds: MediaPlayerItemId[] = [ { media_content_id: undefined, @@ -107,12 +110,34 @@ class PanelMediaBrowser extends LitElement { ) ? html` 0 + ? this.hass.localize( + "ui.components.media-browser.file_management.uploading", + { + count: this._uploading, + } + ) + : this.hass.localize( + "ui.components.media-browser.file_management.add_media" + )} + .disabled=${this._uploading > 0} @click=${this._startUpload} > - + ${this._uploading > 0 + ? html` + + ` + : html` + + `} ` : ""} @@ -255,29 +280,41 @@ class PanelMediaBrowser extends LitElement { } private async _startUpload() { + if (this._uploading > 0) { + return; + } const input = document.createElement("input"); input.type = "file"; input.accept = "audio/*,video/*,image/*"; - input.addEventListener("change", async () => { - try { - await uploadLocalMedia( - this.hass, - this._currentItem!.media_content_id!, - input.files![0] - ); - } catch (err: any) { - showAlertDialog(this, { - text: this.hass.localize( - "ui.components.media-browser.file_management.upload_failed", - { - reason: err.message || err, - } - ), - }); - return; - } - await this._browser.refresh(); - }); + input.multiple = true; + input.addEventListener( + "change", + async () => { + const files = input.files!; + const target = this._currentItem!.media_content_id!; + + for (let i = 0; i < files.length; i++) { + this._uploading = files.length - i; + try { + // eslint-disable-next-line no-await-in-loop + await uploadLocalMedia(this.hass, target, files[i]); + } catch (err: any) { + showAlertDialog(this, { + text: this.hass.localize( + "ui.components.media-browser.file_management.upload_failed", + { + reason: err.message || err, + } + ), + }); + break; + } + } + this._uploading = 0; + await this._browser.refresh(); + }, + { once: true } + ); input.click(); } @@ -285,6 +322,12 @@ class PanelMediaBrowser extends LitElement { return [ haStyle, css` + app-toolbar mwc-button { + --mdc-theme-primary: var(--app-header-text-color); + /* We use icon + text to show disabled state */ + --mdc-button-disabled-ink-color: var(--app-header-text-color); + } + ha-media-player-browse { height: calc(100vh - (100px + var(--header-height))); } @@ -300,7 +343,8 @@ class PanelMediaBrowser extends LitElement { right: 0; } - ha-svg-icon[slot="icon"] { + ha-svg-icon[slot="icon"], + ha-circular-progress[slot="icon"] { vertical-align: middle; } `, diff --git a/src/translations/en.json b/src/translations/en.json index 5bcc4ce4e8..b4892018a6 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -525,8 +525,10 @@ "no_media_folder": "It looks like you have not yet created a media directory.", "setup_local_help": "Check the {documentation} on how to setup local media.", "file_management": { + "highlight_button": "Click here to upload your first media", "upload_failed": "Upload failed: {reason}", - "add_media": "Add Media" + "add_media": "Add Media", + "uploading": "Uploading {count} {count, plural,\n one {file}\n other {files}\n}" }, "class": { "album": "Album", From 901677bbdfecbf9bd4ea47b700ffe615bee8a53f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 15:33:08 -0800 Subject: [PATCH 076/174] Bumped version to 20220214.0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 587f449468..d3ac559300 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220203.0 +version = 20220214.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 9c8d683a19efbd0f01da30a6d2fa23273b7ab2fd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 14 Feb 2022 23:13:35 -0800 Subject: [PATCH 077/174] Group helpers not in an area in a single card (#11690) --- .../common/generate-lovelace-config.ts | 42 +++++++++++++++---- src/translations/en.json | 5 +++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index cc608f06e8..2f9a9560c8 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -26,6 +26,7 @@ import { } from "../cards/types"; import { LovelaceRowConfig } from "../entity-rows/types"; import { ButtonsHeaderFooterConfig } from "../header-footer/types"; +import { HELPER_DOMAINS } from "../../config/helpers/const"; const HIDE_DOMAIN = new Set([ "automation", @@ -281,7 +282,7 @@ export const generateViewConfig = ( ungroupedEntitites[domain].push(state.entity_id); }); - let cards: LovelaceCardConfig[] = []; + const cards: LovelaceCardConfig[] = []; if ("person" in ungroupedEntitites) { const personCards: LovelaceCardConfig[] = []; @@ -340,8 +341,8 @@ export const generateViewConfig = ( } splitted.groups.forEach((groupEntity) => { - cards = cards.concat( - computeCards( + cards.push( + ...computeCards( groupEntity.attributes.entity_id.map( (entityId): [string, HassEntity] => [entityId, entities[entityId]] ), @@ -353,11 +354,38 @@ export const generateViewConfig = ( ); }); + // Group helper entities in a single card + const helperEntities: string[] = []; + + for (const domain of HELPER_DOMAINS) { + if (!(domain in ungroupedEntitites)) { + continue; + } + helperEntities.push(...ungroupedEntitites[domain]); + delete ungroupedEntitites[domain]; + } + + // Prepare translations for cards + const domainTranslations: Record = {}; + + for (const domain of Object.keys(ungroupedEntitites)) { + domainTranslations[domain] = domainToName(localize, domain); + } + + if (helperEntities.length) { + ungroupedEntitites._helpers = helperEntities; + domainTranslations._helpers = localize( + "ui.panel.lovelace.strategy.original-states.helpers" + ); + } + Object.keys(ungroupedEntitites) - .sort() + .sort((domain1, domain2) => + stringCompare(domainTranslations[domain1], domainTranslations[domain2]) + ) .forEach((domain) => { - cards = cards.concat( - computeCards( + cards.push( + ...computeCards( ungroupedEntitites[domain] .sort((a, b) => stringCompare( @@ -370,7 +398,7 @@ export const generateViewConfig = ( entities[entityId], ]), { - title: domainToName(localize, domain), + title: domainTranslations[domain], } ) ); diff --git a/src/translations/en.json b/src/translations/en.json index b4892018a6..69ad6d04c8 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3113,6 +3113,11 @@ } }, "lovelace": { + "strategy": { + "original-states": { + "helpers": "[%key:ui::panel::config::helpers::caption%]" + } + }, "cards": { "confirm_delete": "Are you sure you want to delete this card?", "show_more_info": "Show more information", From d049990f048f42071f52eb0a601e745feefe8d62 Mon Sep 17 00:00:00 2001 From: Brandon Rothweiler Date: Tue, 15 Feb 2022 04:03:58 -0500 Subject: [PATCH 078/174] Improve `stripPrefixFromEntityName` to handle colon and space separator (#11691) --- .../entity/strip_prefix_from_entity_name.ts | 30 ++++++++++++------- .../device-detail/ha-device-entities-card.ts | 4 +-- .../common/generate-lovelace-config.ts | 2 +- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/common/entity/strip_prefix_from_entity_name.ts b/src/common/entity/strip_prefix_from_entity_name.ts index 1efa3704d5..e976b7b18f 100644 --- a/src/common/entity/strip_prefix_from_entity_name.ts +++ b/src/common/entity/strip_prefix_from_entity_name.ts @@ -1,24 +1,32 @@ +const SUFFIXES = [" ", ": "]; + /** * Strips a device name from an entity name. * @param entityName the entity name - * @param lowerCasedPrefixWithSpaceSuffix the prefix to strip, lower cased with a space suffix + * @param lowerCasedPrefix the prefix to strip, lower cased * @returns */ export const stripPrefixFromEntityName = ( entityName: string, - lowerCasedPrefixWithSpaceSuffix: string + lowerCasedPrefix: string ) => { - if (!entityName.toLowerCase().startsWith(lowerCasedPrefixWithSpaceSuffix)) { - return undefined; + const lowerCasedEntityName = entityName.toLowerCase(); + + for (const suffix of SUFFIXES) { + const lowerCasedPrefixWithSuffix = `${lowerCasedPrefix}${suffix}`; + + if (lowerCasedEntityName.startsWith(lowerCasedPrefixWithSuffix)) { + const newName = entityName.substring(lowerCasedPrefixWithSuffix.length); + + // If first word already has an upper case letter (e.g. from brand name) + // leave as-is, otherwise capitalize the first word. + return hasUpperCase(newName.substr(0, newName.indexOf(" "))) + ? newName + : newName[0].toUpperCase() + newName.slice(1); + } } - const newName = entityName.substring(lowerCasedPrefixWithSpaceSuffix.length); - - // If first word already has an upper case letter (e.g. from brand name) - // leave as-is, otherwise capitalize the first word. - return hasUpperCase(newName.substr(0, newName.indexOf(" "))) - ? newName - : newName[0].toUpperCase() + newName.slice(1); + return undefined; }; const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str; diff --git a/src/panels/config/devices/device-detail/ha-device-entities-card.ts b/src/panels/config/devices/device-detail/ha-device-entities-card.ts index 3b4d461145..ebfbd159dd 100644 --- a/src/panels/config/devices/device-detail/ha-device-entities-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -165,7 +165,7 @@ export class HaDeviceEntitiesCard extends LitElement { const stateObj = this.hass.states[entry.entity_id]; const name = stripPrefixFromEntityName( computeStateName(stateObj), - `${this.deviceName} `.toLowerCase() + this.deviceName.toLowerCase() ); if (name) { config.name = name; @@ -198,7 +198,7 @@ export class HaDeviceEntitiesCard extends LitElement { ${name ? stripPrefixFromEntityName( name, - `${this.deviceName} `.toLowerCase() + this.deviceName.toLowerCase() ) || name : entry.entity_id}
    diff --git a/src/panels/lovelace/common/generate-lovelace-config.ts b/src/panels/lovelace/common/generate-lovelace-config.ts index 2f9a9560c8..0d3fd0cb9a 100644 --- a/src/panels/lovelace/common/generate-lovelace-config.ts +++ b/src/panels/lovelace/common/generate-lovelace-config.ts @@ -97,7 +97,7 @@ export const computeCards = ( const entities: Array = []; const titlePrefix = entityCardOptions.title - ? `${entityCardOptions.title} `.toLowerCase() + ? entityCardOptions.title.toLowerCase() : undefined; const footerEntities: ButtonsHeaderFooterConfig["entities"] = []; From 26d4599ef42e540d99c0e765327c68b343c708e9 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 15 Feb 2022 12:18:05 +0100 Subject: [PATCH 079/174] Display transmitted messages in MQTT debug info dialog (#11531) --- src/data/mqtt.ts | 1 + .../mqtt/dialog-mqtt-device-debug-info.ts | 27 ++++++++++++++++++- .../mqtt/mqtt-messages.ts | 4 ++- src/translations/en.json | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/data/mqtt.ts b/src/data/mqtt.ts index 9c744085fd..7d66e062f5 100644 --- a/src/data/mqtt.ts +++ b/src/data/mqtt.ts @@ -22,6 +22,7 @@ export interface MQTTEntityDebugInfo { entity_id: string; discovery_data: MQTTDiscoveryDebugInfo; subscriptions: MQTTTopicDebugInfo[]; + transmitted: MQTTTopicDebugInfo[]; } export interface MQTTTriggerDebugInfo { diff --git a/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts b/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts index 4b0bd660a7..93c3d6a5a9 100644 --- a/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts +++ b/src/panels/config/devices/device-detail/integration-elements/mqtt/dialog-mqtt-device-debug-info.ts @@ -165,6 +165,7 @@ class DialogMQTTDeviceDebugInfo extends LitElement { ${topic.topic} - + + + ` + )} + + Transmitted messages: +
      + ${entity.transmitted.map( + (topic) => html` +
    • + ${topic.topic} + +
    • ` )} diff --git a/src/panels/config/devices/device-detail/integration-elements/mqtt/mqtt-messages.ts b/src/panels/config/devices/device-detail/integration-elements/mqtt/mqtt-messages.ts index ff26193762..90bfb086c0 100644 --- a/src/panels/config/devices/device-detail/integration-elements/mqtt/mqtt-messages.ts +++ b/src/panels/config/devices/device-detail/integration-elements/mqtt/mqtt-messages.ts @@ -12,6 +12,8 @@ class MQTTMessages extends LitElement { @property() public messages!: MQTTMessage[]; + @property() public direction!: string; + @property() public showAsYaml = false; @property() public showDeserialized = false; @@ -50,7 +52,7 @@ class MQTTMessages extends LitElement { (message) => html`
    • - Received + ${this.direction} ${formatTimeWithSeconds( new Date(message.time), this.hass.locale diff --git a/src/translations/en.json b/src/translations/en.json index 69ad6d04c8..d422b9df16 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -894,6 +894,7 @@ "no_triggers": "No triggers", "payload_display": "Payload display", "recent_messages": "{n} most recently received message(s)", + "recent_tx_messages": "{n} most recently transmitted message(s)", "show_as_yaml": "Show as YAML", "triggers": "Triggers" } From ba63ab8b7a72b8cfd419d13db185d565c6ea48df Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 15 Feb 2022 16:11:43 +0100 Subject: [PATCH 080/174] Latest paper-dropdown -> mwc-select conversion (#11692) --- demo/src/entrypoint.ts | 5 -- .../addon-view/config/hassio-addon-audio.ts | 3 +- .../datadisk/dialog-hassio-datadisk.ts | 2 +- src/entrypoints/authorize.ts | 6 -- src/layouts/home-assistant.ts | 3 +- src/onboarding/onboarding-integrations.ts | 2 - .../integration-panels/zha/types.ts | 7 +- .../zha/zha-cluster-attributes.ts | 56 ++++++++-------- .../zha/zha-cluster-commands.ts | 65 +++++++++---------- .../integration-panels/zha/zha-clusters.ts | 41 ++++++------ .../zha/zha-device-binding.ts | 44 +++++++------ .../zha/zha-group-binding.ts | 30 ++++----- .../zha/zha-network-visualization-page.ts | 6 +- .../zwave/ha-config-zwave.js | 1 + 14 files changed, 126 insertions(+), 145 deletions(-) diff --git a/demo/src/entrypoint.ts b/demo/src/entrypoint.ts index 54feef73d5..456bba9de4 100644 --- a/demo/src/entrypoint.ts +++ b/demo/src/entrypoint.ts @@ -2,8 +2,3 @@ import "../../src/resources/ha-style"; import "../../src/resources/roboto"; import "../../src/resources/safari-14-attachshadow-patch"; import "./ha-demo"; - -/* polyfill for paper-dropdown */ -setTimeout(() => { - import("web-animations-js/web-animations-next-lite.min"); -}, 1000); diff --git a/hassio/src/addon-view/config/hassio-addon-audio.ts b/hassio/src/addon-view/config/hassio-addon-audio.ts index ff8480609b..7f7c8d3f2c 100644 --- a/hassio/src/addon-view/config/hassio-addon-audio.ts +++ b/hassio/src/addon-view/config/hassio-addon-audio.ts @@ -110,8 +110,7 @@ class HassioAddonAudio extends LitElement { hassioStyle, css` :host, - ha-card, - paper-dropdown-menu { + ha-card { display: block; } paper-item { diff --git a/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts b/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts index d903651898..4bad79ca7f 100644 --- a/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts +++ b/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts @@ -156,7 +156,7 @@ class HassioDatadiskDialog extends LitElement { haStyle, haStyleDialog, css` - paper-dropdown-menu { + mwc-select { width: 100%; } ha-circular-progress { diff --git a/src/entrypoints/authorize.ts b/src/entrypoints/authorize.ts index 36954a3fcf..fef05a7219 100644 --- a/src/entrypoints/authorize.ts +++ b/src/entrypoints/authorize.ts @@ -5,9 +5,3 @@ import "../resources/ha-style"; import "../resources/roboto"; import "../resources/safari-14-attachshadow-patch"; import "../resources/array.flat.polyfill"; - -/* polyfill for paper-dropdown */ -setTimeout( - () => import("web-animations-js/web-animations-next-lite.min"), - 2000 -); diff --git a/src/layouts/home-assistant.ts b/src/layouts/home-assistant.ts index 7142419b8f..f75efdb624 100644 --- a/src/layouts/home-assistant.ts +++ b/src/layouts/home-assistant.ts @@ -78,8 +78,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) { super.firstUpdated(changedProps); this._initializeHass(); setTimeout(() => registerServiceWorker(this), 1000); - /* polyfill for paper-dropdown */ - import("web-animations-js/web-animations-next-lite.min"); + this.addEventListener("hass-suspend-when-hidden", (ev) => { this._updateHass({ suspendWhenHidden: ev.detail.suspend }); storeState(this.hass!); diff --git a/src/onboarding/onboarding-integrations.ts b/src/onboarding/onboarding-integrations.ts index d6874a6ae8..01c53f3952 100644 --- a/src/onboarding/onboarding-integrations.ts +++ b/src/onboarding/onboarding-integrations.ts @@ -140,8 +140,6 @@ class OnboardingIntegrations extends LitElement { this._scanUSBDevices(); loadConfigFlowDialog(); this._loadConfigEntries(); - /* polyfill for paper-dropdown */ - import("web-animations-js/web-animations-next-lite.min"); } private _createFlow() { diff --git a/src/panels/config/integrations/integration-panels/zha/types.ts b/src/panels/config/integrations/integration-panels/zha/types.ts index dd09861ad9..79e38cbbf9 100644 --- a/src/panels/config/integrations/integration-panels/zha/types.ts +++ b/src/panels/config/integrations/integration-panels/zha/types.ts @@ -1,11 +1,8 @@ +import { Select } from "@material/mwc-select"; import { Cluster, ZHADevice } from "../../../../../data/zha"; -export interface PickerTarget extends EventTarget { - selected: number; -} - export interface ItemSelectedEvent { - target?: PickerTarget; + target?: Select; } export interface ZHADeviceRemovedEvent { diff --git a/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts b/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts index e6e75915b4..e1cf97995c 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-cluster-attributes.ts @@ -1,9 +1,8 @@ import "@material/mwc-button"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select"; import { mdiHelpCircle } from "@mdi/js"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, @@ -13,6 +12,7 @@ import { TemplateResult, } from "lit"; import { property, state } from "lit/decorators"; +import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-button"; @@ -48,7 +48,7 @@ export class ZHAClusterAttributes extends LitElement { @state() private _attributes: Attribute[] = []; - @state() private _selectedAttributeIndex = -1; + @state() private _selectedAttributeId?: number; @state() private _attributeValue?: any = ""; @@ -60,7 +60,7 @@ export class ZHAClusterAttributes extends LitElement { protected updated(changedProperties: PropertyValues): void { if (changedProperties.has("selectedCluster")) { this._attributes = []; - this._selectedAttributeIndex = -1; + this._selectedAttributeId = undefined; this._attributeValue = ""; this._fetchAttributesForCluster(); } @@ -92,29 +92,25 @@ export class ZHAClusterAttributes extends LitElement {
      - - - ${this._attributes.map( - (entry) => html` - ${entry.name + - " (id: " + - formatAsPaddedHex(entry.id) + - ")"} - ` - )} - - + ${this._attributes.map( + (entry) => html` + + ${entry.name + " (id: " + formatAsPaddedHex(entry.id) + ")"} + + ` + )} +
      ${this.showHelp ? html` @@ -125,7 +121,7 @@ export class ZHAClusterAttributes extends LitElement {
      ` : ""} - ${this._selectedAttributeIndex !== -1 + ${this._selectedAttributeId !== undefined ? this._renderAttributeInteractions() : ""} @@ -218,7 +214,7 @@ export class ZHAClusterAttributes extends LitElement { endpoint_id: this.selectedCluster!.endpoint_id, cluster_id: this.selectedCluster!.id, cluster_type: this.selectedCluster!.type, - attribute: this._attributes[this._selectedAttributeIndex].id, + attribute: this._selectedAttributeId!, manufacturer: this._manufacturerCodeOverride ? parseInt(this._manufacturerCodeOverride as string, 10) : undefined, @@ -236,7 +232,7 @@ export class ZHAClusterAttributes extends LitElement { endpoint_id: this.selectedCluster!.endpoint_id, cluster_id: this.selectedCluster!.id, cluster_type: this.selectedCluster!.type, - attribute: this._attributes[this._selectedAttributeIndex].id, + attribute: this._selectedAttributeId!, value: this._attributeValue, manufacturer: this._manufacturerCodeOverride ? parseInt(this._manufacturerCodeOverride as string, 10) @@ -266,7 +262,7 @@ export class ZHAClusterAttributes extends LitElement { } private _selectedAttributeChanged(event: ItemSelectedEvent): void { - this._selectedAttributeIndex = event.target!.selected; + this._selectedAttributeId = Number(event.target!.value); this._attributeValue = ""; } @@ -274,6 +270,10 @@ export class ZHAClusterAttributes extends LitElement { return [ haStyle, css` + mwc-select { + margin-top: 16px; + } + .menu { width: 100%; } diff --git a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts index d00e672d5f..4af410898b 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts @@ -1,8 +1,7 @@ +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select"; import { mdiHelpCircle } from "@mdi/js"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, @@ -12,6 +11,7 @@ import { TemplateResult, } from "lit"; import { property, state } from "lit/decorators"; +import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-button"; @@ -26,11 +26,7 @@ import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; import "../../../ha-config-section"; import { formatAsPaddedHex } from "./functions"; -import { - ChangeEvent, - IssueCommandServiceData, - ItemSelectedEvent, -} from "./types"; +import { ChangeEvent, IssueCommandServiceData } from "./types"; export class ZHAClusterCommands extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; @@ -45,7 +41,7 @@ export class ZHAClusterCommands extends LitElement { @state() private _commands: Command[] = []; - @state() private _selectedCommandIndex = -1; + @state() private _selectedCommandId?: number; @state() private _manufacturerCodeOverride?: number; @@ -55,7 +51,7 @@ export class ZHAClusterCommands extends LitElement { protected updated(changedProperties: PropertyValues): void { if (changedProperties.has("selectedCluster")) { this._commands = []; - this._selectedCommandIndex = -1; + this._selectedCommandId = undefined; this._fetchCommandsForCluster(); } super.update(changedProperties); @@ -86,29 +82,25 @@ export class ZHAClusterCommands extends LitElement {
      - - - ${this._commands.map( - (entry) => html` - ${entry.name + - " (id: " + - formatAsPaddedHex(entry.id) + - ")"} - ` - )} - - + ${this._commands.map( + (entry) => html` + + ${entry.name + " (id: " + formatAsPaddedHex(entry.id) + ")"} + + ` + )} +
      ${this._showHelp ? html` @@ -119,7 +111,7 @@ export class ZHAClusterCommands extends LitElement {
    ` : ""} - ${this._selectedCommandIndex !== -1 + ${this._selectedCommandId !== undefined ? html`
    command.id === this._selectedCommandId + )!.type, }; } @@ -202,8 +196,8 @@ export class ZHAClusterCommands extends LitElement { this._showHelp = !this._showHelp; } - private _selectedCommandChanged(event: ItemSelectedEvent): void { - this._selectedCommandIndex = event.target!.selected; + private _selectedCommandChanged(event): void { + this._selectedCommandId = Number(event.target.value); this._issueClusterCommandServiceData = this._computeIssueClusterCommandServiceData(); } @@ -212,6 +206,9 @@ export class ZHAClusterCommands extends LitElement { return [ haStyle, css` + mwc-select { + margin-top: 16px; + } .menu { width: 100%; } diff --git a/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts b/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts index 89935bc8ab..e9e20a2899 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts @@ -1,7 +1,6 @@ +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select"; import { mdiHelpCircle } from "@mdi/js"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, @@ -12,6 +11,7 @@ import { } from "lit"; import { property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-button"; @@ -25,7 +25,6 @@ import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; import "../../../ha-config-section"; import { computeClusterKey } from "./functions"; -import { ItemSelectedEvent } from "./types"; declare global { // for fire event @@ -79,24 +78,25 @@ export class ZHAClusters extends LitElement {
    - - - ${this._clusters.map( - (entry) => html` - ${computeClusterKey(entry)} - ` - )} - - + ${this._clusters.map( + (entry, idx) => html` + ${computeClusterKey(entry)} + ` + )} +
    ${this.showHelp ? html` @@ -122,8 +122,8 @@ export class ZHAClusters extends LitElement { } } - private _selectedClusterChanged(event: ItemSelectedEvent): void { - this._selectedClusterIndex = event.target!.selected; + private _selectedClusterChanged(event): void { + this._selectedClusterIndex = Number(event.target!.value); fireEvent(this, "zha-cluster-selected", { cluster: this._clusters[this._selectedClusterIndex], }); @@ -137,6 +137,9 @@ export class ZHAClusters extends LitElement { return [ haStyle, css` + mwc-select { + margin-top: 16px; + } .menu { width: 100%; } diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts index 024e35db24..4ff919838e 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts @@ -1,8 +1,7 @@ import "@material/mwc-button/mwc-button"; import { mdiHelpCircle } from "@mdi/js"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select"; import { css, CSSResultGroup, @@ -21,6 +20,7 @@ import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; import "../../../ha-config-section"; import { ItemSelectedEvent } from "./types"; +import { stopPropagation } from "../../../../../common/dom/stop_propagation"; @customElement("zha-device-binding-control") export class ZHADeviceBindingControl extends LitElement { @@ -62,23 +62,25 @@ export class ZHADeviceBindingControl extends LitElement {
    - - - ${this.bindableDevices.map( - (device) => html` - ${device.user_given_name - ? device.user_given_name - : device.name} - ` - )} - - + + ${this.bindableDevices.map( + (device, idx) => html` + + ${device.user_given_name + ? device.user_given_name + : device.name} + + ` + )} +
    ${this._showHelp ? html` @@ -111,7 +113,7 @@ export class ZHADeviceBindingControl extends LitElement { } private _bindTargetIndexChanged(event: ItemSelectedEvent): void { - this._bindTargetIndex = event.target!.selected; + this._bindTargetIndex = Number(event.target!.value); this._deviceToBind = this._bindTargetIndex === -1 ? undefined diff --git a/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts b/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts index 47466a42e5..f5d284ea5c 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts @@ -1,8 +1,5 @@ import "@material/mwc-button/mwc-button"; import { mdiHelpCircle } from "@mdi/js"; -import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-listbox/paper-listbox"; import { css, CSSResultGroup, @@ -13,6 +10,7 @@ import { } from "lit"; import { customElement, property, state, query } from "lit/decorators"; import type { HASSDomEvent } from "../../../../../common/dom/fire_event"; +import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import "../../../../../components/buttons/ha-call-service-button"; import { SelectionChangedEvent } from "../../../../../components/data-table/ha-data-table"; import "../../../../../components/ha-card"; @@ -95,22 +93,24 @@ export class ZHAGroupBindingControl extends LitElement {
    - - - ${this.groups.map( - (group) => html` ${group.name} ` - )} - - + ${this.groups.map( + (group, idx) => + html`${group.name} ` + )} +
    ${this._showHelp ? html` @@ -179,7 +179,7 @@ export class ZHAGroupBindingControl extends LitElement { } private _bindTargetIndexChanged(event: ItemSelectedEvent): void { - this._bindTargetIndex = event.target!.selected; + this._bindTargetIndex = Number(event.target!.value); this._groupToBind = this._bindTargetIndex === -1 ? undefined diff --git a/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts b/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts index 05e23fade9..779df732af 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-network-visualization-page.ts @@ -440,19 +440,15 @@ export class ZHANetworkVisualizationPage extends LitElement { search-input { flex: 1; + display: block; } search-input.header { - display: block; - position: relative; - top: -2px; color: var(--secondary-text-color); } ha-device-picker { flex: 1; - position: relative; - top: -4px; } .controls { diff --git a/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js b/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js index 91081d5158..865d808f66 100644 --- a/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js +++ b/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js @@ -513,6 +513,7 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) { ready() { super.ready(); + import("web-animations-js/web-animations-next-lite.min"); this.addEventListener("hass-service-called", (ev) => this.serviceCalled(ev) ); From 68a411838d0415255dec4a5550919a954b05a410 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 15 Feb 2022 16:24:54 +0100 Subject: [PATCH 081/174] This adds back mobile click accessibility (#11693) --- src/entrypoints/app.ts | 6 +++++- src/entrypoints/authorize.ts | 3 +++ src/entrypoints/custom-panel.ts | 6 +++++- src/entrypoints/onboarding.ts | 3 +++ .../integration-panels/zwave/ha-config-zwave.js | 9 +++++++++ 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/entrypoints/app.ts b/src/entrypoints/app.ts index d4d1852c20..e93a38769e 100644 --- a/src/entrypoints/app.ts +++ b/src/entrypoints/app.ts @@ -1,7 +1,11 @@ -import { setPassiveTouchGestures } from "@polymer/polymer/lib/utils/settings"; +import { + setPassiveTouchGestures, + setCancelSyntheticClickEvents, +} from "@polymer/polymer/lib/utils/settings"; import "../layouts/home-assistant"; import "../resources/ha-style"; import "../resources/roboto"; import "../util/legacy-support"; setPassiveTouchGestures(true); +setCancelSyntheticClickEvents(false); diff --git a/src/entrypoints/authorize.ts b/src/entrypoints/authorize.ts index fef05a7219..0bbfdf65eb 100644 --- a/src/entrypoints/authorize.ts +++ b/src/entrypoints/authorize.ts @@ -1,7 +1,10 @@ // Compat needs to be first import import "../resources/compatibility"; +import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings"; import "../auth/ha-authorize"; import "../resources/ha-style"; import "../resources/roboto"; import "../resources/safari-14-attachshadow-patch"; import "../resources/array.flat.polyfill"; + +setCancelSyntheticClickEvents(false); diff --git a/src/entrypoints/custom-panel.ts b/src/entrypoints/custom-panel.ts index e2da175ea8..4a40fcab04 100644 --- a/src/entrypoints/custom-panel.ts +++ b/src/entrypoints/custom-panel.ts @@ -1,5 +1,6 @@ // Compat needs to be first import import "../resources/compatibility"; +import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings"; import "../resources/safari-14-attachshadow-patch"; import { PolymerElement } from "@polymer/polymer"; @@ -15,6 +16,8 @@ import { createCustomPanelElement } from "../util/custom-panel/create-custom-pan import { loadCustomPanel } from "../util/custom-panel/load-custom-panel"; import { setCustomPanelProperties } from "../util/custom-panel/set-custom-panel-properties"; +setCancelSyntheticClickEvents(false); + declare global { interface Window { loadES5Adapter: () => Promise; @@ -47,7 +50,8 @@ function initialize( ) { const style = document.createElement("style"); - style.innerHTML = `body { margin:0; } + style.innerHTML = ` + body { margin:0; } @media (prefers-color-scheme: dark) { body { background-color: #111111; diff --git a/src/entrypoints/onboarding.ts b/src/entrypoints/onboarding.ts index 44a67890ab..78696169ca 100644 --- a/src/entrypoints/onboarding.ts +++ b/src/entrypoints/onboarding.ts @@ -1,11 +1,14 @@ // Compat needs to be first import import "../resources/compatibility"; +import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings"; import "../onboarding/ha-onboarding"; import "../resources/ha-style"; import "../resources/roboto"; import "../resources/safari-14-attachshadow-patch"; import "../resources/array.flat.polyfill"; +setCancelSyntheticClickEvents(false); + declare global { interface Window { stepsPromise: Promise; diff --git a/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js b/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js index 865d808f66..58caa941af 100644 --- a/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js +++ b/src/panels/config/integrations/integration-panels/zwave/ha-config-zwave.js @@ -4,6 +4,7 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; +import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; @@ -519,6 +520,14 @@ class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) { ); } + attached() { + setCancelSyntheticClickEvents(true); + } + + detached() { + setCancelSyntheticClickEvents(false); + } + serviceCalled(ev) { if (ev.detail.success && ev.detail.service === "set_poll_intensity") { this._saveEntity(); From e95065ed08cafbc5a950ab79bf03f946766f7f99 Mon Sep 17 00:00:00 2001 From: Matthias de Baat Date: Tue, 15 Feb 2022 16:57:26 +0100 Subject: [PATCH 082/174] Updated text part 2 (#11686) Co-authored-by: Zack Barett --- src/translations/en.json | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index d422b9df16..17053b024d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1402,7 +1402,7 @@ }, "confirm_delete_title": "Delete {dashboard_title}?", "confirm_delete_text": "Your dashboard will be permanently deleted.", - "cant_edit_yaml": "Dashboards defined in YAML cannot be edited from the UI. Change them in configuration.yaml.", + "cant_edit_yaml": "Dashboards created in YAML cannot be edited from the UI. Change them in configuration.yaml.", "cant_edit_default": "The default Lovelace dashboard, Overview, cannot be edited from the UI. You can hide it by setting another dashboard as default.", "detail": { "edit_dashboard": "Edit dashboard", @@ -1506,8 +1506,8 @@ "themes": "Themes" }, "server_management": { - "heading": "Server management", - "introduction": "Control your Home Assistant.", + "heading": "Home Assistant", + "introduction": "Restarting Home Assistant will stop your dashboard and automations. After the reboot, each configuration will be reloaded.", "restart": "Restart", "confirm_restart": "Are you sure you want to restart Home Assistant?", "stop": "Stop", @@ -1524,8 +1524,8 @@ "learn_more": "Learn more about automations", "pick_automation": "Pick automation to edit", "no_automations": "We couldn’t find any automations", - "add_automation": "Add automation", - "only_editable": "Only automations defined in automations.yaml are editable.", + "add_automation": "Create automation", + "only_editable": "Only automations in automations.yaml are editable.", "dev_only_editable": "Only automations that have a unique ID assigned are debuggable.", "edit_automation": "Edit automation", "dev_automation": "Debug automation", @@ -2397,10 +2397,10 @@ "person": { "caption": "People", "description": "Manage the people that Home Assistant tracks", - "introduction": "Here you can define each person of interest in Home Assistant.", - "note_about_persons_configured_in_yaml": "Note: people configured via configuration.yaml cannot be edited via the UI.", + "introduction": "Here you can add each person of interest in Home Assistant.", + "note_about_persons_configured_in_yaml": "People configured via configuration.yaml cannot be edited via the UI.", "learn_more": "Learn more about people", - "no_persons_created_yet": "Looks like you have not created any people yet.", + "no_persons_created_yet": "Looks like you have not added any people yet.", "create_person": "Create Person", "add_person": "Add Person", "confirm_delete": "Are you sure you want to delete this person?", @@ -2431,8 +2431,8 @@ "caption": "Zones", "description": "Manage the zones you want to track people in", "introduction": "Zones allow you to specify certain regions on earth. When a person is within a zone, the state will take the name from the zone. Zones can also be used as a trigger or condition inside automation setups.", - "no_zones_created_yet": "Looks like you have not created any zones yet.", - "create_zone": "Create Zone", + "no_zones_created_yet": "Looks like you have not added any zones yet.", + "create_zone": "Add Zone", "add_zone": "Add Zone", "edit_zone": "Edit Zone", "confirm_delete": "Are you sure you want to delete this zone?", @@ -2453,7 +2453,7 @@ "passive_note": "Passive zones are hidden in the frontend and are not used as location for device trackers. This is useful if you just want to use it for automations.", "required_error_msg": "This field is required", "delete": "Delete", - "create": "Create", + "create": "Add", "update": "Update" } }, @@ -2770,10 +2770,10 @@ "update_button": "Update Configuration" }, "add_device_page": { - "spinner": "Searching for ZHA Zigbee devices…", + "spinner": "Searching for Zigbee devices…", "pairing_mode": "Make sure your devices are in pairing mode. Check the instructions of your device on how to do this.", "discovered_text": "Devices will show up here once discovered.", - "no_devices_found": "No devices were found, make sure they are in pairing mode and keep them awake while discovering is running.", + "no_devices_found": "No devices were found, make sure they are in pairing mode and keep them awake while Home Assistant is searching.", "search_again": "Search Again" }, "add_device": "Add Device", @@ -2813,20 +2813,20 @@ "caption": "Network" }, "groups": { - "add_group": "Add Group", + "add_group": "Create Group", "caption": "Groups", "groups": "Groups", "group_id": "Group ID", - "members": "Members", + "members": "Devices", "group_info": "Group Information", "group_details": "Here are all the details for the selected Zigbee group.", "group_not_found": "Group not found!", - "add_members": "Add Members", - "remove_members": "Remove Members", - "removing_members": "Removing Members", - "create_group_details": "Enter the required details to create a new zigbee group", + "add_members": "Add Devices", + "remove_members": "Remove Device", + "removing_members": "Removing Devices", + "create_group_details": "Enter the required details to create a new Zigbee group", "group_name_placeholder": "Group Name", - "create_group": "Zigbee Home Automation - Create Group", + "create_group": "Create Group", "create": "Create Group", "creating_group": "Creating Group", "delete": "Delete Group" @@ -3009,7 +3009,7 @@ }, "add_node": { "title": "Add a Z-Wave Device", - "searching_device": "Searching for device", + "searching_device": "Searching for devices...", "follow_device_instructions": "Follow the directions that came with your device to trigger pairing on the device.", "choose_inclusion_strategy": "How do you want to add your device", "qr_code": "QR Code", From f5feb1d8aa7a2ba9cd9af66fee318c314099df59 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Tue, 15 Feb 2022 11:42:46 -0500 Subject: [PATCH 083/174] Set initial focus for lovelace dialogs (#11667) Co-authored-by: Bram Kragten --- .../dialog-lovelace-dashboard-detail.ts | 1 + .../card-editor/hui-dialog-create-card.ts | 1 + .../card-editor/hui-dialog-delete-card.ts | 6 +++++- .../editor/card-editor/hui-dialog-edit-card.ts | 3 ++- .../card-editor/hui-dialog-suggest-card.ts | 6 +++++- .../hui-dialog-create-headerfooter.ts | 18 ++++++++++++++++-- .../lovelace/editor/hui-dialog-save-config.ts | 2 ++ .../hui-dialog-edit-lovelace.ts | 1 + .../select-view/hui-dialog-select-view.ts | 9 +++++++-- .../editor/view-editor/hui-dialog-edit-view.ts | 2 +- 10 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts b/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts index 6e3aa60a52..39b449d3d7 100644 --- a/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts +++ b/src/panels/config/lovelace/dashboards/dialog-lovelace-dashboard-detail.ts @@ -207,6 +207,7 @@ export class DialogLovelaceDashboardDetail extends LitElement { slot="primaryAction" @click=${this._updateDashboard} .disabled=${urlInvalid || titleInvalid || this._submitting} + dialogInitialFocus > ${this._params.urlPath ? this._params.dashboard?.id diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts index 81d783000e..be2e3c9461 100644 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-create-card.ts @@ -94,6 +94,7 @@ export class HuiCreateDialogCard .label=${this.hass!.localize( "ui.panel.lovelace.editor.cardpicker.by_card" )} + dialogInitialFocus > - + ${this.hass!.localize("ui.common.cancel")} diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts index 730378ed32..f11310b93b 100755 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts @@ -207,6 +207,7 @@ export class HuiDialogEditCard @config-changed=${this._handleConfigChanged} @GUImode-changed=${this._handleGUIModeChanged} @editor-save=${this._save} + dialogInitialFocus >
    @@ -242,7 +243,7 @@ export class HuiDialogEditCard ` : ""}
    - + ${this.hass!.localize("ui.common.cancel")} ${this._cardConfig !== undefined && this._dirty diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts index 57ea84cb90..2ac61bee0f 100755 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-suggest-card.ts @@ -92,7 +92,11 @@ export class HuiDialogSuggestCard extends LitElement { ` : ""}
    - + ${this._params.yaml ? this.hass!.localize("ui.common.close") : this.hass!.localize("ui.common.cancel")} diff --git a/src/panels/lovelace/editor/header-footer-editor/hui-dialog-create-headerfooter.ts b/src/panels/lovelace/editor/header-footer-editor/hui-dialog-create-headerfooter.ts index 0a3cc7f721..dfeb478994 100644 --- a/src/panels/lovelace/editor/header-footer-editor/hui-dialog-create-headerfooter.ts +++ b/src/panels/lovelace/editor/header-footer-editor/hui-dialog-create-headerfooter.ts @@ -56,15 +56,20 @@ export class HuiCreateDialogHeaderFooter >
    ${headerFooterElements.map( - (headerFooter) => + (headerFooter, index) => html` -
    +
    ${this.hass!.localize( `ui.panel.lovelace.editor.header-footer.types.${headerFooter.type}.name` )} @@ -83,6 +88,15 @@ export class HuiCreateDialogHeaderFooter } private async _handleHeaderFooterPicked(ev: CustomEvent): Promise { + if ( + ev instanceof KeyboardEvent && + ev.key !== "Enter" && + ev.key !== " " && + ev.key !== "Spacebar" + ) { + return; + } + const type = (ev.currentTarget as any).type; let config: LovelaceHeaderFooterConfig = { type }; diff --git a/src/panels/lovelace/editor/hui-dialog-save-config.ts b/src/panels/lovelace/editor/hui-dialog-save-config.ts index da724a1adc..d12d38fdb7 100644 --- a/src/panels/lovelace/editor/hui-dialog-save-config.ts +++ b/src/panels/lovelace/editor/hui-dialog-save-config.ts @@ -93,6 +93,7 @@ export class HuiSaveConfig extends LitElement implements HassDialog { ` @@ -115,6 +116,7 @@ export class HuiSaveConfig extends LitElement implements HassDialog { `}
    diff --git a/src/panels/lovelace/editor/lovelace-editor/hui-dialog-edit-lovelace.ts b/src/panels/lovelace/editor/lovelace-editor/hui-dialog-edit-lovelace.ts index 606d822967..5c5e3aa4b7 100644 --- a/src/panels/lovelace/editor/lovelace-editor/hui-dialog-edit-lovelace.ts +++ b/src/panels/lovelace/editor/lovelace-editor/hui-dialog-edit-lovelace.ts @@ -50,6 +50,7 @@ export class HuiDialogEditLovelace extends LitElement { .hass=${this.hass} .config=${this._config} @lovelace-config-changed=${this._ConfigChanged} + dialogInitialFocus >
    diff --git a/src/panels/lovelace/editor/select-view/hui-dialog-select-view.ts b/src/panels/lovelace/editor/select-view/hui-dialog-select-view.ts index 6a8984b322..3d763e3b1b 100644 --- a/src/panels/lovelace/editor/select-view/hui-dialog-select-view.ts +++ b/src/panels/lovelace/editor/select-view/hui-dialog-select-view.ts @@ -79,6 +79,7 @@ export class HuiDialogSelectView extends LitElement { @closed=${stopPropagation} fixedMenuPosition naturalMenuWidth + dialogInitialFocus > 1 ? html` - + ${this._config.views.map( (view, idx) => html` No config found.
    `} - + ${this.hass!.localize("ui.common.cancel")} diff --git a/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts index 3b981ee67a..6b54de03dd 100644 --- a/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts +++ b/src/panels/lovelace/editor/view-editor/hui-dialog-edit-view.ts @@ -183,7 +183,7 @@ export class HuiDialogEditView extends LitElement { .selected=${this._curTabIndex} @selected-item-changed=${this._handleTabSelected} > - ${this.hass!.localize( "ui.panel.lovelace.editor.edit_view.tab_settings" )} Date: Tue, 15 Feb 2022 20:09:34 +0100 Subject: [PATCH 084/174] Migrate all lovelace elements to mwc (#11695) Co-authored-by: Zack Barett --- src/components/ha-date-input.ts | 13 +++---- .../lovelace/cards/hui-alarm-panel-card.ts | 20 +++++++---- .../lovelace/cards/hui-shopping-list-card.ts | 32 +++++++++-------- .../hui-input-number-entity-row.ts | 34 ++++++------------- .../entity-rows/hui-input-text-entity-row.ts | 22 +++++------- .../entity-rows/hui-number-entity-row.ts | 31 ++++++----------- .../shopping-list/ha-panel-shopping-list.ts | 4 ++- src/state-summary/state-card-input_number.js | 19 ++++++----- src/state-summary/state-card-input_text.js | 16 +++++---- src/state-summary/state-card-number.js | 16 +++++---- 10 files changed, 99 insertions(+), 108 deletions(-) diff --git a/src/components/ha-date-input.ts b/src/components/ha-date-input.ts index 08c2590126..42308ee6c7 100644 --- a/src/components/ha-date-input.ts +++ b/src/components/ha-date-input.ts @@ -1,11 +1,11 @@ import { mdiCalendar } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { formatDateNumeric } from "../common/datetime/format_date"; import { fireEvent } from "../common/dom/fire_event"; import { HomeAssistant } from "../types"; import "./ha-svg-icon"; +import "./ha-textfield"; const loadDatePickerDialog = () => import("./ha-dialog-date-picker"); @@ -38,17 +38,17 @@ export class HaDateInput extends LitElement { @property() public label?: string; render() { - return html` - - `; + + `; } private _openDialog() { @@ -73,9 +73,6 @@ export class HaDateInput extends LitElement { static get styles(): CSSResultGroup { return css` - paper-input { - width: 110px; - } ha-svg-icon { color: var(--secondary-text-color); } diff --git a/src/panels/lovelace/cards/hui-alarm-panel-card.ts b/src/panels/lovelace/cards/hui-alarm-panel-card.ts index ba67d54d79..98b33d540f 100644 --- a/src/panels/lovelace/cards/hui-alarm-panel-card.ts +++ b/src/panels/lovelace/cards/hui-alarm-panel-card.ts @@ -1,5 +1,3 @@ -import "@polymer/paper-input/paper-input"; -import type { PaperInputElement } from "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, @@ -15,6 +13,8 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon"; import "../../../components/ha-card"; import "../../../components/ha-chip"; +import type { HaTextField } from "../../../components/ha-textfield"; +import "../../../components/ha-textfield"; import { callAlarmAction, FORMAT_NUMBER, @@ -61,7 +61,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { @state() private _config?: AlarmPanelCardConfig; - @query("#alarmCode") private _input?: PaperInputElement; + @query("#alarmCode") private _input?: HaTextField; public async getCardSize(): Promise { if (!this._config || !this.hass) { @@ -182,14 +182,14 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { ${!stateObj.attributes.code_format ? html`` : html` - + > `} ${stateObj.attributes.code_format !== FORMAT_NUMBER ? html`` @@ -263,6 +263,9 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { padding-bottom: 16px; position: relative; height: 100%; + display: flex; + flex-direction: column; + align-items: center; box-sizing: border-box; --alarm-color-disarmed: var(--label-badge-green); --alarm-color-pending: var(--label-badge-yellow); @@ -282,6 +285,8 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { display: flex; justify-content: space-between; align-items: center; + width: 100%; + box-sizing: border-box; } .unavailable { @@ -319,8 +324,9 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { } } - paper-input { - margin: 0 auto 8px; + ha-textfield { + display: block; + margin: 8px; max-width: 150px; text-align: center; } diff --git a/src/panels/lovelace/cards/hui-shopping-list-card.ts b/src/panels/lovelace/cards/hui-shopping-list-card.ts index 7063c86a67..5c3e2dc444 100644 --- a/src/panels/lovelace/cards/hui-shopping-list-card.ts +++ b/src/panels/lovelace/cards/hui-shopping-list-card.ts @@ -1,5 +1,4 @@ import { mdiDrag, mdiNotificationClearAll, mdiPlus, mdiSort } from "@mdi/js"; -import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, @@ -17,6 +16,7 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen import "../../../components/ha-card"; import "../../../components/ha-svg-icon"; import "../../../components/ha-checkbox"; +import "../../../components/ha-textfield"; import { addItem, clearItems, @@ -29,6 +29,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { HomeAssistant } from "../../../types"; import { LovelaceCard, LovelaceCardEditor } from "../types"; import { SensorCardConfig, ShoppingListCardConfig } from "./types"; +import type { HaTextField } from "../../../components/ha-textfield"; let Sortable; @@ -123,14 +124,13 @@ class HuiShoppingListCard @click=${this._addItem} > - + > - + >
    ` )} @@ -213,12 +213,12 @@ class HuiShoppingListCard .itemId=${item.id} @change=${this._completeItem} > - + > ${this._reordering ? html` ${computeStateDisplay( @@ -108,23 +107,18 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow { ` : html`
    - - - ${stateObj.attributes.unit_of_measurement} - - +
    `} @@ -146,7 +140,7 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow { min-width: 45px; text-align: end; } - paper-input { + ha-textfield { text-align: end; } ha-slider { @@ -185,19 +179,11 @@ class HuiInputNumberEntityRow extends LitElement implements LovelaceRow { } } - private get _inputElement(): { value: string } { - // linter recommended the following syntax - return this.shadowRoot!.getElementById("input") as unknown as { - value: string; - }; - } - - private _selectedValueChanged(): void { - const element = this._inputElement; + private _selectedValueChanged(ev): void { const stateObj = this.hass!.states[this._config!.entity]; - if (element.value !== stateObj.state) { - setValue(this.hass!, stateObj.entity_id, element.value!); + if (ev.target.value !== stateObj.state) { + setValue(this.hass!, stateObj.entity_id, ev.target.value); } } } diff --git a/src/panels/lovelace/entity-rows/hui-input-text-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-text-entity-row.ts index a8af772a68..87df1e0362 100644 --- a/src/panels/lovelace/entity-rows/hui-input-text-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-text-entity-row.ts @@ -1,4 +1,3 @@ -import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity"; @@ -8,6 +7,7 @@ import { hasConfigOrEntityChanged } from "../common/has-changed"; import "../components/hui-generic-entity-row"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { EntityConfig, LovelaceRow } from "./types"; +import "../../../components/ha-textfield"; @customElement("hui-input-text-entity-row") class HuiInputTextEntityRow extends LitElement implements LovelaceRow { @@ -43,8 +43,7 @@ class HuiInputTextEntityRow extends LitElement implements LovelaceRow { return html` - + > `; } - private get _inputEl(): PaperInputElement { - return this.shadowRoot!.querySelector("paper-input") as PaperInputElement; - } - private _selectedValueChanged(ev): void { - const element = this._inputEl; const stateObj = this.hass!.states[this._config!.entity]; + const newValue = ev.target.value; + // Filter out invalid text states - if (element.value && UNAVAILABLE_STATES.includes(element.value)) { - element.value = stateObj.state; + if (newValue && UNAVAILABLE_STATES.includes(newValue)) { + ev.target.value = stateObj.state; return; } - if (element.value !== stateObj.state) { - setValue(this.hass!, stateObj.entity_id, element.value!); + if (newValue !== stateObj.state) { + setValue(this.hass!, stateObj.entity_id, newValue); } ev.target.blur(); diff --git a/src/panels/lovelace/entity-rows/hui-number-entity-row.ts b/src/panels/lovelace/entity-rows/hui-number-entity-row.ts index 65b306137a..22f5c34eb6 100644 --- a/src/panels/lovelace/entity-rows/hui-number-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-number-entity-row.ts @@ -1,4 +1,3 @@ -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, @@ -20,6 +19,7 @@ import { installResizeObserver } from "../common/install-resize-observer"; import "../components/hui-generic-entity-row"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import { EntityConfig, LovelaceRow } from "./types"; +import "../../../components/ha-textfield"; @customElement("hui-number-entity-row") class HuiNumberEntityRow extends LitElement implements LovelaceRow { @@ -98,7 +98,6 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow { pin @change=${this._selectedValueChanged} ignore-bar-touch - id="input" > ${computeStateDisplay( @@ -112,20 +111,18 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow { ` : html`
    - - ${stateObj.attributes.unit_of_measurement} + >
    `} @@ -148,7 +145,7 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow { min-width: 45px; text-align: end; } - paper-input { + ha-textfield { text-align: end; } ha-slider { @@ -187,19 +184,11 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow { } } - private get _inputElement(): { value: string } { - // linter recommended the following syntax - return this.shadowRoot!.getElementById("input") as unknown as { - value: string; - }; - } - - private _selectedValueChanged(): void { - const element = this._inputElement; + private _selectedValueChanged(ev): void { const stateObj = this.hass!.states[this._config!.entity]; - if (element.value !== stateObj.state) { - setValue(this.hass!, stateObj.entity_id, element.value!); + if (ev.target.value !== stateObj.state) { + setValue(this.hass!, stateObj.entity_id, ev.target.value!); } } } diff --git a/src/panels/shopping-list/ha-panel-shopping-list.ts b/src/panels/shopping-list/ha-panel-shopping-list.ts index a852182225..7a3947dd44 100644 --- a/src/panels/shopping-list/ha-panel-shopping-list.ts +++ b/src/panels/shopping-list/ha-panel-shopping-list.ts @@ -88,10 +88,12 @@ class PanelShoppingList extends LitElement { haStyle, css` :host { - --mdc-theme-primary: var(--app-header-text-color); display: block; height: 100%; } + app-header { + --mdc-theme-primary: var(--app-header-text-color); + } :host([narrow]) app-toolbar mwc-button { width: 65px; } diff --git a/src/state-summary/state-card-input_number.js b/src/state-summary/state-card-input_number.js index afa4959bbb..2fabea717a 100644 --- a/src/state-summary/state-card-input_number.js +++ b/src/state-summary/state-card-input_number.js @@ -1,6 +1,5 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import { IronResizableBehavior } from "@polymer/iron-resizable-behavior/iron-resizable-behavior"; -import "@polymer/paper-input/paper-input"; import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ @@ -8,6 +7,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element"; import { computeStateDisplay } from "../common/entity/compute_state_display"; import "../components/entity/state-info"; import "../components/ha-slider"; +import "../components/ha-textfield"; class StateCardInputNumber extends mixinBehaviors( [IronResizableBehavior], @@ -33,7 +33,7 @@ class StateCardInputNumber extends mixinBehaviors( ha-slider[hidden] { display: none !important; } - paper-input { + ha-textfield { text-align: right; margin-left: auto; } @@ -54,23 +54,22 @@ class StateCardInputNumber extends mixinBehaviors( ignore-bar-touch="" > - +
    ${this.stateInfoTemplate} - - +
    `; } @@ -68,6 +68,10 @@ class StateCardInputText extends PolymerElement { this.value = newVal.state; } + onInput(ev) { + this.value = ev.target.value; + } + selectedValueChanged() { if (this.value === this.stateObj.state) { return; diff --git a/src/state-summary/state-card-number.js b/src/state-summary/state-card-number.js index 24a3e64e0c..fd9e9dda2e 100644 --- a/src/state-summary/state-card-number.js +++ b/src/state-summary/state-card-number.js @@ -1,12 +1,12 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import { IronResizableBehavior } from "@polymer/iron-resizable-behavior/iron-resizable-behavior"; -import "@polymer/paper-input/paper-input"; import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; import "../components/entity/state-info"; import "../components/ha-slider"; +import "../components/ha-textfield"; class StateCardNumber extends mixinBehaviors( [IronResizableBehavior], @@ -32,7 +32,7 @@ class StateCardNumber extends mixinBehaviors( ha-slider[hidden] { display: none !important; } - paper-input { + ha-textfield { text-align: right; margin-left: auto; } @@ -53,20 +53,20 @@ class StateCardNumber extends mixinBehaviors( ignore-bar-touch="" > - + @@ -178,6 +178,10 @@ class StateCardNumber extends mixinBehaviors( } } + onInput(ev) { + this.value = ev.target.value; + } + selectedValueChanged() { if (this.value === Number(this.stateObj.state)) { return; From 965fc9bc4e98e951ffed5b299524da8e30952f10 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Feb 2022 11:16:51 -0800 Subject: [PATCH 085/174] Fix import --- src/components/ha-form/ha-form-multi_select.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ha-form/ha-form-multi_select.ts b/src/components/ha-form/ha-form-multi_select.ts index 4777eeb76b..3411977f51 100644 --- a/src/components/ha-form/ha-form-multi_select.ts +++ b/src/components/ha-form/ha-form-multi_select.ts @@ -11,7 +11,8 @@ import { import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import "../ha-button-menu"; -import { HaCheckListItem } from "../ha-check-list-item"; +import "../ha-check-list-item"; +import type { HaCheckListItem } from "../ha-check-list-item"; import "../ha-checkbox"; import type { HaCheckbox } from "../ha-checkbox"; import "../ha-formfield"; From 16d8eb0be34af2a9246a97ebf75fd4f3d3fa44b8 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 15 Feb 2022 14:53:20 -0600 Subject: [PATCH 086/174] Clean up some imports (#11696) --- src/components/entity/ha-statistic-picker.ts | 1 - src/components/ha-picture-upload.ts | 1 - src/components/ha-service-control.ts | 3 --- .../automation/action/types/ha-automation-action-choose.ts | 1 - .../automation/action/types/ha-automation-action-service.ts | 1 - .../automation/trigger/types/ha-automation-trigger-tag.ts | 1 - src/panels/config/cloud/login/cloud-login.ts | 1 - src/panels/config/core/ha-config-section-core.js | 1 - src/panels/config/server_control/ha-config-server-control.ts | 1 - 9 files changed, 11 deletions(-) diff --git a/src/components/entity/ha-statistic-picker.ts b/src/components/entity/ha-statistic-picker.ts index a3965023a0..76965ecee5 100644 --- a/src/components/entity/ha-statistic-picker.ts +++ b/src/components/entity/ha-statistic-picker.ts @@ -1,4 +1,3 @@ -import "@polymer/paper-input/paper-input"; import { HassEntity } from "home-assistant-js-websocket"; import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { ComboBoxLitRenderer } from "lit-vaadin-helpers"; diff --git a/src/components/ha-picture-upload.ts b/src/components/ha-picture-upload.ts index b18852cc3c..15d2fef7ae 100644 --- a/src/components/ha-picture-upload.ts +++ b/src/components/ha-picture-upload.ts @@ -1,5 +1,4 @@ import { mdiImagePlus } from "@mdi/js"; -import "@polymer/paper-input/paper-input-container"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../common/dom/fire_event"; diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index badc1bb488..ee1692e567 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -489,9 +489,6 @@ export class HaServiceControl extends LitElement { margin: var(--service-control-padding, 0 16px); padding: 16px 0; } - :host(:not([narrow])) ha-settings-row paper-input { - width: 60%; - } :host(:not([narrow])) ha-settings-row ha-selector { width: 60%; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts index f239e823c7..43e360c459 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -1,5 +1,4 @@ import { mdiDelete } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; diff --git a/src/panels/config/automation/action/types/ha-automation-action-service.ts b/src/panels/config/automation/action/types/ha-automation-action-service.ts index c67e94195f..251fe0f5f1 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-service.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-service.ts @@ -1,4 +1,3 @@ -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { any, assert, object, optional, string } from "superstruct"; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts index 90772dccfe..f79bbd1619 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts @@ -1,6 +1,5 @@ import "@material/mwc-list/mwc-list-item"; import "@material/mwc-select"; -import "@polymer/paper-input/paper-input"; import { html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; diff --git a/src/panels/config/cloud/login/cloud-login.ts b/src/panels/config/cloud/login/cloud-login.ts index 991de20243..236e0c3763 100644 --- a/src/panels/config/cloud/login/cloud-login.ts +++ b/src/panels/config/cloud/login/cloud-login.ts @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; import { css, html, LitElement, TemplateResult } from "lit"; diff --git a/src/panels/config/core/ha-config-section-core.js b/src/panels/config/core/ha-config-section-core.js index 6c74b6f539..8a807fa346 100644 --- a/src/panels/config/core/ha-config-section-core.js +++ b/src/panels/config/core/ha-config-section-core.js @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; diff --git a/src/panels/config/server_control/ha-config-server-control.ts b/src/panels/config/server_control/ha-config-server-control.ts index 5102f5e1f0..9c0011c049 100644 --- a/src/panels/config/server_control/ha-config-server-control.ts +++ b/src/panels/config/server_control/ha-config-server-control.ts @@ -1,7 +1,6 @@ import "@material/mwc-button"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { componentsWithService } from "../../../common/config/components_with_service"; From 6563984fdd76e64bcfe6a35deaa2b13a8e310f10 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 16 Feb 2022 16:20:19 +0100 Subject: [PATCH 087/174] Convert triple dots to single char in translations (#11697) --- src/translations/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index 17053b024d..df7573c610 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3009,7 +3009,7 @@ }, "add_node": { "title": "Add a Z-Wave Device", - "searching_device": "Searching for devices...", + "searching_device": "Searching for devices…", "follow_device_instructions": "Follow the directions that came with your device to trigger pairing on the device.", "choose_inclusion_strategy": "How do you want to add your device", "qr_code": "QR Code", @@ -3168,7 +3168,7 @@ "reset_focus": "Reset focus" }, "energy": { - "loading": "Loading...", + "loading": "Loading…", "no_data": "There is no data to show. It can take up to 2 hours for new data to arrive after you configure your energy dashboard.", "no_data_period": "There is no data for this period.", "energy_usage_graph": { From f43655eea552019035612903ef358fbef42b7cd7 Mon Sep 17 00:00:00 2001 From: Josh McCarty Date: Wed, 16 Feb 2022 08:54:12 -0700 Subject: [PATCH 088/174] Fixes remote icon state color (#11698) --- src/common/style/icon_color_css.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/style/icon_color_css.ts b/src/common/style/icon_color_css.ts index a0905c0ea9..7e00bf1c77 100644 --- a/src/common/style/icon_color_css.ts +++ b/src/common/style/icon_color_css.ts @@ -15,6 +15,7 @@ export const iconColorCSS = css` ha-state-icon[data-domain="media_player"][data-state="on"], ha-state-icon[data-domain="media_player"][data-state="paused"], ha-state-icon[data-domain="media_player"][data-state="playing"], + ha-state-icon[data-domain="remote"][data-state="on"], ha-state-icon[data-domain="script"][data-state="on"], ha-state-icon[data-domain="sun"][data-state="above_horizon"], ha-state-icon[data-domain="switch"][data-state="on"], From 89f4fe9d206d02123fe13bac5b84650d7313d1f0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 16 Feb 2022 20:47:21 +0100 Subject: [PATCH 089/174] Convert scene action to service call (#11705) * Convert scene action to service call * fix describeAction * rename to metadata * Update script.ts --- src/data/script.ts | 20 +++++++++++--- src/data/script_i18n.ts | 15 ++++++++--- .../action/ha-automation-action-row.ts | 26 ++++++++++++++++--- .../types/ha-automation-action-scene.ts | 24 ++++++++++++++--- 4 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/data/script.ts b/src/data/script.ts index 6b83157bde..81a90d0ed2 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -70,10 +70,15 @@ export interface DelayAction { delay: number | Partial | string; } -export interface SceneAction { +export interface ServiceSceneAction extends ServiceAction { + service: "scene.turn_on"; + metadata: Record; +} +export interface LegacySceneAction { alias?: string; scene: string; } +export type SceneAction = ServiceSceneAction | LegacySceneAction; export interface WaitAction { alias?: string; @@ -153,7 +158,8 @@ export interface ActionTypes { check_condition: Condition; fire_event: EventAction; device_action: DeviceAction; - activate_scene: SceneAction; + legacy_activate_scene: LegacySceneAction; + activate_scene: ServiceSceneAction; repeat: RepeatAction; choose: ChooseAction; wait_for_trigger: WaitForTriggerAction; @@ -218,7 +224,7 @@ export const getActionType = (action: Action): ActionType => { return "device_action"; } if ("scene" in action) { - return "activate_scene"; + return "legacy_activate_scene"; } if ("repeat" in action) { return "repeat"; @@ -233,6 +239,14 @@ export const getActionType = (action: Action): ActionType => { return "variables"; } if ("service" in action) { + if ("metadata" in action) { + if ( + (action as ServiceAction).service === "scene.turn_on" && + !Array.isArray((action as ServiceAction)?.target?.entity_id) + ) { + return "activate_scene"; + } + } return "service"; } return "unknown"; diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index 338cb7c234..a8f2b76356 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -11,7 +11,8 @@ import { DelayAction, EventAction, getActionType, - SceneAction, + LegacySceneAction, + ServiceSceneAction, VariablesAction, WaitForTriggerAction, } from "./script"; @@ -102,14 +103,22 @@ export const describeAction = ( return `Delay ${duration}`; } - if (actionType === "activate_scene") { - const config = action as SceneAction; + if (actionType === "legacy_activate_scene") { + const config = action as LegacySceneAction; const sceneStateObj = hass.states[config.scene]; return `Activate scene ${ sceneStateObj ? computeStateName(sceneStateObj) : config.scene }`; } + if (actionType === "activate_scene") { + const config = action as ServiceSceneAction; + const sceneStateObj = hass.states[config.target!.entity_id as string]; + return `Activate scene ${ + sceneStateObj ? computeStateName(sceneStateObj) : config.target!.entity_id + }`; + } + if (actionType === "wait_for_trigger") { const config = action as WaitForTriggerAction; return `Wait for ${ensureArray(config.wait_for_trigger) diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index c4e90994bf..5c103e9e67 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -16,7 +16,7 @@ import "../../../../components/ha-card"; import "../../../../components/ha-alert"; import "../../../../components/ha-icon-button"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; -import type { Action } from "../../../../data/script"; +import type { Action, ServiceSceneAction } from "../../../../data/script"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; @@ -44,8 +44,28 @@ const OPTIONS = [ "device_id", ]; -const getType = (action: Action | undefined) => - action ? OPTIONS.find((option) => option in action) : undefined; +const getType = (action: Action | undefined) => { + if (!action) { + return undefined; + } + if ("metadata" in action && action.service) { + switch (action.service) { + case "scene.turn_on": + // we dont support arrays of entities + if ( + !Array.isArray( + (action as unknown as ServiceSceneAction).target?.entity_id + ) + ) { + return "scene"; + } + break; + default: + break; + } + } + return OPTIONS.find((option) => option in action); +}; declare global { // for fire event diff --git a/src/panels/config/automation/action/types/ha-automation-action-scene.ts b/src/panels/config/automation/action/types/ha-automation-action-scene.ts index 6af3f06c40..a9d02b83ba 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-scene.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-scene.ts @@ -16,11 +16,23 @@ export class HaSceneAction extends LitElement implements ActionElement { @property() public action!: SceneAction; public static get defaultConfig(): SceneAction { - return { scene: "" }; + return { + service: "scene.turn_on", + target: { + entity_id: "", + }, + metadata: {}, + }; } protected render() { - const { scene } = this.action; + let scene; + + if ("scene" in this.action) { + scene = this.action.scene; + } else { + scene = this.action.target?.entity_id; + } return html` ) { ev.stopPropagation(); fireEvent(this, "value-changed", { - value: { ...this.action, scene: ev.detail.value }, + value: { + service: "scene.turn_on", + target: { + entity_id: ev.detail.value, + }, + metadata: {}, + }, }); } } From 9dec0f8ccdd140adc52cde38f3415593a1100597 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 16 Feb 2022 21:47:49 +0100 Subject: [PATCH 090/174] Fix mode selection in automation editor (#11707) --- src/panels/config/automation/manual-automation-editor.ts | 4 ++++ .../trigger/types/ha-automation-trigger-device.ts | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index af9b6d51c1..755f7b639a 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -85,6 +85,7 @@ export class HaManualAutomationEditor extends LitElement { )} .value=${this.config.mode ? MODES.indexOf(this.config.mode) : 0} @selected=${this._modeChanged} + fixedMenuPosition > ${MODES.map( (mode) => html` @@ -317,6 +318,9 @@ export class HaManualAutomationEditor extends LitElement { ha-entity-toggle { margin-right: 8px; } + mwc-select { + margin-top: 8px; + } `, ]; } diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts index 43ddbeeb14..82ee809f72 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts @@ -1,4 +1,4 @@ -import { html, LitElement } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; @@ -150,6 +150,13 @@ export class HaDeviceTrigger extends LitElement { `ui.panel.config.automation.editor.triggers.type.device.extra_fields.${schema.name}` ) || schema.name; } + + static styles = css` + ha-device-picker { + display: block; + margin-bottom: 8px; + } + `; } declare global { From 53607fe8c62706888d322c2c496ba06a7ebe53f2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 16 Feb 2022 20:01:51 -0800 Subject: [PATCH 091/174] Remove duplicate gallery page (#11711) --- gallery/sidebar.js | 1 - .../src/pages/automation/selectors.markdown | 3 - gallery/src/pages/automation/selectors.ts | 102 ------------------ .../src/pages/components/ha-selector.markdown | 2 +- 4 files changed, 1 insertion(+), 107 deletions(-) delete mode 100644 gallery/src/pages/automation/selectors.markdown delete mode 100644 gallery/src/pages/automation/selectors.ts diff --git a/gallery/sidebar.js b/gallery/sidebar.js index 84bd8f8eff..02ffeb2aa8 100644 --- a/gallery/sidebar.js +++ b/gallery/sidebar.js @@ -20,7 +20,6 @@ module.exports = [ "editor-trigger", "editor-condition", "editor-action", - "selectors", "trace", "trace-timeline", ], diff --git a/gallery/src/pages/automation/selectors.markdown b/gallery/src/pages/automation/selectors.markdown deleted file mode 100644 index 6342871182..0000000000 --- a/gallery/src/pages/automation/selectors.markdown +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Selectors ---- diff --git a/gallery/src/pages/automation/selectors.ts b/gallery/src/pages/automation/selectors.ts deleted file mode 100644 index 1e66055887..0000000000 --- a/gallery/src/pages/automation/selectors.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* eslint-disable lit/no-template-arrow */ -import { LitElement, TemplateResult, html } from "lit"; -import { customElement, state } from "lit/decorators"; -import { provideHass } from "../../../../src/fake_data/provide_hass"; -import type { HomeAssistant } from "../../../../src/types"; -import "../../components/demo-black-white-row"; -import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; -import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry"; -import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; -import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; -import "../../../../src/panels/config/automation/trigger/ha-automation-trigger"; -import { Selector } from "../../../../src/data/selector"; -import "../../../../src/components/ha-selector/ha-selector"; - -const SCHEMAS: { name: string; selector: Selector }[] = [ - { name: "Addon", selector: { addon: {} } }, - - { name: "Entity", selector: { entity: {} } }, - { name: "Device", selector: { device: {} } }, - { name: "Area", selector: { area: {} } }, - { name: "Target", selector: { target: {} } }, - { - name: "Number", - selector: { - number: { - min: 0, - max: 10, - }, - }, - }, - { name: "Boolean", selector: { boolean: {} } }, - { name: "Time", selector: { time: {} } }, - { name: "Action", selector: { action: {} } }, - { name: "Text", selector: { text: { multiline: false } } }, - { name: "Text Multiline", selector: { text: { multiline: true } } }, - { name: "Object", selector: { object: {} } }, - { - name: "Select", - selector: { - select: { - options: ["Everyone Home", "Some Home", "All gone"], - }, - }, - }, -]; - -@customElement("demo-automation-selectors") -class DemoHaSelector extends LitElement { - @state() private hass!: HomeAssistant; - - private data: any = SCHEMAS.map(() => undefined); - - constructor() { - super(); - const hass = provideHass(this); - hass.updateTranslations(null, "en"); - hass.updateTranslations("config", "en"); - mockEntityRegistry(hass); - mockDeviceRegistry(hass); - mockAreaRegistry(hass); - mockHassioSupervisor(hass); - } - - protected render(): TemplateResult { - const valueChanged = (ev) => { - const sampleIdx = ev.target.sampleIdx; - this.data[sampleIdx] = ev.detail.value; - this.requestUpdate(); - }; - return html` - ${SCHEMAS.map( - (info, sampleIdx) => html` - - ${["light", "dark"].map( - (slot) => - html` - - ` - )} - - ` - )} - `; - } -} - -declare global { - interface HTMLElementTagNameMap { - "demo-automation-selectors": DemoHaSelector; - } -} diff --git a/gallery/src/pages/components/ha-selector.markdown b/gallery/src/pages/components/ha-selector.markdown index 2a3143ea61..6342871182 100644 --- a/gallery/src/pages/components/ha-selector.markdown +++ b/gallery/src/pages/components/ha-selector.markdown @@ -1,3 +1,3 @@ --- -title: Target Selectors +title: Selectors --- From 3a664d45a943cd726dc578aa5dbc4707ad6e27d8 Mon Sep 17 00:00:00 2001 From: Josh McCarty Date: Wed, 16 Feb 2022 21:07:45 -0700 Subject: [PATCH 092/174] Add bottom padding to config links list with safe-area-inset-bottom (#11704) --- src/panels/config/dashboard/ha-config-dashboard.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index ad05c9d55f..552692e1e3 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -192,8 +192,11 @@ class HaConfigDashboard extends LitElement { return [ haStyle, css` + ha-card:last-child { + margin-bottom: env(safe-area-inset-bottom); + } :host(:not([narrow])) ha-card:last-child { - margin-bottom: 24px; + margin-bottom: max(24px, env(safe-area-inset-bottom)); } ha-config-section { margin: auto; From bfb90632acda61ad58fb5d3ceef682c16731a341 Mon Sep 17 00:00:00 2001 From: uvjustin <46082645+uvjustin@users.noreply.github.com> Date: Thu, 17 Feb 2022 17:40:47 +0800 Subject: [PATCH 093/174] Bump hls.js to v1.1.5 (#11712) --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 63c5df94ca..a25d8209c6 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "deep-freeze": "^0.0.1", "fuse.js": "^6.0.0", "google-timezones-json": "^1.0.2", - "hls.js": "^1.0.11", + "hls.js": "^1.1.5", "home-assistant-js-websocket": "^6.0.1", "idb-keyval": "^5.1.3", "intl-messageformat": "^9.9.1", diff --git a/yarn.lock b/yarn.lock index b4a106053a..206787e049 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9075,10 +9075,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"hls.js@npm:^1.0.11": - version: 1.0.11 - resolution: "hls.js@npm:1.0.11" - checksum: 0375871cf8ffef3374f44284028d235bb122dc5ee2a36b765124e8caca1ec97ba96738ef8de261fbdecf78669e7bf3fb8aee1070f239c7791add44fad50df180 +"hls.js@npm:^1.1.5": + version: 1.1.5 + resolution: "hls.js@npm:1.1.5" + checksum: 7363eb8be6ad35be73fe3497a2fa1d493b42c76382ccc6b05f298394545eb75d6c466fb233748f3226859360d7aeea99bc5ea3e372ec074c6c2e354dcc5f6435 languageName: node linkType: hard @@ -9228,7 +9228,7 @@ fsevents@^1.2.7: gulp-merge-json: ^1.3.1 gulp-rename: ^2.0.0 gulp-zopfli-green: ^3.0.1 - hls.js: ^1.0.11 + hls.js: ^1.1.5 home-assistant-js-websocket: ^6.0.1 html-minifier: ^4.0.0 husky: ^1.3.1 From b55c7edd70b16e9ec51064492ed2ef70d3c294a0 Mon Sep 17 00:00:00 2001 From: Raman Gupta <7243222+raman325@users.noreply.github.com> Date: Thu, 17 Feb 2022 04:41:12 -0500 Subject: [PATCH 094/174] Make zwave_js config panel inclusion state aware (#11556) Co-authored-by: Bram Kragten --- src/data/zwave_js.ts | 42 ++++++++++++- .../zwave_js/zwave_js-config-dashboard.ts | 61 +++++++++++++++---- src/translations/en.json | 4 +- 3 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/data/zwave_js.ts b/src/data/zwave_js.ts index ab02e4acf8..1cc8653752 100644 --- a/src/data/zwave_js.ts +++ b/src/data/zwave_js.ts @@ -2,6 +2,19 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { HomeAssistant } from "../types"; import { DeviceRegistryEntry } from "./device_registry"; +export enum InclusionState { + /** The controller isn't doing anything regarding inclusion. */ + Idle, + /** The controller is waiting for a node to be included. */ + Including, + /** The controller is waiting for a node to be excluded. */ + Excluding, + /** The controller is busy including or excluding a node. */ + Busy, + /** The controller listening for SmartStart nodes to announce themselves. */ + SmartStart, +} + export const enum InclusionStrategy { /** * Always uses Security S2 if supported, otherwise uses Security S0 for certain devices which don't work without encryption and uses no encryption otherwise. @@ -106,16 +119,33 @@ export interface ZWaveJSNetwork { } export interface ZWaveJSClient { - state: string; + state: "connected" | "disconnected"; ws_server_url: string; server_version: string; driver_version: string; } export interface ZWaveJSController { - home_id: string; - nodes: number[]; + home_id: number; + library_version: string; + type: number; + own_node_id: number; + is_secondary: boolean; + is_using_home_id_from_other_network: boolean; + is_sis_present: boolean; + was_real_primary: boolean; + is_static_update_controller: boolean; + is_slave: boolean; + serial_api_version: string; + manufacturer_id: number; + product_id: number; + product_type: number; + supported_function_types: number[]; + suc_node_id: number; + supports_timers: boolean; is_heal_network_active: boolean; + inclusion_state: InclusionState; + nodes: number[]; } export interface ZWaveJSNodeStatus { @@ -309,6 +339,12 @@ export const stopZwaveInclusion = (hass: HomeAssistant, entry_id: string) => entry_id, }); +export const stopZwaveExclusion = (hass: HomeAssistant, entry_id: string) => + hass.callWS({ + type: "zwave_js/stop_exclusion", + entry_id, + }); + export const zwaveGrantSecurityClasses = ( hass: HomeAssistant, entry_id: string, diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts index 82ef64d6b3..1b1521f32f 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-config-dashboard.ts @@ -19,7 +19,11 @@ import { fetchZwaveNetworkStatus, fetchZwaveNodeStatus, fetchZwaveProvisioningEntries, + InclusionState, setZwaveDataCollectionPreference, + stopZwaveExclusion, + stopZwaveInclusion, + ZWaveJSClient, ZWaveJSNetwork, ZWaveJSNodeStatus, ZwaveJSProvisioningEntry, @@ -60,7 +64,7 @@ class ZWaveJSConfigDashboard extends LitElement { @state() private _provisioningEntries?: ZwaveJSProvisioningEntry[]; - @state() private _status = "unknown"; + @state() private _status?: ZWaveJSClient["state"]; @state() private _icon = mdiCircle; @@ -107,13 +111,38 @@ class ZWaveJSConfigDashboard extends LitElement { "ui.panel.config.zwave_js.dashboard.introduction" )}
    + ${this._network && + this._status === "connected" && + (this._network?.controller.inclusion_state === + InclusionState.Including || + this._network?.controller.inclusion_state === + InclusionState.Excluding) + ? html` + + ${this.hass.localize( + `ui.panel.config.zwave_js.common.in_progress_inclusion_exclusion` + )} + + + + ` + : ""} ${this._network ? html`
    - ${this._status === "connecting" + ${this._status === "disconnected" ? html`` @@ -121,13 +150,13 @@ class ZWaveJSConfigDashboard extends LitElement { `}
    - ${this._status !== "connecting" + ${this._status !== "disconnected" ? html`
    ${this.hass.localize( @@ -207,7 +236,9 @@ class ZWaveJSConfigDashboard extends LitElement {
    ${this.hass.localize( "ui.panel.config.zwave_js.common.remove_node" @@ -215,16 +246,13 @@ class ZWaveJSConfigDashboard extends LitElement { ${this.hass.localize( "ui.panel.config.zwave_js.common.heal_network" )} - + ${this.hass.localize( "ui.panel.config.zwave_js.common.reconfigure_server" )} @@ -272,10 +300,11 @@ class ZWaveJSConfigDashboard extends LitElement { .label=${this.hass.localize( "ui.panel.config.zwave_js.common.add_node" )} - .disabled=${this._status === "connecting"} extended ?rtl=${computeRTL(this.hass)} @click=${this._addNodeClicked} + .disabled=${this._status !== "connected" || + this._network?.controller.inclusion_state !== InclusionState.Idle} > @@ -412,6 +441,16 @@ class ZWaveJSConfigDashboard extends LitElement { }); } + private async _cancelInclusion() { + stopZwaveInclusion(this.hass!, this.configEntryId!); + await this._fetchData(); + } + + private async _cancelExclusion() { + stopZwaveExclusion(this.hass!, this.configEntryId!); + await this._fetchData(); + } + private _dataCollectionToggled(ev) { setZwaveDataCollectionPreference( this.hass!, diff --git a/src/translations/en.json b/src/translations/en.json index df7573c610..c5c1b5feab 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2956,7 +2956,9 @@ "add_node": "Add device", "remove_node": "Remove device", "reconfigure_server": "Re-configure Server", - "heal_network": "Heal Network" + "heal_network": "Heal Network", + "in_progress_inclusion_exclusion": "Z-Wave JS is searching for devices", + "cancel_inclusion_exclusion": "Stop Searching" }, "dashboard": { "header": "Manage your Z-Wave Network", From f9232280781900f19daf9db6226e25544a7fe0d3 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Thu, 17 Feb 2022 03:41:45 -0600 Subject: [PATCH 095/174] Fix mwc-select in lovelace editors (#11708) --- src/panels/lovelace/components/hui-theme-select-editor.ts | 2 +- .../editor/config-elements/hui-calendar-card-editor.ts | 2 ++ .../editor/config-elements/hui-conditional-card-editor.ts | 2 ++ .../editor/config-elements/hui-generic-entity-row-editor.ts | 6 +++++- src/panels/lovelace/editor/hui-element-editor.ts | 1 + .../editor/view-editor/hui-view-visibility-editor.ts | 1 - 6 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/panels/lovelace/components/hui-theme-select-editor.ts b/src/panels/lovelace/components/hui-theme-select-editor.ts index 60a3746e8c..47e05ae1bf 100644 --- a/src/panels/lovelace/components/hui-theme-select-editor.ts +++ b/src/panels/lovelace/components/hui-theme-select-editor.ts @@ -28,7 +28,7 @@ export class HuiThemeSelectEditor extends LitElement { @selected=${this._changed} @closed=${stopPropagation} fixedMenuPosition - naturalMenuWidt + naturalMenuWidth > ${this.hass!.localize( diff --git a/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts index fe59a5b16d..2142f325f4 100644 --- a/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts @@ -91,6 +91,8 @@ export class HuiCalendarCardEditor .configValue=${"initial_view"} @selected=${this._viewChanged} @closed=${stopPropagation} + naturalMenuWidth + fixedMenuPosition > ${views.map( (view) => html` diff --git a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts index c3fd28a9a1..c2a0e65806 100644 --- a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts @@ -160,6 +160,8 @@ export class HuiConditionalCardEditor .configValue=${"invert"} @selected=${this._changeCondition} @closed=${stopPropagation} + naturalMenuWidth + fixedMenuPosition > ${this.hass!.localize( diff --git a/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts b/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts index bc1b534701..40ec4f2253 100644 --- a/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts @@ -5,6 +5,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; import "../../../../components/ha-formfield"; @@ -103,9 +104,12 @@ export class HuiGenericEntityRowEditor
    ${this.hass!.localize( diff --git a/src/panels/lovelace/editor/hui-element-editor.ts b/src/panels/lovelace/editor/hui-element-editor.ts index a1bcb849a2..e06cb7d83b 100644 --- a/src/panels/lovelace/editor/hui-element-editor.ts +++ b/src/panels/lovelace/editor/hui-element-editor.ts @@ -335,6 +335,7 @@ export abstract class HuiElementEditor extends LitElement { ); } } else { + this._guiSupported = false; this.GUImode = false; } } catch (err: any) { diff --git a/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts index d4bb565835..727325d07c 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-visibility-editor.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, From cbd0ef6b651432f6f654222db3c0785b0d48b2aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Thu, 17 Feb 2022 10:43:26 +0100 Subject: [PATCH 096/174] Add signed add-on capability and adjust max rating (#11703) --- .../src/addon-view/info/hassio-addon-info.ts | 26 +++++++++++++++---- src/data/hassio/addon.ts | 3 ++- src/translations/en.json | 7 ++++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/hassio/src/addon-view/info/hassio-addon-info.ts b/hassio/src/addon-view/info/hassio-addon-info.ts index 91e072ba72..47e6087e36 100644 --- a/hassio/src/addon-view/info/hassio-addon-info.ts +++ b/hassio/src/addon-view/info/hassio-addon-info.ts @@ -9,6 +9,7 @@ import { mdiFlask, mdiHomeAssistant, mdiKey, + mdiLinkLock, mdiNetwork, mdiNumeric1, mdiNumeric2, @@ -16,6 +17,8 @@ import { mdiNumeric4, mdiNumeric5, mdiNumeric6, + mdiNumeric7, + mdiNumeric8, mdiPound, mdiShield, } from "@mdi/js"; @@ -31,6 +34,7 @@ import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-card"; import "../../../../src/components/ha-chip"; +import "../../../../src/components/ha-chip-set"; import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-settings-row"; import "../../../../src/components/ha-svg-icon"; @@ -84,6 +88,8 @@ const RATING_ICON = { 4: mdiNumeric4, 5: mdiNumeric5, 6: mdiNumeric6, + 7: mdiNumeric7, + 8: mdiNumeric8, }; @customElement("hassio-addon-info") @@ -209,7 +215,7 @@ class HassioAddonInfo extends LitElement { >`}
    -
    + ${this.addon.stage !== "stable" ? html` = 6, + yellow: [3, 4, 5].includes(Number(this.addon.rating)), + red: Number(this.addon.rating) >= 2, })} @click=${this._showMoreInfo} id="rating" @@ -364,7 +370,17 @@ class HassioAddonInfo extends LitElement { ` : ""} -
    + ${this.addon.signed + ? html` + + + ${this.supervisor.localize( + "addon.dashboard.capability.label.signed" + )} + + ` + : ""} +
    ${this.addon.description}.
    diff --git a/src/data/hassio/addon.ts b/src/data/hassio/addon.ts index ccd099edc7..0cead4ee9a 100644 --- a/src/data/hassio/addon.ts +++ b/src/data/hassio/addon.ts @@ -84,9 +84,10 @@ export interface HassioAddonDetails extends HassioAddonInfo { options: Record; privileged: any; protected: boolean; - rating: "1-6"; + rating: "1-8"; schema: HaFormSchema[] | null; services_role: string[]; + signed: boolean; slug: string; startup: AddonStartup; stdin: boolean; diff --git a/src/translations/en.json b/src/translations/en.json index c5c1b5feab..4ad76f9f98 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4287,6 +4287,10 @@ "title": "Ingress", "description": "This add-on is using Ingress to embed its interface securely into Home Assistant." }, + "signed": { + "title": "Signed", + "description": "This add-on signed and verified with Codenotary Community Attestation Service (CAS)." + }, "label": { "core": "Core", "rating": "rating", @@ -4297,7 +4301,8 @@ "host_pid": "host pid", "apparmor": "apparmor", "auth": "auth", - "ingress": "ingress" + "ingress": "ingress", + "signed": "Signed" }, "stages": { "experimental": "Experimental", From 0dc56d79830533b46c56392c18f7a2a32512fab3 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 17 Feb 2022 16:46:08 +0100 Subject: [PATCH 097/174] Add support for removing config entry from a device --- src/data/config_entries.ts | 1 + src/data/device_registry.ts | 11 +++ .../config/devices/ha-config-device-page.ts | 70 ++++++++++++++++++- src/translations/en.json | 2 + 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts index 78cbe6105a..f2e84ddac0 100644 --- a/src/data/config_entries.ts +++ b/src/data/config_entries.ts @@ -13,6 +13,7 @@ export interface ConfigEntry { | "not_loaded" | "failed_unload"; supports_options: boolean; + supports_remove_device: boolean; supports_unload: boolean; pref_disable_new_entities: boolean; pref_disable_polling: boolean; diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index 65c0bcec56..69cdb6b974 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -77,6 +77,17 @@ export const updateDeviceRegistryEntry = ( ...updates, }); +export const removeConfigEntryFromDevice = ( + hass: HomeAssistant, + deviceId: string, + configEntryId: string, +) => + hass.callWS({ + type: "config/device_registry/remove_config_entry", + device_id: deviceId, + config_entry_id: configEntryId, + }); + export const fetchDeviceRegistry = (conn) => conn.sendMessagePromise({ type: "config/device_registry/list", diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 5e966278e8..999b7aebdd 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -27,6 +27,7 @@ import { computeDeviceName, DeviceRegistryEntry, updateDeviceRegistryEntry, + removeConfigEntryFromDevice, } from "../../../data/device_registry"; import { fetchDiagnosticHandler, @@ -95,6 +96,8 @@ export class HaConfigDevicePage extends LitElement { | number | (TemplateResult | string)[]; + @state() private _deleteButtons?: (TemplateResult | string)[]; + private _device = memoizeOne( ( deviceId: string, @@ -186,10 +189,11 @@ export class HaConfigDevicePage extends LitElement { changedProps.has("entries") ) { this._diagnosticDownloadLinks = undefined; + this._deleteButtons = undefined; } if ( - this._diagnosticDownloadLinks || + (this._diagnosticDownloadLinks && this._deleteButtons) || !this.devices || !this.deviceId || !this.entries @@ -198,7 +202,9 @@ export class HaConfigDevicePage extends LitElement { } this._diagnosticDownloadLinks = Math.random(); + this._deleteButtons = [".."]; this._renderDiagnosticButtons(this._diagnosticDownloadLinks); + this._renderDeleteButtons(); } private async _renderDiagnosticButtons(requestId: number): Promise { @@ -263,6 +269,65 @@ export class HaConfigDevicePage extends LitElement { } } + private _renderDeleteButtons() { + console.log("Hello!") + const device = this._device(this.deviceId, this.devices); + + if (!device) { + return; + } + + let buttons = this._integrations(device, this.entries).map((entry) => { + console.log("Hello!") + console.log(entry.supports_remove_device) + if (entry.state !== "loaded" || !entry.supports_remove_device) { + return false; + } + return { + entry_id: entry.entry_id, + domain: entry.domain, + }; + }); + + buttons = buttons.filter(Boolean); + + if (buttons.length > 0) { + this._deleteButtons = ( + buttons as { entry_id: string; domain: string }[] + ).map( + (button) => html` + + ${buttons.length > 1 + ? this.hass.localize( + `ui.panel.config.devices.delete_device_integration`, + { + integration: domainToName( + this.hass.localize, + button.domain + ), + } + ) + : this.hass.localize( + `ui.panel.config.devices.delete_device` + )} + + ` + ); + } + } + + private async _confirmDeleteEntry(): Promise { + const confirmed = await showConfirmationDialog(this, { + text: this.hass.localize("ui.panel.config.devices.confirm_delete"), + }); + + if (!confirmed) { + return; + } + + await removeConfigEntryFromDevice(this.hass!, this.deviceId, "blabla"); + } + protected firstUpdated(changedProps) { super.firstUpdated(changedProps); loadDeviceRegistryDetailDialog(); @@ -375,6 +440,9 @@ export class HaConfigDevicePage extends LitElement { if (Array.isArray(this._diagnosticDownloadLinks)) { deviceActions.push(...this._diagnosticDownloadLinks); } + if (Array.isArray(this._deleteButtons)) { + deviceActions.push(...this._deleteButtons); + } return html` Date: Thu, 17 Feb 2022 17:12:17 +0100 Subject: [PATCH 098/174] Tweak --- .../config/devices/ha-config-device-page.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 999b7aebdd..15da420e06 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -270,16 +270,13 @@ export class HaConfigDevicePage extends LitElement { } private _renderDeleteButtons() { - console.log("Hello!") const device = this._device(this.deviceId, this.devices); if (!device) { return; } - let buttons = this._integrations(device, this.entries).map((entry) => { - console.log("Hello!") - console.log(entry.supports_remove_device) + let buttons = this._integrations(device, this.entries).map((entry) => { if (entry.state !== "loaded" || !entry.supports_remove_device) { return false; } @@ -296,7 +293,11 @@ export class HaConfigDevicePage extends LitElement { buttons as { entry_id: string; domain: string }[] ).map( (button) => html` - + ${buttons.length > 1 ? this.hass.localize( `ui.panel.config.devices.delete_device_integration`, @@ -307,16 +308,16 @@ export class HaConfigDevicePage extends LitElement { ), } ) - : this.hass.localize( - `ui.panel.config.devices.delete_device` - )} + : this.hass.localize(`ui.panel.config.devices.delete_device`)} ` ); } } - private async _confirmDeleteEntry(): Promise { + private async _confirmDeleteEntry(e: MouseEvent): Promise { + const entry_id = (e.currentTarget! as HTMLElement).getAttribute("entry_id") + const confirmed = await showConfirmationDialog(this, { text: this.hass.localize("ui.panel.config.devices.confirm_delete"), }); @@ -325,7 +326,7 @@ export class HaConfigDevicePage extends LitElement { return; } - await removeConfigEntryFromDevice(this.hass!, this.deviceId, "blabla"); + await removeConfigEntryFromDevice(this.hass!, this.deviceId, entry_id); } protected firstUpdated(changedProps) { From 0abafff4c91782dd786700f4f59d1c8ad4b8e084 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 18 Feb 2022 08:26:37 +0100 Subject: [PATCH 099/174] Fix lint error --- gallery/src/pages/misc/integration-card.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/gallery/src/pages/misc/integration-card.ts b/gallery/src/pages/misc/integration-card.ts index e9d74f2f1b..53f16a16b2 100644 --- a/gallery/src/pages/misc/integration-card.ts +++ b/gallery/src/pages/misc/integration-card.ts @@ -29,6 +29,7 @@ const createConfigEntry = ( source: "zeroconf", state: "loaded", supports_options: false, + supports_remove_device: false, supports_unload: true, disabled_by: null, pref_disable_new_entities: false, From 8f5c9295d3bb723778fafcb34b9c873918b35f5f Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 18 Feb 2022 08:48:53 +0100 Subject: [PATCH 100/174] Tweak --- src/panels/config/devices/ha-config-device-page.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 15da420e06..46b8a161ed 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -202,7 +202,7 @@ export class HaConfigDevicePage extends LitElement { } this._diagnosticDownloadLinks = Math.random(); - this._deleteButtons = [".."]; + this._deleteButtons = []; // To prevent re-rendering if no delete buttons this._renderDiagnosticButtons(this._diagnosticDownloadLinks); this._renderDeleteButtons(); } @@ -316,7 +316,7 @@ export class HaConfigDevicePage extends LitElement { } private async _confirmDeleteEntry(e: MouseEvent): Promise { - const entry_id = (e.currentTarget! as HTMLElement).getAttribute("entry_id") + const entry_id = (e.currentTarget! as HTMLElement).getAttribute("entry_id"); const confirmed = await showConfirmationDialog(this, { text: this.hass.localize("ui.panel.config.devices.confirm_delete"), @@ -326,7 +326,7 @@ export class HaConfigDevicePage extends LitElement { return; } - await removeConfigEntryFromDevice(this.hass!, this.deviceId, entry_id); + await removeConfigEntryFromDevice(this.hass!, this.deviceId, entry_id!); } protected firstUpdated(changedProps) { From 246724c59e51ee80220dffb8e48ea6ff9c04da96 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 18 Feb 2022 09:13:18 +0100 Subject: [PATCH 101/174] Prettier --- src/data/device_registry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index 69cdb6b974..64e29d3a20 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -80,7 +80,7 @@ export const updateDeviceRegistryEntry = ( export const removeConfigEntryFromDevice = ( hass: HomeAssistant, deviceId: string, - configEntryId: string, + configEntryId: string ) => hass.callWS({ type: "config/device_registry/remove_config_entry", From 5c5459bcaff02939e56fcfa5191e865d93f6f90e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 18 Feb 2022 13:21:00 +0100 Subject: [PATCH 102/174] Add play media action (#11702) Co-authored-by: Zack Barett Co-authored-by: Paulus Schoutsen --- .../src/pages/automation/describe-action.ts | 27 +- gallery/src/pages/automation/editor-action.ts | 2 +- gallery/src/pages/components/ha-form.ts | 8 + gallery/src/pages/components/ha-selector.ts | 222 ++++++++++++++- .../ha-selector/ha-selector-media.ts | 264 ++++++++++++++++++ src/components/ha-selector/ha-selector.ts | 1 + .../dialog-media-player-browse.ts | 6 +- .../media-player/ha-browse-media-tts.ts | 144 ++++++---- .../media-player/ha-media-player-browse.ts | 15 +- .../media-player/show-media-browser-dialog.ts | 4 +- src/data/media-player.ts | 2 + src/data/script.ts | 87 +++++- src/data/script_i18n.ts | 44 ++- src/data/selector.ts | 21 +- .../action/ha-automation-action-row.ts | 55 ++-- ...=> ha-automation-action-activate_scene.ts} | 4 +- .../types/ha-automation-action-play_media.ts | 68 +++++ .../types/ha-automation-action-service.ts | 2 +- src/translations/en.json | 40 ++- 19 files changed, 886 insertions(+), 130 deletions(-) create mode 100644 src/components/ha-selector/ha-selector-media.ts rename src/panels/config/automation/action/types/{ha-automation-action-scene.ts => ha-automation-action-activate_scene.ts} (93%) create mode 100644 src/panels/config/automation/action/types/ha-automation-action-play_media.ts diff --git a/gallery/src/pages/automation/describe-action.ts b/gallery/src/pages/automation/describe-action.ts index 55c3317acc..dd3d6c6e93 100644 --- a/gallery/src/pages/automation/describe-action.ts +++ b/gallery/src/pages/automation/describe-action.ts @@ -3,10 +3,20 @@ import { html, css, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import "../../../../src/components/ha-card"; import { describeAction } from "../../../../src/data/script_i18n"; +import { getEntity } from "../../../../src/fake_data/entity"; import { provideHass } from "../../../../src/fake_data/provide_hass"; import { HomeAssistant } from "../../../../src/types"; -const actions = [ +const ENTITIES = [ + getEntity("scene", "kitchen_morning", "scening", { + friendly_name: "Kitchen Morning", + }), + getEntity("media_player", "kitchen", "playing", { + friendly_name: "Sonos Kitchen", + }), +]; + +const ACTIONS = [ { wait_template: "{{ true }}", alias: "Something with an alias" }, { delay: "0:05" }, { wait_template: "{{ true }}" }, @@ -19,8 +29,20 @@ const actions = [ device_id: "abcdefgh", domain: "plex", entity_id: "media_player.kitchen", + type: "turn_on", }, { scene: "scene.kitchen_morning" }, + { + service: "scene.turn_on", + target: { entity_id: "scene.kitchen_morning" }, + metadata: {}, + }, + { + service: "media_player.play_media", + target: { entity_id: "media_player.kitchen" }, + data: { media_content_id: "", media_content_type: "" }, + metadata: { title: "Happy Song" }, + }, { wait_for_trigger: [ { @@ -52,7 +74,7 @@ export class DemoAutomationDescribeAction extends LitElement { } return html` - ${actions.map( + ${ACTIONS.map( (conf) => html`
    ${describeAction(this.hass, conf as any)} @@ -68,6 +90,7 @@ export class DemoAutomationDescribeAction extends LitElement { super.firstUpdated(changedProps); const hass = provideHass(this); hass.updateTranslations(null, "en"); + hass.addEntities(ENTITIES); } static get styles() { diff --git a/gallery/src/pages/automation/editor-action.ts b/gallery/src/pages/automation/editor-action.ts index 01769ccaa8..1f7a0d8206 100644 --- a/gallery/src/pages/automation/editor-action.ts +++ b/gallery/src/pages/automation/editor-action.ts @@ -14,7 +14,7 @@ import { HaDelayAction } from "../../../../src/panels/config/automation/action/t import { HaDeviceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-device_id"; import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event"; import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat"; -import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-scene"; +import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-activate_scene"; import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service"; import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger"; import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template"; diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index dac1320a37..b6ff6d1711 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -36,6 +36,8 @@ const SCHEMAS: { text_multiline: "Text Multiline", object: "Object", select: "Select", + icon: "Icon", + media: "Media", }, schema: [ { name: "addon", selector: { addon: {} } }, @@ -67,6 +69,12 @@ const SCHEMAS: { icon: {}, }, }, + { + name: "media", + selector: { + media: {}, + }, + }, ], }, { diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index 60890a3e4d..afa9d15e4c 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -12,6 +12,100 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry"; import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; +import { getEntity } from "../../../../src/fake_data/entity"; +import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin"; +import { showDialog } from "../../../../src/dialogs/make-dialog-manager"; + +const ENTITIES = [ + getEntity("alarm_control_panel", "alarm", "disarmed", { + friendly_name: "Alarm", + }), + getEntity("media_player", "livingroom", "playing", { + friendly_name: "Livingroom", + }), + getEntity("media_player", "lounge", "idle", { + friendly_name: "Lounge", + supported_features: 444983, + }), + getEntity("light", "bedroom", "on", { + friendly_name: "Bedroom", + }), + getEntity("switch", "coffee", "off", { + friendly_name: "Coffee", + }), +]; + +const DEVICES = [ + { + area_id: "bedroom", + configuration_url: null, + config_entries: ["config_entry_1"], + connections: [], + disabled_by: null, + entry_type: null, + id: "device_1", + identifiers: [["demo", "volume1"] as [string, string]], + manufacturer: null, + model: null, + name_by_user: null, + name: "Dishwasher", + sw_version: null, + hw_version: null, + via_device_id: null, + }, + { + area_id: "backyard", + configuration_url: null, + config_entries: ["config_entry_2"], + connections: [], + disabled_by: null, + entry_type: null, + id: "device_2", + identifiers: [["demo", "pwm1"] as [string, string]], + manufacturer: null, + model: null, + name_by_user: null, + name: "Lamp", + sw_version: null, + hw_version: null, + via_device_id: null, + }, + { + area_id: null, + configuration_url: null, + config_entries: ["config_entry_3"], + connections: [], + disabled_by: null, + entry_type: null, + id: "device_3", + identifiers: [["demo", "pwm1"] as [string, string]], + manufacturer: null, + model: null, + name_by_user: "User name", + name: "Technical name", + sw_version: null, + hw_version: null, + via_device_id: null, + }, +]; + +const AREAS = [ + { + area_id: "backyard", + name: "Backyard", + picture: null, + }, + { + area_id: "bedroom", + name: "Bedroom", + picture: null, + }, + { + area_id: "livingroom", + name: "Livingroom", + picture: null, + }, +]; const SCHEMAS: { name: string; @@ -73,13 +167,14 @@ const SCHEMAS: { selector: { select: { options: ["Option 1", "Option 2"] } }, }, icon: { name: "Icon", selector: { icon: {} } }, + media: { name: "Media", selector: { media: {} } }, }, }, ]; @customElement("demo-components-ha-selector") -class DemoHaSelector extends LitElement { - @state() private hass!: HomeAssistant; +class DemoHaSelector extends LitElement implements ProvideHassElement { + @state() public hass!: HomeAssistant; private data = SCHEMAS.map(() => ({})); @@ -88,12 +183,130 @@ class DemoHaSelector extends LitElement { const hass = provideHass(this); hass.updateTranslations(null, "en"); hass.updateTranslations("config", "en"); + hass.addEntities(ENTITIES); mockEntityRegistry(hass); - mockDeviceRegistry(hass); - mockAreaRegistry(hass); + mockDeviceRegistry(hass, DEVICES); + mockAreaRegistry(hass, AREAS); mockHassioSupervisor(hass); + hass.mockWS("auth/sign_path", (params) => params); + hass.mockWS("media_player/browse_media", this._browseMedia); } + public provideHass(el) { + el.hass = this.hass; + } + + public connectedCallback() { + super.connectedCallback(); + this.addEventListener("show-dialog", this._dialogManager); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this.removeEventListener("show-dialog", this._dialogManager); + } + + private _browseMedia = ({ media_content_id }) => { + if (media_content_id === undefined) { + return { + title: "Media", + media_class: "directory", + media_content_type: "", + media_content_id: "media-source://media_source/local/.", + can_play: false, + can_expand: true, + children_media_class: "directory", + thumbnail: null, + children: [ + { + title: "Misc", + media_class: "directory", + media_content_type: "", + media_content_id: "media-source://media_source/local/misc", + can_play: false, + can_expand: true, + children_media_class: null, + thumbnail: null, + }, + { + title: "Movies", + media_class: "directory", + media_content_type: "", + media_content_id: "media-source://media_source/local/movies", + can_play: true, + can_expand: true, + children_media_class: "movie", + thumbnail: null, + }, + { + title: "Music", + media_class: "album", + media_content_type: "", + media_content_id: "media-source://media_source/local/music", + can_play: false, + can_expand: true, + children_media_class: "music", + thumbnail: "/images/album_cover_2.jpg", + }, + ], + }; + } + return { + title: "Subfolder", + media_class: "directory", + media_content_type: "", + media_content_id: "media-source://media_source/local/sub", + can_play: false, + can_expand: true, + children_media_class: "directory", + thumbnail: null, + children: [ + { + title: "audio.mp3", + media_class: "music", + media_content_type: "audio/mpeg", + media_content_id: "media-source://media_source/local/audio.mp3", + can_play: true, + can_expand: false, + children_media_class: null, + thumbnail: "/images/album_cover.jpg", + }, + { + title: "image.jpg", + media_class: "image", + media_content_type: "image/jpeg", + media_content_id: "media-source://media_source/local/image.jpg", + can_play: true, + can_expand: false, + children_media_class: null, + thumbnail: null, + }, + { + title: "movie.mp4", + media_class: "movie", + media_content_type: "image/jpeg", + media_content_id: "media-source://media_source/local/movie.mp4", + can_play: true, + can_expand: false, + children_media_class: null, + thumbnail: null, + }, + ], + }; + }; + + private _dialogManager = (e) => { + const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail; + showDialog( + this, + this.shadowRoot!, + dialogTag, + dialogParams, + dialogImport, + addHistory + ); + }; + protected render(): TemplateResult { return html` ${SCHEMAS.map((info, idx) => { @@ -132,7 +345,6 @@ class DemoHaSelector extends LitElement { } static styles = css` - paper-input, ha-selector { width: 60; } diff --git a/src/components/ha-selector/ha-selector-media.ts b/src/components/ha-selector/ha-selector-media.ts new file mode 100644 index 0000000000..bbfb203050 --- /dev/null +++ b/src/components/ha-selector/ha-selector-media.ts @@ -0,0 +1,264 @@ +import { mdiPlayBox, mdiPlus } from "@mdi/js"; +import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { fireEvent } from "../../common/dom/fire_event"; +import { supportsFeature } from "../../common/entity/supports-feature"; +import { getSignedPath } from "../../data/auth"; +import { + MediaClassBrowserSettings, + MediaPickedEvent, + SUPPORT_BROWSE_MEDIA, +} from "../../data/media-player"; +import type { MediaSelector, MediaSelectorValue } from "../../data/selector"; +import type { HomeAssistant } from "../../types"; +import "../ha-alert"; +import "../ha-form/ha-form"; +import type { HaFormSchema } from "../ha-form/types"; +import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog"; + +const MANUAL_SCHEMA = [ + { name: "media_content_id", required: false, selector: { text: {} } }, + { name: "media_content_type", required: false, selector: { text: {} } }, +]; + +@customElement("ha-selector-media") +export class HaMediaSelector extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public selector!: MediaSelector; + + @property({ attribute: false }) public value?: MediaSelectorValue; + + @property() public label?: string; + + @property({ type: Boolean, reflect: true }) public disabled = false; + + @state() private _thumbnailUrl?: string | null; + + willUpdate(changedProps: PropertyValues) { + if (changedProps.has("value")) { + const thumbnail = this.value?.metadata?.thumbnail; + const oldThumbnail = (changedProps.get("value") as this["value"]) + ?.metadata?.thumbnail; + if (thumbnail === oldThumbnail) { + return; + } + if (thumbnail && thumbnail.startsWith("/")) { + this._thumbnailUrl = undefined; + // Thumbnails served by local API require authentication + getSignedPath(this.hass, thumbnail).then((signedPath) => { + this._thumbnailUrl = signedPath.path; + }); + } else { + this._thumbnailUrl = thumbnail; + } + } + } + + protected render() { + const stateObj = this.value?.entity_id + ? this.hass.states[this.value.entity_id] + : undefined; + + const supportsBrowse = + !this.value?.entity_id || + (stateObj && supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)); + + return html` + ${!supportsBrowse + ? html` + ${this.hass.localize( + "ui.components.selectors.media.browse_not_supported" + )} + + ` + : html` +
    + ${this.value?.metadata?.thumbnail + ? html` +
    + ` + : html` +
    + +
    + `} +
    +
    + ${!this.value?.media_content_id + ? this.hass.localize("ui.components.selectors.media.pick_media") + : this.value.metadata?.title || this.value.media_content_id} +
    +
    `}`; + } + + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize(`ui.components.selectors.media.${schema.name}`); + + private _entityChanged(ev: CustomEvent) { + ev.stopPropagation(); + fireEvent(this, "value-changed", { + value: { + entity_id: ev.detail.value, + media_content_id: "", + media_content_type: "", + }, + }); + } + + private _pickMedia() { + showMediaBrowserDialog(this, { + action: "pick", + entityId: this.value!.entity_id!, + navigateIds: this.value!.metadata?.navigateIds, + mediaPickedCallback: (pickedMedia: MediaPickedEvent) => { + fireEvent(this, "value-changed", { + value: { + ...this.value, + media_content_id: pickedMedia.item.media_content_id, + media_content_type: pickedMedia.item.media_content_type, + metadata: { + title: pickedMedia.item.title, + thumbnail: pickedMedia.item.thumbnail, + media_class: pickedMedia.item.media_class, + children_media_class: pickedMedia.item.children_media_class, + navigateIds: pickedMedia.navigateIds?.map((id) => ({ + media_content_type: id.media_content_type, + media_content_id: id.media_content_id, + })), + }, + }, + }); + }, + }); + } + + static get styles(): CSSResultGroup { + return css` + ha-entity-picker { + display: block; + margin-bottom: 16px; + } + mwc-button { + margin-top: 8px; + } + ha-alert { + display: block; + margin-bottom: 16px; + } + ha-card { + position: relative; + width: 200px; + box-sizing: border-box; + cursor: pointer; + } + ha-card.disabled { + pointer-events: none; + color: var(--disabled-text-color); + } + ha-card .thumbnail { + width: 100%; + position: relative; + box-sizing: border-box; + transition: padding-bottom 0.1s ease-out; + padding-bottom: 100%; + } + ha-card .thumbnail.portrait { + padding-bottom: 150%; + } + ha-card .image { + border-radius: 3px 3px 0 0; + } + .folder { + --mdc-icon-size: calc(var(--media-browse-item-size, 175px) * 0.4); + } + .title { + font-size: 16px; + padding-top: 16px; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 16px; + padding-left: 16px; + padding-right: 4px; + white-space: nowrap; + } + .image { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + background-size: cover; + background-repeat: no-repeat; + background-position: center; + } + .centered-image { + margin: 0 8px; + background-size: contain; + } + .icon-holder { + display: flex; + justify-content: center; + align-items: center; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-media": HaMediaSelector; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 53aaa6a841..0181c9c863 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -18,6 +18,7 @@ import "./ha-selector-target"; import "./ha-selector-text"; import "./ha-selector-time"; import "./ha-selector-icon"; +import "./ha-selector-media"; @customElement("ha-selector") export class HaSelector extends LitElement { diff --git a/src/components/media-player/dialog-media-player-browse.ts b/src/components/media-player/dialog-media-player-browse.ts index 31acf550aa..7287d7aba2 100644 --- a/src/components/media-player/dialog-media-player-browse.ts +++ b/src/components/media-player/dialog-media-player-browse.ts @@ -28,10 +28,10 @@ class DialogMediaPlayerBrowse extends LitElement { public showDialog(params: MediaPlayerBrowseDialogParams): void { this._params = params; - this._navigateIds = [ + this._navigateIds = params.navigateIds || [ { - media_content_id: this._params.mediaContentId, - media_content_type: this._params.mediaContentType, + media_content_id: undefined, + media_content_type: undefined, }, ]; } diff --git a/src/components/media-player/ha-browse-media-tts.ts b/src/components/media-player/ha-browse-media-tts.ts index 8bec663a5d..43f4b30190 100644 --- a/src/components/media-player/ha-browse-media-tts.ts +++ b/src/components/media-player/ha-browse-media-tts.ts @@ -11,12 +11,26 @@ import { getCloudTtsLanguages, getCloudTtsSupportedGenders, } from "../../data/cloud/tts"; -import { MediaPlayerBrowseAction } from "../../data/media-player"; +import { + MediaPlayerBrowseAction, + MediaPlayerItem, +} from "../../data/media-player"; import { HomeAssistant } from "../../types"; import "../ha-textarea"; import { buttonLinkStyle } from "../../resources/styles"; import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; import { LocalStorage } from "../../common/decorators/local-storage"; +import { stopPropagation } from "../../common/dom/stop_propagation"; + +export interface TtsMediaPickedEvent { + item: MediaPlayerItem; +} + +declare global { + interface HASSDomEvents { + "tts-picked": TtsMediaPickedEvent; + } +} @customElement("ha-browse-media-tts") class BrowseMediaTTS extends LitElement { @@ -32,40 +46,55 @@ class BrowseMediaTTS extends LitElement { @state() private _cloudTTSInfo?: CloudTTSInfo; - @LocalStorage("cloudTtsTryMessage", false, false) private _message!: string; + @LocalStorage("cloudTtsTryMessage", true, false) private _message!: string; protected render() { - return html` - - - ${this._cloudDefaultOptions ? this._renderCloudOptions() : ""} -
    + return html` +
    + + + ${this._cloudDefaultOptions ? this._renderCloudOptions() : ""} +
    +
    ${this._cloudDefaultOptions && (this._cloudDefaultOptions![0] !== this._cloudOptions![0] || this._cloudDefaultOptions![1] !== this._cloudOptions![1]) ? html` ` : html``} - + + + ${this.hass.localize( + `ui.components.media-browser.tts.action_${this.action}` + )} +
    - `; +
    `; } private _renderCloudOptions() { + if (!this._cloudTTSInfo || !this._cloudOptions) { + return ""; + } const languages = this.getLanguages(this._cloudTTSInfo); - const selectedVoice = this._cloudOptions!; + const selectedVoice = this._cloudOptions; const genders = this.getSupportedGenders( selectedVoice[0], this._cloudTTSInfo, @@ -77,9 +106,12 @@ class BrowseMediaTTS extends LitElement { ${languages.map( ([key, label]) => @@ -90,9 +122,10 @@ class BrowseMediaTTS extends LitElement { ${genders.map( ([key, label]) => @@ -106,6 +139,37 @@ class BrowseMediaTTS extends LitElement { protected override willUpdate(changedProps: PropertyValues): void { super.willUpdate(changedProps); + if (changedProps.has("item")) { + if (this.item.media_content_id) { + const params = new URLSearchParams( + this.item.media_content_id.split("?")[1] + ); + const message = params.get("message"); + const language = params.get("language"); + const gender = params.get("gender"); + if (message) { + this._message = message; + } + if (language && gender) { + this._cloudOptions = [language, gender]; + } + } + + if (this.isCloudItem && !this._cloudTTSInfo) { + getCloudTTSInfo(this.hass).then((info) => { + this._cloudTTSInfo = info; + }); + fetchCloudStatus(this.hass).then((status) => { + if (status.logged_in) { + this._cloudDefaultOptions = status.prefs.tts_default_voice; + if (!this._cloudOptions) { + this._cloudOptions = { ...this._cloudDefaultOptions }; + } + } + }); + } + } + if (changedProps.has("message")) { return; } @@ -133,30 +197,12 @@ class BrowseMediaTTS extends LitElement { this._cloudOptions = [this._cloudOptions![0], ev.target.value]; } - protected updated(changedProps: PropertyValues): void { - super.updated(changedProps); - - if (changedProps.has("item")) { - if (this.isCloudItem && !this._cloudTTSInfo) { - getCloudTTSInfo(this.hass).then((info) => { - this._cloudTTSInfo = info; - }); - fetchCloudStatus(this.hass).then((status) => { - if (status.logged_in) { - this._cloudDefaultOptions = status.prefs.tts_default_voice; - this._cloudOptions = { ...this._cloudDefaultOptions }; - } - }); - } - } - } - private getLanguages = memoizeOne(getCloudTtsLanguages); private getSupportedGenders = memoizeOne(getCloudTtsSupportedGenders); private get isCloudItem(): boolean { - return this.item.media_content_id === "media-source://tts/cloud"; + return this.item.media_content_id.startsWith("media-source://tts/cloud"); } private async _ttsClicked(): Promise { @@ -169,9 +215,12 @@ class BrowseMediaTTS extends LitElement { query.append("language", this._cloudOptions[0]); query.append("gender", this._cloudOptions[1]); } - item.media_content_id += `?${query.toString()}`; + item.media_content_id = `${ + item.media_content_id.split("?")[0] + }?${query.toString()}`; item.can_play = true; - fireEvent(this, "media-picked", { item }); + item.title = message; + fireEvent(this, "tts-picked", { item }); } private async _storeDefaults() { @@ -185,7 +234,7 @@ class BrowseMediaTTS extends LitElement { this._cloudDefaultOptions = oldDefaults; showAlertDialog(this, { text: this.hass.localize( - "ui.panel.media-browser.tts.faild_to_store_defaults", + "ui.components.media-browser.tts.faild_to_store_defaults", { error: err.message || err } ), }); @@ -210,15 +259,16 @@ class BrowseMediaTTS extends LitElement { .cloud-options mwc-select { width: 48%; } - - .actions { - display: flex; - justify-content: space-between; - margin-top: 16px; + ha-textarea { + width: 100%; } button.link { color: var(--primary-color); } + .card-actions { + display: flex; + justify-content: space-between; + } `, ]; } diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index be644eacca..fa81e42716 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -49,7 +49,7 @@ import "../ha-svg-icon"; import "../ha-fab"; import { browseLocalMediaPlayer } from "../../data/media_source"; import { isTTSMediaSource } from "../../data/tts"; -import "./ha-browse-media-tts"; +import { TtsMediaPickedEvent } from "./ha-browse-media-tts"; declare global { interface HASSDomEvents { @@ -260,6 +260,7 @@ export class HaMediaPlayerBrowse extends LitElement { .item=${currentItem} .hass=${this.hass} .action=${this.action} + @tts-picked=${this._ttsPicked} > ` : !currentItem.children?.length @@ -562,7 +563,17 @@ export class HaMediaPlayerBrowse extends LitElement { } private _runAction(item: MediaPlayerItem): void { - fireEvent(this, "media-picked", { item }); + fireEvent(this, "media-picked", { item, navigateIds: this.navigateIds }); + } + + private _ttsPicked(ev: CustomEvent): void { + ev.stopPropagation(); + const navigateIds = this.navigateIds.slice(0, -1); + navigateIds.push(ev.detail.item); + fireEvent(this, "media-picked", { + ...ev.detail, + navigateIds, + }); } private async _childClicked(ev: MouseEvent): Promise { diff --git a/src/components/media-player/show-media-browser-dialog.ts b/src/components/media-player/show-media-browser-dialog.ts index b84fd349c7..c99e8184b6 100644 --- a/src/components/media-player/show-media-browser-dialog.ts +++ b/src/components/media-player/show-media-browser-dialog.ts @@ -3,13 +3,13 @@ import { MediaPickedEvent, MediaPlayerBrowseAction, } from "../../data/media-player"; +import { MediaPlayerItemId } from "./ha-media-player-browse"; export interface MediaPlayerBrowseDialogParams { action: MediaPlayerBrowseAction; entityId: string; mediaPickedCallback: (pickedMedia: MediaPickedEvent) => void; - mediaContentId?: string; - mediaContentType?: string; + navigateIds?: MediaPlayerItemId[]; } export const showMediaBrowserDialog = ( diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 6328b4aafe..2d3902faf6 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -28,6 +28,7 @@ import type { HassEntityBase, } from "home-assistant-js-websocket"; import { supportsFeature } from "../common/entity/supports-feature"; +import { MediaPlayerItemId } from "../components/media-player/ha-media-player-browse"; import type { HomeAssistant } from "../types"; import { UNAVAILABLE_STATES } from "./entity"; @@ -147,6 +148,7 @@ export const MediaClassBrowserSettings: { export interface MediaPickedEvent { item: MediaPlayerItem; + navigateIds: MediaPlayerItemId[]; } export interface MediaPlayerThumbnail { diff --git a/src/data/script.ts b/src/data/script.ts index 81a90d0ed2..41db0160c1 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -3,6 +3,17 @@ import { HassEntityBase, HassServiceTarget, } from "home-assistant-js-websocket"; +import { + object, + optional, + string, + union, + array, + assign, + literal, + is, + Describe, +} from "superstruct"; import { computeObjectId } from "../common/entity/compute_object_id"; import { navigate } from "../common/navigate"; import { HomeAssistant } from "../types"; @@ -12,6 +23,48 @@ import { BlueprintInput } from "./blueprint"; export const MODES = ["single", "restart", "queued", "parallel"] as const; export const MODES_MAX = ["queued", "parallel"]; +export const baseActionStruct = object({ + alias: optional(string()), +}); + +const targetStruct = object({ + entity_id: optional(union([string(), array(string())])), + device_id: optional(union([string(), array(string())])), + area_id: optional(union([string(), array(string())])), +}); + +export const serviceActionStruct: Describe = assign( + baseActionStruct, + object({ + service: optional(string()), + service_template: optional(string()), + entity_id: optional(string()), + target: optional(targetStruct), + data: optional(object()), + }) +); + +const playMediaActionStruct: Describe = assign( + baseActionStruct, + object({ + service: literal("media_player.play_media"), + target: optional(object({ entity_id: optional(string()) })), + entity_id: optional(string()), + data: object({ media_content_id: string(), media_content_type: string() }), + metadata: object(), + }) +); + +const activateSceneActionStruct: Describe = assign( + baseActionStruct, + object({ + service: literal("scene.turn_on"), + target: optional(object({ entity_id: optional(string()) })), + entity_id: optional(string()), + metadata: object(), + }) +); + export interface ScriptEntity extends HassEntityBase { attributes: HassEntityAttributeBase & { last_triggered: string; @@ -48,11 +101,12 @@ export interface ServiceAction { service_template?: string; entity_id?: string; target?: HassServiceTarget; - data?: Record; + data?: Record; } export interface DeviceAction { alias?: string; + type: string; device_id: string; domain: string; entity_id: string; @@ -70,9 +124,12 @@ export interface DelayAction { delay: number | Partial | string; } -export interface ServiceSceneAction extends ServiceAction { +export interface ServiceSceneAction { + alias?: string; service: "scene.turn_on"; - metadata: Record; + target?: { entity_id?: string }; + entity_id?: string; + metadata: Record; } export interface LegacySceneAction { alias?: string; @@ -94,6 +151,15 @@ export interface WaitForTriggerAction { continue_on_timeout?: boolean; } +export interface PlayMediaAction { + alias?: string; + service: "media_player.play_media"; + target?: { entity_id?: string }; + entity_id?: string; + data: { media_content_id: string; media_content_type: string }; + metadata: Record; +} + export interface RepeatAction { alias?: string; repeat: CountRepeat | WhileRepeat | UntilRepeat; @@ -150,6 +216,7 @@ export type Action = | RepeatAction | ChooseAction | VariablesAction + | PlayMediaAction | UnknownAction; export interface ActionTypes { @@ -158,13 +225,13 @@ export interface ActionTypes { check_condition: Condition; fire_event: EventAction; device_action: DeviceAction; - legacy_activate_scene: LegacySceneAction; - activate_scene: ServiceSceneAction; + activate_scene: SceneAction; repeat: RepeatAction; choose: ChooseAction; wait_for_trigger: WaitForTriggerAction; variables: VariablesAction; service: ServiceAction; + play_media: PlayMediaAction; unknown: UnknownAction; } @@ -224,7 +291,7 @@ export const getActionType = (action: Action): ActionType => { return "device_action"; } if ("scene" in action) { - return "legacy_activate_scene"; + return "activate_scene"; } if ("repeat" in action) { return "repeat"; @@ -240,12 +307,12 @@ export const getActionType = (action: Action): ActionType => { } if ("service" in action) { if ("metadata" in action) { - if ( - (action as ServiceAction).service === "scene.turn_on" && - !Array.isArray((action as ServiceAction)?.target?.entity_id) - ) { + if (is(action, activateSceneActionStruct)) { return "activate_scene"; } + if (is(action, playMediaActionStruct)) { + return "play_media"; + } } return "service"; } diff --git a/src/data/script_i18n.ts b/src/data/script_i18n.ts index a8f2b76356..230c867ef0 100644 --- a/src/data/script_i18n.ts +++ b/src/data/script_i18n.ts @@ -9,10 +9,11 @@ import { ActionType, ActionTypes, DelayAction, + DeviceAction, EventAction, getActionType, - LegacySceneAction, - ServiceSceneAction, + PlayMediaAction, + SceneAction, VariablesAction, WaitForTriggerAction, } from "./script"; @@ -103,19 +104,32 @@ export const describeAction = ( return `Delay ${duration}`; } - if (actionType === "legacy_activate_scene") { - const config = action as LegacySceneAction; - const sceneStateObj = hass.states[config.scene]; + if (actionType === "activate_scene") { + const config = action as SceneAction; + let entityId: string | undefined; + if ("scene" in config) { + entityId = config.scene; + } else { + entityId = config.target?.entity_id || config.entity_id; + } + const sceneStateObj = entityId ? hass.states[entityId] : undefined; return `Activate scene ${ - sceneStateObj ? computeStateName(sceneStateObj) : config.scene + sceneStateObj + ? computeStateName(sceneStateObj) + : "scene" in config + ? config.scene + : config.target?.entity_id || config.entity_id }`; } - if (actionType === "activate_scene") { - const config = action as ServiceSceneAction; - const sceneStateObj = hass.states[config.target!.entity_id as string]; - return `Activate scene ${ - sceneStateObj ? computeStateName(sceneStateObj) : config.target!.entity_id + if (actionType === "play_media") { + const config = action as PlayMediaAction; + const entityId = config.target?.entity_id || config.entity_id; + const mediaStateObj = entityId ? hass.states[entityId] : undefined; + return `Play ${config.metadata.title || config.data.media_content_id} on ${ + mediaStateObj + ? computeStateName(mediaStateObj) + : config.target?.entity_id || config.entity_id }`; } @@ -147,5 +161,13 @@ export const describeAction = ( return `Test ${describeCondition(action as Condition)}`; } + if (actionType === "device_action") { + const config = action as DeviceAction; + const stateObj = hass.states[config.entity_id as string]; + return `${config.type || "Perform action with"} ${ + stateObj ? computeStateName(stateObj) : config.entity_id + }`; + } + return actionType; }; diff --git a/src/data/selector.ts b/src/data/selector.ts index 458a84235a..e7be0ac147 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -13,7 +13,8 @@ export type Selector = | StringSelector | ObjectSelector | SelectSelector - | IconSelector; + | IconSelector + | MediaSelector; export interface EntitySelector { entity: { @@ -149,3 +150,21 @@ export interface IconSelector { // eslint-disable-next-line @typescript-eslint/ban-types icon: {}; } + +export interface MediaSelector { + // eslint-disable-next-line @typescript-eslint/ban-types + media: {}; +} + +export interface MediaSelectorValue { + entity_id?: string; + media_content_id?: string; + media_content_type?: string; + metadata?: { + title?: string; + thumbnail?: string | null; + media_class?: string; + children_media_class?: string | null; + navigateIds?: { media_content_type: string; media_content_id: string }[]; + }; +} diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 5c103e9e67..dbc9150ecf 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -1,8 +1,8 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; -import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js"; import "@material/mwc-select"; import type { Select } from "@material/mwc-select"; +import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -11,22 +11,23 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { stringCompare } from "../../../../common/string/compare"; import { handleStructError } from "../../../../common/structs/handle-errors"; import { LocalizeFunc } from "../../../../common/translations/localize"; +import "../../../../components/ha-alert"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; -import "../../../../components/ha-alert"; import "../../../../components/ha-icon-button"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; -import type { Action, ServiceSceneAction } from "../../../../data/script"; +import { Action, getActionType } from "../../../../data/script"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; +import "./types/ha-automation-action-activate_scene"; import "./types/ha-automation-action-choose"; import "./types/ha-automation-action-condition"; import "./types/ha-automation-action-delay"; import "./types/ha-automation-action-device_id"; import "./types/ha-automation-action-event"; +import "./types/ha-automation-action-play_media"; import "./types/ha-automation-action-repeat"; -import "./types/ha-automation-action-scene"; import "./types/ha-automation-action-service"; import "./types/ha-automation-action-wait_for_trigger"; import "./types/ha-automation-action-wait_template"; @@ -35,7 +36,8 @@ const OPTIONS = [ "condition", "delay", "event", - "scene", + "play_media", + "activate_scene", "service", "wait_template", "wait_for_trigger", @@ -48,21 +50,8 @@ const getType = (action: Action | undefined) => { if (!action) { return undefined; } - if ("metadata" in action && action.service) { - switch (action.service) { - case "scene.turn_on": - // we dont support arrays of entities - if ( - !Array.isArray( - (action as unknown as ServiceSceneAction).target?.entity_id - ) - ) { - return "scene"; - } - break; - default: - break; - } + if ("service" in action || "scene" in action) { + return getActionType(action); } return OPTIONS.find((option) => option in action); }; @@ -133,24 +122,30 @@ export default class HaAutomationActionRow extends LitElement { ).sort((a, b) => stringCompare(a[1], b[1])) ); + protected willUpdate(changedProperties: PropertyValues) { + if (!changedProperties.has("action")) { + return; + } + this._uiModeAvailable = getType(this.action) !== undefined; + if (!this._uiModeAvailable && !this._yamlMode) { + this._yamlMode = true; + } + } + protected updated(changedProperties: PropertyValues) { if (!changedProperties.has("action")) { return; } - this._uiModeAvailable = Boolean(getType(this.action)); - if (!this._uiModeAvailable && !this._yamlMode) { - this._yamlMode = true; - } - - const yamlEditor = this._yamlEditor; - if (this._yamlMode && yamlEditor && yamlEditor.value !== this.action) { - yamlEditor.setValue(this.action); + if (this._yamlMode) { + const yamlEditor = this._yamlEditor; + if (yamlEditor && yamlEditor.value !== this.action) { + yamlEditor.setValue(this.action); + } } } protected render() { const type = getType(this.action); - const selected = type ? OPTIONS.indexOf(type) : -1; const yamlMode = this._yamlMode; return html` @@ -225,7 +220,7 @@ export default class HaAutomationActionRow extends LitElement { : ""} ${yamlMode ? html` - ${selected === -1 + ${type === undefined ? html` ${this.hass.localize( "ui.panel.config.automation.editor.actions.unsupported_action", diff --git a/src/panels/config/automation/action/types/ha-automation-action-scene.ts b/src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts similarity index 93% rename from src/panels/config/automation/action/types/ha-automation-action-scene.ts rename to src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts index a9d02b83ba..0ce146589a 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-scene.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-activate_scene.ts @@ -9,7 +9,7 @@ import { ActionElement } from "../ha-automation-action-row"; const includeDomains = ["scene"]; -@customElement("ha-automation-action-scene") +@customElement("ha-automation-action-activate_scene") export class HaSceneAction extends LitElement implements ActionElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -61,6 +61,6 @@ export class HaSceneAction extends LitElement implements ActionElement { declare global { interface HTMLElementTagNameMap { - "ha-automation-action-scene": HaSceneAction; + "ha-automation-action-activate_scene": HaSceneAction; } } diff --git a/src/panels/config/automation/action/types/ha-automation-action-play_media.ts b/src/panels/config/automation/action/types/ha-automation-action-play_media.ts new file mode 100644 index 0000000000..ba9f537513 --- /dev/null +++ b/src/panels/config/automation/action/types/ha-automation-action-play_media.ts @@ -0,0 +1,68 @@ +import "@polymer/paper-input/paper-input"; +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-selector/ha-selector-media"; +import { PlayMediaAction } from "../../../../../data/script"; +import type { MediaSelectorValue } from "../../../../../data/selector"; +import type { HomeAssistant } from "../../../../../types"; +import { ActionElement } from "../ha-automation-action-row"; + +@customElement("ha-automation-action-play_media") +export class HaPlayMediaAction extends LitElement implements ActionElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public action!: PlayMediaAction; + + @property({ type: Boolean }) public narrow = false; + + public static get defaultConfig(): PlayMediaAction { + return { + service: "media_player.play_media", + target: { entity_id: "" }, + data: { media_content_id: "", media_content_type: "" }, + metadata: {}, + }; + } + + private _getSelectorValue = memoizeOne( + (action: PlayMediaAction): MediaSelectorValue => ({ + entity_id: action.target?.entity_id || action.entity_id, + media_content_id: action.data?.media_content_id, + media_content_type: action.data?.media_content_type, + metadata: action.metadata, + }) + ); + + protected render() { + return html` + + `; + } + + private _valueChanged(ev: CustomEvent<{ value: MediaSelectorValue }>) { + ev.stopPropagation(); + fireEvent(this, "value-changed", { + value: { + service: "media_player.play_media", + target: { entity_id: ev.detail.value.entity_id }, + data: { + media_content_id: ev.detail.value.media_content_id, + media_content_type: ev.detail.value.media_content_type, + }, + metadata: ev.detail.value.metadata || {}, + } as PlayMediaAction, + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-action-play_media": HaPlayMediaAction; + } +} diff --git a/src/panels/config/automation/action/types/ha-automation-action-service.ts b/src/panels/config/automation/action/types/ha-automation-action-service.ts index 251fe0f5f1..fdac1e1824 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-service.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-service.ts @@ -30,7 +30,7 @@ export class HaServiceAction extends LitElement implements ActionElement { return { service: "", data: {} }; } - protected updated(changedProperties: PropertyValues) { + protected willUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("action")) { return; } diff --git a/src/translations/en.json b/src/translations/en.json index 4ad76f9f98..df493b082b 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -317,6 +317,17 @@ "copied_clipboard": "Copied to clipboard" }, "components": { + "selectors": { + "media": { + "pick_media_player": "Select media player", + "browse_not_supported": "Media player does not support browsing media.", + "pick_media": "Pick media", + "browse_media": "Browse media", + "manual": "Manually enter Media ID", + "media_content_id": "Media content ID", + "media_content_type": "Media content type" + } + }, "logbook": { "entries_not_found": "No logbook events found.", "by": "by", @@ -505,6 +516,18 @@ "clear": "Clear" }, "media-browser": { + "tts": { + "message": "Message", + "example_message": "Hello {name}, you can play any text on any supported media player!", + "language": "Language", + "gender": "Gender", + "gender_male": "Male", + "gender_female": "Female", + "action_play": "Say", + "action_pick": "Select", + "set_as_default": "Set as default options", + "faild_to_store_defaults": "Failed to store defaults: {error}" + }, "pick": "Pick", "play": "Play", "play-media": "Play Media", @@ -1798,6 +1821,9 @@ "service": { "label": "Call service" }, + "play_media": { + "label": "Play media" + }, "delay": { "label": "Wait for time to pass (delay)", "delay": "Duration" @@ -1836,7 +1862,7 @@ "flash": "Flash" } }, - "scene": { + "activate_scene": { "label": "Activate scene" }, "repeat": { @@ -3699,18 +3725,6 @@ "media-browser": { "error": { "player_not_exist": "Media player {name} does not exist" - }, - "tts": { - "message": "Message", - "example_message": "Hello {name}, you can play any text on any supported media player!", - "language": "Language", - "gender": "Gender", - "gender_male": "Male", - "gender_female": "Female", - "action_play": "Say", - "action_pick": "Select", - "set_as_default": "Set as default options", - "faild_to_store_defaults": "Failed to store defaults: {error}" } }, "map": { From 9500ac498ce41fede4560a57852ed7cc5eae995c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 18 Feb 2022 06:04:45 -0800 Subject: [PATCH 103/174] Debounce refresh the cloud status if Google events happen (#11721) --- src/panels/config/cloud/account/cloud-account.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/panels/config/cloud/account/cloud-account.ts b/src/panels/config/cloud/account/cloud-account.ts index 63ea847326..15e5d3c002 100644 --- a/src/panels/config/cloud/account/cloud-account.ts +++ b/src/panels/config/cloud/account/cloud-account.ts @@ -12,6 +12,7 @@ import "../../../../components/buttons/ha-call-api-button"; import "../../../../components/ha-card"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-icon-button"; +import { debounce } from "../../../../common/util/debounce"; import { cloudLogout, CloudStatusLoggedIn, @@ -219,11 +220,15 @@ export class CloudAccount extends SubscribeMixin(LitElement) { } protected override hassSubscribe() { - const googleCheck = () => { - if (!this.cloudStatus?.google_registered) { - fireEvent(this, "ha-refresh-cloud-status"); - } - }; + const googleCheck = debounce( + () => { + if (this.cloudStatus && !this.cloudStatus.google_registered) { + fireEvent(this, "ha-refresh-cloud-status"); + } + }, + 10000, + true + ); return [ this.hass.connection.subscribeEvents(() => { if (!this.cloudStatus?.alexa_registered) { From eae7e8212737e6503b1191bf35c0fe96fea21b66 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 15:28:53 +0100 Subject: [PATCH 104/174] Remove custom MQTT delete device button (#11724) --- src/data/mqtt.ts | 9 --------- .../mqtt/ha-device-actions-mqtt.ts | 17 ----------------- 2 files changed, 26 deletions(-) diff --git a/src/data/mqtt.ts b/src/data/mqtt.ts index 7d66e062f5..2cc710fd51 100644 --- a/src/data/mqtt.ts +++ b/src/data/mqtt.ts @@ -44,15 +44,6 @@ export const subscribeMQTTTopic = ( topic, }); -export const removeMQTTDeviceEntry = ( - hass: HomeAssistant, - deviceId: string -): Promise => - hass.callWS({ - type: "mqtt/device/remove", - device_id: deviceId, - }); - export const fetchMQTTDebugInfo = ( hass: HomeAssistant, deviceId: string diff --git a/src/panels/config/devices/device-detail/integration-elements/mqtt/ha-device-actions-mqtt.ts b/src/panels/config/devices/device-detail/integration-elements/mqtt/ha-device-actions-mqtt.ts index abb7540359..f7e7847722 100644 --- a/src/panels/config/devices/device-detail/integration-elements/mqtt/ha-device-actions-mqtt.ts +++ b/src/panels/config/devices/device-detail/integration-elements/mqtt/ha-device-actions-mqtt.ts @@ -1,8 +1,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { DeviceRegistryEntry } from "../../../../../../data/device_registry"; -import { removeMQTTDeviceEntry } from "../../../../../../data/mqtt"; -import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../../../resources/styles"; import { HomeAssistant } from "../../../../../../types"; import { showMQTTDeviceDebugInfoDialog } from "./show-dialog-mqtt-device-debug-info"; @@ -16,24 +14,9 @@ export class HaDeviceActionsMqtt extends LitElement { protected render(): TemplateResult { return html` MQTT Info - - ${this.hass.localize("ui.panel.config.devices.delete")} - `; } - private async _confirmDeleteEntry(): Promise { - const confirmed = await showConfirmationDialog(this, { - text: this.hass.localize("ui.panel.config.devices.confirm_delete"), - }); - - if (!confirmed) { - return; - } - - await removeMQTTDeviceEntry(this.hass!, this.device.id); - } - private async _showDebugInfo(): Promise { const device = this.device; await showMQTTDeviceDebugInfoDialog(this, { device }); From 4f6a241817da8e55240013b78ed3ed637fcf7884 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 15:57:57 +0100 Subject: [PATCH 105/174] Apply suggestions from code review Co-authored-by: Bram Kragten --- .../config/devices/ha-config-device-page.ts | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 46b8a161ed..8b52379f08 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -276,26 +276,14 @@ export class HaConfigDevicePage extends LitElement { return; } - let buttons = this._integrations(device, this.entries).map((entry) => { + const buttons = this._integrations(device, this.entries).forEach((entry) => { if (entry.state !== "loaded" || !entry.supports_remove_device) { - return false; + return; } - return { - entry_id: entry.entry_id, - domain: entry.domain, - }; - }); - - buttons = buttons.filter(Boolean); - - if (buttons.length > 0) { - this._deleteButtons = ( - buttons as { entry_id: string; domain: string }[] - ).map( - (button) => html` + buttons.push(html` ${buttons.length > 1 @@ -304,19 +292,22 @@ export class HaConfigDevicePage extends LitElement { { integration: domainToName( this.hass.localize, - button.domain + entry.domain ), } ) : this.hass.localize(`ui.panel.config.devices.delete_device`)} - ` - ); + `); + }); + + if (buttons.length > 0) { + this._deleteButtons = buttons; } } private async _confirmDeleteEntry(e: MouseEvent): Promise { - const entry_id = (e.currentTarget! as HTMLElement).getAttribute("entry_id"); + const entryId = (e.currentTarget as any). entryId; const confirmed = await showConfirmationDialog(this, { text: this.hass.localize("ui.panel.config.devices.confirm_delete"), @@ -326,7 +317,7 @@ export class HaConfigDevicePage extends LitElement { return; } - await removeConfigEntryFromDevice(this.hass!, this.deviceId, entry_id!); + await removeConfigEntryFromDevice(this.hass!, this.deviceId, entryId); } protected firstUpdated(changedProps) { From 26689a0a855a21f24b981da63e7d720a1cd687fe Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 16:21:11 +0100 Subject: [PATCH 106/174] Update src/panels/config/devices/ha-config-device-page.ts Co-authored-by: Bram Kragten --- src/panels/config/devices/ha-config-device-page.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 8b52379f08..7e94b312b4 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -276,7 +276,8 @@ export class HaConfigDevicePage extends LitElement { return; } - const buttons = this._integrations(device, this.entries).forEach((entry) => { + const buttons = []; + this._integrations(device, this.entries).forEach((entry) => { if (entry.state !== "loaded" || !entry.supports_remove_device) { return; } From fe8a1152c465698082ee9fdbc30c57ca7bfafd5d Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 18 Feb 2022 16:36:15 +0100 Subject: [PATCH 107/174] Correct typing --- src/panels/config/devices/ha-config-device-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 7e94b312b4..d18fd3e5d4 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -276,7 +276,7 @@ export class HaConfigDevicePage extends LitElement { return; } - const buttons = []; + const buttons: TemplateResult[] = []; this._integrations(device, this.entries).forEach((entry) => { if (entry.state !== "loaded" || !entry.supports_remove_device) { return; From b29563a254de7367cc29093464a596ef14cef1a8 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 18 Feb 2022 16:41:18 +0100 Subject: [PATCH 108/174] Prettier --- .../config/devices/ha-config-device-page.ts | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index d18fd3e5d4..6d56220da3 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -282,24 +282,21 @@ export class HaConfigDevicePage extends LitElement { return; } buttons.push(html` - - ${buttons.length > 1 - ? this.hass.localize( - `ui.panel.config.devices.delete_device_integration`, - { - integration: domainToName( - this.hass.localize, - entry.domain - ), - } - ) - : this.hass.localize(`ui.panel.config.devices.delete_device`)} - - `); + + ${buttons.length > 1 + ? this.hass.localize( + `ui.panel.config.devices.delete_device_integration`, + { + integration: domainToName(this.hass.localize, entry.domain), + } + ) + : this.hass.localize(`ui.panel.config.devices.delete_device`)} + + `); }); if (buttons.length > 0) { @@ -308,7 +305,7 @@ export class HaConfigDevicePage extends LitElement { } private async _confirmDeleteEntry(e: MouseEvent): Promise { - const entryId = (e.currentTarget as any). entryId; + const entryId = (e.currentTarget as any).entryId; const confirmed = await showConfirmationDialog(this, { text: this.hass.localize("ui.panel.config.devices.confirm_delete"), From bc6ef7780c5e9a9d6300b2b6424f1e8d9618a4a3 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 18 Feb 2022 16:49:23 +0100 Subject: [PATCH 109/174] Remove useless Array.isArray check --- src/panels/config/devices/ha-config-device-page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 6d56220da3..ee2bdddda9 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -430,7 +430,7 @@ export class HaConfigDevicePage extends LitElement { if (Array.isArray(this._diagnosticDownloadLinks)) { deviceActions.push(...this._diagnosticDownloadLinks); } - if (Array.isArray(this._deleteButtons)) { + if (this._deleteButtons) { deviceActions.push(...this._deleteButtons); } From cc177ef911e1d6a3ac274a9eabf678de7a1f87d7 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Fri, 18 Feb 2022 21:40:09 +0100 Subject: [PATCH 110/174] Remove custom Tasmota delete device button (#11725) --- src/data/tasmota.ts | 10 ---- .../tasmota/ha-device-actions-tasmota.ts | 46 ------------------- .../config/devices/ha-config-device-page.ts | 11 ----- 3 files changed, 67 deletions(-) delete mode 100644 src/data/tasmota.ts delete mode 100644 src/panels/config/devices/device-detail/integration-elements/tasmota/ha-device-actions-tasmota.ts diff --git a/src/data/tasmota.ts b/src/data/tasmota.ts deleted file mode 100644 index df9aad3032..0000000000 --- a/src/data/tasmota.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { HomeAssistant } from "../types"; - -export const removeTasmotaDeviceEntry = ( - hass: HomeAssistant, - deviceId: string -): Promise => - hass.callWS({ - type: "tasmota/device/remove", - device_id: deviceId, - }); diff --git a/src/panels/config/devices/device-detail/integration-elements/tasmota/ha-device-actions-tasmota.ts b/src/panels/config/devices/device-detail/integration-elements/tasmota/ha-device-actions-tasmota.ts deleted file mode 100644 index a24a7be730..0000000000 --- a/src/panels/config/devices/device-detail/integration-elements/tasmota/ha-device-actions-tasmota.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property } from "lit/decorators"; -import { DeviceRegistryEntry } from "../../../../../../data/device_registry"; -import { removeTasmotaDeviceEntry } from "../../../../../../data/tasmota"; -import { showConfirmationDialog } from "../../../../../../dialogs/generic/show-dialog-box"; -import { haStyle } from "../../../../../../resources/styles"; -import { HomeAssistant } from "../../../../../../types"; - -@customElement("ha-device-actions-tasmota") -export class HaDeviceActionsTasmota extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property() public device!: DeviceRegistryEntry; - - protected render(): TemplateResult { - return html` - - ${this.hass.localize("ui.panel.config.devices.delete")} - - `; - } - - private async _confirmDeleteEntry(): Promise { - const confirmed = await showConfirmationDialog(this, { - text: this.hass.localize("ui.panel.config.devices.confirm_delete"), - }); - - if (!confirmed) { - return; - } - - await removeTasmotaDeviceEntry(this.hass!, this.device.id); - } - - static get styles(): CSSResultGroup { - return [ - haStyle, - css` - :host { - display: flex; - justify-content: space-between; - } - `, - ]; - } -} diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts index 5e966278e8..b29aa39951 100644 --- a/src/panels/config/devices/ha-config-device-page.ts +++ b/src/panels/config/devices/ha-config-device-page.ts @@ -860,17 +860,6 @@ export class HaConfigDevicePage extends LitElement { > `); } - if (domains.includes("tasmota")) { - import( - "./device-detail/integration-elements/tasmota/ha-device-actions-tasmota" - ); - deviceActions.push(html` - - `); - } if (domains.includes("zha")) { import("./device-detail/integration-elements/zha/ha-device-actions-zha"); import("./device-detail/integration-elements/zha/ha-device-info-zha"); From 494cc3a5698af32a3bce79d80d7e87f8dbabdde9 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Fri, 18 Feb 2022 14:48:17 -0600 Subject: [PATCH 111/174] Automation Conditions to conversion to ha-form or mwc (#11727) --- src/components/ha-form/types.ts | 2 +- .../ha-selector/ha-selector-time.ts | 1 + .../ha-automation-condition-editor.ts | 2 +- .../condition/ha-automation-condition-row.ts | 2 +- .../types/ha-automation-condition-device.ts | 11 +- .../types/ha-automation-condition-logical.ts | 11 +- .../ha-automation-condition-numeric_state.ts | 94 +++--- .../types/ha-automation-condition-state.ts | 99 +++--- .../types/ha-automation-condition-sun.ts | 165 ++++------ .../types/ha-automation-condition-template.ts | 31 +- .../types/ha-automation-condition-time.ts | 290 +++++++----------- .../types/ha-automation-condition-trigger.ts | 4 +- .../types/ha-automation-condition-zone.ts | 9 +- .../types/ha-automation-trigger-device.ts | 2 +- .../ha-automation-trigger-numeric_state.ts | 9 +- .../types/ha-automation-trigger-state.ts | 27 +- .../types/ha-automation-trigger-sun.ts | 3 +- .../types/ha-automation-trigger-template.ts | 31 +- .../types/ha-automation-trigger-time.ts | 137 +++++---- .../types/ha-automation-trigger-zone.ts | 9 +- src/translations/en.json | 12 +- 21 files changed, 452 insertions(+), 499 deletions(-) diff --git a/src/components/ha-form/types.ts b/src/components/ha-form/types.ts index d6cfc573ef..f476e759ca 100644 --- a/src/components/ha-form/types.ts +++ b/src/components/ha-form/types.ts @@ -49,7 +49,7 @@ export interface HaFormSelectSchema extends HaFormBaseSchema { export interface HaFormMultiSelectSchema extends HaFormBaseSchema { type: "multi_select"; - options: Record | string[]; + options: Record | string[] | Array<[string, string]>; } export interface HaFormFloatSchema extends HaFormBaseSchema { diff --git a/src/components/ha-selector/ha-selector-time.ts b/src/components/ha-selector/ha-selector-time.ts index fb3b4e2b8d..ff829ff12b 100644 --- a/src/components/ha-selector/ha-selector-time.ts +++ b/src/components/ha-selector/ha-selector-time.ts @@ -22,6 +22,7 @@ export class HaTimeSelector extends LitElement { .value=${this.value} .locale=${this.hass.locale} .disabled=${this.disabled} + .label=${this.label} enable-second > `; diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index 59148694cc..e6c9378c8f 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -6,7 +6,7 @@ import memoizeOne from "memoize-one"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; import { stringCompare } from "../../../../common/string/compare"; -import { LocalizeFunc } from "../../../../common/translations/localize"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-card"; import "../../../../components/ha-yaml-editor"; import type { Condition } from "../../../../data/automation"; diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index bee7358190..4bce08a441 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -27,7 +27,7 @@ export const handleChangeEvent = ( if (!name) { return; } - const newVal = ev.detail.value; + const newVal = ev.detail?.value || (ev.currentTarget as any)?.value; if ((element.condition[name] || "") === newVal) { return; diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-device.ts b/src/panels/config/automation/condition/types/ha-automation-condition-device.ts index b158625876..77b9a94b89 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-device.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-device.ts @@ -1,4 +1,4 @@ -import { html, LitElement } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; @@ -11,7 +11,7 @@ import { DeviceCondition, fetchDeviceConditionCapabilities, } from "../../../../../data/device_automation"; -import { HomeAssistant } from "../../../../../types"; +import type { HomeAssistant } from "../../../../../types"; @customElement("ha-automation-condition-device") export class HaDeviceCondition extends LitElement { @@ -147,6 +147,13 @@ export class HaDeviceCondition extends LitElement { `ui.panel.config.automation.editor.conditions.type.device.extra_fields.${schema.name}` ) || schema.name; } + + static styles = css` + ha-device-picker { + display: block; + margin-bottom: 24px; + } + `; } declare global { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts index 35e9685463..18933eb6df 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-logical.ts @@ -1,17 +1,20 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import { Condition, LogicalCondition } from "../../../../../data/automation"; -import { HomeAssistant } from "../../../../../types"; +import type { + Condition, + LogicalCondition, +} from "../../../../../data/automation"; +import type { HomeAssistant } from "../../../../../types"; import "../ha-automation-condition"; -import { ConditionElement } from "../ha-automation-condition-row"; +import type { ConditionElement } from "../ha-automation-condition-row"; import { HaStateCondition } from "./ha-automation-condition-state"; @customElement("ha-automation-condition-logical") export class HaLogicalCondition extends LitElement implements ConditionElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public condition!: LogicalCondition; + @property({ attribute: false }) public condition!: LogicalCondition; public static get defaultConfig() { return { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts index 37216fdab4..56944306d0 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts @@ -1,17 +1,17 @@ -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-input/paper-textarea"; +import "../../../../../components/ha-form/ha-form"; import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; -import "../../../../../components/entity/ha-entity-picker"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; import { NumericStateCondition } from "../../../../../data/automation"; -import { HomeAssistant } from "../../../../../types"; -import { handleChangeEvent } from "../ha-automation-condition-row"; +import type { HomeAssistant } from "../../../../../types"; @customElement("ha-automation-condition-numeric_state") export default class HaNumericStateCondition extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public condition!: NumericStateCondition; + @property({ attribute: false }) public condition!: NumericStateCondition; public static get defaultConfig() { return { @@ -19,60 +19,54 @@ export default class HaNumericStateCondition extends LitElement { }; } + private _schema = memoizeOne((entityId): HaFormSchema[] => [ + { name: "entity_id", required: true, selector: { entity: {} } }, + { + name: "attribute", + selector: { attribute: { entity_id: entityId } }, + }, + { name: "above", selector: { text: {} } }, + { name: "below", selector: { text: {} } }, + { + name: "value_template", + selector: { text: { multiline: true } }, + }, + ]); + public render() { - const { value_template, entity_id, attribute, below, above } = - this.condition; + const schema = this._schema(this.condition.entity_id); return html` - - - - - + .computeLabel=${this._computeLabelCallback} + > `; } private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); + ev.stopPropagation(); + const newTrigger = ev.detail.value; + fireEvent(this, "value-changed", { value: newTrigger }); } + + private _computeLabelCallback = (schema: HaFormSchema): string => { + switch (schema.name) { + case "entity_id": + return this.hass.localize("ui.components.entity.entity-picker.entity"); + case "attribute": + return this.hass.localize( + "ui.components.entity.entity-attribute-picker.attribute" + ); + default: + return this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.numeric_state.${schema.name}` + ); + } + }; } declare global { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index 4a6e5a86ef..9c88cdfb50 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -1,19 +1,14 @@ -import "@polymer/paper-input/paper-input"; import { html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { assert, literal, object, optional, string, union } from "superstruct"; import { createDurationData } from "../../../../../common/datetime/create_duration_data"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import "../../../../../components/entity/ha-entity-attribute-picker"; -import "../../../../../components/entity/ha-entity-picker"; -import "../../../../../components/ha-duration-input"; -import { StateCondition } from "../../../../../data/automation"; -import { HomeAssistant } from "../../../../../types"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import type { StateCondition } from "../../../../../data/automation"; +import type { HomeAssistant } from "../../../../../types"; import { forDictStruct } from "../../structs"; -import { - ConditionElement, - handleChangeEvent, -} from "../ha-automation-condition-row"; +import type { ConditionElement } from "../ha-automation-condition-row"; const stateConditionStruct = object({ condition: literal("state"), @@ -27,12 +22,22 @@ const stateConditionStruct = object({ export class HaStateCondition extends LitElement implements ConditionElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public condition!: StateCondition; + @property({ attribute: false }) public condition!: StateCondition; public static get defaultConfig() { return { entity_id: "", state: "" }; } + private _schema = memoizeOne((entityId) => [ + { name: "entity_id", required: true, selector: { entity: {} } }, + { + name: "attribute", + selector: { attribute: { entity_id: entityId } }, + }, + { name: "state", selector: { text: {} } }, + { name: "for", selector: { duration: {} } }, + ]); + public shouldUpdate(changedProperties: PropertyValues) { if (changedProperties.has("condition")) { try { @@ -46,50 +51,52 @@ export class HaStateCondition extends LitElement implements ConditionElement { } protected render() { - const { entity_id, attribute, state } = this.condition; - const forTime = createDurationData(this.condition.for); + const trgFor = createDurationData(this.condition.for); + const data = { ...this.condition, ...{ for: trgFor } }; + const schema = this._schema(this.condition.entity_id); return html` - - - - + .computeLabel=${this._computeLabelCallback} + > `; } private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); + ev.stopPropagation(); + const newTrigger = ev.detail.value; + + Object.keys(newTrigger).forEach((key) => + newTrigger[key] === undefined || newTrigger[key] === "" + ? delete newTrigger[key] + : {} + ); + + fireEvent(this, "value-changed", { value: newTrigger }); } + + private _computeLabelCallback = (schema: HaFormSchema): string => { + switch (schema.name) { + case "entity_id": + return this.hass.localize("ui.components.entity.entity-picker.entity"); + case "attribute": + return this.hass.localize( + "ui.components.entity.entity-attribute-picker.attribute" + ); + case "for": + return this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.state.for` + ); + default: + return this.hass.localize( + `ui.panel.config.automation.editor.conditions.type.state.${schema.name}` + ); + } + }; } declare global { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts b/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts index 0cbf5a339a..e3d466ca6e 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts @@ -1,16 +1,12 @@ -import "@polymer/paper-input/paper-input"; -import { css, html, LitElement } from "lit"; +import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; import type { SunCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; -import { - ConditionElement, - handleChangeEvent, -} from "../ha-automation-condition-row"; -import "../../../../../components/ha-radio"; -import "../../../../../components/ha-formfield"; -import type { HaRadio } from "../../../../../components/ha-radio"; +import type { ConditionElement } from "../ha-automation-condition-row"; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; @customElement("ha-automation-condition-sun") export class HaSunCondition extends LitElement implements ConditionElement { @@ -22,111 +18,72 @@ export class HaSunCondition extends LitElement implements ConditionElement { return {}; } + private _schema = memoizeOne((localize: LocalizeFunc) => [ + { + name: "before", + type: "select", + required: true, + options: [ + [ + "sunrise", + localize( + "ui.panel.config.automation.editor.conditions.type.sun.sunrise" + ), + ], + [ + "sunset", + localize( + "ui.panel.config.automation.editor.conditions.type.sun.sunset" + ), + ], + ], + }, + { name: "before_offset", selector: { text: {} } }, + { + name: "after", + type: "select", + required: true, + options: [ + [ + "sunrise", + localize( + "ui.panel.config.automation.editor.conditions.type.sun.sunrise" + ), + ], + [ + "sunset", + localize( + "ui.panel.config.automation.editor.conditions.type.sun.sunset" + ), + ], + ], + }, + { name: "after_offset", selector: { text: {} } }, + ]); + protected render() { - const { after, after_offset, before, before_offset } = this.condition; + const schema = this._schema(this.hass.localize); return html` - - - - - - - + > `; } private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); - } - - private _radioGroupPicked(ev: CustomEvent) { - const key = (ev.target as HaRadio).name; ev.stopPropagation(); - fireEvent(this, "value-changed", { - value: { - ...this.condition, - [key]: (ev.target as HaRadio).value, - }, - }); + const newTrigger = ev.detail.value; + fireEvent(this, "value-changed", { value: newTrigger }); } - static styles = css` - label { - display: flex; - align-items: center; - } - `; + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( + `ui.panel.config.automation.editor.conditions.type.sun.${schema.name}` + ); } declare global { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-template.ts b/src/panels/config/automation/condition/types/ha-automation-condition-template.ts index d83eea64e2..6ada82f152 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-template.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-template.ts @@ -1,15 +1,15 @@ -import "@polymer/paper-input/paper-textarea"; -import { html, LitElement } from "lit"; +import "../../../../../components/ha-textarea"; +import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; -import { TemplateCondition } from "../../../../../data/automation"; -import { HomeAssistant } from "../../../../../types"; +import type { TemplateCondition } from "../../../../../data/automation"; +import type { HomeAssistant } from "../../../../../types"; import { handleChangeEvent } from "../ha-automation-condition-row"; @customElement("ha-automation-condition-template") export class HaTemplateCondition extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public condition!: TemplateCondition; + @property({ attribute: false }) public condition!: TemplateCondition; public static get defaultConfig() { return { value_template: "" }; @@ -18,19 +18,32 @@ export class HaTemplateCondition extends LitElement { protected render() { const { value_template } = this.condition; return html` - + autogrow + > `; } private _valueChanged(ev: CustomEvent): void { handleChangeEvent(this, ev); } + + static styles = css` + ha-textarea { + display: block; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-condition-template": HaTemplateCondition; + } } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts index f227847a3f..f09a132ff2 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts @@ -1,20 +1,12 @@ -import { Radio } from "@material/mwc-radio"; -import { css, CSSResultGroup, html, LitElement } from "lit"; +import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import { computeRTLDirection } from "../../../../../common/util/compute_rtl"; -import "../../../../../components/ha-formfield"; -import "../../../../../components/ha-radio"; -import { HaSwitch } from "../../../../../components/ha-switch"; -import { TimeCondition } from "../../../../../data/automation"; -import { HomeAssistant } from "../../../../../types"; -import { - ConditionElement, - handleChangeEvent, -} from "../ha-automation-condition-row"; -import "../../../../../components/ha-time-input"; - -const includeDomains = ["input_datetime"]; +import type { TimeCondition } from "../../../../../data/automation"; +import type { HomeAssistant } from "../../../../../types"; +import type { ConditionElement } from "../ha-automation-condition-row"; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; const DAYS = { mon: 1, @@ -26,10 +18,6 @@ const DAYS = { sun: 7, }; -interface WeekdayHaSwitch extends HaSwitch { - day: string; -} - @customElement("ha-automation-condition-time") export class HaTimeCondition extends LitElement implements ConditionElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -44,176 +32,136 @@ export class HaTimeCondition extends LitElement implements ConditionElement { return {}; } - protected render() { - const { after, before, weekday } = this.condition; + private _schema = memoizeOne( + ( + localize: LocalizeFunc, + inputModeAfter?: boolean, + inputModeBefore?: boolean + ): HaFormSchema[] => { + const modeAfterSchema = inputModeAfter + ? { name: "after", selector: { entity: { domain: "input_datetime" } } } + : { name: "after", selector: { time: {} } }; + const modeBeforeSchema = inputModeBefore + ? { name: "before", selector: { entity: { domain: "input_datetime" } } } + : { name: "before", selector: { time: {} } }; + + return [ + { + name: "mode_after", + type: "select", + required: true, + options: [ + [ + "value", + localize( + "ui.panel.config.automation.editor.conditions.type.time.type_value" + ), + ], + [ + "input", + localize( + "ui.panel.config.automation.editor.conditions.type.time.type_input" + ), + ], + ], + }, + modeAfterSchema, + { + name: "mode_before", + type: "select", + required: true, + options: [ + [ + "value", + localize( + "ui.panel.config.automation.editor.conditions.type.time.type_value" + ), + ], + [ + "input", + localize( + "ui.panel.config.automation.editor.conditions.type.time.type_input" + ), + ], + ], + }, + modeBeforeSchema, + { + type: "multi_select", + name: "weekday", + options: Object.keys(DAYS).map((day) => [ + day, + localize( + `ui.panel.config.automation.editor.conditions.type.time.weekdays.${day}` + ), + ]), + }, + ]; + } + ); + + protected render() { const inputModeBefore = - this._inputModeBefore ?? before?.startsWith("input_datetime."); + this._inputModeBefore ?? + this.condition.before?.startsWith("input_datetime."); const inputModeAfter = - this._inputModeAfter ?? after?.startsWith("input_datetime."); + this._inputModeAfter ?? + this.condition.after?.startsWith("input_datetime."); + + const schema: HaFormSchema[] = this._schema( + this.hass.localize, + inputModeAfter, + inputModeBefore + ); + + const data = { + mode_before: "value", + mode_after: "value", + ...this.condition, + }; return html` - - - - - - - ${inputModeAfter - ? html`` - : html``} - - - - - - - - ${inputModeBefore - ? html`` - : html``} - ${Object.keys(DAYS).map( - (day) => html` - - - - - ` - )} + `; } - private _handleModeChanged(ev: Event) { - const target = ev.target as Radio; - if (target.getAttribute("name") === "mode_after") { - this._inputModeAfter = target.value === "input"; - } else { - this._inputModeBefore = target.value === "input"; - } - } - private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); - } + ev.stopPropagation(); + const newValue = ev.detail.value; - private _dayValueChanged(ev: CustomEvent): void { - const daySwitch = ev.currentTarget as WeekdayHaSwitch; + const newModeAfter = newValue.mode_after === "input"; + const newModeBefore = newValue.mode_before === "input"; - let days: string[]; - - if (!this.condition.weekday) { - days = Object.keys(DAYS); - } else { - days = !Array.isArray(this.condition.weekday) - ? [this.condition.weekday] - : this.condition.weekday; + if (newModeAfter !== this._inputModeAfter) { + this._inputModeAfter = newModeAfter; + newValue.after = undefined; } - if (daySwitch.checked) { - days.push(daySwitch.day); - } else { - days = days.filter((d) => d !== daySwitch.day); + if (newModeBefore !== this._inputModeBefore) { + this._inputModeBefore = newModeBefore; + newValue.before = undefined; } - days.sort((a: string, b: string) => DAYS[a] - DAYS[b]); + Object.keys(newValue).forEach((key) => + newValue[key] === undefined || newValue[key] === "" + ? delete newValue[key] + : {} + ); - fireEvent(this, "value-changed", { - value: { ...this.condition, weekday: days }, - }); + fireEvent(this, "value-changed", { value: newValue }); } - static get styles(): CSSResultGroup { - return css` - .weekday-toggle { - display: flex; - height: 40px; - } - `; - } + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( + `ui.panel.config.automation.editor.conditions.type.time.${schema.name}` + ); } declare global { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts index ea867147bd..4b81afc26b 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts @@ -5,12 +5,12 @@ import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { ensureArray } from "../../../../../common/ensure-array"; -import { +import type { AutomationConfig, Trigger, TriggerCondition, } from "../../../../../data/automation"; -import { HomeAssistant } from "../../../../../types"; +import type { HomeAssistant } from "../../../../../types"; @customElement("ha-automation-condition-trigger") export class HaTriggerCondition extends LitElement { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts b/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts index cf79928402..6c748351a9 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-zone.ts @@ -1,4 +1,4 @@ -import { html, LitElement } from "lit"; +import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { computeStateDomain } from "../../../../../common/entity/compute_state_domain"; @@ -71,6 +71,13 @@ export class HaZoneCondition extends LitElement { value: { ...this.condition, zone: ev.detail.value }, }); } + + static styles = css` + ha-entity-picker { + display: block; + margin-bottom: 24px; + } + `; } declare global { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts index 82ee809f72..5747edd342 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-device.ts @@ -154,7 +154,7 @@ export class HaDeviceTrigger extends LitElement { static styles = css` ha-device-picker { display: block; - margin-bottom: 8px; + margin-bottom: 24px; } `; } diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts index 1a43407438..c156d2b9aa 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts @@ -16,19 +16,18 @@ export class HaNumericStateTrigger extends LitElement { @property() public trigger!: NumericStateTrigger; private _schema = memoizeOne((entityId): HaFormSchema[] => [ - { name: "entity_id", selector: { entity: {} } }, + { name: "entity_id", required: true, selector: { entity: {} } }, { name: "attribute", selector: { attribute: { entity_id: entityId } }, }, - { name: "above", required: false, selector: { text: {} } }, - { name: "below", required: false, selector: { text: {} } }, + { name: "above", selector: { text: {} } }, + { name: "below", selector: { text: {} } }, { name: "value_template", - required: false, selector: { text: { multiline: true } }, }, - { name: "for", required: false, selector: { duration: {} } }, + { name: "for", selector: { duration: {} } }, ]); public willUpdate(changedProperties: PropertyValues) { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts index d50304561a..44d3183e0d 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts @@ -32,14 +32,6 @@ const stateTriggerStruct = assign( }) ); -const SCHEMA = [ - { name: "entity_id", selector: { entity: {} } }, - { name: "attribute", selector: { attribute: { entity_id: "" } } }, - { name: "from", required: false, selector: { text: {} } }, - { name: "to", required: false, selector: { text: {} } }, - { name: "for", required: false, selector: { duration: {} } }, -]; - @customElement("ha-automation-trigger-state") export class HaStateTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -50,14 +42,16 @@ export class HaStateTrigger extends LitElement implements TriggerElement { return { entity_id: "" }; } - private _schema = memoizeOne((entityId) => { - const schema = [...SCHEMA]; - schema[1] = { + private _schema = memoizeOne((entityId) => [ + { name: "entity_id", required: true, selector: { entity: {} } }, + { name: "attribute", selector: { attribute: { entity_id: entityId } }, - }; - return schema; - }); + }, + { name: "from", selector: { text: {} } }, + { name: "to", selector: { text: {} } }, + { name: "for", selector: { duration: {} } }, + ]); public shouldUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("trigger")) { @@ -118,13 +112,12 @@ export class HaStateTrigger extends LitElement implements TriggerElement { fireEvent(this, "value-changed", { value: newTrigger }); } - private _computeLabelCallback(schema: HaFormSchema): string { - return this.hass.localize( + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( schema.name === "entity_id" ? "ui.components.entity.entity-picker.entity" : `ui.panel.config.automation.editor.triggers.type.state.${schema.name}` ); - } } declare global { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts index a6efcf9d95..0be06923b8 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-sun.ts @@ -45,9 +45,10 @@ export class HaSunTrigger extends LitElement implements TriggerElement { } protected render() { + const schema = this._schema(this.hass.localize); return html` + autogrow + > `; } private _valueChanged(ev: CustomEvent): void { handleChangeEvent(this, ev); } + + static styles = css` + ha-textarea { + display: block; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-trigger-template": HaTemplateTrigger; + } } diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts index a72c5a9749..bdac66a6a1 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts @@ -1,23 +1,19 @@ +import memoizeOne from "memoize-one"; import { html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; -import "../../../../../components/entity/ha-entity-picker"; -import "../../../../../components/ha-formfield"; -import "../../../../../components/ha-radio"; -import { TimeTrigger } from "../../../../../data/automation"; -import { HomeAssistant } from "../../../../../types"; -import { - handleChangeEvent, - TriggerElement, -} from "../ha-automation-trigger-row"; -import "../../../../../components/ha-time-input"; +import type { TimeTrigger } from "../../../../../data/automation"; +import type { HomeAssistant } from "../../../../../types"; +import type { TriggerElement } from "../ha-automation-trigger-row"; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import type { HaFormSchema } from "../../../../../components/ha-form/types"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-form/ha-form"; -const includeDomains = ["input_datetime"]; @customElement("ha-automation-trigger-time") export class HaTimeTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public trigger!: TimeTrigger; + @property({ attribute: false }) public trigger!: TimeTrigger; @state() private _inputMode?: boolean; @@ -25,6 +21,37 @@ export class HaTimeTrigger extends LitElement implements TriggerElement { return { at: "" }; } + private _schema = memoizeOne( + (localize: LocalizeFunc, inputMode?: boolean): HaFormSchema[] => { + const modeSchema = inputMode + ? { name: "at", selector: { entity: { domain: "input_datetime" } } } + : { name: "at", selector: { time: {} } }; + + return [ + { + name: "mode", + type: "select", + required: true, + options: [ + [ + "value", + localize( + "ui.panel.config.automation.editor.triggers.type.time.type_value" + ), + ], + [ + "input", + localize( + "ui.panel.config.automation.editor.triggers.type.time.type_input" + ), + ], + ], + }, + modeSchema, + ]; + } + ); + public willUpdate(changedProperties: PropertyValues) { if (!changedProperties.has("trigger")) { return; @@ -50,67 +77,43 @@ export class HaTimeTrigger extends LitElement implements TriggerElement { this._inputMode ?? (at?.startsWith("input_datetime.") || at?.startsWith("sensor.")); - return html` - - - - - + const schema: HaFormSchema[] = this._schema(this.hass.localize, inputMode); - ${inputMode - ? html`` - : html``} `; - } + const data = { + mode: "value", + ...this.trigger, + }; - private _handleModeChanged(ev: Event) { - this._inputMode = (ev.target as any).value === "input"; + return html` + + `; } private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); + ev.stopPropagation(); + const newValue = ev.detail.value; + + this._inputMode = newValue.mode.value === "input"; + + Object.keys(newValue).forEach((key) => + newValue[key] === undefined || newValue[key] === "" + ? delete newValue[key] + : {} + ); + + fireEvent(this, "value-changed", { value: newValue }); } + + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( + `ui.panel.config.automation.editor.triggers.type.time.${schema.name}` + ); } declare global { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-zone.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-zone.ts index 333f3dc529..7b97d9a5eb 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-zone.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-zone.ts @@ -1,14 +1,13 @@ +import "../../../../../components/entity/ha-entity-picker"; +import "../../../../../components/ha-formfield"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { computeStateDomain } from "../../../../../common/entity/compute_state_domain"; import { hasLocation } from "../../../../../common/entity/has_location"; -import "../../../../../components/entity/ha-entity-picker"; import type { ZoneTrigger } from "../../../../../data/automation"; import type { PolymerChangedEvent } from "../../../../../polymer-types"; import type { HomeAssistant } from "../../../../../types"; -import "../../../../../components/ha-radio"; -import "../../../../../components/ha-formfield"; import type { HaRadio } from "../../../../../components/ha-radio"; function zoneAndLocationFilter(stateObj) { @@ -116,6 +115,10 @@ export class HaZoneTrigger extends LitElement { display: flex; align-items: center; } + ha-entity-picker { + display: block; + margin-bottom: 24px; + } `; } diff --git a/src/translations/en.json b/src/translations/en.json index df493b082b..b7bb981739 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1653,7 +1653,7 @@ "label": "Geolocation", "source": "Source", "zone": "Zone", - "event": "Event:", + "event": "Event", "enter": "Enter", "leave": "Leave" }, @@ -1699,7 +1699,8 @@ "type_value": "Fixed time", "type_input": "Value of a date/time helper", "label": "Time", - "at": "At time" + "at": "At time", + "mode": "Mode" }, "time_pattern": { "label": "Time Pattern", @@ -1767,8 +1768,8 @@ }, "sun": { "label": "[%key:ui::panel::config::automation::editor::triggers::type::sun::label%]", - "before": "Before:", - "after": "After:", + "before": "Before", + "after": "After", "before_offset": "Before offset (optional)", "after_offset": "After offset (optional)", "sunrise": "Sunrise", @@ -1784,6 +1785,9 @@ "label": "[%key:ui::panel::config::automation::editor::triggers::type::time::label%]", "after": "After", "before": "Before", + "weekday": "Weekdays", + "mode_after": "[%key:ui::panel::config::automation::editor::conditions::type::time::after%]", + "mode_before": "[%key:ui::panel::config::automation::editor::conditions::type::time::before%]", "weekdays": { "mon": "Monday", "tue": "Tuesday", From 4fc06172894a17434c912232f548b2ced12f5e68 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Fri, 18 Feb 2022 15:48:59 -0500 Subject: [PATCH 112/174] Set initial focus for energy dialogs (#11730) --- .../config/energy/dialogs/dialog-energy-battery-settings.ts | 1 + .../config/energy/dialogs/dialog-energy-device-settings.ts | 1 + src/panels/config/energy/dialogs/dialog-energy-gas-settings.ts | 1 + .../config/energy/dialogs/dialog-energy-grid-flow-settings.ts | 1 + src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts | 1 + 5 files changed, 5 insertions(+) diff --git a/src/panels/config/energy/dialogs/dialog-energy-battery-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-battery-settings.ts index 394fcfcd65..b91e4e3424 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-battery-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-battery-settings.ts @@ -74,6 +74,7 @@ export class DialogEnergyBatterySettings "ui.panel.config.energy.battery.dialog.energy_into_battery" )} @value-changed=${this._statisticToChanged} + dialogInitialFocus > diff --git a/src/panels/config/energy/dialogs/dialog-energy-gas-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-gas-settings.ts index fea22bc68d..b180c5f151 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-gas-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-gas-settings.ts @@ -107,6 +107,7 @@ export class DialogEnergyGasSettings : "m³" })`} @value-changed=${this._statisticChanged} + dialogInitialFocus >

    diff --git a/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts index 4ae094ab52..d32e57f505 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-grid-flow-settings.ts @@ -104,6 +104,7 @@ export class DialogEnergyGridFlowSettings `ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.energy_stat` )} @value-changed=${this._statisticChanged} + dialogInitialFocus >

    diff --git a/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts b/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts index cdd24d513f..1f2acaf624 100644 --- a/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts +++ b/src/panels/config/energy/dialogs/dialog-energy-solar-settings.ts @@ -86,6 +86,7 @@ export class DialogEnergySolarSettings "ui.panel.config.energy.solar.dialog.solar_production_energy" )} @value-changed=${this._statisticChanged} + dialogInitialFocus >

    From 8999ca2ea06a1525d6747e1bd279a6212ab8ebfe Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Fri, 18 Feb 2022 14:51:37 -0600 Subject: [PATCH 113/174] Entity Settings Page to MWC 3 (#11694) --- .../entities/entity-registry-basic-editor.ts | 19 +++--- .../entities/entity-registry-settings.ts | 39 +++++++----- .../config/helpers/dialog-helper-detail.ts | 62 +++++++++---------- .../config/helpers/forms/ha-counter-form.ts | 42 +++++++------ .../helpers/forms/ha-input_boolean-form.ts | 16 +++-- .../helpers/forms/ha-input_button-form.ts | 16 +++-- .../helpers/forms/ha-input_datetime-form.ts | 16 +++-- .../helpers/forms/ha-input_number-form.ts | 46 ++++++++------ .../helpers/forms/ha-input_select-form.ts | 59 ++++++++++-------- .../helpers/forms/ha-input_text-form.ts | 37 ++++++----- .../config/helpers/forms/ha-timer-form.ts | 21 ++++--- 11 files changed, 211 insertions(+), 162 deletions(-) diff --git a/src/panels/config/entities/entity-registry-basic-editor.ts b/src/panels/config/entities/entity-registry-basic-editor.ts index af79a115aa..52240794b3 100644 --- a/src/panels/config/entities/entity-registry-basic-editor.ts +++ b/src/panels/config/entities/entity-registry-basic-editor.ts @@ -1,10 +1,10 @@ -import "@polymer/paper-input/paper-input"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { computeDomain } from "../../../common/entity/compute_domain"; import "../../../components/ha-area-picker"; import "../../../components/ha-switch"; +import "../../../components/ha-textfield"; import type { HaSwitch } from "../../../components/ha-switch"; import { DeviceRegistryEntry, @@ -17,7 +17,6 @@ import { } from "../../../data/entity_registry"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; -import type { PolymerChangedEvent } from "../../../polymer-types"; import type { HomeAssistant } from "../../../types"; @customElement("ha-registry-basic-editor") @@ -123,16 +122,16 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { computeDomain(this.entry.entity_id); return html` - + @input=${this._entityIdChanged} + > ): void { - this._entityId = ev.detail.value; + private _entityIdChanged(ev): void { + this._entityId = ev.target.value; } private _disabledByChanged(ev: Event): void { @@ -199,6 +198,10 @@ export class HaEntityRegistryBasicEditor extends SubscribeMixin(LitElement) { .secondary { color: var(--secondary-text-color); } + ha-textfield { + display: block; + margin-bottom: 8px; + } `; } } diff --git a/src/panels/config/entities/entity-registry-settings.ts b/src/panels/config/entities/entity-registry-settings.ts index f4c40e335f..4b73600b59 100644 --- a/src/panels/config/entities/entity-registry-settings.ts +++ b/src/panels/config/entities/entity-registry-settings.ts @@ -1,7 +1,7 @@ +import "../../../components/ha-alert"; import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list-item"; import "@material/mwc-select/mwc-select"; -import "@polymer/paper-input/paper-input"; import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, @@ -19,6 +19,7 @@ import "../../../components/ha-area-picker"; import "../../../components/ha-expansion-panel"; import "../../../components/ha-icon-picker"; import "../../../components/ha-switch"; +import "../../../components/ha-textfield"; import type { HaSwitch } from "../../../components/ha-switch"; import { DeviceRegistryEntry, @@ -36,7 +37,6 @@ import { showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; -import type { PolymerChangedEvent } from "../../../polymer-types"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail"; @@ -137,15 +137,18 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {

    ` : ""} - ${this._error ? html`
    ${this._error}
    ` : ""} + ${this._error + ? html`${this._error}` + : ""}
    - + .placeholder=${this.entry.original_name} + @input=${this._nameChanged} + > ` : ""} - + @input=${this._entityIdChanged} + > ${!this.entry.device_id ? html`): void { + private _nameChanged(ev): void { this._error = undefined; - this._name = ev.detail.value; + this._name = ev.target.value; } - private _iconChanged(ev: PolymerChangedEvent): void { + private _iconChanged(ev: CustomEvent): void { this._error = undefined; this._icon = ev.detail.value; } - private _entityIdChanged(ev: PolymerChangedEvent): void { + private _entityIdChanged(ev): void { this._error = undefined; - this._entityId = ev.detail.value; + this._entityId = ev.target.value; } private _deviceClassChanged(ev): void { @@ -423,6 +426,10 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) { ha-switch { margin-right: 16px; } + ha-textfield { + display: block; + margin: 8px 0; + } .row { margin: 8px 0; color: var(--primary-text-color); diff --git a/src/panels/config/helpers/dialog-helper-detail.ts b/src/panels/config/helpers/dialog-helper-detail.ts index eace117eb7..bbd0b645e1 100644 --- a/src/panels/config/helpers/dialog-helper-detail.ts +++ b/src/panels/config/helpers/dialog-helper-detail.ts @@ -116,36 +116,35 @@ export class DialogHelperDetail extends LitElement { ${Object.keys(HELPERS).map((platform: string) => { const isLoaded = isComponentLoaded(this.hass, platform); return html` -
    - - - - ${this.hass.localize( - `ui.panel.config.helpers.types.${platform}` - ) || platform} - - - ${!isLoaded - ? html` - ${this.hass.localize( - "ui.dialogs.helper_settings.platform_not_loaded", - "platform", - platform - )} - ` - : ""} -
    + + + + ${this.hass.localize( + `ui.panel.config.helpers.types.${platform}` + ) || platform} + + + ${!isLoaded + ? html` + ${this.hass.localize( + "ui.dialogs.helper_settings.platform_not_loaded", + "platform", + platform + )} + ` + : ""} `; })} @@ -208,9 +207,6 @@ export class DialogHelperDetail extends LitElement { ha-dialog.button-left { --justify-action-buttons: flex-start; } - paper-icon-item { - cursor: pointer; - } `, ]; } diff --git a/src/panels/config/helpers/forms/ha-counter-form.ts b/src/panels/config/helpers/forms/ha-counter-form.ts index badebfd38c..9aa1186577 100644 --- a/src/panels/config/helpers/forms/ha-counter-form.ts +++ b/src/panels/config/helpers/forms/ha-counter-form.ts @@ -1,9 +1,9 @@ -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-icon-picker"; import "../../../../components/ha-switch"; +import "../../../../components/ha-textfield"; import type { HaSwitch } from "../../../../components/ha-switch"; import { Counter } from "../../../../data/counter"; import { haStyle } from "../../../../resources/styles"; @@ -68,10 +68,10 @@ class HaCounterForm extends LitElement { return html`
    - + > - - + - + + > ${this.hass.userData?.showAdvanced ? html` - + >
    - + > - + > - + > - + > - - + + > ${this.hass.userData?.showAdvanced ? html`
    @@ -143,24 +143,24 @@ class HaInputNumberForm extends LitElement { >
    - + > - + > ` : ""}
    @@ -181,7 +181,10 @@ class HaInputNumberForm extends LitElement { const target = ev.target as any; const configValue = target.configValue; const value = - target.type === "number" ? Number(ev.detail.value) : ev.detail.value; + target.type === "number" + ? Number(target.value) + : ev.detail?.value || target.value; + if (this[`_${configValue}`] === value) { return; } @@ -189,7 +192,7 @@ class HaInputNumberForm extends LitElement { if (value === undefined || value === "") { delete newValue[configValue]; } else { - newValue[configValue] = ev.detail.value; + newValue[configValue] = value; } fireEvent(this, "value-changed", { value: newValue, @@ -203,6 +206,11 @@ class HaInputNumberForm extends LitElement { .form { color: var(--primary-text-color); } + + ha-textfield { + display: block; + margin-bottom: 8px; + } `, ]; } diff --git a/src/panels/config/helpers/forms/ha-input_select-form.ts b/src/panels/config/helpers/forms/ha-input_select-form.ts index 8c23ff0c34..cec1eb952e 100644 --- a/src/panels/config/helpers/forms/ha-input_select-form.ts +++ b/src/panels/config/helpers/forms/ha-input_select-form.ts @@ -1,14 +1,13 @@ import "@material/mwc-button/mwc-button"; +import "@material/mwc-list/mwc-list-item"; import { mdiDelete } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; -import type { PaperInputElement } from "@polymer/paper-input/paper-input"; -import "@polymer/paper-item/paper-item"; -import "@polymer/paper-item/paper-item-body"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-icon-button"; import "../../../../components/ha-icon-picker"; +import "../../../../components/ha-textfield"; +import type { HaTextField } from "../../../../components/ha-textfield"; import type { InputSelect } from "../../../../data/input_select"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; @@ -28,7 +27,7 @@ class HaInputSelectForm extends LitElement { @state() private _options: string[] = []; - @query("#option_input", true) private _optionInput?: PaperInputElement; + @query("#option_input", true) private _optionInput?: HaTextField; set item(item: InputSelect) { this._item = item; @@ -59,19 +58,19 @@ class HaInputSelectForm extends LitElement { return html`
    - + .configValue=${"name"} + @input=${this._valueChanged} + > html` - - ${option} + + ${option} - + ` ) : html` - + ${this.hass!.localize( "ui.dialogs.helper_settings.input_select.no_options" )} - + `} -
    - + + > ${this.hass!.localize( "ui.dialogs.helper_settings.input_select.add" @@ -135,7 +135,7 @@ class HaInputSelectForm extends LitElement { private _addOption() { const input = this._optionInput; - if (!input || !input.value) { + if (!input?.value) { return; } fireEvent(this, "value-changed", { @@ -167,7 +167,8 @@ class HaInputSelectForm extends LitElement { } ev.stopPropagation(); const configValue = (ev.target as any).configValue; - const value = ev.detail.value; + const value = ev.detail?.value || (ev.target as any).value; + if (this[`_${configValue}`] === value) { return; } @@ -175,7 +176,7 @@ class HaInputSelectForm extends LitElement { if (!value) { delete newValue[configValue]; } else { - newValue[configValue] = ev.detail.value; + newValue[configValue] = value; } fireEvent(this, "value-changed", { value: newValue, @@ -193,10 +194,18 @@ class HaInputSelectForm extends LitElement { border: 1px solid var(--divider-color); border-radius: 4px; margin-top: 4px; + --mdc-icon-button-size: 24px; } mwc-button { margin-left: 8px; } + ha-textfield { + display: block; + margin-bottom: 8px; + } + #option_input { + margin-top: 8px; + } `, ]; } diff --git a/src/panels/config/helpers/forms/ha-input_text-form.ts b/src/panels/config/helpers/forms/ha-input_text-form.ts index 7c912ca3df..f34bfa7e8c 100644 --- a/src/panels/config/helpers/forms/ha-input_text-form.ts +++ b/src/panels/config/helpers/forms/ha-input_text-form.ts @@ -1,8 +1,9 @@ -import "@polymer/paper-input/paper-input"; +import "../../../../components/ha-form/ha-form"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-icon-picker"; +import "../../../../components/ha-textfield"; import type { HaRadio } from "../../../../components/ha-radio"; import { InputText } from "../../../../data/input_text"; import { haStyle } from "../../../../resources/styles"; @@ -64,10 +65,10 @@ class HaInputTextForm extends LitElement { return html`
    - + > ${this.hass.userData?.showAdvanced ? html` - - + + >
    ${this.hass.localize( "ui.dialogs.helper_settings.input_text.mode" @@ -138,14 +139,14 @@ class HaInputTextForm extends LitElement { >
    - + > ` : ""}
    @@ -164,7 +165,7 @@ class HaInputTextForm extends LitElement { } ev.stopPropagation(); const configValue = (ev.target as any).configValue; - const value = ev.detail.value; + const value = ev.detail?.value || (ev.target as any).value; if (this[`_${configValue}`] === value) { return; } @@ -172,7 +173,7 @@ class HaInputTextForm extends LitElement { if (!value) { delete newValue[configValue]; } else { - newValue[configValue] = ev.detail.value; + newValue[configValue] = value; } fireEvent(this, "value-changed", { value: newValue, @@ -189,6 +190,10 @@ class HaInputTextForm extends LitElement { .row { padding: 16px 0; } + ha-textfield { + display: block; + margin: 8px 0; + } `, ]; } diff --git a/src/panels/config/helpers/forms/ha-timer-form.ts b/src/panels/config/helpers/forms/ha-timer-form.ts index 32e1f25e7a..5d7b13cfad 100644 --- a/src/panels/config/helpers/forms/ha-timer-form.ts +++ b/src/panels/config/helpers/forms/ha-timer-form.ts @@ -2,6 +2,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-icon-picker"; +import "../../../../components/ha-textfield"; import { DurationDict, Timer } from "../../../../data/timer"; import { haStyle } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; @@ -49,10 +50,10 @@ class HaTimerForm extends LitElement { return html`
    - + > - + >
    `; } @@ -88,7 +89,7 @@ class HaTimerForm extends LitElement { } ev.stopPropagation(); const configValue = (ev.target as any).configValue; - const value = ev.detail.value; + const value = ev.detail?.value || (ev.target as any).value; if (this[`_${configValue}`] === value) { return; } @@ -96,7 +97,7 @@ class HaTimerForm extends LitElement { if (!value) { delete newValue[configValue]; } else { - newValue[configValue] = ev.detail.value; + newValue[configValue] = value; } fireEvent(this, "value-changed", { value: newValue, @@ -110,6 +111,10 @@ class HaTimerForm extends LitElement { .form { color: var(--primary-text-color); } + ha-textfield { + display: block; + margin: 8px 0; + } `, ]; } From 728c391b5dd57c687ffa9f9bd435d74b1fb7d031 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 18 Feb 2022 16:06:19 -0800 Subject: [PATCH 114/174] Show why relayer is reconnecting (#11732) --- src/data/cloud.ts | 1 + src/panels/config/cloud/account/cloud-account.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/data/cloud.ts b/src/data/cloud.ts index 8f4f14b72b..e8add4f4ef 100644 --- a/src/data/cloud.ts +++ b/src/data/cloud.ts @@ -47,6 +47,7 @@ export interface CloudPreferences { export interface CloudStatusLoggedIn { logged_in: true; cloud: "disconnected" | "connecting" | "connected"; + cloud_last_disconnect_reason: { clean: boolean; reason: string } | null; email: string; google_registered: boolean; google_entities: EntityFilter; diff --git a/src/panels/config/cloud/account/cloud-account.ts b/src/panels/config/cloud/account/cloud-account.ts index 15e5d3c002..5d4d798be9 100644 --- a/src/panels/config/cloud/account/cloud-account.ts +++ b/src/panels/config/cloud/account/cloud-account.ts @@ -10,6 +10,7 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import "../../../../components/buttons/ha-call-api-button"; import "../../../../components/ha-card"; +import "../../../../components/ha-alert"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-icon-button"; import { debounce } from "../../../../common/util/debounce"; @@ -106,6 +107,17 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
    + ${this.cloudStatus.cloud === "connecting" && + this.cloudStatus.cloud_last_disconnect_reason + ? html` + + ` + : ""} + ${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA) ? html` - + > + + ` : ""}
    @@ -183,21 +184,8 @@ class MoreInfoMediaPlayer extends LitElement { supportsFeature(stateObj, SUPPORT_PLAY_MEDIA) ? html`
    - - + Text to speech has moved to the media browser.
    -
    ` : ""} `; @@ -207,14 +195,14 @@ class MoreInfoMediaPlayer extends LitElement { return css` ha-icon-button[action="turn_off"], ha-icon-button[action="turn_on"], - ha-slider, - #ttsInput { + ha-slider { flex-grow: 1; } .controls { display: flex; align-items: center; + --mdc-theme-primary: currentColor; } .basic-controls { @@ -223,8 +211,7 @@ class MoreInfoMediaPlayer extends LitElement { .volume, .source-input, - .sound-input, - .tts { + .sound-input { display: flex; align-items: center; justify-content: space-between; @@ -241,6 +228,15 @@ class MoreInfoMediaPlayer extends LitElement { margin-left: 10px; flex-grow: 1; } + + .tts { + margin-top: 16px; + font-style: italic; + } + + mwc-button > ha-svg-icon { + vertical-align: text-bottom; + } `; } @@ -295,32 +291,6 @@ class MoreInfoMediaPlayer extends LitElement { }); } - private _ttsCheckForEnter(e: KeyboardEvent) { - if (e.keyCode === 13) this._sendTTS(); - } - - private _sendTTS() { - const ttsInput = this._ttsInput; - if (!ttsInput) { - return; - } - - const services = this.hass.services.tts; - const serviceKeys = Object.keys(services).sort(); - - const service = serviceKeys.find((key) => key.indexOf("_say") !== -1); - - if (!service) { - return; - } - - this.hass.callService("tts", service, { - entity_id: this.stateObj!.entity_id, - message: ttsInput.value, - }); - ttsInput.value = ""; - } - private _showBrowseMedia(): void { showMediaBrowserDialog(this, { action: "play", From 3d6d07e5bdd1b4a9fb51d280b916e972b443dcfe Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 19 Feb 2022 21:35:58 -0800 Subject: [PATCH 117/174] Pass hass to ha-form to enable selectors (#11739) --- src/dialogs/config-flow/step-flow-form.ts | 1 + .../automation/action/types/ha-automation-action-device_id.ts | 1 + .../automation/condition/types/ha-automation-condition-device.ts | 1 + .../automation/trigger/types/ha-automation-trigger-device.ts | 1 + .../integrations/integration-panels/zha/zha-config-dashboard.ts | 1 + src/panels/profile/dialog-ha-mfa-module-setup-flow.ts | 1 + 6 files changed, 6 insertions(+) diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts index 3519540633..59e3656b1b 100644 --- a/src/dialogs/config-flow/step-flow-form.ts +++ b/src/dialogs/config-flow/step-flow-form.ts @@ -47,6 +47,7 @@ class StepFlowForm extends LitElement { ? html`${this._errorMsg}` : ""}
    Date: Sat, 19 Feb 2022 21:36:14 -0800 Subject: [PATCH 118/174] Bumped version to 20220220.0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index d3ac559300..211732d1e8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220214.0 +version = 20220220.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 3269b2878bd647174ecf6e82f36eeb291fadf410 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 19 Feb 2022 22:13:42 -0800 Subject: [PATCH 119/174] Add link to the selector docs --- gallery/src/pages/components/ha-selector.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gallery/src/pages/components/ha-selector.markdown b/gallery/src/pages/components/ha-selector.markdown index 6342871182..8f6af00f26 100644 --- a/gallery/src/pages/components/ha-selector.markdown +++ b/gallery/src/pages/components/ha-selector.markdown @@ -1,3 +1,5 @@ --- title: Selectors --- + +See the website for [list of available selectors](https://www.home-assistant.io/docs/blueprint/selectors/). From cf8e2a6d0299be4f6ee2dd3b6299c7e60ff665f6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 20 Feb 2022 08:52:38 -0800 Subject: [PATCH 120/174] TTS form no longer showed due to import oopsie (#11742) --- src/components/media-player/ha-media-player-browse.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index fa81e42716..f4f5e49d5e 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -49,7 +49,8 @@ import "../ha-svg-icon"; import "../ha-fab"; import { browseLocalMediaPlayer } from "../../data/media_source"; import { isTTSMediaSource } from "../../data/tts"; -import { TtsMediaPickedEvent } from "./ha-browse-media-tts"; +import type { TtsMediaPickedEvent } from "./ha-browse-media-tts"; +import "./ha-browse-media-tts"; declare global { interface HASSDomEvents { From dc2038916bb36214063a8c034d37a3533d28b08b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 20 Feb 2022 08:53:03 -0800 Subject: [PATCH 121/174] Improve logo rendering for playing media in browser (#11741) --- src/panels/media-browser/browser-media-player.ts | 2 +- src/panels/media-browser/ha-bar-media-player.ts | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/panels/media-browser/browser-media-player.ts b/src/panels/media-browser/browser-media-player.ts index 34e78d9069..ac2ed08137 100644 --- a/src/panels/media-browser/browser-media-player.ts +++ b/src/panels/media-browser/browser-media-player.ts @@ -15,7 +15,7 @@ export class BrowserMediaPlayer { constructor( public hass: HomeAssistant, - private item: MediaPlayerItem, + public item: MediaPlayerItem, private onChange: () => void ) {} diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index 1dee8318f1..ec999e53d5 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -19,6 +19,7 @@ import { TemplateResult, } from "lit"; import { customElement, property, query, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../common/dom/fire_event"; import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; @@ -164,7 +165,11 @@ class BarMediaPlayer extends LitElement { return html`
    ${mediaArt ? html`` : ""} @@ -498,6 +503,11 @@ class BarMediaPlayer extends LitElement { max-height: 100px; } + .app img { + max-height: 68px; + margin: 16px 0 16px 16px; + } + ha-button-menu mwc-button { line-height: 1; } From afe044d152b441a6c31f4c21e9ece554038d101d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 20 Feb 2022 08:53:25 -0800 Subject: [PATCH 122/174] Fix media upload on iOS (#11740) --- src/panels/media-browser/ha-panel-media-browser.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index ad47077d0f..9f3fa2ca1d 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -291,6 +291,7 @@ class PanelMediaBrowser extends LitElement { "change", async () => { const files = input.files!; + document.body.removeChild(input); const target = this._currentItem!.media_content_id!; for (let i = 0; i < files.length; i++) { @@ -315,6 +316,9 @@ class PanelMediaBrowser extends LitElement { }, { once: true } ); + // https://stackoverflow.com/questions/47664777/javascript-file-input-onchange-not-working-ios-safari-only + input.style.display = "none"; + document.body.append(input); input.click(); } From 9b4c6eea63c4819ffe6788de5e87e6c07ec159c1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 20 Feb 2022 20:07:10 -0800 Subject: [PATCH 123/174] Handle inifinity media duration (#11749) --- src/data/media-player.ts | 2 +- src/panels/media-browser/ha-bar-media-player.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 2d3902faf6..9f7856183c 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -339,7 +339,7 @@ export const computeMediaControls = ( }; export const formatMediaTime = (seconds: number | undefined): string => { - if (seconds === undefined) { + if (seconds === undefined || seconds === Infinity) { return ""; } diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index ec999e53d5..3f76564e1e 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -205,7 +205,9 @@ class BarMediaPlayer extends LitElement { ` )}
    - ${this.narrow + ${stateObj?.attributes.media_duration === Infinity + ? html`` + : this.narrow ? html`` : html`
    From 28cd9b6408bd1a21a58b620aa25798b99e2dda03 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Feb 2022 00:55:01 -0800 Subject: [PATCH 124/174] Show when media is being loaded (#11750) --- src/data/media-player.ts | 3 +- .../media-browser/browser-media-player.ts | 65 ++--- .../media-browser/ha-bar-media-player.ts | 268 +++++++++++------- .../media-browser/ha-panel-media-browser.ts | 27 +- 4 files changed, 216 insertions(+), 147 deletions(-) diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 9f7856183c..7a8a944957 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -33,7 +33,8 @@ import type { HomeAssistant } from "../types"; import { UNAVAILABLE_STATES } from "./entity"; interface MediaPlayerEntityAttributes extends HassEntityAttributeBase { - media_content_type?: any; + media_content_id?: string; + media_content_type?: string; media_artist?: string; media_playlist?: string; media_series_title?: string; diff --git a/src/panels/media-browser/browser-media-player.ts b/src/panels/media-browser/browser-media-player.ts index ac2ed08137..40b05fdae4 100644 --- a/src/panels/media-browser/browser-media-player.ts +++ b/src/panels/media-browser/browser-media-player.ts @@ -5,61 +5,60 @@ import { SUPPORT_PAUSE, SUPPORT_PLAY, } from "../../data/media-player"; -import { resolveMediaSource } from "../../data/media_source"; +import { ResolvedMediaSource } from "../../data/media_source"; import { HomeAssistant } from "../../types"; export class BrowserMediaPlayer { - private player?: HTMLAudioElement; + private player: HTMLAudioElement; - private stopped = false; + // We pretend we're playing while still buffering. + public buffering = true; + + private _removed = false; constructor( public hass: HomeAssistant, public item: MediaPlayerItem, + public resolved: ResolvedMediaSource, private onChange: () => void - ) {} - - public async initialize() { - const resolvedUrl: any = await resolveMediaSource( - this.hass, - this.item.media_content_id - ); - - const player = new Audio(resolvedUrl.url); + ) { + const player = new Audio(this.resolved.url); player.addEventListener("play", this._handleChange); - player.addEventListener("playing", this._handleChange); + player.addEventListener("playing", () => { + this.buffering = false; + this._handleChange(); + }); player.addEventListener("pause", this._handleChange); player.addEventListener("ended", this._handleChange); player.addEventListener("canplaythrough", () => { - if (this.stopped) { + if (this._removed) { return; } - this.player = player; - player.play(); + if (this.buffering) { + player.play(); + } this.onChange(); }); + this.player = player; } private _handleChange = () => { - if (!this.stopped) { + if (!this._removed) { this.onChange(); } }; public pause() { - if (this.player) { - this.player.pause(); - } + this.buffering = false; + this.player.pause(); } public play() { - if (this.player) { - this.player.play(); - } + this.player.play(); } - public stop() { - this.stopped = true; + public remove() { + this._removed = true; // @ts-ignore this.onChange = undefined; if (this.player) { @@ -68,9 +67,7 @@ export class BrowserMediaPlayer { } public get isPlaying(): boolean { - return ( - this.player !== undefined && !this.player.paused && !this.player.ended - ); + return this.buffering || (!this.player.paused && !this.player.ended); } static idleStateObj(): MediaPlayerEntity { @@ -88,19 +85,19 @@ export class BrowserMediaPlayer { toStateObj(): MediaPlayerEntity { // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement const base = BrowserMediaPlayer.idleStateObj(); - if (!this.player) { - return base; - } base.state = this.isPlaying ? "playing" : "paused"; base.attributes = { media_title: this.item.title, - media_duration: this.player.duration, - media_position: this.player.currentTime, - media_position_updated_at: base.last_updated, entity_picture: this.item.thumbnail, // eslint-disable-next-line no-bitwise supported_features: SUPPORT_PLAY | SUPPORT_PAUSE, }; + + if (this.player.duration) { + base.attributes.media_duration = this.player.duration; + base.attributes.media_position = this.player.currentTime; + base.attributes.media_position_updated_at = base.last_updated; + } return base; } } diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index 3f76564e1e..4bc0a6a767 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -20,6 +20,7 @@ import { } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; +import { until } from "lit/directives/until"; import { fireEvent } from "../../common/dom/fire_event"; import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain"; @@ -27,6 +28,7 @@ import { computeStateName } from "../../common/entity/compute_state_name"; import { domainIcon } from "../../common/entity/domain_icon"; import { supportsFeature } from "../../common/entity/supports-feature"; import "../../components/ha-button-menu"; +import "../../components/ha-circular-progress"; import "../../components/ha-icon-button"; import { UNAVAILABLE_STATES } from "../../data/entity"; import { @@ -43,6 +45,7 @@ import { SUPPORT_PLAY, SUPPORT_STOP, } from "../../data/media-player"; +import { ResolvedMediaSource } from "../../data/media_source"; import type { HomeAssistant } from "../../types"; import "../lovelace/components/hui-marquee"; import { BrowserMediaPlayer } from "./browser-media-player"; @@ -54,7 +57,7 @@ declare global { } @customElement("ha-bar-media-player") -class BarMediaPlayer extends LitElement { +export class BarMediaPlayer extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public entityId!: string; @@ -68,6 +71,8 @@ class BarMediaPlayer extends LitElement { @state() private _marqueeActive = false; + @state() private _newMediaExpected = false; + @state() private _browserPlayer?: BrowserMediaPlayer; private _progressInterval?: number; @@ -98,32 +103,54 @@ class BarMediaPlayer extends LitElement { clearInterval(this._progressInterval); this._progressInterval = undefined; } - - if (this._browserPlayer) { - this._browserPlayer.stop(); - this._browserPlayer = undefined; - } + this._tearDownBrowserPlayer(); } - public async playItem(item: MediaPlayerItem) { + public showResolvingNewMediaPicked() { + this._tearDownBrowserPlayer(); + this._newMediaExpected = true; + } + + public hideResolvingNewMediaPicked() { + this._newMediaExpected = false; + } + + public playItem(item: MediaPlayerItem, resolved: ResolvedMediaSource) { if (this.entityId !== BROWSER_PLAYER) { throw Error("Only browser supported"); } - if (this._browserPlayer) { - this._browserPlayer.stop(); - } - this._browserPlayer = new BrowserMediaPlayer(this.hass, item, () => - this.requestUpdate("_browserPlayer") + this._tearDownBrowserPlayer(); + this._browserPlayer = new BrowserMediaPlayer( + this.hass, + item, + resolved, + () => this.requestUpdate("_browserPlayer") ); - await this._browserPlayer.initialize(); + this._newMediaExpected = false; } protected render(): TemplateResult { + if (this._newMediaExpected) { + return html` +
    + ${until( + // Only show spinner after 500ms + new Promise((resolve) => setTimeout(resolve, 500)).then( + () => html`` + ) + )} +
    + `; + } + const isBrowser = this.entityId === BROWSER_PLAYER; const stateObj = this._stateObj; - const controls = !stateObj - ? undefined - : !this.narrow + + if (!stateObj) { + return this._renderChoosePlayer(stateObj); + } + + const controls = !this.narrow ? computeMediaControls(stateObj) : (stateObj.state === "playing" && (supportsFeature(stateObj, SUPPORT_PAUSE) || @@ -152,16 +179,14 @@ class BarMediaPlayer extends LitElement { }, ] : [{}]; - const mediaDescription = stateObj ? computeMediaDescription(stateObj) : ""; - const mediaDuration = formatMediaTime(stateObj?.attributes.media_duration); + const mediaDescription = computeMediaDescription(stateObj); + const mediaDuration = formatMediaTime(stateObj.attributes.media_duration); const mediaTitleClean = cleanupMediaTitle( - stateObj?.attributes.media_title || "" + stateObj.attributes.media_title || "" ); - - const mediaArt = stateObj - ? stateObj.attributes.entity_picture_local || - stateObj.attributes.entity_picture - : undefined; + const mediaArt = + stateObj.attributes.entity_picture_local || + stateObj.attributes.entity_picture; return html`
    -
    - ${controls === undefined - ? "" - : controls.map( - (control) => html` - - - ` - )} -
    - ${stateObj?.attributes.media_duration === Infinity - ? html`` - : this.narrow - ? html`` + ${this._browserPlayer?.buffering + ? html` ` : html` -
    -
    - -
    ${mediaDuration}
    +
    + ${controls === undefined + ? "" + : controls.map( + (control) => html` + + + ` + )}
    + ${stateObj.attributes.media_duration === Infinity + ? html`` + : this.narrow + ? html`` + : html` +
    +
    + +
    ${mediaDuration}
    +
    + `} `}
    -
    - - ${this.narrow - ? html` - - ` - : html` - + + ${ + this.narrow + ? html` + + ` + : html` + + + + + ` + } + + ${this.hass.localize("ui.components.media-browser.web-browser")} + + ${this._mediaPlayerEntities.map( + (source) => html` + - - - - `} - - ${this.hass.localize("ui.components.media-browser.web-browser")} - - ${this._mediaPlayerEntities.map( - (source) => html` - - ${computeStateName(source)} - - ` - )} - + ${computeStateName(source)} + + ` + )} + +
    + `; } public willUpdate(changedProps: PropertyValues) { super.willUpdate(changedProps); + if (changedProps.has("entityId")) { + this._tearDownBrowserPlayer(); + } + if (!changedProps.has("hass") || this.entityId === BROWSER_PLAYER) { + return; + } + // Reset new media expected if media player state changes + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; if ( - changedProps.has("entityId") && - this.entityId !== BROWSER_PLAYER && - this._browserPlayer + !oldHass || + oldHass.states[this.entityId] !== this.hass.states[this.entityId] ) { - this._browserPlayer?.stop(); - this._browserPlayer = undefined; + this._newMediaExpected = false; } } @@ -329,6 +382,13 @@ class BarMediaPlayer extends LitElement { return this.hass!.states[this.entityId] as MediaPlayerEntity | undefined; } + private _tearDownBrowserPlayer() { + if (this._browserPlayer) { + this._browserPlayer.remove(); + this._browserPlayer = undefined; + } + } + private _openMoreInfo() { if (this._browserPlayer) { return; diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 9f3fa2ca1d..9bf6df9990 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -37,6 +37,7 @@ import "../../layouts/ha-app-layout"; import { haStyle } from "../../resources/styles"; import type { HomeAssistant, Route } from "../../types"; import "./ha-bar-media-player"; +import type { BarMediaPlayer } from "./ha-bar-media-player"; import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog"; import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; import { @@ -79,6 +80,8 @@ class PanelMediaBrowser extends LitElement { @query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse; + @query("ha-bar-media-player") private _player!: BarMediaPlayer; + protected render(): TemplateResult { return html` @@ -235,15 +238,23 @@ class PanelMediaBrowser extends LitElement { ev: HASSDomEvent ): Promise { const item = ev.detail.item; + if (this._entityId !== BROWSER_PLAYER) { - this.hass!.callService("media_player", "play_media", { - entity_id: this._entityId, - media_content_id: item.media_content_id, - media_content_type: item.media_content_type, - }); + this._player.showResolvingNewMediaPicked(); + try { + await this.hass!.callService("media_player", "play_media", { + entity_id: this._entityId, + media_content_id: item.media_content_id, + media_content_type: item.media_content_type, + }); + } catch (err) { + this._player.hideResolvingNewMediaPicked(); + } return; } + // We won't cancel current media being played if we're going to + // open a camera. if (isCameraMediaSource(item.media_content_id)) { fireEvent(this, "hass-more-info", { entityId: getEntityIdFromCameraMediaSource(item.media_content_id), @@ -251,15 +262,15 @@ class PanelMediaBrowser extends LitElement { return; } + this._player.showResolvingNewMediaPicked(); + const resolvedUrl = await resolveMediaSource( this.hass, item.media_content_id ); if (resolvedUrl.mime_type.startsWith("audio/")) { - await this.shadowRoot!.querySelector("ha-bar-media-player")!.playItem( - item - ); + this._player.playItem(item, resolvedUrl); return; } From 6cac7eeff008c34d5c03a1697d1bf7122c5570b7 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 21 Feb 2022 09:53:03 -0600 Subject: [PATCH 125/174] Lovelace Entity Card Editor to Ha Form - Adds Theme Selector and HaFormColumn (#11731) Co-authored-by: Bram Kragten --- src/components/ha-form/ha-form-grid.ts | 73 ++++++ src/components/ha-form/ha-form.ts | 30 ++- src/components/ha-form/types.ts | 9 +- .../ha-selector/ha-selector-boolean.ts | 5 +- .../ha-selector/ha-selector-icon.ts | 2 + .../ha-selector/ha-selector-theme.ts | 34 +++ src/components/ha-selector/ha-selector.ts | 5 +- src/data/selector.ts | 12 +- .../components/hui-theme-select-editor.ts | 4 +- .../config-elements/hui-entity-card-editor.ts | 238 ++++++------------ 10 files changed, 230 insertions(+), 182 deletions(-) create mode 100644 src/components/ha-form/ha-form-grid.ts create mode 100644 src/components/ha-selector/ha-selector-theme.ts diff --git a/src/components/ha-form/ha-form-grid.ts b/src/components/ha-form/ha-form-grid.ts new file mode 100644 index 0000000000..2c3ed39285 --- /dev/null +++ b/src/components/ha-form/ha-form-grid.ts @@ -0,0 +1,73 @@ +import "./ha-form"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators"; +import type { + HaFormGridSchema, + HaFormDataContainer, + HaFormElement, + HaFormSchema, +} from "./types"; +import type { HomeAssistant } from "../../types"; + +@customElement("ha-form-grid") +export class HaFormGrid extends LitElement implements HaFormElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public data!: HaFormDataContainer; + + @property({ attribute: false }) public schema!: HaFormGridSchema; + + @property({ type: Boolean }) public disabled = false; + + @property() public computeLabel?: ( + schema: HaFormSchema, + data?: HaFormDataContainer + ) => string; + + @property() public computeHelper?: (schema: HaFormSchema) => string; + + protected firstUpdated() { + this.setAttribute("own-margin", ""); + } + + protected render(): TemplateResult { + return html` + ${this.schema.schema.map( + (item) => + html` + + ` + )} + `; + } + + static get styles(): CSSResultGroup { + return css` + :host { + display: grid !important; + grid-template-columns: repeat( + var(--form-grid-column-count, auto-fit), + minmax(var(--form-grid-min-width, 200px), 1fr) + ); + grid-gap: 8px; + } + :host > ha-form { + display: block; + margin-bottom: 24px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-form-grid": HaFormGrid; + } +} diff --git a/src/components/ha-form/ha-form.ts b/src/components/ha-form/ha-form.ts index 610d2d2de5..c824d8fb89 100644 --- a/src/components/ha-form/ha-form.ts +++ b/src/components/ha-form/ha-form.ts @@ -1,10 +1,18 @@ -import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; import { customElement, property } from "lit/decorators"; import { dynamicElement } from "../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../common/dom/fire_event"; import "../ha-alert"; import "./ha-form-boolean"; import "./ha-form-constant"; +import "./ha-form-grid"; import "./ha-form-float"; import "./ha-form-integer"; import "./ha-form-multi_select"; @@ -14,17 +22,18 @@ import "./ha-form-string"; import { HaFormElement, HaFormDataContainer, HaFormSchema } from "./types"; import { HomeAssistant } from "../../types"; -const getValue = (obj, item) => (obj ? obj[item.name] : null); +const getValue = (obj, item) => + obj ? (!item.name ? obj : obj[item.name]) : null; let selectorImported = false; @customElement("ha-form") export class HaForm extends LitElement implements HaFormElement { - @property() public hass!: HomeAssistant; + @property({ attribute: false }) public hass!: HomeAssistant; - @property() public data!: HaFormDataContainer; + @property({ attribute: false }) public data!: HaFormDataContainer; - @property() public schema!: HaFormSchema[]; + @property({ attribute: false }) public schema!: HaFormSchema[]; @property() public error?: Record; @@ -64,7 +73,7 @@ export class HaForm extends LitElement implements HaFormElement { } } - protected render() { + protected render(): TemplateResult { return html`
    ${this.error && this.error.base @@ -101,6 +110,9 @@ export class HaForm extends LitElement implements HaFormElement { data: getValue(this.data, item), label: this._computeLabel(item, this.data), disabled: this.disabled, + hass: this.hass, + computeLabel: this.computeLabel, + computeHelper: this.computeHelper, })} `; })} @@ -115,8 +127,12 @@ export class HaForm extends LitElement implements HaFormElement { ev.stopPropagation(); const schema = (ev.target as HaFormElement).schema as HaFormSchema; + const newValue = !schema.name + ? ev.detail.value + : { [schema.name]: ev.detail.value }; + fireEvent(this, "value-changed", { - value: { ...this.data, [schema.name]: ev.detail.value }, + value: { ...this.data, ...newValue }, }); }); return root; diff --git a/src/components/ha-form/types.ts b/src/components/ha-form/types.ts index f476e759ca..5d9572147a 100644 --- a/src/components/ha-form/types.ts +++ b/src/components/ha-form/types.ts @@ -11,7 +11,8 @@ export type HaFormSchema = | HaFormSelectSchema | HaFormMultiSelectSchema | HaFormTimeSchema - | HaFormSelector; + | HaFormSelector + | HaFormGridSchema; export interface HaFormBaseSchema { name: string; @@ -25,6 +26,12 @@ export interface HaFormBaseSchema { }; } +export interface HaFormGridSchema extends HaFormBaseSchema { + type: "grid"; + name: ""; + schema: HaFormSchema[]; +} + export interface HaFormSelector extends HaFormBaseSchema { type?: never; selector: Selector; diff --git a/src/components/ha-selector/ha-selector-boolean.ts b/src/components/ha-selector/ha-selector-boolean.ts index f140ba99e4..4ac5918b4c 100644 --- a/src/components/ha-selector/ha-selector-boolean.ts +++ b/src/components/ha-selector/ha-selector-boolean.ts @@ -35,9 +35,12 @@ export class HaBooleanSelector extends LitElement { static get styles(): CSSResultGroup { return css` + :host { + height: 56px; + display: flex; + } ha-formfield { width: 100%; - margin: 16px 0; --mdc-typography-body2-font-size: 1em; } `; diff --git a/src/components/ha-selector/ha-selector-icon.ts b/src/components/ha-selector/ha-selector-icon.ts index 046a612d5e..0e4a712588 100644 --- a/src/components/ha-selector/ha-selector-icon.ts +++ b/src/components/ha-selector/ha-selector-icon.ts @@ -22,6 +22,8 @@ export class HaIconSelector extends LitElement { `; diff --git a/src/components/ha-selector/ha-selector-theme.ts b/src/components/ha-selector/ha-selector-theme.ts new file mode 100644 index 0000000000..d25539908f --- /dev/null +++ b/src/components/ha-selector/ha-selector-theme.ts @@ -0,0 +1,34 @@ +import "../../panels/lovelace/components/hui-theme-select-editor"; +import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators"; +import type { HomeAssistant } from "../../types"; +import type { ThemeSelector } from "../../data/selector"; + +@customElement("ha-selector-theme") +export class HaThemeSelector extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public selector!: ThemeSelector; + + @property() public value?: string; + + @property() public label?: string; + + @property({ type: Boolean, reflect: true }) public disabled = false; + + protected render() { + return html` + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-selector-theme": HaThemeSelector; + } +} diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts index 0181c9c863..5f6a31aebc 100644 --- a/src/components/ha-selector/ha-selector.ts +++ b/src/components/ha-selector/ha-selector.ts @@ -1,8 +1,8 @@ import { html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { dynamicElement } from "../../common/dom/dynamic-element-directive"; -import { Selector } from "../../data/selector"; -import { HomeAssistant } from "../../types"; +import type { Selector } from "../../data/selector"; +import type { HomeAssistant } from "../../types"; import "./ha-selector-action"; import "./ha-selector-addon"; import "./ha-selector-area"; @@ -19,6 +19,7 @@ import "./ha-selector-text"; import "./ha-selector-time"; import "./ha-selector-icon"; import "./ha-selector-media"; +import "./ha-selector-theme"; @customElement("ha-selector") export class HaSelector extends LitElement { diff --git a/src/data/selector.ts b/src/data/selector.ts index e7be0ac147..b51c077b01 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -14,7 +14,8 @@ export type Selector = | ObjectSelector | SelectSelector | IconSelector - | MediaSelector; + | MediaSelector + | ThemeSelector; export interface EntitySelector { entity: { @@ -147,8 +148,15 @@ export interface SelectSelector { } export interface IconSelector { + icon: { + placeholder?: string; + fallbackPath?: string; + }; +} + +export interface ThemeSelector { // eslint-disable-next-line @typescript-eslint/ban-types - icon: {}; + theme: {}; } export interface MediaSelector { diff --git a/src/panels/lovelace/components/hui-theme-select-editor.ts b/src/panels/lovelace/components/hui-theme-select-editor.ts index 47e05ae1bf..30bc0bdef0 100644 --- a/src/panels/lovelace/components/hui-theme-select-editor.ts +++ b/src/panels/lovelace/components/hui-theme-select-editor.ts @@ -39,7 +39,7 @@ export class HuiThemeSelectEditor extends LitElement { .sort() .map( (theme) => - html` ${theme} ` + html`${theme}` )} `; @@ -57,7 +57,7 @@ export class HuiThemeSelectEditor extends LitElement { if (!this.hass || ev.target.value === "") { return; } - this.value = ev.target.value === "remove" ? "" : ev.target.selected; + this.value = ev.target.value === "remove" ? "" : ev.target.value; fireEvent(this, "value-changed", { value: this.value }); } } diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts index df9cb0e627..7dcf65a1bc 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts @@ -1,22 +1,18 @@ -import "@polymer/paper-input/paper-input"; -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import "../../../../components/ha-form/ha-form"; +import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { assert, assign, boolean, object, optional, string } from "superstruct"; +import type { HassEntity } from "home-assistant-js-websocket/dist/types"; import { fireEvent } from "../../../../common/dom/fire_event"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; -import "../../../../components/entity/ha-entity-attribute-picker"; -import "../../../../components/ha-icon-picker"; -import { HomeAssistant } from "../../../../types"; -import { EntityCardConfig } from "../../cards/types"; -import "../../components/hui-action-editor"; -import "../../components/hui-entity-editor"; -import "../../components/hui-theme-select-editor"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../types"; +import type { EntityCardConfig } from "../../cards/types"; import { headerFooterConfigStructs } from "../../header-footer/structs"; -import { LovelaceCardEditor } from "../../types"; +import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { EditorTarget, EntitiesEditorEvent } from "../types"; -import { configElementStyle } from "./config-elements-style"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -46,174 +42,82 @@ export class HuiEntityCardEditor this._config = config; } - get _entity(): string { - return this._config!.entity || ""; - } + private _schema = memoizeOne( + (entity: string, icon: string, entityState: HassEntity): HaFormSchema[] => [ + { name: "entity", required: true, selector: { entity: {} } }, + { + type: "grid", + name: "", + schema: [ + { name: "name", selector: { text: {} } }, + { + name: "icon", + selector: { + icon: { + placeholder: icon || entityState?.attributes.icon, + fallbackPath: + !icon && !entityState?.attributes.icon && entityState + ? domainIcon(computeDomain(entity), entityState) + : undefined, + }, + }, + }, - get _name(): string { - return this._config!.name || ""; - } - - get _icon(): string { - return this._config!.icon || ""; - } - - get _attribute(): string { - return this._config!.attribute || ""; - } - - get _unit(): string { - return this._config!.unit || ""; - } - - get _state_color(): boolean { - return this._config!.state_color ?? false; - } - - get _theme(): string { - return this._config!.theme || ""; - } + { + name: "attribute", + selector: { attribute: { entity_id: entity } }, + }, + { name: "unit", selector: { text: {} } }, + { name: "theme", selector: { theme: {} } }, + { name: "state_color", selector: { boolean: {} } }, + ], + }, + ] + ); protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; } - const entityState = this.hass.states[this._entity]; + + const entityState = this.hass.states[this._config.entity]; + + const schema = this._schema( + this._config.entity, + this._config.icon, + entityState + ); return html` -
    - -
    - - -
    -
    - - -
    -
    - - - - - - -
    -
    + `; } - private _valueChanged(ev: EntitiesEditorEvent): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.currentTarget! as EditorTarget; - - if ( - this[`_${target.configValue}`] === target.value || - this[`_${target.configValue}`] === target.config - ) { - return; - } - if (target.configValue) { - if (target.value === "") { - this._config = { ...this._config }; - delete this._config[target.configValue!]; - } else { - let newValue: string | undefined; - if ( - target.configValue === "icon_height" && - !isNaN(Number(target.value)) - ) { - newValue = `${String(target.value)}px`; - } - this._config = { - ...this._config, - [target.configValue!]: - target.checked !== undefined - ? target.checked - : newValue !== undefined - ? newValue - : target.value - ? target.value - : target.config, - }; - } - } - fireEvent(this, "config-changed", { config: this._config }); + private _valueChanged(ev: CustomEvent): void { + const config = ev.detail.value; + Object.keys(config).forEach((k) => config[k] === "" && delete config[k]); + fireEvent(this, "config-changed", { config }); } - static get styles(): CSSResultGroup { - return configElementStyle; - } + private _computeLabelCallback = (schema: HaFormSchema) => { + if (schema.name === "entity") { + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.entity" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.required" + )})`; + } + + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + }; } declare global { From 2281f5bafacb4dee01d0d0f358036525430a0dd6 Mon Sep 17 00:00:00 2001 From: Steve Repsher Date: Mon, 21 Feb 2022 11:02:55 -0500 Subject: [PATCH 126/174] Set initial focus for supervisor dialogs (#11710) --- hassio/src/components/supervisor-backup-content.ts | 8 +++++++- hassio/src/dialogs/backup/dialog-hassio-backup-upload.ts | 1 + hassio/src/dialogs/backup/dialog-hassio-backup.ts | 1 + hassio/src/dialogs/backup/dialog-hassio-create-backup.ts | 1 + hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts | 7 ++++++- hassio/src/dialogs/hardware/dialog-hassio-hardware.ts | 2 +- hassio/src/dialogs/markdown/dialog-hassio-markdown.ts | 5 ++++- hassio/src/dialogs/network/dialog-hassio-network.ts | 2 ++ hassio/src/dialogs/registries/dialog-hassio-registries.ts | 3 ++- .../dialogs/repositories/dialog-hassio-repositories.ts | 1 + 10 files changed, 26 insertions(+), 5 deletions(-) diff --git a/hassio/src/components/supervisor-backup-content.ts b/hassio/src/components/supervisor-backup-content.ts index d1422521b8..8a24c48d3c 100644 --- a/hassio/src/components/supervisor-backup-content.ts +++ b/hassio/src/components/supervisor-backup-content.ts @@ -1,7 +1,7 @@ import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js"; import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property } from "lit/decorators"; +import { customElement, property, query } from "lit/decorators"; import { atLeastVersion } from "../../../src/common/config/version"; import { formatDate } from "../../../src/common/datetime/format_date"; import { formatDateTime } from "../../../src/common/datetime/format_date_time"; @@ -92,6 +92,8 @@ export class SupervisorBackupContent extends LitElement { @property() public confirmBackupPassword = ""; + @query("paper-input, ha-radio, ha-checkbox", true) private _focusTarget; + public willUpdate(changedProps) { super.willUpdate(changedProps); if (!this.hasUpdated) { @@ -109,6 +111,10 @@ export class SupervisorBackupContent extends LitElement { } } + public override focus() { + this._focusTarget?.focus(); + } + private _localize = (string: string) => this.supervisor?.localize(`backup.${string}`) || this.localize!(`ui.panel.page-onboarding.restore.${string}`); diff --git a/hassio/src/dialogs/backup/dialog-hassio-backup-upload.ts b/hassio/src/dialogs/backup/dialog-hassio-backup-upload.ts index f6a80cefe6..05901f2a3a 100644 --- a/hassio/src/dialogs/backup/dialog-hassio-backup-upload.ts +++ b/hassio/src/dialogs/backup/dialog-hassio-backup-upload.ts @@ -64,6 +64,7 @@ export class DialogHassioBackupUpload .path=${mdiClose} slot="actionItems" dialogAction="cancel" + dialogInitialFocus >
    diff --git a/hassio/src/dialogs/backup/dialog-hassio-backup.ts b/hassio/src/dialogs/backup/dialog-hassio-backup.ts index 5a5fc61810..a993113ee5 100644 --- a/hassio/src/dialogs/backup/dialog-hassio-backup.ts +++ b/hassio/src/dialogs/backup/dialog-hassio-backup.ts @@ -92,6 +92,7 @@ class HassioBackupDialog .backup=${this._backup} .onboarding=${this._dialogParams.onboarding || false} .localize=${this._dialogParams.localize} + dialogInitialFocus > `} ${this._error diff --git a/hassio/src/dialogs/backup/dialog-hassio-create-backup.ts b/hassio/src/dialogs/backup/dialog-hassio-create-backup.ts index 0c73b81e88..f5fe92e28d 100644 --- a/hassio/src/dialogs/backup/dialog-hassio-create-backup.ts +++ b/hassio/src/dialogs/backup/dialog-hassio-create-backup.ts @@ -61,6 +61,7 @@ class HassioCreateBackupDialog extends LitElement { : html` `} ${this._error diff --git a/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts b/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts index 4bad79ca7f..564a1fe689 100644 --- a/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts +++ b/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts @@ -94,6 +94,7 @@ class HassioDatadiskDialog extends LitElement { "dialog.datadisk_move.select_device" )} @selected=${this._select_device} + dialogInitialFocus > ${this.devices.map( (device) => @@ -111,7 +112,11 @@ class HassioDatadiskDialog extends LitElement { "dialog.datadisk_move.no_devices" )} - + ${this.dialogParams.supervisor.localize( "dialog.datadisk_move.cancel" )} diff --git a/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts b/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts index 5575658d5e..4022305946 100755 --- a/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts +++ b/hassio/src/dialogs/hardware/dialog-hassio-hardware.ts @@ -80,7 +80,7 @@ class HassioHardwareDialog extends LitElement { > - + `; } diff --git a/hassio/src/dialogs/network/dialog-hassio-network.ts b/hassio/src/dialogs/network/dialog-hassio-network.ts index 3e3c8720b4..898806009c 100644 --- a/hassio/src/dialogs/network/dialog-hassio-network.ts +++ b/hassio/src/dialogs/network/dialog-hassio-network.ts @@ -119,6 +119,7 @@ export class DialogHassioNetwork html` ` )} @@ -315,6 +316,7 @@ export class DialogHassioNetwork value="auto" name="${version}method" .checked=${this._interface![version]?.method === "auto"} + dialogInitialFocus > diff --git a/hassio/src/dialogs/registries/dialog-hassio-registries.ts b/hassio/src/dialogs/registries/dialog-hassio-registries.ts index 5f7d1967ae..7ca0b3de1c 100644 --- a/hassio/src/dialogs/registries/dialog-hassio-registries.ts +++ b/hassio/src/dialogs/registries/dialog-hassio-registries.ts @@ -80,6 +80,7 @@ class HassioRegistriesDialog extends LitElement { .schema=${SCHEMA} @value-changed=${this._valueChanged} .computeLabel=${this._computeLabel} + dialogInitialFocus >
    `}
    - + ${this.supervisor.localize( "dialog.registries.add_new_registry" )} diff --git a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts index b9707f5986..b208093465 100644 --- a/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts +++ b/hassio/src/dialogs/repositories/dialog-hassio-repositories.ts @@ -139,6 +139,7 @@ class HassioRepositoriesDialog extends LitElement { "dialog.repositories.add" )} @keydown=${this._handleKeyAdd} + dialogInitialFocus > ${this._processing From decc0d3e0d1814971158415d95787ae154e51440 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 21 Feb 2022 10:37:11 -0600 Subject: [PATCH 127/174] Convert Automation Actions to mwc/ha-form + other automation items (#11753) --- .../action/ha-automation-action-row.ts | 4 +- .../types/ha-automation-action-device_id.ts | 5 +- .../types/ha-automation-action-event.ts | 21 +++-- .../types/ha-automation-action-play_media.ts | 1 - .../types/ha-automation-action-repeat.ts | 28 +++--- .../ha-automation-action-wait_for_trigger.ts | 22 +++-- .../ha-automation-action-wait_template.ts | 86 +++++++++---------- .../ha-automation-condition-editor.ts | 2 +- .../types/ha-automation-condition-state.ts | 1 + .../types/ha-automation-condition-sun.ts | 1 + .../types/ha-automation-condition-time.ts | 1 + .../config/automation/ha-automation-editor.ts | 41 +++++---- .../config/automation/ha-automation-picker.ts | 1 - .../automation/manual-automation-editor.ts | 47 +++++----- .../trigger/ha-automation-trigger-row.ts | 17 ++-- .../types/ha-automation-trigger-event.ts | 18 ++-- 16 files changed, 165 insertions(+), 131 deletions(-) diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index dbc9150ecf..2d68bf0461 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -73,7 +73,7 @@ export const handleChangeEvent = (element: ActionElement, ev: CustomEvent) => { if (!name) { return; } - const newVal = ev.detail.value; + const newVal = ev.detail?.value || (ev.target as any).value; if ((element.action[name] || "") === newVal) { return; @@ -376,7 +376,7 @@ export default class HaAutomationActionRow extends LitElement { margin: 4px 0; } mwc-select { - margin-bottom: 16px; + margin-bottom: 24px; } `, ]; diff --git a/src/panels/config/automation/action/types/ha-automation-action-device_id.ts b/src/panels/config/automation/action/types/ha-automation-action-device_id.ts index c03b480d1b..c837304ea9 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-device_id.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-device_id.ts @@ -143,9 +143,12 @@ export class HaDeviceAction extends LitElement { } static styles = css` + ha-device-picker { + display: block; + margin-bottom: 24px; + } ha-device-action-picker { display: block; - margin-top: 8px; } `; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-event.ts b/src/panels/config/automation/action/types/ha-automation-action-event.ts index 26ebb424c9..ee133fa7da 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-event.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-event.ts @@ -1,9 +1,9 @@ -import "@polymer/paper-input/paper-input"; -import { html, LitElement, PropertyValues } from "lit"; +import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/entity/ha-entity-picker"; import "../../../../../components/ha-service-picker"; +import "../../../../../components/ha-textfield"; import "../../../../../components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor"; import type { EventAction } from "../../../../../data/script"; @@ -40,14 +40,13 @@ export class HaEventAction extends LitElement implements ActionElement { const { event, event_data } = this.action; return html` - + @change=${this._eventChanged} + > ${type === "count" - ? html`` + ? html` + + ` : ""} ${type === "while" ? html`

    @@ -142,7 +144,7 @@ export class HaRepeatAction extends LitElement implements ActionElement { } private _countChanged(ev: CustomEvent): void { - const newVal = ev.detail.value; + const newVal = (ev.target as any).value; if ((this.action.repeat as CountRepeat).count === newVal) { return; } diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts index 997ab3e752..2b76b0918c 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts @@ -1,6 +1,5 @@ -import "@polymer/paper-input/paper-input"; -import "@polymer/paper-input/paper-textarea"; -import { html, LitElement } from "lit"; +import "../../../../../components/ha-textfield"; +import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-formfield"; @@ -26,14 +25,14 @@ export class HaWaitForTriggerAction const { wait_for_trigger, continue_on_timeout, timeout } = this.action; return html` - + .value=${timeout || ""} + @change=${this._valueChanged} + >
    - -
    - - - + `; } - private _continueChanged(ev) { - fireEvent(this, "value-changed", { - value: { ...this.action, continue_on_timeout: ev.target.checked }, - }); - } - - private _valueChanged(ev: CustomEvent): void { - handleChangeEvent(this, ev); - } + private _computeLabelCallback = (schema: HaFormSchema): string => + this.hass.localize( + `ui.panel.config.automation.editor.actions.type.wait_template.${ + schema.name === "continue_on_timeout" ? "continue_timeout" : schema.name + }` + ); } declare global { diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts index e6c9378c8f..c03933e600 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts @@ -147,7 +147,7 @@ export default class HaAutomationConditionEditor extends LitElement { haStyle, css` mwc-select { - margin-bottom: 16px; + margin-bottom: 24px; } `, ]; diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index 9c88cdfb50..e6d166adaa 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -9,6 +9,7 @@ import type { StateCondition } from "../../../../../data/automation"; import type { HomeAssistant } from "../../../../../types"; import { forDictStruct } from "../../structs"; import type { ConditionElement } from "../ha-automation-condition-row"; +import "../../../../../components/ha-form/ha-form"; const stateConditionStruct = object({ condition: literal("state"), diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts b/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts index e3d466ca6e..e8966b6969 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-sun.ts @@ -7,6 +7,7 @@ import type { HomeAssistant } from "../../../../../types"; import type { ConditionElement } from "../ha-automation-condition-row"; import type { LocalizeFunc } from "../../../../../common/translations/localize"; import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import "../../../../../components/ha-form/ha-form"; @customElement("ha-automation-condition-sun") export class HaSunCondition extends LitElement implements ConditionElement { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts index f09a132ff2..fca4616a7e 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts @@ -7,6 +7,7 @@ import type { HomeAssistant } from "../../../../../types"; import type { ConditionElement } from "../ha-automation-condition-row"; import type { LocalizeFunc } from "../../../../../common/translations/localize"; import type { HaFormSchema } from "../../../../../components/ha-form/types"; +import "../../../../../components/ha-form/ha-form"; const DAYS = { mon: 1, diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 3a0d34706b..9a6293175c 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -9,7 +9,6 @@ import { } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import "@polymer/paper-input/paper-textarea"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, @@ -201,7 +200,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { ${this._config ? html` ${this.narrow - ? html` ${this._config?.alias} ` + ? html`${this._config?.alias}` : ""}
    ${this._errors}
    ` + ? html`
    ${this._errors}
    ` : ""} ${this._mode === "gui" ? html` ${"use_blueprint" in this._config - ? html`` - : html``} + ? html` + + ` + : html` + + `} ` : this._mode === "yaml" ? html` diff --git a/src/panels/config/automation/ha-automation-picker.ts b/src/panels/config/automation/ha-automation-picker.ts index 9f25fe3a00..7c637b4dc7 100644 --- a/src/panels/config/automation/ha-automation-picker.ts +++ b/src/panels/config/automation/ha-automation-picker.ts @@ -7,7 +7,6 @@ import { mdiPlayCircleOutline, mdiPlus, } from "@mdi/js"; -import "@polymer/paper-tooltip/paper-tooltip"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 755f7b639a..3fd2d8c915 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -1,11 +1,12 @@ import "@material/mwc-button/mwc-button"; -import "@polymer/paper-input/paper-textarea"; import { HassEntity } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/ha-entity-toggle"; import "../../../components/ha-card"; +import "../../../components/ha-textarea"; +import "../../../components/ha-textfield"; import { Condition, ManualAutomationConfig, @@ -14,7 +15,7 @@ import { } from "../../../data/automation"; import { Action, MODES, MODES_MAX } from "../../../data/script"; import { haStyle } from "../../../resources/styles"; -import { HomeAssistant } from "../../../types"; +import type { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import "../ha-config-section"; import "./action/ha-automation-action"; @@ -36,7 +37,7 @@ export class HaManualAutomationEditor extends LitElement { protected render() { return html` ${!this.narrow - ? html` ${this.config.alias} ` + ? html`${this.config.alias}` : ""} ${this.hass.localize( @@ -45,16 +46,16 @@ export class HaManualAutomationEditor extends LitElement {
    - - - + + .value=${this.config.description || ""} + @change=${this._valueChanged} + >

    ${this.hass.localize( "ui.panel.config.automation.editor.modes.description", @@ -98,16 +99,18 @@ export class HaManualAutomationEditor extends LitElement { )} ${this.config.mode && MODES_MAX.includes(this.config.mode) - ? html` - ` + ? html` + + + ` : html``}

    ${this.stateObj @@ -243,7 +246,7 @@ export class HaManualAutomationEditor extends LitElement { if (!name) { return; } - let newVal = ev.detail.value; + let newVal = target.value; if (target.type === "number") { newVal = Number(newVal); } diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 9103ab5540..ecf1f1a4b7 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -14,6 +14,7 @@ import { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; import "../../../../components/ha-alert"; +import "../../../../components/ha-textfield"; import "../../../../components/ha-icon-button"; import type { Trigger } from "../../../../data/automation"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; @@ -200,14 +201,14 @@ export default class HaAutomationTriggerRow extends LitElement { ${showId ? html` - - + ` : ""}
    @@ -287,7 +288,7 @@ export default class HaAutomationTriggerRow extends LitElement { } private _idChanged(ev: CustomEvent) { - const newId = ev.detail.value; + const newId = (ev.target as any).value; if (newId === (this.trigger.id ?? "")) { return; } @@ -333,7 +334,11 @@ export default class HaAutomationTriggerRow extends LitElement { --mdc-theme-text-primary-on-background: var(--disabled-text-color); } mwc-select { - margin-bottom: 16px; + margin-bottom: 24px; + } + ha-textfield { + display: block; + margin-bottom: 24px; } `, ]; diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts index 6804fb8787..5c191e76db 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-event.ts @@ -1,7 +1,7 @@ -import "@polymer/paper-input/paper-input"; -import { html, LitElement } from "lit"; +import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-textfield"; import "../../../../../components/ha-yaml-editor"; import "../../../../../components/user/ha-users-picker"; import { EventTrigger } from "../../../../../data/automation"; @@ -24,14 +24,14 @@ export class HaEventTrigger extends LitElement implements TriggerElement { protected render() { const { event_type, event_data, context } = this.trigger; return html` - + @change=${this._valueChanged} + > Date: Mon, 21 Feb 2022 17:37:29 +0100 Subject: [PATCH 128/174] Selector: remove text value when not required and empty (#11754) --- src/components/ha-selector/ha-selector-text.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts index fa1d48ff63..6827de4ac6 100644 --- a/src/components/ha-selector/ha-selector-text.ts +++ b/src/components/ha-selector/ha-selector-text.ts @@ -69,10 +69,13 @@ export class HaTextSelector extends LitElement { } private _handleChange(ev) { - const value = ev.target.value; + let value = ev.target.value; if (this.value === value) { return; } + if (value === "" && !this.required) { + value = undefined; + } fireEvent(this, "value-changed", { value }); } From 94b4b818aab5996470122cab6daaae4f60f5ffac Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 21 Feb 2022 17:48:31 +0100 Subject: [PATCH 129/174] Convert date-range-picker to mwc (#11755) --- src/components/ha-date-range-picker.ts | 18 +++++++++--------- src/panels/history/ha-panel-history.ts | 8 +++++++- src/panels/logbook/ha-panel-logbook.ts | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/components/ha-date-range-picker.ts b/src/components/ha-date-range-picker.ts index 2940c3a883..3baef7c22e 100644 --- a/src/components/ha-date-range-picker.ts +++ b/src/components/ha-date-range-picker.ts @@ -3,7 +3,6 @@ import "@material/mwc-list/mwc-list"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import { mdiCalendar } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, @@ -19,6 +18,7 @@ import { computeRTLDirection } from "../common/util/compute_rtl"; import { HomeAssistant } from "../types"; import "./date-range-picker"; import "./ha-svg-icon"; +import "./ha-textfield"; export interface DateRangePickerRanges { [key: string]: [Date, Date]; @@ -61,7 +61,7 @@ export class HaDateRangePicker extends LitElement { >
    - - + + >
    ${this.ranges ? html`
    -
    +
    Date: Mon, 21 Feb 2022 09:12:15 -0800 Subject: [PATCH 130/174] Radio Browser is now added during onboarding (#11756) --- src/onboarding/onboarding-integrations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onboarding/onboarding-integrations.ts b/src/onboarding/onboarding-integrations.ts index 01c53f3952..6ac3504e5e 100644 --- a/src/onboarding/onboarding-integrations.ts +++ b/src/onboarding/onboarding-integrations.ts @@ -30,7 +30,7 @@ import { HomeAssistant } from "../types"; import "./action-badge"; import "./integration-badge"; -const HIDDEN_DOMAINS = new Set(["met", "rpi_power", "hassio"]); +const HIDDEN_DOMAINS = new Set(["hassio", "met", "radio_browser", "rpi_power"]); @customElement("onboarding-integrations") class OnboardingIntegrations extends LitElement { From a14d75deecffb55096a2c843d8e882ef404271d1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Feb 2022 09:21:29 -0800 Subject: [PATCH 131/174] Add support for the media browser My link (#11757) --- src/panels/my/ha-panel-my.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index d7be560e7b..c02fb74523 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -155,6 +155,10 @@ const REDIRECTS: Redirects = { component: "history", redirect: "/history", }, + media_browser: { + component: "media_source", + redirect: "/media-browser", + }, }; export type ParamType = "url" | "string"; @@ -178,7 +182,7 @@ class HaPanelMy extends LitElement { connectedCallback() { super.connectedCallback(); - const path = this.route.path.substr(1); + const path = this.route.path.substring(1); if (path.startsWith("supervisor")) { if (!isComponentLoaded(this.hass, "hassio")) { From eaf97ee7f5011e036f224f81fc171686257ebe31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Mon, 21 Feb 2022 18:33:02 +0100 Subject: [PATCH 132/174] Show Home Assistant when creating partial backup (#11758) --- .../components/supervisor-backup-content.ts | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/hassio/src/components/supervisor-backup-content.ts b/hassio/src/components/supervisor-backup-content.ts index 8a24c48d3c..5d0b71741f 100644 --- a/hassio/src/components/supervisor-backup-content.ts +++ b/hassio/src/components/supervisor-backup-content.ts @@ -175,24 +175,23 @@ export class SupervisorBackupContent extends LitElement { : ""} ${this.backupType === "partial" ? html`
    - ${this.backup && this.backup.homeassistant - ? html` - - `} - > - - - - ` - : ""} + + `} + > + + + + ${foldersSection?.templates.length ? html` Date: Mon, 21 Feb 2022 19:52:09 +0100 Subject: [PATCH 133/174] Fix zwave migration (#11751) --- src/data/device_registry.ts | 10 +++-- .../zwave/zwave-migration.ts | 45 ++++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/data/device_registry.ts b/src/data/device_registry.ts index 64e29d3a20..6fb72404aa 100644 --- a/src/data/device_registry.ts +++ b/src/data/device_registry.ts @@ -1,4 +1,5 @@ import { Connection, createCollection } from "home-assistant-js-websocket"; +import { Store } from "home-assistant-js-websocket/dist/store"; import { computeStateName } from "../common/entity/compute_state_name"; import { caseInsensitiveStringCompare } from "../common/string/compare"; import { debounce } from "../common/util/debounce"; @@ -88,12 +89,15 @@ export const removeConfigEntryFromDevice = ( config_entry_id: configEntryId, }); -export const fetchDeviceRegistry = (conn) => - conn.sendMessagePromise({ +export const fetchDeviceRegistry = (conn: Connection) => + conn.sendMessagePromise({ type: "config/device_registry/list", }); -const subscribeDeviceRegistryUpdates = (conn, store) => +const subscribeDeviceRegistryUpdates = ( + conn: Connection, + store: Store +) => conn.subscribeEvents( debounce( () => diff --git a/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts b/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts index ee19b1aae6..2272bd884e 100644 --- a/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts +++ b/src/panels/config/integrations/integration-panels/zwave/zwave-migration.ts @@ -5,9 +5,11 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../../../../common/config/is_component_loaded"; +import { computeStateDomain } from "../../../../../common/entity/compute_state_domain"; import { computeStateName } from "../../../../../common/entity/compute_state_name"; import "../../../../../components/buttons/ha-call-api-button"; import "../../../../../components/buttons/ha-call-service-button"; +import "../../../../../components/ha-alert"; import "../../../../../components/ha-card"; import "../../../../../components/ha-circular-progress"; import "../../../../../components/ha-icon"; @@ -18,30 +20,28 @@ import { fetchDeviceRegistry, subscribeDeviceRegistry, } from "../../../../../data/device_registry"; -import { - migrateZwave, - ZWaveJsMigrationData, - fetchZwaveNetworkStatus as fetchZwaveJsNetworkStatus, - fetchZwaveNodeStatus, - getZwaveJsIdentifiersFromDevice, - subscribeZwaveNodeReady, -} from "../../../../../data/zwave_js"; import { fetchMigrationConfig, + fetchNetworkStatus, startZwaveJsConfigFlow, ZWaveMigrationConfig, ZWaveNetworkStatus, ZWAVE_NETWORK_STATE_STOPPED, - fetchNetworkStatus, } from "../../../../../data/zwave"; +import { + fetchZwaveNetworkStatus as fetchZwaveJsNetworkStatus, + fetchZwaveNodeStatus, + getZwaveJsIdentifiersFromDevice, + migrateZwave, + subscribeZwaveNodeReady, + ZWaveJsMigrationData, +} from "../../../../../data/zwave_js"; import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow"; import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box"; import "../../../../../layouts/hass-subpage"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant, Route } from "../../../../../types"; import "../../../ha-config-section"; -import { computeStateDomain } from "../../../../../common/entity/compute_state_domain"; -import "../../../../../components/ha-alert"; @customElement("zwave-migration") export class ZwaveMigration extends LitElement { @@ -155,7 +155,7 @@ export class ZwaveMigration extends LitElement { .filter( (entityState) => computeStateDomain(entityState) === "zwave" && - entityState.state !== "ready" + !["ready", "sleeping"].includes(entityState.state) ) .map( (entityState) => @@ -430,6 +430,10 @@ export class ZwaveMigration extends LitElement { const nodesNotReady = (await Promise.all(nodeStatePromisses)).filter( (node) => !node.ready ); + + // eslint-disable-next-line no-console + console.log("waiting for nodes to be ready", nodesNotReady); + this._getMigrationData(); if (nodesNotReady.length === 0) { this._waitingOnDevices = []; @@ -445,10 +449,19 @@ export class ZwaveMigration extends LitElement { } ) ); - const deviceReg = await fetchDeviceRegistry(this.hass); - this._waitingOnDevices = deviceReg - .map((device) => getZwaveJsIdentifiersFromDevice(device)) - .filter(Boolean); + const deviceReg: DeviceRegistryEntry[] = await fetchDeviceRegistry( + this.hass.connection + ); + this._waitingOnDevices = deviceReg.filter((device) => { + const identifiers = getZwaveJsIdentifiersFromDevice(device); + if ( + !identifiers || + Number(identifiers.home_id) !== networkStatus.controller.home_id + ) { + return false; + } + return nodesNotReady.some((node) => identifiers.node_id === node.node_id); + }); } private async _getMigrationData() { From 564a72528449386f46723fc717f5dcefaa4cf505 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 21 Feb 2022 08:52:59 -1000 Subject: [PATCH 134/174] Allow config entries to be reloaded when they are in setup_retry state (#11759) --- src/panels/config/integrations/ha-integration-card.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts index 89cc0306c2..18518c2d5e 100644 --- a/src/panels/config/integrations/ha-integration-card.ts +++ b/src/panels/config/integrations/ha-integration-card.ts @@ -369,7 +369,7 @@ export class HaIntegrationCard extends LitElement { ` : ""} ${!item.disabled_by && - item.state === "loaded" && + (item.state === "loaded" || item.state === "setup_retry") && item.supports_unload && item.source !== "system" ? html` From 27750b8b5d01a0b0270f4cf3e4e012445ab635c0 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Mon, 21 Feb 2022 13:21:21 -0600 Subject: [PATCH 135/174] Area Card Editor to Ha Form (#11762) --- .../config-elements/hui-area-card-editor.ts | 134 ++++++------------ 1 file changed, 42 insertions(+), 92 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts index c2e9bb7564..547b3af4f5 100644 --- a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts @@ -1,18 +1,14 @@ -import "@polymer/paper-input/paper-input"; -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import "../../../../components/ha-form/ha-form"; +import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, boolean, object, optional, string } from "superstruct"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-area-picker"; -import { HomeAssistant } from "../../../../types"; -import { AreaCardConfig } from "../../cards/types"; -import "../../components/hui-theme-select-editor"; -import { LovelaceCardEditor } from "../../types"; +import type { HomeAssistant } from "../../../../types"; +import type { AreaCardConfig } from "../../cards/types"; +import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { EditorTarget } from "../types"; -import { configElementStyle } from "./config-elements-style"; -import "../../../../components/ha-formfield"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -38,21 +34,18 @@ export class HuiAreaCardEditor this._config = config; } - get _area(): string { - return this._config!.area || ""; - } - - get _navigation_path(): string { - return this._config!.navigation_path || ""; - } - - get _theme(): string { - return this._config!.theme || ""; - } - - get _show_camera(): boolean { - return this._config!.show_camera || false; - } + private _schema = memoizeOne((): HaFormSchema[] => [ + { name: "area", selector: { area: {} } }, + { name: "show_camera", required: false, selector: { boolean: {} } }, + { + name: "", + type: "grid", + schema: [ + { name: "navigation_path", required: false, selector: { text: {} } }, + { name: "theme", required: false, selector: { theme: {} } }, + ], + }, + ]); protected render(): TemplateResult { if (!this.hass || !this._config) { @@ -60,78 +53,35 @@ export class HuiAreaCardEditor } return html` -
    - - - - - - - -
    + `; } private _valueChanged(ev: CustomEvent): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target! as EditorTarget; - const value = - target.checked !== undefined ? target.checked : ev.detail.value; - - if (this[`_${target.configValue}`] === value) { - return; - } - - let newConfig; - if (target.configValue) { - if (!value) { - newConfig = { ...this._config }; - delete newConfig[target.configValue!]; - } else { - newConfig = { - ...this._config, - [target.configValue!]: value, - }; - } - } - fireEvent(this, "config-changed", { config: newConfig }); + const config = ev.detail.value; + Object.keys(config).forEach((k) => config[k] === "" && delete config[k]); + fireEvent(this, "config-changed", { config }); } - static get styles(): CSSResultGroup { - return configElementStyle; - } + private _computeLabelCallback = (schema: HaFormSchema) => { + switch (schema.name) { + case "area": + return this.hass!.localize("ui.panel.lovelace.editor.card.area.name"); + case "navigation_path": + return this.hass!.localize( + "ui.panel.lovelace.editor.action-editor.navigation_path" + ); + } + return this.hass!.localize( + `ui.panel.lovelace.editor.card.area.${schema.name}` + ); + }; } declare global { From eb1f94c37094fac7465fbe968c91b8a0466b12c2 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Mon, 21 Feb 2022 12:35:37 -0800 Subject: [PATCH 136/174] Fix WebRTC player stream playback when disconnected/connected (#11764) --- src/components/ha-web-rtc-player.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/ha-web-rtc-player.ts b/src/components/ha-web-rtc-player.ts index e65017265e..b55a33eb56 100644 --- a/src/components/ha-web-rtc-player.ts +++ b/src/components/ha-web-rtc-player.ts @@ -43,7 +43,7 @@ class HaWebRtcPlayer extends LitElement { private _remoteStream?: MediaStream; - protected render(): TemplateResult { + protected override render(): TemplateResult { if (this._error) { return html`${this._error}`; } @@ -58,12 +58,19 @@ class HaWebRtcPlayer extends LitElement { `; } - public disconnectedCallback() { + public override connectedCallback() { + super.connectedCallback(); + if (this.hasUpdated) { + this._startWebRtc(); + } + } + + public override disconnectedCallback() { super.disconnectedCallback(); this._cleanUp(); } - protected updated(changedProperties: PropertyValues) { + protected override updated(changedProperties: PropertyValues) { if (!changedProperties.has("entityid")) { return; } From e6d1e86c644c08b70bd9fcd628058f70920db4c7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 21 Feb 2022 22:56:10 +0100 Subject: [PATCH 137/174] set theme to undefined when no theme (#11765) --- src/panels/lovelace/components/hui-theme-select-editor.ts | 2 +- .../lovelace/editor/config-elements/hui-area-card-editor.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/panels/lovelace/components/hui-theme-select-editor.ts b/src/panels/lovelace/components/hui-theme-select-editor.ts index 30bc0bdef0..af07d88788 100644 --- a/src/panels/lovelace/components/hui-theme-select-editor.ts +++ b/src/panels/lovelace/components/hui-theme-select-editor.ts @@ -57,7 +57,7 @@ export class HuiThemeSelectEditor extends LitElement { if (!this.hass || ev.target.value === "") { return; } - this.value = ev.target.value === "remove" ? "" : ev.target.value; + this.value = ev.target.value === "remove" ? undefined : ev.target.value; fireEvent(this, "value-changed", { value: this.value }); } } diff --git a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts index 547b3af4f5..ad9062320e 100644 --- a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts @@ -65,7 +65,6 @@ export class HuiAreaCardEditor private _valueChanged(ev: CustomEvent): void { const config = ev.detail.value; - Object.keys(config).forEach((k) => config[k] === "" && delete config[k]); fireEvent(this, "config-changed", { config }); } From b1f369a3556b6791db39794ada10de44063ba529 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 21 Feb 2022 23:09:13 +0100 Subject: [PATCH 138/174] Paper input migrations (#11766) --- src/dialogs/generic/dialog-box.ts | 13 ++-- .../controls/more-info-media_player.ts | 1 - src/panels/config/scene/ha-scene-editor.ts | 18 +++--- .../config/script/blueprint-script-editor.ts | 11 ++-- src/panels/profile/ha-change-password-card.ts | 61 ++++++++++--------- src/panels/profile/ha-pick-theme-row.ts | 21 ++++--- 6 files changed, 64 insertions(+), 61 deletions(-) diff --git a/src/dialogs/generic/dialog-box.ts b/src/dialogs/generic/dialog-box.ts index 4fc1c09cde..2ca11e4ac5 100644 --- a/src/dialogs/generic/dialog-box.ts +++ b/src/dialogs/generic/dialog-box.ts @@ -1,12 +1,11 @@ import "@material/mwc-button/mwc-button"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-dialog"; import "../../components/ha-switch"; -import { PolymerChangedEvent } from "../../polymer-types"; +import "../../components/ha-textfield"; import { haStyleDialog } from "../../resources/styles"; import { HomeAssistant } from "../../types"; import { DialogBoxParams } from "./show-dialog-box"; @@ -71,18 +70,18 @@ class DialogBox extends LitElement { : ""} ${this._params.prompt ? html` - + > ` : ""}
    @@ -107,8 +106,8 @@ class DialogBox extends LitElement { `; } - private _valueChanged(ev: PolymerChangedEvent) { - this._value = ev.detail.value; + private _valueChanged(ev) { + this._value = ev.target.value; } private _dismiss(): void { diff --git a/src/dialogs/more-info/controls/more-info-media_player.ts b/src/dialogs/more-info/controls/more-info-media_player.ts index a721ae0a40..c415d322e9 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.ts +++ b/src/dialogs/more-info/controls/more-info-media_player.ts @@ -10,7 +10,6 @@ import { mdiVolumeOff, mdiVolumePlus, } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; diff --git a/src/panels/config/scene/ha-scene-editor.ts b/src/panels/config/scene/ha-scene-editor.ts index ee2ab401cd..3ecd3b026a 100644 --- a/src/panels/config/scene/ha-scene-editor.ts +++ b/src/panels/config/scene/ha-scene-editor.ts @@ -28,12 +28,13 @@ import { navigate } from "../../../common/navigate"; import { computeRTL } from "../../../common/util/compute_rtl"; import "../../../components/device/ha-device-picker"; import "../../../components/entity/ha-entities-picker"; +import "../../../components/ha-area-picker"; import "../../../components/ha-card"; import "../../../components/ha-fab"; import "../../../components/ha-icon-button"; import "../../../components/ha-icon-picker"; -import "../../../components/ha-area-picker"; import "../../../components/ha-svg-icon"; +import "../../../components/ha-textfield"; import { computeDeviceName, DeviceRegistryEntry, @@ -288,14 +289,14 @@ export class HaSceneEditor extends SubscribeMixin(
    - + > ` - : html``} + @change=${this._inputChanged} + >`} ` ) : html`

    @@ -145,7 +144,7 @@ export class HaBlueprintScriptEditor extends LitElement { ev.stopPropagation(); const target = ev.target as any; const key = target.key; - const value = ev.detail.value; + const value = ev.detail?.value ?? target.value; if ( (this.config.use_blueprint.input && this.config.use_blueprint.input[key] === value) || diff --git a/src/panels/profile/ha-change-password-card.ts b/src/panels/profile/ha-change-password-card.ts index 4ba20e8478..e0ad18412a 100644 --- a/src/panels/profile/ha-change-password-card.ts +++ b/src/panels/profile/ha-change-password-card.ts @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, @@ -11,8 +10,10 @@ import { import { customElement, property, state } from "lit/decorators"; import "../../components/ha-card"; import "../../components/ha-circular-progress"; +import "../../components/ha-textfield"; import { haStyle } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; +import "../../components/ha-alert"; @customElement("ha-change-password-card") class HaChangePasswordCard extends LitElement { @@ -24,11 +25,11 @@ class HaChangePasswordCard extends LitElement { @state() private _errorMsg?: string; - @state() private _currentPassword?: string; + @state() private _currentPassword = ""; - @state() private _password?: string; + @state() private _password = ""; - @state() private _passwordConfirm?: string; + @state() private _passwordConfirm = ""; protected render(): TemplateResult { return html` @@ -40,46 +41,48 @@ class HaChangePasswordCard extends LitElement { >

    ${this._errorMsg - ? html`
    ${this._errorMsg}
    ` + ? html`${this._errorMsg}` : ""} ${this._statusMsg - ? html`
    ${this._statusMsg}
    ` + ? html`${this._statusMsg}` : ""} - + > ${this._currentPassword - ? html` - + ` + >` : ""}
    @@ -101,16 +104,16 @@ class HaChangePasswordCard extends LitElement { `; } - private _currentPasswordChanged(ev: CustomEvent) { - this._currentPassword = ev.detail.value; + private _currentPasswordChanged(ev) { + this._currentPassword = ev.target.value; } - private _newPasswordChanged(ev: CustomEvent) { - this._password = ev.detail.value; + private _newPasswordChanged(ev) { + this._password = ev.target.value; } - private _newPasswordConfirmChanged(ev: CustomEvent) { - this._passwordConfirm = ev.detail.value; + private _newPasswordConfirmChanged(ev) { + this._passwordConfirm = ev.target.value; } protected firstUpdated(changedProps: PropertyValues) { @@ -162,23 +165,21 @@ class HaChangePasswordCard extends LitElement { this._statusMsg = this.hass.localize( "ui.panel.profile.change_password.success" ); - this._currentPassword = undefined; - this._password = undefined; - this._passwordConfirm = undefined; + this._currentPassword = ""; + this._password = ""; + this._passwordConfirm = ""; } static get styles(): CSSResultGroup { return [ haStyle, css` - .error { - color: var(--error-color); - } - .status { - color: var(--primary-color); + ha-textfield { + margin-top: 8px; + display: block; } #currentPassword { - margin-top: -8px; + margin-top: 0; } `, ]; diff --git a/src/panels/profile/ha-pick-theme-row.ts b/src/panels/profile/ha-pick-theme-row.ts index 214cc20515..d9fe4f78c2 100644 --- a/src/panels/profile/ha-pick-theme-row.ts +++ b/src/panels/profile/ha-pick-theme-row.ts @@ -1,5 +1,6 @@ import "@material/mwc-button/mwc-button"; -import "@polymer/paper-input/paper-input"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, @@ -14,14 +15,13 @@ import "../../components/ha-formfield"; import "../../components/ha-radio"; import type { HaRadio } from "../../components/ha-radio"; import "../../components/ha-settings-row"; +import "../../components/ha-textfield"; import { - DEFAULT_PRIMARY_COLOR, DEFAULT_ACCENT_COLOR, + DEFAULT_PRIMARY_COLOR, } from "../../resources/ha-style"; import { HomeAssistant } from "../../types"; import { documentationUrl } from "../../util/documentation-url"; -import "@material/mwc-select/mwc-select"; -import "@material/mwc-list/mwc-list-item"; @customElement("ha-pick-theme-row") export class HaPickThemeRow extends LitElement { @@ -115,8 +115,8 @@ export class HaPickThemeRow extends LitElement { ${curTheme === "default" - ? html`
    - + - + + > ${themeSettings?.primaryColor || themeSettings?.accentColor ? html` ${this.hass.localize("ui.panel.profile.themes.reset")} @@ -228,7 +228,8 @@ export class HaPickThemeRow extends LitElement { align-items: center; flex-grow: 1; } - paper-input { + ha-textfield { + --text-field-padding: 8px; min-width: 75px; flex-grow: 1; margin: 0 4px; From d230541256802ec177b3d1f555e04d954c0a2a6e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 00:57:56 -0800 Subject: [PATCH 139/174] Only show description when set (#11772) --- .../automation/manual-automation-editor.ts | 62 +++++++++++++++---- src/translations/en.json | 3 +- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 3fd2d8c915..b0df97fbfc 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -1,7 +1,7 @@ import "@material/mwc-button/mwc-button"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, CSSResultGroup, html, LitElement } from "lit"; -import { customElement, property } from "lit/decorators"; +import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/ha-entity-toggle"; import "../../../components/ha-card"; @@ -34,6 +34,8 @@ export class HaManualAutomationEditor extends LitElement { @property() public stateObj?: HassEntity; + @state() private _showDescription = false; + protected render() { return html` ${!this.narrow @@ -55,17 +57,30 @@ export class HaManualAutomationEditor extends LitElement { @change=${this._valueChanged} > - + ${this._showDescription + ? html` + + ` + : html` + + `}

    ${this.hass.localize( "ui.panel.config.automation.editor.modes.description", @@ -235,6 +250,17 @@ export class HaManualAutomationEditor extends LitElement { `; } + protected willUpdate(changedProps: PropertyValues): void { + super.willUpdate(changedProps); + if ( + !this._showDescription && + changedProps.has("config") && + this.config.description + ) { + this._showDescription = true; + } + } + private _runActions(ev: Event) { triggerAutomationActions(this.hass, (ev.target as any).stateObj.entity_id); } @@ -305,6 +331,10 @@ export class HaManualAutomationEditor extends LitElement { }); } + private _addDescription() { + this._showDescription = true; + } + static get styles(): CSSResultGroup { return [ haStyle, @@ -312,6 +342,12 @@ export class HaManualAutomationEditor extends LitElement { ha-card { overflow: hidden; } + .link-button-row { + padding: 14px; + } + ha-textarea { + display: block; + } span[slot="introduction"] a { color: var(--primary-color); } diff --git a/src/translations/en.json b/src/translations/en.json index 36998964a5..7e214663dd 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1593,7 +1593,8 @@ "move_down": "Move down", "description": { "label": "Description", - "placeholder": "Optional description" + "placeholder": "Optional description", + "add": "Add description" }, "blueprint": { "header": "Blueprint", From 73855e6f99f34a4fe4232c3501a2f333a6462965 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 22 Feb 2022 03:56:04 -0600 Subject: [PATCH 140/174] Thermostat Editor to HA - Form (#11763) * Thermostat - Ha Form * Update hui-thermostat-card-editor.ts Co-authored-by: Bram Kragten --- .../hui-thermostat-card-editor.ts | 113 ++++++------------ 1 file changed, 39 insertions(+), 74 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts index 38ee6a87ee..fbae156c62 100644 --- a/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-thermostat-card-editor.ts @@ -1,16 +1,13 @@ -import "@polymer/paper-input/paper-input"; -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import "../../../../components/ha-form/ha-form"; +import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/entity/ha-entity-picker"; -import { HomeAssistant } from "../../../../types"; -import { ThermostatCardConfig } from "../../cards/types"; -import "../../components/hui-theme-select-editor"; -import { LovelaceCardEditor } from "../../types"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../types"; +import type { ThermostatCardConfig } from "../../cards/types"; +import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { EditorTarget, EntitiesEditorEvent } from "../types"; -import { configElementStyle } from "./config-elements-style"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -21,7 +18,17 @@ const cardConfigStruct = assign( }) ); -const includeDomains = ["climate"]; +const SCHEMA: HaFormSchema[] = [ + { name: "entity", selector: { entity: { domain: "climate" } } }, + { + type: "grid", + name: "", + schema: [ + { name: "name", required: false, selector: { text: {} } }, + { name: "theme", required: false, selector: { theme: {} } }, + ], + }, +]; @customElement("hui-thermostat-card-editor") export class HuiThermostatCardEditor @@ -37,81 +44,39 @@ export class HuiThermostatCardEditor this._config = config; } - get _entity(): string { - return this._config!.entity || ""; - } - - get _name(): string { - return this._config!.name || ""; - } - - get _theme(): string { - return this._config!.theme || ""; - } - protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; } return html` -

    - - - -
    + `; } - private _valueChanged(ev: EntitiesEditorEvent): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target! as EditorTarget; - - if (this[`_${target.configValue}`] === target.value) { - return; - } - if (target.configValue) { - if (target.value === "") { - this._config = { ...this._config }; - delete this._config[target.configValue!]; - } else { - this._config = { ...this._config, [target.configValue!]: target.value }; - } - } - fireEvent(this, "config-changed", { config: this._config }); + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); } - static get styles(): CSSResultGroup { - return configElementStyle; - } + private _computeLabelCallback = (schema: HaFormSchema) => { + if (schema.name === "entity") { + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.entity" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.required" + )})`; + } + + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + }; } declare global { From 15d1b8b2ac23698cc47d873973eba01847d14f0d Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 22 Feb 2022 04:00:13 -0600 Subject: [PATCH 141/174] Alarm Card Editor to HA Form (#11760) * Move to ha-form * Update hui-alarm-panel-card-editor.ts Co-authored-by: Bram Kragten --- .../hui-alarm-panel-card-editor.ts | 230 +++++------------- 1 file changed, 63 insertions(+), 167 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts index c4c974715a..1a516e8e46 100644 --- a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts @@ -1,20 +1,15 @@ -import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; -import { mdiClose } from "@mdi/js"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import "../../../../components/ha-form/ha-form"; +import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { array, assert, assign, object, optional, string } from "superstruct"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { stopPropagation } from "../../../../common/dom/stop_propagation"; -import "../../../../components/entity/ha-entity-picker"; -import "../../../../components/ha-svg-icon"; -import { HomeAssistant } from "../../../../types"; -import { AlarmPanelCardConfig } from "../../cards/types"; -import "../../components/hui-theme-select-editor"; -import { LovelaceCardEditor } from "../../types"; +import type { HomeAssistant } from "../../../../types"; +import type { AlarmPanelCardConfig } from "../../cards/types"; +import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { EditorTarget, EntitiesEditorEvent } from "../types"; -import { configElementStyle } from "./config-elements-style"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { LocalizeFunc } from "../../../../common/translations/localize"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -26,7 +21,13 @@ const cardConfigStruct = assign( }) ); -const includeDomains = ["alarm_control_panel"]; +const states = [ + "arm_home", + "arm_away", + "arm_night", + "arm_vacation", + "arm_custom_bypass", +]; @customElement("hui-alarm-panel-card-editor") export class HuiAlarmPanelCardEditor @@ -42,177 +43,72 @@ export class HuiAlarmPanelCardEditor this._config = config; } - get _entity(): string { - return this._config!.entity || ""; - } + private _schema = memoizeOne((localize: LocalizeFunc): HaFormSchema[] => [ + { + name: "entity", + required: true, + selector: { entity: { domain: "alarm_control_panel" } }, + }, + { + type: "grid", + name: "", + schema: [ + { name: "name", selector: { text: {} } }, + { name: "theme", selector: { theme: {} } }, + ], + }, - get _name(): string { - return this._config!.name || ""; - } - - get _states(): string[] { - return this._config!.states || []; - } - - get _theme(): string { - return this._config!.theme || ""; - } + { + type: "multi_select", + name: "states", + options: states.map((s) => [ + s, + localize(`ui.card.alarm_control_panel.${s}`), + ]) as [string, string][], + }, + ]); protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; } - const states = [ - "arm_home", - "arm_away", - "arm_night", - "arm_vacation", - "arm_custom_bypass", - ]; + const schema = this._schema(this.hass.localize); return html` -
    - - - Used States ${this._states.map( - (entityState, index) => html` -
    - ${entityState} - -
    - ` - )} - - ${states.map( - (entityState) => - html`${entityState} ` - )} - - -
    + `; } - static get styles(): CSSResultGroup { - return [ - configElementStyle, - css` - .states { - display: flex; - flex-direction: row; - } - .deleteState { - visibility: hidden; - } - .states:hover > .deleteState { - visibility: visible; - } - ha-svg-icon { - padding-top: 12px; - } - `, - ]; + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _stateRemoved(ev: EntitiesEditorEvent): void { - if (!this._config || !this._states || !this.hass) { - return; + private _computeLabelCallback = (schema: HaFormSchema) => { + if (schema.name === "entity") { + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.entity" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.required" + )})`; } - const target = ev.target! as EditorTarget; - const index = Number(target.value); - if (index > -1) { - const newStates = [...this._states]; - newStates.splice(index, 1); - fireEvent(this, "config-changed", { - config: { - ...this._config, - states: newStates, - }, - }); + if (schema.name === "name") { + return this.hass!.localize(`ui.panel.lovelace.editor.card.generic.name`); } - } - private _stateAdded(ev: EntitiesEditorEvent): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target! as EditorTarget; - if (!target.value || this._states.indexOf(target.value) !== -1) { - return; - } - const newStates = [...this._states]; - newStates.push(target.value); - target.value = ""; - fireEvent(this, "config-changed", { - config: { - ...this._config, - states: newStates, - }, - }); - } - - private _valueChanged(ev: EntitiesEditorEvent): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target! as EditorTarget; - if (this[`_${target.configValue}`] === target.value) { - return; - } - if (target.configValue) { - if (target.value === "") { - this._config = { ...this._config }; - delete this._config[target.configValue!]; - } else { - this._config = { - ...this._config, - [target.configValue!]: target.value, - }; - } - } - fireEvent(this, "config-changed", { config: this._config }); - } + return this.hass!.localize( + `ui.panel.lovelace.editor.card.alarm-panel.${ + schema.name === "states" ? "available_states" : schema.name + }` + ); + }; } declare global { From 940f5c0002a51969b71211e466291d0aa235fc34 Mon Sep 17 00:00:00 2001 From: Pascal Winters Date: Tue, 22 Feb 2022 13:33:08 +0100 Subject: [PATCH 142/174] Change icons for cover with device_class curtain (#11752) Co-authored-by: Bram Kragten --- src/common/entity/cover_icon.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/entity/cover_icon.ts b/src/common/entity/cover_icon.ts index 4308306890..c7fa277ecb 100644 --- a/src/common/entity/cover_icon.ts +++ b/src/common/entity/cover_icon.ts @@ -120,6 +120,7 @@ export const computeOpenIcon = (stateObj: HassEntity): string => { case "awning": case "door": case "gate": + case "curtain": return mdiArrowExpandHorizontal; default: return mdiArrowUp; @@ -131,6 +132,7 @@ export const computeCloseIcon = (stateObj: HassEntity): string => { case "awning": case "door": case "gate": + case "curtain": return mdiArrowCollapseHorizontal; default: return mdiArrowDown; From ed9d886009369676350fa07bc0964846e5ab76fa Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 22 Feb 2022 13:38:44 +0100 Subject: [PATCH 143/174] no need for memoize --- .../hui-alarm-panel-card-editor.ts | 4 +-- .../config-elements/hui-area-card-editor.ts | 33 +++++++++---------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts index 1a516e8e46..bc6efba022 100644 --- a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts @@ -73,13 +73,11 @@ export class HuiAlarmPanelCardEditor return html``; } - const schema = this._schema(this.hass.localize); - return html` diff --git a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts index ad9062320e..447c1ccd2e 100644 --- a/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-area-card-editor.ts @@ -1,14 +1,13 @@ -import "../../../../components/ha-form/ha-form"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, assign, boolean, object, optional, string } from "superstruct"; -import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-form/ha-form"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { AreaCardConfig } from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import type { HaFormSchema } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -20,6 +19,19 @@ const cardConfigStruct = assign( }) ); +const SCHEMA: HaFormSchema[] = [ + { name: "area", selector: { area: {} } }, + { name: "show_camera", required: false, selector: { boolean: {} } }, + { + name: "", + type: "grid", + schema: [ + { name: "navigation_path", required: false, selector: { text: {} } }, + { name: "theme", required: false, selector: { theme: {} } }, + ], + }, +]; + @customElement("hui-area-card-editor") export class HuiAreaCardEditor extends LitElement @@ -34,19 +46,6 @@ export class HuiAreaCardEditor this._config = config; } - private _schema = memoizeOne((): HaFormSchema[] => [ - { name: "area", selector: { area: {} } }, - { name: "show_camera", required: false, selector: { boolean: {} } }, - { - name: "", - type: "grid", - schema: [ - { name: "navigation_path", required: false, selector: { text: {} } }, - { name: "theme", required: false, selector: { theme: {} } }, - ], - }, - ]); - protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; @@ -56,7 +55,7 @@ export class HuiAreaCardEditor From 1e6f402d0f5e57d3fe17bb273be16ec1d70cce4e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 22 Feb 2022 18:32:56 +0100 Subject: [PATCH 144/174] Include scoped custom element polyfill (#11776) --- package.json | 1 + src/entrypoints/app.ts | 1 + src/html/_js_base.html.template | 2 +- yarn.lock | 8 ++++++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a25d8209c6..5067141225 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "@vibrant/core": "^3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", "@vue/web-component-wrapper": "^1.2.0", + "@webcomponents/scoped-custom-element-registry": "^0.0.5", "@webcomponents/webcomponentsjs": "^2.2.10", "app-datepicker": "^5.0.1", "chart.js": "^3.3.2", diff --git a/src/entrypoints/app.ts b/src/entrypoints/app.ts index e93a38769e..f7f607720b 100644 --- a/src/entrypoints/app.ts +++ b/src/entrypoints/app.ts @@ -2,6 +2,7 @@ import { setPassiveTouchGestures, setCancelSyntheticClickEvents, } from "@polymer/polymer/lib/utils/settings"; +import "@webcomponents/scoped-custom-element-registry/scoped-custom-element-registry.min"; import "../layouts/home-assistant"; import "../resources/ha-style"; import "../resources/roboto"; diff --git a/src/html/_js_base.html.template b/src/html/_js_base.html.template index 0eea886de2..ed3408e16e 100644 --- a/src/html/_js_base.html.template +++ b/src/html/_js_base.html.template @@ -22,4 +22,4 @@ document.write(" + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 206787e049..469c9c90b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4785,6 +4785,13 @@ __metadata: languageName: node linkType: hard +"@webcomponents/scoped-custom-element-registry@npm:^0.0.5": + version: 0.0.5 + resolution: "@webcomponents/scoped-custom-element-registry@npm:0.0.5" + checksum: eb422f63f042f9e525d89491267073a1c822d60d44b91fed5178d5dc2092e95f180e45e3eb50d214a35ea16580277461c15cade6f4ca1e7694e8a7a451355d31 + languageName: node + linkType: hard + "@webcomponents/shadycss@npm:^1.9.1": version: 1.10.2 resolution: "@webcomponents/shadycss@npm:1.10.2" @@ -9194,6 +9201,7 @@ fsevents@^1.2.7: "@vue/web-component-wrapper": ^1.2.0 "@web/dev-server": ^0.0.24 "@web/dev-server-rollup": ^0.2.11 + "@webcomponents/scoped-custom-element-registry": ^0.0.5 "@webcomponents/webcomponentsjs": ^2.2.10 app-datepicker: ^5.0.1 babel-loader: ^8.2.2 From f5b5414461496f90878fd0ec30c5eb1ba912673c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 14:03:32 -0800 Subject: [PATCH 145/174] Show triggered in automation editor (#11771) Co-authored-by: Bram Kragten --- src/common/util/debounce.ts | 6 +- src/data/config.ts | 19 +++ .../trigger/ha-automation-trigger-row.ts | 127 +++++++++++++++++- src/translations/en.json | 1 + 4 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 src/data/config.ts diff --git a/src/common/util/debounce.ts b/src/common/util/debounce.ts index 70aaddf2e0..cd61f072b9 100644 --- a/src/common/util/debounce.ts +++ b/src/common/util/debounce.ts @@ -11,7 +11,7 @@ export const debounce = ( immediate = false ) => { let timeout: number | undefined; - return (...args: T): void => { + const debouncedFunc = (...args: T): void => { const later = () => { timeout = undefined; if (!immediate) { @@ -25,4 +25,8 @@ export const debounce = ( func(...args); } }; + debouncedFunc.cancel = () => { + clearTimeout(timeout); + }; + return debouncedFunc; }; diff --git a/src/data/config.ts b/src/data/config.ts new file mode 100644 index 0000000000..281e2debbf --- /dev/null +++ b/src/data/config.ts @@ -0,0 +1,19 @@ +import { HomeAssistant } from "../types"; + +interface ValidationResult { + valid: boolean; + error: string | null; +} + +type ValidKeys = "trigger" | "action" | "condition"; + +export const validateConfig = < + T extends Partial<{ [key in ValidKeys]: unknown }> +>( + hass: HomeAssistant, + config: T +): Promise> => + hass.callWS({ + type: "validate_config", + ...config, + }); diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index ecf1f1a4b7..7d3c15c3ed 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -1,11 +1,13 @@ +import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import { mdiDotsVertical } from "@mdi/js"; import "@material/mwc-select"; import type { Select } from "@material/mwc-select"; -import { css, CSSResultGroup, html, LitElement } from "lit"; +import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { classMap } from "lit/directives/class-map"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; import { stringCompare } from "../../../../common/string/compare"; @@ -16,7 +18,7 @@ import "../../../../components/ha-card"; import "../../../../components/ha-alert"; import "../../../../components/ha-textfield"; import "../../../../components/ha-icon-button"; -import type { Trigger } from "../../../../data/automation"; +import { subscribeTrigger, Trigger } from "../../../../data/automation"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; @@ -34,6 +36,8 @@ import "./types/ha-automation-trigger-time"; import "./types/ha-automation-trigger-time_pattern"; import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-zone"; +import { debounce } from "../../../../common/util/debounce"; +import { validateConfig } from "../../../../data/config"; const OPTIONS = [ "device", @@ -90,6 +94,12 @@ export default class HaAutomationTriggerRow extends LitElement { @state() private _requestShowId = false; + @state() private _triggered = false; + + @state() private _triggerColor = false; + + private _triggerUnsub?: Promise; + private _processedTypes = memoizeOne( (localize: LocalizeFunc): [string, string][] => OPTIONS.map( @@ -219,10 +229,98 @@ export default class HaAutomationTriggerRow extends LitElement {
    `}
    +
    + ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.triggered" + )} +
    `; } + protected override updated(changedProps: PropertyValues): void { + super.updated(changedProps); + if (changedProps.has("trigger")) { + this._subscribeTrigger(); + } + } + + public connectedCallback(): void { + super.connectedCallback(); + if (this.hasUpdated && this.trigger) { + this._subscribeTrigger(); + } + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + if (this._triggerUnsub) { + this._triggerUnsub.then((unsub) => unsub()); + this._triggerUnsub = undefined; + } + this._doSubscribeTrigger.cancel(); + } + + private _subscribeTrigger() { + // Clean up old trigger subscription. + if (this._triggerUnsub) { + this._triggerUnsub.then((unsub) => unsub()); + this._triggerUnsub = undefined; + } + + this._doSubscribeTrigger(); + } + + private _doSubscribeTrigger = debounce(async () => { + let untriggerTimeout: number | undefined; + const showTriggeredTime = 5000; + const trigger = this.trigger; + + // Clean up old trigger subscription. + if (this._triggerUnsub) { + this._triggerUnsub.then((unsub) => unsub()); + this._triggerUnsub = undefined; + } + + const validateResult = await validateConfig(this.hass, { + trigger: this.trigger, + }); + + // Don't do anything if trigger not valid or if trigger changed. + if (!validateResult.trigger.valid || this.trigger !== trigger) { + return; + } + + const triggerUnsub = subscribeTrigger( + this.hass, + () => { + if (untriggerTimeout !== undefined) { + clearTimeout(untriggerTimeout); + this._triggerColor = !this._triggerColor; + } else { + this._triggerColor = false; + } + this._triggered = true; + untriggerTimeout = window.setTimeout(() => { + this._triggered = false; + untriggerTimeout = undefined; + }, showTriggeredTime); + }, + trigger + ); + triggerUnsub.catch(() => { + if (this._triggerUnsub === triggerUnsub) { + this._triggerUnsub = undefined; + } + }); + this._triggerUnsub = triggerUnsub; + }, 5000); + private _handleUiModeNotAvailable(ev: CustomEvent) { this._warnings = handleStructError(this.hass, ev.detail).warnings; if (!this._yamlMode) { @@ -327,6 +425,31 @@ export default class HaAutomationTriggerRow extends LitElement { z-index: 3; --mdc-theme-text-primary-on-background: var(--primary-text-color); } + .triggered { + position: absolute; + top: 0px; + right: 0px; + left: 0px; + text-transform: uppercase; + pointer-events: none; + font-weight: bold; + font-size: 14px; + background-color: var(--primary-color); + color: var(--text-primary-color); + max-height: 0px; + overflow: hidden; + transition: max-height 0.3s; + text-align: center; + border-top-right-radius: var(--ha-card-border-radius, 4px); + border-top-left-radius: var(--ha-card-border-radius, 4px); + } + .triggered.active { + max-height: 100px; + } + .triggered.accent { + background-color: var(--accent-color); + color: var(--text-accent-color, var(--text-primary-color)); + } .rtl .card-menu { float: left; } diff --git a/src/translations/en.json b/src/translations/en.json index 7e214663dd..20c6c5d86f 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1623,6 +1623,7 @@ "header": "Triggers", "introduction": "Triggers are what starts the processing of an automation rule. It is possible to specify multiple triggers for the same rule. Once a trigger starts, Home Assistant will validate the conditions, if any, and call the action.", "learn_more": "Learn more about triggers", + "triggered": "Triggered", "add": "Add trigger", "id": "Trigger ID", "edit_id": "Edit trigger ID", From 5335772a7a75d306a354da88d64eae2c4ec53ee6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 14:51:25 -0800 Subject: [PATCH 146/174] Allow changing volume media player entity (#11781) Co-authored-by: Zack Barett --- src/data/media-player.ts | 14 +++++++ .../media-browser/browser-media-player.ts | 11 ++++- .../media-browser/ha-bar-media-player.ts | 42 ++++++++++++++++++- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 7a8a944957..087b431479 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -360,3 +360,17 @@ export const cleanupMediaTitle = (title?: string): string | undefined => { const index = title.indexOf("?authSig="); return index > 0 ? title.slice(0, index) : title; }; + +/** + * Set volume of a media player entity. + * @param hass Home Assistant object + * @param entity_id entity ID of media player + * @param volume_level number between 0..1 + * @returns + */ +export const setMediaPlayerVolume = ( + hass: HomeAssistant, + entity_id: string, + volume_level: number +) => + hass.callService("media_player", "volume_set", { entity_id, volume_level }); diff --git a/src/panels/media-browser/browser-media-player.ts b/src/panels/media-browser/browser-media-player.ts index 40b05fdae4..182f944e3f 100644 --- a/src/panels/media-browser/browser-media-player.ts +++ b/src/panels/media-browser/browser-media-player.ts @@ -4,6 +4,7 @@ import { MediaPlayerItem, SUPPORT_PAUSE, SUPPORT_PLAY, + SUPPORT_VOLUME_SET, } from "../../data/media-player"; import { ResolvedMediaSource } from "../../data/media_source"; import { HomeAssistant } from "../../types"; @@ -20,9 +21,11 @@ export class BrowserMediaPlayer { public hass: HomeAssistant, public item: MediaPlayerItem, public resolved: ResolvedMediaSource, + volume: number, private onChange: () => void ) { const player = new Audio(this.resolved.url); + player.volume = volume; player.addEventListener("play", this._handleChange); player.addEventListener("playing", () => { this.buffering = false; @@ -57,6 +60,11 @@ export class BrowserMediaPlayer { this.player.play(); } + public setVolume(volume: number) { + this.player.volume = volume; + this.onChange(); + } + public remove() { this._removed = true; // @ts-ignore @@ -89,8 +97,9 @@ export class BrowserMediaPlayer { base.attributes = { media_title: this.item.title, entity_picture: this.item.thumbnail, + volume_level: this.player.volume, // eslint-disable-next-line no-bitwise - supported_features: SUPPORT_PLAY | SUPPORT_PAUSE, + supported_features: SUPPORT_PLAY | SUPPORT_PAUSE | SUPPORT_VOLUME_SET, }; if (this.player.duration) { diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index 4bc0a6a767..5f0a75c611 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -9,6 +9,7 @@ import { mdiPlay, mdiPlayPause, mdiStop, + mdiVolumeHigh, } from "@mdi/js"; import { css, @@ -40,10 +41,12 @@ import { getCurrentProgress, MediaPlayerEntity, MediaPlayerItem, + setMediaPlayerVolume, SUPPORT_BROWSE_MEDIA, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_STOP, + SUPPORT_VOLUME_SET, } from "../../data/media-player"; import { ResolvedMediaSource } from "../../data/media_source"; import type { HomeAssistant } from "../../types"; @@ -77,6 +80,8 @@ export class BarMediaPlayer extends LitElement { private _progressInterval?: number; + private _browserPlayerVolume = 0.8; + public connectedCallback(): void { super.connectedCallback(); @@ -124,6 +129,7 @@ export class BarMediaPlayer extends LitElement { this.hass, item, resolved, + this._browserPlayerVolume, () => this.requestUpdate("_browserPlayer") ); this._newMediaExpected = false; @@ -230,7 +236,7 @@ export class BarMediaPlayer extends LitElement { )} .path=${control.icon} action=${control.action} - @click=${this._handleClick} + @click=${this._handleControlClick} > ` @@ -257,6 +263,27 @@ export class BarMediaPlayer extends LitElement { const isBrowser = this.entityId === BROWSER_PLAYER; return html`
    + ${ + stateObj && supportsFeature(stateObj, SUPPORT_VOLUME_SET) + ? html` + + + + + + ` + : "" + } + ${ this.narrow @@ -441,7 +468,7 @@ export class BarMediaPlayer extends LitElement { } } - private _handleClick(e: MouseEvent): void { + private _handleControlClick(e: MouseEvent): void { const action = (e.currentTarget! as HTMLElement).getAttribute("action")!; if (!this._browserPlayer) { @@ -474,6 +501,17 @@ export class BarMediaPlayer extends LitElement { fireEvent(this, "player-picked", { entityId }); } + private async _handleVolumeChange(ev) { + ev.stopPropagation(); + const value = Number(ev.target.value) / 100; + if (this._browserPlayer) { + this._browserPlayerVolume = value; + this._browserPlayer.setVolume(value); + } else { + await setMediaPlayerVolume(this.hass, this.entityId, value); + } + } + static get styles(): CSSResultGroup { return css` :host { From ebd6a26554a48706418a9e51a8355bcc13335f42 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 22 Feb 2022 17:03:37 -0600 Subject: [PATCH 147/174] Add community section (#11779) --- .../config/dashboard/ha-config-dashboard.ts | 71 ++++++++++++++++++- src/translations/en.json | 4 ++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 552692e1e3..9d1d77cfd7 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -1,4 +1,10 @@ -import { mdiCloudLock, mdiDotsVertical, mdiMagnify } from "@mdi/js"; +import { + mdiCloudLock, + mdiDotsVertical, + mdiLightbulbOutline, + mdiMagnify, + mdiNewBox, +} from "@mdi/js"; import "@material/mwc-list/mwc-list-item"; import type { ActionDetail } from "@material/mwc-list"; import "@polymer/app-layout/app-header/app-header"; @@ -18,6 +24,7 @@ import "../../../components/ha-icon-next"; import "../../../components/ha-icon-button"; import "../../../components/ha-menu-button"; import "../../../components/ha-button-menu"; +import "../../../components/ha-svg-icon"; import { CloudStatus } from "../../../data/cloud"; import { refreshSupervisorAvailableUpdates, @@ -34,6 +41,7 @@ import "./ha-config-updates"; import { fireEvent } from "../../../common/dom/fire_event"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showToast } from "../../../util/toast"; +import { documentationUrl } from "../../../util/documentation-url"; @customElement("ha-config-dashboard") class HaConfigDashboard extends LitElement { @@ -134,6 +142,51 @@ class HaConfigDashboard extends LitElement { .pages=${configSections.dashboard} > `} +
    + + Tip! + + ${this.hass.localize( + "ui.panel.config.tips.join", + "forums", + html`Forums`, + "twitter", + html`Twitter`, + "discord", + html`Chat`, + "blog", + html`Blog`, + "newsletter", + html`Newsletter + ` + )} + +
    `; @@ -223,6 +276,22 @@ class HaConfigDashboard extends LitElement { :host([narrow]) ha-config-section { margin-top: -42px; } + + .tips { + text-align: center; + } + + .tips .text { + color: var(--secondary-text-color); + } + + .tip-word { + font-weight: 500; + } + + .new { + color: var(--primary-color); + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index 20c6c5d86f..c538c0fc9a 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3147,6 +3147,10 @@ "log_level_changed": "Log Level changed to: {level}", "download_logs": "Download logs" } + }, + "tips": { + "tip": "Tip!", + "join": "Join the community on our {forums}, {twitter}, {discord}, {blog} or {newsletter}" } }, "lovelace": { From 8263e299a88810b4c0836d84f45d7d60a77062f3 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 15:03:52 -0800 Subject: [PATCH 148/174] Bumped version to 20220222.0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 211732d1e8..9770797945 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220220.0 +version = 20220222.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 1baaf764711b7e88b963d83e03426f668b199ba8 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 22 Feb 2022 17:22:04 -0600 Subject: [PATCH 149/174] Fix State Condition 'For' Data (#11782) --- src/common/datetime/create_duration_data.ts | 4 ++-- .../automation/action/types/ha-automation-action-delay.ts | 2 +- .../condition/types/ha-automation-condition-state.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/datetime/create_duration_data.ts b/src/common/datetime/create_duration_data.ts index b198b5dbcb..3b743471ca 100644 --- a/src/common/datetime/create_duration_data.ts +++ b/src/common/datetime/create_duration_data.ts @@ -3,9 +3,9 @@ import type { ForDict } from "../../data/automation"; export const createDurationData = ( duration: string | number | ForDict | undefined -): HaDurationData => { +): HaDurationData | undefined => { if (duration === undefined) { - return {}; + return undefined; } if (typeof duration !== "object") { if (typeof duration === "string" || isNaN(duration)) { diff --git a/src/panels/config/automation/action/types/ha-automation-action-delay.ts b/src/panels/config/automation/action/types/ha-automation-action-delay.ts index 488494e65e..2d71065f8b 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-delay.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-delay.ts @@ -15,7 +15,7 @@ export class HaDelayAction extends LitElement implements ActionElement { @property() public action!: DelayAction; - @property() public _timeData!: HaDurationData; + @property() public _timeData?: HaDurationData; public static get defaultConfig() { return { delay: "" }; diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index e6d166adaa..61e064ff4c 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -53,7 +53,7 @@ export class HaStateCondition extends LitElement implements ConditionElement { protected render() { const trgFor = createDurationData(this.condition.for); - const data = { ...this.condition, ...{ for: trgFor } }; + const data = { ...this.condition, for: trgFor }; const schema = this._schema(this.condition.entity_id); return html` From f633cc2b0d56ad0a88d3e44049182d7c2a69a228 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 22 Feb 2022 23:16:54 -0600 Subject: [PATCH 150/174] entities card editor to MWC (#11785) --- .../config-elements/hui-entities-card-editor.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts index d0558373bd..b0c418718e 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts @@ -1,4 +1,3 @@ -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { @@ -27,6 +26,7 @@ import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import "../../../../components/entity/state-badge"; import "../../../../components/ha-card"; import "../../../../components/ha-formfield"; +import "../../../../components/ha-textfield"; import "../../../../components/ha-icon"; import "../../../../components/ha-switch"; import type { HomeAssistant } from "../../../../types"; @@ -255,7 +255,7 @@ export class HuiEntitiesCardEditor return html`
    - + @input=${this._valueChanged} + > Date: Wed, 23 Feb 2022 06:18:20 +0100 Subject: [PATCH 151/174] Fix ripple corner radius for button card (#11780) --- src/panels/lovelace/cards/hui-button-card.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts index f96a1d9201..58d28bc46a 100644 --- a/src/panels/lovelace/cards/hui-button-card.ts +++ b/src/panels/lovelace/cards/hui-button-card.ts @@ -273,6 +273,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard { box-sizing: border-box; justify-content: center; position: relative; + overflow: hidden; } ha-card:focus { From 7e68393c8469e8bf8462113a865ff4a87b8c6a8f Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 23 Feb 2022 02:42:06 -0600 Subject: [PATCH 152/174] Condition Card Editor to MWC (#11783) --- .../hui-conditional-card-editor.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts index c2a0e65806..d30336a1e8 100644 --- a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts @@ -17,17 +17,21 @@ import { import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; import { stopPropagation } from "../../../../common/dom/stop_propagation"; import "../../../../components/entity/ha-entity-picker"; -import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace"; -import { HomeAssistant } from "../../../../types"; -import { ConditionalCardConfig } from "../../cards/types"; -import { LovelaceCardEditor } from "../../types"; +import "../../../../components/ha-textfield"; +import type { + LovelaceCardConfig, + LovelaceConfig, +} from "../../../../data/lovelace"; +import type { HomeAssistant } from "../../../../types"; +import type { ConditionalCardConfig } from "../../cards/types"; +import type { LovelaceCardEditor } from "../../types"; import "../card-editor/hui-card-element-editor"; import type { HuiCardElementEditor } from "../card-editor/hui-card-element-editor"; import "../card-editor/hui-card-picker"; import "../hui-element-editor"; import type { ConfigChangedEvent } from "../hui-element-editor"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { GUIModeChangedEvent } from "../types"; +import type { GUIModeChangedEvent } from "../types"; import { configElementStyle } from "./config-elements-style"; const conditionStruct = object({ @@ -174,7 +178,7 @@ export class HuiConditionalCardEditor )} - + @input=${this._changeCondition} + >
    ` From 1dd1095d194000a52e15d80d56b0a04a89a9d377 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 23 Feb 2022 02:31:46 -0800 Subject: [PATCH 153/174] Show number of hidden items (#11786) --- .../media-player/ha-media-player-browse.ts | 55 +++++++++++++++++-- src/data/media-player.ts | 3 +- src/translations/en.json | 1 + 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index f4f5e49d5e..1895c78608 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -158,10 +158,11 @@ export class HaMediaPlayerBrowse extends LitElement { const subtitle = this.hass.localize( `ui.components.media-browser.class.${currentItem.media_class}` ); - + const children = currentItem.children || []; const mediaClass = MediaClassBrowserSettings[currentItem.media_class]; - const childrenMediaClass = - MediaClassBrowserSettings[currentItem.children_media_class]; + const childrenMediaClass = currentItem.children_media_class + ? MediaClassBrowserSettings[currentItem.children_media_class] + : MediaClassBrowserSettings.directory; return html` ${ @@ -264,7 +265,7 @@ export class HaMediaPlayerBrowse extends LitElement { @tts-picked=${this._ttsPicked} > ` - : !currentItem.children?.length + : !children.length && !currentItem.not_shown ? html`
    ${currentItem.media_content_id === @@ -296,7 +297,7 @@ export class HaMediaPlayerBrowse extends LitElement { childrenMediaClass.thumbnail_ratio === "portrait", })}" > - ${currentItem.children.map( + ${children.map( (child) => html`
    ` )} + ${currentItem.not_shown + ? html` +
    +
    + ${this.hass.localize( + "ui.components.media-browser.not_shown", + { count: currentItem.not_shown } + )} +
    +
    + ` + : ""}
    ` : html` - ${currentItem.children.map( + ${children.map( (child) => html` ` )} + ${currentItem.not_shown + ? html` + + + ${this.hass.localize( + "ui.components.media-browser.not_shown", + { count: currentItem.not_shown } + )} + + + ` + : ""} ` } @@ -874,6 +906,17 @@ export class HaMediaPlayerBrowse extends LitElement { transition: height 0.5s, margin 0.5s; } + .not-shown { + font-style: italic; + color: var(--secondary-text-color); + } + + .grid.not-shown { + display: flex; + align-items: center; + text-align: center; + } + /* ============= CHILDREN ============= */ mwc-list { diff --git a/src/data/media-player.ts b/src/data/media-player.ts index 087b431479..14b1a19c80 100644 --- a/src/data/media-player.ts +++ b/src/data/media-player.ts @@ -168,11 +168,12 @@ export interface MediaPlayerItem { media_content_type: string; media_content_id: string; media_class: string; - children_media_class: string; + children_media_class?: string; can_play: boolean; can_expand: boolean; thumbnail?: string; children?: MediaPlayerItem[]; + not_shown?: number; } export const browseMediaPlayer = ( diff --git a/src/translations/en.json b/src/translations/en.json index c538c0fc9a..250b37799d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -533,6 +533,7 @@ "play-media": "Play Media", "pick-media": "Pick Media", "no_items": "No items", + "not_shown": "{count} incompatible {count, plural,\n one {item}\n other {items}\n} hidden", "choose_player": "Choose Player", "media-player-browser": "Media", "web-browser": "Web Browser", From aa988c758d7280d28caf08ad698f4250373610b0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 23 Feb 2022 11:34:06 +0100 Subject: [PATCH 154/174] Put volume slider in the middle of the button (#11788) --- src/components/ha-button-menu.ts | 11 ++++++++++- src/panels/media-browser/ha-bar-media-player.ts | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/ha-button-menu.ts b/src/components/ha-button-menu.ts index 4154480615..bf0d1d3487 100644 --- a/src/components/ha-button-menu.ts +++ b/src/components/ha-button-menu.ts @@ -1,5 +1,5 @@ import "@material/mwc-menu"; -import type { Corner, Menu } from "@material/mwc-menu"; +import type { Corner, Menu, MenuCorner } from "@material/mwc-menu"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query } from "lit/decorators"; @@ -7,6 +7,12 @@ import { customElement, property, query } from "lit/decorators"; export class HaButtonMenu extends LitElement { @property() public corner: Corner = "TOP_START"; + @property() public menuCorner: MenuCorner = "START"; + + @property({ type: Number }) public x?: number; + + @property({ type: Number }) public y?: number; + @property({ type: Boolean }) public multi = false; @property({ type: Boolean }) public activatable = false; @@ -32,9 +38,12 @@ export class HaButtonMenu extends LitElement {
    diff --git a/src/panels/media-browser/ha-bar-media-player.ts b/src/panels/media-browser/ha-bar-media-player.ts index 5f0a75c611..1bfb6b2a27 100644 --- a/src/panels/media-browser/ha-bar-media-player.ts +++ b/src/panels/media-browser/ha-bar-media-player.ts @@ -266,7 +266,7 @@ export class BarMediaPlayer extends LitElement { ${ stateObj && supportsFeature(stateObj, SUPPORT_VOLUME_SET) ? html` - + Date: Wed, 23 Feb 2022 03:43:49 -0800 Subject: [PATCH 155/174] Add media management dialog (#11787) Co-authored-by: Bram Kragten --- package.json | 1 + .../media-player/dialog-media-manage.ts | 337 ++++++++++++++++++ .../dialog-media-player-browse.ts | 24 +- .../media-player/ha-media-manage-button.ts | 69 ++++ .../media-player/ha-media-player-browse.ts | 5 + .../media-player/ha-media-upload-button.ts | 129 +++++++ .../media-player/show-media-manage-dialog.ts | 18 + src/data/media_source.ts | 9 + src/dialogs/generic/dialog-box.ts | 24 +- .../media-browser/ha-panel-media-browser.ts | 107 +----- src/panels/profile/ha-panel-profile.ts | 10 +- src/translations/en.json | 9 +- yarn.lock | 10 + 13 files changed, 638 insertions(+), 114 deletions(-) create mode 100644 src/components/media-player/dialog-media-manage.ts create mode 100644 src/components/media-player/ha-media-manage-button.ts create mode 100644 src/components/media-player/ha-media-upload-button.ts create mode 100644 src/components/media-player/show-media-manage-dialog.ts diff --git a/package.json b/package.json index 5067141225..5402e5d93d 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@fullcalendar/daygrid": "5.9.0", "@fullcalendar/interaction": "5.9.0", "@fullcalendar/list": "5.9.0", + "@lit-labs/motion": "^1.0.2", "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch", "@material/chips": "14.0.0-canary.261f2db59.0", "@material/data-table": "14.0.0-canary.261f2db59.0", diff --git a/src/components/media-player/dialog-media-manage.ts b/src/components/media-player/dialog-media-manage.ts new file mode 100644 index 0000000000..1bd20ca667 --- /dev/null +++ b/src/components/media-player/dialog-media-manage.ts @@ -0,0 +1,337 @@ +import { animate } from "@lit-labs/motion"; +import "@material/mwc-list/mwc-check-list-item"; +import "@material/mwc-list/mwc-list-item"; +import "@material/mwc-list/mwc-list"; +import { repeat } from "lit/directives/repeat"; +import { mdiClose, mdiDelete } from "@mdi/js"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../common/dom/fire_event"; +import { computeRTLDirection } from "../../common/util/compute_rtl"; +import { haStyleDialog } from "../../resources/styles"; +import type { HomeAssistant } from "../../types"; +import "../ha-header-bar"; +import "../ha-dialog"; +import "../ha-svg-icon"; +import "../ha-circular-progress"; +import "./ha-media-player-browse"; +import "./ha-media-upload-button"; +import type { MediaManageDialogParams } from "./show-media-manage-dialog"; +import { + MediaClassBrowserSettings, + MediaPlayerItem, +} from "../../data/media-player"; +import { + browseLocalMediaPlayer, + removeLocalMedia, +} from "../../data/media_source"; +import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box"; + +@customElement("dialog-media-manage") +class DialogMediaManage extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _currentItem?: MediaPlayerItem; + + @state() private _params?: MediaManageDialogParams; + + @state() private _uploading = false; + + @state() private _deleting = false; + + @state() private _selected = new Set(); + + private _filesChanged = false; + + public showDialog(params: MediaManageDialogParams): void { + this._params = params; + this._refreshMedia(); + } + + public closeDialog() { + if (this._filesChanged && this._params!.onClose) { + this._params!.onClose(); + } + this._params = undefined; + this._currentItem = undefined; + this._uploading = false; + this._deleting = false; + this._filesChanged = false; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult { + if (!this._params) { + return html``; + } + + const children = + this._currentItem?.children?.filter((child) => !child.can_expand) || []; + + let fileIndex = 0; + + return html` + + + ${this._selected.size === 0 + ? html` + + ${this.hass.localize( + "ui.components.media-browser.file_management.title" + )} + + + + ${this._uploading + ? "" + : html` + + `} + ` + : html` + + + + + ${this._deleting + ? "" + : html` + + + + `} + `} + + ${!this._currentItem + ? html` +
    + +
    + ` + : !children.length + ? html`
    +

    + ${this.hass.localize( + "ui.components.media-browser.file_management.no_items" + )} +

    + ${this._currentItem?.children?.length + ? html`${this.hass.localize( + "ui.components.media-browser.file_management.folders_not_supported" + )}` + : ""} +
    ` + : html` + + ${repeat( + children, + (item) => item.media_content_id, + (item) => { + const icon = html` + + `; + return html` + + ${icon} ${item.title} + + `; + } + )} + + `} +
    + `; + } + + private _handleSelected(ev) { + this._selected = ev.detail.index; + } + + private _startUploading() { + this._uploading = true; + this._filesChanged = true; + } + + private _doneUploading() { + this._uploading = false; + this._refreshMedia(); + } + + private _handleDeselectAll() { + if (this._selected.size) { + this._selected = new Set(); + } + } + + private async _handleDelete() { + if ( + !(await showConfirmationDialog(this, { + text: this.hass.localize( + "ui.components.media-browser.file_management.confirm_delete", + { count: this._selected.size } + ), + warning: true, + })) + ) { + return; + } + this._filesChanged = true; + this._deleting = true; + + const toDelete: MediaPlayerItem[] = []; + let fileIndex = 0; + this._currentItem!.children!.forEach((item) => { + if (item.can_expand) { + return; + } + if (this._selected.has(fileIndex++)) { + toDelete.push(item); + } + }); + + try { + await Promise.all( + toDelete.map(async (item) => { + await removeLocalMedia(this.hass, item.media_content_id); + this._currentItem = { + ...this._currentItem!, + children: this._currentItem!.children!.filter((i) => i !== item), + }; + }) + ); + } finally { + this._deleting = false; + this._selected = new Set(); + } + } + + private async _refreshMedia() { + this._selected = new Set(); + this._currentItem = undefined; + this._currentItem = await browseLocalMediaPlayer( + this.hass, + this._params!.currentItem.media_content_id + ); + } + + static get styles(): CSSResultGroup { + return [ + haStyleDialog, + css` + ha-dialog { + --dialog-z-index: 8; + --dialog-content-padding: 0; + } + + @media (min-width: 800px) { + ha-dialog { + --mdc-dialog-max-width: 800px; + --dialog-surface-position: fixed; + --dialog-surface-top: 40px; + --mdc-dialog-max-height: calc(100vh - 72px); + } + } + + ha-header-bar { + --mdc-theme-on-primary: var(--primary-text-color); + --mdc-theme-primary: var(--mdc-theme-surface); + flex-shrink: 0; + border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12)); + } + + ha-media-upload-button, + mwc-button { + --mdc-theme-primary: var(--mdc-theme-on-primary); + } + + .danger { + --mdc-theme-primary: var(--error-color); + } + + ha-svg-icon[slot="icon"] { + vertical-align: middle; + } + + .refresh { + display: flex; + height: 200px; + justify-content: center; + align-items: center; + } + + .no-items { + text-align: center; + padding: 16px; + } + .folders { + color: var(--secondary-text-color); + font-style: italic; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-media-manage": DialogMediaManage; + } +} diff --git a/src/components/media-player/dialog-media-player-browse.ts b/src/components/media-player/dialog-media-player-browse.ts index 7287d7aba2..01f7efbbd8 100644 --- a/src/components/media-player/dialog-media-player-browse.ts +++ b/src/components/media-player/dialog-media-player-browse.ts @@ -1,7 +1,7 @@ import "../ha-header-bar"; import { mdiArrowLeft, mdiClose } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event"; import { computeRTLDirection } from "../../common/util/compute_rtl"; import type { @@ -13,7 +13,11 @@ import { haStyleDialog } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; import "../ha-dialog"; import "./ha-media-player-browse"; -import type { MediaPlayerItemId } from "./ha-media-player-browse"; +import "./ha-media-manage-button"; +import type { + HaMediaPlayerBrowse, + MediaPlayerItemId, +} from "./ha-media-player-browse"; import { MediaPlayerBrowseDialogParams } from "./show-media-browser-dialog"; @customElement("dialog-media-player-browse") @@ -26,6 +30,8 @@ class DialogMediaPlayerBrowse extends LitElement { @state() private _params?: MediaPlayerBrowseDialogParams; + @query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse; + public showDialog(params: MediaPlayerBrowseDialogParams): void { this._params = params; this._navigateIds = params.navigateIds || [ @@ -80,6 +86,12 @@ class DialogMediaPlayerBrowse extends LitElement { : this._currentItem.title} + + + + `; + } + + private _manage() { + showMediaManageDialog(this, { + currentItem: this.currentItem!, + onClose: () => fireEvent(this, "media-refresh"), + }); + } + + static styles = css` + mwc-button { + /* We use icon + text to show disabled state */ + --mdc-button-disabled-ink-color: --mdc-theme-primary; + } + + ha-svg-icon[slot="icon"], + ha-circular-progress[slot="icon"] { + vertical-align: middle; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-media-manage-button": MediaManageButton; + } +} diff --git a/src/components/media-player/ha-media-player-browse.ts b/src/components/media-player/ha-media-player-browse.ts index 1895c78608..882673175e 100644 --- a/src/components/media-player/ha-media-player-browse.ts +++ b/src/components/media-player/ha-media-player-browse.ts @@ -131,6 +131,11 @@ export class HaMediaPlayerBrowse extends LitElement { currentId.media_content_id, currentId.media_content_type ); + // Update the parent with latest item. + fireEvent(this, "media-browsed", { + ids: this.navigateIds, + current: this._currentItem, + }); } catch (err) { this._setError(err); } diff --git a/src/components/media-player/ha-media-upload-button.ts b/src/components/media-player/ha-media-upload-button.ts new file mode 100644 index 0000000000..65d7b36982 --- /dev/null +++ b/src/components/media-player/ha-media-upload-button.ts @@ -0,0 +1,129 @@ +import { mdiUpload } from "@mdi/js"; +import "@material/mwc-button"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../common/dom/fire_event"; +import { MediaPlayerItem } from "../../data/media-player"; +import "../ha-circular-progress"; +import "../ha-svg-icon"; +import { + isLocalMediaSourceContentId, + uploadLocalMedia, +} from "../../data/media_source"; +import type { HomeAssistant } from "../../types"; +import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; + +declare global { + interface HASSDomEvents { + uploading: unknown; + "media-refresh": unknown; + } +} + +@customElement("ha-media-upload-button") +class MediaUploadButton extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() currentItem?: MediaPlayerItem; + + @state() _uploading = 0; + + protected render(): TemplateResult { + if ( + !this.currentItem || + !isLocalMediaSourceContentId(this.currentItem.media_content_id || "") + ) { + return html``; + } + return html` + 0 + ? this.hass.localize( + "ui.components.media-browser.file_management.uploading", + { + count: this._uploading, + } + ) + : this.hass.localize( + "ui.components.media-browser.file_management.add_media" + )} + .disabled=${this._uploading > 0} + @click=${this._startUpload} + > + ${this._uploading > 0 + ? html` + + ` + : html` `} + + `; + } + + private async _startUpload() { + if (this._uploading > 0) { + return; + } + const input = document.createElement("input"); + input.type = "file"; + input.accept = "audio/*,video/*,image/*"; + input.multiple = true; + input.addEventListener( + "change", + async () => { + fireEvent(this, "uploading"); + const files = input.files!; + document.body.removeChild(input); + const target = this.currentItem!.media_content_id!; + + for (let i = 0; i < files.length; i++) { + this._uploading = files.length - i; + + try { + // eslint-disable-next-line no-await-in-loop + await uploadLocalMedia(this.hass, target, files[i]); + } catch (err: any) { + showAlertDialog(this, { + text: this.hass.localize( + "ui.components.media-browser.file_management.upload_failed", + { + reason: err.message || err, + } + ), + }); + break; + } + } + this._uploading = 0; + fireEvent(this, "media-refresh"); + }, + { once: true } + ); + // https://stackoverflow.com/questions/47664777/javascript-file-input-onchange-not-working-ios-safari-only + input.style.display = "none"; + document.body.append(input); + input.click(); + } + + static styles = css` + mwc-button { + /* We use icon + text to show disabled state */ + --mdc-button-disabled-ink-color: --mdc-theme-primary; + } + + ha-svg-icon[slot="icon"], + ha-circular-progress[slot="icon"] { + vertical-align: middle; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-media-upload-button": MediaUploadButton; + } +} diff --git a/src/components/media-player/show-media-manage-dialog.ts b/src/components/media-player/show-media-manage-dialog.ts new file mode 100644 index 0000000000..efb2c08c2d --- /dev/null +++ b/src/components/media-player/show-media-manage-dialog.ts @@ -0,0 +1,18 @@ +import { fireEvent } from "../../common/dom/fire_event"; +import { MediaPlayerItem } from "../../data/media-player"; + +export interface MediaManageDialogParams { + currentItem: MediaPlayerItem; + onClose?: () => void; +} + +export const showMediaManageDialog = ( + element: HTMLElement, + dialogParams: MediaManageDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-media-manage", + dialogImport: () => import("./dialog-media-manage"), + dialogParams, + }); +}; diff --git a/src/data/media_source.ts b/src/data/media_source.ts index dfcdbb37ab..61494ee3f8 100644 --- a/src/data/media_source.ts +++ b/src/data/media_source.ts @@ -49,3 +49,12 @@ export const uploadLocalMedia = async ( } return resp.json(); }; + +export const removeLocalMedia = async ( + hass: HomeAssistant, + media_content_id: string +) => + hass.callWS({ + type: "media_source/local_source/remove", + media_content_id, + }); diff --git a/src/dialogs/generic/dialog-box.ts b/src/dialogs/generic/dialog-box.ts index 2ca11e4ac5..df13ffaf4b 100644 --- a/src/dialogs/generic/dialog-box.ts +++ b/src/dialogs/generic/dialog-box.ts @@ -1,9 +1,10 @@ import "@material/mwc-button/mwc-button"; +import { mdiAlertOutline } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; -import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-dialog"; +import "../../components/ha-svg-icon"; import "../../components/ha-switch"; import "../../components/ha-textfield"; import { haStyleDialog } from "../../resources/styles"; @@ -50,20 +51,22 @@ class DialogBox extends LitElement { ?escapeKeyAction=${confirmPrompt} @closed=${this._dialogClosed} defaultAction="ignore" - .heading=${this._params.title + .heading=${html`${this._params.warning + ? html` ` + : ""}${this._params.title ? this._params.title : this._params.confirmation && - this.hass.localize("ui.dialogs.generic.default_confirmation_title")} + this.hass.localize( + "ui.dialogs.generic.default_confirmation_title" + )}`} >
    ${this._params.text ? html` -

    +

    ${this._params.text}

    ` @@ -172,9 +175,6 @@ class DialogBox extends LitElement { /* Place above other dialogs */ --dialog-z-index: 104; } - .warning { - color: var(--warning-color); - } `, ]; } diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 9bf6df9990..8d21a8192a 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -1,4 +1,4 @@ -import { mdiArrowLeft, mdiUpload } from "@mdi/js"; +import { mdiArrowLeft } from "@mdi/js"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@material/mwc-button"; @@ -15,10 +15,9 @@ import { LocalStorage } from "../../common/decorators/local-storage"; import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event"; import { navigate } from "../../common/navigate"; import "../../components/ha-menu-button"; -import "../../components/ha-circular-progress"; import "../../components/ha-icon-button"; -import "../../components/ha-svg-icon"; import "../../components/media-player/ha-media-player-browse"; +import "../../components/media-player/ha-media-manage-button"; import type { HaMediaPlayerBrowse, MediaPlayerItemId, @@ -28,11 +27,7 @@ import { MediaPickedEvent, MediaPlayerItem, } from "../../data/media-player"; -import { - isLocalMediaSourceContentId, - resolveMediaSource, - uploadLocalMedia, -} from "../../data/media_source"; +import { resolveMediaSource } from "../../data/media_source"; import "../../layouts/ha-app-layout"; import { haStyle } from "../../resources/styles"; import type { HomeAssistant, Route } from "../../types"; @@ -66,8 +61,6 @@ class PanelMediaBrowser extends LitElement { @state() _currentItem?: MediaPlayerItem; - @state() _uploading = 0; - private _navigateIds: MediaPlayerItemId[] = [ { media_content_id: undefined, @@ -107,43 +100,11 @@ class PanelMediaBrowser extends LitElement { ) : this._currentItem.title}
    - ${this._currentItem && - isLocalMediaSourceContentId( - this._currentItem.media_content_id || "" - ) - ? html` - 0 - ? this.hass.localize( - "ui.components.media-browser.file_management.uploading", - { - count: this._uploading, - } - ) - : this.hass.localize( - "ui.components.media-browser.file_management.add_media" - )} - .disabled=${this._uploading > 0} - @click=${this._startUpload} - > - ${this._uploading > 0 - ? html` - - ` - : html` - - `} - - ` - : ""} + 0) { - return; - } - const input = document.createElement("input"); - input.type = "file"; - input.accept = "audio/*,video/*,image/*"; - input.multiple = true; - input.addEventListener( - "change", - async () => { - const files = input.files!; - document.body.removeChild(input); - const target = this._currentItem!.media_content_id!; - - for (let i = 0; i < files.length; i++) { - this._uploading = files.length - i; - try { - // eslint-disable-next-line no-await-in-loop - await uploadLocalMedia(this.hass, target, files[i]); - } catch (err: any) { - showAlertDialog(this, { - text: this.hass.localize( - "ui.components.media-browser.file_management.upload_failed", - { - reason: err.message || err, - } - ), - }); - break; - } - } - this._uploading = 0; - await this._browser.refresh(); - }, - { once: true } - ); - // https://stackoverflow.com/questions/47664777/javascript-file-input-onchange-not-working-ios-safari-only - input.style.display = "none"; - document.body.append(input); - input.click(); + private _refreshMedia() { + this._browser.refresh(); } static get styles(): CSSResultGroup { return [ haStyle, css` - app-toolbar mwc-button { + app-toolbar { --mdc-theme-primary: var(--app-header-text-color); - /* We use icon + text to show disabled state */ - --mdc-button-disabled-ink-color: var(--app-header-text-color); } ha-media-player-browse { @@ -357,11 +277,6 @@ class PanelMediaBrowser extends LitElement { left: 0; right: 0; } - - ha-svg-icon[slot="icon"], - ha-circular-progress[slot="icon"] { - vertical-align: middle; - } `, ]; } diff --git a/src/panels/profile/ha-panel-profile.ts b/src/panels/profile/ha-panel-profile.ts index a1c7b66ad3..7ecc99b68f 100644 --- a/src/panels/profile/ha-panel-profile.ts +++ b/src/panels/profile/ha-panel-profile.ts @@ -3,7 +3,7 @@ import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { property, state } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-card"; import "../../components/ha-menu-button"; @@ -33,6 +33,7 @@ import "./ha-refresh-tokens-card"; import "./ha-set-suspend-row"; import "./ha-set-vibrate-row"; +@customElement("ha-panel-profile") class HaPanelProfile extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -252,5 +253,8 @@ class HaPanelProfile extends LitElement { ]; } } - -customElements.define("ha-panel-profile", HaPanelProfile); +declare global { + interface HTMLElementTagNameMap { + "ha-panel-profile": HaPanelProfile; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 250b37799d..54eda22ef1 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -549,10 +549,17 @@ "no_media_folder": "It looks like you have not yet created a media directory.", "setup_local_help": "Check the {documentation} on how to setup local media.", "file_management": { + "title": "Media Management", + "manage": "Manage", + "no_items": "No media items found", + "folders_not_supported": "Folders can not be managed via the UI.", "highlight_button": "Click here to upload your first media", "upload_failed": "Upload failed: {reason}", "add_media": "Add Media", - "uploading": "Uploading {count} {count, plural,\n one {file}\n other {files}\n}" + "uploading": "Uploading {count} {count, plural,\n one {file}\n other {files}\n}", + "confirm_delete": "Do you want to delete {count} {count, plural,\n one {file}\n other {files}\n}?", + "delete": "Delete {count}", + "deleting": "Deleting {count}" }, "class": { "album": "Album", diff --git a/yarn.lock b/yarn.lock index 469c9c90b1..e642cb2d89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1960,6 +1960,15 @@ __metadata: languageName: node linkType: hard +"@lit-labs/motion@npm:^1.0.2": + version: 1.0.2 + resolution: "@lit-labs/motion@npm:1.0.2" + dependencies: + lit: ^2.0.0 + checksum: 598e0be22a3f931ec971fa001e863c5a4dd82f572d8d0214211bde1d6403b00e3c8fafa92f30b0c02b7272bc12510ec40060bf2c8ab18151bdb264cf32f0ef71 + languageName: node + linkType: hard + "@lit-labs/virtualizer@0.7.0-pre.2": version: 0.7.0-pre.2 resolution: "@lit-labs/virtualizer@npm:0.7.0-pre.2" @@ -9131,6 +9140,7 @@ fsevents@^1.2.7: "@fullcalendar/interaction": 5.9.0 "@fullcalendar/list": 5.9.0 "@koa/cors": ^3.1.0 + "@lit-labs/motion": ^1.0.2 "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch" "@material/chips": 14.0.0-canary.261f2db59.0 "@material/data-table": 14.0.0-canary.261f2db59.0 From 64459a06c627ece8e4ede0a6cba82c6515d06c5f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 23 Feb 2022 14:01:44 +0100 Subject: [PATCH 156/174] Convert alarm control panel more info (#11791) * Convert alarm control panel more info * Update more-info-alarm_control_panel.ts * Update src/dialogs/more-info/controls/more-info-alarm_control_panel.ts * Apply suggestions from code review Co-authored-by: Zack Barett * import Co-authored-by: Zack Barett --- .../controls/more-info-alarm_control_panel.js | 281 ------------------ .../controls/more-info-alarm_control_panel.ts | 165 ++++++++++ 2 files changed, 165 insertions(+), 281 deletions(-) delete mode 100644 src/dialogs/more-info/controls/more-info-alarm_control_panel.js create mode 100644 src/dialogs/more-info/controls/more-info-alarm_control_panel.ts diff --git a/src/dialogs/more-info/controls/more-info-alarm_control_panel.js b/src/dialogs/more-info/controls/more-info-alarm_control_panel.js deleted file mode 100644 index 99be49908f..0000000000 --- a/src/dialogs/more-info/controls/more-info-alarm_control_panel.js +++ /dev/null @@ -1,281 +0,0 @@ -import "@material/mwc-button"; -import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "@polymer/paper-input/paper-input"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import { fireEvent } from "../../../common/dom/fire_event"; -import { FORMAT_NUMBER } from "../../../data/alarm_control_panel"; -import LocalizeMixin from "../../../mixins/localize-mixin"; - -class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - - - - -
    - - -
    - `; - } - - static get properties() { - return { - hass: Object, - stateObj: { - type: Object, - observer: "_stateObjChanged", - }, - _enteredCode: { - type: String, - value: "", - }, - _codeFormat: { - type: String, - value: "", - }, - _codeValid: { - type: Boolean, - computed: - "_validateCode(_enteredCode, _codeFormat, _armVisible, _codeArmRequired)", - }, - _disarmVisible: { - type: Boolean, - value: false, - }, - _armVisible: { - type: Boolean, - value: false, - }, - _inputEnabled: { - type: Boolean, - value: false, - }, - _inputMode: { - type: String, - computed: "_getInputMode(_codeFormat)", - }, - }; - } - - constructor() { - super(); - this._armedStates = [ - "armed_home", - "armed_away", - "armed_night", - "armed_custom_bypass", - ]; - } - - _stateObjChanged(newVal, oldVal) { - if (newVal) { - const state = newVal.state; - const props = { - _codeFormat: newVal.attributes.code_format, - _armVisible: state === "disarmed", - _codeArmRequired: newVal.attributes.code_arm_required, - _disarmVisible: - this._armedStates.includes(state) || - state === "pending" || - state === "triggered" || - state === "arming", - }; - props._inputEnabled = props._disarmVisible || props._armVisible; - this.setProperties(props); - } - if (oldVal) { - setTimeout(() => { - fireEvent(this, "iron-resize"); - }, 500); - } - } - - _getInputMode(format) { - return this._isNumber(format) ? "numeric" : "text"; - } - - _isNumber(format) { - return format === FORMAT_NUMBER; - } - - _validateCode(code, format, armVisible, codeArmRequired) { - return !format || code.length > 0 || (armVisible && !codeArmRequired); - } - - _digitClicked(ev) { - this._enteredCode += ev.target.getAttribute("data-digit"); - } - - _clearEnteredCode() { - this._enteredCode = ""; - } - - _callService(ev) { - const service = ev.target.getAttribute("data-service"); - const data = { - entity_id: this.stateObj.entity_id, - code: this._enteredCode, - }; - this.hass.callService("alarm_control_panel", service, data).then(() => { - this._enteredCode = ""; - }); - } -} -customElements.define( - "more-info-alarm_control_panel", - MoreInfoAlarmControlPanel -); diff --git a/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts b/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts new file mode 100644 index 0000000000..1efb0094f7 --- /dev/null +++ b/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts @@ -0,0 +1,165 @@ +import "@material/mwc-button"; +import type { HassEntity } from "home-assistant-js-websocket"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, query } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import "../../../components/ha-textfield"; +import type { HaTextField } from "../../../components/ha-textfield"; +import { + callAlarmAction, + FORMAT_NUMBER, +} from "../../../data/alarm_control_panel"; +import type { HomeAssistant } from "../../../types"; + +const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"]; +const ARM_ACTIONS = ["arm_away", "arm_home"]; +const DISARM_ACTIONS = ["disarm"]; + +@customElement("more-info-alarm_control_panel") +export class MoreInfoAlarmControlPanel extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj?: HassEntity; + + @query("#alarmCode") private _input?: HaTextField; + + protected render(): TemplateResult { + if (!this.hass || !this.stateObj) { + return html``; + } + + return html` + ${!this.stateObj.attributes.code_format + ? "" + : html` +
    + +
    + `} + ${this.stateObj.attributes.code_format !== FORMAT_NUMBER + ? "" + : html` +
    + ${BUTTONS.map((value) => + value === "" + ? html`` + : html` + + ${value === "clear" + ? this.hass!.localize( + `ui.card.alarm_control_panel.clear_code` + ) + : value} + + ` + )} +
    + `} +
    + ${(this.stateObj.state === "disarmed" + ? ARM_ACTIONS + : DISARM_ACTIONS + ).map( + (stateAction) => html` + + ${this.hass!.localize( + `ui.card.alarm_control_panel.${stateAction}` + )} + + ` + )} +
    + `; + } + + private _handlePadClick(e: MouseEvent): void { + const val = (e.currentTarget! as any).value; + this._input!.value = val === "clear" ? "" : this._input!.value + val; + } + + private _handleActionClick(e: MouseEvent): void { + const input = this._input; + callAlarmAction( + this.hass!, + this.stateObj!.entity_id, + (e.currentTarget! as any).action, + input?.value || undefined + ); + if (input) { + input.value = ""; + } + } + + static styles = css` + ha-textfield { + display: block; + margin: 8px; + max-width: 150px; + text-align: center; + } + + #keypad { + display: flex; + justify-content: center; + flex-wrap: wrap; + margin: auto; + width: 100%; + max-width: 300px; + } + + #keypad mwc-button { + padding: 8px; + width: 30%; + box-sizing: border-box; + } + + .actions { + margin: 0; + display: flex; + flex-wrap: wrap; + justify-content: center; + } + + .actions mwc-button { + margin: 0 4px 4px; + } + + mwc-button#disarm { + color: var(--error-color); + } + + mwc-button.numberkey { + --mdc-typography-button-font-size: var(--keypad-font-size, 0.875rem); + } + + .center { + display: flex; + justify-content: center; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "more-info-alarm_control_panel": MoreInfoAlarmControlPanel; + } +} From 6be6755f6f5f8c159666892909967ac1066e5fe2 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 23 Feb 2022 14:02:19 +0100 Subject: [PATCH 157/174] Migrate more-info configurator (#11792) * Migrate more-info configurator * Update more-info-configurator.ts * Update src/dialogs/more-info/controls/more-info-configurator.ts * Update src/dialogs/more-info/controls/more-info-configurator.ts Co-authored-by: Zack Barett * Import Co-authored-by: Zack Barett --- .../controls/more-info-configurator.js | 148 ------------------ .../controls/more-info-configurator.ts | 128 +++++++++++++++ 2 files changed, 128 insertions(+), 148 deletions(-) delete mode 100644 src/dialogs/more-info/controls/more-info-configurator.js create mode 100644 src/dialogs/more-info/controls/more-info-configurator.ts diff --git a/src/dialogs/more-info/controls/more-info-configurator.js b/src/dialogs/more-info/controls/more-info-configurator.js deleted file mode 100644 index 52f7ce6406..0000000000 --- a/src/dialogs/more-info/controls/more-info-configurator.js +++ /dev/null @@ -1,148 +0,0 @@ -import "@material/mwc-button"; -import "@polymer/iron-flex-layout/iron-flex-layout-classes"; -import "@polymer/paper-input/paper-input"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../components/ha-circular-progress"; -import "../../../components/ha-markdown"; - -class MoreInfoConfigurator extends PolymerElement { - static get template() { - return html` - - - -
    - -
    - `; - } - - static get properties() { - return { - stateObj: { - type: Object, - }, - - action: { - type: String, - value: "display", - }, - - isConfigurable: { - type: Boolean, - computed: "computeIsConfigurable(stateObj)", - }, - - isConfiguring: { - type: Boolean, - value: false, - }, - - fieldInput: { - type: Object, - value: function () { - return {}; - }, - }, - }; - } - - computeIsConfigurable(stateObj) { - return stateObj.state === "configure"; - } - - fieldChanged(ev) { - const el = ev.target; - this.fieldInput[el.name] = el.value; - } - - submitClicked() { - const data = { - configure_id: this.stateObj.attributes.configure_id, - fields: this.fieldInput, - }; - - this.isConfiguring = true; - - this.hass.callService("configurator", "configure", data).then( - () => { - this.isConfiguring = false; - }, - () => { - this.isConfiguring = false; - } - ); - } -} - -customElements.define("more-info-configurator", MoreInfoConfigurator); diff --git a/src/dialogs/more-info/controls/more-info-configurator.ts b/src/dialogs/more-info/controls/more-info-configurator.ts new file mode 100644 index 0000000000..f6f77f451b --- /dev/null +++ b/src/dialogs/more-info/controls/more-info-configurator.ts @@ -0,0 +1,128 @@ +import "@material/mwc-button"; +import type { HassEntity } from "home-assistant-js-websocket"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../../components/ha-alert"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-markdown"; +import "../../../components/ha-textfield"; +import type { HomeAssistant } from "../../../types"; + +@customElement("more-info-configurator") +export class MoreInfoConfigurator extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj?: HassEntity; + + @state() private _isConfiguring = false; + + private _fieldInput = {}; + + protected render(): TemplateResult { + if (this.stateObj?.state !== "configure") { + return html``; + } + + return html` +
    + + + ${this.stateObj.attributes.errors + ? html` + ${this.stateObj.attributes.errors} + ` + : ""} + ${this.stateObj.attributes.fields.map( + (field) => html`` + )} + ${this.stateObj.attributes.submit_caption + ? html`

    + + ${this._isConfiguring + ? html`` + : ""} + ${this.stateObj.attributes.submit_caption} + +

    ` + : ""} +
    + `; + } + + private _fieldChanged(ev) { + const el = ev.target; + this._fieldInput[el.name] = el.value; + } + + private _submitClicked() { + const data = { + configure_id: this.stateObj!.attributes.configure_id, + fields: this._fieldInput, + }; + + this._isConfiguring = true; + + this.hass.callService("configurator", "configure", data).then( + () => { + this._isConfiguring = false; + }, + () => { + this._isConfiguring = false; + } + ); + } + + static styles = css` + .container { + display: flex; + flex-direction: column; + } + p { + margin: 8px 0; + } + + a { + color: var(--primary-color); + } + + p > img { + max-width: 100%; + } + + p.center { + text-align: center; + } + + p.submit { + text-align: center; + height: 41px; + } + + ha-circular-progress { + width: 14px; + height: 14px; + margin-right: 20px; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "more-info-configurator": MoreInfoConfigurator; + } +} From 7f8ecf57d72d20b3e1fd01a0da6cc2136a5dabb8 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 23 Feb 2022 14:09:13 +0100 Subject: [PATCH 158/174] Convert more info lock (#11794) --- .../more-info/controls/more-info-lock.js | 80 ------------------- .../more-info/controls/more-info-lock.ts | 70 ++++++++++++++++ 2 files changed, 70 insertions(+), 80 deletions(-) delete mode 100644 src/dialogs/more-info/controls/more-info-lock.js create mode 100644 src/dialogs/more-info/controls/more-info-lock.ts diff --git a/src/dialogs/more-info/controls/more-info-lock.js b/src/dialogs/more-info/controls/more-info-lock.js deleted file mode 100644 index 346e1a8625..0000000000 --- a/src/dialogs/more-info/controls/more-info-lock.js +++ /dev/null @@ -1,80 +0,0 @@ -import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import "../../../components/ha-attributes"; -import LocalizeMixin from "../../../mixins/localize-mixin"; - -/* - * @appliesMixin LocalizeMixin - */ -class MoreInfoLock extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - - - - `; - } - - static get properties() { - return { - hass: Object, - stateObj: { - type: Object, - observer: "stateObjChanged", - }, - enteredCode: { - type: String, - value: "", - }, - isLocked: Boolean, - }; - } - - stateObjChanged(newVal) { - if (newVal) { - this.isLocked = newVal.state === "locked"; - } - } - - callService(ev) { - const service = ev.target.getAttribute("data-service"); - const data = { - entity_id: this.stateObj.entity_id, - code: this.enteredCode, - }; - this.hass.callService("lock", service, data); - } -} - -customElements.define("more-info-lock", MoreInfoLock); diff --git a/src/dialogs/more-info/controls/more-info-lock.ts b/src/dialogs/more-info/controls/more-info-lock.ts new file mode 100644 index 0000000000..0c380d267f --- /dev/null +++ b/src/dialogs/more-info/controls/more-info-lock.ts @@ -0,0 +1,70 @@ +import "@material/mwc-button"; +import type { HassEntity } from "home-assistant-js-websocket"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, query } from "lit/decorators"; +import "../../../components/ha-attributes"; +import "../../../components/ha-textfield"; +import type { HaTextField } from "../../../components/ha-textfield"; +import type { HomeAssistant } from "../../../types"; + +@customElement("more-info-lock") +class MoreInfoLock extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public stateObj?: HassEntity; + + @query("ha-textfield") private _textfield?: HaTextField; + + protected render(): TemplateResult { + if (!this.hass || !this.stateObj) { + return html``; + } + return html` + ${this.stateObj.attributes.code_format + ? html` + + ${this.stateObj.state === "locked" + ? html`${this.hass.localize("ui.card.lock.unlock")}` + : html`${this.hass.localize("ui.card.lock.lock")}`} + ` + : ""} + + `; + } + + private _callService(ev) { + const service = ev.target.getAttribute("data-service"); + const data = { + entity_id: this.stateObj!.entity_id, + code: this._textfield?.value, + }; + this.hass.callService("lock", service, data); + } + + static styles = css` + :host { + display: flex; + align-items: center; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "more-info-lock": MoreInfoLock; + } +} From aa9ff010302fe8375176cd4150f2fc6f3f06488b Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 23 Feb 2022 07:32:50 -0600 Subject: [PATCH 159/174] Add Margin to Tip (#11790) Co-authored-by: Bram Kragten --- src/panels/config/dashboard/ha-config-dashboard.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 9d1d77cfd7..707b707232 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -279,6 +279,7 @@ class HaConfigDashboard extends LitElement { .tips { text-align: center; + margin-bottom: max(env(safe-area-inset-bottom), 8px); } .tips .text { From 6e2e80a297fdbc972e3b701bc0f962d8eb7bc44e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 23 Feb 2022 14:52:52 +0100 Subject: [PATCH 160/174] Dont render double label on number selector (#11796) --- .../ha-selector/ha-selector-number.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/components/ha-selector/ha-selector-number.ts b/src/components/ha-selector/ha-selector-number.ts index 75d050e03c..4d15be84d2 100644 --- a/src/components/ha-selector/ha-selector-number.ts +++ b/src/components/ha-selector/ha-selector-number.ts @@ -22,19 +22,18 @@ export class HaNumberSelector extends LitElement { @property({ type: Boolean }) public disabled = false; protected render() { - return html`${this.label} - ${this.selector.number.mode !== "box" - ? html` - ` + return html`${this.selector.number.mode !== "box" + ? html`${this.label} + ` : ""} Date: Wed, 23 Feb 2022 15:24:00 +0100 Subject: [PATCH 161/174] Input conversion in dev tools (#11795) --- README.md | 2 +- src/dialogs/generic/dialog-box.ts | 2 +- .../event/developer-tools-event.js | 17 +++-- .../event/event-subscribe-card.ts | 18 +++--- .../state/developer-tools-state.js | 63 ++++++++++++++----- .../editor/card-editor/hui-card-preview.ts | 1 - .../hui-conditional-card-editor.ts | 2 +- src/panels/mailbox/ha-panel-mailbox.js | 1 - .../ha-long-lived-access-token-dialog.ts | 10 ++- 9 files changed, 80 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 62b0343f95..685dddba09 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This is the repository for the official [Home Assistant](https://home-assistant.io) frontend. -[![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/home-assistant-polymer/master/docs/screenshot.png)](https://demo.home-assistant.io/) +[![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/frontend/master/docs/screenshot.png)](https://demo.home-assistant.io/) - [View demo of Home Assistant](https://demo.home-assistant.io/) - [More information about Home Assistant](https://home-assistant.io) diff --git a/src/dialogs/generic/dialog-box.ts b/src/dialogs/generic/dialog-box.ts index df13ffaf4b..d57508e3cc 100644 --- a/src/dialogs/generic/dialog-box.ts +++ b/src/dialogs/generic/dialog-box.ts @@ -75,7 +75,7 @@ class DialogBox extends LitElement { ? html`

    - + value="[[eventType]]" + on-change="eventTypeChanged" + >

    [[localize( 'ui.panel.developer-tools.tabs.events.data' )]]

    @@ -150,6 +155,10 @@ class HaPanelDevEvent extends EventsMixin(LocalizeMixin(PolymerElement)) { this.eventType = ev.detail.eventType; } + eventTypeChanged(ev) { + this.eventType = ev.target.value; + } + _computeParsedEventData(eventData) { try { return eventData.trim() ? load(eventData) : {}; diff --git a/src/panels/developer-tools/event/event-subscribe-card.ts b/src/panels/developer-tools/event/event-subscribe-card.ts index cfa8a67be6..6e8d889f6a 100644 --- a/src/panels/developer-tools/event/event-subscribe-card.ts +++ b/src/panels/developer-tools/event/event-subscribe-card.ts @@ -1,11 +1,10 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; import { HassEvent } from "home-assistant-js-websocket"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { formatTime } from "../../../common/datetime/format_time"; import "../../../components/ha-card"; -import { PolymerChangedEvent } from "../../../polymer-types"; +import "../../../components/ha-textfield"; import { HomeAssistant } from "../../../types"; @customElement("event-subscribe-card") @@ -39,7 +38,7 @@ class EventSubscribeCard extends LitElement { )} >
    - + @input=${this._valueChanged} + > ): void { - this._eventType = ev.detail.value; + private _valueChanged(ev): void { + this._eventType = ev.target.value; } private async _handleSubmit(): Promise { @@ -116,9 +115,8 @@ class EventSubscribeCard extends LitElement { display: block; padding: 0 0 16px 16px; } - paper-input { - display: inline-block; - width: 200px; + ha-textfield { + width: 300px; } mwc-button { vertical-align: middle; diff --git a/src/panels/developer-tools/state/developer-tools-state.js b/src/panels/developer-tools/state/developer-tools-state.js index e90e0365ce..8196bbe7eb 100644 --- a/src/panels/developer-tools/state/developer-tools-state.js +++ b/src/panels/developer-tools/state/developer-tools-state.js @@ -5,7 +5,6 @@ import { mdiInformationOutline, mdiRefresh, } from "@mdi/js"; -import "@polymer/paper-input/paper-input"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ import { PolymerElement } from "@polymer/polymer/polymer-element"; @@ -42,6 +41,14 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) { padding: 16px; } + ha-textfield { + display: block; + } + + .state-input { + margin-top: 16px; + } + ha-expansion-panel { margin: 0 8px 16px; } @@ -74,6 +81,14 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) { ); } + .filters th { + padding: 0; + } + + .filters ha-textfield { + --mdc-text-field-fill-color: transparent; + } + th.attributes { position: relative; } @@ -164,16 +179,17 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) { on-change="entityIdChanged" allow-custom-entity > - + >

    [[localize('ui.panel.developer-tools.tabs.states.state_attributes')]]

    @@ -234,27 +250,30 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) { > - + - + value="[[_entityFilter]]" + on-input="_entityFilterChanged" + > - + value="[[_stateFilter]]" + on-input="_stateFilterChanged" + > - + value="[[_attributeFilter]]" + on-input="_attributeFilterChanged" + > @@ -416,6 +435,22 @@ class HaPanelDevState extends EventsMixin(LocalizeMixin(PolymerElement)) { this._expanded = true; } + stateChanged(ev) { + this._state = ev.target.value; + } + + _entityFilterChanged(ev) { + this._entityFilter = ev.target.value; + } + + _stateFilterChanged(ev) { + this._stateFilter = ev.target.value; + } + + _attributeFilterChanged(ev) { + this._attributeFilter = ev.target.value; + } + _getHistoryURL(entityId, inputDate) { const date = new Date(inputDate); const hourBefore = addHours(date, -1).toISOString(); diff --git a/src/panels/lovelace/editor/card-editor/hui-card-preview.ts b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts index b489d4a898..8bcfb57cf3 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-preview.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts @@ -1,4 +1,3 @@ -import "@polymer/paper-input/paper-textarea"; import { PropertyValues, ReactiveElement } from "lit"; import { property } from "lit/decorators"; import { computeRTL } from "../../../../common/util/compute_rtl"; diff --git a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts index d30336a1e8..a62cf2bb7b 100644 --- a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts @@ -333,7 +333,7 @@ export class HuiConditionalCardEditor .condition .state mwc-select { margin-right: 16px; } - .condition .state paper-input { + .condition .state ha-textfield { flex-grow: 1; } diff --git a/src/panels/mailbox/ha-panel-mailbox.js b/src/panels/mailbox/ha-panel-mailbox.js index 0cdc7f7799..7cb4af0b20 100644 --- a/src/panels/mailbox/ha-panel-mailbox.js +++ b/src/panels/mailbox/ha-panel-mailbox.js @@ -1,7 +1,6 @@ import "@material/mwc-button"; import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; -import "@polymer/paper-input/paper-textarea"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-tabs/paper-tab"; diff --git a/src/panels/profile/ha-long-lived-access-token-dialog.ts b/src/panels/profile/ha-long-lived-access-token-dialog.ts index edc28e64c6..5786a783a4 100644 --- a/src/panels/profile/ha-long-lived-access-token-dialog.ts +++ b/src/panels/profile/ha-long-lived-access-token-dialog.ts @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; @@ -7,6 +6,7 @@ import { createCloseHeading } from "../../components/ha-dialog"; import { haStyleDialog } from "../../resources/styles"; import type { HomeAssistant } from "../../types"; import { LongLivedAccessTokenDialogParams } from "./show-long-lived-access-token-dialog"; +import "../../components/ha-textfield"; const QR_LOGO_URL = "/static/icons/favicon-192x192.png"; @@ -41,14 +41,15 @@ export class HaLongLivedAccessTokenDialog extends LitElement { @closed=${this.closeDialog} >
    - + readOnly + >
    ${this._qrCode ? this._qrCode @@ -94,6 +95,9 @@ export class HaLongLivedAccessTokenDialog extends LitElement { #qr { text-align: center; } + ha-textfield { + display: block; + } `, ]; } From e6dbbc31a854ae0e2d6cde89b5c1a2c203c09aa0 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 23 Feb 2022 08:35:58 -0600 Subject: [PATCH 162/174] Gauge Editor to Ha Form (#11793) --- .../ha-selector/ha-selector-entity.ts | 7 +- .../ha-selector/ha-selector-text.ts | 1 + src/data/selector.ts | 2 +- .../config-elements/hui-gauge-card-editor.ts | 378 ++++++------------ 4 files changed, 134 insertions(+), 254 deletions(-) diff --git a/src/components/ha-selector/ha-selector-entity.ts b/src/components/ha-selector/ha-selector-entity.ts index 2facd604ba..9159a5dd7b 100644 --- a/src/components/ha-selector/ha-selector-entity.ts +++ b/src/components/ha-selector/ha-selector-entity.ts @@ -50,7 +50,12 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) { private _filterEntities = (entity: HassEntity): boolean => { if (this.selector.entity?.domain) { - if (computeStateDomain(entity) !== this.selector.entity.domain) { + const filterDomain = this.selector.entity.domain; + const entityDomain = computeStateDomain(entity); + if ( + (Array.isArray(filterDomain) && !filterDomain.includes(entityDomain)) || + entityDomain !== filterDomain + ) { return false; } } diff --git a/src/components/ha-selector/ha-selector-text.ts b/src/components/ha-selector/ha-selector-text.ts index 6827de4ac6..2e6c879d9e 100644 --- a/src/components/ha-selector/ha-selector-text.ts +++ b/src/components/ha-selector/ha-selector-text.ts @@ -76,6 +76,7 @@ export class HaTextSelector extends LitElement { if (value === "" && !this.required) { value = undefined; } + fireEvent(this, "value-changed", { value }); } diff --git a/src/data/selector.ts b/src/data/selector.ts index b51c077b01..f3d1d39c71 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -20,7 +20,7 @@ export type Selector = export interface EntitySelector { entity: { integration?: string; - domain?: string; + domain?: string | string[]; device_class?: string; }; } diff --git a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts index 2656a291f2..a0e1694d3b 100644 --- a/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-gauge-card-editor.ts @@ -1,5 +1,5 @@ -import "@polymer/paper-input/paper-input"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import "../../../../components/ha-form/ha-form"; +import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, @@ -10,18 +10,13 @@ import { optional, string, } from "superstruct"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; -import "../../../../components/ha-formfield"; -import "../../../../components/ha-switch"; -import { HomeAssistant } from "../../../../types"; -import { GaugeCardConfig, SeverityConfig } from "../../cards/types"; -import "../../components/hui-entity-editor"; -import "../../components/hui-theme-select-editor"; -import { LovelaceCardEditor } from "../../types"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../types"; +import type { GaugeCardConfig } from "../../cards/types"; +import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import { EditorTarget, EntitiesEditorEvent } from "../types"; -import { configElementStyle } from "./config-elements-style"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -37,8 +32,6 @@ const cardConfigStruct = assign( }) ); -const includeDomains = ["counter", "input_number", "number", "sensor"]; - @customElement("hui-gauge-card-editor") export class HuiGaugeCardEditor extends LitElement @@ -53,264 +46,145 @@ export class HuiGaugeCardEditor this._config = config; } - get _name(): string { - return this._config!.name || ""; - } + private _schema = memoizeOne((showSeverity: boolean) => { + const schema = [ + { + name: "entity", + selector: { + entity: { + domain: ["counter", "input_number", "number", "sensor"], + }, + }, + }, + { + name: "", + type: "grid", + schema: [ + { name: "name", selector: { text: {} } }, + { name: "unit", selector: { text: {} } }, + ], + }, + { name: "theme", selector: { theme: {} } }, + { + name: "", + type: "grid", + schema: [ + { name: "min", selector: { number: { min: 1, mode: "box" } } }, + { name: "max", selector: { number: { min: 1, mode: "box" } } }, + ], + }, + { + name: "", + type: "grid", + schema: [ + { name: "needle", selector: { boolean: {} } }, + { name: "show_severity", selector: { boolean: {} } }, + ], + }, + ]; - get _entity(): string { - return this._config!.entity || ""; - } + if (showSeverity) { + schema.push({ + name: "", + type: "grid", + schema: [ + { name: "green", selector: { number: { min: 0, mode: "box" } } }, + { name: "yellow", selector: { number: { min: 0, mode: "box" } } }, + { name: "red", selector: { number: { min: 0, mode: "box" } } }, + ], + }); + } - get _unit(): string { - return this._config!.unit || ""; - } - - get _theme(): string { - return this._config!.theme || ""; - } - - get _min(): number { - return this._config!.min || 0; - } - - get _max(): number { - return this._config!.max || 100; - } - - get _severity(): SeverityConfig | undefined { - return this._config!.severity || undefined; - } + return schema; + }); protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; } + const schema = this._schema(this._config!.severity !== undefined); + const data = { + show_severity: this._config!.severity !== undefined, + ...this._config, + }; + return html` -
    - - - - - - - - - - - ${this._config!.severity !== undefined - ? html` - - - -
    - ` - : ""} -
    + `; } - static get styles(): CSSResultGroup { - return [ - configElementStyle, - css` - .severity { - display: none; - width: 100%; - padding-left: 16px; - flex-direction: row; - flex-wrap: wrap; - } - .severity > * { - flex: 1 0 30%; - padding-right: 4px; - } - ha-switch[checked] ~ .severity { - display: flex; - } - `, - ]; - } + private _valueChanged(ev: CustomEvent): void { + let config = ev.detail.value; - private _toggleNeedle(ev: EntitiesEditorEvent): void { - if (!this._config || !this.hass) { - return; - } - if ((ev.target as EditorTarget).checked) { - this._config = { - ...this._config, - needle: true, - }; - } else { - this._config = { ...this._config }; - delete this._config.needle; - } - fireEvent(this, "config-changed", { config: this._config }); - } - - private _toggleSeverity(ev: EntitiesEditorEvent): void { - if (!this._config || !this.hass) { - return; - } - - if ((ev.target as EditorTarget).checked) { - this._config = { - ...this._config, + if (config.show_severity) { + config = { + ...config, severity: { - green: 0, - yellow: 0, - red: 0, + green: config.green || config.severity?.green || 0, + yellow: config.yellow || config.severity?.yellow || 0, + red: config.red || config.severity?.red || 0, }, }; - } else { - this._config = { ...this._config }; - delete this._config.severity; + } else if (!config.show_severity && config.severity) { + delete config.severity; } - fireEvent(this, "config-changed", { config: this._config }); + + delete config.show_severity; + delete config.green; + delete config.yellow; + delete config.red; + + fireEvent(this, "config-changed", { config }); } - private _severityChanged(ev: EntitiesEditorEvent): void { - if (!this._config || !this.hass) { - return; + private _computeLabelCallback = (schema: HaFormSchema) => { + switch (schema.name) { + case "name": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.name" + ); + case "entity": + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.entity" + )} (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.required" + )})`; + case "max": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.maximum" + ); + case "min": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.minimum" + ); + case "show_severity": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.gauge.severity.define" + ); + case "needle": + return this.hass!.localize( + "ui.panel.lovelace.editor.card.gauge.needle_gauge" + ); } - const target = ev.target! as EditorTarget; - const severity = { - ...this._config.severity, - [target.configValue!]: Number(target.value), - }; - this._config = { - ...this._config, - severity, - }; - fireEvent(this, "config-changed", { config: this._config }); - } - - private _valueChanged(ev: EntitiesEditorEvent): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target! as EditorTarget; - - if (target.configValue) { - if ( - target.value === "" || - (target.type === "number" && isNaN(Number(target.value))) - ) { - this._config = { ...this._config }; - delete this._config[target.configValue!]; - } else { - let value: any = target.value; - if (target.type === "number") { - value = Number(value); - } - this._config = { ...this._config, [target.configValue!]: value }; - } - } - fireEvent(this, "config-changed", { config: this._config }); - } + return ( + this.hass!.localize( + `ui.panel.lovelace.editor.card.gauge.${schema.name}` + ) || + this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ) || + this.hass!.localize( + `ui.panel.lovelace.editor.card.gauge.severity.${schema.name}` + ) + ); + }; } declare global { From b341ee9d386253a6d04bc74a4f1e002008120bce Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 23 Feb 2022 16:13:09 +0100 Subject: [PATCH 163/174] Stop spinning when opening media in dialog (#11800) --- .../media-player/dialog-media-manage.ts | 28 +++++++++---------- .../media-browser/ha-panel-media-browser.ts | 1 + 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/components/media-player/dialog-media-manage.ts b/src/components/media-player/dialog-media-manage.ts index 1bd20ca667..daee21d017 100644 --- a/src/components/media-player/dialog-media-manage.ts +++ b/src/components/media-player/dialog-media-manage.ts @@ -1,22 +1,12 @@ import { animate } from "@lit-labs/motion"; -import "@material/mwc-list/mwc-check-list-item"; -import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list"; -import { repeat } from "lit/directives/repeat"; +import "@material/mwc-list/mwc-list-item"; import { mdiClose, mdiDelete } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { repeat } from "lit/directives/repeat"; import { fireEvent } from "../../common/dom/fire_event"; import { computeRTLDirection } from "../../common/util/compute_rtl"; -import { haStyleDialog } from "../../resources/styles"; -import type { HomeAssistant } from "../../types"; -import "../ha-header-bar"; -import "../ha-dialog"; -import "../ha-svg-icon"; -import "../ha-circular-progress"; -import "./ha-media-player-browse"; -import "./ha-media-upload-button"; -import type { MediaManageDialogParams } from "./show-media-manage-dialog"; import { MediaClassBrowserSettings, MediaPlayerItem, @@ -26,6 +16,16 @@ import { removeLocalMedia, } from "../../data/media_source"; import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box"; +import { haStyleDialog } from "../../resources/styles"; +import type { HomeAssistant } from "../../types"; +import "../ha-circular-progress"; +import "../ha-dialog"; +import "../ha-header-bar"; +import "../ha-svg-icon"; +import "../ha-check-list-item"; +import "./ha-media-player-browse"; +import "./ha-media-upload-button"; +import type { MediaManageDialogParams } from "./show-media-manage-dialog"; @customElement("dialog-media-manage") class DialogMediaManage extends LitElement { @@ -180,7 +180,7 @@ class DialogMediaManage extends LitElement { > `; return html` - ${icon} ${item.title} - + `; } )} diff --git a/src/panels/media-browser/ha-panel-media-browser.ts b/src/panels/media-browser/ha-panel-media-browser.ts index 8d21a8192a..af3339077e 100644 --- a/src/panels/media-browser/ha-panel-media-browser.ts +++ b/src/panels/media-browser/ha-panel-media-browser.ts @@ -241,6 +241,7 @@ class PanelMediaBrowser extends LitElement { title: item.title, can_play: item.can_play, }); + this._player.hideResolvingNewMediaPicked(); } private _playerPicked(ev) { From 5d8b3227f3620bf903f9f2bdfe7956c41f06596b Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 23 Feb 2022 10:18:56 -0600 Subject: [PATCH 164/174] Fix Entities picker (#11802) --- src/components/entity/ha-entities-picker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/entity/ha-entities-picker.ts b/src/components/entity/ha-entities-picker.ts index deac6335ca..35e21dc950 100644 --- a/src/components/entity/ha-entities-picker.ts +++ b/src/components/entity/ha-entities-picker.ts @@ -114,7 +114,7 @@ class HaEntitiesPickerLight extends LitElement { const newValue = event.detail.value; if ( newValue === curValue || - (newValue !== "" && !isValidEntityId(newValue)) + (newValue !== undefined && !isValidEntityId(newValue)) ) { return; } @@ -147,7 +147,7 @@ class HaEntitiesPickerLight extends LitElement { } static override styles = css` - ha-entity-picker { + div { margin-top: 8px; } `; From 430b47fc4abc5c17d46ca1472e090f1942e2de59 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 23 Feb 2022 17:27:03 +0100 Subject: [PATCH 165/174] Migrate single textfields (#11799) * Migrate single textfields * Update ha-config-name-form.ts * Update dialog-area-registry-detail.ts * Update manual-automation-editor.ts * Update manual-automation-editor.ts * required to number selector fix script * review --- .../ha-selector/ha-selector-number.ts | 12 +++++-- .../areas/dialog-area-registry-detail.ts | 25 ++++++-------- .../automation/manual-automation-editor.ts | 11 +++--- .../blueprint/dialog-import-blueprint.ts | 34 +++++++++++-------- src/panels/config/core/ha-config-name-form.ts | 21 ++++++------ .../dialog-device-registry-detail.ts | 22 ++++++------ .../config/person/dialog-person-detail.ts | 15 ++++---- src/panels/config/script/ha-script-editor.ts | 7 ++-- src/panels/config/tags/dialog-tag-detail.ts | 26 ++++++++------ src/panels/config/users/dialog-user-detail.ts | 13 +++---- 10 files changed, 103 insertions(+), 83 deletions(-) diff --git a/src/components/ha-selector/ha-selector-number.ts b/src/components/ha-selector/ha-selector-number.ts index 4d15be84d2..0094192f1b 100644 --- a/src/components/ha-selector/ha-selector-number.ts +++ b/src/components/ha-selector/ha-selector-number.ts @@ -19,6 +19,8 @@ export class HaNumberSelector extends LitElement { @property() public label?: string; + @property({ type: Boolean }) public required = true; + @property({ type: Boolean }) public disabled = false; protected render() { @@ -29,6 +31,7 @@ export class HaNumberSelector extends LitElement { .value=${this._value} .step=${this.selector.number.step ?? 1} .disabled=${this.disabled} + .required=${this.required} pin ignore-bar-touch @change=${this._handleSliderChange} @@ -43,9 +46,10 @@ export class HaNumberSelector extends LitElement { class=${classMap({ single: this.selector.number.mode === "box" })} .min=${this.selector.number.min} .max=${this.selector.number.max} - .value=${this.value} + .value=${this.value || ""} .step=${this.selector.number.step ?? 1} .disabled=${this.disabled} + .required=${this.required} .suffix=${this.selector.number.unit_of_measurement} type="number" autoValidate @@ -56,14 +60,16 @@ export class HaNumberSelector extends LitElement { } private get _value() { - return this.value ?? 0; + return this.value ?? (this.selector.number.min || 0); } private _handleInputChange(ev) { ev.stopPropagation(); const value = ev.target.value === "" || isNaN(ev.target.value) - ? undefined + ? this.required + ? this.selector.number.min || 0 + : undefined : Number(ev.target.value); if (this.value === value) { return; diff --git a/src/panels/config/areas/dialog-area-registry-detail.ts b/src/panels/config/areas/dialog-area-registry-detail.ts index 3fadb0ea58..fcf95fc6c1 100644 --- a/src/panels/config/areas/dialog-area-registry-detail.ts +++ b/src/panels/config/areas/dialog-area-registry-detail.ts @@ -1,10 +1,10 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property, state } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import { createCloseHeading } from "../../../components/ha-dialog"; import "../../../components/ha-alert"; +import "../../../components/ha-textfield"; import "../../../components/ha-picture-upload"; import type { HaPictureUpload } from "../../../components/ha-picture-upload"; import { AreaRegistryEntryMutableParams } from "../../../data/area_registry"; @@ -69,7 +69,7 @@ class DialogAreaDetail extends LitElement { >
    ${this._error - ? html` ${this._error} ` + ? html`${this._error}` : ""}
    ${entry @@ -83,17 +83,16 @@ class DialogAreaDetail extends LitElement { ` : ""} - + > ) { + private _nameChanged(ev) { this._error = undefined; - this._name = ev.detail.value; + this._name = ev.target.value; } private _pictureChanged(ev: PolymerChangedEvent) { @@ -188,6 +181,10 @@ class DialogAreaDetail extends LitElement { .form { padding-bottom: 24px; } + ha-textfield { + display: block; + margin-bottom: 16px; + } `, ]; } diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index b0df97fbfc..19a82decb9 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -99,7 +99,7 @@ export class HaManualAutomationEditor extends LitElement { .label=${this.hass.localize( "ui.panel.config.automation.editor.modes.label" )} - .value=${this.config.mode ? MODES.indexOf(this.config.mode) : 0} + .value=${this.config.mode} @selected=${this._modeChanged} fixedMenuPosition > @@ -115,7 +115,7 @@ export class HaManualAutomationEditor extends LitElement { ${this.config.mode && MODES_MAX.includes(this.config.mode) ? html` - ` @@ -357,8 +358,10 @@ export class HaManualAutomationEditor extends LitElement { ha-entity-toggle { margin-right: 8px; } - mwc-select { - margin-top: 8px; + mwc-select, + .max { + margin-top: 16px; + width: 200px; } `, ]; diff --git a/src/panels/config/blueprint/dialog-import-blueprint.ts b/src/panels/config/blueprint/dialog-import-blueprint.ts index ad2cb1eb6c..ef43378c16 100644 --- a/src/panels/config/blueprint/dialog-import-blueprint.ts +++ b/src/panels/config/blueprint/dialog-import-blueprint.ts @@ -1,13 +1,13 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; -import type { PaperInputElement } from "@polymer/paper-input/paper-input"; -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state, query } from "lit/decorators"; +import { css, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/ha-circular-progress"; import { createCloseHeading } from "../../../components/ha-dialog"; import "../../../components/ha-expansion-panel"; import "../../../components/ha-markdown"; +import "../../../components/ha-textfield"; +import type { HaTextField } from "../../../components/ha-textfield"; import { BlueprintImportResult, importBlueprint, @@ -32,7 +32,7 @@ class DialogImportBlueprint extends LitElement { @state() private _url?: string; - @query("#input") private _input?: PaperInputElement; + @query("#input") private _input?: HaTextField; public showDialog(params): void { this._params = params; @@ -90,13 +90,13 @@ class DialogImportBlueprint extends LitElement { ` : html` - + > `} ` - )}`} + >`}
    ${!this._result ? html` ` : ""} - + @changed=${this._handleChange} + >
    @@ -62,9 +60,9 @@ class ConfigNameForm extends LitElement { : this.hass.config.location_name; } - private _handleChange(ev: PolymerChangedEvent) { - const target = ev.currentTarget as PaperInputElement; - this[`_${target.name}`] = target.value; + private _handleChange(ev) { + const target = ev.currentTarget as HaTextField; + this._name = target.value; } private async _save() { @@ -85,6 +83,9 @@ class ConfigNameForm extends LitElement { .card-actions { text-align: right; } + ha-textfield { + display: block; + } `; } } diff --git a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts index 7a68ba8786..cb287db2a1 100644 --- a/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts +++ b/src/panels/config/devices/device-registry-detail/dialog-device-registry-detail.ts @@ -1,13 +1,12 @@ import "@material/mwc-button/mwc-button"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-area-picker"; import "../../../../components/ha-dialog"; import type { HaSwitch } from "../../../../components/ha-switch"; +import "../../../../components/ha-textfield"; import { computeDeviceName } from "../../../../data/device_registry"; -import { PolymerChangedEvent } from "../../../../polymer-types"; import { haStyle, haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail"; @@ -57,16 +56,18 @@ class DialogDeviceRegistryDetail extends LitElement { .heading=${computeDeviceName(device, this.hass)} >
    - ${this._error ? html`
    ${this._error}
    ` : ""} + ${this._error + ? html`${this._error} ` + : ""}
    - + > ): void { + private _nameChanged(ev): void { this._error = undefined; - this._nameByUser = ev.detail.value; + this._nameByUser = ev.target.value; } private _areaPicked(event: CustomEvent): void { @@ -174,8 +175,9 @@ class DialogDeviceRegistryDetail extends LitElement { mwc-button.warning { margin-right: auto; } - .error { - color: var(--error-color); + ha-textfield { + display: block; + margin-bottom: 16px; } ha-switch { margin-right: 16px; diff --git a/src/panels/config/person/dialog-person-detail.ts b/src/panels/config/person/dialog-person-detail.ts index 2724d8ef2e..185056404f 100644 --- a/src/panels/config/person/dialog-person-detail.ts +++ b/src/panels/config/person/dialog-person-detail.ts @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -7,6 +6,7 @@ import { computeRTLDirection } from "../../../common/util/compute_rtl"; import "../../../components/entity/ha-entities-picker"; import { createCloseHeading } from "../../../components/ha-dialog"; import "../../../components/ha-formfield"; +import "../../../components/ha-textfield"; import "../../../components/ha-picture-upload"; import type { HaPictureUpload } from "../../../components/ha-picture-upload"; import { adminChangePassword } from "../../../data/auth"; @@ -120,17 +120,17 @@ class DialogPersonDetail extends LitElement {
    ${this._error ? html`
    ${this._error}
    ` : ""}
    - + > ) { + private _nameChanged(ev) { this._error = undefined; - this._name = ev.detail.value; + this._name = ev.target.value; } private _adminChanged(ev): void { @@ -460,7 +460,8 @@ class DialogPersonDetail extends LitElement { .form { padding-bottom: 24px; } - ha-picture-upload { + ha-picture-upload, + ha-textfield { display: block; } ha-formfield { diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index c4ce09a6b0..2aeb262d47 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -136,10 +136,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { if (currentMode && MODES_MAX.includes(currentMode)) { schema.push({ name: "max", + required: true, selector: { - text: { - type: "number", - }, + number: { mode: "box", min: 1, max: Infinity }, }, }); } @@ -161,11 +160,11 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { const data = { mode: MODES[0], + icon: undefined, max: this._config.mode && MODES_MAX.includes(this._config.mode) ? 10 : undefined, - icon: undefined, ...this._config, id: this._entityId, }; diff --git a/src/panels/config/tags/dialog-tag-detail.ts b/src/panels/config/tags/dialog-tag-detail.ts index 02a6aa03fa..a084f22e77 100644 --- a/src/panels/config/tags/dialog-tag-detail.ts +++ b/src/panels/config/tags/dialog-tag-detail.ts @@ -1,11 +1,12 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/ha-alert"; import { createCloseHeading } from "../../../components/ha-dialog"; import "../../../components/ha-formfield"; import "../../../components/ha-switch"; +import "../../../components/ha-textfield"; import { Tag, UpdateTagParams } from "../../../data/tag"; import { HassDialog } from "../../../dialogs/make-dialog-manager"; import { haStyleDialog } from "../../../resources/styles"; @@ -71,7 +72,9 @@ class DialogTagDetail )} >
    - ${this._error ? html`
    ${this._error}
    ` : ""} + ${this._error + ? html`${this._error}` + : ""}
    ${this._params.entry ? html`${this.hass!.localize( @@ -79,30 +82,30 @@ class DialogTagDetail )}: ${this._params.entry.id}` : ""} - + > ${!this._params.entry - ? html` ` + >` : ""}
    ${this._params.entry @@ -165,11 +168,12 @@ class DialogTagDetail `; } - private _valueChanged(ev: CustomEvent) { - const configValue = (ev.target as any).configValue; + private _valueChanged(ev: Event) { + const target = ev.target as any; + const configValue = target.configValue; this._error = undefined; - this[`_${configValue}`] = ev.detail.value; + this[`_${configValue}`] = target.value; } private async _updateEntry() { diff --git a/src/panels/config/users/dialog-user-detail.ts b/src/panels/config/users/dialog-user-detail.ts index 731de2e065..9729262167 100644 --- a/src/panels/config/users/dialog-user-detail.ts +++ b/src/panels/config/users/dialog-user-detail.ts @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "@polymer/paper-input/paper-input"; import "@polymer/paper-tooltip/paper-tooltip"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -11,6 +10,7 @@ import "../../../components/ha-help-tooltip"; import "../../../components/ha-chip-set"; import "../../../components/ha-chip"; import "../../../components/ha-svg-icon"; +import "../../../components/ha-textfield"; import "../../../components/ha-switch"; import { adminChangePassword } from "../../../data/auth"; import { @@ -92,13 +92,13 @@ class DialogUserDetail extends LitElement { `}
    - + @input=${this._nameChanged} + .label=${this.hass!.localize("ui.panel.config.users.editor.name")} + >
    Date: Wed, 23 Feb 2022 17:27:23 +0100 Subject: [PATCH 166/174] change repository url and project description (#11801) --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5402e5d93d..6562576055 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "description": "A frontend for Home Assistant using the Polymer framework", + "description": "A frontend for Home Assistant", "repository": { "type": "git", - "url": "https://github.com/home-assistant/home-assistant-polymer" + "url": "https://github.com/home-assistant/frontend" }, "name": "home-assistant-frontend", "version": "1.0.0", From 8db22d4f884a4fc74b5b933d73bcbeb92c637128 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 23 Feb 2022 10:28:49 -0600 Subject: [PATCH 167/174] Calendar card to HA Form (#11784) --- .../hui-calendar-card-editor.ts | 176 +++++++----------- src/translations/en.json | 2 +- 2 files changed, 66 insertions(+), 112 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts index 2142f325f4..30c81af52c 100644 --- a/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-calendar-card-editor.ts @@ -1,7 +1,8 @@ -import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import "../../../../components/entity/ha-entities-picker"; +import "../../../../components/ha-form/ha-form"; +import { css, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { array, assert, @@ -13,16 +14,12 @@ import { union, } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { stopPropagation } from "../../../../common/dom/stop_propagation"; -import "../../../../components/entity/ha-entities-picker"; +import { LocalizeFunc } from "../../../../common/translations/localize"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import type { CalendarCardConfig } from "../../cards/types"; -import "../../components/hui-entity-editor"; -import "../../components/hui-theme-select-editor"; import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import type { EditorTarget, EntitiesEditorEvent } from "../types"; -import { configElementStyle } from "./config-elements-style"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -43,75 +40,54 @@ export class HuiCalendarCardEditor { @property({ attribute: false }) public hass?: HomeAssistant; - @property({ attribute: false }) private _config?: CalendarCardConfig; - - @state() private _configEntities?: string[]; + @state() private _config?: CalendarCardConfig; public setConfig(config: CalendarCardConfig): void { assert(config, cardConfigStruct); this._config = config; - this._configEntities = config.entities; } - get _title(): string { - return this._config!.title || ""; - } - - get _initial_view(): string { - return this._config!.initial_view || "dayGridMonth"; - } - - get _theme(): string { - return this._config!.theme || ""; - } + private _schema = memoizeOne((localize: LocalizeFunc) => [ + { + name: "", + type: "grid", + schema: [ + { name: "title", required: false, selector: { text: {} } }, + { + name: "initial_view", + required: false, + selector: { + select: { + options: views.map((view) => [ + view, + localize( + `ui.panel.lovelace.editor.card.calendar.views.${view}` + ), + ]), + }, + }, + }, + ], + }, + { name: "theme", required: false, selector: { theme: {} } }, + ]); protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; } + const schema = this._schema(this.hass.localize); + const data = { initial_view: "dayGridMonth", ...this._config }; + return html` -
    -
    - - - ${views.map( - (view) => html` - ${this.hass!.localize( - `ui.panel.lovelace.editor.card.calendar.views.${view}` - )} - - ` - )} - -
    - -
    +

    ${this.hass.localize( "ui.panel.lovelace.editor.card.calendar.calendar_entities" @@ -122,62 +98,40 @@ export class HuiCalendarCardEditor

    `; } - private _valueChanged(ev: EntitiesEditorEvent | CustomEvent): void { - if (!this._config || !this.hass) { - return; - } - - const target = ev.target! as EditorTarget; - - if (this[`_${target.configValue}`] === target.value) { - return; - } - - if (ev.detail && ev.detail.value && Array.isArray(ev.detail.value)) { - this._config = { ...this._config, entities: ev.detail.value }; - } else if (target.configValue) { - if (target.value === "") { - this._config = { ...this._config }; - delete this._config[target.configValue!]; - } else { - this._config = { - ...this._config, - [target.configValue]: target.value, - }; - } - } - - fireEvent(this, "config-changed", { config: this._config }); + private _valueChanged(ev: CustomEvent): void { + const config = ev.detail.value; + fireEvent(this, "config-changed", { config }); } - private _viewChanged(ev): void { - if (!this._config || !this.hass) { - return; - } - - if (ev.target.value === "") { - this._config = { ...this._config }; - delete this._config.initial_view; - } else { - this._config = { - ...this._config, - initial_view: ev.target.value, - }; - } - fireEvent(this, "config-changed", { config: this._config }); + private _entitiesChanged(ev): void { + const config = { ...this._config!, entities: ev.detail.value }; + fireEvent(this, "config-changed", { config }); } - static get styles(): CSSResultGroup { - return configElementStyle; - } + private _computeLabelCallback = (schema: HaFormSchema) => { + if (schema.name === "title") { + return this.hass!.localize("ui.panel.lovelace.editor.card.generic.title"); + } + + return this.hass!.localize( + `ui.panel.lovelace.editor.card.calendar.${schema.name}` + ); + }; + + static styles = css` + ha-form { + display: block; + overflow: auto; + } + `; } declare global { diff --git a/src/translations/en.json b/src/translations/en.json index 54eda22ef1..da653b245d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3449,7 +3449,7 @@ "calendar": { "name": "Calendar", "description": "The Calendar card displays a calendar including day, week and list views", - "inital_view": "Initial View", + "initial_view": "Initial View", "calendar_entities": "Calendar Entities", "views": { "dayGridMonth": "Month", From 5f69a4c1657c2eac7ce9f2e3644b24cec2a1c467 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 23 Feb 2022 10:31:33 -0600 Subject: [PATCH 168/174] Graph Footer to MWC (#11803) --- .../editor/config-elements/hui-graph-footer-editor.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-graph-footer-editor.ts b/src/panels/lovelace/editor/config-elements/hui-graph-footer-editor.ts index 5b9f10c5fd..736b9ed5a8 100644 --- a/src/panels/lovelace/editor/config-elements/hui-graph-footer-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-graph-footer-editor.ts @@ -1,10 +1,10 @@ -import "@polymer/paper-input/paper-input"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert } from "superstruct"; import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; import "../../../../components/entity/ha-entity-picker"; import "../../../../components/ha-formfield"; +import "../../../../components/ha-textfield"; import "../../../../components/ha-switch"; import type { HomeAssistant } from "../../../../types"; import { graphHeaderFooterConfigStruct } from "../../header-footer/structs"; @@ -73,7 +73,7 @@ export class HuiGraphFooterEditor @change=${this._change} >
    - + @input=${this._valueChanged} + >
    `; From 2a98ace0b371f9ac208e048860fe87bc8413d45b Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 23 Feb 2022 11:15:17 -0600 Subject: [PATCH 169/174] History Graph Editor to ha form (#11797) --- src/data/selector.ts | 4 +- .../hui-history-graph-card-editor.ts | 146 +++++++----------- 2 files changed, 54 insertions(+), 96 deletions(-) diff --git a/src/data/selector.ts b/src/data/selector.ts index f3d1d39c71..d8cb9a4e63 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -87,8 +87,8 @@ export interface TargetSelector { export interface NumberSelector { number: { - min: number; - max: number; + min?: number; + max?: number; step?: number; mode?: "box" | "slider"; unit_of_measurement?: string; diff --git a/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts index 9ed7e4279b..a18b13c2c1 100644 --- a/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-history-graph-card-editor.ts @@ -1,5 +1,5 @@ -import "@polymer/paper-input/paper-input"; -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import "../../../../components/ha-form/ha-form"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { array, @@ -11,16 +11,15 @@ import { assign, } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { HomeAssistant } from "../../../../types"; -import { HistoryGraphCardConfig } from "../../cards/types"; +import type { HomeAssistant } from "../../../../types"; +import type { HistoryGraphCardConfig } from "../../cards/types"; import "../../components/hui-entity-editor"; -import { EntityConfig } from "../../entity-rows/types"; -import { LovelaceCardEditor } from "../../types"; +import type { EntityConfig } from "../../entity-rows/types"; +import type { LovelaceCardEditor } from "../../types"; import { processEditorEntities } from "../process-editor-entities"; import { entitiesConfigStruct } from "../structs/entities-struct"; -import { EditorTarget, EntitiesEditorEvent } from "../types"; -import { configElementStyle } from "./config-elements-style"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -32,6 +31,21 @@ const cardConfigStruct = assign( }) ); +const SCHEMA: HaFormSchema[] = [ + { name: "title", selector: { text: {} } }, + { + name: "", + type: "grid", + schema: [ + { name: "hours_to_show", selector: { number: { min: 1, mode: "box" } } }, + { + name: "refresh_interval", + selector: { number: { min: 1, mode: "box" } }, + }, + ], + }, +]; + @customElement("hui-history-graph-card-editor") export class HuiHistoryGraphCardEditor extends LitElement @@ -49,104 +63,48 @@ export class HuiHistoryGraphCardEditor this._configEntities = processEditorEntities(config.entities); } - get _title(): string { - return this._config!.title || ""; - } - - get _hours_to_show(): number { - return this._config!.hours_to_show || 24; - } - - get _refresh_interval(): number { - return this._config!.refresh_interval || 0; - } - protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; } return html` -
    - -
    - - -
    - -
    + + `; } - private _valueChanged(ev: EntitiesEditorEvent): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target! as EditorTarget; - - if (!ev.detail && this[`_${target.configValue}`] === target.value) { - return; - } - - if (ev.detail && ev.detail.entities) { - this._config = { ...this._config, entities: ev.detail.entities }; - this._configEntities = processEditorEntities(this._config.entities); - } else if (target.configValue) { - if (target.value === "") { - this._config = { ...this._config }; - delete this._config[target.configValue!]; - } else { - let value: any = target.value; - if (target.type === "number") { - value = Number(value); - } - this._config = { - ...this._config, - [target.configValue!]: value, - }; - } - } - - fireEvent(this, "config-changed", { config: this._config }); + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); } - static get styles(): CSSResultGroup { - return configElementStyle; + private _entitiesChanged(ev: CustomEvent): void { + let config = this._config!; + + config = { ...config, entities: ev.detail.entities }; + this._configEntities = processEditorEntities(config.entities); + + fireEvent(this, "config-changed", { config }); } + + private _computeLabelCallback = (schema: HaFormSchema) => + this.hass!.localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`); + + static styles: CSSResultGroup = css` + ha-form { + margin-bottom: 24px; + } + `; } declare global { From 0113cc3cf66a377561984892b000daa1db5aeea7 Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 23 Feb 2022 11:48:18 -0600 Subject: [PATCH 170/174] Glance editor to ha-form (#11804) --- src/components/ha-form/ha-form-grid.ts | 26 +- src/components/ha-form/types.ts | 1 + .../config-elements/hui-glance-card-editor.ts | 224 +++++------------- 3 files changed, 82 insertions(+), 169 deletions(-) diff --git a/src/components/ha-form/ha-form-grid.ts b/src/components/ha-form/ha-form-grid.ts index 2c3ed39285..82baf2b3b4 100644 --- a/src/components/ha-form/ha-form-grid.ts +++ b/src/components/ha-form/ha-form-grid.ts @@ -1,5 +1,12 @@ import "./ha-form"; -import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { + css, + CSSResultGroup, + html, + LitElement, + PropertyValues, + TemplateResult, +} from "lit"; import { customElement, property } from "lit/decorators"; import type { HaFormGridSchema, @@ -26,10 +33,25 @@ export class HaFormGrid extends LitElement implements HaFormElement { @property() public computeHelper?: (schema: HaFormSchema) => string; - protected firstUpdated() { + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); this.setAttribute("own-margin", ""); } + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + if (changedProps.has("schema")) { + if (this.schema.column_min_width) { + this.style.setProperty( + "--form-grid-min-width", + this.schema.column_min_width + ); + } else { + this.style.setProperty("--form-grid-min-width", ""); + } + } + } + protected render(): TemplateResult { return html` ${this.schema.schema.map( diff --git a/src/components/ha-form/types.ts b/src/components/ha-form/types.ts index 5d9572147a..fc45bc3ef1 100644 --- a/src/components/ha-form/types.ts +++ b/src/components/ha-form/types.ts @@ -29,6 +29,7 @@ export interface HaFormBaseSchema { export interface HaFormGridSchema extends HaFormBaseSchema { type: "grid"; name: ""; + column_min_width?: string; schema: HaFormSchema[]; } diff --git a/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts index f4fbb031e7..f794b5b317 100644 --- a/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts @@ -1,4 +1,5 @@ -import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import "../../../../components/ha-form/ha-form"; +import { html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { array, @@ -12,23 +13,14 @@ import { assign, } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; -import "../../../../components/entity/state-badge"; -import "../../../../components/ha-card"; -import "../../../../components/ha-formfield"; -import "../../../../components/ha-icon"; -import "../../../../components/ha-switch"; -import { HomeAssistant } from "../../../../types"; +import type { HomeAssistant } from "../../../../types"; import { ConfigEntity, GlanceCardConfig } from "../../cards/types"; import "../../components/hui-entity-editor"; -import "../../components/hui-theme-select-editor"; -import { LovelaceCardEditor } from "../../types"; +import type { LovelaceCardEditor } from "../../types"; import { processEditorEntities } from "../process-editor-entities"; import { entitiesConfigStruct } from "../structs/entities-struct"; -import { EditorTarget, EntitiesEditorEvent } from "../types"; -import { configElementStyle } from "./config-elements-style"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; -import "@polymer/paper-input/paper-input"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -44,6 +36,29 @@ const cardConfigStruct = assign( }) ); +const SCHEMA: HaFormSchema[] = [ + { name: "title", selector: { text: {} } }, + { + name: "", + type: "grid", + schema: [ + { name: "columns", selector: { number: { min: 1, mode: "box" } } }, + { name: "theme", selector: { theme: {} } }, + ], + }, + { + name: "", + type: "grid", + column_min_width: "100px", + schema: [ + { name: "show_name", selector: { boolean: {} } }, + { name: "show_icon", selector: { boolean: {} } }, + { name: "show_state", selector: { boolean: {} } }, + ], + }, + { name: "state_color", selector: { boolean: {} } }, +]; + @customElement("hui-glance-card-editor") export class HuiGlanceCardEditor extends LitElement @@ -61,177 +76,52 @@ export class HuiGlanceCardEditor this._configEntities = processEditorEntities(config.entities); } - get _title(): string { - return this._config!.title || ""; - } - - get _theme(): string { - return this._config!.theme || ""; - } - - get _columns(): number { - return this._config!.columns || NaN; - } - - get _show_name(): boolean { - return this._config!.show_name || true; - } - - get _show_icon(): boolean { - return this._config!.show_icon || true; - } - - get _show_state(): boolean { - return this._config!.show_state || true; - } - - get _state_color(): boolean { - return this._config!.state_color ?? true; - } - protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; } - const dir = computeRTLDirection(this.hass!); + const data = { + show_name: true, + show_icon: true, + show_state: true, + ...this._config, + }; return html` -
    - -
    - - -
    -
    -
    - - - -
    -
    - - - - -
    -
    - - - - -
    -
    - - - -
    + `; } - private _valueChanged(ev: EntitiesEditorEvent): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target! as EditorTarget; - - if (target.configValue && this[`_${target.configValue}`] === target.value) { - return; - } - if (ev.detail && ev.detail.entities) { - this._config = { ...this._config, entities: ev.detail.entities }; - - this._configEntities = processEditorEntities(this._config.entities); - } else if (target.configValue) { - if ( - target.value === "" || - (target.type === "number" && isNaN(Number(target.value))) - ) { - this._config = { ...this._config }; - delete this._config[target.configValue!]; - } else { - let value: any = target.value; - if (target.type === "number") { - value = Number(value); - } - this._config = { - ...this._config, - [target.configValue!]: - target.checked !== undefined ? target.checked : value, - }; - } - } - fireEvent(this, "config-changed", { config: this._config }); + private _valueChanged(ev: CustomEvent): void { + const config = ev.detail.value; + fireEvent(this, "config-changed", { config }); } - static get styles(): CSSResultGroup { - return configElementStyle; + private _entitiesChanged(ev: CustomEvent): void { + let config = this._config!; + config = { ...config, entities: ev.detail.entities! }; + + this._configEntities = processEditorEntities(this._config!.entities); + fireEvent(this, "config-changed", { config }); } + + private _computeLabelCallback = (schema: HaFormSchema) => + this.hass!.localize( + `ui.panel.lovelace.editor.card.glance.${schema.name}` + ) || + this.hass!.localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`); } declare global { From 9ae1f01ad64c4f31b7271eee28d53ed88d0d3b3f Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 23 Feb 2022 11:51:40 -0600 Subject: [PATCH 171/174] Grid Card to HA Form (#11798) --- .../config-elements/hui-grid-card-editor.ts | 90 ++++++------------- 1 file changed, 26 insertions(+), 64 deletions(-) diff --git a/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts index 1d45bce946..49c30c100f 100644 --- a/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-grid-card-editor.ts @@ -12,8 +12,8 @@ import { string, } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; -import { GridCardConfig } from "../../cards/types"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; +import type { GridCardConfig } from "../../cards/types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { HuiStackCardEditor } from "./hui-stack-card-editor"; @@ -27,6 +27,17 @@ const cardConfigStruct = assign( }) ); +const SCHEMA: HaFormSchema[] = [ + { + type: "grid", + name: "", + schema: [ + { name: "columns", selector: { number: { min: 1, mode: "box" } } }, + { name: "square", selector: { boolean: {} } }, + ], + }, +]; + @customElement("hui-grid-card-editor") export class HuiGridCardEditor extends HuiStackCardEditor { public setConfig(config: Readonly): void { @@ -34,80 +45,31 @@ export class HuiGridCardEditor extends HuiStackCardEditor { this._config = config; } - get _columns(): number { - return this._config!.columns || 3; - } - - get _square(): boolean { - return this._config!.square ?? true; - } - protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; } + const data = { square: true, columns: 3, ...this._config }; + return html` -
    -
    - - - - -
    -
    + ${super.render()} `; } - private _handleColumnsChanged(ev): void { - if (!this._config) { - return; - } - const value = Number(ev.target.value); - if (this._columns === value) { - return; - } - if (!ev.target.value) { - this._config = { ...this._config }; - delete this._config.columns; - } else { - this._config = { - ...this._config, - columns: value, - }; - } - fireEvent(this, "config-changed", { config: this._config }); + private _valueChanged(ev: CustomEvent): void { + fireEvent(this, "config-changed", { config: ev.detail.value }); } - private _handleSquareChanged(ev): void { - if (!this._config || this._square === ev.target.checked) { - return; - } - - fireEvent(this, "config-changed", { - config: { ...this._config, square: ev.target.checked }, - }); - } + private _computeLabelCallback = (schema: HaFormSchema) => + this.hass!.localize(`ui.panel.lovelace.editor.card.grid.${schema.name}`); } declare global { From 70ca27c8c98d90d706e423dea859e14f9186b5cd Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 23 Feb 2022 12:49:15 -0600 Subject: [PATCH 172/174] Button editor to ha-form (#11808) --- src/panels/lovelace/cards/types.ts | 1 + .../config-elements/hui-button-card-editor.ts | 275 +++++++----------- 2 files changed, 105 insertions(+), 171 deletions(-) diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 2a2b5e0fd9..9c043e4699 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -87,6 +87,7 @@ export interface ButtonCardConfig extends LovelaceCardConfig { name?: string; show_name?: boolean; icon?: string; + icon_height?: string; show_icon?: boolean; theme?: string; tap_action?: ActionConfig; diff --git a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts index 417c4f327e..e5f8525d99 100644 --- a/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-button-card-editor.ts @@ -1,25 +1,22 @@ -import "@polymer/paper-input/paper-input"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { assert, boolean, object, optional, string, assign } from "superstruct"; +import type { HassEntity } from "home-assistant-js-websocket"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; -import "../../../../components/ha-formfield"; -import "../../../../components/ha-icon-picker"; -import "../../../../components/ha-switch"; import { ActionConfig } from "../../../../data/lovelace"; -import { HomeAssistant } from "../../../../types"; -import { ButtonCardConfig } from "../../cards/types"; +import type { HomeAssistant } from "../../../../types"; +import type { ButtonCardConfig } from "../../cards/types"; import "../../components/hui-action-editor"; -import "../../components/hui-entity-editor"; -import "../../components/hui-theme-select-editor"; -import { LovelaceCardEditor } from "../../types"; +import "../../../../components/ha-form/ha-form"; +import type { LovelaceCardEditor } from "../../types"; import { actionConfigStruct } from "../structs/action-struct"; -import { EditorTarget } from "../types"; +import type { EditorTarget } from "../types"; import { configElementStyle } from "./config-elements-style"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { computeDomain } from "../../../../common/entity/compute_domain"; import { domainIcon } from "../../../../common/entity/domain_icon"; +import type { HaFormSchema } from "../../../../components/ha-form/types"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -60,35 +57,55 @@ export class HuiButtonCardEditor this._config = config; } - get _entity(): string { - return this._config!.entity || ""; - } - - get _name(): string { - return this._config!.name || ""; - } - - get _show_name(): boolean { - return this._config!.show_name ?? true; - } - - get _show_state(): boolean { - return this._config!.show_state ?? false; - } - - get _icon(): string { - return this._config!.icon || ""; - } - - get _show_icon(): boolean { - return this._config!.show_icon ?? true; - } - - get _icon_height(): string { - return this._config!.icon_height && this._config!.icon_height.includes("px") - ? String(parseFloat(this._config!.icon_height)) - : ""; - } + private _schema = memoizeOne( + ( + entity?: string, + icon?: string, + entityState?: HassEntity + ): HaFormSchema[] => [ + { name: "entity", selector: { entity: {} } }, + { + name: "", + type: "grid", + schema: [ + { name: "name", selector: { text: {} } }, + { + name: "icon", + selector: { + icon: { + placeholder: icon || entityState?.attributes.icon, + fallbackPath: + !icon && + !entityState?.attributes.icon && + entityState && + entity + ? domainIcon(computeDomain(entity), entityState) + : undefined, + }, + }, + }, + ], + }, + { + name: "", + type: "grid", + column_min_width: "100px", + schema: [ + { name: "show_name", selector: { boolean: {} } }, + { name: "show_state", selector: { boolean: {} } }, + { name: "show_icon", selector: { boolean: {} } }, + ], + }, + { + name: "", + type: "grid", + schema: [ + { name: "icon_height", selector: { text: { suffix: "px" } } }, + { name: "theme", selector: { theme: {} } }, + ], + }, + ] + ); get _tap_action(): ActionConfig | undefined { return this._config!.tap_action; @@ -98,124 +115,40 @@ export class HuiButtonCardEditor return this._config!.hold_action || { action: "more-info" }; } - get _theme(): string { - return this._config!.theme || ""; - } - protected render(): TemplateResult { if (!this.hass || !this._config) { return html``; } - const dir = computeRTLDirection(this.hass!); - const entityState = this.hass.states[this._entity]; + const entityState = this._config.entity + ? this.hass.states[this._config.entity] + : undefined; + + const schema = this._schema( + this._config.entity, + this._config.icon, + entityState + ); + + const data = { + show_name: true, + show_icon: true, + ...this._config, + }; + + if (data.icon_height?.includes("px")) { + data.icon_height = String(parseFloat(data.icon_height)); + } return html` +
    - -
    - - -
    -
    -
    - - - -
    -
    - - - -
    -
    - - - -
    -
    -
    -
    px
    -
    - -
    `; } - private _change(ev: Event) { - if (!this._config || !this.hass) { - return; - } - const target = ev.target! as EditorTarget; - const value = target.checked; + private _valueChanged(ev: CustomEvent): void { + const config = ev.detail.value; - if (this[`_${target.configValue}`] === value) { - return; + if (config.icon_height && !config.icon_height.endsWith("px")) { + config.icon_height += "px"; } - fireEvent(this, "config-changed", { - config: { - ...this._config, - [target.configValue!]: value, - }, - }); + fireEvent(this, "config-changed", { config }); } - private _valueChanged(ev: CustomEvent): void { + private _computeLabelCallback = (schema: HaFormSchema) => { + if (schema.name === "entity") { + return `${this.hass!.localize( + "ui.panel.lovelace.editor.card.generic.entity" + )}`; + } + + return this.hass!.localize( + `ui.panel.lovelace.editor.card.generic.${schema.name}` + ); + }; + + private _actionChanged(ev: CustomEvent): void { if (!this._config || !this.hass) { return; } @@ -289,10 +225,7 @@ export class HuiButtonCardEditor } else { newConfig = { ...this._config, - [target.configValue!]: - target.configValue === "icon_height" && !isNaN(Number(target.value)) - ? `${String(value)}px` - : value, + [target.configValue!]: value, }; } } From fec0dc003273269247b43534c97ecc1c85f3af56 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 23 Feb 2022 19:51:07 +0100 Subject: [PATCH 173/174] Bumped version to 20220223.0 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9770797945..6205a6a259 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = home-assistant-frontend -version = 20220222.0 +version = 20220223.0 author = The Home Assistant Authors author_email = hello@home-assistant.io license = Apache-2.0 From 1719d062b39402173e055d56479dcb9581e1dc90 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 23 Feb 2022 19:59:36 +0100 Subject: [PATCH 174/174] mwc-select -> ha-select (#11806) --- .../addon-view/config/hassio-addon-audio.ts | 14 +++--- .../datadisk/dialog-hassio-datadisk.ts | 8 ++-- hassio/src/system/hassio-supervisor-log.ts | 7 +-- .../device/ha-device-automation-picker.ts | 8 ++-- src/components/ha-base-time-input.ts | 8 ++-- src/components/ha-blueprint-picker.ts | 10 ++-- .../ha-form/ha-form-multi_select.ts | 1 - src/components/ha-form/ha-form-select.ts | 19 ++++---- src/components/ha-qr-scanner.ts | 1 - src/components/ha-select.ts | 47 +++++++++++++++++++ .../ha-selector/ha-selector-select.ts | 10 ++-- .../media-player/ha-browse-media-tts.ts | 22 ++++----- src/components/user/ha-user-picker.ts | 8 ++-- .../more-info/controls/more-info-climate.ts | 24 +++++----- .../more-info/controls/more-info-fan.js | 10 ++-- .../controls/more-info-humidifier.ts | 6 +-- .../more-info/controls/more-info-light.ts | 6 +-- .../controls/more-info-media_player.ts | 29 +++++------- .../more-info/controls/more-info-vacuum.ts | 8 ++-- .../controls/more-info-water_heater.js | 10 ++-- src/fake_data/provide_hass.ts | 2 + .../action/ha-automation-action-row.ts | 12 ++--- .../types/ha-automation-action-repeat.ts | 6 +-- .../ha-automation-condition-editor.ts | 12 ++--- .../types/ha-automation-condition-trigger.ts | 6 +-- .../automation/manual-automation-editor.ts | 6 +-- .../trigger/ha-automation-trigger-row.ts | 24 +++++----- .../types/ha-automation-trigger-tag.ts | 6 +-- .../config/cloud/account/cloud-tts-pref.ts | 10 ++-- .../cloud/account/dialog-cloud-tts-try.ts | 14 +++--- .../entities/entity-registry-settings.ts | 12 ++--- .../integration-panels/zha/types.ts | 4 +- .../zha/zha-cluster-attributes.ts | 8 ++-- .../zha/zha-cluster-commands.ts | 8 ++-- .../integration-panels/zha/zha-clusters.ts | 8 ++-- .../zha/zha-device-binding.ts | 10 ++-- .../zha/zha-group-binding.ts | 4 +- .../zwave_js/zwave_js-logs.ts | 6 +-- .../zwave_js/zwave_js-node-config.ts | 8 ++-- .../dialog-lovelace-resource-detail.ts | 10 ++-- .../lovelace/components/hui-action-editor.ts | 6 +-- .../components/hui-theme-select-editor.ts | 12 ++--- .../config-elements/config-elements-style.ts | 2 +- .../hui-conditional-card-editor.ts | 8 ++-- .../hui-generic-entity-row-editor.ts | 6 +-- .../hui-picture-entity-card-editor.ts | 6 +-- .../hui-picture-glance-card-editor.ts | 6 +-- .../config-elements/hui-sensor-card-editor.ts | 6 +-- .../hui-statistics-graph-card-editor.ts | 6 +-- .../select-view/hui-dialog-select-view.ts | 8 ++-- .../editor/view-editor/hui-view-editor.ts | 6 +-- .../hui-input-select-entity-row.ts | 8 ++-- .../entity-rows/hui-select-entity-row.ts | 8 ++-- src/panels/profile/ha-pick-dashboard-row.ts | 8 ++-- src/panels/profile/ha-pick-language-row.ts | 8 ++-- .../profile/ha-pick-number-format-row.ts | 6 +-- src/panels/profile/ha-pick-theme-row.ts | 6 +-- src/panels/profile/ha-pick-time-format-row.ts | 8 ++-- src/state-summary/state-card-input_select.ts | 8 ++-- src/state-summary/state-card-select.ts | 8 ++-- src/state/quick-bar-mixin.ts | 2 +- src/state/translations-mixin.ts | 3 ++ 62 files changed, 308 insertions(+), 269 deletions(-) create mode 100644 src/components/ha-select.ts diff --git a/hassio/src/addon-view/config/hassio-addon-audio.ts b/hassio/src/addon-view/config/hassio-addon-audio.ts index 7f7c8d3f2c..acf59950fa 100644 --- a/hassio/src/addon-view/config/hassio-addon-audio.ts +++ b/hassio/src/addon-view/config/hassio-addon-audio.ts @@ -1,5 +1,4 @@ import "@material/mwc-button"; -import "@material/mwc-select"; import "@material/mwc-list/mwc-list-item"; import { css, @@ -14,6 +13,7 @@ import { stopPropagation } from "../../../../src/common/dom/stop_propagation"; import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-card"; +import "../../../../src/components/ha-select"; import { HassioAddonDetails, HassioAddonSetOptionParams, @@ -57,7 +57,7 @@ class HassioAddonAudio extends LitElement { ? html`${this._error}` : ""} ${this._inputDevices && - html` ` )} - `} + `} ${this._outputDevices && - html` ` )} - `} + `}
    @@ -119,10 +119,10 @@ class HassioAddonAudio extends LitElement { .card-actions { text-align: right; } - mwc-select { + ha-select { width: 100%; } - mwc-select:last-child { + ha-select:last-child { margin-top: 8px; } `, diff --git a/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts b/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts index 564a1fe689..92eda0e5b5 100644 --- a/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts +++ b/hassio/src/dialogs/datadisk/dialog-hassio-datadisk.ts @@ -1,11 +1,11 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../src/common/dom/fire_event"; import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-markdown"; +import "../../../../src/components/ha-select"; import { extractApiErrorMessage, ignoreSupervisorError, @@ -89,7 +89,7 @@ class HassioDatadiskDialog extends LitElement { )}

    - ${device}` )} - + ` : this.devices === undefined ? this.dialogParams.supervisor.localize( @@ -161,7 +161,7 @@ class HassioDatadiskDialog extends LitElement { haStyle, haStyleDialog, css` - mwc-select { + ha-select { width: 100%; } ha-circular-progress { diff --git a/hassio/src/system/hassio-supervisor-log.ts b/hassio/src/system/hassio-supervisor-log.ts index 65cb0f8a66..a18f3c1d3b 100644 --- a/hassio/src/system/hassio-supervisor-log.ts +++ b/hassio/src/system/hassio-supervisor-log.ts @@ -4,6 +4,7 @@ import { customElement, property, state } from "lit/decorators"; import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/ha-alert"; import "../../../src/components/ha-card"; +import "../../../src/components/ha-select"; import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { fetchHassioLogs } from "../../../src/data/hassio/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor"; @@ -70,7 +71,7 @@ class HassioSupervisorLog extends LitElement { : ""} ${this.hass.userData?.showAdvanced ? html` - ` )} - + ` : ""} @@ -145,7 +146,7 @@ class HassioSupervisorLog extends LitElement { pre { white-space: pre-wrap; } - mwc-select { + ha-select { width: 100%; margin-bottom: 4px; } diff --git a/src/components/device/ha-device-automation-picker.ts b/src/components/device/ha-device-automation-picker.ts index 007f40e77c..57b46e5b5b 100644 --- a/src/components/device/ha-device-automation-picker.ts +++ b/src/components/device/ha-device-automation-picker.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; @@ -8,6 +7,7 @@ import { deviceAutomationsEqual, } from "../../data/device_automation"; import { HomeAssistant } from "../../types"; +import "../ha-select"; const NO_AUTOMATION_KEY = "NO_AUTOMATION"; const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION"; @@ -90,7 +90,7 @@ export abstract class HaDeviceAutomationPicker< } const value = this._value; return html` - ` )} - + `; } @@ -167,7 +167,7 @@ export abstract class HaDeviceAutomationPicker< static get styles(): CSSResultGroup { return css` - mwc-select { + ha-select { width: 100%; margin-top: 4px; } diff --git a/src/components/ha-base-time-input.ts b/src/components/ha-base-time-input.ts index 27b72e42f2..64b12f18e2 100644 --- a/src/components/ha-base-time-input.ts +++ b/src/components/ha-base-time-input.ts @@ -1,6 +1,6 @@ import { LitElement, html, TemplateResult, css } from "lit"; import { customElement, property } from "lit/decorators"; -import "@material/mwc-select/mwc-select"; +import "./ha-select"; import "@material/mwc-list/mwc-list-item"; import "./ha-textfield"; import { fireEvent } from "../common/dom/fire_event"; @@ -193,7 +193,7 @@ export class HaBaseTimeInput extends LitElement { : ""} ${this.format === 24 ? "" - : html` AM PM - `} + `}
    `; } @@ -280,7 +280,7 @@ export class HaBaseTimeInput extends LitElement { ha-textfield:last-child { --text-field-border-top-right-radius: var(--mdc-shape-medium); } - mwc-select { + ha-select { --mdc-shape-small: 0; width: 85px; } diff --git a/src/components/ha-blueprint-picker.ts b/src/components/ha-blueprint-picker.ts index bbe399e214..e45e31b6da 100644 --- a/src/components/ha-blueprint-picker.ts +++ b/src/components/ha-blueprint-picker.ts @@ -1,5 +1,5 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; +import "./ha-select"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -24,7 +24,7 @@ class HaBluePrintPicker extends LitElement { @property({ type: Boolean }) public disabled = false; public open() { - const select = this.shadowRoot?.querySelector("mwc-select"); + const select = this.shadowRoot?.querySelector("ha-select"); if (select) { // @ts-expect-error select.menuOpen = true; @@ -49,7 +49,7 @@ class HaBluePrintPicker extends LitElement { return html``; } return html` - ` )} - + `; } @@ -101,7 +101,7 @@ class HaBluePrintPicker extends LitElement { :host { display: inline-block; } - mwc-select { + ha-select { width: 100%; min-width: 200px; display: block; diff --git a/src/components/ha-form/ha-form-multi_select.ts b/src/components/ha-form/ha-form-multi_select.ts index 3411977f51..a591f710bc 100644 --- a/src/components/ha-form/ha-form-multi_select.ts +++ b/src/components/ha-form/ha-form-multi_select.ts @@ -1,4 +1,3 @@ -import "@material/mwc-select/mwc-select"; import { mdiMenuDown, mdiMenuUp } from "@mdi/js"; import { css, diff --git a/src/components/ha-form/ha-form-select.ts b/src/components/ha-form/ha-form-select.ts index bbe561ff40..2e342a39a2 100644 --- a/src/components/ha-form/ha-form-select.ts +++ b/src/components/ha-form/ha-form-select.ts @@ -1,14 +1,13 @@ -import "@material/mwc-select"; -import type { Select } from "@material/mwc-select"; import "@material/mwc-list/mwc-list-item"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; -import "../ha-radio"; -import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./types"; - import { stopPropagation } from "../../common/dom/stop_propagation"; +import "../ha-radio"; import type { HaRadio } from "../ha-radio"; +import "../ha-select"; +import type { HaSelect } from "../ha-select"; +import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./types"; @customElement("ha-form-select") export class HaFormSelect extends LitElement implements HaFormElement { @@ -20,7 +19,7 @@ export class HaFormSelect extends LitElement implements HaFormElement { @property({ type: Boolean }) public disabled = false; - @query("mwc-select", true) private _input?: HTMLElement; + @query("ha-select", true) private _input?: HTMLElement; public focus() { if (this._input) { @@ -50,7 +49,7 @@ export class HaFormSelect extends LitElement implements HaFormElement { } return html` - ${label} ` )} - + `; } private _valueChanged(ev: CustomEvent) { ev.stopPropagation(); - let value: string | undefined = (ev.target as Select | HaRadio).value; + let value: string | undefined = (ev.target as HaSelect | HaRadio).value; if (value === this.data) { return; @@ -90,7 +89,7 @@ export class HaFormSelect extends LitElement implements HaFormElement { static get styles(): CSSResultGroup { return css` - mwc-select, + ha-select, mwc-formfield { display: block; } diff --git a/src/components/ha-qr-scanner.ts b/src/components/ha-qr-scanner.ts index 53ffb463f1..87d0399914 100644 --- a/src/components/ha-qr-scanner.ts +++ b/src/components/ha-qr-scanner.ts @@ -1,6 +1,5 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { mdiCamera } from "@mdi/js"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, query, state } from "lit/decorators"; diff --git a/src/components/ha-select.ts b/src/components/ha-select.ts new file mode 100644 index 0000000000..a6e69ee858 --- /dev/null +++ b/src/components/ha-select.ts @@ -0,0 +1,47 @@ +import { SelectBase } from "@material/mwc-select/mwc-select-base"; +import { styles } from "@material/mwc-select/mwc-select.css"; +import { html, nothing } from "lit"; +import { customElement, property } from "lit/decorators"; +import { debounce } from "../common/util/debounce"; +import { nextRender } from "../common/util/render-status"; + +@customElement("ha-select") +export class HaSelect extends SelectBase { + // @ts-ignore + @property({ type: Boolean }) public icon?: boolean; + + protected override renderLeadingIcon() { + if (!this.icon) { + return nothing; + } + + return html``; + } + + static override styles = [styles]; + + connectedCallback() { + super.connectedCallback(); + window.addEventListener("translations-updated", this._translationsUpdated); + } + + disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener( + "translations-updated", + this._translationsUpdated + ); + } + + private _translationsUpdated = debounce(async () => { + await nextRender(); + this.layoutOptions(); + }, 500); +} +declare global { + interface HTMLElementTagNameMap { + "ha-select": HaSelect; + } +} diff --git a/src/components/ha-selector/ha-selector-select.ts b/src/components/ha-selector/ha-selector-select.ts index 5c1cc3e91b..ebc2704992 100644 --- a/src/components/ha-selector/ha-selector-select.ts +++ b/src/components/ha-selector/ha-selector-select.ts @@ -1,11 +1,11 @@ +import "@material/mwc-list/mwc-list-item"; import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { stopPropagation } from "../../common/dom/stop_propagation"; import { SelectOption, SelectSelector } from "../../data/selector"; import { HomeAssistant } from "../../types"; -import "@material/mwc-select/mwc-select"; -import "@material/mwc-list/mwc-list-item"; +import "../ha-select"; @customElement("ha-selector-select") export class HaSelectSelector extends LitElement { @@ -22,7 +22,7 @@ export class HaSelectSelector extends LitElement { @property({ type: Boolean }) public disabled = false; protected render() { - return html`${label}`; })} - `; + `; } private _valueChanged(ev) { @@ -53,7 +53,7 @@ export class HaSelectSelector extends LitElement { static get styles(): CSSResultGroup { return css` - mwc-select { + ha-select { width: 100%; } `; diff --git a/src/components/media-player/ha-browse-media-tts.ts b/src/components/media-player/ha-browse-media-tts.ts index 43f4b30190..3cd39d3a73 100644 --- a/src/components/media-player/ha-browse-media-tts.ts +++ b/src/components/media-player/ha-browse-media-tts.ts @@ -1,9 +1,10 @@ -import "@material/mwc-select"; import "@material/mwc-list/mwc-list-item"; import { css, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { LocalStorage } from "../../common/decorators/local-storage"; import { fireEvent } from "../../common/dom/fire_event"; +import { stopPropagation } from "../../common/dom/stop_propagation"; import { fetchCloudStatus, updateCloudPref } from "../../data/cloud"; import { CloudTTSInfo, @@ -15,12 +16,11 @@ import { MediaPlayerBrowseAction, MediaPlayerItem, } from "../../data/media-player"; -import { HomeAssistant } from "../../types"; -import "../ha-textarea"; -import { buttonLinkStyle } from "../../resources/styles"; import { showAlertDialog } from "../../dialogs/generic/show-dialog-box"; -import { LocalStorage } from "../../common/decorators/local-storage"; -import { stopPropagation } from "../../common/dom/stop_propagation"; +import { buttonLinkStyle } from "../../resources/styles"; +import { HomeAssistant } from "../../types"; +import "../ha-select"; +import "../ha-textarea"; export interface TtsMediaPickedEvent { item: MediaPlayerItem; @@ -103,7 +103,7 @@ class BrowseMediaTTS extends LitElement { return html`
    - html`${label}` )} - + - html`${label}` )} - +
    `; } @@ -256,7 +256,7 @@ class BrowseMediaTTS extends LitElement { display: flex; justify-content: space-between; } - .cloud-options mwc-select { + .cloud-options ha-select { width: 48%; } ha-textarea { diff --git a/src/components/user/ha-user-picker.ts b/src/components/user/ha-user-picker.ts index f1ac638e0b..0e7ae97466 100644 --- a/src/components/user/ha-user-picker.ts +++ b/src/components/user/ha-user-picker.ts @@ -1,3 +1,4 @@ +import "@material/mwc-list/mwc-list-item"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { property } from "lit/decorators"; import memoizeOne from "memoize-one"; @@ -5,9 +6,8 @@ import { fireEvent } from "../../common/dom/fire_event"; import { stringCompare } from "../../common/string/compare"; import { fetchUsers, User } from "../../data/user"; import { HomeAssistant } from "../../types"; +import "../ha-select"; import "./ha-user-badge"; -import "@material/mwc-select/mwc-select"; -import "@material/mwc-list/mwc-list-item"; class HaUserPicker extends LitElement { public hass?: HomeAssistant; @@ -34,7 +34,7 @@ class HaUserPicker extends LitElement { protected render(): TemplateResult { return html` - ` )} - + `; } diff --git a/src/dialogs/more-info/controls/more-info-climate.ts b/src/dialogs/more-info/controls/more-info-climate.ts index 3512ba86a3..2a66080055 100644 --- a/src/dialogs/more-info/controls/more-info-climate.ts +++ b/src/dialogs/more-info/controls/more-info-climate.ts @@ -1,3 +1,4 @@ +import "@material/mwc-list/mwc-list-item"; import { css, CSSResultGroup, @@ -9,9 +10,11 @@ import { import { property } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { fireEvent } from "../../../common/dom/fire_event"; +import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; import { computeRTLDirection } from "../../../common/util/compute_rtl"; import "../../../components/ha-climate-control"; +import "../../../components/ha-select"; import "../../../components/ha-slider"; import "../../../components/ha-switch"; import { @@ -26,9 +29,6 @@ import { compareClimateHvacModes, } from "../../../data/climate"; import { HomeAssistant } from "../../../types"; -import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; -import { stopPropagation } from "../../../common/dom/stop_propagation"; class MoreInfoClimate extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -168,7 +168,7 @@ class MoreInfoClimate extends LitElement {
    - ` )} - +
    ${supportPresetMode && stateObj.attributes.preset_modes ? html`
    - ` )} - +
    ` : ""} ${supportFanMode && stateObj.attributes.fan_modes ? html`
    - ` )} - +
    ` : ""} ${supportSwingMode && stateObj.attributes.swing_modes ? html`
    - ${mode} ` )} - +
    ` : ""} @@ -427,7 +427,7 @@ class MoreInfoClimate extends LitElement { color: var(--primary-text-color); } - mwc-select { + ha-select { width: 100%; margin-top: 8px; } diff --git a/src/dialogs/more-info/controls/more-info-fan.js b/src/dialogs/more-info/controls/more-info-fan.js index 3d712670c7..5a2f042448 100644 --- a/src/dialogs/more-info/controls/more-info-fan.js +++ b/src/dialogs/more-info/controls/more-info-fan.js @@ -1,3 +1,4 @@ +import "@material/mwc-list/mwc-list-item"; import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import { html } from "@polymer/polymer/lib/utils/html-tag"; /* eslint-plugin-disable lit */ @@ -8,12 +9,11 @@ import "../../../components/ha-attributes"; import "../../../components/ha-icon"; import "../../../components/ha-icon-button"; import "../../../components/ha-labeled-slider"; +import "../../../components/ha-select"; import "../../../components/ha-switch"; import { SUPPORT_SET_SPEED } from "../../../data/fan"; import { EventsMixin } from "../../../mixins/events-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin"; -import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; /* * @appliesMixin EventsMixin @@ -37,7 +37,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) { display: block; } - mwc-select { + ha-select { width: 100%; } @@ -57,7 +57,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
    - [[item]] - +
    diff --git a/src/dialogs/more-info/controls/more-info-humidifier.ts b/src/dialogs/more-info/controls/more-info-humidifier.ts index 3d6aa6e0c9..bce976b45b 100644 --- a/src/dialogs/more-info/controls/more-info-humidifier.ts +++ b/src/dialogs/more-info/controls/more-info-humidifier.ts @@ -170,11 +170,7 @@ class MoreInfoHumidifier extends LitElement { color: var(--primary-text-color); } - mwc-select { - width: 100%; - } - - ha-slider { + ha-select { width: 100%; } diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts index 08b53c1825..5a2a07c21b 100644 --- a/src/dialogs/more-info/controls/more-info-light.ts +++ b/src/dialogs/more-info/controls/more-info-light.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { mdiPalette } from "@mdi/js"; import { css, @@ -18,6 +17,7 @@ import "../../../components/ha-button-toggle-group"; import "../../../components/ha-color-picker"; import "../../../components/ha-icon-button"; import "../../../components/ha-labeled-slider"; +import "../../../components/ha-select"; import { getLightCurrentModeRgbColor, LightColorModes, @@ -208,7 +208,7 @@ class MoreInfoLight extends LitElement { this.stateObj!.attributes.effect_list?.length ? html`
    - ` )} - + ` : ""} ` diff --git a/src/dialogs/more-info/controls/more-info-media_player.ts b/src/dialogs/more-info/controls/more-info-media_player.ts index c415d322e9..efb7bf3617 100644 --- a/src/dialogs/more-info/controls/more-info-media_player.ts +++ b/src/dialogs/more-info/controls/more-info-media_player.ts @@ -1,6 +1,5 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { mdiLoginVariant, mdiMusicNote, @@ -17,6 +16,7 @@ import { stopPropagation } from "../../../common/dom/stop_propagation"; import { supportsFeature } from "../../../common/entity/supports-feature"; import { computeRTLDirection } from "../../../common/util/compute_rtl"; import "../../../components/ha-icon-button"; +import "../../../components/ha-select"; import "../../../components/ha-slider"; import "../../../components/ha-svg-icon"; import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog"; @@ -135,12 +135,9 @@ class MoreInfoMediaPlayer extends LitElement { stateObj.attributes.source_list?.length ? html`
    - - ${source} ` )} - + +
    ` : ""} @@ -161,10 +159,10 @@ class MoreInfoMediaPlayer extends LitElement { stateObj.attributes.sound_mode_list?.length ? html`
    - - ${mode} ` )} - + +
    ` : ""} @@ -216,14 +215,8 @@ class MoreInfoMediaPlayer extends LitElement { justify-content: space-between; } - .source-input ha-svg-icon, - .sound-input ha-svg-icon { - padding: 7px; - margin-top: 24px; - } - - .source-input mwc-select, - .sound-input mwc-select { + .source-input ha-select, + .sound-input ha-select { margin-left: 10px; flex-grow: 1; } diff --git a/src/dialogs/more-info/controls/more-info-vacuum.ts b/src/dialogs/more-info/controls/more-info-vacuum.ts index 44ef096299..852a41b98a 100644 --- a/src/dialogs/more-info/controls/more-info-vacuum.ts +++ b/src/dialogs/more-info/controls/more-info-vacuum.ts @@ -1,3 +1,4 @@ +import "@material/mwc-list/mwc-list-item"; import { mdiFan, mdiHomeMapMarker, @@ -15,6 +16,7 @@ import { supportsFeature } from "../../../common/entity/supports-feature"; import "../../../components/ha-attributes"; import "../../../components/ha-icon"; import "../../../components/ha-icon-button"; +import "../../../components/ha-select"; import { UNAVAILABLE } from "../../../data/entity"; import { VacuumEntity, @@ -29,8 +31,6 @@ import { VACUUM_SUPPORT_STOP, } from "../../../data/vacuum"; import { HomeAssistant } from "../../../types"; -import "@material/mwc-select/mwc-select"; -import "@material/mwc-list/mwc-list-item"; interface VacuumCommand { translationKey: string; @@ -173,7 +173,7 @@ class MoreInfoVacuum extends LitElement { ? html`
    - ${mode} ` )} - +
    diff --git a/src/dialogs/more-info/controls/more-info-water_heater.js b/src/dialogs/more-info/controls/more-info-water_heater.js index d46bf1a178..a48c3b4118 100644 --- a/src/dialogs/more-info/controls/more-info-water_heater.js +++ b/src/dialogs/more-info/controls/more-info-water_heater.js @@ -1,3 +1,4 @@ +import "@material/mwc-list/mwc-list-item"; import "@polymer/iron-flex-layout/iron-flex-layout-classes"; import { timeOut } from "@polymer/polymer/lib/utils/async"; import { Debouncer } from "@polymer/polymer/lib/utils/debounce"; @@ -6,12 +7,11 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; import { featureClassNames } from "../../../common/entity/feature_class_names"; import { supportsFeature } from "../../../common/entity/supports-feature"; +import "../../../components/ha-select"; import "../../../components/ha-switch"; import "../../../components/ha-water_heater-control"; import { EventsMixin } from "../../../mixins/events-mixin"; import LocalizeMixin from "../../../mixins/localize-mixin"; -import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; /* * @appliesMixin EventsMixin @@ -26,7 +26,7 @@ class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) { color: var(--primary-text-color); } - mwc-select { + ha-select { width: 100%; } @@ -70,7 +70,7 @@ class MoreInfoWaterHeater extends LocalizeMixin(EventsMixin(PolymerElement)) { - +
    diff --git a/src/fake_data/provide_hass.ts b/src/fake_data/provide_hass.ts index 8ca7b46de0..dc9e875b32 100644 --- a/src/fake_data/provide_hass.ts +++ b/src/fake_data/provide_hass.ts @@ -3,6 +3,7 @@ import { applyThemesOnElement, invalidateThemeCache, } from "../common/dom/apply_themes_on_element"; +import { fireEvent } from "../common/dom/fire_event"; import { computeLocalize } from "../common/translations/localize"; import { DEFAULT_PANEL } from "../data/panel"; import { NumberFormat, TimeFormat } from "../data/translation"; @@ -85,6 +86,7 @@ export const provideHass = ( hass().updateHass({ localize: await computeLocalize(elements[0], lang, hass().resources), }); + fireEvent(window, "translations-updated"); } function updateStates(newStates: HassEntities) { diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index 2d68bf0461..1844341245 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -1,7 +1,5 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select"; -import type { Select } from "@material/mwc-select"; import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, query, state } from "lit/decorators"; @@ -15,6 +13,8 @@ import "../../../../components/ha-alert"; import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; import "../../../../components/ha-icon-button"; +import "../../../../components/ha-select"; +import type { HaSelect } from "../../../../components/ha-select"; import type { HaYamlEditor } from "../../../../components/ha-yaml-editor"; import { Action, getActionType } from "../../../../data/script"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; @@ -241,7 +241,7 @@ export default class HaAutomationActionRow extends LitElement { > ` : html` - ${label} ` )} - +
    ${dynamicElement(`ha-automation-action-${type}`, { @@ -315,7 +315,7 @@ export default class HaAutomationActionRow extends LitElement { } private _typeChanged(ev: CustomEvent) { - const type = (ev.target as Select).value; + const type = (ev.target as HaSelect).value; if (!type) { return; @@ -375,7 +375,7 @@ export default class HaAutomationActionRow extends LitElement { .warning ul { margin: 4px 0; } - mwc-select { + ha-select { margin-bottom: 24px; } `, diff --git a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts index 0238269e14..13942a211a 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts @@ -35,7 +35,7 @@ export class HaRepeatAction extends LitElement implements ActionElement { const type = getType(action); return html` - ` )} - + ${type === "count" ? html` ` : html` - ${label} ` )} - +
    ${dynamicElement( @@ -112,7 +112,7 @@ export default class HaAutomationConditionEditor extends LitElement { } private _typeChanged(ev: CustomEvent) { - const type = (ev.target as Select).value; + const type = (ev.target as HaSelect).value; if (!type) { return; @@ -146,7 +146,7 @@ export default class HaAutomationConditionEditor extends LitElement { static styles = [ haStyle, css` - mwc-select { + ha-select { margin-bottom: 24px; } `, diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts index eb5e04fb78..cb80c3769b 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-trigger.ts @@ -1,10 +1,10 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { ensureArray } from "../../../../../common/ensure-array"; +import "../../../../../components/ha-select"; import type { AutomationConfig, Trigger, @@ -50,7 +50,7 @@ export class HaTriggerCondition extends LitElement { "ui.panel.config.automation.editor.conditions.type.trigger.no_triggers" ); } - return html` ${trigger.id} ` )} - `; + `; } private _automationUpdated(config?: AutomationConfig) { diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 19a82decb9..86dcbd72d9 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -95,7 +95,7 @@ export class HaManualAutomationEditor extends LitElement { >` )}

    - ` )} - + ${this.config.mode && MODES_MAX.includes(this.config.mode) ? html`
    ` : html` - ${label} ` )} - + ${showId ? html` @@ -359,7 +359,7 @@ export default class HaAutomationTriggerRow extends LitElement { } private _typeChanged(ev: CustomEvent) { - const type = (ev.target as Select).value; + const type = (ev.target as HaSelect).value; if (!type) { return; @@ -456,7 +456,7 @@ export default class HaAutomationTriggerRow extends LitElement { mwc-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); } - mwc-select { + ha-select { margin-bottom: 24px; } ha-textfield { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts index f79bbd1619..9565c87347 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-tag.ts @@ -1,9 +1,9 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select"; import { html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { caseInsensitiveStringCompare } from "../../../../../common/string/compare"; +import "../../../../../components/ha-select"; import { TagTrigger } from "../../../../../data/automation"; import { fetchTags, Tag } from "../../../../../data/tag"; import { HomeAssistant } from "../../../../../types"; @@ -29,7 +29,7 @@ export class HaTagTrigger extends LitElement implements TriggerElement { protected render() { const { tag_id } = this.trigger; return html` - ` )} - + `; } diff --git a/src/panels/config/cloud/account/cloud-tts-pref.ts b/src/panels/config/cloud/account/cloud-tts-pref.ts index b92a9e45d7..e7f36d8edf 100644 --- a/src/panels/config/cloud/account/cloud-tts-pref.ts +++ b/src/panels/config/cloud/account/cloud-tts-pref.ts @@ -1,11 +1,11 @@ import "@material/mwc-button"; -import "@material/mwc-select"; import "@material/mwc-list/mwc-list-item"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-card"; +import "../../../../components/ha-select"; import "../../../../components/ha-svg-icon"; import "../../../../components/ha-switch"; import { CloudStatusLoggedIn, updateCloudPref } from "../../../../data/cloud"; @@ -54,7 +54,7 @@ export class CloudTTSPref extends LitElement { )}

    - html`${label}` )} - + - html`${label}` )} - +
    diff --git a/src/panels/config/cloud/account/dialog-cloud-tts-try.ts b/src/panels/config/cloud/account/dialog-cloud-tts-try.ts index bb4bad9e49..b93cbeac21 100644 --- a/src/panels/config/cloud/account/dialog-cloud-tts-try.ts +++ b/src/panels/config/cloud/account/dialog-cloud-tts-try.ts @@ -1,9 +1,8 @@ import "@material/mwc-button"; -import "@material/mwc-select"; import "@material/mwc-list/mwc-list-item"; import { mdiPlayCircleOutline, mdiRobot } from "@mdi/js"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; -import { customElement, property, state, query } from "lit/decorators"; +import { customElement, property, query, state } from "lit/decorators"; import { LocalStorage } from "../../../../common/decorators/local-storage"; import { fireEvent } from "../../../../common/dom/fire_event"; import { stopPropagation } from "../../../../common/dom/stop_propagation"; @@ -11,8 +10,9 @@ import { computeStateDomain } from "../../../../common/entity/compute_state_doma import { computeStateName } from "../../../../common/entity/compute_state_name"; import { supportsFeature } from "../../../../common/entity/supports-feature"; import { createCloseHeading } from "../../../../components/ha-dialog"; -import type { HaTextArea } from "../../../../components/ha-textarea"; +import "../../../../components/ha-select"; import "../../../../components/ha-textarea"; +import type { HaTextArea } from "../../../../components/ha-textarea"; import { showAutomationEditor } from "../../../../data/automation"; import { SUPPORT_PLAY_MEDIA } from "../../../../data/media-player"; import { convertTextToSpeech } from "../../../../data/tts"; @@ -74,7 +74,7 @@ export class DialogTryTts extends LitElement { > - ` )} - +
    ${OVERRIDE_DEVICE_CLASSES[domain]?.includes(this._deviceClass) || (domain === "cover" && this.entry.original_device_class === null) - ? html` ` )} - ` + ` : ""}
    - ` )} - +
    ${this.showHelp ? html` @@ -270,7 +270,7 @@ export class ZHAClusterAttributes extends LitElement { return [ haStyle, css` - mwc-select { + ha-select { margin-top: 16px; } diff --git a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts index 4af410898b..ca3a29be5f 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-cluster-commands.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select"; import { mdiHelpCircle } from "@mdi/js"; import "@polymer/paper-input/paper-input"; import { @@ -15,6 +14,7 @@ import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-button"; +import "../../../../../components/ha-select"; import "../../../../../components/ha-service-description"; import { Cluster, @@ -82,7 +82,7 @@ export class ZHAClusterCommands extends LitElement {
    - ` )} - +
    ${this._showHelp ? html` @@ -206,7 +206,7 @@ export class ZHAClusterCommands extends LitElement { return [ haStyle, css` - mwc-select { + ha-select { margin-top: 16px; } .menu { diff --git a/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts b/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts index e9e20a2899..19f2d270d2 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-clusters.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select"; import { mdiHelpCircle } from "@mdi/js"; import { css, @@ -15,6 +14,7 @@ import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-button"; +import "../../../../../components/ha-select"; import "../../../../../components/ha-service-description"; import { Cluster, @@ -78,7 +78,7 @@ export class ZHAClusters extends LitElement {
    - ` )} - +
    ${this.showHelp ? html` @@ -137,7 +137,7 @@ export class ZHAClusters extends LitElement { return [ haStyle, css` - mwc-select { + ha-select { margin-top: 16px; } .menu { diff --git a/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts b/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts index 4ff919838e..1445a572d5 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-device-binding.ts @@ -1,7 +1,6 @@ import "@material/mwc-button/mwc-button"; -import { mdiHelpCircle } from "@mdi/js"; import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select"; +import { mdiHelpCircle } from "@mdi/js"; import { css, CSSResultGroup, @@ -11,16 +10,17 @@ import { TemplateResult, } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { stopPropagation } from "../../../../../common/dom/stop_propagation"; import "../../../../../components/buttons/ha-call-service-button"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-button"; +import "../../../../../components/ha-select"; import "../../../../../components/ha-service-description"; import { bindDevices, unbindDevices, ZHADevice } from "../../../../../data/zha"; import { haStyle } from "../../../../../resources/styles"; import { HomeAssistant } from "../../../../../types"; import "../../../ha-config-section"; import { ItemSelectedEvent } from "./types"; -import { stopPropagation } from "../../../../../common/dom/stop_propagation"; @customElement("zha-device-binding-control") export class ZHADeviceBindingControl extends LitElement { @@ -62,7 +62,7 @@ export class ZHADeviceBindingControl extends LitElement {
    - ` )} - +
    ${this._showHelp ? html` diff --git a/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts b/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts index f5d284ea5c..67671e9047 100644 --- a/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts +++ b/src/panels/config/integrations/integration-panels/zha/zha-group-binding.ts @@ -93,7 +93,7 @@ export class ZHAGroupBindingControl extends LitElement {
    - ${group.name} ` )} - +
    ${this._showHelp ? html` diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts index 542a492e8f..23deecbaf8 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-logs.ts @@ -1,11 +1,11 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { mdiDownload } from "@mdi/js"; import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { css, CSSResultArray, html, LitElement } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { capitalizeFirstLetter } from "../../../../../common/string/capitalize-first-letter"; import "../../../../../components/ha-icon-button"; +import "../../../../../components/ha-select"; import { fetchZWaveJSLogConfig, setZWaveJSLogLevel, @@ -77,7 +77,7 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
    ${this._logConfig ? html` - Verbose Debug Silly - + ` : ""}
    diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts index 4cf083893d..decfd847df 100644 --- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts +++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts @@ -1,6 +1,5 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { mdiCheckCircle, mdiCircle, @@ -22,6 +21,7 @@ import memoizeOne from "memoize-one"; import { debounce } from "../../../../../common/util/debounce"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-next"; +import "../../../../../components/ha-select"; import "../../../../../components/ha-settings-row"; import "../../../../../components/ha-svg-icon"; import "../../../../../components/ha-switch"; @@ -286,7 +286,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) { return html` ${labelAndDescription}
    - ${entityState} ` )} - +
    `; } @@ -455,7 +455,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) { } .flex .config-label, - .flex mwc-select { + .flex ha-select { flex: 1; } diff --git a/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts b/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts index ed04677669..eb2ebe92d3 100644 --- a/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts +++ b/src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts @@ -1,8 +1,11 @@ import "@material/mwc-button/mwc-button"; +import "@material/mwc-list/mwc-list-item"; import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { createCloseHeading } from "../../../../components/ha-dialog"; +import "../../../../components/ha-select"; import { LovelaceResource, LovelaceResourcesMutableParams, @@ -11,9 +14,6 @@ import { PolymerChangedEvent } from "../../../../polymer-types"; import { haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; import { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail"; -import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; -import { stopPropagation } from "../../../../common/dom/stop_propagation"; const detectResourceType = (url: string) => { const ext = url.split(".").pop() || ""; @@ -102,7 +102,7 @@ export class DialogLovelaceResourceDetail extends LitElement { dialogInitialFocus >
    - ` : ""} - +
    ${this._params.resource diff --git a/src/panels/lovelace/components/hui-action-editor.ts b/src/panels/lovelace/components/hui-action-editor.ts index 114233e2ec..f57a51f51d 100644 --- a/src/panels/lovelace/components/hui-action-editor.ts +++ b/src/panels/lovelace/components/hui-action-editor.ts @@ -57,7 +57,7 @@ export class HuiActionEditor extends LitElement { return html`
    - - +
    - +
    `; } diff --git a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts index aa1ed1eb7b..5c1ca02a96 100644 --- a/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-picture-entity-card-editor.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import "@polymer/paper-input/paper-input"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -8,6 +7,7 @@ import { fireEvent } from "../../../../common/dom/fire_event"; import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import "../../../../components/ha-formfield"; +import "../../../../components/ha-select"; import "../../../../components/ha-switch"; import { ActionConfig } from "../../../../data/lovelace"; import { HomeAssistant } from "../../../../types"; @@ -155,7 +155,7 @@ export class HuiPictureEntityCardEditor allow-custom-entity >
    - html`${view} ` )} - +
    - html`${view} ` )} - + - html`${graph}` )} - +
    - ` )} - + ${this._params.allowDashboardChange - ? html` `; })} - ` + ` : ""} ${this._config ? this._config.views.length > 1 @@ -183,7 +183,7 @@ export class HuiDialogSelectView extends LitElement { return [ haStyleDialog, css` - mwc-select { + ha-select { width: 100%; } `, diff --git a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts index 411b7906ef..f04f74e0dd 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-editor.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import "@polymer/paper-input/paper-input"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; @@ -8,6 +7,7 @@ import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { slugify } from "../../../../common/string/slugify"; import "../../../../components/ha-formfield"; import "../../../../components/ha-icon-picker"; +import "../../../../components/ha-select"; import "../../../../components/ha-switch"; import { LovelaceViewConfig } from "../../../../data/lovelace"; import { HomeAssistant } from "../../../../types"; @@ -124,7 +124,7 @@ export class HuiViewEditor extends LitElement { .configValue=${"theme"} @value-changed=${this._valueChanged} > - ` )} - +
    `; } diff --git a/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts index 30fd573dbe..512d3c7785 100644 --- a/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-input-select-entity-row.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, @@ -11,6 +10,7 @@ import { import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeStateName } from "../../../common/entity/compute_state_name"; +import "../../../components/ha-select"; import { UNAVAILABLE_STATES } from "../../../data/entity"; import { forwardHaptic } from "../../../data/haptics"; import { @@ -65,7 +65,7 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow { .config=${this._config} hideName > - ` ) : ""} - + `; } @@ -93,7 +93,7 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow { display: flex; align-items: center; } - mwc-select { + ha-select { width: 100%; } `; diff --git a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts index f6bc9ec867..5cc2ab0ee9 100644 --- a/src/panels/lovelace/entity-rows/hui-select-entity-row.ts +++ b/src/panels/lovelace/entity-rows/hui-select-entity-row.ts @@ -1,5 +1,4 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, @@ -11,6 +10,7 @@ import { import { customElement, property, state } from "lit/decorators"; import { stopPropagation } from "../../../common/dom/stop_propagation"; import { computeStateName } from "../../../common/entity/compute_state_name"; +import "../../../components/ha-select"; import { UNAVAILABLE } from "../../../data/entity"; import { forwardHaptic } from "../../../data/haptics"; import { SelectEntity, setSelectOption } from "../../../data/select"; @@ -62,7 +62,7 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow { .config=${this._config} hideName > - + `; } @@ -99,7 +99,7 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow { display: flex; align-items: center; } - mwc-select { + ha-select { width: 100%; } `; diff --git a/src/panels/profile/ha-pick-dashboard-row.ts b/src/panels/profile/ha-pick-dashboard-row.ts index 9fa0dc057e..4b5d647002 100644 --- a/src/panels/profile/ha-pick-dashboard-row.ts +++ b/src/panels/profile/ha-pick-dashboard-row.ts @@ -1,11 +1,11 @@ +import "@material/mwc-list/mwc-list-item"; import { html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; +import "../../components/ha-select"; import "../../components/ha-settings-row"; import { fetchDashboards, LovelaceDashboard } from "../../data/lovelace"; import { setDefaultPanel } from "../../data/panel"; import { HomeAssistant } from "../../types"; -import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; @customElement("ha-pick-dashboard-row") class HaPickDashboardRow extends LitElement { @@ -29,7 +29,7 @@ class HaPickDashboardRow extends LitElement { ${this.hass.localize("ui.panel.profile.dashboard.description")} - `; })} - + `; } diff --git a/src/panels/profile/ha-pick-language-row.ts b/src/panels/profile/ha-pick-language-row.ts index ced951b463..c4fe06bb18 100644 --- a/src/panels/profile/ha-pick-language-row.ts +++ b/src/panels/profile/ha-pick-language-row.ts @@ -1,10 +1,10 @@ +import "@material/mwc-list/mwc-list-item"; import { css, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; +import "../../components/ha-select"; import "../../components/ha-settings-row"; import { HomeAssistant, Translation } from "../../types"; -import "@material/mwc-select/mwc-select"; -import "@material/mwc-list/mwc-list-item"; @customElement("ha-pick-language-row") export class HaPickLanguageRow extends LitElement { @@ -33,7 +33,7 @@ export class HaPickLanguageRow extends LitElement { >${this.hass.localize("ui.panel.profile.language.link_promo")} - ` )} - + `; } diff --git a/src/panels/profile/ha-pick-number-format-row.ts b/src/panels/profile/ha-pick-number-format-row.ts index f32ee02dce..d8588f6e1a 100644 --- a/src/panels/profile/ha-pick-number-format-row.ts +++ b/src/panels/profile/ha-pick-number-format-row.ts @@ -1,10 +1,10 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import { formatNumber } from "../../common/number/format_number"; import "../../components/ha-card"; +import "../../components/ha-select"; import "../../components/ha-settings-row"; import { NumberFormat } from "../../data/translation"; import { HomeAssistant } from "../../types"; @@ -24,7 +24,7 @@ class NumberFormatRow extends LitElement { ${this.hass.localize("ui.panel.profile.number_format.description")} - `; })} - + `; } diff --git a/src/panels/profile/ha-pick-theme-row.ts b/src/panels/profile/ha-pick-theme-row.ts index d9fe4f78c2..b0147b6842 100644 --- a/src/panels/profile/ha-pick-theme-row.ts +++ b/src/panels/profile/ha-pick-theme-row.ts @@ -1,6 +1,5 @@ import "@material/mwc-button/mwc-button"; import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; import { css, CSSResultGroup, @@ -14,6 +13,7 @@ import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-formfield"; import "../../components/ha-radio"; import type { HaRadio } from "../../components/ha-radio"; +import "../../components/ha-select"; import "../../components/ha-settings-row"; import "../../components/ha-textfield"; import { @@ -62,7 +62,7 @@ export class HaPickThemeRow extends LitElement { ${this.hass.localize("ui.panel.profile.themes.link_promo")} - html`${theme}` )} - + ${curTheme === "default" || this._supportsModeSelection(curTheme) ? html`
    diff --git a/src/panels/profile/ha-pick-time-format-row.ts b/src/panels/profile/ha-pick-time-format-row.ts index 5f68076f2a..48a910cd9e 100644 --- a/src/panels/profile/ha-pick-time-format-row.ts +++ b/src/panels/profile/ha-pick-time-format-row.ts @@ -1,13 +1,13 @@ +import "@material/mwc-list/mwc-list-item"; import { html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { formatTime } from "../../common/datetime/format_time"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-card"; +import "../../components/ha-select"; import "../../components/ha-settings-row"; import { TimeFormat } from "../../data/translation"; import { HomeAssistant } from "../../types"; -import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; @customElement("ha-pick-time-format-row") class TimeFormatRow extends LitElement { @@ -25,7 +25,7 @@ class TimeFormatRow extends LitElement { ${this.hass.localize("ui.panel.profile.time_format.description")} - ${formattedTime} `; })} - + `; } diff --git a/src/state-summary/state-card-input_select.ts b/src/state-summary/state-card-input_select.ts index c992975a82..e249ae1319 100644 --- a/src/state-summary/state-card-input_select.ts +++ b/src/state-summary/state-card-input_select.ts @@ -1,5 +1,5 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; +import "../components/ha-select"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { stopPropagation } from "../common/dom/stop_propagation"; @@ -18,7 +18,7 @@ class StateCardInputSelect extends LitElement { protected render(): TemplateResult { return html` - html`${option}` )} - + `; } @@ -54,7 +54,7 @@ class StateCardInputSelect extends LitElement { margin-top: 10px; } - mwc-select { + ha-select { width: 100%; } `; diff --git a/src/state-summary/state-card-select.ts b/src/state-summary/state-card-select.ts index 1aae15ad6c..a37c8e07ad 100644 --- a/src/state-summary/state-card-select.ts +++ b/src/state-summary/state-card-select.ts @@ -1,5 +1,5 @@ import "@material/mwc-list/mwc-list-item"; -import "@material/mwc-select/mwc-select"; +import "../components/ha-select"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators"; import { stopPropagation } from "../common/dom/stop_propagation"; @@ -18,7 +18,7 @@ class StateCardSelect extends LitElement { protected render(): TemplateResult { return html` - ` )} - + `; } @@ -63,7 +63,7 @@ class StateCardSelect extends LitElement { margin-top: 10px; } - mwc-select { + ha-select { width: 100%; } `; diff --git a/src/state/quick-bar-mixin.ts b/src/state/quick-bar-mixin.ts index a0e98b327c..b9e6997182 100644 --- a/src/state/quick-bar-mixin.ts +++ b/src/state/quick-bar-mixin.ts @@ -58,7 +58,7 @@ export default >(superClass: T) => return false; } - if (el.parentElement.tagName === "MWC-SELECT") { + if (el.parentElement.tagName === "ha-select") { return false; } diff --git a/src/state/translations-mixin.ts b/src/state/translations-mixin.ts index c26978ddc1..5a118b67cc 100644 --- a/src/state/translations-mixin.ts +++ b/src/state/translations-mixin.ts @@ -19,6 +19,7 @@ import { getUserLocale, } from "../util/common-translation"; import { HassBaseEl } from "./hass-base-mixin"; +import { fireEvent } from "../common/dom/fire_event"; declare global { // for fire event @@ -32,6 +33,7 @@ declare global { "hass-time-format-select": { time_format: TimeFormat; }; + "translations-updated": undefined; } } @@ -352,6 +354,7 @@ export default >(superClass: T) => localize, }); } + fireEvent(this, "translations-updated"); } private _refetchCachedHassTranslations(