20231205.0 (#18916)

This commit is contained in:
Bram Kragten 2023-12-05 18:10:37 +01:00 committed by GitHub
commit eb5e7ba3f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
114 changed files with 1631 additions and 853 deletions

View File

@ -39,7 +39,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
<div class="picker">
<div class="label">
${this._switching
? html`<ha-circular-progress active></ha-circular-progress>`
? html`<ha-circular-progress
indeterminate
></ha-circular-progress>`
: until(
selectedDemoConfig.then(
(conf) => html`

View File

@ -0,0 +1,4 @@
---
title: Circular Progress
subtitle: Can be used to indicate an ongoing task.
---

View File

@ -0,0 +1,64 @@
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../../src/components/ha-bar";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-circular-progress";
import "@material/web/progress/circular-progress";
import { HomeAssistant } from "../../../../src/types";
@customElement("demo-components-ha-circular-progress")
export class DemoHaCircularProgress extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
protected render(): TemplateResult {
return html`<ha-card header="Basic circular progress">
<div class="card-content">
<ha-circular-progress indeterminate></ha-circular-progress></div
></ha-card>
<ha-card header="Different circular progress sizes">
<div class="card-content">
<ha-circular-progress
indeterminate
size="tiny"
></ha-circular-progress>
<ha-circular-progress
indeterminate
size="small"
></ha-circular-progress>
<ha-circular-progress
indeterminate
size="medium"
></ha-circular-progress>
<ha-circular-progress
indeterminate
size="large"
></ha-circular-progress></div
></ha-card>
<ha-card header="Circular progress with an aria-label">
<div class="card-content">
<ha-circular-progress
indeterminate
aria-label="Doing something..."
></ha-circular-progress>
<ha-circular-progress
indeterminate
.ariaLabel=${"Doing something..."}
></ha-circular-progress></div
></ha-card>`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-circular-progress": DemoHaCircularProgress;
}
}

View File

@ -20,7 +20,7 @@ class HassioAddonConfigDashboard extends LitElement {
protected render(): TemplateResult {
if (!this.addon) {
return html`<ha-circular-progress active></ha-circular-progress>`;
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
}
const hasConfiguration =
(this.addon.options && Object.keys(this.addon.options).length) ||

View File

@ -34,7 +34,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
protected render(): TemplateResult {
if (!this.addon) {
return html`<ha-circular-progress active></ha-circular-progress>`;
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
}
return html`
<div class="content">

View File

@ -22,7 +22,7 @@ class HassioAddonInfoDashboard extends LitElement {
protected render(): TemplateResult {
if (!this.addon) {
return html`<ha-circular-progress active></ha-circular-progress>`;
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
}
return html`

View File

@ -18,7 +18,9 @@ class HassioAddonLogDashboard extends LitElement {
protected render(): TemplateResult {
if (!this.addon) {
return html` <ha-circular-progress active></ha-circular-progress> `;
return html`
<ha-circular-progress indeterminate></ha-circular-progress>
`;
}
return html`
<div class="content">

View File

@ -95,7 +95,7 @@ class HassioBackupDialog
</ha-header-bar>
</div>
${this._restoringBackup
? html`<ha-circular-progress active></ha-circular-progress>`
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
: html`
<supervisor-backup-content
.hass=${this.hass}

View File

@ -57,7 +57,7 @@ class HassioCreateBackupDialog extends LitElement {
)}
>
${this._creatingBackup
? html`<ha-circular-progress active></ha-circular-progress>`
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
: html`<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}

View File

@ -71,7 +71,11 @@ class HassioDatadiskDialog extends LitElement {
?hideActions=${this.moving}
>
${this.moving
? html` <ha-circular-progress alt="Moving" size="large" active>
? html` <ha-circular-progress
aria-label="Moving"
size="large"
indeterminate
>
</ha-circular-progress>
<p class="progress-text">
${this.dialogParams.supervisor.localize(

View File

@ -155,7 +155,11 @@ export class DialogHassioNetwork
.disabled=${this._scanning}
>
${this._scanning
? html`<ha-circular-progress active size="small">
? html`<ha-circular-progress
aria-label="Scanning"
indeterminate
size="small"
>
</ha-circular-progress>`
: this.supervisor.localize("dialog.network.scan_ap")}
</mwc-button>
@ -274,7 +278,7 @@ export class DialogHassioNetwork
</mwc-button>
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
${this._processing
? html`<ha-circular-progress active size="small">
? html`<ha-circular-progress indeterminate size="small">
</ha-circular-progress>`
: this.supervisor.localize("common.save")}
</mwc-button>

View File

@ -158,7 +158,7 @@ class HassioRepositoriesDialog extends LitElement {
<mwc-button @click=${this._addRepository}>
${this._processing
? html`<ha-circular-progress
active
indeterminate
size="small"
></ha-circular-progress>`
: this._dialogParams!.supervisor.localize(

View File

@ -174,7 +174,11 @@ class UpdateAvailableCard extends LitElement {
`
: ""}
`
: html`<ha-circular-progress alt="Updating" size="large" active>
: html`<ha-circular-progress
aria-label="Updating"
size="large"
indeterminate
>
</ha-circular-progress>
<p class="progress-text">
${this.supervisor.localize("update_available.updating", {

View File

@ -60,7 +60,6 @@
"@material/mwc-base": "0.27.0",
"@material/mwc-button": "0.27.0",
"@material/mwc-checkbox": "0.27.0",
"@material/mwc-circular-progress": "0.27.0",
"@material/mwc-dialog": "0.27.0",
"@material/mwc-drawer": "0.27.0",
"@material/mwc-fab": "0.27.0",
@ -91,8 +90,8 @@
"@polymer/paper-toast": "3.0.1",
"@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.2.4",
"@vaadin/vaadin-themable-mixin": "24.2.4",
"@vaadin/combo-box": "24.2.5",
"@vaadin/vaadin-themable-mixin": "24.2.5",
"@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@ -120,14 +119,13 @@
"leaflet-draw": "1.0.4",
"lit": "2.8.0",
"luxon": "3.4.4",
"marked": "10.0.0",
"marked": "11.0.0",
"memoize-one": "6.0.0",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2",
"punycode": "2.3.1",
"qr-scanner": "1.4.2",
"qrcode": "1.5.3",
"resize-observer-polyfill": "1.5.1",
"roboto-fontface": "0.10.0",
"rrule": "2.8.1",
"sortablejs": "1.15.1",
@ -194,7 +192,7 @@
"babel-plugin-template-html-minifier": "4.1.0",
"chai": "4.3.10",
"del": "7.1.0",
"eslint": "8.54.0",
"eslint": "8.55.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.1.0",
"eslint-config-prettier": "9.0.0",
@ -206,7 +204,7 @@
"eslint-plugin-unused-imports": "3.0.0",
"eslint-plugin-wc": "2.0.4",
"fancy-log": "2.0.0",
"fs-extra": "11.1.1",
"fs-extra": "11.2.0",
"glob": "10.3.10",
"gulp": "4.0.2",
"gulp-flatmap": "1.0.2",

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20231204.0"
version = "20231205.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@ -45,7 +45,7 @@ export class HaProgressButton extends LitElement {
? html`
<ha-circular-progress
size="small"
active
indeterminate
></ha-circular-progress>
`
: ""}

View File

@ -469,6 +469,7 @@ export class HaChartBase extends LitElement {
.chartTooltip li {
display: flex;
white-space: pre-line;
word-break: break-word;
align-items: center;
line-height: 16px;
padding: 4px 0;
@ -476,6 +477,7 @@ export class HaChartBase extends LitElement {
.chartTooltip .title {
text-align: center;
font-weight: 500;
word-break: break-word;
direction: ltr;
}
.chartTooltip .footer {

View File

@ -1,54 +1,44 @@
import { CircularProgress } from "@material/mwc-circular-progress";
import { CSSResultGroup, css } from "lit";
import "element-internals-polyfill";
import { MdCircularProgress } from "@material/web/progress/circular-progress";
import { CSSResult, PropertyValues, css } from "lit";
import { customElement, property } from "lit/decorators";
@customElement("ha-circular-progress")
// @ts-ignore
export class HaCircularProgress extends CircularProgress {
@property({ type: Boolean })
public active = false;
export class HaCircularProgress extends MdCircularProgress {
@property({ attribute: "aria-label", type: String }) public ariaLabel =
"Loading";
@property()
public alt = "Loading";
@property() public size: "tiny" | "small" | "medium" | "large" = "medium";
@property()
public size: "tiny" | "small" | "medium" | "large" = "medium";
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
// @ts-ignore
public set density(_) {
// just a dummy
}
public get density() {
switch (this.size) {
case "tiny":
return -8;
case "small":
return -5;
case "medium":
return 0;
case "large":
return 5;
default:
return 0;
if (changedProps.has("size")) {
switch (this.size) {
case "tiny":
this.style.setProperty("--md-circular-progress-size", "16px");
break;
case "small":
this.style.setProperty("--md-circular-progress-size", "28px");
break;
// medium is default size
case "medium":
this.style.setProperty("--md-circular-progress-size", "48px");
break;
case "large":
this.style.setProperty("--md-circular-progress-size", "68px");
break;
}
}
}
// @ts-ignore
public set indeterminate(_) {
// just a dummy
}
public get indeterminate() {
return this.active;
}
static get styles(): CSSResultGroup {
static get styles(): CSSResult[] {
return [
super.styles,
...super.styles,
css`
:host {
overflow: hidden;
--md-sys-color-primary: var(--primary-color);
--md-circular-progress-size: 48px;
}
`,
];

View File

@ -618,6 +618,7 @@ export class HaControlCircularSlider extends LitElement {
--control-circular-slider-high-color: var(
--control-circular-slider-color
);
--control-circular-slider-interaction-margin: 12px;
width: 320px;
display: block;
}
@ -633,7 +634,9 @@ export class HaControlCircularSlider extends LitElement {
fill: none;
stroke: transparent;
stroke-linecap: round;
stroke-width: 48px;
stroke-width: calc(
24px + 2 * var(--control-circular-slider-interaction-margin)
);
cursor: pointer;
}
#display {

View File

@ -1,3 +1,4 @@
import { ResizeController } from "@lit-labs/observers/resize-controller";
import { mdiMinus, mdiPlus } from "@mdi/js";
import {
CSSResultGroup,
@ -49,6 +50,13 @@ export class HaControlNumberButton extends LitElement {
@query("#input") _input!: HTMLDivElement;
private _hideUnit = new ResizeController(this, {
callback: (entries) => {
const width = entries[0]?.contentRect.width;
return width < 100;
},
});
private boundedValue(value: number) {
const clamped = conditionalClamp(value, this.min, this.max);
return Math.round(clamped / this._step) * this._step;
@ -145,7 +153,10 @@ export class HaControlNumberButton extends LitElement {
?disabled=${this.disabled}
@keydown=${this._handleKeyDown}
>
${value} ${unit ? html`<span class="unit">${unit}</span>` : nothing}
${value}
${unit && !this._hideUnit.value
? html`<span class="unit">${unit}</span>`
: nothing}
</div>
<button
class="button minus"

View File

@ -6,6 +6,11 @@ import { MdOutlinedIconButton } from "@material/web/iconbutton/outlined-icon-but
@customElement("ha-outlined-icon-button")
export class HaOutlinedIconButton extends MdOutlinedIconButton {
static override styles = [
css`
.icon-button {
border-radius: var(--_container-shape);
}
`,
...super.styles,
css`
:host {

View File

@ -1,9 +1,19 @@
import { DEFAULT_SCHEMA, dump, load, Schema } from "js-yaml";
import { html, LitElement, nothing, PropertyValues } from "lit";
import {
CSSResultGroup,
css,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import type { HomeAssistant } from "../types";
import { haStyle } from "../resources/styles";
import "./ha-code-editor";
import { showToast } from "../util/toast";
import { copyToClipboard } from "../common/util/copy-clipboard";
const isEmpty = (obj: Record<string, unknown>): boolean => {
if (typeof obj !== "object") {
@ -37,6 +47,8 @@ export class HaYamlEditor extends LitElement {
@property({ type: Boolean }) public required = false;
@property({ type: Boolean }) public copyClipboard = false;
@state() private _yaml = "";
public setValue(value): void {
@ -88,6 +100,15 @@ export class HaYamlEditor extends LitElement {
@value-changed=${this._onChange}
dir="ltr"
></ha-code-editor>
${this.copyClipboard
? html`<div class="card-actions">
<mwc-button @click=${this._copyYaml}>
${this.hass.localize(
"ui.components.yaml-editor.copy_to_clipboard"
)}
</mwc-button>
</div>`
: nothing}
`;
}
@ -117,6 +138,35 @@ export class HaYamlEditor extends LitElement {
get yaml() {
return this._yaml;
}
private async _copyYaml(): Promise<void> {
if (this.yaml) {
await copyToClipboard(this.yaml);
showToast(this, {
message: this.hass.localize("ui.common.copied_clipboard"),
});
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
css`
.card-actions {
border-radius: var(
--actions-border-radius,
0px 0px var(--ha-card-border-radius, 12px)
var(--ha-card-border-radius, 12px)
);
border: 1px solid var(--divider-color);
padding: 5px 16px;
}
ha-code-editor {
flex-grow: 1;
}
`,
];
}
}
declare global {

View File

@ -147,7 +147,7 @@ class DialogMediaManage extends LitElement {
${!this._currentItem
? html`
<div class="refresh">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
`
: !children.length

View File

@ -332,7 +332,7 @@ export class HaMediaPlayerBrowse extends LitElement {
}
if (!this._currentItem) {
return html`<ha-circular-progress active></ha-circular-progress>`;
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
}
const currentItem = this._currentItem;

View File

@ -54,8 +54,8 @@ class MediaUploadButton extends LitElement {
? html`
<ha-circular-progress
size="tiny"
active
alt=""
indeterminate
area-label="Uploading"
slot="icon"
></ha-circular-progress>
`

View File

@ -2,6 +2,7 @@ import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { getExtendedEntityRegistryEntry } from "./entity_registry";
import { showEnterCodeDialogDialog } from "../dialogs/enter-code/show-enter-code-dialog";
import { HomeAssistant } from "../types";
@ -30,15 +31,20 @@ export const callProtectedLockService = async (
service: ProtectedLockService
) => {
let code: string | undefined;
const lockRegistryEntry = await getExtendedEntityRegistryEntry(
hass,
stateObj.entity_id
).catch(() => undefined);
const defaultCode = lockRegistryEntry?.options?.lock?.default_code;
if (stateObj!.attributes.code_format) {
if (stateObj!.attributes.code_format && !defaultCode) {
const response = await showEnterCodeDialogDialog(element, {
codeFormat: "text",
codePattern: stateObj!.attributes.code_format,
title: hass.localize(`ui.card.lock.${service}`),
submitText: hass.localize(`ui.card.lock.${service}`),
});
if (!response) {
if (response == null) {
throw new Error("Code dialog closed");
}
code = response;

View File

@ -207,7 +207,7 @@ export class DialogAreaFilter
color: var(--disabled-text-color);
}
.handle {
cursor: grab;
cursor: move;
}
.actions {
display: flex;

View File

@ -90,7 +90,7 @@ class StepFlowForm extends LitElement {
${this._loading
? html`
<div class="submit-spinner">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
`
: html`

View File

@ -27,7 +27,7 @@ class StepFlowLoading extends LitElement {
return html`
<div class="init-spinner">
${description ? html`<div>${description}</div>` : ""}
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
`;
}

View File

@ -24,7 +24,7 @@ class StepFlowProgress extends LitElement {
${this.flowConfig.renderShowFormProgressHeader(this.hass, this.step)}
</h2>
<div class="content">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
${this.flowConfig.renderShowFormProgressDescription(
this.hass,
this.step

View File

@ -99,6 +99,8 @@ export class DialogEnterCode
id="code"
.label=${this.hass.localize("ui.dialogs.enter_code.input_label")}
type="password"
autoValidate
validateOnInitialRender
pattern=${ifDefined(this._dialogParams.codePattern)}
inputmode="text"
></ha-textfield>

View File

@ -53,8 +53,8 @@ export class MoreInfoConfigurator extends LitElement {
>
${this._isConfiguring
? html`<ha-circular-progress
active
alt="Configuring"
indeterminate
aria-label="Configuring"
></ha-circular-progress>`
: ""}
${this.stateObj.attributes.submit_caption}

View File

@ -104,7 +104,7 @@ class MoreInfoUpdate extends LitElement {
${supportsFeature(this.stateObj!, UPDATE_SUPPORT_RELEASE_NOTES) &&
!this._error
? this._releaseNotes === undefined
? html`<ha-circular-progress active></ha-circular-progress>`
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
: html`<hr />
<ha-faded>
<ha-markdown .content=${this._releaseNotes}></ha-markdown>

View File

@ -214,7 +214,7 @@ export class QuickBar extends LitElement {
${!items
? html`<ha-circular-progress
size="small"
active
indeterminate
></ha-circular-progress>`
: items.length === 0
? html`
@ -375,7 +375,7 @@ export class QuickBar extends LitElement {
const spinner = document.createElement("ha-circular-progress");
spinner.size = "small";
spinner.slot = "meta";
spinner.active = true;
spinner.indeterminate = true;
this._getItemAtIndex(index)?.appendChild(spinner);
}

View File

@ -91,7 +91,7 @@ class DialogRestart extends LitElement {
${this._loadingHostInfo
? html`
<div class="loader">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
`
: html`

View File

@ -85,8 +85,7 @@ export class TTSTryDialog extends LitElement {
? html`
<ha-circular-progress
size="small"
active
alt=""
indeterminate
slot="primaryAction"
class="loading"
></ha-circular-progress>

View File

@ -35,7 +35,7 @@ class HaInitPage extends LitElement {
`
: html`
<div id="progress-indicator-wrapper">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
<div id="loading-text">
${this.migration

View File

@ -46,7 +46,7 @@ class HassLoadingScreen extends LitElement {
`}
</div>`}
<div class="content">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
${this.message
? html`<div id="loading-text">${this.message}</div>`
: nothing}

View File

@ -57,7 +57,7 @@ class OnboardingCoreConfig extends LitElement {
}
if (this._skipCore) {
return html`<div class="row center">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>`;
}
return html`

View File

@ -123,7 +123,7 @@ class OnboardingLocation extends LitElement {
? html`
<ha-circular-progress
slot="trailingIcon"
active
indeterminate
size="small"
></ha-circular-progress>
`

View File

@ -64,8 +64,7 @@ class PanelCalendar extends LitElement {
private _end?: Date;
private _showPaneController = new ResizeController(this, {
callback: (entries: ResizeObserverEntry[]) =>
entries[0]?.contentRect.width > 750,
callback: (entries) => entries[0]?.contentRect.width > 750,
});
private _mql?: MediaQueryList;

View File

@ -227,7 +227,7 @@ export class DialogAddApplicationCredential extends LitElement {
${this._loading
? html`
<div slot="primaryAction" class="submit-spinner">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
`
: html`

View File

@ -101,7 +101,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
: this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_blueprints"
)
: html`<ha-circular-progress active></ha-circular-progress>`}
: html`<ha-circular-progress indeterminate></ha-circular-progress>`}
</div>
${this.config.use_blueprint.path

View File

@ -25,19 +25,16 @@ import {
html,
nothing,
} from "lit";
import { property, query, state } from "lit/decorators";
import { property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { copyToClipboard } from "../../../common/util/copy-clipboard";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
import {
AutomationConfig,
AutomationEntity,
@ -112,8 +109,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
@state() private _validationErrors?: (string | TemplateResult)[];
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
private _configSubscriptions: Record<
string,
(config?: AutomationConfig) => void
@ -342,8 +337,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
></manual-automation-editor>
`
: this._mode === "yaml"
? html`
${this._readOnly
? html` ${this._readOnly
? html`<ha-alert alert-type="warning">
${this.hass.localize(
"ui.panel.config.automation.editor.read_only"
@ -376,22 +370,13 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
`
: ""}
<ha-yaml-editor
copyClipboard
.hass=${this.hass}
.defaultValue=${this._preprocessYaml()}
.readOnly=${this._readOnly}
@value-changed=${this._yamlChanged}
></ha-yaml-editor>
<ha-card outlined>
<div class="card-actions">
<mwc-button @click=${this._copyYaml}>
${this.hass.localize(
"ui.panel.config.automation.editor.copy_to_clipboard"
)}
</mwc-button>
</div>
</ha-card>
`
: ``}
></ha-yaml-editor>`
: nothing}
</div>
`
: ""}
@ -612,15 +597,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
return cleanConfig;
}
private async _copyYaml(): Promise<void> {
if (this._yamlEditor?.yaml) {
await copyToClipboard(this._yamlEditor.yaml);
showToast(this, {
message: this.hass.localize("ui.common.copied_clipboard"),
});
}
}
private _yamlChanged(ev: CustomEvent) {
ev.stopPropagation();
if (!ev.detail.isValid) {
@ -776,9 +752,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
return [
haStyle,
css`
ha-card {
overflow: hidden;
}
.content {
padding-bottom: 20px;
}
@ -796,13 +769,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
}
ha-yaml-editor {
flex-grow: 1;
--actions-border-radius: 0;
--code-mirror-height: 100%;
min-height: 0;
}
.yaml-mode ha-card {
overflow: initial;
--ha-card-border-radius: 0;
border-bottom: 1px solid var(--divider-color);
display: flex;
flex-direction: column;
}
p {
margin-bottom: 0;

View File

@ -158,7 +158,7 @@ class HaConfigBackup extends LitElement {
${this._backupData.backing_up
? html`<ha-circular-progress
slot="icon"
active
indeterminate
></ha-circular-progress>`
: html`<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>`}
</ha-fab>

View File

@ -163,9 +163,9 @@ class DialogImportBlueprint extends LitElement {
>
${this._importing
? html`<ha-circular-progress
active
indeterminate
size="small"
.title=${this.hass.localize(
.ariaLabel=${this.hass.localize(
"ui.panel.config.blueprint.add.importing"
)}
></ha-circular-progress>`
@ -183,9 +183,9 @@ class DialogImportBlueprint extends LitElement {
>
${this._saving
? html`<ha-circular-progress
active
indeterminate
size="small"
.title=${this.hass.localize(
.ariaLabel=${this.hass.localize(
"ui.panel.config.blueprint.add.saving"
)}
></ha-circular-progress>`

View File

@ -93,7 +93,7 @@ export class CloudWebhooks extends LitElement {
? html`
<div class="progress">
<ha-circular-progress
active
indeterminate
></ha-circular-progress>
</div>
`

View File

@ -108,7 +108,7 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
></state-badge>
${this.narrow && entity.attributes.in_progress
? html`<ha-circular-progress
active
indeterminate
size="small"
slot="graphic"
class="absolute"
@ -128,7 +128,7 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
${!this.narrow
? entity.attributes.in_progress
? html`<ha-circular-progress
active
indeterminate
size="small"
slot="meta"
></ha-circular-progress>`

View File

@ -164,7 +164,9 @@ export class DialogHelperDetail extends LitElement {
</mwc-button>
`;
} else if (this._loading || this._helperFlows === undefined) {
content = html`<ha-circular-progress active></ha-circular-progress>`;
content = html`<ha-circular-progress
indeterminate
></ha-circular-progress>`;
} else {
const items: [string, string][] = [];

View File

@ -449,7 +449,7 @@ class AddIntegrationDialog extends LitElement {
>
</lit-virtualizer>
</mwc-list>`
: html`<ha-circular-progress active></ha-circular-progress>`} `;
: html`<ha-circular-progress indeterminate></ha-circular-progress>`} `;
}
private _keyFunction = (integration: IntegrationListItem) =>

View File

@ -57,7 +57,7 @@ class DialogMatterAddDevice extends LitElement {
)
: html`<ha-circular-progress
size="large"
active
indeterminate
></ha-circular-progress>`}
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>

View File

@ -102,7 +102,7 @@ class DialogZHAReconfigureDevice extends LitElement {
${this._status === "started"
? html`
<div class="flex-container">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
<div class="status">
<p>
<b>

View File

@ -98,8 +98,8 @@ class ZHAAddDevicesPage extends LitElement {
)}
</h1>
<ha-circular-progress
active
alt="Searching"
indeterminate
aria-label="Searching"
></ha-circular-progress>
`
: html`

View File

@ -98,9 +98,9 @@ export class ZHAAddGroupPage extends LitElement {
>
${this._processingAdd
? html`<ha-circular-progress
active
indeterminate
size="small"
.title=${this.hass!.localize(
.ariaLabel=${this.hass!.localize(
"ui.panel.config.zha.groups.creating_group"
)}
></ha-circular-progress>`

View File

@ -120,9 +120,8 @@ class ZHADeviceNeighbors extends LitElement {
return html`
${!this._devices
? html`<ha-circular-progress
alt="Loading"
size="large"
active
indeterminate
></ha-circular-progress>`
: html`<ha-data-table
.hass=${this.hass}

View File

@ -169,12 +169,14 @@ export class ZHAGroupPage extends LitElement {
@click=${this._removeMembersFromGroup}
class="button"
>
<ha-circular-progress
?active=${this._processingRemove}
alt=${this.hass.localize(
"ui.panel.config.zha.groups.removing_members"
)}
></ha-circular-progress>
${this._processingRemove
? html`<ha-circular-progress
indeterminate
.ariaLabel=${this.hass.localize(
"ui.panel.config.zha.groups.removing_members"
)}
></ha-circular-progress>`
: nothing}
${this.hass!.localize(
"ui.panel.config.zha.groups.remove_members"
)}</mwc-button
@ -208,7 +210,7 @@ export class ZHAGroupPage extends LitElement {
? html`<ha-circular-progress
active
size="small"
title="Saving"
aria-label="Saving"
></ha-circular-progress>`
: ""}
${this.hass!.localize(

View File

@ -116,7 +116,10 @@ class DialogZWaveJSAddNode extends LitElement {
>
${this._status === "loading"
? html`<div style="display: flex; justify-content: center;">
<ha-circular-progress size="large" active></ha-circular-progress>
<ha-circular-progress
size="large"
indeterminate
></ha-circular-progress>
</div>`
: this._status === "choose_strategy"
? html`<h3>Choose strategy</h3>
@ -288,7 +291,9 @@ class DialogZWaveJSAddNode extends LitElement {
"ui.panel.config.zwave_js.add_node.searching_device"
)}
</h3>
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress
indeterminate
></ha-circular-progress>
<p>
${this.hass.localize(
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
@ -304,7 +309,7 @@ class DialogZWaveJSAddNode extends LitElement {
)}
</h2>
<ha-circular-progress
active
indeterminate
></ha-circular-progress>
<p>
${this.hass.localize(
@ -358,7 +363,7 @@ class DialogZWaveJSAddNode extends LitElement {
? html`
<div class="flex-container">
<ha-circular-progress
active
indeterminate
></ha-circular-progress>
<div class="status">
<p>

View File

@ -97,7 +97,7 @@ class DialogZWaveJSRebuildNodeRoutes extends LitElement {
${this._status === "started"
? html`
<div class="flex-container">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
<div class="status">
<p>
${this.hass.localize(

View File

@ -68,7 +68,7 @@ class DialogZWaveJSReinterviewNode extends LitElement {
${this._status === "started"
? html`
<div class="flex-container">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
<div class="status">
<p>
<b>

View File

@ -91,7 +91,7 @@ class DialogZWaveJSRemoveFailedNode extends LitElement {
${this._status === "started"
? html`
<div class="flex-container">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
<div class="status">
<p>
<b>

View File

@ -71,7 +71,7 @@ class DialogZWaveJSRemoveNode extends LitElement {
${this._status === "started"
? html`
<div class="flex-container">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
<div class="status">
<p>
<b

View File

@ -171,7 +171,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
<div class="icon">
${this._status === "disconnected"
? html`<ha-circular-progress
active
indeterminate
></ha-circular-progress>`
: html`
<ha-svg-icon
@ -457,7 +457,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
: html`
<ha-circular-progress
size="small"
active
indeterminate
></ha-circular-progress>
`}
</div>

View File

@ -97,7 +97,7 @@ export class SystemLogCard extends LitElement {
${this._items === undefined
? html`
<div class="loading-container">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
`
: html`

View File

@ -69,7 +69,7 @@ export class HassioHostname extends LitElement {
<div class="card-actions">
<mwc-button @click=${this._save} .disabled=${this._processing}>
${this._processing
? html`<ha-circular-progress active size="small">
? html`<ha-circular-progress indeterminate size="small">
</ha-circular-progress>`
: this.hass.localize("ui.common.save")}
</mwc-button>

View File

@ -126,7 +126,7 @@ export class HassioNetwork extends LitElement {
.disabled=${this._scanning}
>
${this._scanning
? html`<ha-circular-progress active size="small">
? html`<ha-circular-progress indeterminate size="small">
</ha-circular-progress>`
: this.hass.localize(
"ui.panel.config.network.supervisor.scan_ap"
@ -242,7 +242,7 @@ export class HassioNetwork extends LitElement {
<div class="card-actions">
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
${this._processing
? html`<ha-circular-progress active size="small">
? html`<ha-circular-progress indeterminate size="small">
</ha-circular-progress>`
: this.hass.localize("ui.common.save")}
</mwc-button>

View File

@ -304,7 +304,7 @@ class DialogSystemInformation extends LitElement {
if (!this._systemInfo) {
sections.push(html`
<div class="loading-container">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
`);
} else {
@ -324,7 +324,10 @@ class DialogSystemInformation extends LitElement {
if (info.type === "pending") {
value = html`
<ha-circular-progress active size="tiny"></ha-circular-progress>
<ha-circular-progress
indeterminate
size="tiny"
></ha-circular-progress>
`;
} else if (info.type === "failed") {
value = html`

View File

@ -80,7 +80,7 @@ export class HaBlueprintScriptEditor extends LitElement {
: this.hass.localize(
"ui.panel.config.automation.editor.blueprint.no_blueprints"
)
: html`<ha-circular-progress active></ha-circular-progress>`}
: html`<ha-circular-progress indeterminate></ha-circular-progress>`}
</div>
${this.config.use_blueprint.path

View File

@ -26,7 +26,6 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import { computeRTL } from "../../../common/util/compute_rtl";
import { copyToClipboard } from "../../../common/util/copy-clipboard";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/ha-button-menu";
import "../../../components/ha-card";
@ -38,7 +37,6 @@ import type {
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
import { validateConfig } from "../../../data/config";
import { UNAVAILABLE } from "../../../data/entity";
import { EntityRegistryEntry } from "../../../data/entity_registry";
@ -94,8 +92,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
@state() private _readOnly = false;
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
@query("manual-script-editor")
private _manualEditor?: HaManualScriptEditor;
@ -405,24 +401,14 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
</div>
`
: this._mode === "yaml"
? html`
<ha-yaml-editor
.hass=${this.hass}
.defaultValue=${this._preprocessYaml()}
.readOnly=${this._readOnly}
@value-changed=${this._yamlChanged}
></ha-yaml-editor>
<ha-card outlined>
<div class="card-actions">
<mwc-button @click=${this._copyYaml}>
${this.hass.localize(
"ui.panel.config.automation.editor.copy_to_clipboard"
)}
</mwc-button>
</div>
</ha-card>
`
: ``}
? html` <ha-yaml-editor
copyClipboard
.hass=${this.hass}
.defaultValue=${this._preprocessYaml()}
.readOnly=${this._readOnly}
@value-changed=${this._yamlChanged}
></ha-yaml-editor>`
: nothing}
</div>
<ha-fab
slot="fab"
@ -735,15 +721,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
return this._config;
}
private async _copyYaml(): Promise<void> {
if (this._yamlEditor?.yaml) {
await copyToClipboard(this._yamlEditor.yaml);
showToast(this, {
message: this.hass.localize("ui.common.copied_clipboard"),
});
}
}
private _yamlChanged(ev: CustomEvent) {
ev.stopPropagation();
if (!ev.detail.isValid) {
@ -903,8 +880,11 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
}
ha-yaml-editor {
flex-grow: 1;
--actions-border-radius: 0;
--code-mirror-height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
}
.yaml-mode ha-card {
overflow: initial;

View File

@ -106,7 +106,11 @@ class MoveDatadiskDialog extends LitElement {
>
${this._moving
? html`
<ha-circular-progress alt="Moving" size="large" active>
<ha-circular-progress
aria-label="Moving"
size="large"
indeterminate
>
</ha-circular-progress>
<p class="progress-text">
${this.hass.localize(

View File

@ -194,7 +194,7 @@ export class DialogAddUser extends LitElement {
${this._loading
? html`
<div slot="primaryAction" class="submit-spinner">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
`
: html`

View File

@ -90,7 +90,7 @@ const renderProgress = (
return html``;
}
return html`
<ha-circular-progress size="tiny" active></ha-circular-progress>
<ha-circular-progress size="tiny" indeterminate></ha-circular-progress>
`;
}

View File

@ -184,6 +184,8 @@ class HaPanelDevService extends LitElement {
>
<div class="card-content">
<ha-yaml-editor
.hass=${this.hass}
copyClipboard
readOnly
autoUpdate
.value=${this._response}

View File

@ -131,7 +131,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
let stats: TemplateResult;
if (!this._stats5min || !this._statsHour) {
stats = html`<ha-circular-progress active></ha-circular-progress>`;
stats = html`<ha-circular-progress indeterminate></ha-circular-progress>`;
} else if (this._statsHour.length < 1 && this._stats5min.length < 1) {
stats = html`<p>
${this.hass.localize(

View File

@ -156,7 +156,7 @@ class HaPanelDevTemplate extends LitElement {
${this._rendering
? html`<ha-circular-progress
class="render-spinner"
active
indeterminate
size="small"
></ha-circular-progress>`
: ""}

View File

@ -77,7 +77,7 @@ export class DeveloperYamlConfig extends LitElement {
? html`<div
class="validate-container layout vertical center-center"
>
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div> `
: nothing
: html`
@ -94,7 +94,7 @@ export class DeveloperYamlConfig extends LitElement {
)
}
</div>
${
this._validateResult.errors
? html`<ha-alert
@ -233,7 +233,7 @@ export class DeveloperYamlConfig extends LitElement {
}
.validate-log {
white-space: pre;
white-space: pre-wrap;
direction: ltr;
}

View File

@ -193,10 +193,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
</div>
${this._isLoading
? html`<div class="progress-wrapper">
<ha-circular-progress
active
alt=${this.hass.localize("ui.common.loading")}
></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>`
: !this._targetPickerValue
? html`<div class="start-search">

View File

@ -107,10 +107,7 @@ export class HaLogbook extends LitElement {
if (this._logbookEntries === undefined) {
return html`
<div class="progress-wrapper">
<ha-circular-progress
active
alt=${this.hass.localize("ui.common.loading")}
></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
`;
}

View File

@ -1,25 +1,34 @@
import { mdiPower, mdiWaterPercent } from "@mdi/js";
import { mdiTuneVariant } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { LitElement, PropertyValues, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-control-select";
import type { ControlSelectOption } from "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import type { HaControlSelectMenu } from "../../../components/ha-control-select-menu";
import {
HumidifierEntityFeature,
HumidifierEntity,
computeHumidiferModeIcon,
} from "../../../data/humidifier";
import { UNAVAILABLE } from "../../../data/entity";
import { HumidifierEntity, HumidifierState } from "../../../data/humidifier";
import { HomeAssistant } from "../../../types";
import { LovelaceCardFeature } from "../types";
import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { HumidifierModesCardFeatureConfig } from "./types";
export const supportsHumidifierModesCardFeature = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "humidifier";
return (
domain === "humidifier" &&
supportsFeature(stateObj, HumidifierEntityFeature.MODES)
);
};
@customElement("hui-humidifier-modes-card-feature")
class HuiHumidifierModeCardFeature
class HuiHumidifierModesCardFeature
extends LitElement
implements LovelaceCardFeature
{
@ -29,14 +38,29 @@ class HuiHumidifierModeCardFeature
@state() private _config?: HumidifierModesCardFeatureConfig;
@state() _currentState?: HumidifierState;
@state() _currentMode?: string;
static getStubConfig(): HumidifierModesCardFeatureConfig {
@query("ha-control-select-menu", true)
private _haSelect?: HaControlSelectMenu;
static getStubConfig(
_,
stateObj?: HassEntity
): HumidifierModesCardFeatureConfig {
return {
type: "humidifier-modes",
style: "dropdown",
modes: stateObj?.attributes.available_modes || [],
};
}
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
await import(
"../editor/config-elements/hui-humidifier-modes-card-feature-editor"
);
return document.createElement("hui-humidifier-modes-card-feature-editor");
}
public setConfig(config: HumidifierModesCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
@ -47,33 +71,46 @@ class HuiHumidifierModeCardFeature
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (changedProp.has("stateObj") && this.stateObj) {
this._currentState = this.stateObj.state as HumidifierState;
this._currentMode = this.stateObj.attributes.mode;
}
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (this._haSelect && changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (
this.hass &&
this.hass.formatEntityAttributeValue !==
oldHass?.formatEntityAttributeValue
) {
this._haSelect.layoutOptions();
}
}
}
private async _valueChanged(ev: CustomEvent) {
const newState = (ev.detail as any).value as HumidifierState;
const mode =
(ev.detail as any).value ?? ((ev.target as any).value as string);
if (newState === this.stateObj!.state) return;
const oldMode = this.stateObj!.attributes.mode;
const oldState = this.stateObj!.state as HumidifierState;
this._currentState = newState;
if (mode === oldMode) return;
this._currentMode = mode;
try {
await this._setState(newState);
await this._setMode(mode);
} catch (err) {
this._currentState = oldState;
this._currentMode = oldMode;
}
}
private async _setState(newState: HumidifierState) {
await this.hass!.callService(
"humidifier",
newState === "on" ? "turn_on" : "turn_off",
{
entity_id: this.stateObj!.entity_id,
}
);
private async _setMode(mode: string) {
await this.hass!.callService("humidifier", "set_mode", {
entity_id: this.stateObj!.entity_id,
mode: mode,
});
}
protected render(): TemplateResult | null {
@ -86,34 +123,75 @@ class HuiHumidifierModeCardFeature
return null;
}
const color = stateColorCss(this.stateObj);
const stateObj = this.stateObj;
const options = ["on", "off"].map<ControlSelectOption>((entityState) => ({
value: entityState,
label: this.hass!.formatEntityState(this.stateObj!, entityState),
path: entityState === "on" ? mdiWaterPercent : mdiPower,
}));
const modes = stateObj.attributes.available_modes || [];
const options = modes
.filter((mode) => (this._config!.modes || []).includes(mode))
.map<ControlSelectOption>((mode) => ({
value: mode,
label: this.hass!.formatEntityAttributeValue(
this.stateObj!,
"mode",
mode
),
path: computeHumidiferModeIcon(mode),
}));
if (this._config.style === "icons") {
return html`
<div class="container">
<ha-control-select
.options=${options}
.value=${this._currentMode}
@value-changed=${this._valueChanged}
hide-label
.ariaLabel=${this.hass!.formatEntityAttributeName(stateObj, "mode")}
.disabled=${this.stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
</div>
`;
}
return html`
<div class="container">
<ha-control-select
.options=${options}
.value=${this._currentState}
@value-changed=${this._valueChanged}
<ha-control-select-menu
show-arrow
hide-label
.ariaLabel=${this.hass.localize("ui.card.humidifier.state")}
style=${styleMap({
"--control-select-color": color,
})}
.disabled=${this.stateObj!.state === UNAVAILABLE}
.label=${this.hass!.formatEntityAttributeName(stateObj, "mode")}
.value=${this._currentMode}
.disabled=${this.stateObj.state === UNAVAILABLE}
fixedMenuPosition
naturalMenuWidth
@selected=${this._valueChanged}
@closed=${stopPropagation}
>
</ha-control-select>
<ha-svg-icon slot="icon" .path=${mdiTuneVariant}></ha-svg-icon>
${options.map(
(option) => html`
<ha-list-item .value=${option.value} graphic="icon">
<ha-svg-icon slot="graphic" .path=${option.path}></ha-svg-icon>
${option.label}
</ha-list-item>
`
)}
</ha-control-select-menu>
</div>
`;
}
static get styles() {
return css`
ha-control-select-menu {
box-sizing: border-box;
--control-select-menu-height: 40px;
--control-select-menu-border-radius: 10px;
line-height: 1.2;
display: block;
width: 100%;
}
ha-control-select {
--control-select-color: var(--feature-color);
--control-select-padding: 0;
@ -131,6 +209,6 @@ class HuiHumidifierModeCardFeature
declare global {
interface HTMLElementTagNameMap {
"hui-humidifier-modes-card-feature": HuiHumidifierModeCardFeature;
"hui-humidifier-modes-card-feature": HuiHumidifierModesCardFeature;
}
}

View File

@ -0,0 +1,136 @@
import { mdiPower, mdiWaterPercent } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { LitElement, PropertyValues, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-control-select";
import type { ControlSelectOption } from "../../../components/ha-control-select";
import { UNAVAILABLE } from "../../../data/entity";
import { HumidifierEntity, HumidifierState } from "../../../data/humidifier";
import { HomeAssistant } from "../../../types";
import { LovelaceCardFeature } from "../types";
import { HumidifierToggleCardFeatureConfig } from "./types";
export const supportsHumidifierToggleCardFeature = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "humidifier";
};
@customElement("hui-humidifier-toggle-card-feature")
class HuiHumidifierToggleCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public stateObj?: HumidifierEntity;
@state() private _config?: HumidifierToggleCardFeatureConfig;
@state() _currentState?: HumidifierState;
static getStubConfig(): HumidifierToggleCardFeatureConfig {
return {
type: "humidifier-toggle",
};
}
public setConfig(config: HumidifierToggleCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (changedProp.has("stateObj") && this.stateObj) {
this._currentState = this.stateObj.state as HumidifierState;
}
}
private async _valueChanged(ev: CustomEvent) {
const newState = (ev.detail as any).value as HumidifierState;
if (newState === this.stateObj!.state) return;
const oldState = this.stateObj!.state as HumidifierState;
this._currentState = newState;
try {
await this._setState(newState);
} catch (err) {
this._currentState = oldState;
}
}
private async _setState(newState: HumidifierState) {
await this.hass!.callService(
"humidifier",
newState === "on" ? "turn_on" : "turn_off",
{
entity_id: this.stateObj!.entity_id,
}
);
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.stateObj ||
!supportsHumidifierToggleCardFeature(this.stateObj)
) {
return null;
}
const color = stateColorCss(this.stateObj);
const options = ["on", "off"].map<ControlSelectOption>((entityState) => ({
value: entityState,
label: this.hass!.formatEntityState(this.stateObj!, entityState),
path: entityState === "on" ? mdiWaterPercent : mdiPower,
}));
return html`
<div class="container">
<ha-control-select
.options=${options}
.value=${this._currentState}
@value-changed=${this._valueChanged}
hide-label
.ariaLabel=${this.hass.localize("ui.card.humidifier.state")}
style=${styleMap({
"--control-select-color": color,
})}
.disabled=${this.stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
</div>
`;
}
static get styles() {
return css`
ha-control-select {
--control-select-color: var(--feature-color);
--control-select-padding: 0;
--control-select-thickness: 40px;
--control-select-border-radius: 10px;
--control-select-button-border-radius: 10px;
}
.container {
padding: 0 12px 12px 12px;
width: auto;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-humidifier-toggle-card-feature": HuiHumidifierToggleCardFeature;
}
}

View File

@ -0,0 +1,127 @@
import { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/ha-control-slider";
import { UNAVAILABLE } from "../../../data/entity";
import { HumidifierEntity } from "../../../data/humidifier";
import { HomeAssistant } from "../../../types";
import { LovelaceCardFeature } from "../types";
import { TargetHumidityCardFeatureConfig } from "./types";
export const supportsTargetHumidityCardFeature = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "humidifier";
};
@customElement("hui-target-humidity-card-feature")
class HuiTargetHumidityCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public stateObj?: HumidifierEntity;
@state() private _config?: TargetHumidityCardFeatureConfig;
@state() private _targetHumidity?: number;
static getStubConfig(): TargetHumidityCardFeatureConfig {
return {
type: "target-humidity",
};
}
public setConfig(config: TargetHumidityCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (changedProp.has("stateObj")) {
this._targetHumidity = this.stateObj!.attributes.humidity;
}
}
private get _step() {
return 1;
}
private get _min() {
return this.stateObj!.attributes.min_humidity ?? 0;
}
private get _max() {
return this.stateObj!.attributes.max_humidity ?? 100;
}
private _valueChanged(ev: CustomEvent) {
const value = (ev.detail as any).value;
if (isNaN(value)) return;
this._targetHumidity = value;
this._callService();
}
private _callService() {
this.hass!.callService("humidifier", "set_humidity", {
entity_id: this.stateObj!.entity_id,
humidity: this._targetHumidity,
});
}
protected render() {
if (
!this._config ||
!this.hass ||
!this.stateObj ||
!supportsTargetHumidityCardFeature(this.stateObj)
) {
return nothing;
}
return html`
<div class="container">
<ha-control-slider
.value=${this.stateObj.attributes.humidity}
.min=${this._min}
.max=${this._max}
.step=${this._step}
.disabled=${this.stateObj!.state === UNAVAILABLE}
@value-changed=${this._valueChanged}
.label=${this.hass.formatEntityAttributeName(
this.stateObj,
"humidity"
)}
unit="%"
.locale=${this.hass.locale}
></ha-control-slider>
</div>
`;
}
static get styles() {
return css`
ha-control-slider {
--control-slider-color: var(--feature-color);
--control-slider-background: var(--feature-color);
--control-slider-background-opacity: 0.2;
--control-slider-thickness: 40px;
--control-slider-border-radius: 10px;
}
.container {
padding: 0 12px 12px 12px;
width: auto;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-target-humidity-card-feature": HuiTargetHumidityCardFeature;
}
}

View File

@ -55,6 +55,10 @@ export interface NumericInputCardFeatureConfig {
style?: "buttons" | "slider";
}
export interface TargetHumidityCardFeatureConfig {
type: "target-humidity";
}
export interface TargetTemperatureCardFeatureConfig {
type: "target-temperature";
}
@ -66,6 +70,12 @@ export interface WaterHeaterOperationModesCardFeatureConfig {
export interface HumidifierModesCardFeatureConfig {
type: "humidifier-modes";
style?: "dropdown" | "icons";
modes?: string[];
}
export interface HumidifierToggleCardFeatureConfig {
type: "humidifier-toggle";
}
export const VACUUM_COMMANDS = [
@ -101,11 +111,13 @@ export type LovelaceCardFeatureConfig =
| CoverTiltPositionCardFeatureConfig
| CoverTiltCardFeatureConfig
| FanSpeedCardFeatureConfig
| HumidifierToggleCardFeatureConfig
| HumidifierModesCardFeatureConfig
| LawnMowerCommandsCardFeatureConfig
| LightBrightnessCardFeatureConfig
| LightColorTempCardFeatureConfig
| VacuumCommandsCardFeatureConfig
| TargetHumidityCardFeatureConfig
| TargetTemperatureCardFeatureConfig
| WaterHeaterOperationModesCardFeatureConfig
| SelectOptionsCardFeatureConfig

View File

@ -51,7 +51,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
entity: foundEntities[0] || "",
features: [
{
type: "humidifier-modes",
type: "humidifier-toggle",
},
],
};
@ -180,6 +180,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
max-width: 344px; /* 12px + 12px + 320px */
padding: 0 12px 12px 12px;
box-sizing: border-box;
--interaction-margin: 0px;
}
.more-info {

View File

@ -101,6 +101,8 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
}
this._config = config;
this.updateComplete.then(() => this._measureCard());
}
public connectedCallback(): void {
@ -339,15 +341,12 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
protected firstUpdated(): void {
this._attachObserver();
this._measureCard();
}
public willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (!this.hasUpdated) {
this._measureCard();
}
if (
!this._config ||
!this.hass ||
@ -468,6 +467,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
private _measureCard() {
const card = this.shadowRoot!.querySelector("ha-card");
if (!card) {
return;
}

View File

@ -46,7 +46,7 @@ export class HuiStartingCard extends LitElement implements LovelaceCard {
return html`
<div class="content">
<ha-circular-progress active></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
${this.hass.localize("ui.panel.lovelace.cards.starting.description")}
</div>
`;

View File

@ -172,6 +172,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
max-width: 344px; /* 12px + 12px + 320px */
padding: 0 12px 12px 12px;
box-sizing: border-box;
--interaction-margin: 0px;
}
.more-info {

View File

@ -22,6 +22,7 @@ import memoizeOne from "memoize-one";
import type { SortableEvent } from "sortablejs";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../components/ha-card";
import "../../../components/ha-checkbox";
import "../../../components/ha-icon-button";
@ -347,7 +348,7 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
.title=${this.hass!.localize(
"ui.panel.lovelace.cards.todo-list.drag_and_drop"
)}
class="reorderButton"
class="reorderButton handle"
.path=${mdiDrag}
>
</ha-svg-icon>
@ -439,7 +440,21 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
}
const checkedItems = this._getCheckedItems(this._items);
const uids = checkedItems.map((item: TodoItem) => item.uid);
deleteItems(this.hass!, this._entityId!, uids);
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.lovelace.cards.todo-list.delete_confirm_title"
),
text: this.hass.localize(
"ui.panel.lovelace.cards.todo-list.delete_confirm_text",
{ number: uids.length }
),
dismissText: this.hass.localize("ui.common.cancel"),
confirmText: this.hass.localize("ui.common.delete"),
destructive: true,
confirm: () => {
deleteItems(this.hass!, this._entityId!, uids);
},
});
}
private get _newItem(): HaTextField {
@ -583,6 +598,10 @@ export class HuiTodoListCard extends LitElement implements LovelaceCard {
direction: var(--direction);
}
.handle {
cursor: move;
}
ha-checkbox {
margin-left: -12px;
margin-inline-start: -12px;

View File

@ -263,7 +263,7 @@ export class HuiImage extends LitElement {
>
<ha-circular-progress
class="render-spinner"
active
indeterminate
size="small"
></ha-circular-progress>
</div>`

View File

@ -7,12 +7,14 @@ import "../card-features/hui-cover-tilt-card-feature";
import "../card-features/hui-cover-tilt-position-card-feature";
import "../card-features/hui-fan-speed-card-feature";
import "../card-features/hui-humidifier-modes-card-feature";
import "../card-features/hui-humidifier-toggle-card-feature";
import "../card-features/hui-lawn-mower-commands-card-feature";
import "../card-features/hui-light-brightness-card-feature";
import "../card-features/hui-light-color-temp-card-feature";
import "../card-features/hui-numeric-input-card-feature";
import "../card-features/hui-select-options-card-feature";
import "../card-features/hui-target-temperature-card-feature";
import "../card-features/hui-target-humidity-card-feature";
import "../card-features/hui-vacuum-commands-card-feature";
import "../card-features/hui-water-heater-operation-modes-card-feature";
import { LovelaceCardFeatureConfig } from "../card-features/types";
@ -31,11 +33,13 @@ const TYPES: Set<LovelaceCardFeatureConfig["type"]> = new Set([
"cover-tilt",
"fan-speed",
"humidifier-modes",
"humidifier-toggle",
"lawn-mower-commands",
"light-brightness",
"light-color-temp",
"numeric-input",
"select-options",
"target-humidity",
"target-temperature",
"vacuum-commands",
"water-heater-operation-modes",

View File

@ -142,8 +142,7 @@ export class HuiCardPicker extends LitElement {
html`
<div class="card spinner">
<ha-circular-progress
active
alt="Loading"
indeterminate
></ha-circular-progress>
</div>
`
@ -238,7 +237,7 @@ export class HuiCardPicker extends LitElement {
this._renderCardElement(card),
html`
<div class="card spinner">
<ha-circular-progress active alt="Loading"></ha-circular-progress>
<ha-circular-progress indeterminate></ha-circular-progress>
</div>
`
)}`,

View File

@ -235,8 +235,8 @@ export class HuiDialogEditCard
${this._error
? html`
<ha-circular-progress
active
alt="Can't update card"
indeterminate
aria-label="Can't update card"
></ha-circular-progress>
`
: ``}
@ -271,8 +271,8 @@ export class HuiDialogEditCard
${this._saving
? html`
<ha-circular-progress
active
title="Saving"
indeterminate
aria-label="Saving"
size="small"
></ha-circular-progress>
`

View File

@ -110,8 +110,8 @@ export class HuiDialogSuggestCard extends LitElement {
${this._saving
? html`
<ha-circular-progress
active
title="Saving"
indeterminate
aria-label="Saving"
size="small"
></ha-circular-progress>
`

View File

@ -29,12 +29,14 @@ import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-
import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature";
import { supportsCoverTiltPositionCardFeature } from "../../card-features/hui-cover-tilt-position-card-feature";
import { supportsFanSpeedCardFeature } from "../../card-features/hui-fan-speed-card-feature";
import { supportsHumidifierToggleCardFeature } from "../../card-features/hui-humidifier-toggle-card-feature";
import { supportsHumidifierModesCardFeature } from "../../card-features/hui-humidifier-modes-card-feature";
import { supportsLawnMowerCommandCardFeature } from "../../card-features/hui-lawn-mower-commands-card-feature";
import { supportsLightBrightnessCardFeature } from "../../card-features/hui-light-brightness-card-feature";
import { supportsLightColorTempCardFeature } from "../../card-features/hui-light-color-temp-card-feature";
import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature";
import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature";
import { supportsTargetHumidityCardFeature } from "../../card-features/hui-target-humidity-card-feature";
import { supportsTargetTemperatureCardFeature } from "../../card-features/hui-target-temperature-card-feature";
import { supportsVacuumCommandsCardFeature } from "../../card-features/hui-vacuum-commands-card-feature";
import { supportsWaterHeaterOperationModesCardFeature } from "../../card-features/hui-water-heater-operation-modes-card-feature";
@ -54,10 +56,12 @@ const UI_FEATURE_TYPES = [
"cover-tilt",
"fan-speed",
"humidifier-modes",
"humidifier-toggle",
"lawn-mower-commands",
"light-brightness",
"light-color-temp",
"select-options",
"target-humidity",
"target-temperature",
"vacuum-commands",
"water-heater-operation-modes",
@ -70,6 +74,7 @@ const EDITABLES_FEATURE_TYPES = new Set<UiFeatureTypes>([
"vacuum-commands",
"alarm-modes",
"climate-hvac-modes",
"humidifier-modes",
"water-heater-operation-modes",
"lawn-mower-commands",
"climate-preset-modes",
@ -89,10 +94,12 @@ const SUPPORTS_FEATURE_TYPES: Record<
"cover-tilt": supportsCoverTiltCardFeature,
"fan-speed": supportsFanSpeedCardFeature,
"humidifier-modes": supportsHumidifierModesCardFeature,
"humidifier-toggle": supportsHumidifierToggleCardFeature,
"lawn-mower-commands": supportsLawnMowerCommandCardFeature,
"light-brightness": supportsLightBrightnessCardFeature,
"light-color-temp": supportsLightColorTempCardFeature,
"numeric-input": supportsNumericInputCardFeature,
"target-humidity": supportsTargetHumidityCardFeature,
"target-temperature": supportsTargetTemperatureCardFeature,
"vacuum-commands": supportsVacuumCommandsCardFeature,
"water-heater-operation-modes": supportsWaterHeaterOperationModesCardFeature,

View File

@ -14,15 +14,22 @@ import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
import type { HumidifierCardConfig } from "../../cards/types";
import {
LovelaceCardFeatureConfig,
LovelaceCardFeatureContext,
} from "../../card-features/types";
import type { HumidifierCardConfig } from "../../cards/types";
import type { LovelaceCardEditor } from "../../types";
import "../hui-sub-element-editor";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { EditSubElementEvent, SubElementEditorConfig } from "../types";
import "./hui-card-features-editor";
import type { FeatureType } from "./hui-card-features-editor";
const COMPATIBLE_FEATURES_TYPES: FeatureType[] = [
"humidifier-modes",
"humidifier-toggle",
];
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@ -103,6 +110,7 @@ export class HuiHumidifierCardEditor
<hui-card-features-editor
.hass=${this.hass}
.stateObj=${stateObj}
.featuresTypes=${COMPATIBLE_FEATURES_TYPES}
.features=${this._config!.features ?? []}
@features-changed=${this._featuresChanged}
@edit-detail-element=${this._editDetailElement}

View File

@ -0,0 +1,129 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { FormatEntityAttributeValueFunc } from "../../../../common/translations/entity-state";
import { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-form/ha-form";
import type {
HaFormSchema,
SchemaUnion,
} from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
import {
HumidifierModesCardFeatureConfig,
LovelaceCardFeatureContext,
} from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types";
@customElement("hui-humidifier-modes-card-feature-editor")
export class HuiHumidifierModesCardFeatureEditor
extends LitElement
implements LovelaceCardFeatureEditor
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state() private _config?: HumidifierModesCardFeatureConfig;
public setConfig(config: HumidifierModesCardFeatureConfig): void {
this._config = config;
}
private _schema = memoizeOne(
(
localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc,
stateObj?: HassEntity
) =>
[
{
name: "style",
selector: {
select: {
multiple: false,
mode: "list",
options: ["dropdown", "icons"].map((mode) => ({
value: mode,
label: localize(
`ui.panel.lovelace.editor.features.types.humidifier-modes.style_list.${mode}`
),
})),
},
},
},
{
name: "modes",
selector: {
select: {
multiple: true,
mode: "list",
options:
stateObj?.attributes.available_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(stateObj, "mode", mode),
})) || [],
},
},
},
] as const satisfies readonly HaFormSchema[]
);
protected render() {
if (!this.hass || !this._config) {
return nothing;
}
const stateObj = this.context?.entity_id
? this.hass.states[this.context?.entity_id]
: undefined;
const data: HumidifierModesCardFeatureConfig = {
style: "dropdown",
modes: [],
...this._config,
};
const schema = this._schema(
this.hass.localize,
this.hass.formatEntityAttributeValue,
stateObj
);
return html`
<ha-form
.hass=${this.hass}
.data=${data}
.schema=${schema}
.computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged}
></ha-form>
`;
}
private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value });
}
private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
) => {
switch (schema.name) {
case "style":
case "modes":
return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.humidifier-modes.${schema.name}`
);
default:
return "";
}
};
}
declare global {
interface HTMLElementTagNameMap {
"hui-humidifier-modes-card-feature-editor": HuiHumidifierModesCardFeatureEditor;
}
}

View File

@ -87,6 +87,8 @@ const HIDDEN_ATTRIBUTES = [
"unit_of_measurement",
"visibility_unit",
"wind_speed_unit",
"battery_icon",
"battery_level",
];
const cardConfigStruct = assign(

View File

@ -132,9 +132,9 @@ export class HuiSaveConfig extends LitElement implements HassDialog {
>
${this._saving
? html`<ha-circular-progress
active
indeterminate
size="small"
title="Saving"
aria-label="Saving"
></ha-circular-progress>`
: ""}
${this.hass!.localize(

View File

@ -207,8 +207,7 @@ export abstract class HuiElementEditor<T, C = any> extends LitElement {
${this._loading
? html`
<ha-circular-progress
active
alt="Loading"
indeterminate
class="center margin-bot"
></ha-circular-progress>
`

View File

@ -63,9 +63,9 @@ export class HuiDialogEditLovelace extends LitElement {
>
${this._saving
? html`<ha-circular-progress
active
indeterminate
size="small"
title="Saving"
aria-label="Saving"
></ha-circular-progress>`
: ""}
${this.hass!.localize("ui.common.save")}</mwc-button

View File

@ -323,9 +323,9 @@ export class HuiDialogEditView extends LitElement {
>
${this._saving
? html`<ha-circular-progress
active
indeterminate
size="small"
title="Saving"
aria-label="Saving"
></ha-circular-progress>`
: ""}
${this.hass!.localize("ui.common.save")}</mwc-button
@ -528,7 +528,7 @@ export class HuiDialogEditView extends LitElement {
ha-circular-progress {
display: none;
}
ha-circular-progress[active] {
ha-circular-progress[indeterminate] {
display: block;
}
.selected_menu_item {

View File

@ -111,7 +111,10 @@ export class HuiGraphHeaderFooter
if (!this._coordinates) {
return html`
<div class="container">
<ha-circular-progress active size="small"></ha-circular-progress>
<ha-circular-progress
indeterminate
size="small"
></ha-circular-progress>
</div>
`;
}

Some files were not shown because too many files have changed in this diff Show More