Compare commits

..

88 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
Bram Kragten
6d0eb05954 Bumped version to 20231204.0 2023-12-04 12:08:11 +01:00
Bram Kragten
96fbbc55e1 Make history split by device class option in history card (#18871) 2023-12-04 12:06:26 +01:00
Bram Kragten
7e74502ba3 Add particles to login, move forgot password link (#18868) 2023-12-04 11:34:42 +01:00
Paul Bottein
b273b31b03 Don't allow yaml dashboard to be in edit mode (#18879) 2023-12-04 10:17:52 +01:00
renovate[bot]
cf00ac74c6 Update dependency sortablejs to v1.15.1 (#18876)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 00:57:31 -05:00
renovate[bot]
cc702f0fb3 Update dependency @types/luxon to v3.3.6 (#18875)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 00:53:54 -05:00
renovate[bot]
cc6eb5789d Update dependency clean-css to v5.3.3 (#18870) 2023-12-03 11:28:51 -05:00
renovate[bot]
669b80a9e1 Update babel monorepo to v7.23.5 (#18865) 2023-12-02 19:41:02 -05:00
karwosts
4c4fddee94 Fix for energy cards not refreshing hourly (#18854) 2023-12-02 23:33:04 +01:00
renovate[bot]
b28a4e6b50 Update fullcalendar monorepo to v6.1.10 (#18859) 2023-12-02 03:26:39 +00:00
renovate[bot]
bdd6a55a84 Update dependency @codemirror/commands to v6.3.2 (#18857) 2023-12-01 22:16:38 -05:00
renovate[bot]
d1ebc06994 Update typescript-eslint monorepo to v6.13.1 (#18853) 2023-12-01 22:15:42 -05:00
renovate[bot]
2a150788b4 Update typescript-eslint monorepo to v6.13.0 (#18845)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-30 15:06:07 -05:00
Bram Kragten
72caf72e80 Bumped version to 20231130.0 2023-11-30 17:18:58 +01:00
Bram Kragten
a6ff1899df Prevent double quick action (#18842) 2023-11-30 16:18:04 +00:00
Paul Bottein
6a999597df Rename number feature to number value feature (#18836) 2023-11-30 16:17:54 +00:00
Bram Kragten
219fc9e53a Group history by device class (#18841) 2023-11-30 17:17:26 +01:00
Bram Kragten
861959ed2d add previous button to login screen (#18837) 2023-11-30 17:06:22 +01:00
Damian Sypniewski
f7f50294e7 Added hold and double tap actions for tile card (#18345) 2023-11-30 16:03:07 +01:00
Bram Kragten
a226333c1e Update area-filter-dialog.ts 2023-11-30 15:16:01 +01:00
Bram Kragten
75f18aa69b Move disabled items always to the bottom, only change color of text, … (#18814)
* Move disabled items always to the bottom, only change color of text, dont drag disabled items

* Don't allow to drag hidden items

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2023-11-30 14:37:10 +01:00
Simon Lamon
60ff4fdc1f Ensure helper type is always localized (#18825)
* Ensure helper type is always localized

* Remove the check
2023-11-30 14:14:13 +01:00
renovate[bot]
8d14cb0ab7 Update vaadinWebComponents monorepo to v24.2.4 (#18835)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-30 08:12:43 -05:00
Bram Kragten
606c809880 Update size of icon on launch screen (#18833) 2023-11-30 12:34:46 +01:00
Simon Lamon
f1692038f8 Fix developer tools set state for unexisting entity (#18827)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2023-11-30 10:59:51 +00:00
Paul Bottein
79f5ec0cd5 Fix dashboard crashed when deleting hidden area (#18832) 2023-11-30 10:59:31 +00:00
renovate[bot]
416a2a080d Update CodeMirror (#18829)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-30 10:58:49 +00:00
Paul Bottein
92dc8b1561 Fix map dark mode (#18830) 2023-11-30 11:47:44 +01:00
karwosts
07b807adfc Fit map on load (#18819) 2023-11-30 10:49:40 +01:00
Simon Lamon
843430ef41 Cleanup iron resize event (#18813) 2023-11-29 21:02:57 +01:00
Bram Kragten
b5b2392dd2 Guard for no person for user (#18812) 2023-11-29 20:59:06 +01:00
161 changed files with 2814 additions and 1690 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

@@ -25,15 +25,15 @@
"license": "Apache-2.0",
"type": "module",
"dependencies": {
"@babel/runtime": "7.23.4",
"@braintree/sanitize-url": "6.0.4",
"@codemirror/autocomplete": "6.11.0",
"@codemirror/commands": "6.3.0",
"@codemirror/language": "6.9.2",
"@babel/runtime": "7.23.5",
"@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.4",
"@codemirror/state": "6.3.1",
"@codemirror/view": "6.22.0",
"@codemirror/search": "6.5.5",
"@codemirror/state": "6.3.3",
"@codemirror/view": "6.22.1",
"@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.12.0",
"@formatjs/intl-displaynames": "6.6.4",
@@ -43,12 +43,12 @@
"@formatjs/intl-numberformat": "8.9.0",
"@formatjs/intl-pluralrules": "5.2.10",
"@formatjs/intl-relativetimeformat": "11.2.10",
"@fullcalendar/core": "6.1.9",
"@fullcalendar/daygrid": "6.1.9",
"@fullcalendar/interaction": "6.1.9",
"@fullcalendar/list": "6.1.9",
"@fullcalendar/luxon3": "6.1.9",
"@fullcalendar/timegrid": "6.1.9",
"@fullcalendar/core": "6.1.10",
"@fullcalendar/daygrid": "6.1.10",
"@fullcalendar/interaction": "6.1.10",
"@fullcalendar/list": "6.1.10",
"@fullcalendar/luxon3": "6.1.10",
"@fullcalendar/timegrid": "6.1.10",
"@lezer/highlight": "1.2.0",
"@lit-labs/context": "0.4.1",
"@lit-labs/motion": "1.0.6",
@@ -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.3",
"@vaadin/vaadin-themable-mixin": "24.2.3",
"@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,17 +119,16 @@
"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.0",
"sortablejs": "1.15.1",
"stacktrace-js": "2.0.2",
"superstruct": "1.0.3",
"tinykeys": "2.1.0",
@@ -152,15 +150,15 @@
"xss": "1.0.14"
},
"devDependencies": {
"@babel/core": "7.23.3",
"@babel/core": "7.23.5",
"@babel/helper-define-polyfill-provider": "0.4.3",
"@babel/plugin-proposal-decorators": "7.23.3",
"@babel/plugin-proposal-decorators": "7.23.5",
"@babel/plugin-transform-runtime": "7.23.4",
"@babel/preset-env": "7.23.3",
"@babel/preset-env": "7.23.5",
"@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.5",
"@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.12.0",
"@typescript-eslint/parser": "6.12.0",
"@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",
@@ -255,7 +253,7 @@
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@material/mwc-button@^0.25.3": "^0.27.0",
"lit": "2.8.0",
"clean-css": "5.3.2",
"clean-css": "5.3.3",
"@lit/reactive-element": "1.6.3",
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"

View File

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

View File

@@ -21,6 +21,7 @@ import {
DataEntryFlowStepForm,
} from "../data/data_entry_flow";
import "./ha-auth-form";
import { fireEvent } from "../common/dom/fire_event";
type State = "loading" | "error" | "step";
@@ -85,10 +86,6 @@ export class HaAuthFlow extends LitElement {
protected render() {
return html`
<style>
ha-auth-flow .action {
margin: 24px 0 8px;
text-align: center;
}
ha-auth-flow .store-token {
margin-left: -16px;
}
@@ -158,14 +155,25 @@ export class HaAuthFlow extends LitElement {
}
private _renderForm() {
const showBack =
this.step?.type === "form" &&
this.authProvider?.users &&
!["select_mfa_module", "mfa"].includes(this.step.step_id);
switch (this._state) {
case "step":
if (this.step == null) {
return nothing;
}
return html`
${this._renderStep(this.step)}
<div class="action">
<div class="action ${showBack ? "space-between" : ""}">
${showBack
? html`<mwc-button @click=${this._localFlow}>
${this.localize("ui.panel.page-authorize.form.previous")}
</mwc-button>`
: nothing}
<mwc-button
raised
@click=${this._handleSubmit}
@@ -294,7 +302,8 @@ export class HaAuthFlow extends LitElement {
redirectWithAuthCode(
this.redirectUri!,
data.result,
this.oauth2State
this.oauth2State,
this.storeToken
);
return;
}
@@ -375,7 +384,8 @@ export class HaAuthFlow extends LitElement {
redirectWithAuthCode(
this.redirectUri!,
newStep.result,
this.oauth2State
this.oauth2State,
this.storeToken
);
return;
}
@@ -390,6 +400,10 @@ export class HaAuthFlow extends LitElement {
this._submitting = false;
}
}
private _localFlow() {
fireEvent(this, "default-login-flow", { value: false });
}
}
declare global {

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;
@@ -116,6 +118,16 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
position: relative;
padding: 16px;
}
.action {
margin: 16px 0 8px;
display: flex;
width: 100%;
max-width: 336px;
justify-content: center;
}
.space-between {
justify-content: space-between;
}
.footer {
padding-top: 8px;
display: flex;
@@ -164,7 +176,10 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
</ha-alert>`
: nothing}
<div class="card-content">
<div
class="card-content"
@default-login-flow=${this._handleDefaultLoginFlow}
>
${!this._authProvider
? html`<p>
${this.localize("ui.panel.page-authorize.initializing")}
@@ -181,7 +196,6 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
.authProviders=${this._authProviders}
.localize=${this.localize}
.ownInstance=${this._ownInstance}
@default-login-flow=${this._handleDefaultLoginFlow}
></ha-local-auth-flow>`
: html`<ha-auth-flow
.clientId=${this.clientId}
@@ -267,6 +281,10 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
);
}
if (window.innerWidth > 450) {
import("../resources/particles");
}
// If we are logging into the instance that is hosting this auth form
// we will register the service worker to start preloading.
if (url.host === location.host) {
@@ -315,8 +333,8 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
}
}
private _handleDefaultLoginFlow() {
this._forceDefaultLogin = true;
private _handleDefaultLoginFlow(ev) {
this._forceDefaultLogin = ev.detail.value;
}
private async _handleAuthProviderPick(ev) {

View File

@@ -17,7 +17,7 @@ import {
submitLoginFlow,
} from "../data/auth";
import { DataEntryFlowStep } from "../data/data_entry_flow";
import { listPersons } from "../data/person";
import { BasePerson, listUserPersons } from "../data/person";
import "./ha-auth-textfield";
import type { HaAuthTextField } from "./ha-auth-textfield";
@@ -43,7 +43,7 @@ export class HaLocalAuthFlow extends LitElement {
@state() private _submitting = false;
@state() private _persons?: Promise<Record<string, string>>;
@state() private _persons?: Record<string, BasePerson>;
@state() private _selectedUser?: string;
@@ -65,7 +65,9 @@ export class HaLocalAuthFlow extends LitElement {
if (!this.authProvider?.users || !this._persons) {
return nothing;
}
const userIds = Object.keys(this.authProvider.users);
const userIds = Object.keys(this.authProvider.users).filter(
(userId) => userId in this._persons!
);
return html`
<style>
.content {
@@ -146,22 +148,16 @@ export class HaLocalAuthFlow extends LitElement {
height: 120px;
--person-badge-font-size: 3em;
}
.action {
margin: 16px 0 8px;
display: flex;
width: 100%;
max-width: 336px;
justify-content: center;
}
.space-between {
justify-content: space-between;
}
ha-list-item {
margin-top: 16px;
}
ha-button {
--mdc-typography-button-text-transform: none;
}
.forgot-password-container {
text-align: right;
padding: 8px 0 16px 0;
}
a.forgot-password {
color: var(--primary-color);
text-decoration: none;
@@ -185,7 +181,7 @@ export class HaLocalAuthFlow extends LitElement {
</style>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
: nothing}
${this._step
? html`<ha-auth-flow
.clientId=${this.clientId}
@@ -196,72 +192,76 @@ export class HaLocalAuthFlow extends LitElement {
.localize=${this.localize}
></ha-auth-flow>`
: this._selectedUser
? html`<div class="login-form"><div class="person">
<ha-person-badge
.person=${this._persons![this._selectedUser]}
></ha-person-badge>
<p>${this._persons![this._selectedUser].name}</p>
</div>
<form>
<input
type="hidden"
name="username"
autocomplete="username"
.value=${this.authProvider.users[this._selectedUser]}
/>
<ha-auth-textfield
.type=${this._unmaskedPassword ? "text" : "password"}
id="password"
name="password"
.label=${this.localize(
"ui.panel.page-authorize.form.providers.homeassistant.step.init.data.password"
)}
required
autoValidate
autocomplete
iconTrailing
validationMessage="Required"
>
<ha-icon-button
toggles
.label=${
this.localize(
this._unmaskedPassword
? "ui.panel.page-authorize.form.hide_password"
: "ui.panel.page-authorize.form.show_password"
) || (this._unmaskedPassword ? "Hide password" : "Show password")
}
@click=${this._toggleUnmaskedPassword}
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>
</ha-auth-textfield>
</div>
<div class="action space-between">
<mwc-button
@click=${this._restart}
.disabled=${this._submitting}
>
${this.localize("ui.panel.page-authorize.form.previous")}
</mwc-button>
<mwc-button
raised
@click=${this._handleSubmit}
.disabled=${this._submitting}
>
${this.localize("ui.panel.page-authorize.form.next")}
</mwc-button>
? html`<div class="login-form">
<div class="person">
<ha-person-badge
.person=${this._persons[this._selectedUser]}
></ha-person-badge>
<p>${this._persons[this._selectedUser].name}</p>
</div>
<div class="action">
<a class="forgot-password"
href="https://www.home-assistant.io/docs/locked_out/#forgot-password"
target="_blank"
rel="noreferrer noopener"
>${this.localize(
"ui.panel.page-authorize.forgot_password"
)}</a
<form>
<input
type="hidden"
name="username"
autocomplete="username"
readonly
.value=${this.authProvider.users[this._selectedUser]}
/>
<ha-auth-textfield
.type=${this._unmaskedPassword ? "text" : "password"}
autocomplete="current-password"
id="password"
name="password"
.label=${this.localize(
"ui.panel.page-authorize.form.providers.homeassistant.step.init.data.password"
)}
required
autoValidate
iconTrailing
validationMessage="Required"
>
<ha-icon-button
toggles
.label=${this.localize(
this._unmaskedPassword
? "ui.panel.page-authorize.form.hide_password"
: "ui.panel.page-authorize.form.show_password"
) ||
(this._unmaskedPassword
? "Hide password"
: "Show password")}
@click=${this._toggleUnmaskedPassword}
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
></ha-icon-button>
</ha-auth-textfield>
<div class="forgot-password-container">
<a
class="forgot-password"
href="https://www.home-assistant.io/docs/locked_out/#forgot-password"
target="_blank"
rel="noreferrer noopener"
>${this.localize(
"ui.panel.page-authorize.forgot_password"
)}</a
>
</div>
</form>`
<div class="action space-between">
<mwc-button
@click=${this._restart}
.disabled=${this._submitting}
>
${this.localize("ui.panel.page-authorize.form.previous")}
</mwc-button>
<mwc-button
raised
@click=${this._handleSubmit}
.disabled=${this._submitting}
>
${this.localize("ui.panel.page-authorize.form.next")}
</mwc-button>
</div>
</form>
</div>`
: html`<h1>
${this.localize("ui.panel.page-authorize.welcome_home")}
</h1>
@@ -273,6 +273,7 @@ export class HaLocalAuthFlow extends LitElement {
>
${userIds.map((userId) => {
const person = this._persons![userId];
return html`<div
class="person"
.userId=${userId}
@@ -316,7 +317,12 @@ export class HaLocalAuthFlow extends LitElement {
}
private async _load() {
this._persons = await (await listPersons()).json();
try {
this._persons = await listUserPersons();
} catch {
this._persons = {};
this._error = "Failed to fetch persons";
}
}
private _restart() {
@@ -353,7 +359,8 @@ export class HaLocalAuthFlow extends LitElement {
redirectWithAuthCode(
this.redirectUri!,
data.result,
this.oauth2State
this.oauth2State,
true
);
return;
}
@@ -374,7 +381,8 @@ export class HaLocalAuthFlow extends LitElement {
redirectWithAuthCode(
this.redirectUri!,
result.result,
this.oauth2State
this.oauth2State,
true
);
return;
}
@@ -433,7 +441,8 @@ export class HaLocalAuthFlow extends LitElement {
redirectWithAuthCode(
this.redirectUri!,
newStep.result,
this.oauth2State
this.oauth2State,
true
);
return;
}
@@ -462,7 +471,7 @@ export class HaLocalAuthFlow extends LitElement {
}
private _otherLogin() {
fireEvent(this, "default-login-flow");
fireEvent(this, "default-login-flow", { value: true });
}
}
@@ -471,6 +480,6 @@ declare global {
"ha-local-auth-flow": HaLocalAuthFlow;
}
interface HASSDomEvents {
"default-login-flow": undefined;
"default-login-flow": { value: boolean };
}
}

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

@@ -303,6 +303,11 @@ export class StateHistoryCharts extends LitElement {
padding-right: 1px;
}
.entry-container:not(:first-child) {
border-top: 2px solid var(--divider-color);
margin-top: 16px;
}
.container,
lit-virtualizer {
height: 100%;

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

@@ -17,7 +17,7 @@ class HaFaded extends LitElement {
@click=${this._showContent}
>
<slot
@iron-resize=${
@content-resize=${
// ha-markdown-element fire this when render is complete
this._setShowContent
}
@@ -79,4 +79,7 @@ declare global {
interface HTMLElementTagNameMap {
"ha-faded": HaFaded;
}
interface HASSDomEvents {
"content-resize": undefined;
}
}

View File

@@ -99,7 +99,7 @@ class HaMarkdownElement extends ReactiveElement {
}
}
private _resize = () => fireEvent(this, "iron-resize");
private _resize = () => fireEvent(this, "content-resize");
}
declare global {

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

@@ -136,7 +136,7 @@ export class HaMap extends ReactiveElement {
autoFitRequired = true;
}
if (this.autoFit && autoFitRequired) {
if (changedProps.has("_loaded") || (this.autoFit && autoFitRequired)) {
this.fitMap();
}
@@ -155,10 +155,11 @@ export class HaMap extends ReactiveElement {
}
private _updateMapStyle(): void {
const darkMode = this.darkMode ?? this.hass.themes.darkMode;
const darkMode = this.darkMode ?? this.hass.themes.darkMode ?? false;
const forcedDark = this.darkMode ?? false;
const map = this.shadowRoot!.getElementById("map");
map!.classList.toggle("dark", darkMode);
map!.classList.toggle("forced-dark", this.darkMode);
map!.classList.toggle("forced-dark", forcedDark);
}
private async _loadMap(): Promise<void> {

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

@@ -2,12 +2,12 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { Person } from "../../data/person";
import { BasePerson } from "../../data/person";
import { computeUserInitials } from "../../data/user";
@customElement("ha-person-badge")
class PersonBadge extends LitElement {
@property({ attribute: false }) public person?: Person;
@property({ attribute: false }) public person?: BasePerson;
protected render() {
if (!this.person) {

View File

@@ -130,8 +130,8 @@ export const areaCompare =
const indexA = order ? order.indexOf(a) : -1;
const indexB = order ? order.indexOf(b) : 1;
if (indexA === -1 && indexB === -1) {
const nameA = entries?.[a].name ?? a;
const nameB = entries?.[b].name ?? b;
const nameA = entries?.[a]?.name ?? a;
const nameB = entries?.[b]?.name ?? b;
return stringCompare(nameA, nameB);
}
if (indexA === -1) {

View File

@@ -80,7 +80,8 @@ export const deleteLoginFlow = (flow_id) =>
export const redirectWithAuthCode = (
url: string,
authCode: string,
oauth2State: string | undefined
oauth2State: string | undefined,
storeToken: boolean
) => {
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
if (!url.includes("?")) {
@@ -94,7 +95,9 @@ export const redirectWithAuthCode = (
if (oauth2State) {
url += `&state=${encodeURIComponent(oauth2State)}`;
}
url += `&storeToken=true`;
if (storeToken) {
url += `&storeToken=true`;
}
document.location.assign(url);
};

View File

@@ -581,6 +581,28 @@ const clearEnergyCollectionPreferences = (hass: HomeAssistant) => {
});
};
const scheduleHourlyRefresh = (collection: EnergyCollection) => {
if (collection._refreshTimeout) {
clearTimeout(collection._refreshTimeout);
}
if (collection._active && (!collection.end || collection.end > new Date())) {
// The stats are created every hour
// Schedule a refresh for 20 minutes past the hour
// If the end is larger than the current time.
const nextFetch = new Date();
if (nextFetch.getMinutes() >= 20) {
nextFetch.setHours(nextFetch.getHours() + 1);
}
nextFetch.setMinutes(20, 0, 0);
collection._refreshTimeout = window.setTimeout(
() => collection.refresh(),
nextFetch.getTime() - Date.now()
);
}
};
export const getEnergyDataCollection = (
hass: HomeAssistant,
options: { prefs?: EnergyPreferences; key?: string } = {}
@@ -609,28 +631,7 @@ export const getEnergyDataCollection = (
collection.prefs = await getEnergyPreferences(hass);
}
if (collection._refreshTimeout) {
clearTimeout(collection._refreshTimeout);
}
if (
collection._active &&
(!collection.end || collection.end > new Date())
) {
// The stats are created every hour
// Schedule a refresh for 20 minutes past the hour
// If the end is larger than the current time.
const nextFetch = new Date();
if (nextFetch.getMinutes() >= 20) {
nextFetch.setHours(nextFetch.getHours() + 1);
}
nextFetch.setMinutes(20, 0, 0);
collection._refreshTimeout = window.setTimeout(
() => collection.refresh(),
nextFetch.getTime() - Date.now()
);
}
scheduleHourlyRefresh(collection);
return getEnergyData(
hass,
@@ -647,6 +648,11 @@ export const getEnergyDataCollection = (
collection.subscribe = (subscriber: (data: EnergyData) => void) => {
const unsub = origSubscribe(subscriber);
collection._active++;
if (collection._refreshTimeout === undefined) {
scheduleHourlyRefresh(collection);
}
return () => {
collection._active--;
if (collection._active < 1) {

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

@@ -49,6 +49,7 @@ export interface LineChartEntity {
export interface LineChartUnit {
unit: string;
device_class?: string;
identifier: string;
data: LineChartEntity[];
}
@@ -323,7 +324,8 @@ const processTimelineEntity = (
};
const processLineChartEntities = (
unit,
unit: string,
device_class: string | undefined,
entities: HistoryStates,
hassEntities: HassEntities
): LineChartUnit => {
@@ -391,6 +393,7 @@ const processLineChartEntities = (
return {
unit,
device_class,
identifier: Object.keys(entities).join(""),
data,
};
@@ -417,7 +420,8 @@ export const computeHistory = (
hass: HomeAssistant,
stateHistory: HistoryStates,
localize: LocalizeFunc,
sensorNumericalDeviceClasses: string[]
sensorNumericalDeviceClasses: string[],
splitDeviceClasses = false
): HistoryResult => {
const lineChartDevices: { [unit: string]: HistoryStates } = {};
const timelineDevices: TimelineEntity[] = [];
@@ -466,6 +470,12 @@ export const computeHistory = (
}[domain];
}
const deviceClass: string | undefined = (
currentState?.attributes || numericStateFromHistory?.a
)?.device_class;
const key = computeGroupKey(unit, deviceClass, splitDeviceClasses);
if (!unit) {
timelineDevices.push(
processTimelineEntity(
@@ -478,19 +488,37 @@ export const computeHistory = (
currentState
)
);
} else if (unit in lineChartDevices && entityId in lineChartDevices[unit]) {
lineChartDevices[unit][entityId].push(...stateInfo);
} else {
if (!(unit in lineChartDevices)) {
lineChartDevices[unit] = {};
} else if (
key &&
key in lineChartDevices &&
entityId in lineChartDevices[key]
) {
lineChartDevices[key][entityId].push(...stateInfo);
} else if (key) {
if (!(key in lineChartDevices)) {
lineChartDevices[key] = {};
}
lineChartDevices[unit][entityId] = stateInfo;
lineChartDevices[key][entityId] = stateInfo;
}
});
const unitStates = Object.keys(lineChartDevices).map((unit) =>
processLineChartEntities(unit, lineChartDevices[unit], hass.states)
);
const unitStates = Object.keys(lineChartDevices).map((key) => {
const splitKey = key.split("_");
const unit = splitKey[0];
const deviceClass = splitKey[1] || undefined;
return processLineChartEntities(
unit,
deviceClass,
lineChartDevices[key],
hass.states
);
});
return { line: unitStates, timeline: timelineDevices };
};
export const computeGroupKey = (
unit: string | undefined,
device_class: string | undefined,
splitDeviceClasses: boolean
) => (splitDeviceClasses ? `${unit}_${device_class || ""}` : unit);

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

@@ -1,11 +1,14 @@
import { HomeAssistant } from "../types";
export interface Person {
id: string;
export interface BasePerson {
name: string;
picture?: string;
}
export interface Person extends BasePerson {
id: string;
user_id?: string;
device_trackers?: string[];
picture?: string;
}
export interface PersonMutableParams {
@@ -21,9 +24,14 @@ export const fetchPersons = (hass: HomeAssistant) =>
config: Person[];
}>({ type: "person/list" });
export const listPersons = () =>
export const listUserPersons = (): Promise<Record<string, BasePerson>> =>
fetch("/api/person/list", {
credentials: "same-origin",
}).then((resp) => {
if (resp.ok) {
return resp.json();
}
throw new Error(resp.statusText);
});
export const createPerson = (

View File

@@ -73,6 +73,7 @@ export class DialogAreaFilter
animation: 150,
fallbackClass: "sortable-fallback",
handle: ".handle",
draggable: ".draggable",
onChoose: (evt: SortableEvent) => {
(evt.item as any).placeholder =
document.createComment("sort-placeholder");
@@ -128,16 +129,21 @@ export class DialogAreaFilter
const name = this.hass!.areas[area]?.name || area;
return html`
<ha-list-item
class=${classMap({ hidden: !isVisible })}
class=${classMap({
hidden: !isVisible,
draggable: isVisible,
})}
hasMeta
graphic="icon"
noninteractive
>
<ha-svg-icon
class="handle"
.path=${mdiDrag}
slot="graphic"
></ha-svg-icon>
${isVisible
? html`<ha-svg-icon
class="handle"
.path=${mdiDrag}
slot="graphic"
></ha-svg-icon>`
: nothing}
${name}
<ha-icon-button
tabindex="0"
@@ -177,6 +183,11 @@ export class DialogAreaFilter
hidden.push(area);
}
this._hidden = hidden;
const nonHiddenAreas = this._areas.filter(
(ar) => !this._hidden.includes(ar)
);
const hiddenAreas = this._areas.filter((ar) => this._hidden.includes(ar));
this._areas = [...nonHiddenAreas, ...hiddenAreas];
}
static get styles(): CSSResultGroup {
@@ -193,10 +204,10 @@ export class DialogAreaFilter
overflow: visible;
}
.hidden {
opacity: 0.3;
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

@@ -6,16 +6,8 @@ import {
mdiTuneVariant,
mdiWaterPercent,
} from "@mdi/js";
import {
CSSResultGroup,
LitElement,
PropertyValues,
css,
html,
nothing,
} from "lit";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-control-select-menu";
@@ -50,8 +42,6 @@ class MoreInfoClimate extends LitElement {
@state() private _mainControl: MainControl = "temperature";
private _resizeDebounce?: number;
protected render() {
if (!this.stateObj) {
return nothing;
@@ -293,21 +283,6 @@ class MoreInfoClimate extends LitElement {
`;
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!changedProps.has("stateObj") || !this.stateObj) {
return;
}
if (this._resizeDebounce) {
clearTimeout(this._resizeDebounce);
}
this._resizeDebounce = window.setTimeout(() => {
fireEvent(this, "iron-resize");
this._resizeDebounce = undefined;
}, 500);
}
private _setMainControl(ev: any) {
ev.stopPropagation();
this._mainControl = ev.currentTarget.control;

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

@@ -8,7 +8,6 @@ import {
nothing,
} from "lit";
import { property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-control-select-menu";
@@ -38,8 +37,6 @@ class MoreInfoHumidifier extends LitElement {
}
}
private _resizeDebounce?: number;
protected render() {
if (!this.stateObj) {
return nothing;
@@ -135,21 +132,6 @@ class MoreInfoHumidifier extends LitElement {
`;
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!changedProps.has("stateObj") || !this.stateObj) {
return;
}
if (this._resizeDebounce) {
clearTimeout(this._resizeDebounce);
}
this._resizeDebounce = window.setTimeout(() => {
fireEvent(this, "iron-resize");
this._resizeDebounce = undefined;
}, 500);
}
private _handleStateChanged(ev) {
const newVal = ev.target.value || null;
this._callServiceHelper(

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

@@ -39,7 +39,7 @@
align-items: center;
}
#ha-launch-screen svg {
width: 170px;
width: 112px;
flex-shrink: 0;
}
#ha-launch-screen .ha-launch-screen-spacer {

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

@@ -218,7 +218,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
this._handleProgress(ev)
);
if (window.innerWidth > 450) {
import("./particles");
import("../resources/particles");
}
makeDialogManager(this, this.shadowRoot!);
import("../components/ha-language-picker");

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

@@ -226,10 +226,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
};
});
if (!Object.keys(configEntriesCopy).length) {
return states;
}
const entries = Object.values(configEntriesCopy).map((configEntry) => ({
id: configEntry.entry_id,
entity_id: "",

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(

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