Compare commits

...

57 Commits

Author SHA1 Message Date
Bram Kragten
e98eb8de7f Add inital form data to hui-form-editor 2023-12-12 09:58:50 +01:00
renovate[bot]
12e6701ffa Update dependency marked to v11.0.1 (#19007) 2023-12-11 20:56:46 -05:00
karwosts
7167b66719 Don't leave empty arrays in the service control target (#18986) 2023-12-11 11:19:28 +01:00
dependabot[bot]
a52ba5fbd1 Bump actions/stale from 8.0.0 to 9.0.0 (#19004) 2023-12-11 08:06:47 +01:00
dependabot[bot]
22cf903656 Bump actions/setup-python from 4 to 5 (#19005) 2023-12-11 08:05:40 +01:00
dependabot[bot]
d77b657036 Bump actions/labeler from 4.3.0 to 5.0.0 (#19003) 2023-12-11 07:46:59 +01:00
renovate[bot]
4a3038c12c Update dependency hls.js to v1.4.13 (#18994) 2023-12-10 22:02:24 -05:00
renovate[bot]
3ac7cd5d4a Update dependency @codemirror/state to v6.3.3 (#18984) 2023-12-09 20:57:44 -05:00
renovate[bot]
58eddd2b42 Update dependency typescript to v5.3.3 (#18993) 2023-12-09 16:44:38 -05:00
renovate[bot]
d808da68bd Update dependency @lokalise/node-api to v12.1.0 (#18973) 2023-12-08 22:09:23 -05:00
renovate[bot]
2ed4d1efa0 Update dependency @braintree/sanitize-url to v7 (#18975) 2023-12-08 22:00:36 -05:00
Paul Bottein
fcb9e13a84 Bumped version to 20231208.2 2023-12-08 14:49:04 +01:00
Bram Kragten
3ada2f3279 Fix label when there is no target (#18969) 2023-12-08 13:38:01 +00:00
Paul Bottein
8d2d45ae4e Add dropdown style to hvac_modes feature (#18963) 2023-12-08 13:43:59 +01:00
Simon Lamon
c9e6963387 Fix todo url (#18954)
* Fix todo url

* Move searchParams

* Update src/panels/todo/ha-panel-todo.ts

* check if saved entity exists

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-12-08 13:35:57 +01:00
Paul Bottein
6d36b0e28c Hide climate mode control on default dashboard if there is only one hvac mode (#18964)
Hide hvac mode on default dashboard if there is only one hvac mode
2023-12-08 10:58:08 +00:00
Paul Bottein
61117bb34f Bumped version to 20231208.1 2023-12-08 10:30:33 +01:00
Simon Lamon
86a3c32844 Fix area in device picker (#18955)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-12-08 09:17:40 +00:00
Paul Bottein
b855b3e103 Format number attribute for media player (#18949) 2023-12-08 10:14:55 +01:00
renovate[bot]
80edeebab9 Update dependency chart.js to v4.4.1 (#18957) 2023-12-07 20:38:49 -05:00
renovate[bot]
f366e287b1 Update typescript-eslint monorepo to v6.13.2 (#18953) 2023-12-07 20:34:08 -05:00
renovate[bot]
9ce8684aba Update dependency lint-staged to v15.2.0 (#18930) 2023-12-07 20:32:47 -05:00
renovate[bot]
caa6ea531c Update dependency @types/luxon to v3.3.7 (#18956) 2023-12-07 20:28:26 -05:00
Paul Bottein
cca1183ee3 Revert "Remove card features for humidifier and climate on default dashboard" (#18944)
* Revert "Remove card features for humidifier and climate on default dashboard (#18747)"

This reverts commit 2afd2788e2.

* Rename humidifier feature
2023-12-07 16:53:07 +01:00
Paul Bottein
0f9c97aea0 Center login content for every screen size (#18943) 2023-12-07 13:45:05 +00:00
Paul Bottein
8d08aa8c79 Fix tile card interaction when border width is set to 0 (#18941) 2023-12-07 14:40:23 +01:00
renovate[bot]
eebcab435d Update dependency rollup-plugin-visualizer to v5.10.0 (#18936)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-07 11:15:26 +01:00
Bram Kragten
76d3c6e237 Revert fetching config flow after subscribe for progress (#18939) 2023-12-07 09:31:34 +01:00
karwosts
a820ca1e90 Fix number selector display for 0 (#18927) 2023-12-06 17:03:44 +01:00
Paul Bottein
39260d172f Bumped version to 20231206.0 2023-12-06 14:23:09 +01:00
Paul Bottein
e646528b86 Fix empty classmap in state control (#18922)
* Fix empy classmap in state control

* Don't use class map
2023-12-06 14:22:32 +01:00
Bram Kragten
86726102fb Fix issues with circular progress (#18920) 2023-12-06 14:21:28 +01:00
Bram Kragten
1330558819 Force media player browser dialog re-layout after open animation (#18910) 2023-12-06 14:14:14 +01:00
Paul Bottein
b4ab0fc10b Reduce sensitivity of the circular slider on touch devices (#18921) 2023-12-06 14:13:19 +01:00
Paul Bottein
15becf9ef6 Add ellipsis to thermostat and humidifier card title (#18924) 2023-12-06 13:28:03 +01:00
Paul Bottein
ef3785ce9f Fix particles over alert in login screen (#18923) 2023-12-06 13:16:47 +01:00
renovate[bot]
134e13005d Update dependency eslint-config-prettier to v9.1.0 (#18909) 2023-12-05 18:18:46 -05:00
Bram Kragten
dccd9b2541 Bumped version to 20231205.0 2023-12-05 18:07:59 +01:00
Paul Bottein
de83ad7a7a Reduce circular slider sensitivity when used in card (#18908)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-12-05 18:06:29 +01:00
Paul Bottein
7e55b9e6b8 Add humidifier modes and toggle feature (#18912) 2023-12-05 18:06:03 +01:00
Paul Bottein
1b74ca47bf Add target humidity feature (#18913) 2023-12-05 16:42:07 +01:00
Bram Kragten
c3d4be9ceb wrap config validation (#18914) 2023-12-05 14:32:35 +00:00
karwosts
b8d0c7f7c7 Consistent sortable cursor (#18897) 2023-12-05 10:45:20 +01:00
karwosts
92bce4078f Confirmation dialog for todo clear checked items (#18905) 2023-12-05 10:44:40 +01:00
Bram Kragten
ff8f0697c2 Fix media control card background image (#18891) 2023-12-05 09:15:04 +01:00
renovate[bot]
dddba7af00 Update dependency eslint to v8.55.0 (#18904) 2023-12-05 02:19:03 +00:00
karwosts
14a49d6664 Copy to clipboard button for service response (#18872) 2023-12-04 14:24:35 +00:00
Bram Kragten
22da402d56 Fix border radius for outlined icon button old browsers (#18888) 2023-12-04 15:17:43 +01:00
Bram Kragten
aa4acc6572 Use resize polyfill from lit virtualizer (#18886) 2023-12-04 08:34:31 -05:00
Simon Lamon
4d432fba57 Hide battery level and battery icon in tile card (#18826) 2023-12-04 14:26:45 +01:00
G Johansson
97b71c785b Fix use of "" on lock entity more info (use default code) (#18706)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-12-04 13:23:11 +00:00
Paul Bottein
8a93284bb3 Use resize-controller instead of container queries (#18885) 2023-12-04 14:16:10 +01:00
renovate[bot]
bb2abe4efc Update dependency marked to v11 (#18860)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-12-04 13:11:51 +00:00
Simon Lamon
ccada33caf Accessibility improvements for circular progress indicators (#18506) 2023-12-04 14:06:25 +01:00
ildar170975
c5f15ee6ba Update ha-chart-base.ts: fix "Tooltip: a long name may overflow" (#18849) 2023-12-04 14:05:02 +01:00
renovate[bot]
6f240ec681 Update vaadinWebComponents monorepo to v24.2.5 (#18887)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 12:59:01 +00:00
renovate[bot]
fc6aef138d Update dependency fs-extra to v11.2.0 (#18848)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 13:57:40 +01:00
133 changed files with 2228 additions and 1180 deletions

View File

@@ -10,6 +10,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Apply labels
uses: actions/labeler@v4.3.0
uses: actions/labeler@v5.0.0
with:
sync-labels: true

View File

@@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v4.1.1
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

View File

@@ -29,7 +29,7 @@ jobs:
uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 90 days stale policy
uses: actions/stale@v8.0.0
uses: actions/stale@v9.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90

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

@@ -26,13 +26,13 @@
"type": "module",
"dependencies": {
"@babel/runtime": "7.23.5",
"@braintree/sanitize-url": "6.0.4",
"@braintree/sanitize-url": "7.0.0",
"@codemirror/autocomplete": "6.11.1",
"@codemirror/commands": "6.3.2",
"@codemirror/language": "6.9.3",
"@codemirror/legacy-modes": "6.3.3",
"@codemirror/search": "6.5.5",
"@codemirror/state": "6.3.2",
"@codemirror/state": "6.3.3",
"@codemirror/view": "6.22.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.12.0",
@@ -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",
@@ -100,7 +99,7 @@
"@webcomponents/scoped-custom-element-registry": "0.0.9",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"chart.js": "4.4.0",
"chart.js": "4.4.1",
"comlink": "4.4.1",
"core-js": "3.33.3",
"cropperjs": "1.6.1",
@@ -111,7 +110,7 @@
"element-internals-polyfill": "1.3.9",
"fuse.js": "7.0.0",
"google-timezones-json": "1.2.0",
"hls.js": "1.4.12",
"hls.js": "1.4.13",
"home-assistant-js-websocket": "9.1.0",
"idb-keyval": "6.2.1",
"intl-messageformat": "10.5.8",
@@ -120,14 +119,13 @@
"leaflet-draw": "1.0.4",
"lit": "2.8.0",
"luxon": "3.4.4",
"marked": "10.0.0",
"marked": "11.0.1",
"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",
@@ -160,7 +158,7 @@
"@babel/preset-typescript": "7.23.3",
"@bundle-stats/plugin-webpack-filter": "4.8.3",
"@koa/cors": "4.0.0",
"@lokalise/node-api": "12.0.0",
"@lokalise/node-api": "12.1.0",
"@octokit/auth-oauth-device": "6.0.1",
"@octokit/plugin-retry": "6.0.1",
"@octokit/rest": "20.0.2",
@@ -178,7 +176,7 @@
"@types/js-yaml": "4.0.9",
"@types/leaflet": "1.9.8",
"@types/leaflet-draw": "1.0.11",
"@types/luxon": "3.3.6",
"@types/luxon": "3.3.7",
"@types/mocha": "10.0.6",
"@types/qrcode": "1.5.5",
"@types/serve-handler": "6.1.4",
@@ -186,18 +184,18 @@
"@types/tar": "6.1.10",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "6.13.1",
"@typescript-eslint/parser": "6.13.1",
"@typescript-eslint/eslint-plugin": "6.13.2",
"@typescript-eslint/parser": "6.13.2",
"@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3",
"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",
"eslint-config-prettier": "9.1.0",
"eslint-import-resolver-webpack": "0.13.8",
"eslint-plugin-disable": "2.0.3",
"eslint-plugin-import": "2.29.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",
@@ -218,7 +216,7 @@
"husky": "8.0.3",
"instant-mocha": "1.5.2",
"jszip": "3.10.1",
"lint-staged": "15.1.0",
"lint-staged": "15.2.0",
"lit-analyzer": "2.0.1",
"lodash.template": "4.5.0",
"magic-string": "0.30.5",
@@ -231,7 +229,7 @@
"rollup": "2.79.1",
"rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-visualizer": "5.9.3",
"rollup-plugin-visualizer": "5.10.0",
"serve-handler": "6.1.5",
"sinon": "17.0.1",
"source-map-url": "0.4.1",
@@ -239,7 +237,7 @@
"tar": "6.2.0",
"terser-webpack-plugin": "5.3.9",
"ts-lit-plugin": "2.0.1",
"typescript": "5.3.2",
"typescript": "5.3.3",
"vinyl-buffer": "1.0.1",
"vinyl-source-stream": "2.0.0",
"webpack": "5.89.0",

View File

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

View File

@@ -63,6 +63,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
ha-authorize ha-alert {
display: block;
margin: 16px 0;
background-color: var(--primary-background-color, #fafafa);
}
</style>
<ha-alert alert-type="error"
@@ -93,6 +94,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
ha-alert {
display: block;
margin: 16px 0;
background-color: var(--primary-background-color, #fafafa);
}
p {
font-size: 14px;

View File

@@ -1,5 +1,6 @@
import { HassConfig, HassEntity } from "home-assistant-js-websocket";
import {
DOMAIN_ATTRIBUTES_FORMATERS,
DOMAIN_ATTRIBUTES_UNITS,
TEMPERATURE_ATTRIBUTES,
} from "../../data/entity_attributes";
@@ -14,11 +15,10 @@ import { formatNumber } from "../number/format_number";
import { capitalizeFirstLetter } from "../string/capitalize-first-letter";
import { isDate } from "../string/is_date";
import { isTimestamp } from "../string/is_timestamp";
import { blankBeforePercent } from "../translations/blank_before_percent";
import { blankBeforeUnit } from "../translations/blank_before_unit";
import { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
import { computeStateDomain } from "./compute_state_domain";
import { blankBeforeUnit } from "../translations/blank_before_unit";
export const computeAttributeValueDisplay = (
localize: LocalizeFunc,
@@ -39,19 +39,18 @@ export const computeAttributeValueDisplay = (
// Number value, return formatted number
if (typeof attributeValue === "number") {
const formattedValue = formatNumber(attributeValue, locale);
const domain = computeStateDomain(stateObj);
const formatter = DOMAIN_ATTRIBUTES_FORMATERS[domain]?.[attribute];
const formattedValue = formatter
? formatter(attributeValue, locale)
: formatNumber(attributeValue, locale);
let unit = DOMAIN_ATTRIBUTES_UNITS[domain]?.[attribute] as
| string
| undefined;
if (domain === "light" && attribute === "brightness") {
const percentage = Math.round((attributeValue / 255) * 100);
return `${percentage}${blankBeforePercent(locale)}%`;
}
if (domain === "weather") {
unit = getWeatherUnit(config, stateObj as WeatherEntity, attribute);
}

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

@@ -10,7 +10,6 @@ import {
ScorableTextItem,
fuzzyFilterSort,
} from "../../common/string/filter/sequence-matching";
import { AreaRegistryEntry } from "../../data/area_registry";
import {
DeviceEntityDisplayLookup,
DeviceRegistryEntry,
@@ -102,7 +101,7 @@ export class HaDevicePicker extends LitElement {
private _getDevices = memoizeOne(
(
devices: DeviceRegistryEntry[],
areas: AreaRegistryEntry[],
areas: HomeAssistant["areas"],
entities: EntityRegistryDisplayEntry[],
includeDomains: this["includeDomains"],
excludeDomains: this["excludeDomains"],
@@ -133,8 +132,6 @@ export class HaDevicePicker extends LitElement {
deviceEntityLookup = getDeviceEntityDisplayLookup(entities);
}
const areaLookup = areas;
let inputDevices = devices.filter(
(device) => device.id === this.value || !device.disabled_by
);
@@ -224,8 +221,8 @@ export class HaDevicePicker extends LitElement {
id: device.id,
name: name,
area:
device.area_id && areaLookup[device.area_id]
? areaLookup[device.area_id].name
device.area_id && areas[device.area_id]
? areas[device.area_id].name
: this.hass.localize("ui.components.device-picker.no_area"),
strings: [name || ""],
};
@@ -267,7 +264,7 @@ export class HaDevicePicker extends LitElement {
this._init = true;
const devices = this._getDevices(
Object.values(this.hass.devices),
Object.values(this.hass.areas),
this.hass.areas,
Object.values(this.hass.entities),
this.includeDomains,
this.excludeDomains,

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

@@ -2,6 +2,7 @@ import {
DIRECTION_ALL,
Manager,
Pan,
Press,
Tap,
TouchMouseInput,
} from "@egjs/hammerjs";
@@ -108,6 +109,9 @@ export class HaControlCircularSlider extends LitElement {
@property({ type: Number })
public max = 100;
@property({ type: Boolean, attribute: "prevent-interaction-on-scroll" })
public preventInteractionOnScroll?: boolean;
@state()
public _localValue?: number = this.value;
@@ -246,16 +250,62 @@ export class HaControlCircularSlider extends LitElement {
this._mc = new Manager(this._interaction, {
inputClass: TouchMouseInput,
});
const pressToActivate =
this.preventInteractionOnScroll && "ontouchstart" in window;
// If press to activate is true, a 60ms press is required to activate the slider
this._mc.add(
new Pan({
direction: DIRECTION_ALL,
enable: true,
threshold: 0,
new Press({
enable: pressToActivate,
pointers: 1,
time: 60,
})
);
const panRecognizer = new Pan({
direction: DIRECTION_ALL,
enable: !pressToActivate,
threshold: 0,
});
this._mc.add(panRecognizer);
this._mc.add(new Tap({ event: "singletap" }));
this._mc.on("press", (e) => {
e.srcEvent.stopPropagation();
e.srcEvent.preventDefault();
if (this.disabled || this.readonly) return;
const percentage = this._getPercentageFromEvent(e);
const raw = this._percentageToValue(percentage);
this._activeSlider = this._findActiveSlider(raw);
const bounded = this._boundedValue(raw);
this._setActiveValue(bounded);
const stepped = this._steppedValue(bounded);
if (this._activeSlider) {
fireEvent(this, `${this._activeSlider}-changing`, { value: stepped });
}
panRecognizer.set({ enable: true });
});
this._mc.on("pressup", (e) => {
e.srcEvent.stopPropagation();
e.srcEvent.preventDefault();
const percentage = this._getPercentageFromEvent(e);
const raw = this._percentageToValue(percentage);
const bounded = this._boundedValue(raw);
const stepped = this._steppedValue(bounded);
this._setActiveValue(stepped);
if (this._activeSlider) {
fireEvent(this, `${this._activeSlider}-changing`, {
value: undefined,
});
fireEvent(this, `${this._activeSlider}-changed`, { value: stepped });
}
this._activeSlider = undefined;
});
this._mc.on("pan", (e) => {
e.srcEvent.stopPropagation();
e.srcEvent.preventDefault();
@@ -271,6 +321,9 @@ export class HaControlCircularSlider extends LitElement {
this._mc.on("pancancel", () => {
if (this.disabled || this.readonly) return;
this._activeSlider = undefined;
if (pressToActivate) {
panRecognizer.set({ enable: false });
}
});
this._mc.on("panmove", (e) => {
if (this.disabled || this.readonly) return;
@@ -297,6 +350,9 @@ export class HaControlCircularSlider extends LitElement {
fireEvent(this, `${this._activeSlider}-changed`, { value: stepped });
}
this._activeSlider = undefined;
if (pressToActivate) {
panRecognizer.set({ enable: false });
}
});
this._mc.on("singletap", (e) => {
if (this.disabled || this.readonly) return;
@@ -315,6 +371,9 @@ export class HaControlCircularSlider extends LitElement {
this._lastSlider = this._activeSlider;
this.shadowRoot?.getElementById("#slider")?.focus();
this._activeSlider = undefined;
if (pressToActivate) {
panRecognizer.set({ enable: false });
}
});
}
}
@@ -618,6 +677,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 +693,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

@@ -30,9 +30,9 @@ export class HaNumberSelector extends LitElement {
protected willUpdate(changedProps: PropertyValues) {
if (changedProps.has("value")) {
if (this.value !== Number(this._valueStr)) {
if (this._valueStr === "" || this.value !== Number(this._valueStr)) {
this._valueStr =
!this.value || isNaN(this.value) ? "" : this.value.toString();
this.value == null || isNaN(this.value) ? "" : this.value.toString();
}
}
}

View File

@@ -597,9 +597,9 @@ export class HaServiceControl extends LitElement {
);
}
target = {
entity_id: targetEntities,
device_id: targetDevices,
area_id: targetAreas,
...(targetEntities.length ? { entity_id: targetEntities } : {}),
...(targetDevices.length ? { device_id: targetDevices } : {}),
...(targetAreas.length ? { area_id: targetAreas } : {}),
};
}
}

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

@@ -58,6 +58,7 @@ class DialogMediaPlayerBrowse extends LitElement {
this._navigateIds = undefined;
this._currentItem = undefined;
this._preferredLayout = "auto";
this.classList.remove("opened");
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -79,6 +80,7 @@ class DialogMediaPlayerBrowse extends LitElement {
)
: this._currentItem.title}
@closed=${this.closeDialog}
@opened=${this._dialogOpened}
>
<ha-dialog-header show-border slot="heading">
${this._navigateIds.length > 1
@@ -167,6 +169,10 @@ class DialogMediaPlayerBrowse extends LitElement {
`;
}
private _dialogOpened() {
this.classList.add("opened");
}
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
@@ -217,10 +223,13 @@ class DialogMediaPlayerBrowse extends LitElement {
ha-media-player-browse {
--media-browser-max-height: calc(100vh - 65px);
height: calc(100vh - 65px);
direction: ltr;
}
:host(.opened) ha-media-player-browse {
height: calc(100vh - 65px);
}
@media (min-width: 800px) {
ha-dialog {
--mdc-dialog-max-width: 800px;
@@ -231,7 +240,6 @@ class DialogMediaPlayerBrowse extends LitElement {
ha-media-player-browse {
position: initial;
--media-browser-max-height: 100vh - 137px;
height: 100vh - 137px;
width: 700px;
}
}

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

@@ -1,3 +1,6 @@
import { formatDuration } from "../common/datetime/duration";
import { FrontendLocaleData } from "./translation";
export const STATE_ATTRIBUTES = [
"entity_id",
"assumed_state",
@@ -64,6 +67,7 @@ export const DOMAIN_ATTRIBUTES_UNITS = {
color_temp_kelvin: "K",
min_color_temp_kelvin: "K",
max_color_temp_kelvin: "K",
brightness: "%",
},
sun: {
elevation: "°",
@@ -74,4 +78,22 @@ export const DOMAIN_ATTRIBUTES_UNITS = {
sensor: {
battery_level: "%",
},
media_player: {
volume_level: "%",
},
} as const satisfies Record<string, Record<string, string>>;
type Formatter = (value: number, locale: FrontendLocaleData) => string;
export const DOMAIN_ATTRIBUTES_FORMATERS: Record<
string,
Record<string, Formatter>
> = {
light: {
brightness: (value) => Math.round((value / 255) * 100).toString(),
},
media_player: {
volume_level: (value) => Math.round(value * 100).toString(),
media_duration: (value) => formatDuration(value.toString(), "s"),
},
};

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

@@ -425,13 +425,6 @@ class DataEntryFlowDialog extends LitElement {
);
}
);
if (this._step?.flow_id) {
await this._unsubDataEntryFlowProgressed;
// fetch flow after we subscribe to the event, so we don't miss the first event
this._processStep(
this._params!.flowConfig.fetchFlow(this.hass, this._step.flow_id)
);
}
}
static get styles(): CSSResultGroup {

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

@@ -103,8 +103,10 @@ class MoreInfoUpdate extends LitElement {
: ""}
${supportsFeature(this.stateObj!, UPDATE_SUPPORT_RELEASE_NOTES) &&
!this._error
? this._releaseNotes === undefined
? html`<ha-circular-progress active></ha-circular-progress>`
? !this._releaseNotes
? html`<div class="flex center">
<ha-circular-progress indeterminate></ha-circular-progress>
</div>`
: html`<hr />
<ha-faded>
<ha-markdown .content=${this._releaseNotes}></ha-markdown>
@@ -254,9 +256,10 @@ class MoreInfoUpdate extends LitElement {
a {
color: var(--primary-color);
}
ha-circular-progress {
width: 100%;
.flex.center {
display: flex;
justify-content: center;
align-items: center;
}
mwc-linear-progress {
margin-bottom: -8px;

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

@@ -26,7 +26,8 @@
width: 100%;
max-width: 400px;
margin: 0 auto;
box-sizing: border-box;
padding: 0 16px;
box-sizing: content-box;
}
.header {
@@ -40,12 +41,6 @@
height: 56px;
width: 56px;
}
@media (max-width: 592px) {
.content {
margin: 0 16px;
}
}
</style>
</head>
<body>

View File

@@ -22,7 +22,8 @@
.content {
max-width: 560px;
margin: 0 auto;
box-sizing: border-box;
padding: 0 16px;
box-sizing: content-box;
}
.header {
@@ -36,12 +37,6 @@
height: 56px;
width: 56px;
}
@media (max-width: 592px) {
.content {
margin: 0 16px;
}
}
</style>
</head>
<body id="particles">

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,9 @@ class AddIntegrationDialog extends LitElement {
>
</lit-virtualizer>
</mwc-list>`
: html`<ha-circular-progress active></ha-circular-progress>`} `;
: html`<div class="flex center">
<ha-circular-progress indeterminate></ha-circular-progress>
</div>`} `;
}
private _keyFunction = (integration: IntegrationListItem) =>
@@ -682,10 +684,12 @@ class AddIntegrationDialog extends LitElement {
p > a {
color: var(--primary-color);
}
ha-circular-progress {
width: 100%;
.flex.center {
display: flex;
justify-content: center;
align-items: center;
}
ha-circular-progress {
margin: 24px 0;
}
mwc-list {

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`
@@ -236,7 +236,7 @@ class ZHAAddDevicesPage extends LitElement {
color: var(--error-color);
}
ha-circular-progress {
padding: 20px;
margin: 20px;
}
.searching {
margin-top: 20px;

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,11 +1,15 @@
import { mdiThermostat } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import type { ControlSelectOption } from "../../../components/ha-control-select";
import type { HaControlSelectMenu } from "../../../components/ha-control-select-menu";
import {
ClimateEntity,
compareClimateHvacModes,
@@ -35,6 +39,9 @@ class HuiClimateHvacModesCardFeature
@state() _currentHvacMode?: HvacMode;
@query("ha-control-select-menu", true)
private _haSelect?: HaControlSelectMenu;
static getStubConfig(
_,
stateObj?: HassEntity
@@ -66,8 +73,23 @@ class HuiClimateHvacModesCardFeature
}
}
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 mode = (ev.detail as any).value as HvacMode;
const mode =
(ev.detail as any).value ?? ((ev.target as any).value as HvacMode);
if (mode === this.stateObj!.state) return;
@@ -111,6 +133,37 @@ class HuiClimateHvacModesCardFeature
path: computeHvacModeIcon(mode),
}));
if (this._config.style === "dropdown") {
return html`
<div class="container">
<ha-control-select-menu
show-arrow
hide-label
.label=${this.hass.localize("ui.card.climate.mode")}
.value=${this._currentHvacMode}
.disabled=${this.stateObj.state === UNAVAILABLE}
fixedMenuPosition
naturalMenuWidth
@selected=${this._valueChanged}
@closed=${stopPropagation}
>
<ha-svg-icon slot="icon" .path=${mdiThermostat}></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>
`;
}
return html`
<div class="container">
<ha-control-select
@@ -131,6 +184,14 @@ class HuiClimateHvacModesCardFeature
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-padding: 0;
--control-select-thickness: 40px;

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

@@ -37,6 +37,7 @@ export interface AlarmModesCardFeatureConfig {
export interface ClimateHvacModesCardFeatureConfig {
type: "climate-hvac-modes";
style?: "dropdown" | "icons";
hvac_modes?: HvacMode[];
}
@@ -55,6 +56,10 @@ export interface NumericInputCardFeatureConfig {
style?: "buttons" | "slider";
}
export interface TargetHumidityCardFeatureConfig {
type: "target-humidity";
}
export interface TargetTemperatureCardFeatureConfig {
type: "target-temperature";
}
@@ -66,6 +71,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 +112,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",
},
],
};
@@ -127,6 +127,7 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
<ha-card>
<p class="title">${name}</p>
<ha-state-control-humidifier-humidity
prevent-interaction-on-scroll
show-current
.hass=${this.hass}
.stateObj=${stateObj}
@@ -168,11 +169,14 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
.title {
width: 100%;
font-size: 18px;
line-height: 24px;
padding: 12px 36px 16px 36px;
line-height: 36px;
padding: 8px 30px 8px 30px;
margin: 0;
text-align: center;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
ha-state-control-humidifier-humidity {

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>
`;
@@ -59,7 +59,7 @@ export class HuiStartingCard extends LitElement implements LovelaceCard {
height: calc(100vh - var(--header-height));
}
ha-circular-progress {
padding-bottom: 20px;
margin-bottom: 20px;
}
.content {
height: 100%;

View File

@@ -119,6 +119,7 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
<ha-card>
<p class="title">${name}</p>
<ha-state-control-climate-temperature
prevent-interaction-on-scroll
show-current
.hass=${this.hass}
.stateObj=${stateObj}
@@ -160,11 +161,14 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
.title {
width: 100%;
font-size: 18px;
line-height: 24px;
padding: 12px 36px 16px 36px;
line-height: 36px;
padding: 8px 30px 8px 30px;
margin: 0;
text-align: center;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
ha-state-control-climate-temperature {

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