Merge branch 'dev' into persistent_notification_trigger

This commit is contained in:
J. Nick Koston 2023-06-21 09:58:42 +02:00
commit 1e5c35c158
No known key found for this signature in database
96 changed files with 3614 additions and 1128 deletions

View File

@ -1,5 +1,5 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.10 FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.11
ENV \ ENV \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive \

View File

@ -6,7 +6,7 @@ on:
- cron: "0 1 * * *" - cron: "0 1 * * *"
env: env:
PYTHON_VERSION: "3.10" PYTHON_VERSION: "3.11"
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
permissions: permissions:

View File

@ -6,7 +6,7 @@ on:
- published - published
env: env:
PYTHON_VERSION: "3.10" PYTHON_VERSION: "3.11"
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions # Set default workflow permissions
@ -76,7 +76,7 @@ jobs:
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2023.04.0 uses: home-assistant/wheels@2023.04.0
with: with:
abi: cp310 abi: cp311
tag: musllinux_1_2 tag: musllinux_1_2
arch: amd64 arch: amd64
wheels-key: ${{ secrets.WHEELS_KEY }} wheels-key: ${{ secrets.WHEELS_KEY }}

View File

@ -167,6 +167,8 @@ const createWebpackConfig = ({
"lit/polyfill-support$": "lit/polyfill-support.js", "lit/polyfill-support$": "lit/polyfill-support.js",
"@lit-labs/virtualizer/layouts/grid": "@lit-labs/virtualizer/layouts/grid":
"@lit-labs/virtualizer/layouts/grid.js", "@lit-labs/virtualizer/layouts/grid.js",
"@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver":
"@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver.js",
}, },
}, },
output: { output: {

View File

@ -0,0 +1,24 @@
import type { ControlSelectOption } from "../../../src/components/ha-control-select";
export const timeOptions: ControlSelectOption[] = [
{
value: "now",
label: "Now",
},
{
value: "00:15:30",
label: "12:15:30 AM",
},
{
value: "06:15:30",
label: "06:15:30 AM",
},
{
value: "12:15:30",
label: "12:15:30 PM",
},
{
value: "18:15:30",
label: "06:15:30 PM",
},
];

View File

@ -0,0 +1,7 @@
---
title: Date-Time Format (Numeric)
---
This pages lists all supported languages with their available date-time formats.
Formatting function: `const formatDateTimeNumeric: (dateObj: Date, locale: FrontendLocaleData) => string`

View File

@ -0,0 +1,136 @@
import { html, css, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatDateTimeNumeric } from "../../../../src/common/datetime/format_date_time";
import { timeOptions } from "../../data/date-options";
import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
FrontendLocaleData,
NumberFormat,
TimeFormat,
DateFormat,
FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
@customElement("demo-date-time-date-time-numeric")
export class DemoDateTimeDateTimeNumeric extends LitElement {
@state() private selection?: string = "now";
@state() private date: Date = new Date();
handleValueChanged(e: CustomEvent) {
this.selection = e.detail.value as string;
this.date = new Date();
if (this.selection !== "now") {
const [hours, minutes, seconds] = this.selection.split(":").map(Number);
this.date.setHours(hours);
this.date.setMinutes(minutes);
this.date.setSeconds(seconds);
}
}
protected render() {
const defaultLocale: FrontendLocaleData = {
language: "en",
number_format: NumberFormat.language,
time_format: TimeFormat.language,
date_format: DateFormat.language,
first_weekday: FirstWeekday.language,
time_zone: TimeZone.local,
};
return html`
<ha-control-select
.value=${this.selection}
.options=${timeOptions}
@value-changed=${this.handleValueChanged}
>
</ha-control-select>
<mwc-list>
<div class="container header">
<div>Language</div>
<div class="center">Default (lang)</div>
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateTimeNumeric(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTimeNumeric(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTimeNumeric(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}
static get styles() {
return css`
ha-control-select {
max-width: 800px;
margin: 12px auto;
}
.header {
font-weight: bold;
}
.center {
text-align: center;
}
.container {
max-width: 900px;
margin: 12px auto;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.container > div {
flex-grow: 1;
width: 20%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-date-time-date-time-numeric": DemoDateTimeDateTimeNumeric;
}
}

View File

@ -0,0 +1,7 @@
---
title: Date-Time Format (Seconds)
---
This pages lists all supported languages with their available date-time formats.
Formatting function: `const formatDateTimeWithSeconds: (dateObj: Date, locale: FrontendLocaleData) => string`

View File

@ -0,0 +1,136 @@
import { html, css, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatDateTimeWithSeconds } from "../../../../src/common/datetime/format_date_time";
import { timeOptions } from "../../data/date-options";
import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
FrontendLocaleData,
NumberFormat,
TimeFormat,
DateFormat,
FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
@customElement("demo-date-time-date-time-seconds")
export class DemoDateTimeDateTimeSeconds extends LitElement {
@state() private selection?: string = "now";
@state() private date: Date = new Date();
handleValueChanged(e: CustomEvent) {
this.selection = e.detail.value as string;
this.date = new Date();
if (this.selection !== "now") {
const [hours, minutes, seconds] = this.selection.split(":").map(Number);
this.date.setHours(hours);
this.date.setMinutes(minutes);
this.date.setSeconds(seconds);
}
}
protected render() {
const defaultLocale: FrontendLocaleData = {
language: "en",
number_format: NumberFormat.language,
time_format: TimeFormat.language,
date_format: DateFormat.language,
first_weekday: FirstWeekday.language,
time_zone: TimeZone.local,
};
return html`
<ha-control-select
.value=${this.selection}
.options=${timeOptions}
@value-changed=${this.handleValueChanged}
>
</ha-control-select>
<mwc-list>
<div class="container header">
<div>Language</div>
<div class="center">Default (lang)</div>
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}
static get styles() {
return css`
ha-control-select {
max-width: 800px;
margin: 12px auto;
}
.header {
font-weight: bold;
}
.center {
text-align: center;
}
.container {
max-width: 900px;
margin: 12px auto;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.container > div {
flex-grow: 1;
width: 20%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-date-time-date-time-seconds": DemoDateTimeDateTimeSeconds;
}
}

View File

@ -0,0 +1,7 @@
---
title: Date-Time Format (Short w/ Year)
---
This pages lists all supported languages with their available date-time formats.
Formatting function: `const formatShortDateTimeWithYear: (dateObj: Date, locale: FrontendLocaleData) => string`

View File

@ -0,0 +1,136 @@
import { html, css, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatShortDateTimeWithYear } from "../../../../src/common/datetime/format_date_time";
import { timeOptions } from "../../data/date-options";
import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
FrontendLocaleData,
NumberFormat,
TimeFormat,
DateFormat,
FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
@customElement("demo-date-time-date-time-short-year")
export class DemoDateTimeDateTimeShortYear extends LitElement {
@state() private selection?: string = "now";
@state() private date: Date = new Date();
handleValueChanged(e: CustomEvent) {
this.selection = e.detail.value as string;
this.date = new Date();
if (this.selection !== "now") {
const [hours, minutes, seconds] = this.selection.split(":").map(Number);
this.date.setHours(hours);
this.date.setMinutes(minutes);
this.date.setSeconds(seconds);
}
}
protected render() {
const defaultLocale: FrontendLocaleData = {
language: "en",
number_format: NumberFormat.language,
time_format: TimeFormat.language,
date_format: DateFormat.language,
first_weekday: FirstWeekday.language,
time_zone: TimeZone.local,
};
return html`
<ha-control-select
.value=${this.selection}
.options=${timeOptions}
@value-changed=${this.handleValueChanged}
>
</ha-control-select>
<mwc-list>
<div class="container header">
<div>Language</div>
<div class="center">Default (lang)</div>
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatShortDateTimeWithYear(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatShortDateTimeWithYear(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatShortDateTimeWithYear(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}
static get styles() {
return css`
ha-control-select {
max-width: 800px;
margin: 12px auto;
}
.header {
font-weight: bold;
}
.center {
text-align: center;
}
.container {
max-width: 900px;
margin: 12px auto;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.container > div {
flex-grow: 1;
width: 20%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-date-time-date-time-short-year": DemoDateTimeDateTimeShortYear;
}
}

View File

@ -0,0 +1,7 @@
---
title: Date-Time Format (Short)
---
This pages lists all supported languages with their available date-time formats.
Formatting function: `const formatShortDateTime: (dateObj: Date, locale: FrontendLocaleData) => string`

View File

@ -0,0 +1,136 @@
import { html, css, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatShortDateTime } from "../../../../src/common/datetime/format_date_time";
import { timeOptions } from "../../data/date-options";
import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
FrontendLocaleData,
NumberFormat,
TimeFormat,
DateFormat,
FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
@customElement("demo-date-time-date-time-short")
export class DemoDateTimeDateTimeShort extends LitElement {
@state() private selection?: string = "now";
@state() private date: Date = new Date();
handleValueChanged(e: CustomEvent) {
this.selection = e.detail.value as string;
this.date = new Date();
if (this.selection !== "now") {
const [hours, minutes, seconds] = this.selection.split(":").map(Number);
this.date.setHours(hours);
this.date.setMinutes(minutes);
this.date.setSeconds(seconds);
}
}
protected render() {
const defaultLocale: FrontendLocaleData = {
language: "en",
number_format: NumberFormat.language,
time_format: TimeFormat.language,
date_format: DateFormat.language,
first_weekday: FirstWeekday.language,
time_zone: TimeZone.local,
};
return html`
<ha-control-select
.value=${this.selection}
.options=${timeOptions}
@value-changed=${this.handleValueChanged}
>
</ha-control-select>
<mwc-list>
<div class="container header">
<div>Language</div>
<div class="center">Default (lang)</div>
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatShortDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatShortDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatShortDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}
static get styles() {
return css`
ha-control-select {
max-width: 800px;
margin: 12px auto;
}
.header {
font-weight: bold;
}
.center {
text-align: center;
}
.container {
max-width: 900px;
margin: 12px auto;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.container > div {
flex-grow: 1;
width: 20%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-date-time-date-time-short": DemoDateTimeDateTimeShort;
}
}

View File

@ -0,0 +1,7 @@
---
title: Date-Time Format
---
This pages lists all supported languages with their available date-time formats.
Formatting function: `const formatDateTime: (dateObj: Date, locale: FrontendLocaleData) => string`

View File

@ -0,0 +1,136 @@
import { html, css, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatDateTime } from "../../../../src/common/datetime/format_date_time";
import { timeOptions } from "../../data/date-options";
import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
FrontendLocaleData,
NumberFormat,
TimeFormat,
DateFormat,
FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
@customElement("demo-date-time-date-time")
export class DemoDateTimeDateTime extends LitElement {
@state() private selection?: string = "now";
@state() private date: Date = new Date();
handleValueChanged(e: CustomEvent) {
this.selection = e.detail.value as string;
this.date = new Date();
if (this.selection !== "now") {
const [hours, minutes, seconds] = this.selection.split(":").map(Number);
this.date.setHours(hours);
this.date.setMinutes(minutes);
this.date.setSeconds(seconds);
}
}
protected render() {
const defaultLocale: FrontendLocaleData = {
language: "en",
number_format: NumberFormat.language,
time_format: TimeFormat.language,
date_format: DateFormat.language,
first_weekday: FirstWeekday.language,
time_zone: TimeZone.local,
};
return html`
<ha-control-select
.value=${this.selection}
.options=${timeOptions}
@value-changed=${this.handleValueChanged}
>
</ha-control-select>
<mwc-list>
<div class="container header">
<div>Language</div>
<div class="center">Default (lang)</div>
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatDateTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}
static get styles() {
return css`
ha-control-select {
max-width: 800px;
margin: 12px auto;
}
.header {
font-weight: bold;
}
.center {
text-align: center;
}
.container {
max-width: 900px;
margin: 12px auto;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.container > div {
flex-grow: 1;
width: 20%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-date-time-date-time": DemoDateTimeDateTime;
}
}

View File

@ -1,5 +1,5 @@
--- ---
title: (Numeric) Date Formatting title: Date Format (Numeric)
--- ---
This pages lists all supported languages with their available (numeric) date formats. This pages lists all supported languages with their available (numeric) date formats.

View File

@ -0,0 +1,7 @@
---
title: Time Format (Seconds)
---
This pages lists all supported languages with their available time formats.
Formatting function: `const formatTimeWithSeconds: (dateObj: Date, locale: FrontendLocaleData) => string`

View File

@ -0,0 +1,135 @@
import { html, css, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatTimeWithSeconds } from "../../../../src/common/datetime/format_time";
import { timeOptions } from "../../data/date-options";
import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
FrontendLocaleData,
NumberFormat,
TimeFormat,
DateFormat,
FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
@customElement("demo-date-time-time-seconds")
export class DemoDateTimeTimeSeconds extends LitElement {
@state() private selection?: string = "now";
@state() private date: Date = new Date();
handleValueChanged(e: CustomEvent) {
this.selection = e.detail.value as string;
this.date = new Date();
if (this.selection !== "now") {
const [hours, minutes, seconds] = this.selection.split(":").map(Number);
this.date.setHours(hours);
this.date.setMinutes(minutes);
this.date.setSeconds(seconds);
}
}
protected render() {
const defaultLocale: FrontendLocaleData = {
language: "en",
number_format: NumberFormat.language,
time_format: TimeFormat.language,
date_format: DateFormat.language,
first_weekday: FirstWeekday.language,
time_zone: TimeZone.local,
};
return html`
<ha-control-select
.value=${this.selection}
.options=${timeOptions}
@value-changed=${this.handleValueChanged}
>
</ha-control-select>
<mwc-list>
<div class="container header">
<div>Language</div>
<div class="center">Default (lang)</div>
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatTimeWithSeconds(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}
static get styles() {
return css`
ha-control-select {
max-width: 800px;
margin: 12px auto;
}
.header {
font-weight: bold;
}
.center {
text-align: center;
}
.container {
max-width: 600px;
margin: 12px auto;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.container > div {
flex-grow: 1;
width: 20%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-date-time-time-seconds": DemoDateTimeTimeSeconds;
}
}

View File

@ -0,0 +1,7 @@
---
title: Time Format (Weekday)
---
This pages lists all supported languages with their available time formats.
Formatting function: `const formatTimeWeekday: (dateObj: Date, locale: FrontendLocaleData) => string`

View File

@ -0,0 +1,135 @@
import { html, css, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatTimeWeekday } from "../../../../src/common/datetime/format_time";
import { timeOptions } from "../../data/date-options";
import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
FrontendLocaleData,
NumberFormat,
TimeFormat,
DateFormat,
FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
@customElement("demo-date-time-time-weekday")
export class DemoDateTimeTimeWeekday extends LitElement {
@state() private selection?: string = "now";
@state() private date: Date = new Date();
handleValueChanged(e: CustomEvent) {
this.selection = e.detail.value as string;
this.date = new Date();
if (this.selection !== "now") {
const [hours, minutes, seconds] = this.selection.split(":").map(Number);
this.date.setHours(hours);
this.date.setMinutes(minutes);
this.date.setSeconds(seconds);
}
}
protected render() {
const defaultLocale: FrontendLocaleData = {
language: "en",
number_format: NumberFormat.language,
time_format: TimeFormat.language,
date_format: DateFormat.language,
first_weekday: FirstWeekday.language,
time_zone: TimeZone.local,
};
return html`
<ha-control-select
.value=${this.selection}
.options=${timeOptions}
@value-changed=${this.handleValueChanged}
>
</ha-control-select>
<mwc-list>
<div class="container header">
<div>Language</div>
<div class="center">Default (lang)</div>
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatTimeWeekday(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatTimeWeekday(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatTimeWeekday(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}
static get styles() {
return css`
ha-control-select {
max-width: 800px;
margin: 12px auto;
}
.header {
font-weight: bold;
}
.center {
text-align: center;
}
.container {
max-width: 800px;
margin: 12px auto;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.container > div {
flex-grow: 1;
width: 20%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-date-time-time-weekday": DemoDateTimeTimeWeekday;
}
}

View File

@ -0,0 +1,7 @@
---
title: Time Format
---
This pages lists all supported languages with their available time formats.
Formatting function: `const formatTime: (dateObj: Date, locale: FrontendLocaleData) => string`

View File

@ -0,0 +1,136 @@
import { html, css, LitElement } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-control-select";
import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatTime } from "../../../../src/common/datetime/format_time";
import { timeOptions } from "../../data/date-options";
import { demoConfig } from "../../../../src/fake_data/demo_config";
import {
FrontendLocaleData,
NumberFormat,
TimeFormat,
DateFormat,
FirstWeekday,
TimeZone,
} from "../../../../src/data/translation";
@customElement("demo-date-time-time")
export class DemoDateTimeTime extends LitElement {
@state() private selection?: string = "now";
@state() private date: Date = new Date();
handleValueChanged(e: CustomEvent) {
this.selection = e.detail.value as string;
this.date = new Date();
if (this.selection !== "now") {
const [hours, minutes, seconds] = this.selection.split(":").map(Number);
this.date.setHours(hours);
this.date.setMinutes(minutes);
this.date.setSeconds(seconds);
}
}
protected render() {
const defaultLocale: FrontendLocaleData = {
language: "en",
number_format: NumberFormat.language,
time_format: TimeFormat.language,
date_format: DateFormat.language,
first_weekday: FirstWeekday.language,
time_zone: TimeZone.local,
};
return html`
<ha-control-select
.value=${this.selection}
.options=${timeOptions}
@value-changed=${this.handleValueChanged}
>
</ha-control-select>
<mwc-list>
<div class="container header">
<div>Language</div>
<div class="center">Default (lang)</div>
<div class="center">12 Hours</div>
<div class="center">24 Hours</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.language,
},
demoConfig
)}
</div>
<div class="center">
${formatTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.am_pm,
},
demoConfig
)}
</div>
<div class="center">
${formatTime(
this.date,
{
...defaultLocale,
language: key,
time_format: TimeFormat.twenty_four,
},
demoConfig
)}
</div>
</div>
`
)}
</mwc-list>
`;
}
static get styles() {
return css`
ha-control-select {
max-width: 800px;
margin: 12px auto;
}
.header {
font-weight: bold;
}
.center {
text-align: center;
}
.container {
max-width: 600px;
margin: 12px auto;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.container > div {
flex-grow: 1;
width: 20%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-date-time-time": DemoDateTimeTime;
}
}

View File

@ -135,6 +135,9 @@ const ENTITIES: HassEntity[] = [
createEntity("climate.fan_only", "fan_only"), createEntity("climate.fan_only", "fan_only"),
createEntity("climate.auto_idle", "auto", undefined, { hvac_action: "idle" }), createEntity("climate.auto_idle", "auto", undefined, { hvac_action: "idle" }),
createEntity("climate.auto_off", "auto", undefined, { hvac_action: "off" }), createEntity("climate.auto_off", "auto", undefined, { hvac_action: "off" }),
createEntity("climate.auto_preheating", "auto", undefined, {
hvac_action: "preheating",
}),
createEntity("climate.auto_heating", "auto", undefined, { createEntity("climate.auto_heating", "auto", undefined, {
hvac_action: "heating", hvac_action: "heating",
}), }),

View File

@ -49,9 +49,9 @@
"@fullcalendar/list": "6.1.8", "@fullcalendar/list": "6.1.8",
"@fullcalendar/timegrid": "6.1.8", "@fullcalendar/timegrid": "6.1.8",
"@lezer/highlight": "1.1.6", "@lezer/highlight": "1.1.6",
"@lit-labs/context": "0.3.2", "@lit-labs/context": "0.3.3",
"@lit-labs/motion": "1.0.3", "@lit-labs/motion": "1.0.3",
"@lit-labs/virtualizer": "2.0.2", "@lit-labs/virtualizer": "2.0.3",
"@lrnwebcomponents/simple-tooltip": "7.0.2", "@lrnwebcomponents/simple-tooltip": "7.0.2",
"@material/chips": "=14.0.0-canary.53b3cad2f.0", "@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0", "@material/data-table": "=14.0.0-canary.53b3cad2f.0",
@ -93,8 +93,8 @@
"@polymer/paper-toast": "3.0.1", "@polymer/paper-toast": "3.0.1",
"@polymer/polymer": "3.5.1", "@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0", "@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.1.0", "@vaadin/combo-box": "24.1.1",
"@vaadin/vaadin-themable-mixin": "24.1.0", "@vaadin/vaadin-themable-mixin": "24.1.1",
"@vibrant/color": "3.2.1-alpha.1", "@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1", "@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@ -156,9 +156,9 @@
"@babel/preset-env": "7.22.5", "@babel/preset-env": "7.22.5",
"@babel/preset-typescript": "7.22.5", "@babel/preset-typescript": "7.22.5",
"@koa/cors": "4.0.0", "@koa/cors": "4.0.0",
"@octokit/auth-oauth-device": "5.0.0", "@octokit/auth-oauth-device": "5.0.2",
"@octokit/plugin-retry": "5.0.3", "@octokit/plugin-retry": "5.0.4",
"@octokit/rest": "19.0.11", "@octokit/rest": "19.0.13",
"@open-wc/dev-server-hmr": "0.1.4", "@open-wc/dev-server-hmr": "0.1.4",
"@rollup/plugin-babel": "6.0.3", "@rollup/plugin-babel": "6.0.3",
"@rollup/plugin-commonjs": "25.0.1", "@rollup/plugin-commonjs": "25.0.1",
@ -189,7 +189,7 @@
"babel-plugin-template-html-minifier": "4.1.0", "babel-plugin-template-html-minifier": "4.1.0",
"chai": "4.3.7", "chai": "4.3.7",
"del": "7.0.0", "del": "7.0.0",
"eslint": "8.42.0", "eslint": "8.43.0",
"eslint-config-airbnb-base": "15.0.0", "eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.0.0", "eslint-config-airbnb-typescript": "17.0.0",
"eslint-config-prettier": "8.8.0", "eslint-config-prettier": "8.8.0",
@ -215,7 +215,7 @@
"instant-mocha": "1.5.1", "instant-mocha": "1.5.1",
"jszip": "3.10.1", "jszip": "3.10.1",
"lint-staged": "13.2.2", "lint-staged": "13.2.2",
"lit-analyzer": "1.2.1", "lit-analyzer": "2.0.0-pre.3",
"lodash.template": "4.5.0", "lodash.template": "4.5.0",
"magic-string": "0.30.0", "magic-string": "0.30.0",
"map-stream": "0.0.7", "map-stream": "0.0.7",
@ -235,8 +235,8 @@
"systemjs": "6.14.1", "systemjs": "6.14.1",
"tar": "6.1.15", "tar": "6.1.15",
"terser-webpack-plugin": "5.3.9", "terser-webpack-plugin": "5.3.9",
"ts-lit-plugin": "1.2.1", "ts-lit-plugin": "2.0.0-pre.1",
"typescript": "4.9.5", "typescript": "5.1.3",
"vinyl-buffer": "1.0.1", "vinyl-buffer": "1.0.1",
"vinyl-source-stream": "2.0.0", "vinyl-source-stream": "2.0.0",
"webpack": "5.87.0", "webpack": "5.87.0",

View File

@ -11,7 +11,7 @@ readme = "README.md"
authors = [ authors = [
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"} {name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
] ]
requires-python = ">=3.4.0" requires-python = ">=3.10.0"
[project.urls] [project.urls]
"Homepage" = "https://github.com/home-assistant/frontend" "Homepage" = "https://github.com/home-assistant/frontend"

View File

@ -8,9 +8,9 @@ cd "$(dirname "$0")/.."
# Install/upgrade node when inside devcontainer # Install/upgrade node when inside devcontainer
if [[ -n "$DEVCONTAINER" ]]; then if [[ -n "$DEVCONTAINER" ]]; then
nodeCurrent=$(nvm version default || echo "") nodeCurrent=$(nvm version default || :)
nodeLatest=$(nvm version-remote "$(cat .nvmrc)") nodeLatest=$(nvm version-remote "$(cat .nvmrc)")
if [[ -z "$nodeCurrent" ]]; then if [[ -z "$nodeCurrent" || "$nodeCurrent" == "N/A" ]]; then
nvm install nvm install
elif [[ "$nodeCurrent" != "$nodeLatest" ]]; then elif [[ "$nodeCurrent" != "$nodeLatest" ]]; then
nvm install --reinstall-packages-from="$nodeCurrent" --default nvm install --reinstall-packages-from="$nodeCurrent" --default

View File

@ -15,20 +15,15 @@ export const formatDateTime = (
const formatDateTimeMem = memoizeOne( const formatDateTimeMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) => (locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat( new Intl.DateTimeFormat(locale.language, {
locale.language === "en" && !useAmPm(locale)
? "en-u-hc-h23"
: locale.language,
{
year: "numeric", year: "numeric",
month: "long", month: "long",
day: "numeric", day: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit", hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit", minute: "2-digit",
hour12: useAmPm(locale), hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
} })
)
); );
// Aug 9, 2021, 8:23 AM // Aug 9, 2021, 8:23 AM
@ -40,20 +35,15 @@ export const formatShortDateTimeWithYear = (
const formatShortDateTimeWithYearMem = memoizeOne( const formatShortDateTimeWithYearMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) => (locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat( new Intl.DateTimeFormat(locale.language, {
locale.language === "en" && !useAmPm(locale)
? "en-u-hc-h23"
: locale.language,
{
year: "numeric", year: "numeric",
month: "short", month: "short",
day: "numeric", day: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit", hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit", minute: "2-digit",
hour12: useAmPm(locale), hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
} })
)
); );
// Aug 9, 8:23 AM // Aug 9, 8:23 AM
@ -65,19 +55,14 @@ export const formatShortDateTime = (
const formatShortDateTimeMem = memoizeOne( const formatShortDateTimeMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) => (locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat( new Intl.DateTimeFormat(locale.language, {
locale.language === "en" && !useAmPm(locale)
? "en-u-hc-h23"
: locale.language,
{
month: "short", month: "short",
day: "numeric", day: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit", hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit", minute: "2-digit",
hour12: useAmPm(locale), hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
} })
)
); );
// August 9, 2021, 8:23:15 AM // August 9, 2021, 8:23:15 AM
@ -89,21 +74,16 @@ export const formatDateTimeWithSeconds = (
const formatDateTimeWithSecondsMem = memoizeOne( const formatDateTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) => (locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat( new Intl.DateTimeFormat(locale.language, {
locale.language === "en" && !useAmPm(locale)
? "en-u-hc-h23"
: locale.language,
{
year: "numeric", year: "numeric",
month: "long", month: "long",
day: "numeric", day: "numeric",
hour: useAmPm(locale) ? "numeric" : "2-digit", hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit", minute: "2-digit",
second: "2-digit", second: "2-digit",
hour12: useAmPm(locale), hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
} })
)
); );
// 9/8/2021, 8:23 AM // 9/8/2021, 8:23 AM

View File

@ -13,17 +13,12 @@ export const formatTime = (
const formatTimeMem = memoizeOne( const formatTimeMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) => (locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat( new Intl.DateTimeFormat(locale.language, {
locale.language === "en" && !useAmPm(locale)
? "en-u-hc-h23"
: locale.language,
{
hour: "numeric", hour: "numeric",
minute: "2-digit", minute: "2-digit",
hour12: useAmPm(locale), hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
} })
)
); );
// 9:15:24 PM || 21:15:24 // 9:15:24 PM || 21:15:24
@ -35,18 +30,13 @@ export const formatTimeWithSeconds = (
const formatTimeWithSecondsMem = memoizeOne( const formatTimeWithSecondsMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) => (locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat( new Intl.DateTimeFormat(locale.language, {
locale.language === "en" && !useAmPm(locale)
? "en-u-hc-h23"
: locale.language,
{
hour: useAmPm(locale) ? "numeric" : "2-digit", hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit", minute: "2-digit",
second: "2-digit", second: "2-digit",
hour12: useAmPm(locale), hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
} })
)
); );
// Tuesday 7:00 PM || Tuesday 19:00 // Tuesday 7:00 PM || Tuesday 19:00
@ -58,18 +48,13 @@ export const formatTimeWeekday = (
const formatTimeWeekdayMem = memoizeOne( const formatTimeWeekdayMem = memoizeOne(
(locale: FrontendLocaleData, serverTimeZone: string) => (locale: FrontendLocaleData, serverTimeZone: string) =>
new Intl.DateTimeFormat( new Intl.DateTimeFormat(locale.language, {
locale.language === "en" && !useAmPm(locale)
? "en-u-hc-h23"
: locale.language,
{
weekday: "long", weekday: "long",
hour: useAmPm(locale) ? "numeric" : "2-digit", hour: useAmPm(locale) ? "numeric" : "2-digit",
minute: "2-digit", minute: "2-digit",
hour12: useAmPm(locale), hourCycle: useAmPm(locale) ? "h12" : "h23",
timeZone: locale.time_zone === "server" ? serverTimeZone : undefined, timeZone: locale.time_zone === "server" ? serverTimeZone : undefined,
} })
)
); );
// 21:15 // 21:15

View File

@ -8,8 +8,10 @@ export const useAmPm = memoizeOne((locale: FrontendLocaleData): boolean => {
) { ) {
const testLanguage = const testLanguage =
locale.time_format === TimeFormat.language ? locale.language : undefined; locale.time_format === TimeFormat.language ? locale.language : undefined;
const test = new Date().toLocaleString(testLanguage); const test = new Date("January 1, 2023 22:00:00").toLocaleString(
return test.includes("AM") || test.includes("PM"); testLanguage
);
return test.includes("10");
} }
return locale.time_format === TimeFormat.am_pm; return locale.time_format === TimeFormat.am_pm;

View File

@ -102,7 +102,15 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
frontend_stream_type: ["hls", "web_rtc"], frontend_stream_type: ["hls", "web_rtc"],
}, },
climate: { climate: {
hvac_action: ["off", "idle", "heating", "cooling", "drying", "fan"], hvac_action: [
"off",
"idle",
"preheating",
"heating",
"cooling",
"drying",
"fan",
],
}, },
cover: { cover: {
device_class: [ device_class: [

View File

@ -30,6 +30,8 @@ class StateHistoryChartLine extends LitElement {
@property({ type: Boolean }) public showNames = true; @property({ type: Boolean }) public showNames = true;
@property({ attribute: false }) public startTime!: Date;
@property({ attribute: false }) public endTime!: Date; @property({ attribute: false }) public endTime!: Date;
@property({ type: Number }) public paddingYAxis = 0; @property({ type: Number }) public paddingYAxis = 0;
@ -57,7 +59,12 @@ class StateHistoryChartLine extends LitElement {
} }
public willUpdate(changedProps: PropertyValues) { public willUpdate(changedProps: PropertyValues) {
if (!this.hasUpdated || changedProps.has("showNames")) { if (
!this.hasUpdated ||
changedProps.has("showNames") ||
changedProps.has("startTime") ||
changedProps.has("endTime")
) {
this._chartOptions = { this._chartOptions = {
parsing: false, parsing: false,
animation: false, animation: false,
@ -74,6 +81,7 @@ class StateHistoryChartLine extends LitElement {
config: this.hass.config, config: this.hass.config,
}, },
}, },
suggestedMin: this.startTime,
suggestedMax: this.endTime, suggestedMax: this.endTime,
ticks: { ticks: {
maxRotation: 0, maxRotation: 0,
@ -146,6 +154,8 @@ class StateHistoryChartLine extends LitElement {
} }
if ( if (
changedProps.has("data") || changedProps.has("data") ||
changedProps.has("startTime") ||
changedProps.has("endTime") ||
this._chartTime < this._chartTime <
new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES) new Date(this.endTime.getTime() - MIN_TIME_BETWEEN_UPDATES)
) { ) {

View File

@ -52,8 +52,12 @@ export class StateHistoryCharts extends LitElement {
@property({ attribute: false }) public endTime?: Date; @property({ attribute: false }) public endTime?: Date;
@property({ attribute: false }) public startTime?: Date;
@property({ type: Boolean, attribute: "up-to-now" }) public upToNow = false; @property({ type: Boolean, attribute: "up-to-now" }) public upToNow = false;
@property() public hoursToShow?: number;
@property({ type: Boolean }) public showNames = true; @property({ type: Boolean }) public showNames = true;
@property({ type: Boolean }) public isLoadingData = false; @property({ type: Boolean }) public isLoadingData = false;
@ -95,13 +99,24 @@ export class StateHistoryCharts extends LitElement {
this._computedEndTime = this._computedEndTime =
this.upToNow || !this.endTime || this.endTime > now ? now : this.endTime; this.upToNow || !this.endTime || this.endTime > now ? now : this.endTime;
if (this.startTime) {
this._computedStartTime = this.startTime;
} else if (this.hoursToShow) {
this._computedStartTime = new Date(
new Date().getTime() - 60 * 60 * this.hoursToShow * 1000
);
} else {
this._computedStartTime = new Date( this._computedStartTime = new Date(
this.historyData.timeline.reduce( this.historyData.timeline.reduce(
(minTime, stateInfo) => (minTime, stateInfo) =>
Math.min(minTime, new Date(stateInfo.data[0].last_changed).getTime()), Math.min(
minTime,
new Date(stateInfo.data[0].last_changed).getTime()
),
new Date().getTime() new Date().getTime()
) )
); );
}
const combinedItems = this.historyData.timeline.length const combinedItems = this.historyData.timeline.length
? (this.virtualize ? (this.virtualize
@ -142,6 +157,7 @@ export class StateHistoryCharts extends LitElement {
.data=${item.data} .data=${item.data}
.identifier=${item.identifier} .identifier=${item.identifier}
.showNames=${this.showNames} .showNames=${this.showNames}
.startTime=${this._computedStartTime}
.endTime=${this._computedEndTime} .endTime=${this._computedEndTime}
.paddingYAxis=${this._maxYWidth} .paddingYAxis=${this._maxYWidth}
.names=${this.names} .names=${this.names}

View File

@ -26,6 +26,10 @@ import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { ValueChangedEvent, HomeAssistant } from "../../types"; import { ValueChangedEvent, HomeAssistant } from "../../types";
import "../ha-combo-box"; import "../ha-combo-box";
import type { HaComboBox } from "../ha-combo-box"; import type { HaComboBox } from "../ha-combo-box";
import {
fuzzyFilterSort,
ScorableTextItem,
} from "../../common/string/filter/sequence-matching";
interface Device { interface Device {
name: string; name: string;
@ -33,6 +37,8 @@ interface Device {
id: string; id: string;
} }
type ScorableDevice = ScorableTextItem & Device;
export type HaDevicePickerDeviceFilterFunc = ( export type HaDevicePickerDeviceFilterFunc = (
device: DeviceRegistryEntry device: DeviceRegistryEntry
) => boolean; ) => boolean;
@ -119,13 +125,14 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
deviceFilter: this["deviceFilter"], deviceFilter: this["deviceFilter"],
entityFilter: this["entityFilter"], entityFilter: this["entityFilter"],
excludeDevices: this["excludeDevices"] excludeDevices: this["excludeDevices"]
): Device[] => { ): ScorableDevice[] => {
if (!devices.length) { if (!devices.length) {
return [ return [
{ {
id: "no_devices", id: "no_devices",
area: "", area: "",
name: this.hass.localize("ui.components.device-picker.no_devices"), name: this.hass.localize("ui.components.device-picker.no_devices"),
strings: [],
}, },
]; ];
} }
@ -235,6 +242,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
device.area_id && areaLookup[device.area_id] device.area_id && areaLookup[device.area_id]
? areaLookup[device.area_id].name ? areaLookup[device.area_id].name
: this.hass.localize("ui.components.device-picker.no_area"), : this.hass.localize("ui.components.device-picker.no_area"),
strings: [device.name || ""],
})); }));
if (!outputDevices.length) { if (!outputDevices.length) {
return [ return [
@ -242,6 +250,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
id: "no_devices", id: "no_devices",
area: "", area: "",
name: this.hass.localize("ui.components.device-picker.no_match"), name: this.hass.localize("ui.components.device-picker.no_match"),
strings: [],
}, },
]; ];
} }
@ -284,7 +293,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
(this._init && changedProps.has("_opened") && this._opened) (this._init && changedProps.has("_opened") && this._opened)
) { ) {
this._init = true; this._init = true;
(this.comboBox as any).items = this._getDevices( const devices = this._getDevices(
this.devices!, this.devices!,
this.areas!, this.areas!,
this.entities!, this.entities!,
@ -295,6 +304,8 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
this.entityFilter, this.entityFilter,
this.excludeDevices this.excludeDevices
); );
this.comboBox.items = devices;
this.comboBox.filteredItems = devices;
} }
} }
@ -314,6 +325,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
item-label-path="name" item-label-path="name"
@opened-changed=${this._openedChanged} @opened-changed=${this._openedChanged}
@value-changed=${this._deviceChanged} @value-changed=${this._deviceChanged}
@filter-changed=${this._filterChanged}
></ha-combo-box> ></ha-combo-box>
`; `;
} }
@ -322,6 +334,14 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
return this.value || ""; return this.value || "";
} }
private _filterChanged(ev: CustomEvent): void {
const target = ev.target as HaComboBox;
const filterString = ev.detail.value.toLowerCase();
target.filteredItems = filterString.length
? fuzzyFilterSort<ScorableDevice>(filterString, target.items || [])
: target.items;
}
private _deviceChanged(ev: ValueChangedEvent<string>) { private _deviceChanged(ev: ValueChangedEvent<string>) {
ev.stopPropagation(); ev.stopPropagation();
let newValue = ev.detail.value; let newValue = ev.detail.value;

View File

@ -7,15 +7,19 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain"; import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateName } from "../../common/entity/compute_state_name"; import { computeStateName } from "../../common/entity/compute_state_name";
import { caseInsensitiveStringCompare } from "../../common/string/compare"; import {
fuzzyFilterSort,
ScorableTextItem,
} from "../../common/string/filter/sequence-matching";
import { ValueChangedEvent, HomeAssistant } from "../../types"; import { ValueChangedEvent, HomeAssistant } from "../../types";
import "../ha-combo-box"; import "../ha-combo-box";
import type { HaComboBox } from "../ha-combo-box"; import type { HaComboBox } from "../ha-combo-box";
import "../ha-icon-button"; import "../ha-icon-button";
import "../ha-svg-icon"; import "../ha-svg-icon";
import "./state-badge"; import "./state-badge";
import { caseInsensitiveStringCompare } from "../../common/string/compare";
interface HassEntityWithCachedName extends HassEntity { interface HassEntityWithCachedName extends HassEntity, ScorableTextItem {
friendly_name: string; friendly_name: string;
} }
@ -159,6 +163,7 @@ export class HaEntityPicker extends LitElement {
), ),
icon: "mdi:magnify", icon: "mdi:magnify",
}, },
strings: [],
}, },
]; ];
} }
@ -169,10 +174,14 @@ export class HaEntityPicker extends LitElement {
); );
return entityIds return entityIds
.map((key) => ({ .map((key) => {
const friendly_name = computeStateName(hass!.states[key]) || key;
return {
...hass!.states[key], ...hass!.states[key],
friendly_name: computeStateName(hass!.states[key]) || key, friendly_name,
})) strings: [key, friendly_name],
};
})
.sort((entityA, entityB) => .sort((entityA, entityB) =>
caseInsensitiveStringCompare( caseInsensitiveStringCompare(
entityA.friendly_name, entityA.friendly_name,
@ -201,10 +210,14 @@ export class HaEntityPicker extends LitElement {
} }
states = entityIds states = entityIds
.map((key) => ({ .map((key) => {
const friendly_name = computeStateName(hass!.states[key]) || key;
return {
...hass!.states[key], ...hass!.states[key],
friendly_name: computeStateName(hass!.states[key]) || key, friendly_name,
})) strings: [key, friendly_name],
};
})
.sort((entityA, entityB) => .sort((entityA, entityB) =>
caseInsensitiveStringCompare( caseInsensitiveStringCompare(
entityA.friendly_name, entityA.friendly_name,
@ -260,6 +273,7 @@ export class HaEntityPicker extends LitElement {
), ),
icon: "mdi:magnify", icon: "mdi:magnify",
}, },
strings: [],
}, },
]; ];
} }
@ -293,7 +307,7 @@ export class HaEntityPicker extends LitElement {
this.excludeEntities this.excludeEntities
); );
if (this._initedStates) { if (this._initedStates) {
(this.comboBox as any).filteredItems = this._states; this.comboBox.filteredItems = this._states;
} }
this._initedStates = true; this._initedStates = true;
} }
@ -340,12 +354,11 @@ export class HaEntityPicker extends LitElement {
} }
private _filterChanged(ev: CustomEvent): void { private _filterChanged(ev: CustomEvent): void {
const target = ev.target as HaComboBox;
const filterString = ev.detail.value.toLowerCase(); const filterString = ev.detail.value.toLowerCase();
(this.comboBox as any).filteredItems = this._states.filter( target.filteredItems = filterString.length
(entityState) => ? fuzzyFilterSort<HassEntityWithCachedName>(filterString, this._states)
entityState.entity_id.toLowerCase().includes(filterString) || : this._states;
computeStateName(entityState).toLowerCase().includes(filterString)
);
} }
private _setValue(value: string) { private _setValue(value: string) {

View File

@ -7,6 +7,10 @@ import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { computeDomain } from "../common/entity/compute_domain"; import { computeDomain } from "../common/entity/compute_domain";
import {
fuzzyFilterSort,
ScorableTextItem,
} from "../common/string/filter/sequence-matching";
import { import {
AreaRegistryEntry, AreaRegistryEntry,
createAreaRegistryEntry, createAreaRegistryEntry,
@ -28,6 +32,8 @@ import type { HaComboBox } from "./ha-combo-box";
import "./ha-icon-button"; import "./ha-icon-button";
import "./ha-svg-icon"; import "./ha-svg-icon";
type ScorableAreaRegistryEntry = ScorableTextItem & AreaRegistryEntry;
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = ( const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (
item item
) => html`<mwc-list-item ) => html`<mwc-list-item
@ -306,9 +312,12 @@ export class HaAreaPicker extends LitElement {
this.entityFilter, this.entityFilter,
this.noAdd, this.noAdd,
this.excludeAreas this.excludeAreas
); ).map((area) => ({
(this.comboBox as any).items = areas; ...area,
(this.comboBox as any).filteredItems = areas; strings: [area.area_id, ...area.aliases, area.name],
}));
this.comboBox.items = areas;
this.comboBox.filteredItems = areas;
} }
} }
@ -345,8 +354,9 @@ export class HaAreaPicker extends LitElement {
return; return;
} }
const filteredItems = this.comboBox.items?.filter((item) => const filteredItems = fuzzyFilterSort<ScorableAreaRegistryEntry>(
item.name.toLowerCase().includes(filter!.toLowerCase()) filter,
this.comboBox?.items || []
); );
if (!this.noAdd && filteredItems?.length === 0) { if (!this.noAdd && filteredItems?.length === 0) {
this._suggestion = filter; this._suggestion = filter;
@ -409,7 +419,7 @@ export class HaAreaPicker extends LitElement {
name, name,
}); });
const areas = [...Object.values(this.hass.areas), area]; const areas = [...Object.values(this.hass.areas), area];
(this.comboBox as any).filteredItems = this._getAreas( this.comboBox.filteredItems = this._getAreas(
areas, areas,
Object.values(this.hass.devices)!, Object.values(this.hass.devices)!,
Object.values(this.hass.entities)!, Object.values(this.hass.entities)!,

View File

@ -186,9 +186,8 @@ class HaHsColorPicker extends LitElement {
} }
if (changedProps.has("value")) { if (changedProps.has("value")) {
if ( if (
this.value !== undefined && this._localValue?.[0] !== this.value?.[0] ||
(this._localValue?.[0] !== this.value[0] || this._localValue?.[1] !== this.value?.[1]
this._localValue?.[1] !== this.value[1])
) { ) {
this._resetPosition(); this._resetPosition();
} }
@ -243,7 +242,11 @@ class HaHsColorPicker extends LitElement {
} }
private _resetPosition() { private _resetPosition() {
if (this.value === undefined) return; if (this.value === undefined) {
this._cursorPosition = undefined;
this._localValue = undefined;
return;
}
this._cursorPosition = this._getCoordsFromValue(this.value); this._cursorPosition = this._getCoordsFromValue(this.value);
this._localValue = this.value; this._localValue = this.value;
} }
@ -384,6 +387,7 @@ class HaHsColorPicker extends LitElement {
canvas { canvas {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: contain;
border-radius: 50%; border-radius: 50%;
cursor: pointer; cursor: pointer;
} }

View File

@ -0,0 +1,38 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-icon-button-group")
export class HaIconButtonGroup extends LitElement {
protected render(): TemplateResult {
return html`<slot></slot>`;
}
static get styles(): CSSResultGroup {
return css`
:host {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
height: 56px;
border-radius: 28px;
background-color: rgba(139, 145, 151, 0.1);
box-sizing: border-box;
width: auto;
padding: 4px;
gap: 4px;
}
::slotted(.separator) {
background-color: rgba(var(--rgb-primary-text-color), 0.15);
width: 1px;
height: 40px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-icon-button-group": HaIconButtonGroup;
}
}

View File

@ -0,0 +1,52 @@
import { css, CSSResultGroup } from "lit";
import { customElement, property } from "lit/decorators";
import { HaIconButton } from "./ha-icon-button";
@customElement("ha-icon-button-toggle")
export class HaIconButtonToggle extends HaIconButton {
@property({ type: Boolean, reflect: true }) selected = false;
static get styles(): CSSResultGroup {
return css`
:host {
position: relative;
}
mwc-icon-button {
position: relative;
transition: color 180ms ease-in-out;
}
mwc-icon-button::before {
opacity: 0;
transition: opacity 180ms ease-in-out;
background-color: var(--primary-text-color);
border-radius: 20px;
height: 40px;
width: 40px;
content: "";
position: absolute;
top: -10px;
left: -10px;
bottom: -10px;
right: -10px;
margin: auto;
box-sizing: border-box;
}
:host([border-only]) mwc-icon-button::before {
background-color: transparent;
border: 2px solid var(--primary-text-color);
}
:host([selected]) mwc-icon-button {
color: var(--primary-background-color);
}
:host([selected]) mwc-icon-button::before {
opacity: 1;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-icon-button-toggle": HaIconButtonToggle;
}
}

View File

@ -4,6 +4,7 @@ import { css, html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { stopPropagation } from "../../common/dom/stop_propagation"; import { stopPropagation } from "../../common/dom/stop_propagation";
import { ensureArray } from "../../common/array/ensure-array";
import type { SelectOption, SelectSelector } from "../../data/selector"; import type { SelectOption, SelectSelector } from "../../data/selector";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import "../ha-checkbox"; import "../ha-checkbox";
@ -40,7 +41,7 @@ export class HaSelectSelector extends LitElement {
protected render() { protected render() {
const options = const options =
this.selector.select?.options.map((option) => this.selector.select?.options?.map((option) =>
typeof option === "object" typeof option === "object"
? (option as SelectOption) ? (option as SelectOption)
: ({ value: option, label: option } as SelectOption) : ({ value: option, label: option } as SelectOption)
@ -77,7 +78,8 @@ export class HaSelectSelector extends LitElement {
${this._renderHelper()} ${this._renderHelper()}
`; `;
} }
const value =
!this.value || this.value === "" ? [] : ensureArray(this.value);
return html` return html`
<div> <div>
${this.label} ${this.label}
@ -85,7 +87,7 @@ export class HaSelectSelector extends LitElement {
(item: SelectOption) => html` (item: SelectOption) => html`
<ha-formfield .label=${item.label}> <ha-formfield .label=${item.label}>
<ha-checkbox <ha-checkbox
.checked=${this.value?.includes(item.value)} .checked=${value.includes(item.value)}
.value=${item.value} .value=${item.value}
.disabled=${item.disabled || this.disabled} .disabled=${item.disabled || this.disabled}
@change=${this._checkboxChanged} @change=${this._checkboxChanged}
@ -100,7 +102,7 @@ export class HaSelectSelector extends LitElement {
if (this.selector.select?.multiple) { if (this.selector.select?.multiple) {
const value = const value =
!this.value || this.value === "" ? [] : (this.value as string[]); !this.value || this.value === "" ? [] : ensureArray(this.value);
const optionItems = options.filter( const optionItems = options.filter(
(option) => !option.disabled && !value?.includes(option.value) (option) => !option.disabled && !value?.includes(option.value)
@ -231,19 +233,19 @@ export class HaSelectSelector extends LitElement {
const value: string = ev.target.value; const value: string = ev.target.value;
const checked = ev.target.checked; const checked = ev.target.checked;
const oldValue =
!this.value || this.value === "" ? [] : ensureArray(this.value);
if (checked) { if (checked) {
if (!this.value) { if (oldValue.includes(value)) {
newValue = [value];
} else if (this.value.includes(value)) {
return;
} else {
newValue = [...this.value, value];
}
} else {
if (!this.value?.includes(value)) {
return; return;
} }
newValue = (this.value as string[]).filter((v) => v !== value); newValue = [...oldValue, value];
} else {
if (!oldValue?.includes(value)) {
return;
}
newValue = oldValue.filter((v) => v !== value);
} }
fireEvent(this, "value-changed", { fireEvent(this, "value-changed", {
@ -252,7 +254,7 @@ export class HaSelectSelector extends LitElement {
} }
private async _removeItem(ev) { private async _removeItem(ev) {
const value: string[] = [...(this.value! as string[])]; const value: string[] = [...ensureArray(this.value!)];
value.splice(ev.target.idx, 1); value.splice(ev.target.idx, 1);
fireEvent(this, "value-changed", { fireEvent(this, "value-changed", {
@ -277,7 +279,10 @@ export class HaSelectSelector extends LitElement {
return; return;
} }
if (newValue !== undefined && this.value?.includes(newValue)) { const currentValue =
!this.value || this.value === "" ? [] : ensureArray(this.value);
if (newValue !== undefined && currentValue.includes(newValue)) {
return; return;
} }
@ -286,9 +291,6 @@ export class HaSelectSelector extends LitElement {
this.comboBox.setInputValue(""); this.comboBox.setInputValue("");
}, 0); }, 0);
const currentValue =
!this.value || this.value === "" ? [] : (this.value as string[]);
fireEvent(this, "value-changed", { fireEvent(this, "value-changed", {
value: [...currentValue, newValue], value: [...currentValue, newValue],
}); });

View File

@ -24,6 +24,7 @@ export class HaThemeSelector extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.value=${this.value} .value=${this.value}
.label=${this.label} .label=${this.label}
.includeDefault=${this.selector.theme?.include_default}
.disabled=${this.disabled} .disabled=${this.disabled}
.required=${this.required} .required=${this.required}
></ha-theme-picker> ></ha-theme-picker>

View File

@ -139,7 +139,7 @@ class HaTempColorPicker extends LitElement {
this.setAttribute("aria-valuemax", this.max.toString()); this.setAttribute("aria-valuemax", this.max.toString());
} }
if (changedProps.has("value")) { if (changedProps.has("value")) {
if (this.value != null && this._localValue !== this.value) { if (this._localValue !== this.value) {
this._resetPosition(); this._resetPosition();
} }
} }
@ -197,7 +197,11 @@ class HaTempColorPicker extends LitElement {
} }
private _resetPosition() { private _resetPosition() {
if (this.value === undefined) return; if (this.value === undefined) {
this._cursorPosition = undefined;
this._localValue = undefined;
return;
}
const [, y] = this._getCoordsFromValue(this.value); const [, y] = this._getCoordsFromValue(this.value);
const currentX = this._cursorPosition?.[0] ?? 0; const currentX = this._cursorPosition?.[0] ?? 0;
const x = const x =
@ -391,6 +395,7 @@ class HaTempColorPicker extends LitElement {
canvas { canvas {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: contain;
border-radius: 50%; border-radius: 50%;
transition: box-shadow 180ms ease-in-out; transition: box-shadow 180ms ease-in-out;
cursor: pointer; cursor: pointer;

View File

@ -99,6 +99,10 @@ export class HaTextField extends TextFieldBase {
direction: var(--direction); direction: var(--direction);
} }
.mdc-text-field__icon--trailing {
padding: var(--textfield-icon-trailing-padding, 12px);
}
.mdc-floating-label:not(.mdc-floating-label--float-above) { .mdc-floating-label:not(.mdc-floating-label--float-above) {
text-overflow: ellipsis; text-overflow: ellipsis;
width: inherit; width: inherit;

View File

@ -1,17 +1,28 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
css,
CSSResultGroup,
html,
nothing,
LitElement,
TemplateResult,
} from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation"; import { stopPropagation } from "../common/dom/stop_propagation";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import "./ha-select"; import "./ha-select";
const DEFAULT_THEME = "default";
@customElement("ha-theme-picker") @customElement("ha-theme-picker")
export class HaThemePicker extends LitElement { export class HaThemePicker extends LitElement {
@property() public value?: string; @property() public value?: string;
@property() public label?: string; @property() public label?: string;
@property() includeDefault?: boolean = false;
@property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean, reflect: true }) public disabled = false; @property({ type: Boolean, reflect: true }) public disabled = false;
@ -36,6 +47,13 @@ export class HaThemePicker extends LitElement {
"ui.components.theme-picker.no_theme" "ui.components.theme-picker.no_theme"
)}</mwc-list-item )}</mwc-list-item
> >
${this.includeDefault
? html`<mwc-list-item .value=${DEFAULT_THEME}
>${this.hass!.localize(
"ui.components.theme-picker.default"
)}</mwc-list-item
>`
: nothing}
${Object.keys(this.hass!.themes.themes) ${Object.keys(this.hass!.themes.themes)
.sort() .sort()
.map( .map(

View File

@ -24,6 +24,9 @@ import { EntityRegistryEntry } from "./entity_registry";
import "../resources/intl-polyfill"; import "../resources/intl-polyfill";
import { FrontendLocaleData } from "./translation"; import { FrontendLocaleData } from "./translation";
const triggerTranslationBaseKey =
"ui.panel.config.automation.editor.triggers.type";
const describeDuration = (forTime: number | string | ForDict) => { const describeDuration = (forTime: number | string | ForDict) => {
let duration: string | null; let duration: string | null;
if (typeof forTime === "number") { if (typeof forTime === "number") {
@ -90,29 +93,30 @@ export const describeTrigger = (
// Event Trigger // Event Trigger
if (trigger.platform === "event" && trigger.event_type) { if (trigger.platform === "event" && trigger.event_type) {
let eventTypes = ""; const eventTypes: string[] = [];
if (Array.isArray(trigger.event_type)) { if (Array.isArray(trigger.event_type)) {
for (const [index, state] of trigger.event_type.entries()) { for (const state of trigger.event_type.values()) {
eventTypes += `${index > 0 ? "," : ""} ${ eventTypes.push(state);
trigger.event_type.length > 1 &&
index === trigger.event_type.length - 1
? "or"
: ""
} ${state}`;
} }
} else { } else {
eventTypes = trigger.event_type.toString(); eventTypes.push(trigger.event_type);
} }
return `When ${eventTypes} event is fired`; const eventTypesString = disjunctionFormatter.format(eventTypes);
return hass.localize(
`${triggerTranslationBaseKey}.event.description.full`,
{ eventTypes: eventTypesString }
);
} }
// Home Assistant Trigger // Home Assistant Trigger
if (trigger.platform === "homeassistant" && trigger.event) { if (trigger.platform === "homeassistant" && trigger.event) {
return `When Home Assistant is ${ return hass.localize(
trigger.event === "start" ? "started" : "shutdown" trigger.event === "start"
}`; ? `${triggerTranslationBaseKey}.homeassistant.description.started`
: `${triggerTranslationBaseKey}.homeassistant.description.shutdown`
);
} }
// Numeric State Trigger // Numeric State Trigger
@ -157,7 +161,7 @@ export const describeTrigger = (
// State Trigger // State Trigger
if (trigger.platform === "state") { if (trigger.platform === "state") {
let base = "When"; let base = "When";
let entities = ""; const entities: string[] = [];
const states = hass.states; const states = hass.states;
if (trigger.attribute) { if (trigger.attribute) {
@ -173,25 +177,22 @@ export const describeTrigger = (
} }
if (Array.isArray(trigger.entity_id)) { if (Array.isArray(trigger.entity_id)) {
for (const [index, entity] of trigger.entity_id.entries()) { for (const entity of trigger.entity_id.values()) {
if (states[entity]) { if (states[entity]) {
entities += `${index > 0 ? "," : ""} ${ entities.push(computeStateName(states[entity]) || entity);
trigger.entity_id.length > 1 &&
index === trigger.entity_id.length - 1
? "or"
: ""
} ${computeStateName(states[entity]) || entity}`;
} }
} }
} else if (trigger.entity_id) { } else if (trigger.entity_id) {
entities = states[trigger.entity_id] entities.push(
states[trigger.entity_id]
? computeStateName(states[trigger.entity_id]) ? computeStateName(states[trigger.entity_id])
: trigger.entity_id; : trigger.entity_id
);
} }
if (!entities) { if (entities.length === 0) {
// no entity_id or empty array // no entity_id or empty array
entities = "something"; entities.push("something");
} }
base += ` ${entities} changes`; base += ` ${entities} changes`;
@ -208,13 +209,9 @@ export const describeTrigger = (
base += " from any state"; base += " from any state";
} }
} else if (Array.isArray(trigger.from)) { } else if (Array.isArray(trigger.from)) {
let from = ""; const from: string[] = [];
for (const [index, state] of trigger.from.entries()) { for (const state of trigger.from.values()) {
from += `${index > 0 ? "," : ""} ${ from.push(
trigger.from.length > 1 && index === trigger.from.length - 1
? "or"
: ""
} '${
trigger.attribute trigger.attribute
? computeAttributeValueDisplay( ? computeAttributeValueDisplay(
hass.localize, hass.localize,
@ -224,7 +221,7 @@ export const describeTrigger = (
hass.entities, hass.entities,
trigger.attribute, trigger.attribute,
state state
) ).toString()
: computeStateDisplay( : computeStateDisplay(
hass.localize, hass.localize,
stateObj, stateObj,
@ -233,13 +230,14 @@ export const describeTrigger = (
hass.entities, hass.entities,
state state
) )
}'`; );
} }
if (from) { if (from.length !== 0) {
base += ` from ${from}`; const fromString = disjunctionFormatter.format(from);
base += ` from ${fromString}`;
} }
} else { } else {
base += ` from '${ base += ` from ${
trigger.attribute trigger.attribute
? computeAttributeValueDisplay( ? computeAttributeValueDisplay(
hass.localize, hass.localize,
@ -258,7 +256,7 @@ export const describeTrigger = (
hass.entities, hass.entities,
trigger.from.toString() trigger.from.toString()
).toString() ).toString()
}'`; }`;
} }
} }
@ -268,11 +266,9 @@ export const describeTrigger = (
base += " to any state"; base += " to any state";
} }
} else if (Array.isArray(trigger.to)) { } else if (Array.isArray(trigger.to)) {
let to = ""; const to: string[] = [];
for (const [index, state] of trigger.to.entries()) { for (const state of trigger.to.values()) {
to += `${index > 0 ? "," : ""} ${ to.push(
trigger.to.length > 1 && index === trigger.to.length - 1 ? "or" : ""
} '${
trigger.attribute trigger.attribute
? computeAttributeValueDisplay( ? computeAttributeValueDisplay(
hass.localize, hass.localize,
@ -291,13 +287,14 @@ export const describeTrigger = (
hass.entities, hass.entities,
state state
).toString() ).toString()
}'`; );
} }
if (to) { if (to.length !== 0) {
base += ` to ${to}`; const toString = disjunctionFormatter.format(to);
base += ` to ${toString}`;
} }
} else { } else {
base += ` to '${ base += ` to ${
trigger.attribute trigger.attribute
? computeAttributeValueDisplay( ? computeAttributeValueDisplay(
hass.localize, hass.localize,
@ -315,8 +312,8 @@ export const describeTrigger = (
hass.config, hass.config,
hass.entities, hass.entities,
trigger.to.toString() trigger.to.toString()
).toString() )
}'`; }`;
} }
} }
@ -340,29 +337,28 @@ export const describeTrigger = (
// Sun Trigger // Sun Trigger
if (trigger.platform === "sun" && trigger.event) { if (trigger.platform === "sun" && trigger.event) {
let base = `When the sun ${trigger.event === "sunset" ? "sets" : "rises"}`;
if (trigger.offset) {
let duration = ""; let duration = "";
if (trigger.offset) { if (trigger.offset) {
if (typeof trigger.offset === "number") { if (typeof trigger.offset === "number") {
duration = ` offset by ${secondsToDuration(trigger.offset)!}`; duration = secondsToDuration(trigger.offset)!;
} else if (typeof trigger.offset === "string") { } else if (typeof trigger.offset === "string") {
duration = ` offset by ${trigger.offset}`; duration = trigger.offset;
} else { } else {
duration = ` offset by ${JSON.stringify(trigger.offset)}`; duration = JSON.stringify(trigger.offset);
} }
} }
base += duration;
}
return base; return hass.localize(
trigger.event === "sunset"
? `${triggerTranslationBaseKey}.sun.description.sets`
: `${triggerTranslationBaseKey}.sun.description.rises`,
{ hasDuration: duration !== "", duration: duration }
);
} }
// Tag Trigger // Tag Trigger
if (trigger.platform === "tag") { if (trigger.platform === "tag") {
return "When a tag is scanned"; return hass.localize(`${triggerTranslationBaseKey}.tag.description.full`);
} }
// Time Trigger // Time Trigger
@ -375,10 +371,9 @@ export const describeTrigger = (
: localizeTimeString(at, hass.locale, hass.config) : localizeTimeString(at, hass.locale, hass.config)
); );
const last = result.splice(-1, 1)[0]; return hass.localize(`${triggerTranslationBaseKey}.time.description.full`, {
return `When the time is equal to ${ time: disjunctionFormatter.format(result),
result.length ? `${result.join(", ")} or ` : "" });
}${last}`;
} }
// Time Pattern Trigger // Time Pattern Trigger
@ -501,9 +496,9 @@ export const describeTrigger = (
const states = hass.states; const states = hass.states;
if (Array.isArray(trigger.entity_id)) { if (Array.isArray(trigger.entity_id)) {
for (const [entity] of trigger.entity_id.entries()) { for (const entity of trigger.entity_id.values()) {
if (states[entity]) { if (states[entity]) {
entities.push(`${computeStateName(states[entity]) || entity}`); entities.push(computeStateName(states[entity]) || entity);
} }
} }
} else { } else {
@ -515,9 +510,9 @@ export const describeTrigger = (
} }
if (Array.isArray(trigger.zone)) { if (Array.isArray(trigger.zone)) {
for (const [zone] of trigger.zone.entries()) { for (const zone of trigger.zone.values()) {
if (states[zone]) { if (states[zone]) {
zones.push(`${computeStateName(states[zone]) || zone}`); zones.push(computeStateName(states[zone]) || zone);
} }
} }
} else { } else {
@ -537,67 +532,62 @@ export const describeTrigger = (
// Geo Location Trigger // Geo Location Trigger
if (trigger.platform === "geo_location" && trigger.source && trigger.zone) { if (trigger.platform === "geo_location" && trigger.source && trigger.zone) {
let sources = ""; const sources: string[] = [];
let zones = ""; const zones: string[] = [];
let zonesPlural = false;
const states = hass.states; const states = hass.states;
if (Array.isArray(trigger.source)) { if (Array.isArray(trigger.source)) {
for (const [index, source] of trigger.source.entries()) { for (const source of trigger.source.values()) {
sources += `${index > 0 ? "," : ""} ${ sources.push(source);
trigger.source.length > 1 && index === trigger.source.length - 1
? "or"
: ""
} ${source}`;
} }
} else { } else {
sources = trigger.source; sources.push(trigger.source);
} }
if (Array.isArray(trigger.zone)) { if (Array.isArray(trigger.zone)) {
if (trigger.zone.length > 1) { for (const zone of trigger.zone.values()) {
zonesPlural = true;
}
for (const [index, zone] of trigger.zone.entries()) {
if (states[zone]) { if (states[zone]) {
zones += `${index > 0 ? "," : ""} ${ zones.push(computeStateName(states[zone]) || zone);
trigger.zone.length > 1 && index === trigger.zone.length - 1
? "or"
: ""
} ${computeStateName(states[zone]) || zone}`;
} }
} }
} else { } else {
zones = states[trigger.zone] zones.push(
states[trigger.zone]
? computeStateName(states[trigger.zone]) ? computeStateName(states[trigger.zone])
: trigger.zone; : trigger.zone
);
} }
return `When ${sources} ${trigger.event}s ${zones} ${ const sourcesString = disjunctionFormatter.format(sources);
zonesPlural ? "zones" : "zone" const zonesString = disjunctionFormatter.format(zones);
return `When ${sourcesString} ${trigger.event}s ${zonesString} ${
zones.length > 1 ? "zones" : "zone"
}`; }`;
} }
// MQTT Trigger // MQTT Trigger
if (trigger.platform === "mqtt") { if (trigger.platform === "mqtt") {
return "When an MQTT message has been received"; return hass.localize(`${triggerTranslationBaseKey}.mqtt.description.full`);
} }
// Template Trigger // Template Trigger
if (trigger.platform === "template") { if (trigger.platform === "template") {
let base = "When a template triggers"; let duration = "";
if (trigger.for) { if (trigger.for) {
const duration = describeDuration(trigger.for); duration = describeDuration(trigger.for) ?? "";
if (duration) {
base += ` for ${duration}`;
} }
}
return base; return hass.localize(
`${triggerTranslationBaseKey}.template.description.full`,
{ hasDuration: duration !== "", duration: duration }
);
} }
// Webhook Trigger // Webhook Trigger
if (trigger.platform === "webhook") { if (trigger.platform === "webhook") {
return "When a Webhook payload has been received"; return hass.localize(
`${triggerTranslationBaseKey}.webhook.description.full`
);
} }
// Persistent Notification Trigger // Persistent Notification Trigger
@ -640,6 +630,10 @@ export const describeCondition = (
return condition.alias; return condition.alias;
} }
const conjunctionFormatter = new Intl.ListFormat("en", {
style: "long",
type: "conjunction",
});
const disjunctionFormatter = new Intl.ListFormat("en", { const disjunctionFormatter = new Intl.ListFormat("en", {
style: "long", style: "long",
type: "disjunction", type: "disjunction",
@ -714,21 +708,20 @@ export const describeCondition = (
} }
if (Array.isArray(condition.entity_id)) { if (Array.isArray(condition.entity_id)) {
let entities = ""; const entities: string[] = [];
for (const [index, entity] of condition.entity_id.entries()) { for (const entity of condition.entity_id.values()) {
if (hass.states[entity]) { if (hass.states[entity]) {
entities += `${index > 0 ? "," : ""} ${ entities.push(computeStateName(hass.states[entity]) || entity);
condition.entity_id.length > 1 &&
index === condition.entity_id.length - 1
? condition.match === "any"
? "or"
: "and"
: ""
} ${computeStateName(hass.states[entity]) || entity}`;
} }
} }
if (entities) { if (entities.length !== 0) {
base += ` ${entities} ${condition.entity_id.length > 1 ? "are" : "is"}`; const entitiesString =
condition.match === "any"
? disjunctionFormatter.format(entities)
: conjunctionFormatter.format(entities);
base += ` ${entitiesString} ${
condition.entity_id.length > 1 ? "are" : "is"
}`;
} else { } else {
// no entity_id or empty array // no entity_id or empty array
base += " an entity"; base += " an entity";
@ -741,7 +734,7 @@ export const describeCondition = (
} is`; } is`;
} }
let states = ""; const states: string[] = [];
const stateObj = const stateObj =
hass.states[ hass.states[
Array.isArray(condition.entity_id) Array.isArray(condition.entity_id)
@ -749,12 +742,8 @@ export const describeCondition = (
: condition.entity_id : condition.entity_id
]; ];
if (Array.isArray(condition.state)) { if (Array.isArray(condition.state)) {
for (const [index, state] of condition.state.entries()) { for (const state of condition.state.values()) {
states += `${index > 0 ? "," : ""} ${ states.push(
condition.state.length > 1 && index === condition.state.length - 1
? "or"
: ""
} '${
condition.attribute condition.attribute
? computeAttributeValueDisplay( ? computeAttributeValueDisplay(
hass.localize, hass.localize,
@ -764,7 +753,7 @@ export const describeCondition = (
hass.entities, hass.entities,
condition.attribute, condition.attribute,
state state
) ).toString()
: computeStateDisplay( : computeStateDisplay(
hass.localize, hass.localize,
stateObj, stateObj,
@ -773,10 +762,10 @@ export const describeCondition = (
hass.entities, hass.entities,
state state
) )
}'`; );
} }
} else if (condition.state !== "") { } else if (condition.state !== "") {
states = `'${ states.push(
condition.attribute condition.attribute
? computeAttributeValueDisplay( ? computeAttributeValueDisplay(
hass.localize, hass.localize,
@ -794,15 +783,16 @@ export const describeCondition = (
hass.config, hass.config,
hass.entities, hass.entities,
condition.state.toString() condition.state.toString()
).toString() )
}'`; );
} }
if (!states) { if (states.length === 0) {
states = "a state"; states.push("a state");
} }
base += ` ${states}`; const statesString = disjunctionFormatter.format(states);
base += ` ${statesString}`;
if (condition.for) { if (condition.for) {
const duration = describeDuration(condition.for); const duration = describeDuration(condition.for);
@ -891,17 +881,7 @@ export const describeCondition = (
`ui.panel.config.automation.editor.conditions.type.time.weekdays.${d}` `ui.panel.config.automation.editor.conditions.type.time.weekdays.${d}`
) )
); );
const last = localizedDays.pop(); result += " day is " + disjunctionFormatter.format(localizedDays);
result += " day is " + localizedDays.join(", ");
if (localizedDays.length) {
if (localizedDays.length > 1) {
result += ",";
}
result += " or ";
}
result += last;
} }
return result; return result;
@ -953,9 +933,9 @@ export const describeCondition = (
const states = hass.states; const states = hass.states;
if (Array.isArray(condition.entity_id)) { if (Array.isArray(condition.entity_id)) {
for (const [entity] of condition.entity_id.entries()) { for (const entity of condition.entity_id.values()) {
if (states[entity]) { if (states[entity]) {
entities.push(`${computeStateName(states[entity]) || entity}`); entities.push(computeStateName(states[entity]) || entity);
} }
} }
} else { } else {
@ -967,9 +947,9 @@ export const describeCondition = (
} }
if (Array.isArray(condition.zone)) { if (Array.isArray(condition.zone)) {
for (const [zone] of condition.zone.entries()) { for (const zone of condition.zone.values()) {
if (states[zone]) { if (states[zone]) {
zones.push(`${computeStateName(states[zone]) || zone}`); zones.push(computeStateName(states[zone]) || zone);
} }
} }
} else { } else {

View File

@ -16,6 +16,7 @@ export const CLIMATE_PRESET_NONE = "none";
export type HvacAction = export type HvacAction =
| "off" | "off"
| "preheating"
| "heating" | "heating"
| "cooling" | "cooling"
| "drying" | "drying"
@ -77,6 +78,7 @@ export const HVAC_ACTION_TO_MODE: Record<HvacAction, HvacMode> = {
cooling: "cool", cooling: "cool",
drying: "dry", drying: "dry",
fan: "fan_only", fan: "fan_only",
preheating: "heat",
heating: "heat", heating: "heat",
idle: "off", idle: "off",
off: "off", off: "off",

View File

@ -159,3 +159,5 @@ export const computeDefaultFavoriteColors = (
return colors; return colors;
}; };
export const formatTempColor = (value: number) => `${value} K`;

20
src/data/lock.ts Normal file
View File

@ -0,0 +1,20 @@
import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
export const FORMAT_TEXT = "text";
export const FORMAT_NUMBER = "number";
export const enum LockEntityFeature {
OPEN = 1,
}
interface LockEntityAttributes extends HassEntityAttributeBase {
code_format?: string;
changed_by?: string | null;
}
export interface LockEntity extends HassEntityBase {
attributes: LockEntityAttributes;
}

View File

@ -152,6 +152,12 @@ export interface MoreInfoActionConfig extends BaseActionConfig {
action: "more-info"; action: "more-info";
} }
export interface AssistActionConfig extends BaseActionConfig {
action: "assist";
pipeline_id?: string;
start_listening?: boolean;
}
export interface NoActionConfig extends BaseActionConfig { export interface NoActionConfig extends BaseActionConfig {
action: "none"; action: "none";
} }
@ -180,6 +186,7 @@ export type ActionConfig =
| NavigateActionConfig | NavigateActionConfig
| UrlActionConfig | UrlActionConfig
| MoreInfoActionConfig | MoreInfoActionConfig
| AssistActionConfig
| NoActionConfig | NoActionConfig
| CustomActionConfig; | CustomActionConfig;

View File

@ -33,6 +33,7 @@ export const isMaxMode = arrayLiteralIncludes(MODES_MAX);
export const baseActionStruct = object({ export const baseActionStruct = object({
alias: optional(string()), alias: optional(string()),
continue_on_error: optional(boolean()),
enabled: optional(boolean()), enabled: optional(boolean()),
}); });
@ -99,6 +100,7 @@ export interface BlueprintScriptConfig extends ManualScriptConfig {
interface BaseAction { interface BaseAction {
alias?: string; alias?: string;
continue_on_error?: boolean;
enabled?: boolean; enabled?: boolean;
} }
@ -230,14 +232,10 @@ interface UnknownAction extends BaseAction {
[key: string]: unknown; [key: string]: unknown;
} }
export type Action = export type NonConditionAction =
| EventAction | EventAction
| DeviceAction | DeviceAction
| ServiceAction | ServiceAction
| Condition
| ShorthandAndCondition
| ShorthandOrCondition
| ShorthandNotCondition
| DelayAction | DelayAction
| SceneAction | SceneAction
| WaitAction | WaitAction
@ -251,6 +249,13 @@ export type Action =
| ParallelAction | ParallelAction
| UnknownAction; | UnknownAction;
export type Action =
| NonConditionAction
| Condition
| ShorthandAndCondition
| ShorthandOrCondition
| ShorthandNotCondition;
export interface ActionTypes { export interface ActionTypes {
delay: DelayAction; delay: DelayAction;
wait_template: WaitAction; wait_template: WaitAction;

View File

@ -345,8 +345,7 @@ export interface TemplateSelector {
} }
export interface ThemeSelector { export interface ThemeSelector {
// eslint-disable-next-line @typescript-eslint/ban-types theme: { include_default?: boolean } | null;
theme: {} | null;
} }
export interface TimeSelector { export interface TimeSelector {
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types

View File

@ -1,14 +1,15 @@
import { mdiCheck, mdiClose } from "@mdi/js"; import { mdiCheck, mdiClose } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event"; import { ifDefined } from "lit/directives/if-defined";
import "../../../../components/ha-button"; import { fireEvent } from "../../common/dom/fire_event";
import "../../../../components/ha-control-button"; import "../../components/ha-button";
import { createCloseHeading } from "../../../../components/ha-dialog"; import "../../components/ha-control-button";
import "../../../../components/ha-textfield"; import { createCloseHeading } from "../../components/ha-dialog";
import type { HaTextField } from "../../../../components/ha-textfield"; import "../../components/ha-textfield";
import { HomeAssistant } from "../../../../types"; import type { HaTextField } from "../../components/ha-textfield";
import { HassDialog } from "../../../make-dialog-manager"; import { HomeAssistant } from "../../types";
import { HassDialog } from "../make-dialog-manager";
import { EnterCodeDialogParams } from "./show-enter-code-dialog"; import { EnterCodeDialogParams } from "./show-enter-code-dialog";
const BUTTONS = [ const BUTTONS = [
@ -72,7 +73,8 @@ export class DialogEnterCode
} }
private _inputValueChange(e) { private _inputValueChange(e) {
const val = (e.currentTarget! as any).value; const field = e.currentTarget as HaTextField;
const val = field.value;
this._showClearButton = !!val; this._showClearButton = !!val;
} }
@ -97,6 +99,7 @@ export class DialogEnterCode
id="code" id="code"
.label=${this.hass.localize("ui.dialogs.enter_code.input_label")} .label=${this.hass.localize("ui.dialogs.enter_code.input_label")}
type="password" type="password"
pattern=${ifDefined(this._dialogParams.codePattern)}
input-mode="text" input-mode="text"
></ha-textfield> ></ha-textfield>
<ha-button slot="secondaryAction" dialogAction="cancel"> <ha-button slot="secondaryAction" dialogAction="cancel">

View File

@ -1,7 +1,8 @@
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
export interface EnterCodeDialogParams { export interface EnterCodeDialogParams {
codeFormat: "text" | "number"; codeFormat: "text" | "number";
codePattern?: string;
submitText?: string; submitText?: string;
cancelText?: string; cancelText?: string;
title?: string; title?: string;

View File

@ -14,7 +14,7 @@ import {
} from "../../../../data/alarm_control_panel"; } from "../../../../data/alarm_control_panel";
import { UNAVAILABLE } from "../../../../data/entity"; import { UNAVAILABLE } from "../../../../data/entity";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { showEnterCodeDialogDialog } from "./show-enter-code-dialog"; import { showEnterCodeDialogDialog } from "../../../enter-code/show-enter-code-dialog";
@customElement("ha-more-info-alarm_control_panel-modes") @customElement("ha-more-info-alarm_control_panel-modes")
export class HaMoreInfoAlarmControlPanelModes extends LitElement { export class HaMoreInfoAlarmControlPanelModes extends LitElement {

View File

@ -1,16 +1,28 @@
import { mdiClose } from "@mdi/js"; import { mdiClose } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/ha-button"; import "../../../../components/ha-button";
import "../../../../components/ha-dialog"; import "../../../../components/ha-dialog";
import "../../../../components/ha-dialog-header"; import "../../../../components/ha-dialog-header";
import { EntityRegistryEntry } from "../../../../data/entity_registry"; import "../../../../components/ha-icon-button-toggle";
import { LightColor } from "../../../../data/light"; import type { EntityRegistryEntry } from "../../../../data/entity_registry";
import {
formatTempColor,
LightColor,
LightColorMode,
LightEntity,
lightSupportsColor,
lightSupportsColorMode,
} from "../../../../data/light";
import { haStyleDialog } from "../../../../resources/styles"; import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import "./light-color-picker"; import "./light-color-rgb-picker";
import { LightColorFavoriteDialogParams } from "./show-dialog-light-color-favorite"; import "./light-color-temp-picker";
import type { LightColorFavoriteDialogParams } from "./show-dialog-light-color-favorite";
export type LightPickerMode = "color_temp" | "color";
@customElement("dialog-light-color-favorite") @customElement("dialog-light-color-favorite")
class DialogLightColorFavorite extends LitElement { class DialogLightColorFavorite extends LitElement {
@ -22,11 +34,26 @@ class DialogLightColorFavorite extends LitElement {
@state() _color?: LightColor; @state() _color?: LightColor;
@state() private _mode?: LightPickerMode;
@state() private _modes: LightPickerMode[] = [];
@state() private _currentValue?: string;
private _colorHovered(ev: CustomEvent<HASSDomEvents["color-hovered"]>) {
if (ev.detail && "color_temp_kelvin" in ev.detail) {
this._currentValue = formatTempColor(ev.detail.color_temp_kelvin);
} else {
this._currentValue = undefined;
}
}
public async showDialog( public async showDialog(
dialogParams: LightColorFavoriteDialogParams dialogParams: LightColorFavoriteDialogParams
): Promise<void> { ): Promise<void> {
this._entry = dialogParams.entry; this._entry = dialogParams.entry;
this._dialogParams = dialogParams; this._dialogParams = dialogParams;
this._updateModes(dialogParams.defaultMode);
await this.updateComplete; await this.updateComplete;
} }
@ -37,10 +64,43 @@ class DialogLightColorFavorite extends LitElement {
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
private _updateModes(defaultMode?: LightPickerMode) {
const supportsTemp = lightSupportsColorMode(
this.stateObj!,
LightColorMode.COLOR_TEMP
);
const supportsColor = lightSupportsColor(this.stateObj!);
const modes: LightPickerMode[] = [];
if (supportsColor) {
modes.push("color");
}
if (supportsTemp) {
modes.push("color_temp");
}
this._modes = modes;
this._mode =
defaultMode ??
(this.stateObj!.attributes.color_mode
? this.stateObj!.attributes.color_mode === LightColorMode.COLOR_TEMP
? LightColorMode.COLOR_TEMP
: "color"
: this._modes[0]);
}
private _colorChanged(ev: CustomEvent) { private _colorChanged(ev: CustomEvent) {
this._color = ev.detail; this._color = ev.detail;
} }
get stateObj() {
return (
this._entry &&
(this.hass.states[this._entry.entity_id] as LightEntity | undefined)
);
}
private async _cancel() { private async _cancel() {
this._dialogParams?.cancel?.(); this._dialogParams?.cancel?.();
this.closeDialog(); this.closeDialog();
@ -55,8 +115,16 @@ class DialogLightColorFavorite extends LitElement {
this.closeDialog(); this.closeDialog();
} }
private _modeChanged(ev): void {
const newMode = ev.currentTarget.mode;
if (newMode === this._mode) {
return;
}
this._mode = newMode;
}
protected render() { protected render() {
if (!this._entry) { if (!this._entry || !this.stateObj) {
return nothing; return nothing;
} }
@ -76,13 +144,58 @@ class DialogLightColorFavorite extends LitElement {
></ha-icon-button> ></ha-icon-button>
<span slot="title">${this._dialogParams?.title}</span> <span slot="title">${this._dialogParams?.title}</span>
</ha-dialog-header> </ha-dialog-header>
<light-color-picker <div class="header">
.hass=${this.hass} <span class="value">${this._currentValue}</span>
entityId=${this._entry.entity_id} ${this._modes.length > 1
.defaultMode=${this._dialogParams?.defaultMode} ? html`
@color-changed=${this._colorChanged} <div class="modes">
${this._modes.map(
(value) =>
html`
<ha-icon-button-toggle
border-only
.selected=${value === this._mode}
.label=${this.hass.localize(
`ui.dialogs.more_info_control.light.color_picker.mode.${value}`
)}
.mode=${value}
@click=${this._modeChanged}
> >
</light-color-picker> <span
class="wheel ${classMap({ [value]: true })}"
></span>
</ha-icon-button-toggle>
`
)}
</div>
`
: nothing}
</div>
<div class="content">
${this._mode === "color_temp"
? html`
<light-color-temp-picker
.hass=${this.hass}
.stateObj=${this.stateObj}
@color-changed=${this._colorChanged}
@color-hovered=${this._colorHovered}
>
</light-color-temp-picker>
`
: nothing}
${this._mode === "color"
? html`
<light-color-rgb-picker
.hass=${this.hass}
.stateObj=${this.stateObj}
@color-changed=${this._colorChanged}
@color-hovered=${this._colorHovered}
>
</light-color-rgb-picker>
`
: nothing}
</div>
<ha-button slot="secondaryAction" dialogAction="cancel"> <ha-button slot="secondaryAction" dialogAction="cancel">
${this.hass.localize("ui.common.cancel")} ${this.hass.localize("ui.common.cancel")}
</ha-button> </ha-button>
@ -101,16 +214,10 @@ class DialogLightColorFavorite extends LitElement {
--dialog-content-padding: 0; --dialog-content-padding: 0;
} }
light-color-picker {
display: flex;
flex-direction: column;
flex: 1;
}
@media all and (max-width: 450px), all and (max-height: 500px) { @media all and (max-width: 450px), all and (max-height: 500px) {
ha-dialog { ha-dialog {
--dialog-surface-margin-top: 100px; --dialog-surface-margin-top: 100px;
--mdc-dialog-min-height: calc(100% - 100px); --mdc-dialog-min-height: auto;
--mdc-dialog-max-height: calc(100% - 100px); --mdc-dialog-max-height: calc(100% - 100px);
--ha-dialog-border-radius: var( --ha-dialog-border-radius: var(
--ha-dialog-bottom-sheet-border-radius, --ha-dialog-bottom-sheet-border-radius,
@ -118,6 +225,54 @@ class DialogLightColorFavorite extends LitElement {
); );
} }
} }
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
flex: 1;
}
.modes {
display: flex;
flex-direction: row;
justify-content: flex-end;
padding: 0 24px;
}
.wheel {
width: 30px;
height: 30px;
flex: none;
border-radius: 15px;
}
.wheel.color {
background-image: url("/static/images/color_wheel.png");
background-size: cover;
}
.wheel.color_temp {
background: linear-gradient(
0,
rgb(166, 209, 255) 0%,
white 50%,
rgb(255, 160, 0) 100%
);
}
.value {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
right: 0;
margin: auto;
font-style: normal;
font-weight: 500;
font-size: 16px;
height: 48px;
line-height: 48px;
letter-spacing: 0.1px;
text-align: center;
}
`, `,
]; ];
} }

View File

@ -30,10 +30,16 @@ import {
} from "../../../../resources/sortable.ondemand"; } from "../../../../resources/sortable.ondemand";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { showConfirmationDialog } from "../../../generic/show-dialog-box"; import { showConfirmationDialog } from "../../../generic/show-dialog-box";
import type { LightPickerMode } from "./dialog-light-color-favorite";
import "./ha-favorite-color-button"; import "./ha-favorite-color-button";
import type { LightPickerMode } from "./light-color-picker";
import { showLightColorFavoriteDialog } from "./show-dialog-light-color-favorite"; import { showLightColorFavoriteDialog } from "./show-dialog-light-color-favorite";
declare global {
interface HASSDomEvents {
"favorite-color-edit-started";
}
}
@customElement("ha-more-info-light-favorite-colors") @customElement("ha-more-info-light-favorite-colors")
export class HaMoreInfoLightFavoriteColors extends LitElement { export class HaMoreInfoLightFavoriteColors extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -147,8 +153,8 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
private _edit = async (index) => { private _edit = async (index) => {
// Make sure the current favorite color is set // Make sure the current favorite color is set
fireEvent(this, "favorite-color-edit-started");
await this._apply(index); await this._apply(index);
const defaultMode: LightPickerMode = const defaultMode: LightPickerMode =
"color_temp_kelvin" in this._favoriteColors[index] "color_temp_kelvin" in this._favoriteColors[index]
? "color_temp" ? "color_temp"

View File

@ -1,51 +0,0 @@
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { HomeAssistant } from "../../../../types";
import "./light-color-picker";
import { LightColorPickerViewParams } from "./show-view-light-color-picker";
@customElement("ha-more-info-view-light-color-picker")
class MoreInfoViewLightColorPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public params?: LightColorPickerViewParams;
protected render() {
if (!this.params) {
return nothing;
}
return html`
<light-color-picker
.hass=${this.hass}
.entityId=${this.params.entityId}
.defaultMode=${this.params.defaultMode}
>
</light-color-picker>
`;
}
static get styles(): CSSResultGroup {
return [
css`
:host {
position: relative;
display: flex;
flex-direction: column;
flex: 1;
}
light-color-picker {
display: flex;
flex-direction: column;
flex: 1;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-more-info-view-light-color-picker": MoreInfoViewLightColorPicker;
}
}

View File

@ -23,21 +23,18 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import { throttle } from "../../../../common/util/throttle"; import { throttle } from "../../../../common/util/throttle";
import "../../../../components/ha-button-toggle-group"; import "../../../../components/ha-button-toggle-group";
import "../../../../components/ha-hs-color-picker"; import "../../../../components/ha-hs-color-picker";
import "../../../../components/ha-icon";
import "../../../../components/ha-icon-button-prev"; import "../../../../components/ha-icon-button-prev";
import "../../../../components/ha-labeled-slider"; import "../../../../components/ha-labeled-slider";
import "../../../../components/ha-temp-color-picker"; import "../../../../components/ha-temp-color-picker";
import { import {
LightColor,
getLightCurrentModeRgbColor, getLightCurrentModeRgbColor,
LightColor,
LightColorMode, LightColorMode,
LightEntity, LightEntity,
lightSupportsColor,
lightSupportsColorMode, lightSupportsColorMode,
} from "../../../../data/light"; } from "../../../../data/light";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import "../../../../components/ha-icon";
export type LightPickerMode = "color_temp" | "color";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
@ -45,13 +42,11 @@ declare global {
} }
} }
@customElement("light-color-picker") @customElement("light-color-rgb-picker")
class LightColorPicker extends LitElement { class LightRgbColorPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId!: string; @property({ attribute: false }) public stateObj!: LightEntity;
@property() public defaultMode?: LightPickerMode;
@state() private _cwSliderValue?: number; @state() private _cwSliderValue?: number;
@ -65,16 +60,6 @@ class LightColorPicker extends LitElement {
@state() private _hsPickerValue?: [number, number]; @state() private _hsPickerValue?: [number, number];
@state() private _ctPickerValue?: number;
@state() private _mode?: LightPickerMode;
@state() private _modes: LightPickerMode[] = [];
get stateObj() {
return this.hass.states[this.entityId] as LightEntity | undefined;
}
protected render() { protected render() {
if (!this.stateObj) { if (!this.stateObj) {
return nothing; return nothing;
@ -100,41 +85,6 @@ class LightColorPicker extends LitElement {
: ""; : "";
return html` return html`
${this._modes.length > 1
? html`
<mwc-tab-bar
.activeIndex=${this._mode ? this._modes.indexOf(this._mode) : 0}
@MDCTabBar:activated=${this._handleTabChanged}
>
${this._modes.map(
(value) =>
html`<mwc-tab
.label=${this.hass.localize(
`ui.dialogs.more_info_control.light.color_picker.mode.${value}`
)}
></mwc-tab>`
)}
</mwc-tab-bar>
`
: nothing}
<div class="content">
${this._mode === LightColorMode.COLOR_TEMP
? html`
<p class="color-temp-value">
${this._ctPickerValue ? `${this._ctPickerValue} K` : nothing}
</p>
<ha-temp-color-picker
@value-changed=${this._ctColorChanged}
@cursor-moved=${this._ctColorCursorMoved}
.min=${this.stateObj.attributes.min_color_temp_kelvin!}
.max=${this.stateObj.attributes.max_color_temp_kelvin!}
.value=${this._ctPickerValue}
>
</ha-temp-color-picker>
`
: nothing}
${this._mode === "color"
? html`
<div class="color-container"> <div class="color-container">
<label class="native-color-picker"> <label class="native-color-picker">
<input <input
@ -168,9 +118,7 @@ class LightColorPicker extends LitElement {
</div> </div>
${supportsRgbw || supportsRgbww ${supportsRgbw || supportsRgbww
? html`<ha-labeled-slider ? html`<ha-labeled-slider
.caption=${this.hass.localize( .caption=${this.hass.localize("ui.card.light.color_brightness")}
"ui.card.light.color_brightness"
)}
icon="hass:brightness-7" icon="hass:brightness-7"
max="100" max="100"
.value=${this._colorBrightnessSliderValue} .value=${this._colorBrightnessSliderValue}
@ -181,9 +129,7 @@ class LightColorPicker extends LitElement {
${supportsRgbw ${supportsRgbw
? html` ? html`
<ha-labeled-slider <ha-labeled-slider
.caption=${this.hass.localize( .caption=${this.hass.localize("ui.card.light.white_value")}
"ui.card.light.white_value"
)}
icon="hass:file-word-box" icon="hass:file-word-box"
max="100" max="100"
.name=${"wv"} .name=${"wv"}
@ -196,9 +142,7 @@ class LightColorPicker extends LitElement {
${supportsRgbww ${supportsRgbww
? html` ? html`
<ha-labeled-slider <ha-labeled-slider
.caption=${this.hass.localize( .caption=${this.hass.localize("ui.card.light.cold_white_value")}
"ui.card.light.cold_white_value"
)}
icon="hass:file-word-box-outline" icon="hass:file-word-box-outline"
max="100" max="100"
.name=${"cw"} .name=${"cw"}
@ -207,9 +151,7 @@ class LightColorPicker extends LitElement {
pin pin
></ha-labeled-slider> ></ha-labeled-slider>
<ha-labeled-slider <ha-labeled-slider
.caption=${this.hass.localize( .caption=${this.hass.localize("ui.card.light.warm_white_value")}
"ui.card.light.warm_white_value"
)}
icon="hass:file-word-box" icon="hass:file-word-box"
max="100" max="100"
.name=${"ww"} .name=${"ww"}
@ -219,16 +161,13 @@ class LightColorPicker extends LitElement {
></ha-labeled-slider> ></ha-labeled-slider>
` `
: nothing} : nothing}
`
: nothing}
</div>
`; `;
} }
public _updateSliderValues() { public _updateSliderValues() {
const stateObj = this.stateObj; const stateObj = this.stateObj;
if (stateObj?.state === "on") { if (stateObj.state === "on") {
this._brightnessAdjusted = undefined; this._brightnessAdjusted = undefined;
if ( if (
stateObj.attributes.color_mode === LightColorMode.RGB && stateObj.attributes.color_mode === LightColorMode.RGB &&
@ -242,10 +181,6 @@ class LightColorPicker extends LitElement {
this._brightnessAdjusted = maxVal; this._brightnessAdjusted = maxVal;
} }
} }
this._ctPickerValue =
stateObj.attributes.color_mode === LightColorMode.COLOR_TEMP
? stateObj.attributes.color_temp_kelvin
: undefined;
this._wvSliderValue = this._wvSliderValue =
stateObj.attributes.color_mode === LightColorMode.RGBW && stateObj.attributes.color_mode === LightColorMode.RGBW &&
@ -273,8 +208,7 @@ class LightColorPicker extends LitElement {
? rgb2hs(currentRgbColor.slice(0, 3) as [number, number, number]) ? rgb2hs(currentRgbColor.slice(0, 3) as [number, number, number])
: undefined; : undefined;
} else { } else {
this._hsPickerValue = [0, 0]; this._hsPickerValue = undefined;
this._ctPickerValue = undefined;
this._wvSliderValue = undefined; this._wvSliderValue = undefined;
this._cwSliderValue = undefined; this._cwSliderValue = undefined;
this._wwSliderValue = undefined; this._wwSliderValue = undefined;
@ -288,43 +222,9 @@ class LightColorPicker extends LitElement {
return; return;
} }
if (changedProps.has("entityId")) {
const supportsTemp = lightSupportsColorMode(
this.stateObj!,
LightColorMode.COLOR_TEMP
);
const supportsColor = lightSupportsColor(this.stateObj!);
const modes: LightPickerMode[] = [];
if (supportsColor) {
modes.push("color");
}
if (supportsTemp) {
modes.push("color_temp");
}
this._modes = modes;
this._mode =
this.defaultMode ??
(this.stateObj!.attributes.color_mode
? this.stateObj!.attributes.color_mode === LightColorMode.COLOR_TEMP
? LightColorMode.COLOR_TEMP
: "color"
: this._modes[0]);
}
this._updateSliderValues(); this._updateSliderValues();
} }
private _handleTabChanged(ev: CustomEvent): void {
const newMode = this._modes[ev.detail.index];
if (newMode === this._mode) {
return;
}
this._mode = newMode;
}
private _hsColorCursorMoved(ev: CustomEvent) { private _hsColorCursorMoved(ev: CustomEvent) {
if (!ev.detail.value) { if (!ev.detail.value) {
return; return;
@ -404,40 +304,6 @@ class LightColorPicker extends LitElement {
this._updateColor(); this._updateColor();
} }
private _ctColorCursorMoved(ev: CustomEvent) {
const ct = ev.detail.value;
if (isNaN(ct) || this._ctPickerValue === ct) {
return;
}
this._ctPickerValue = ct;
this._throttleUpdateColorTemp();
}
private _throttleUpdateColorTemp = throttle(() => {
this._updateColorTemp();
}, 500);
private _ctColorChanged(ev: CustomEvent) {
const ct = ev.detail.value;
if (isNaN(ct) || this._ctPickerValue === ct) {
return;
}
this._ctPickerValue = ct;
this._updateColorTemp();
}
private _updateColorTemp() {
const color_temp_kelvin = this._ctPickerValue!;
this._applyColor({ color_temp_kelvin });
}
private _wvSliderChanged(ev: CustomEvent) { private _wvSliderChanged(ev: CustomEvent) {
const target = ev.target as any; const target = ev.target as any;
let wv = Number(target.value); let wv = Number(target.value);
@ -574,19 +440,12 @@ class LightColorPicker extends LitElement {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
flex: 1;
}
.native-color-picker { .native-color-picker {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
z-index: 1;
} }
.native-color-picker ha-svg-icon { .native-color-picker ha-svg-icon {
@ -639,37 +498,18 @@ class LightColorPicker extends LitElement {
.color-container { .color-container {
position: relative; position: relative;
max-width: 300px;
min-width: 200px;
margin: 0 0 44px 0;
padding-top: 44px;
} }
ha-hs-color-picker { ha-hs-color-picker {
width: 100%; height: 45vh;
} max-height: 320px;
min-height: 200px;
ha-temp-color-picker {
max-width: 300px;
min-width: 200px;
margin: 20px 0 44px 0;
} }
ha-labeled-slider { ha-labeled-slider {
width: 100%; width: 100%;
} }
.color-temp-value {
font-style: normal;
font-weight: 500;
font-size: 16px;
height: 24px;
line-height: 24px;
letter-spacing: 0.1px;
margin: 0;
direction: ltr;
}
hr { hr {
border-color: var(--divider-color); border-color: var(--divider-color);
border-bottom: none; border-bottom: none;
@ -682,6 +522,6 @@ class LightColorPicker extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"light-color-picker": LightColorPicker; "light-color-rgb-picker": LightRgbColorPicker;
} }
} }

View File

@ -0,0 +1,146 @@
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { throttle } from "../../../../common/util/throttle";
import "../../../../components/ha-temp-color-picker";
import {
LightColor,
LightColorMode,
LightEntity,
} from "../../../../data/light";
import { HomeAssistant } from "../../../../types";
declare global {
interface HASSDomEvents {
"color-changed": LightColor;
"color-hovered": LightColor | undefined;
}
}
@customElement("light-color-temp-picker")
class LightColorTempPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: LightEntity;
@state() private _ctPickerValue?: number;
protected render() {
if (!this.stateObj) {
return nothing;
}
return html`
<ha-temp-color-picker
@value-changed=${this._ctColorChanged}
@cursor-moved=${this._ctColorCursorMoved}
.min=${this.stateObj.attributes.min_color_temp_kelvin!}
.max=${this.stateObj.attributes.max_color_temp_kelvin!}
.value=${this._ctPickerValue}
>
</ha-temp-color-picker>
`;
}
public _updateSliderValues() {
const stateObj = this.stateObj;
if (stateObj.state === "on") {
this._ctPickerValue =
stateObj.attributes.color_mode === LightColorMode.COLOR_TEMP
? stateObj.attributes.color_temp_kelvin
: undefined;
} else {
this._ctPickerValue = undefined;
}
}
public willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (!changedProps.has("stateObj")) {
return;
}
this._updateSliderValues();
}
private _ctColorCursorMoved(ev: CustomEvent) {
const ct = ev.detail.value;
if (isNaN(ct) || this._ctPickerValue === ct) {
return;
}
this._ctPickerValue = ct;
fireEvent(this, "color-hovered", {
color_temp_kelvin: ct,
});
this._throttleUpdateColorTemp();
}
private _throttleUpdateColorTemp = throttle(() => {
this._updateColorTemp();
}, 500);
private _ctColorChanged(ev: CustomEvent) {
const ct = ev.detail.value;
fireEvent(this, "color-hovered", undefined);
if (isNaN(ct) || this._ctPickerValue === ct) {
return;
}
this._ctPickerValue = ct;
this._updateColorTemp();
}
private _updateColorTemp() {
const color_temp_kelvin = this._ctPickerValue!;
this._applyColor({ color_temp_kelvin });
}
private _applyColor(color: LightColor, params?: Record<string, any>) {
fireEvent(this, "color-changed", color);
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
...color,
...params,
});
}
static get styles(): CSSResultGroup {
return [
css`
:host {
display: flex;
flex-direction: column;
}
ha-temp-color-picker {
height: 45vh;
max-height: 320px;
min-height: 200px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"light-color-temp-picker": LightColorTempPicker;
}
}

View File

@ -1,7 +1,7 @@
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { ExtEntityRegistryEntry } from "../../../../data/entity_registry"; import { ExtEntityRegistryEntry } from "../../../../data/entity_registry";
import { LightColor } from "../../../../data/light"; import { LightColor } from "../../../../data/light";
import type { LightPickerMode } from "./light-color-picker"; import type { LightPickerMode } from "./dialog-light-color-favorite";
export interface LightColorFavoriteDialogParams { export interface LightColorFavoriteDialogParams {
entry: ExtEntityRegistryEntry; entry: ExtEntityRegistryEntry;

View File

@ -1,23 +0,0 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LightPickerMode } from "./light-color-picker";
export interface LightColorPickerViewParams {
entityId: string;
defaultMode: LightPickerMode;
}
export const loadLightColorPickerView = () =>
import("./ha-more-info-view-light-color-picker");
export const showLightColorPickerView = (
element: HTMLElement,
title: string,
params: LightColorPickerViewParams
): void => {
fireEvent(element, "show-child-view", {
viewTag: "ha-more-info-view-light-color-picker",
viewImport: loadLightColorPickerView,
viewTitle: title,
viewParams: params,
});
};

View File

@ -0,0 +1,199 @@
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { domainIcon } from "../../../../common/entity/domain_icon";
import { stateColorCss } from "../../../../common/entity/state_color";
import "../../../../components/ha-control-button";
import "../../../../components/ha-control-switch";
import { UNAVAILABLE, UNKNOWN } from "../../../../data/entity";
import { forwardHaptic } from "../../../../data/haptics";
import { LockEntity } from "../../../../data/lock";
import { HomeAssistant } from "../../../../types";
import { showEnterCodeDialogDialog } from "../../../enter-code/show-enter-code-dialog";
@customElement("ha-more-info-lock-toggle")
export class HaMoreInfoLockToggle extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj!: LockEntity;
@state() private _isOn = false;
public willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (changedProps.has("stateObj")) {
this._isOn =
this.stateObj.state === "locked" || this.stateObj.state === "locking";
}
}
private _valueChanged(ev) {
const checked = ev.target.checked as boolean;
if (checked) {
this._turnOn();
} else {
this._turnOff();
}
}
private async _turnOn() {
this._isOn = true;
try {
await this._callService(true);
} catch (err) {
this._isOn = false;
}
}
private async _turnOff() {
this._isOn = false;
try {
await this._callService(false);
} catch (err) {
this._isOn = true;
}
}
private async _callService(turnOn: boolean): Promise<void> {
if (!this.hass || !this.stateObj) {
return;
}
forwardHaptic("light");
let code: string | undefined;
if (this.stateObj.attributes.code_format) {
const response = await showEnterCodeDialogDialog(this, {
codeFormat: "text",
codePattern: this.stateObj.attributes.code_format,
title: this.hass.localize(
`ui.dialogs.more_info_control.lock.${turnOn ? "lock" : "unlock"}`
),
submitText: this.hass.localize(
`ui.dialogs.more_info_control.lock.${turnOn ? "lock" : "unlock"}`
),
});
if (response == null) {
throw new Error("cancel");
}
code = response;
}
await this.hass.callService("lock", turnOn ? "lock" : "unlock", {
entity_id: this.stateObj.entity_id,
code,
});
}
protected render(): TemplateResult {
const locking = this.stateObj.state === "locking";
const unlocking = this.stateObj.state === "unlocking";
const color = stateColorCss(this.stateObj);
const onIcon = domainIcon(
"lock",
this.stateObj,
locking ? "locking" : "locked"
);
const offIcon = domainIcon(
"lock",
this.stateObj,
unlocking ? "unlocking" : "unlocked"
);
if (this.stateObj.state === UNKNOWN) {
return html`
<div class="buttons">
<ha-control-button
.label=${this.hass.localize(
"ui.dialogs.more_info_control.lock.lock"
)}
@click=${this._turnOn}
>
<ha-svg-icon .path=${onIcon}></ha-svg-icon>
</ha-control-button>
<ha-control-button
.label=${this.hass.localize(
"ui.dialogs.more_info_control.lock.unlock"
)}
@click=${this._turnOff}
>
<ha-svg-icon .path=${offIcon}></ha-svg-icon>
</ha-control-button>
</div>
`;
}
return html`
<ha-control-switch
.pathOn=${onIcon}
.pathOff=${offIcon}
vertical
reversed
.checked=${this._isOn}
@change=${this._valueChanged}
.ariaLabel=${this.hass.localize("ui.dialogs.more_info_control.toggle")}
style=${styleMap({
"--control-switch-on-color": color,
"--control-switch-off-color": color,
})}
.disabled=${this.stateObj.state === UNAVAILABLE}
>
</ha-control-switch>
`;
}
static get styles(): CSSResultGroup {
return css`
ha-control-switch {
height: 45vh;
max-height: 320px;
min-height: 200px;
--control-switch-thickness: 100px;
--control-switch-border-radius: 24px;
--control-switch-padding: 6px;
--mdc-icon-size: 24px;
}
.buttons {
display: flex;
flex-direction: column;
width: 100px;
height: 45vh;
max-height: 320px;
min-height: 200px;
padding: 6px;
box-sizing: border-box;
}
ha-control-button {
flex: 1;
width: 100%;
--control-button-border-radius: 18px;
--mdc-icon-size: 24px;
}
ha-control-button.active {
--control-button-icon-color: white;
--control-button-background-color: var(--color);
--control-button-background-opacity: 1;
}
ha-control-button:not(:last-child) {
margin-bottom: 6px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-more-info-lock-toggle": HaMoreInfoLockToggle;
}
}

View File

@ -22,6 +22,7 @@ export const DOMAINS_WITH_NEW_MORE_INFO = [
"fan", "fan",
"input_boolean", "input_boolean",
"light", "light",
"lock",
"siren", "siren",
"switch", "switch",
]; ];

View File

@ -7,8 +7,8 @@ import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-outlined-button"; import "../../../components/ha-outlined-button";
import { AlarmControlPanelEntity } from "../../../data/alarm_control_panel"; import { AlarmControlPanelEntity } from "../../../data/alarm_control_panel";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { showEnterCodeDialogDialog } from "../../enter-code/show-enter-code-dialog";
import "../components/alarm_control_panel/ha-more-info-alarm_control_panel-modes"; import "../components/alarm_control_panel/ha-more-info-alarm_control_panel-modes";
import { showEnterCodeDialogDialog } from "../components/alarm_control_panel/show-enter-code-dialog";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style"; import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import "../components/ha-more-info-state-header"; import "../components/ha-more-info-state-header";

View File

@ -11,6 +11,8 @@ import { customElement, property, state } from "lit/decorators";
import { computeStateDisplay } from "../../../common/entity/compute_state_display"; import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { supportsFeature } from "../../../common/entity/supports-feature"; import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes"; import "../../../components/ha-attributes";
import "../../../components/ha-icon-button-group";
import "../../../components/ha-icon-button-toggle";
import { import {
computeCoverPositionStateDisplay, computeCoverPositionStateDisplay,
CoverEntity, CoverEntity,
@ -24,6 +26,8 @@ import "../components/cover/ha-more-info-cover-toggle";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style"; import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import "../components/ha-more-info-state-header"; import "../components/ha-more-info-state-header";
type Mode = "position" | "button";
@customElement("more-info-cover") @customElement("more-info-cover")
class MoreInfoCover extends LitElement { class MoreInfoCover extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -34,10 +38,10 @@ class MoreInfoCover extends LitElement {
@state() private _liveTilt?: number; @state() private _liveTilt?: number;
@state() private _mode?: "position" | "button"; @state() private _mode?: Mode;
private _toggleMode() { private _setMode(ev) {
this._mode = this._mode === "position" ? "button" : "position"; this._mode = ev.currentTarget.mode;
} }
private _positionSliderMoved(ev) { private _positionSliderMoved(ev) {
@ -192,19 +196,26 @@ class MoreInfoCover extends LitElement {
(supportsPosition || supportsTiltPosition) && (supportsPosition || supportsTiltPosition) &&
(supportsOpenClose || supportsTilt) (supportsOpenClose || supportsTilt)
? html` ? html`
<div class="actions"> <ha-icon-button-group>
<ha-icon-button <ha-icon-button-toggle
.label=${this.hass.localize( .label=${this.hass.localize(
`ui.dialogs.more_info_control.cover.switch_mode.${ `ui.dialogs.more_info_control.cover.switch_mode.position`
this._mode === "position" ? "button" : "position"
}`
)} )}
.path=${this._mode === "position" .selected=${this._mode === "position"}
? mdiSwapVertical .path=${mdiMenu}
: mdiMenu} .mode=${"position"}
@click=${this._toggleMode} @click=${this._setMode}
></ha-icon-button> ></ha-icon-button-toggle>
</div> <ha-icon-button-toggle
.label=${this.hass.localize(
`ui.dialogs.more_info_control.cover.switch_mode.button`
)}
.selected=${this._mode === "button"}
.path=${mdiSwapVertical}
.mode=${"button"}
@click=${this._setMode}
></ha-icon-button-toggle>
</ha-icon-button-group>
` `
: nothing : nothing
} }

View File

@ -1,5 +1,6 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { import {
mdiBrightness6,
mdiCreation, mdiCreation,
mdiFileWordBox, mdiFileWordBox,
mdiLightbulb, mdiLightbulb,
@ -24,6 +25,8 @@ import { supportsFeature } from "../../../common/entity/supports-feature";
import { blankBeforePercent } from "../../../common/translations/blank_before_percent"; import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
import "../../../components/ha-attributes"; import "../../../components/ha-attributes";
import "../../../components/ha-button-menu"; import "../../../components/ha-button-menu";
import "../../../components/ha-icon-button-group";
import "../../../components/ha-icon-button-toggle";
import "../../../components/ha-outlined-button"; import "../../../components/ha-outlined-button";
import "../../../components/ha-outlined-icon-button"; import "../../../components/ha-outlined-icon-button";
import "../../../components/ha-select"; import "../../../components/ha-select";
@ -31,6 +34,7 @@ import { UNAVAILABLE } from "../../../data/entity";
import { ExtEntityRegistryEntry } from "../../../data/entity_registry"; import { ExtEntityRegistryEntry } from "../../../data/entity_registry";
import { forwardHaptic } from "../../../data/haptics"; import { forwardHaptic } from "../../../data/haptics";
import { import {
formatTempColor,
LightColorMode, LightColorMode,
LightEntity, LightEntity,
LightEntityFeature, LightEntityFeature,
@ -46,7 +50,10 @@ import "../components/ha-more-info-toggle";
import "../components/lights/ha-favorite-color-button"; import "../components/lights/ha-favorite-color-button";
import "../components/lights/ha-more-info-light-brightness"; import "../components/lights/ha-more-info-light-brightness";
import "../components/lights/ha-more-info-light-favorite-colors"; import "../components/lights/ha-more-info-light-favorite-colors";
import { showLightColorPickerView } from "../components/lights/show-view-light-color-picker"; import "../components/lights/light-color-rgb-picker";
import "../components/lights/light-color-temp-picker";
type MainControl = "brightness" | "color_temp" | "color";
@customElement("more-info-light") @customElement("more-info-light")
class MoreInfoLight extends LitElement { class MoreInfoLight extends LitElement {
@ -62,12 +69,24 @@ class MoreInfoLight extends LitElement {
@state() private _selectedBrightness?: number; @state() private _selectedBrightness?: number;
@state() private _colorTempPreview?: number;
@state() private _mainControl: MainControl = "brightness";
private _brightnessChanged(ev) { private _brightnessChanged(ev) {
const value = (ev.detail as any).value; const value = (ev.detail as any).value;
if (isNaN(value)) return; if (isNaN(value)) return;
this._selectedBrightness = value; this._selectedBrightness = value;
} }
private _tempColorHovered(ev: CustomEvent<HASSDomEvents["color-hovered"]>) {
if (ev.detail && "color_temp_kelvin" in ev.detail) {
this._colorTempPreview = ev.detail.color_temp_kelvin;
} else {
this._colorTempPreview = undefined;
}
}
protected updated(changedProps: PropertyValues<typeof this>): void { protected updated(changedProps: PropertyValues<typeof this>): void {
if (changedProps.has("stateObj")) { if (changedProps.has("stateObj")) {
this._selectedBrightness = this.stateObj?.attributes.brightness this._selectedBrightness = this.stateObj?.attributes.brightness
@ -77,6 +96,28 @@ class MoreInfoLight extends LitElement {
} }
} }
private _setMainControl(ev: any) {
ev.stopPropagation();
this._mainControl = ev.currentTarget.control;
}
private _resetMainControl(ev: any) {
ev.stopPropagation();
this._mainControl = "brightness";
}
private get _stateOverride() {
if (this._colorTempPreview) {
return formatTempColor(this._colorTempPreview);
}
if (this._selectedBrightness) {
return `${Math.round(this._selectedBrightness)}${blankBeforePercent(
this.hass!.locale
)}%`;
}
return undefined;
}
protected render() { protected render() {
if (!this.hass || !this.stateObj) { if (!this.hass || !this.stateObj) {
return nothing; return nothing;
@ -106,20 +147,26 @@ class MoreInfoLight extends LitElement {
(this.entry.options?.light?.favorite_colors == null || (this.entry.options?.light?.favorite_colors == null ||
this.entry.options.light.favorite_colors.length > 0); this.entry.options.light.favorite_colors.length > 0);
const stateOverride = this._selectedBrightness
? `${Math.round(this._selectedBrightness)}${blankBeforePercent(
this.hass!.locale
)}%`
: undefined;
return html` return html`
<ha-more-info-state-header <ha-more-info-state-header
.hass=${this.hass} .hass=${this.hass}
.stateObj=${this.stateObj} .stateObj=${this.stateObj}
.stateOverride=${stateOverride} .stateOverride=${this._stateOverride}
></ha-more-info-state-header> ></ha-more-info-state-header>
<div class="controls"> <div class="controls">
${supportsBrightness ${!supportsBrightness
? html`
<ha-more-info-toggle
.stateObj=${this.stateObj}
.hass=${this.hass}
.iconPathOn=${mdiLightbulb}
.iconPathOff=${mdiLightbulbOff}
></ha-more-info-toggle>
`
: nothing}
${supportsColorTemp || supportsColor || supportsBrightness
? html`
${supportsBrightness && this._mainControl === "brightness"
? html` ? html`
<ha-more-info-light-brightness <ha-more-info-light-brightness
.stateObj=${this.stateObj} .stateObj=${this.stateObj}
@ -128,25 +175,32 @@ class MoreInfoLight extends LitElement {
> >
</ha-more-info-light-brightness> </ha-more-info-light-brightness>
` `
: html` : nothing}
<ha-more-info-toggle ${supportsColor && this._mainControl === "color"
.stateObj=${this.stateObj}
.hass=${this.hass}
.iconPathOn=${mdiLightbulb}
.iconPathOff=${mdiLightbulbOff}
></ha-more-info-toggle>
`}
${supportsColorTemp || supportsColor || supportsBrightness
? html` ? html`
<div class="button-bar"> <light-color-rgb-picker
.hass=${this.hass}
.stateObj=${this.stateObj}
>
</light-color-rgb-picker>
`
: nothing}
${supportsColorTemp && this._mainControl === "color_temp"
? html`
<light-color-temp-picker
.hass=${this.hass}
.stateObj=${this.stateObj}
@color-hovered=${this._tempColorHovered}
>
</light-color-temp-picker>
`
: nothing}
<ha-icon-button-group>
${supportsBrightness ${supportsBrightness
? html` ? html`
<ha-icon-button <ha-icon-button
.disabled=${this.stateObj!.state === UNAVAILABLE} .disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.more_info_control.light.toggle"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.light.toggle" "ui.dialogs.more_info_control.light.toggle"
)} )}
@click=${this._toggle} @click=${this._toggle}
@ -155,49 +209,60 @@ class MoreInfoLight extends LitElement {
</ha-icon-button> </ha-icon-button>
` `
: nothing} : nothing}
${supportsColor || supportsColorTemp
? html`
<div class="separator"></div>
<ha-icon-button-toggle
.selected=${this._mainControl === "brightness"}
.disabled=${this.stateObj!.state === UNAVAILABLE}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.light.brightness"
)}
.control=${"brightness"}
@click=${this._setMainControl}
>
<ha-svg-icon .path=${mdiBrightness6}></ha-svg-icon>
</ha-icon-button-toggle>
`
: nothing}
${supportsColor ${supportsColor
? html` ? html`
<ha-icon-button <ha-icon-button-toggle
class="color-mode" border-only
.selected=${this._mainControl === "color"}
.disabled=${this.stateObj!.state === UNAVAILABLE} .disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.more_info_control.light.change_color" "ui.dialogs.more_info_control.light.color"
)} )}
.ariaLabel=${this.hass.localize( .control=${"color"}
"ui.dialogs.more_info_control.light.change_color" @click=${this._setMainControl}
)}
.mode=${"color"}
@click=${this._showLightColorPickerView}
> >
<span class="wheel color"></span> <span class="wheel color"></span>
</ha-icon-button> </ha-icon-button-toggle>
` `
: nothing} : nothing}
${supportsColorTemp ${supportsColorTemp
? html` ? html`
<ha-icon-button <ha-icon-button-toggle
border-only
.selected=${this._mainControl === "color_temp"}
.disabled=${this.stateObj!.state === UNAVAILABLE} .disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.more_info_control.light.change_color_temp" "ui.dialogs.more_info_control.light.color_temp"
)} )}
.ariaLabel=${this.hass.localize( .control=${"color_temp"}
"ui.dialogs.more_info_control.light.change_color_temp" @click=${this._setMainControl}
)}
.mode=${"color_temp"}
@click=${this._showLightColorPickerView}
> >
<span class="wheel color-temp"></span> <span class="wheel color-temp"></span>
</ha-icon-button> </ha-icon-button-toggle>
` `
: nothing} : nothing}
${supportsWhite ${supportsWhite
? html` ? html`
<div class="separator"></div>
<ha-icon-button <ha-icon-button
.disabled=${this.stateObj!.state === UNAVAILABLE} .disabled=${this.stateObj!.state === UNAVAILABLE}
.title=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.more_info_control.light.set_white"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.light.set_white" "ui.dialogs.more_info_control.light.set_white"
)} )}
@click=${this._setWhite} @click=${this._setWhite}
@ -206,7 +271,7 @@ class MoreInfoLight extends LitElement {
</ha-icon-button> </ha-icon-button>
` `
: nothing} : nothing}
</div> </ha-icon-button-group>
${this.entry && ${this.entry &&
lightSupportsFavoriteColors(this.stateObj) && lightSupportsFavoriteColors(this.stateObj) &&
(this.editMode || hasFavoriteColors) (this.editMode || hasFavoriteColors)
@ -216,6 +281,7 @@ class MoreInfoLight extends LitElement {
.stateObj=${this.stateObj} .stateObj=${this.stateObj}
.entry=${this.entry} .entry=${this.entry}
.editMode=${this.editMode} .editMode=${this.editMode}
@favorite-color-edit-started=${this._resetMainControl}
> >
</ha-more-info-light-favorite-colors> </ha-more-info-light-favorite-colors>
` `
@ -291,19 +357,6 @@ class MoreInfoLight extends LitElement {
}); });
}; };
private _showLightColorPickerView = (ev) => {
showLightColorPickerView(
this,
this.hass.localize(
"ui.dialogs.more_info_control.light.color_picker.title"
),
{
entityId: this.stateObj!.entity_id,
defaultMode: ev.currentTarget.mode,
}
);
};
private _setWhite = () => { private _setWhite = () => {
this.hass.callService("light", "turn_on", { this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id, entity_id: this.stateObj!.entity_id,
@ -347,9 +400,6 @@ class MoreInfoLight extends LitElement {
flex: none; flex: none;
border-radius: 15px; border-radius: 15px;
} }
ha-icon-button[disabled] .wheel {
filter: grayscale(1) opacity(0.5);
}
.wheel.color { .wheel.color {
background-image: url("/static/images/color_wheel.png"); background-image: url("/static/images/color_wheel.png");
background-size: cover; background-size: cover;
@ -362,6 +412,9 @@ class MoreInfoLight extends LitElement {
rgb(255, 160, 0) 100% rgb(255, 160, 0) 100%
); );
} }
*[disabled] .wheel {
filter: grayscale(1) opacity(0.5);
}
.buttons { .buttons {
flex-wrap: wrap; flex-wrap: wrap;
max-width: 250px; max-width: 250px;

View File

@ -1,43 +1,156 @@
import "@material/mwc-button"; import "@material/web/iconbutton/outlined-icon-button";
import type { HassEntity } from "home-assistant-js-websocket"; import { mdiDoorOpen, mdiLock, mdiLockOff } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { domainIcon } from "../../../common/entity/domain_icon";
import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes"; import "../../../components/ha-attributes";
import "../../../components/ha-textfield"; import { UNAVAILABLE } from "../../../data/entity";
import type { HaTextField } from "../../../components/ha-textfield"; import { LockEntity, LockEntityFeature } from "../../../data/lock";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { showEnterCodeDialogDialog } from "../../enter-code/show-enter-code-dialog";
import { moreInfoControlStyle } from "../components/ha-more-info-control-style";
import "../components/ha-more-info-state-header";
import "../components/lock/ha-more-info-lock-toggle";
@customElement("more-info-lock") @customElement("more-info-lock")
class MoreInfoLock extends LitElement { class MoreInfoLock extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj?: HassEntity; @property({ attribute: false }) public stateObj?: LockEntity;
@query("ha-textfield") private _textfield?: HaTextField; private async _open() {
this._callService("open");
}
private async _lock() {
this._callService("lock");
}
private async _unlock() {
this._callService("unlock");
}
private async _callService(service: "open" | "lock" | "unlock") {
let code: string | undefined;
if (this.stateObj!.attributes.code_format) {
const response = await showEnterCodeDialogDialog(this, {
codeFormat: "text",
codePattern: this.stateObj!.attributes.code_format,
title: this.hass.localize(
`ui.dialogs.more_info_control.lock.${service}`
),
submitText: this.hass.localize(
`ui.dialogs.more_info_control.lock.${service}`
),
});
if (!response) {
return;
}
code = response;
}
this.hass.callService("lock", service, {
entity_id: this.stateObj!.entity_id,
code,
});
}
protected render() { protected render() {
if (!this.hass || !this.stateObj) { if (!this.hass || !this.stateObj) {
return nothing; return nothing;
} }
const supportsOpen = supportsFeature(this.stateObj, LockEntityFeature.OPEN);
const color = stateColorCss(this.stateObj);
const style = {
"--icon-color": color,
};
const isJammed = this.stateObj.state === "jammed";
return html` return html`
${this.stateObj.attributes.code_format <ha-more-info-state-header
? html`<div class="code"> .hass=${this.hass}
<ha-textfield .stateObj=${this.stateObj}
.label=${this.hass.localize("ui.card.lock.code")} ></ha-more-info-state-header>
.pattern=${this.stateObj.attributes.code_format} <div class="controls" style=${styleMap(style)}>
type="password" ${
></ha-textfield> this.stateObj.state === "jammed"
${this.stateObj.state === "locked" ? html`
? html`<mwc-button <div class="status">
@click=${this._callService} <span></span>
data-service="unlock" <div class="icon">
>${this.hass.localize("ui.card.lock.unlock")}</mwc-button <ha-svg-icon
>` .path=${domainIcon("lock", this.stateObj)}
: html`<mwc-button @click=${this._callService} data-service="lock" ></ha-svg-icon>
>${this.hass.localize("ui.card.lock.lock")}</mwc-button </div>
>`} </div>
</div>` `
: ""} : html`
<ha-more-info-lock-toggle
.stateObj=${this.stateObj}
.hass=${this.hass}
>
</ha-more-info-lock-toggle>
`
}
${
supportsOpen || isJammed
? html`
<div class="buttons">
${supportsOpen
? html`
<md-outlined-icon-button
.disabled=${this.stateObj.state === UNAVAILABLE}
.title=${this.hass.localize(
"ui.dialogs.more_info_control.lock.open"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.lock.open"
)}
@click=${this._open}
>
<ha-svg-icon .path=${mdiDoorOpen}></ha-svg-icon>
</md-outlined-icon-button>
`
: nothing}
${isJammed
? html`
<md-outlined-icon-button
.title=${this.hass.localize(
"ui.dialogs.more_info_control.lock.lock"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.lock.lock"
)}
@click=${this._lock}
>
<ha-svg-icon .path=${mdiLock}></ha-svg-icon>
</md-outlined-icon-button>
<md-outlined-icon-button
.title=${this.hass.localize(
"ui.dialogs.more_info_control.lock.unlock"
)}
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.lock.unlock"
)}
@click=${this._unlock}
>
<ha-svg-icon .path=${mdiLockOff}></ha-svg-icon>
</md-outlined-icon-button>
`
: nothing}
</div>
`
: nothing
}
</div>
</div>
<ha-attributes <ha-attributes
.hass=${this.hass} .hass=${this.hass}
.stateObj=${this.stateObj} .stateObj=${this.stateObj}
@ -46,33 +159,64 @@ class MoreInfoLock extends LitElement {
`; `;
} }
private _callService(ev) { static get styles(): CSSResultGroup {
const service = ev.target.getAttribute("data-service"); return [
const data = { moreInfoControlStyle,
entity_id: this.stateObj!.entity_id, css`
code: this._textfield?.value, md-outlined-icon-button {
}; --ha-icon-display: block;
this.hass.callService("lock", service, data); --md-sys-color-on-surface: var(--secondary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-sys-color-on-surface-rgb: var(--rgb-secondary-text-color);
--md-sys-color-outline: var(--secondary-text-color);
} }
@keyframes pulse {
static styles = css` 0% {
:host { opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.status {
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
justify-content: center;
height: 45vh;
max-height: 320px;
min-height: 200px;
} }
.code { .status .icon {
position: relative;
--mdc-icon-size: 80px;
animation: pulse 1s infinite;
color: var(--icon-color);
border-radius: 50%;
width: 144px;
height: 144px;
display: flex; display: flex;
flex-direction: row;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
margin-bottom: 8px;
width: 100%;
} }
ha-attributes { .status .icon::before {
content: "";
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%; width: 100%;
border-radius: 50%;
background-color: var(--icon-color);
transition: background-color 180ms ease-in-out;
opacity: 0.2;
}
`,
];
} }
`;
} }
declare global { declare global {

View File

@ -24,10 +24,10 @@ import { stopPropagation } from "../../common/dom/stop_propagation";
import "../../components/ha-button"; import "../../components/ha-button";
import "../../components/ha-button-menu"; import "../../components/ha-button-menu";
import "../../components/ha-dialog"; import "../../components/ha-dialog";
import "../../components/ha-dialog-header";
import "../../components/ha-icon-button"; import "../../components/ha-icon-button";
import "../../components/ha-list-item"; import "../../components/ha-list-item";
import "../../components/ha-textfield"; import "../../components/ha-textfield";
import "../../components/ha-dialog-header";
import type { HaTextField } from "../../components/ha-textfield"; import type { HaTextField } from "../../components/ha-textfield";
import { import {
AssistPipeline, AssistPipeline,
@ -41,6 +41,7 @@ import type { HomeAssistant } from "../../types";
import { AudioRecorder } from "../../util/audio-recorder"; import { AudioRecorder } from "../../util/audio-recorder";
import { documentationUrl } from "../../util/documentation-url"; import { documentationUrl } from "../../util/documentation-url";
import { showAlertDialog } from "../generic/show-dialog-box"; import { showAlertDialog } from "../generic/show-dialog-box";
import { VoiceCommandDialogParams } from "./show-ha-voice-command-dialog";
interface Message { interface Message {
who: string; who: string;
@ -82,7 +83,13 @@ export class HaVoiceCommandDialog extends LitElement {
private _stt_binary_handler_id?: number | null; private _stt_binary_handler_id?: number | null;
public async showDialog(): Promise<void> { private _pipelinePromise?: Promise<AssistPipeline>;
public async showDialog(params?: VoiceCommandDialogParams): Promise<void> {
if (params?.pipeline_id) {
this._pipelineId = params?.pipeline_id;
}
this._conversation = [ this._conversation = [
{ {
who: "hass", who: "hass",
@ -92,6 +99,11 @@ export class HaVoiceCommandDialog extends LitElement {
this._opened = true; this._opened = true;
await this.updateComplete; await this.updateComplete;
this._scrollMessagesBottom(); this._scrollMessagesBottom();
await this._pipelinePromise;
if (params?.start_listening && this._pipeline?.stt_engine) {
this._toggleListening();
}
} }
public async closeDialog(): Promise<void> { public async closeDialog(): Promise<void> {
@ -230,7 +242,7 @@ export class HaVoiceCommandDialog extends LitElement {
<div class="listening-icon"> <div class="listening-icon">
<ha-icon-button <ha-icon-button
.path=${mdiMicrophone} .path=${mdiMicrophone}
@click=${this._toggleListening} @click=${this._handleListeningButton}
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.voice_command.start_listening" "ui.dialogs.voice_command.start_listening"
)} )}
@ -275,7 +287,8 @@ export class HaVoiceCommandDialog extends LitElement {
private async _getPipeline() { private async _getPipeline() {
try { try {
this._pipeline = await getAssistPipeline(this.hass, this._pipelineId); this._pipelinePromise = getAssistPipeline(this.hass, this._pipelineId);
this._pipeline = await this._pipelinePromise;
} catch (e: any) { } catch (e: any) {
if (e.code === "not_found") { if (e.code === "not_found") {
this._pipelineId = undefined; this._pipelineId = undefined;
@ -392,9 +405,13 @@ export class HaVoiceCommandDialog extends LitElement {
} }
} }
private _toggleListening(ev) { private _handleListeningButton(ev) {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault(); ev.preventDefault();
this._toggleListening();
}
private _toggleListening() {
const supportsMicrophone = AudioRecorder.isSupported; const supportsMicrophone = AudioRecorder.isSupported;
if (!supportsMicrophone) { if (!supportsMicrophone) {
this._showNotSupportedMessage(); this._showNotSupportedMessage();

View File

@ -3,19 +3,29 @@ import { HomeAssistant } from "../../types";
const loadVoiceCommandDialog = () => import("./ha-voice-command-dialog"); const loadVoiceCommandDialog = () => import("./ha-voice-command-dialog");
export interface VoiceCommandDialogParams {
pipeline_id?: string;
start_listening?: boolean;
}
export const showVoiceCommandDialog = ( export const showVoiceCommandDialog = (
element: HTMLElement, element: HTMLElement,
hass: HomeAssistant hass: HomeAssistant,
dialogParams?: VoiceCommandDialogParams
): void => { ): void => {
if (hass.auth.external?.config.hasAssist) { if (hass.auth.external?.config.hasAssist) {
hass.auth.external!.fireMessage({ hass.auth.external!.fireMessage({
type: "assist/show", type: "assist/show",
payload: {
pipeline_id: dialogParams?.pipeline_id,
start_listening: dialogParams?.start_listening,
},
}); });
return; return;
} }
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "ha-voice-command-dialog", dialogTag: "ha-voice-command-dialog",
dialogImport: loadVoiceCommandDialog, dialogImport: loadVoiceCommandDialog,
dialogParams: {}, dialogParams,
}); });
}; };

View File

@ -133,7 +133,15 @@ window.hassConnection.then(({ conn }) => {
}); });
window.addEventListener("error", (e) => { window.addEventListener("error", (e) => {
if (!__DEV__ && e.message === "ResizeObserver loop limit exceeded") { if (
!__DEV__ &&
typeof e.message === "string" &&
(e.message.includes("ResizeObserver loop limit exceeded") ||
e.message.includes(
"ResizeObserver loop completed with undelivered notifications"
))
) {
e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
e.stopPropagation(); e.stopPropagation();
return; return;

View File

@ -131,7 +131,7 @@ export class ExternalAuth extends Auth {
export const createExternalAuth = async (hassUrl: string) => { export const createExternalAuth = async (hassUrl: string) => {
const auth = new ExternalAuth(hassUrl); const auth = new ExternalAuth(hassUrl);
if ( if (
(window.externalApp && window.externalApp.externalBus) || window.externalApp?.externalBus ||
(window.webkit && window.webkit.messageHandlers.externalBus) (window.webkit && window.webkit.messageHandlers.externalBus)
) { ) {
auth.external = new ExternalMessaging(); auth.external = new ExternalMessaging();

View File

@ -1,6 +1,7 @@
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { import {
mdiAlertCircleCheck,
mdiCheck, mdiCheck,
mdiContentDuplicate, mdiContentDuplicate,
mdiContentCopy, mdiContentCopy,
@ -14,7 +15,14 @@ import {
mdiStopCircleOutline, mdiStopCircleOutline,
} from "@mdi/js"; } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
@ -34,7 +42,11 @@ import {
subscribeEntityRegistry, subscribeEntityRegistry,
} from "../../../../data/entity_registry"; } from "../../../../data/entity_registry";
import { Clipboard } from "../../../../data/automation"; import { Clipboard } from "../../../../data/automation";
import { Action, getActionType } from "../../../../data/script"; import {
Action,
NonConditionAction,
getActionType,
} from "../../../../data/script";
import { describeAction } from "../../../../data/script_i18n"; import { describeAction } from "../../../../data/script_i18n";
import { callExecuteScript } from "../../../../data/service"; import { callExecuteScript } from "../../../../data/service";
import { import {
@ -184,6 +196,17 @@ export default class HaAutomationActionRow extends LitElement {
</h3> </h3>
<slot name="icons" slot="icons"></slot> <slot name="icons" slot="icons"></slot>
${type !== "condition" &&
(this.action as NonConditionAction).continue_on_error === true
? html`<div slot="icons">
<ha-svg-icon .path=${mdiAlertCircleCheck}></ha-svg-icon>
<simple-tooltip animation-delay="0">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.continue_on_error"
)}
</simple-tooltip>
</div> `
: nothing}
${this.hideMenu ${this.hideMenu
? "" ? ""
: html` : html`

View File

@ -1,7 +1,10 @@
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { assert } from "superstruct"; import { assert } from "superstruct";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import { computeDomain } from "../../../../../common/entity/compute_domain";
import { computeObjectId } from "../../../../../common/entity/compute_object_id";
import { hasTemplate } from "../../../../../common/string/has-template"; import { hasTemplate } from "../../../../../common/string/has-template";
import "../../../../../components/ha-service-control"; import "../../../../../components/ha-service-control";
import { ServiceAction, serviceActionStruct } from "../../../../../data/script"; import { ServiceAction, serviceActionStruct } from "../../../../../data/script";
@ -20,6 +23,26 @@ export class HaServiceAction extends LitElement implements ActionElement {
@state() private _action!: ServiceAction; @state() private _action!: ServiceAction;
private _fields = memoizeOne(
(
serviceDomains: HomeAssistant["services"],
domainService: string | undefined
): { fields: any } => {
if (!domainService) {
return { fields: {} };
}
const domain = computeDomain(domainService);
const service = computeObjectId(domainService);
if (!(domain in serviceDomains)) {
return { fields: {} };
}
if (!(service in serviceDomains[domain])) {
return { fields: {} };
}
return { fields: serviceDomains[domain][service].fields };
}
);
public static get defaultConfig() { public static get defaultConfig() {
return { service: "", data: {} }; return { service: "", data: {} };
} }
@ -34,7 +57,28 @@ export class HaServiceAction extends LitElement implements ActionElement {
fireEvent(this, "ui-mode-not-available", err); fireEvent(this, "ui-mode-not-available", err);
return; return;
} }
if (this.action && hasTemplate(this.action)) {
const fields = this._fields(
this.hass.services,
this.action?.service
).fields;
if (
this.action &&
(Object.entries(this.action).some(
([key, val]) => key !== "data" && hasTemplate(val)
) ||
(this.action.data &&
Object.entries(this.action.data).some(([key, val]) => {
const field = fields[key];
if (
field?.selector &&
("template" in field.selector || "object" in field.selector)
) {
return false;
}
return hasTemplate(val);
})))
) {
fireEvent( fireEvent(
this, this,
"ui-mode-not-available", "ui-mode-not-available",

View File

@ -1,9 +1,11 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import memoizeOne from "memoize-one";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement } from "lit"; import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import { ensureArray } from "../../../../../common/array/ensure-array"; import { ensureArray } from "../../../../../common/array/ensure-array";
import type { SchemaUnion } from "../../../../../components/ha-form/types";
import "../../../../../components/ha-select"; import "../../../../../components/ha-select";
import type { import type {
AutomationConfig, AutomationConfig,
@ -30,6 +32,22 @@ export class HaTriggerCondition extends LitElement {
}; };
} }
private _schema = memoizeOne(
(triggers: Trigger[]) =>
[
{
name: "id",
selector: {
select: {
multiple: true,
options: triggers.map((trigger) => trigger.id!),
},
},
required: true,
},
] as const
);
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
const details = { callback: (config) => this._automationUpdated(config) }; const details = { callback: (config) => this._automationUpdated(config) };
@ -45,30 +63,33 @@ export class HaTriggerCondition extends LitElement {
} }
protected render() { protected render() {
const { id } = this.condition;
if (!this._triggers.length) { if (!this._triggers.length) {
return this.hass.localize( return this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.trigger.no_triggers" "ui.panel.config.automation.editor.conditions.type.trigger.no_triggers"
); );
} }
return html`<ha-select
.label=${this.hass.localize( const schema = this._schema(this._triggers);
"ui.panel.config.automation.editor.conditions.type.trigger.id"
)} return html`
.value=${id} <ha-form
.schema=${schema}
.data=${this.condition}
.hass=${this.hass}
.disabled=${this.disabled} .disabled=${this.disabled}
@selected=${this._triggerPicked} .computeLabel=${this._computeLabelCallback}
> @value-changed=${this._valueChanged}
${this._triggers.map( ></ha-form>
(trigger) => `;
html`
<mwc-list-item .value=${trigger.id}> ${trigger.id} </mwc-list-item>
`
)}
</ha-select>`;
} }
private _computeLabelCallback = (
schema: SchemaUnion<ReturnType<typeof this._schema>>
): string =>
this.hass.localize(
`ui.panel.config.automation.editor.conditions.type.trigger.${schema.name}`
);
private _automationUpdated(config?: AutomationConfig) { private _automationUpdated(config?: AutomationConfig) {
const seenIds = new Set(); const seenIds = new Set();
this._triggers = config?.trigger this._triggers = config?.trigger
@ -78,18 +99,24 @@ export class HaTriggerCondition extends LitElement {
: []; : [];
} }
private _triggerPicked(ev) { private _valueChanged(ev: CustomEvent): void {
ev.stopPropagation(); ev.stopPropagation();
if (!ev.target.value) { const newValue = ev.detail.value;
return;
if (typeof newValue.id === "string") {
if (!this._triggers.some((trigger) => trigger.id === newValue.id)) {
newValue.id = "";
} }
const newTrigger = ev.target.value; } else if (Array.isArray(newValue.id)) {
if (this.condition.id === newTrigger) { newValue.id = newValue.id.filter((id) =>
return; this._triggers.some((trigger) => trigger.id === id)
);
if (!newValue.id.length) {
newValue.id = "";
} }
fireEvent(this, "value-changed", { }
value: { ...this.condition, id: newTrigger },
}); fireEvent(this, "value-changed", { value: newValue });
} }
} }

View File

@ -18,8 +18,10 @@ import "../../../components/ha-icon-next";
import "../../../components/ha-list-item"; import "../../../components/ha-list-item";
import "../../../components/ha-tip"; import "../../../components/ha-tip";
import { showAutomationEditor } from "../../../data/automation"; import { showAutomationEditor } from "../../../data/automation";
import { showScriptEditor } from "../../../data/script";
import { import {
Blueprint, Blueprint,
BlueprintDomain,
Blueprints, Blueprints,
BlueprintSourceType, BlueprintSourceType,
fetchBlueprints, fetchBlueprints,
@ -29,6 +31,7 @@ import { HassDialog } from "../../../dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../resources/styles"; import { haStyle, haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
import type { NewAutomationDialogParams } from "./show-dialog-new-automation";
const SOURCE_TYPE_ICONS: Record<BlueprintSourceType, string> = { const SOURCE_TYPE_ICONS: Record<BlueprintSourceType, string> = {
local: mdiFile, local: mdiFile,
@ -42,11 +45,15 @@ class DialogNewAutomation extends LitElement implements HassDialog {
@state() private _opened = false; @state() private _opened = false;
@state() private _mode: BlueprintDomain = "automation";
@state() public blueprints?: Blueprints; @state() public blueprints?: Blueprints;
public showDialog(): void { public showDialog(params: NewAutomationDialogParams): void {
this._opened = true; this._opened = true;
fetchBlueprints(this.hass!, "automation").then((blueprints) => { this._mode = params?.mode || "automation";
fetchBlueprints(this.hass!, this._mode).then((blueprints) => {
this.blueprints = blueprints; this.blueprints = blueprints;
}); });
} }
@ -92,14 +99,14 @@ class DialogNewAutomation extends LitElement implements HassDialog {
@closed=${this.closeDialog} @closed=${this.closeDialog}
.heading=${createCloseHeading( .heading=${createCloseHeading(
this.hass, this.hass,
this.hass.localize("ui.panel.config.automation.dialog_new.header") this.hass.localize(`ui.panel.config.${this._mode}.dialog_new.header`)
)} )}
> >
<mwc-list <mwc-list
innerRole="listbox" innerRole="listbox"
itemRoles="option" itemRoles="option"
innerAriaLabel=${this.hass.localize( innerAriaLabel=${this.hass.localize(
"ui.panel.config.automation.dialog_new.header" `ui.panel.config.${this._mode}.dialog_new.header`
)} )}
rootTabbable rootTabbable
dialogInitialFocus dialogInitialFocus
@ -112,11 +119,11 @@ class DialogNewAutomation extends LitElement implements HassDialog {
> >
<ha-svg-icon slot="graphic" .path=${mdiPencilOutline}></ha-svg-icon> <ha-svg-icon slot="graphic" .path=${mdiPencilOutline}></ha-svg-icon>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.dialog_new.create_empty" `ui.panel.config.${this._mode}.dialog_new.create_empty`
)} )}
<span slot="secondary"> <span slot="secondary">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.dialog_new.create_empty_description" `ui.panel.config.${this._mode}.dialog_new.create_empty_description`
)} )}
</span> </span>
<ha-icon-next slot="meta"></ha-icon-next> <ha-icon-next slot="meta"></ha-icon-next>
@ -139,11 +146,11 @@ class DialogNewAutomation extends LitElement implements HassDialog {
<span slot="secondary"> <span slot="secondary">
${blueprint.author ${blueprint.author
? this.hass.localize( ? this.hass.localize(
`ui.panel.config.automation.dialog_new.blueprint_source.author`, `ui.panel.config.${this._mode}.dialog_new.blueprint_source.author`,
{ author: blueprint.author } { author: blueprint.author }
) )
: this.hass.localize( : this.hass.localize(
`ui.panel.config.automation.dialog_new.blueprint_source.${blueprint.sourceType}` `ui.panel.config.${this._mode}.dialog_new.blueprint_source.${blueprint.sourceType}`
)} )}
</span> </span>
<ha-icon-next slot="meta"></ha-icon-next> <ha-icon-next slot="meta"></ha-icon-next>
@ -161,11 +168,11 @@ class DialogNewAutomation extends LitElement implements HassDialog {
<ha-list-item hasmeta twoline graphic="icon"> <ha-list-item hasmeta twoline graphic="icon">
<ha-svg-icon slot="graphic" .path=${mdiWeb}></ha-svg-icon> <ha-svg-icon slot="graphic" .path=${mdiWeb}></ha-svg-icon>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.dialog_new.create_blueprint" `ui.panel.config.${this._mode}.dialog_new.create_blueprint`
)} )}
<span slot="secondary"> <span slot="secondary">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.dialog_new.create_blueprint_description" `ui.panel.config.${this._mode}.dialog_new.create_blueprint_description`
)} )}
</span> </span>
<ha-svg-icon slot="meta" path=${mdiOpenInNew}></ha-svg-icon> <ha-svg-icon slot="meta" path=${mdiOpenInNew}></ha-svg-icon>
@ -180,7 +187,7 @@ class DialogNewAutomation extends LitElement implements HassDialog {
rel="noreferrer noopener" rel="noreferrer noopener"
> >
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.dialog_new.discover_blueprint_tip" `ui.panel.config.${this._mode}.dialog_new.discover_blueprint_tip`
)} )}
</a> </a>
</ha-tip> </ha-tip>
@ -196,16 +203,24 @@ class DialogNewAutomation extends LitElement implements HassDialog {
} }
const path = (ev.currentTarget! as any).path; const path = (ev.currentTarget! as any).path;
this.closeDialog(); this.closeDialog();
if (this._mode === "script") {
showScriptEditor({ use_blueprint: { path } });
} else {
showAutomationEditor({ use_blueprint: { path } }); showAutomationEditor({ use_blueprint: { path } });
} }
}
private async _blank(ev) { private async _blank(ev) {
if (!shouldHandleRequestSelectedEvent(ev)) { if (!shouldHandleRequestSelectedEvent(ev)) {
return; return;
} }
this.closeDialog(); this.closeDialog();
if (this._mode === "script") {
showScriptEditor();
} else {
showAutomationEditor(); showAutomationEditor();
} }
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [

View File

@ -486,7 +486,7 @@ class HaAutomationPicker extends LitElement {
private _createNew() { private _createNew() {
if (isComponentLoaded(this.hass, "blueprint")) { if (isComponentLoaded(this.hass, "blueprint")) {
showNewAutomationDialog(this); showNewAutomationDialog(this, { mode: "automation" });
} else { } else {
navigate("/config/automation/edit/new"); navigate("/config/automation/edit/new");
} }

View File

@ -1,11 +1,18 @@
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
export interface NewAutomationDialogParams {
mode: "script" | "automation";
}
export const loadNewAutomationDialog = () => import("./dialog-new-automation"); export const loadNewAutomationDialog = () => import("./dialog-new-automation");
export const showNewAutomationDialog = (element: HTMLElement): void => { export const showNewAutomationDialog = (
element: HTMLElement,
newAutomationDialogParams: NewAutomationDialogParams
): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "ha-dialog-new-automation", dialogTag: "ha-dialog-new-automation",
dialogImport: loadNewAutomationDialog, dialogImport: loadNewAutomationDialog,
dialogParams: {}, dialogParams: newAutomationDialogParams,
}); });
}; };

View File

@ -160,14 +160,16 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
return nothing; return nothing;
} }
const useBlueprint = "use_blueprint" in this._config;
const schema = this._schema( const schema = this._schema(
!!this.scriptId, !!this.scriptId,
"use_blueprint" in this._config, useBlueprint,
this._config.mode this._config.mode
); );
const data = { const data = {
mode: MODES[0], ...(!this._config.mode && !useBlueprint && { mode: MODES[0] }),
icon: undefined, icon: undefined,
max: this._config.mode && isMaxMode(this._config.mode) ? 10 : undefined, max: this._config.mode && isMaxMode(this._config.mode) ? 10 : undefined,
...this._config, ...this._config,
@ -332,7 +334,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
</ha-card> </ha-card>
</div> </div>
${"use_blueprint" in this._config ${useBlueprint
? html` ? html`
<blueprint-script-editor <blueprint-script-editor
.hass=${this.hass} .hass=${this.hass}

View File

@ -12,6 +12,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { differenceInDays } from "date-fns/esm"; import { differenceInDays } from "date-fns/esm";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatShortDateTime } from "../../../common/datetime/format_date_time"; import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { relativeTime } from "../../../common/datetime/relative_time"; import { relativeTime } from "../../../common/datetime/relative_time";
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event"; import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
@ -44,6 +45,7 @@ import { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url"; import { documentationUrl } from "../../../util/documentation-url";
import { showToast } from "../../../util/toast"; import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showNewAutomationDialog } from "../automation/show-dialog-new-automation";
import { EntityRegistryEntry } from "../../../data/entity_registry"; import { EntityRegistryEntry } from "../../../data/entity_registry";
import { findRelated } from "../../../data/search"; import { findRelated } from "../../../data/search";
import { fetchBlueprints } from "../../../data/blueprint"; import { fetchBlueprints } from "../../../data/blueprint";
@ -242,8 +244,8 @@ class HaScriptPicker extends LitElement {
@related-changed=${this._relatedFilterChanged} @related-changed=${this._relatedFilterChanged}
> >
</ha-button-related-filter-menu> </ha-button-related-filter-menu>
<a href="/config/script/edit/new" slot="fab">
<ha-fab <ha-fab
slot="fab"
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
.label=${this.hass.localize( .label=${this.hass.localize(
@ -251,10 +253,10 @@ class HaScriptPicker extends LitElement {
)} )}
extended extended
?rtl=${computeRTL(this.hass)} ?rtl=${computeRTL(this.hass)}
@click=${this._createNew}
> >
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon> <ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab> </ha-fab>
</a>
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
`; `;
} }
@ -312,6 +314,14 @@ class HaScriptPicker extends LitElement {
} }
} }
private _createNew() {
if (isComponentLoaded(this.hass, "blueprint")) {
showNewAutomationDialog(this, { mode: "script" });
} else {
navigate("/config/script/edit/new");
}
}
private _runScript = async (script: any) => { private _runScript = async (script: any) => {
const entry = this.entityRegistry.find( const entry = this.entityRegistry.find(
(e) => e.entity_id === script.entity_id (e) => e.entity_id === script.entity_id

View File

@ -1,8 +1,10 @@
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { mdiHelpCircle } from "@mdi/js";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-form/ha-form"; import "../../../components/ha-form/ha-form";
import "../../../components/ha-icon-button";
import { extractApiErrorMessage } from "../../../data/hassio/common"; import { extractApiErrorMessage } from "../../../data/hassio/common";
import { import {
createSupervisorMount, createSupervisorMount,
@ -17,6 +19,8 @@ import { HomeAssistant } from "../../../types";
import { MountViewDialogParams } from "./show-dialog-view-mount"; import { MountViewDialogParams } from "./show-dialog-view-mount";
import { LocalizeFunc } from "../../../common/translations/localize"; import { LocalizeFunc } from "../../../common/translations/localize";
import type { SchemaUnion } from "../../../components/ha-form/types"; import type { SchemaUnion } from "../../../components/ha-form/types";
import { documentationUrl } from "../../../util/documentation-url";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
const mountSchema = memoizeOne( const mountSchema = memoizeOne(
( (
@ -158,6 +162,33 @@ class ViewMountDialog extends LitElement {
)} )}
@closed=${this.closeDialog} @closed=${this.closeDialog}
> >
<ha-dialog-header slot="heading">
<span slot="title"
>${this._existing
? this.hass.localize(
"ui.panel.config.storage.network_mounts.update_title"
)
: this.hass.localize(
"ui.panel.config.storage.network_mounts.add_title"
)}
</span>
<a
slot="actionItems"
class="header_button"
href=${documentationUrl(
this.hass,
"/common-tasks/os#network-storage"
)}
title=${this.hass.localize(
"ui.panel.config.storage.network_mounts.documentation"
)}
target="_blank"
rel="noreferrer"
dir=${computeRTLDirection(this.hass)}
>
<ha-icon-button .path=${mdiHelpCircle}></ha-icon-button>
</a>
</ha-dialog-header>
${this._error ${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>` ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing} : nothing}
@ -274,6 +305,9 @@ class ViewMountDialog extends LitElement {
haStyle, haStyle,
haStyleDialog, haStyleDialog,
css` css`
ha-icon-button {
color: var(--primary-text-color);
}
.delete-btn { .delete-btn {
--mdc-theme-primary: var(--error-color); --mdc-theme-primary: var(--error-color);
} }

View File

@ -346,7 +346,27 @@ class HaPanelDevService extends LitElement {
} }
private _checkUiSupported() { private _checkUiSupported() {
if (this._serviceData && hasTemplate(this._serviceData)) { const fields = this._fields(
this.hass.services,
this._serviceData?.service
).fields;
if (
this._serviceData &&
(Object.entries(this._serviceData).some(
([key, val]) => key !== "data" && hasTemplate(val)
) ||
(this._serviceData.data &&
Object.entries(this._serviceData.data).some(([key, val]) => {
const field = fields.find((f) => f.key === key);
if (
field?.selector &&
("template" in field.selector || "object" in field.selector)
) {
return false;
}
return hasTemplate(val);
})))
) {
this._yamlMode = true; this._yamlMode = true;
this._uiAvailable = false; this._uiAvailable = false;
} else { } else {

View File

@ -141,6 +141,11 @@ export class EnergyStrategy {
view_layout: { position: "sidebar" }, view_layout: { position: "sidebar" },
collection_key: "energy_dashboard", collection_key: "energy_dashboard",
}); });
view.cards!.push({
type: "energy-self-sufficiency-gauge",
view_layout: { position: "sidebar" },
collection_key: "energy_dashboard",
});
} }
// Only include if we have a grid // Only include if we have a grid

View File

@ -190,6 +190,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
<state-history-charts <state-history-charts
.hass=${this.hass} .hass=${this.hass}
.historyData=${this._stateHistory} .historyData=${this._stateHistory}
.startTime=${this._startDate}
.endTime=${this._endDate} .endTime=${this._endDate}
> >
</state-history-charts> </state-history-charts>

View File

@ -0,0 +1,257 @@
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { mdiInformation } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import "../../../../components/ha-card";
import "../../../../components/ha-gauge";
import "../../../../components/ha-svg-icon";
import {
EnergyData,
energySourcesByType,
getEnergyDataCollection,
} from "../../../../data/energy";
import { calculateStatisticsSumGrowth } from "../../../../data/recorder";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard } from "../../types";
import { severityMap } from "../hui-gauge-card";
import type { EnergySelfSufficiencyGaugeCardConfig } from "../types";
@customElement("hui-energy-self-sufficiency-gauge-card")
class HuiEnergySelfSufficiencyGaugeCard
extends SubscribeMixin(LitElement)
implements LovelaceCard
{
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _config?: EnergySelfSufficiencyGaugeCardConfig;
@state() private _data?: EnergyData;
protected hassSubscribeRequiredHostProps = ["_config"];
public hassSubscribe(): UnsubscribeFunc[] {
return [
getEnergyDataCollection(this.hass!, {
key: this._config?.collection_key,
}).subscribe((data) => {
this._data = data;
}),
];
}
public getCardSize(): number {
return 4;
}
public setConfig(config: EnergySelfSufficiencyGaugeCardConfig): void {
this._config = config;
}
protected render() {
if (!this._config || !this.hass) {
return nothing;
}
if (!this._data) {
return html`${this.hass.localize(
"ui.panel.lovelace.cards.energy.loading"
)}`;
}
const prefs = this._data.prefs;
const types = energySourcesByType(prefs);
// The strategy only includes this card if we have a grid.
const hasConsumption = true;
const hasSolarProduction = types.solar !== undefined;
const hasBattery = types.battery !== undefined;
const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0;
const totalFromGrid =
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
) ?? 0;
let totalSolarProduction: number | null = null;
if (hasSolarProduction) {
totalSolarProduction =
calculateStatisticsSumGrowth(
this._data.stats,
types.solar!.map((source) => source.stat_energy_from)
) || 0;
}
let totalBatteryIn: number | null = null;
let totalBatteryOut: number | null = null;
if (hasBattery) {
totalBatteryIn =
calculateStatisticsSumGrowth(
this._data.stats,
types.battery!.map((source) => source.stat_energy_to)
) || 0;
totalBatteryOut =
calculateStatisticsSumGrowth(
this._data.stats,
types.battery!.map((source) => source.stat_energy_from)
) || 0;
}
let returnedToGrid: number | null = null;
if (hasReturnToGrid) {
returnedToGrid =
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
) || 0;
}
let solarConsumption: number | null = null;
if (hasSolarProduction) {
solarConsumption =
(totalSolarProduction || 0) -
(returnedToGrid || 0) -
(totalBatteryIn || 0);
}
let batteryFromGrid: null | number = null;
let batteryToGrid: null | number = null;
if (solarConsumption !== null && solarConsumption < 0) {
// What we returned to the grid and what went in to the battery is more than produced,
// so we have used grid energy to fill the battery
// or returned battery energy to the grid
if (hasBattery) {
batteryFromGrid = solarConsumption * -1;
if (batteryFromGrid > totalFromGrid) {
batteryToGrid = Math.min(0, batteryFromGrid - totalFromGrid);
batteryFromGrid = totalFromGrid;
}
}
solarConsumption = 0;
}
let batteryConsumption: number | null = null;
if (hasBattery) {
batteryConsumption = (totalBatteryOut || 0) - (batteryToGrid || 0);
}
const gridConsumption = Math.max(0, totalFromGrid - (batteryFromGrid || 0));
const totalHomeConsumption = Math.max(
0,
gridConsumption + (solarConsumption || 0) + (batteryConsumption || 0)
);
let value: number | undefined;
if (
totalFromGrid !== null &&
totalHomeConsumption !== null &&
totalHomeConsumption > 0
) {
value = (1 - totalFromGrid / totalHomeConsumption) * 100;
}
return html`
<ha-card>
${value !== undefined
? html`
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<simple-tooltip animation-delay="0" for="info" position="left">
<span>
${this.hass.localize(
"ui.panel.lovelace.cards.energy.self_sufficiency_gauge.card_indicates_self_sufficiency_quota"
)}
</span>
</simple-tooltip>
<ha-gauge
min="0"
max="100"
.value=${value}
.locale=${this.hass.locale}
label="%"
style=${styleMap({
"--gauge-color": this._computeSeverity(value),
})}
></ha-gauge>
<div class="name">
${this.hass.localize(
"ui.panel.lovelace.cards.energy.self_sufficiency_gauge.self_sufficiency_quota"
)}
</div>
`
: this.hass.localize(
"ui.panel.lovelace.cards.energy.self_sufficiency_gauge.self_sufficiency_could_not_calc"
)}
</ha-card>
`;
}
private _computeSeverity(numberValue: number): string {
if (numberValue > 75) {
return severityMap.green;
}
if (numberValue < 50) {
return severityMap.yellow;
}
return severityMap.normal;
}
static get styles(): CSSResultGroup {
return css`
ha-card {
height: 100%;
overflow: hidden;
padding: 16px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
box-sizing: border-box;
}
ha-gauge {
width: 100%;
max-width: 250px;
direction: ltr;
}
.name {
text-align: center;
line-height: initial;
color: var(--primary-text-color);
width: 100%;
font-size: 15px;
margin-top: 8px;
}
ha-svg-icon {
position: absolute;
right: 4px;
top: 4px;
color: var(--secondary-text-color);
}
simple-tooltip > span {
font-size: 12px;
line-height: 12px;
}
simple-tooltip {
width: 80%;
max-width: 250px;
top: 8px !important;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-energy-self-sufficiency-gauge-card": HuiEnergySelfSufficiencyGaugeCard;
}
}

View File

@ -68,10 +68,11 @@ class HuiEnergySolarGaugeCard
return nothing; return nothing;
} }
const totalSolarProduction = calculateStatisticsSumGrowth( const totalSolarProduction =
calculateStatisticsSumGrowth(
this._data.stats, this._data.stats,
types.solar.map((source) => source.stat_energy_from) types.solar.map((source) => source.stat_energy_from)
); ) || 0;
const productionReturnedToGrid = calculateStatisticsSumGrowth( const productionReturnedToGrid = calculateStatisticsSumGrowth(
this._data.stats, this._data.stats,

View File

@ -205,6 +205,7 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
.historyData=${this._stateHistory} .historyData=${this._stateHistory}
.names=${this._names} .names=${this._names}
up-to-now up-to-now
.hoursToShow=${this._hoursToShow}
.showNames=${this._config.show_names !== undefined .showNames=${this._config.show_names !== undefined
? this._config.show_names ? this._config.show_names
: true} : true}

View File

@ -2,6 +2,7 @@ import {
mdiClockOutline, mdiClockOutline,
mdiFan, mdiFan,
mdiFire, mdiFire,
mdiHeatWave,
mdiPower, mdiPower,
mdiSnowflake, mdiSnowflake,
mdiWaterPercent, mdiWaterPercent,
@ -21,6 +22,7 @@ export const CLIMATE_HVAC_ACTION_ICONS: Record<HvacAction, string> = {
heating: mdiFire, heating: mdiFire,
idle: mdiClockOutline, idle: mdiClockOutline,
off: mdiPower, off: mdiPower,
preheating: mdiHeatWave,
}; };
export const CLIMATE_HVAC_ACTION_MODE: Record<HvacAction, HvacMode> = { export const CLIMATE_HVAC_ACTION_MODE: Record<HvacAction, HvacMode> = {
@ -30,6 +32,7 @@ export const CLIMATE_HVAC_ACTION_MODE: Record<HvacAction, HvacMode> = {
heating: "heat", heating: "heat",
idle: "off", idle: "off",
off: "off", off: "off",
preheating: "heat",
}; };
export const computeClimateBadge: ComputeBadgeFunction = (stateObj) => { export const computeClimateBadge: ComputeBadgeFunction = (stateObj) => {

View File

@ -159,6 +159,13 @@ export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig {
collection_key?: string; collection_key?: string;
} }
export interface EnergySelfSufficiencyGaugeCardConfig
extends LovelaceCardConfig {
type: "energy-self-sufficiency-gauge";
title?: string;
collection_key?: string;
}
export interface EnergyGridGaugeCardConfig extends LovelaceCardConfig { export interface EnergyGridGaugeCardConfig extends LovelaceCardConfig {
type: "energy-grid-result-gauge"; type: "energy-grid-result-gauge";
title?: string; title?: string;

View File

@ -4,6 +4,7 @@ import { forwardHaptic } from "../../../data/haptics";
import { domainToName } from "../../../data/integration"; import { domainToName } from "../../../data/integration";
import { ActionConfig } from "../../../data/lovelace"; import { ActionConfig } from "../../../data/lovelace";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { showVoiceCommandDialog } from "../../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { showToast } from "../../../util/toast"; import { showToast } from "../../../util/toast";
import { toggleEntity } from "./entity/toggle-entity"; import { toggleEntity } from "./entity/toggle-entity";
@ -155,6 +156,13 @@ export const handleAction = async (
forwardHaptic("light"); forwardHaptic("light");
break; break;
} }
case "assist": {
showVoiceCommandDialog(node, hass, {
start_listening: actionConfig.start_listening,
pipeline_id: actionConfig.pipeline_id,
});
break;
}
case "fire-dom-event": { case "fire-dom-event": {
fireEvent(node, "ll-custom", actionConfig); fireEvent(node, "ll-custom", actionConfig);
} }

View File

@ -3,6 +3,8 @@ import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation"; import { stopPropagation } from "../../../common/dom/stop_propagation";
import "../../../components/ha-assist-pipeline-picker";
import { HaFormSchema, SchemaUnion } from "../../../components/ha-form/types";
import "../../../components/ha-help-tooltip"; import "../../../components/ha-help-tooltip";
import "../../../components/ha-navigation-picker"; import "../../../components/ha-navigation-picker";
import "../../../components/ha-service-control"; import "../../../components/ha-service-control";
@ -24,9 +26,31 @@ const DEFAULT_ACTIONS: UiAction[] = [
"navigate", "navigate",
"url", "url",
"call-service", "call-service",
"assist",
"none", "none",
]; ];
const ASSIST_SCHEMA = [
{
type: "grid",
name: "",
schema: [
{
name: "pipeline_id",
selector: {
assist_pipeline: {},
},
},
{
name: "start_listening",
selector: {
boolean: {},
},
},
],
},
] as const satisfies readonly HaFormSchema[];
@customElement("hui-action-editor") @customElement("hui-action-editor")
export class HuiActionEditor extends LitElement { export class HuiActionEditor extends LitElement {
@property() public config?: ActionConfig; @property() public config?: ActionConfig;
@ -101,7 +125,7 @@ export class HuiActionEditor extends LitElement {
? html` ? html`
<ha-help-tooltip .label=${this.tooltipText}></ha-help-tooltip> <ha-help-tooltip .label=${this.tooltipText}></ha-help-tooltip>
` `
: ""} : nothing}
</div> </div>
${this.config?.action === "navigate" ${this.config?.action === "navigate"
? html` ? html`
@ -114,7 +138,7 @@ export class HuiActionEditor extends LitElement {
@value-changed=${this._navigateValueChanged} @value-changed=${this._navigateValueChanged}
></ha-navigation-picker> ></ha-navigation-picker>
` `
: ""} : nothing}
${this.config?.action === "url" ${this.config?.action === "url"
? html` ? html`
<ha-textfield <ha-textfield
@ -126,7 +150,7 @@ export class HuiActionEditor extends LitElement {
@input=${this._valueChanged} @input=${this._valueChanged}
></ha-textfield> ></ha-textfield>
` `
: ""} : nothing}
${this.config?.action === "call-service" ${this.config?.action === "call-service"
? html` ? html`
<ha-service-control <ha-service-control
@ -137,7 +161,19 @@ export class HuiActionEditor extends LitElement {
@value-changed=${this._serviceValueChanged} @value-changed=${this._serviceValueChanged}
></ha-service-control> ></ha-service-control>
` `
: ""} : nothing}
${this.config?.action === "assist"
? html`
<ha-form
.hass=${this.hass}
.schema=${ASSIST_SCHEMA}
.data=${this.config}
.computeLabel=${this._computeFormLabel}
@value-changed=${this._formValueChanged}
>
</ha-form>
`
: nothing}
`; `;
} }
@ -182,7 +218,7 @@ export class HuiActionEditor extends LitElement {
return; return;
} }
const target = ev.target! as EditorTarget; const target = ev.target! as EditorTarget;
const value = ev.target.value; const value = ev.target.value ?? ev.target.checked;
if (this[`_${target.configValue}`] === value) { if (this[`_${target.configValue}`] === value) {
return; return;
} }
@ -193,6 +229,21 @@ export class HuiActionEditor extends LitElement {
} }
} }
private _formValueChanged(ev): void {
ev.stopPropagation();
const value = ev.detail.value;
fireEvent(this, "value-changed", {
value: value,
});
}
private _computeFormLabel(schema: SchemaUnion<typeof ASSIST_SCHEMA>) {
return this.hass?.localize(
`ui.panel.lovelace.editor.action-editor.${schema.name}`
);
}
private _serviceValueChanged(ev: CustomEvent) { private _serviceValueChanged(ev: CustomEvent) {
ev.stopPropagation(); ev.stopPropagation();
const value = { const value = {
@ -240,17 +291,25 @@ export class HuiActionEditor extends LitElement {
width: 100%; width: 100%;
} }
ha-service-control, ha-service-control,
ha-navigation-picker { ha-navigation-picker,
ha-form {
display: block; display: block;
} }
ha-textfield, ha-textfield,
ha-service-control, ha-service-control,
ha-navigation-picker { ha-navigation-picker,
ha-form {
margin-top: 8px; margin-top: 8px;
} }
ha-service-control { ha-service-control {
--service-control-padding: 0; --service-control-padding: 0;
} }
ha-formfield {
display: flex;
height: 56px;
align-items: center;
--mdc-typography-body2-font-size: 1em;
}
`; `;
} }
} }

View File

@ -53,6 +53,8 @@ const LAZY_LOAD_TYPES = {
import("../cards/energy/hui-energy-grid-neutrality-gauge-card"), import("../cards/energy/hui-energy-grid-neutrality-gauge-card"),
"energy-solar-consumed-gauge": () => "energy-solar-consumed-gauge": () =>
import("../cards/energy/hui-energy-solar-consumed-gauge-card"), import("../cards/energy/hui-energy-solar-consumed-gauge-card"),
"energy-self-sufficiency-gauge": () =>
import("../cards/energy/hui-energy-self-sufficiency-gauge-card"),
"energy-solar-graph": () => "energy-solar-graph": () =>
import("../cards/energy/hui-energy-solar-graph-card"), import("../cards/energy/hui-energy-solar-graph-card"),
"energy-sources-table": () => "energy-sources-table": () =>

View File

@ -51,6 +51,12 @@ const actionConfigStructNavigate = object({
confirmation: optional(actionConfigStructConfirmation), confirmation: optional(actionConfigStructConfirmation),
}); });
const actionConfigStructAssist = type({
action: literal("assist"),
pipeline_id: optional(string()),
start_listening: optional(boolean()),
});
const actionConfigStructCustom = type({ const actionConfigStructCustom = type({
action: literal("fire-dom-event"), action: literal("fire-dom-event"),
}); });
@ -63,6 +69,7 @@ export const actionConfigStructType = object({
"call-service", "call-service",
"url", "url",
"navigate", "navigate",
"assist",
]), ]),
confirmation: optional(actionConfigStructConfirmation), confirmation: optional(actionConfigStructConfirmation),
}); });
@ -82,6 +89,9 @@ export const actionConfigStruct = dynamic<any>((value) => {
case "url": { case "url": {
return actionConfigStructUrl; return actionConfigStructUrl;
} }
case "assist": {
return actionConfigStructAssist;
}
} }
} }

View File

@ -18,10 +18,10 @@ import {
ALARM_MODES, ALARM_MODES,
} from "../../../data/alarm_control_panel"; } from "../../../data/alarm_control_panel";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
import { showEnterCodeDialogDialog } from "../../../dialogs/more-info/components/alarm_control_panel/show-enter-code-dialog";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { LovelaceTileFeature, LovelaceTileFeatureEditor } from "../types"; import { LovelaceTileFeature, LovelaceTileFeatureEditor } from "../types";
import { AlarmModesFileFeatureConfig } from "./types"; import { AlarmModesFileFeatureConfig } from "./types";
import { showEnterCodeDialogDialog } from "../../../dialogs/enter-code/show-enter-code-dialog";
export const supportsAlarmModesTileFeature = (stateObj: HassEntity) => { export const supportsAlarmModesTileFeature = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id); const domain = computeDomain(stateObj.entity_id);

View File

@ -1,4 +1,5 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { mdiContentCopy } from "@mdi/js";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
@ -11,9 +12,13 @@ import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { createCloseHeading } from "../../components/ha-dialog"; import { createCloseHeading } from "../../components/ha-dialog";
import "../../components/ha-textfield"; import "../../components/ha-textfield";
import "../../components/ha-icon-button";
import { haStyleDialog } from "../../resources/styles"; import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import { LongLivedAccessTokenDialogParams } from "./show-long-lived-access-token-dialog"; import { LongLivedAccessTokenDialogParams } from "./show-long-lived-access-token-dialog";
import type { HaTextField } from "../../components/ha-textfield";
import { copyToClipboard } from "../../common/util/copy-clipboard";
import { showToast } from "../../util/toast";
const QR_LOGO_URL = "/static/icons/favicon-192x192.png"; const QR_LOGO_URL = "/static/icons/favicon-192x192.png";
@ -55,8 +60,15 @@ export class HaLongLivedAccessTokenDialog extends LitElement {
"ui.panel.profile.long_lived_access_tokens.prompt_copy_token" "ui.panel.profile.long_lived_access_tokens.prompt_copy_token"
)} )}
type="text" type="text"
iconTrailing
readOnly readOnly
></ha-textfield> >
<ha-icon-button
@click=${this._copyToken}
slot="trailingIcon"
.path=${mdiContentCopy}
></ha-icon-button>
</ha-textfield>
<div id="qr"> <div id="qr">
${this._qrCode ${this._qrCode
? this._qrCode ? this._qrCode
@ -71,6 +83,14 @@ export class HaLongLivedAccessTokenDialog extends LitElement {
`; `;
} }
private async _copyToken(ev): Promise<void> {
const textField = ev.target.parentElement as HaTextField;
await copyToClipboard(textField.value);
showToast(this, {
message: this.hass.localize("ui.common.copied_clipboard"),
});
}
private async _generateQR() { private async _generateQR() {
const qrcode = await import("qrcode"); const qrcode = await import("qrcode");
const canvas = await qrcode.toCanvas(this._params!.token, { const canvas = await qrcode.toCanvas(this._params!.token, {
@ -111,6 +131,17 @@ export class HaLongLivedAccessTokenDialog extends LitElement {
} }
ha-textfield { ha-textfield {
display: block; display: block;
--textfield-icon-trailing-padding: 0;
}
ha-textfield > ha-icon-button {
position: relative;
right: -8px;
--mdc-icon-button-size: 36px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
inset-inline-start: initial;
inset-inline-end: -8px;
direction: var(--direction);
} }
`, `,
]; ];

View File

@ -5,7 +5,7 @@ export const loadPolyfillIfNeeded = async () => {
} catch (e) { } catch (e) {
window.ResizeObserver = ( window.ResizeObserver = (
await import( await import(
"@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver.js" "@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver"
) )
).default; ).default;
} }

83
src/translations/en.json Executable file → Normal file
View File

@ -396,7 +396,8 @@
}, },
"theme-picker": { "theme-picker": {
"theme": "Theme", "theme": "Theme",
"no_theme": "No theme" "no_theme": "No theme",
"default": "[%key:ui::panel::profile::themes::default%]"
}, },
"language-picker": { "language-picker": {
"language": "Language", "language": "Language",
@ -929,8 +930,8 @@
"light": { "light": {
"edit_mode": "Edit favorite colors", "edit_mode": "Edit favorite colors",
"toggle": "Toggle", "toggle": "Toggle",
"change_color": "Change color", "color": "Color",
"change_color_temp": "Change color temperature", "color_temp": "Temperature",
"set_white": "Set white", "set_white": "Set white",
"select_effect": "Select effect", "select_effect": "Select effect",
"brightness": "Brightness", "brightness": "Brightness",
@ -978,6 +979,11 @@
"disarm_action": "Disarm", "disarm_action": "Disarm",
"arm_title": "Arm", "arm_title": "Arm",
"arm_action": "Arm" "arm_action": "Arm"
},
"lock": {
"open": "Open",
"lock": "Lock",
"unlock": "Unlock"
} }
}, },
"entity_registry": { "entity_registry": {
@ -2286,7 +2292,10 @@
"event_data": "Event data", "event_data": "Event data",
"context_users": "Limit to events triggered by", "context_users": "Limit to events triggered by",
"context_user_picked": "User firing event", "context_user_picked": "User firing event",
"context_user_pick": "Select user" "context_user_pick": "Select user",
"description": {
"full": "When {eventTypes} event is fired"
}
}, },
"geo_location": { "geo_location": {
"label": "Geolocation", "label": "Geolocation",
@ -2308,12 +2317,19 @@
"label": "Home Assistant", "label": "Home Assistant",
"event": "Event:", "event": "Event:",
"start": "Start", "start": "Start",
"shutdown": "Shutdown" "shutdown": "Shutdown",
"description": {
"started": "When Home Assistant is started",
"shutdown": "When Home Assistant is shutdown"
}
}, },
"mqtt": { "mqtt": {
"label": "MQTT", "label": "MQTT",
"topic": "Topic", "topic": "Topic",
"payload": "Payload (optional)" "payload": "Payload (optional)",
"description": {
"full": "When an MQTT message has been received"
}
}, },
"numeric_state": { "numeric_state": {
"label": "Numeric state", "label": "Numeric state",
@ -2341,22 +2357,35 @@
"event": "[%key:ui::panel::config::automation::editor::triggers::type::homeassistant::event%]", "event": "[%key:ui::panel::config::automation::editor::triggers::type::homeassistant::event%]",
"sunrise": "Sunrise", "sunrise": "Sunrise",
"sunset": "Sunset", "sunset": "Sunset",
"offset": "Offset (optional)" "offset": "Offset (optional)",
"description": {
"sets": "When the sun sets{hasDuration, select, \n true { offset by {duration}} \n other {}\n }",
"rises": "When the sun rises{hasDuration, select, \n true { offset by {duration}} \n other {}\n }"
}
}, },
"tag": { "tag": {
"label": "Tag" "label": "Tag",
"description": {
"full": "When a tag is scanned"
}
}, },
"template": { "template": {
"label": "Template", "label": "Template",
"value_template": "Value template", "value_template": "Value template",
"for": "For" "for": "For",
"description": {
"full": "When a template triggers{hasDuration, select, \n true { for {duration}} \n other {}\n }"
}
}, },
"time": { "time": {
"type_value": "Fixed time", "type_value": "Fixed time",
"type_input": "Value of a date/time helper or timestamp-class sensor", "type_input": "Value of a date/time helper or timestamp-class sensor",
"label": "Time", "label": "Time",
"at": "At time", "at": "At time",
"mode": "Mode" "mode": "Mode",
"description": {
"full": "When the time is equal to {time}"
}
}, },
"time_pattern": { "time_pattern": {
"label": "Time Pattern", "label": "Time Pattern",
@ -2370,7 +2399,10 @@
"local_only": "Only accessible from the local network", "local_only": "Only accessible from the local network",
"webhook_id": "Webhook ID", "webhook_id": "Webhook ID",
"webhook_id_helper": "Treat this ID like a password: keep it secret, and make it hard to guess.", "webhook_id_helper": "Treat this ID like a password: keep it secret, and make it hard to guess.",
"webhook_settings": "Webhook Settings" "webhook_settings": "Webhook Settings",
"description": {
"full": "When a Webhook payload has been received"
}
}, },
"zone": { "zone": {
"label": "Zone", "label": "Zone",
@ -2507,6 +2539,7 @@
"delete_confirm_text": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm_text%]", "delete_confirm_text": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm_text%]",
"unsupported_action": "No visual editor support for action: {action}", "unsupported_action": "No visual editor support for action: {action}",
"type_select": "Action type", "type_select": "Action type",
"continue_on_error": "Continue on error",
"type": { "type": {
"service": { "service": {
"label": "Call service" "label": "Call service"
@ -2703,6 +2736,21 @@
"delete": "[%key:ui::common::delete%]", "delete": "[%key:ui::common::delete%]",
"duplicate": "[%key:ui::common::duplicate%]" "duplicate": "[%key:ui::common::duplicate%]"
}, },
"dialog_new": {
"header": "Create script",
"create_empty": "Create new script",
"create_empty_description": "Start with an empty script from scratch",
"create_blueprint": "[%key:ui::panel::config::automation::dialog_new::create_blueprint%]",
"create_blueprint_description": "[%key:ui::panel::config::automation::dialog_new::create_blueprint_description%]",
"blueprint_source": {
"author": "[%key:ui::panel::config::automation::dialog_new::blueprint_source::author%]",
"local": "[%key:ui::panel::config::automation::dialog_new::blueprint_source::local%]",
"community": "[%key:ui::panel::config::automation::dialog_new::blueprint_source::community%]",
"homeassistant": "[%key:ui::panel::config::automation::dialog_new::blueprint_source::homeassistant%]"
},
"discover_blueprint_tip": "[%key:ui::panel::config::automation::dialog_new::discover_blueprint_tip%]"
},
"editor": { "editor": {
"alias": "Name", "alias": "Name",
"icon": "Icon", "icon": "Icon",
@ -3499,11 +3547,11 @@
}, },
"thread": { "thread": {
"other_networks": "Other networks", "other_networks": "Other networks",
"my_network": "My network", "my_network": "Preferred network",
"no_preferred_network": "You don't have a preferred network yet.", "no_preferred_network": "You don't have a preferred network yet.",
"add_open_thread_border_router": "Add an OpenThread border router", "add_open_thread_border_router": "Add an OpenThread border router",
"reset_border_router": "Reset border router", "reset_border_router": "Reset border router",
"add_to_my_network": "Add to my network", "add_to_my_network": "Add to preferred network",
"no_routers_otbr_network": "No border routers where found, maybe the border router is not configured correctly. You can try to reset it to the factory settings.", "no_routers_otbr_network": "No border routers where found, maybe the border router is not configured correctly. You can try to reset it to the factory settings.",
"add_dataset_from_tlv": "Add dataset from TLV", "add_dataset_from_tlv": "Add dataset from TLV",
"add_dataset": "Add Thread dataset", "add_dataset": "Add Thread dataset",
@ -4066,6 +4114,7 @@
"add_title": "Add network storage", "add_title": "Add network storage",
"update_title": "Update network storage", "update_title": "Update network storage",
"no_mounts": "No connected network storage", "no_mounts": "No connected network storage",
"documentation": "Documentation",
"not_supported": { "not_supported": {
"title": "The operating system does not support network storage", "title": "The operating system does not support network storage",
"supervised": "Network storage is not supported on this host", "supervised": "Network storage is not supported on this host",
@ -4229,6 +4278,11 @@
"not_produced_solar_energy": "You have not produced any solar energy", "not_produced_solar_energy": "You have not produced any solar energy",
"self_consumed_solar_could_not_calc": "Self-consumed solar energy couldn't be calculated" "self_consumed_solar_could_not_calc": "Self-consumed solar energy couldn't be calculated"
}, },
"self_sufficiency_gauge": {
"card_indicates_self_sufficiency_quota": "This card indicates how self-sufficient your home is.",
"self_sufficiency_quota": "Self-sufficiency quota",
"self_sufficiency_could_not_calc": "Self-sufficiency quota couldn't be calculated"
},
"grid_neutrality_gauge": { "grid_neutrality_gauge": {
"energy_dependency": "This card indicates your net energy usage.", "energy_dependency": "This card indicates your net energy usage.",
"color_explain": "If the needle is in the purple, you returned more energy to the grid than you consumed from it. If it's in the blue, you consumed more energy from the grid than you returned.", "color_explain": "If the needle is in the purple, you returned more energy to the grid than you consumed from it. If it's in the blue, you consumed more energy from the grid than you returned.",
@ -4416,12 +4470,15 @@
"action-editor": { "action-editor": {
"navigation_path": "Navigation Path", "navigation_path": "Navigation Path",
"url_path": "URL Path", "url_path": "URL Path",
"start_listening": "Start listening",
"pipeline_id": "Assistant",
"actions": { "actions": {
"default_action": "Default Action", "default_action": "Default Action",
"call-service": "Call Service", "call-service": "Call Service",
"more-info": "More Info", "more-info": "More Info",
"toggle": "Toggle", "toggle": "Toggle",
"navigate": "Navigate", "navigate": "Navigate",
"assist": "Assist",
"url": "URL", "url": "URL",
"none": "No Action" "none": "No Action"
} }

434
yarn.lock
View File

@ -1582,10 +1582,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@eslint/js@npm:8.42.0": "@eslint/js@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "@eslint/js@npm:8.42.0" resolution: "@eslint/js@npm:8.43.0"
checksum: 750558843ac458f7da666122083ee05306fc087ecc1e5b21e7e14e23885775af6c55bcc92283dff1862b7b0d8863ec676c0f18c7faf1219c722fe91a8ece56b6 checksum: 580487a09c82ac169744d36e4af77bc4f582c9a37749d1e9481eb93626c8f3991b2390c6e4e69e5642e3b6e870912b839229a0e23594fae348156ea5a8ed7e2e
languageName: node languageName: node
linkType: hard linkType: hard
@ -2062,13 +2062,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@lit-labs/context@npm:0.3.2": "@lit-labs/context@npm:0.3.3":
version: 0.3.2 version: 0.3.3
resolution: "@lit-labs/context@npm:0.3.2" resolution: "@lit-labs/context@npm:0.3.3"
dependencies: dependencies:
"@lit/reactive-element": ^1.5.0 "@lit/reactive-element": ^1.5.0
lit: ^2.7.0 lit: ^2.7.0
checksum: 55920366798a3337a455c627c0b6911c7b78dee94a783ad77edb9a9e237a2e48201d6cf869f3d0b805316e5c8e8fb817f52f663bc556dd40ca6e8b3168662daf checksum: 3607a7f965f22072feeef8db791e37be45921d9168ea3f432e160cb1fb337de19b2b41a2cd8db5d4fd2675d704d567a24695b796d0b14590616e9232f27579d3
languageName: node languageName: node
linkType: hard linkType: hard
@ -2088,13 +2088,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@lit-labs/virtualizer@npm:2.0.2": "@lit-labs/virtualizer@npm:2.0.3":
version: 2.0.2 version: 2.0.3
resolution: "@lit-labs/virtualizer@npm:2.0.2" resolution: "@lit-labs/virtualizer@npm:2.0.3"
dependencies: dependencies:
lit: ^2.7.0 lit: ^2.7.0
tslib: ^2.0.3 tslib: ^2.0.3
checksum: 419aedf72f2b08f8fda43d9729810d5c7f34f470484bd9dff2df49d42cc56e57fcdfd98f69dd84e582d07fd2f372b90077cf42e12c4464b2c04c83755eebb495 checksum: 594b89aca53210a6c0127c331fd05b795074df41aba086b63cb13ad5990e6962b86ca8403fe3a673e3bf46735e2def75d5412afe582702346fbd92a3331d34e1
languageName: node languageName: node
linkType: hard linkType: hard
@ -3166,16 +3166,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@mrmlnc/readdir-enhanced@npm:^2.2.1":
version: 2.2.1
resolution: "@mrmlnc/readdir-enhanced@npm:2.2.1"
dependencies:
call-me-maybe: ^1.0.1
glob-to-regexp: ^0.3.0
checksum: d3b82b29368821154ce8e10bef5ccdbfd070d3e9601643c99ea4607e56f3daeaa4e755dd6d2355da20762c695c1b0570543d9f84b48f70c211ec09c4aaada2e1
languageName: node
linkType: hard
"@nodelib/fs.scandir@npm:2.1.5": "@nodelib/fs.scandir@npm:2.1.5":
version: 2.1.5 version: 2.1.5
resolution: "@nodelib/fs.scandir@npm:2.1.5" resolution: "@nodelib/fs.scandir@npm:2.1.5"
@ -3193,13 +3183,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@nodelib/fs.stat@npm:^1.1.2":
version: 1.1.3
resolution: "@nodelib/fs.stat@npm:1.1.3"
checksum: 318deab369b518a34778cdaa0054dd28a4381c0c78e40bbd20252f67d084b1d7bf9295fea4423de2c19ac8e1a34f120add9125f481b2a710f7068bcac7e3e305
languageName: node
linkType: hard
"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": "@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8":
version: 1.2.8 version: 1.2.8
resolution: "@nodelib/fs.walk@npm:1.2.8" resolution: "@nodelib/fs.walk@npm:1.2.8"
@ -3219,15 +3202,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@octokit/auth-oauth-device@npm:5.0.0": "@octokit/auth-oauth-device@npm:5.0.2":
version: 5.0.0 version: 5.0.2
resolution: "@octokit/auth-oauth-device@npm:5.0.0" resolution: "@octokit/auth-oauth-device@npm:5.0.2"
dependencies: dependencies:
"@octokit/oauth-methods": ^2.0.0 "@octokit/oauth-methods": ^3.0.1
"@octokit/request": ^6.0.0 "@octokit/request": ^7.0.0
"@octokit/types": ^9.0.0 "@octokit/types": ^10.0.0
universal-user-agent: ^6.0.0 universal-user-agent: ^6.0.0
checksum: 7089bf13bc01629e501af88dc01c18b29b70dba87e26bd4eb2b7faf6baefe3ba9e4ed92f77d5b7a8a56afbbdb1a01b4b264c140c10c4ca6fbd28a7976fcfdc6e checksum: b625a2d7604351e52df46d3fdad04d1eb2ec68f80bce065047691ea83044967ef1e7dd0a70e9f8aab818d8c5ecf7f2550d2aa029ffdba85e0ff8c0ce2e25736a
languageName: node languageName: node
linkType: hard linkType: hard
@ -3264,6 +3247,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@octokit/endpoint@npm:^8.0.0":
version: 8.0.1
resolution: "@octokit/endpoint@npm:8.0.1"
dependencies:
"@octokit/types": ^10.0.0
is-plain-object: ^5.0.0
universal-user-agent: ^6.0.0
checksum: 0cff7c972d8304cb59c4cc28016c15bca05e6d7e9e2d9b00af88ce05bf9abdfdb17025f38080162a71ea15b21c740bcb5079361396f18a24bbe55134c504a581
languageName: node
linkType: hard
"@octokit/graphql@npm:^5.0.0": "@octokit/graphql@npm:^5.0.0":
version: 5.0.6 version: 5.0.6
resolution: "@octokit/graphql@npm:5.0.6" resolution: "@octokit/graphql@npm:5.0.6"
@ -3275,23 +3269,23 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@octokit/oauth-authorization-url@npm:^5.0.0": "@octokit/oauth-authorization-url@npm:^6.0.2":
version: 5.0.0 version: 6.0.2
resolution: "@octokit/oauth-authorization-url@npm:5.0.0" resolution: "@octokit/oauth-authorization-url@npm:6.0.2"
checksum: bc457c4af9559e9e8f752e643fc9d116247f4e4246e69959d99b9e39196c93d7af53c1c8e3bd946bd0e4fc29f7ba27efe9bced8525ffa41fe45ef56a8281014b checksum: 0f11169a3eeb782cc08312c923de1a702b25ae033b972ba40380b6d72cb3f684543c8b6a5cf6f05936fdc6b8892070d4f7581138d8efc1b4c4a55ae6d7762327
languageName: node languageName: node
linkType: hard linkType: hard
"@octokit/oauth-methods@npm:^2.0.0": "@octokit/oauth-methods@npm:^3.0.1":
version: 2.0.6 version: 3.0.1
resolution: "@octokit/oauth-methods@npm:2.0.6" resolution: "@octokit/oauth-methods@npm:3.0.1"
dependencies: dependencies:
"@octokit/oauth-authorization-url": ^5.0.0 "@octokit/oauth-authorization-url": ^6.0.2
"@octokit/request": ^6.2.3 "@octokit/request": ^7.0.0
"@octokit/request-error": ^3.0.3 "@octokit/request-error": ^4.0.1
"@octokit/types": ^9.0.0 "@octokit/types": ^10.0.0
btoa-lite: ^1.0.0 btoa-lite: ^1.0.0
checksum: 151b933d79d6fbf36fdfae8cdc868a3d43316352eaccf46cb8c420cfd238658275e41996d2d377177553bc0c637c3aefe8ca99c1ab7fd62054654b6119b7b1cc checksum: ad327084f97d2f3be270d8957545dbd06c35df3e99d8e58702217beb7ac3574c361b49dfe28ba5d96b7f1911ac9c8e26ae07d6180a0598eef8b7fab4b0fe4ad5
languageName: node languageName: node
linkType: hard linkType: hard
@ -3334,20 +3328,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@octokit/plugin-retry@npm:5.0.3": "@octokit/plugin-retry@npm:5.0.4":
version: 5.0.3 version: 5.0.4
resolution: "@octokit/plugin-retry@npm:5.0.3" resolution: "@octokit/plugin-retry@npm:5.0.4"
dependencies: dependencies:
"@octokit/request-error": ^4.0.1 "@octokit/request-error": ^4.0.1
"@octokit/types": ^9.0.0 "@octokit/types": ^10.0.0
bottleneck: ^2.15.3 bottleneck: ^2.15.3
peerDependencies: peerDependencies:
"@octokit/core": ">=3" "@octokit/core": ">=3"
checksum: f98b90333e26a214f057557ee5b13a926e0472a47d103345c504f99e6c3d8564a8fa54bf2871eda8ef47a8f9c1dba84fb68e749ab7a1a749c0a86a3ae74bdfa7 checksum: 0c5645613f7ff758ac126da11ba20b4d49e4067676e30808f5ee3ee471adbd2ccfdea2200adfa5a4663b207964b3d60987f4c5e8682fb275bf134b33f2ef5178
languageName: node languageName: node
linkType: hard linkType: hard
"@octokit/request-error@npm:^3.0.0, @octokit/request-error@npm:^3.0.3": "@octokit/request-error@npm:^3.0.0":
version: 3.0.3 version: 3.0.3
resolution: "@octokit/request-error@npm:3.0.3" resolution: "@octokit/request-error@npm:3.0.3"
dependencies: dependencies:
@ -3369,7 +3363,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@octokit/request@npm:^6.0.0, @octokit/request@npm:^6.2.3": "@octokit/request@npm:^6.0.0":
version: 6.2.8 version: 6.2.8
resolution: "@octokit/request@npm:6.2.8" resolution: "@octokit/request@npm:6.2.8"
dependencies: dependencies:
@ -3383,15 +3377,28 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@octokit/rest@npm:19.0.11": "@octokit/request@npm:^7.0.0":
version: 19.0.11 version: 7.0.0
resolution: "@octokit/rest@npm:19.0.11" resolution: "@octokit/request@npm:7.0.0"
dependencies:
"@octokit/endpoint": ^8.0.0
"@octokit/request-error": ^4.0.1
"@octokit/types": ^10.0.0
is-plain-object: ^5.0.0
universal-user-agent: ^6.0.0
checksum: d3b8ac25c3702bb69c5b345f7a9f16b158209db7e244cc2d60dbcbfbaf1edec8252d78885de3607ee85eb86db7c1d2e07fa2515ba6e25cf2880440c0df5e918a
languageName: node
linkType: hard
"@octokit/rest@npm:19.0.13":
version: 19.0.13
resolution: "@octokit/rest@npm:19.0.13"
dependencies: dependencies:
"@octokit/core": ^4.2.1 "@octokit/core": ^4.2.1
"@octokit/plugin-paginate-rest": ^6.1.2 "@octokit/plugin-paginate-rest": ^6.1.2
"@octokit/plugin-request-log": ^1.0.4 "@octokit/plugin-request-log": ^1.0.4
"@octokit/plugin-rest-endpoint-methods": ^7.1.2 "@octokit/plugin-rest-endpoint-methods": ^7.1.2
checksum: 147518ad51d214ead88adc717b5fdc4f33317949d58c124f4069bdf07d2e6b49fa66861036b9e233aed71fcb88ff367a6da0357653484e466175ab4fb7183b3b checksum: ca1553e3fe46efabffef60e68e4a228d4cc0f0d545daf7f019560f666d3e934c6f3a6402a42bbd786af4f3c0a6e69380776312f01b7d52998fe1bbdd1b068f69
languageName: node languageName: node
linkType: hard linkType: hard
@ -4785,126 +4792,126 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/a11y-base@npm:~24.1.0": "@vaadin/a11y-base@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/a11y-base@npm:24.1.0" resolution: "@vaadin/a11y-base@npm:24.1.1"
dependencies: dependencies:
"@open-wc/dedupe-mixin": ^1.3.0 "@open-wc/dedupe-mixin": ^1.3.0
"@polymer/polymer": ^3.0.0 "@polymer/polymer": ^3.0.0
"@vaadin/component-base": ~24.1.0 "@vaadin/component-base": ~24.1.1
lit: ^2.0.0 lit: ^2.0.0
checksum: f019f2c04473c60c3714ec3da65a129833e1fab4e2eefb4f88d4caa81eb45da756dce2cec8b222bd259d0b87bc67439a4aa88f636b90e6f704c037197bdc1492 checksum: 1b83e3e44ebc8c395a5ba9a6bc92d42aeb6afce3bcd6a1c492bbc7eb166a228474bf7dc56fb6509d47618fdd87107c60e4ebc60d2aab3c72dcc1392465fd7ac1
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/combo-box@npm:24.1.0": "@vaadin/combo-box@npm:24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/combo-box@npm:24.1.0" resolution: "@vaadin/combo-box@npm:24.1.1"
dependencies: dependencies:
"@open-wc/dedupe-mixin": ^1.3.0 "@open-wc/dedupe-mixin": ^1.3.0
"@polymer/polymer": ^3.0.0 "@polymer/polymer": ^3.0.0
"@vaadin/a11y-base": ~24.1.0 "@vaadin/a11y-base": ~24.1.1
"@vaadin/component-base": ~24.1.0 "@vaadin/component-base": ~24.1.1
"@vaadin/field-base": ~24.1.0 "@vaadin/field-base": ~24.1.1
"@vaadin/input-container": ~24.1.0 "@vaadin/input-container": ~24.1.1
"@vaadin/item": ~24.1.0 "@vaadin/item": ~24.1.1
"@vaadin/lit-renderer": ~24.1.0 "@vaadin/lit-renderer": ~24.1.1
"@vaadin/overlay": ~24.1.0 "@vaadin/overlay": ~24.1.1
"@vaadin/vaadin-lumo-styles": ~24.1.0 "@vaadin/vaadin-lumo-styles": ~24.1.1
"@vaadin/vaadin-material-styles": ~24.1.0 "@vaadin/vaadin-material-styles": ~24.1.1
"@vaadin/vaadin-themable-mixin": ~24.1.0 "@vaadin/vaadin-themable-mixin": ~24.1.1
checksum: cbdbfba535a16faa84a897d61565e91d1b2ec0dad87c1644e287c180fed13fcf3e2b0c436fb85ad7f394a4bb7aceb596b9070e3e171afe8167f2158908e71ea5 checksum: 0011a1271ebe41c6eaf5d5cf5f15c7cfdfab46534c70b4bdbcc0b1afdd6e83e0c7983d3927db6df4c7669269d7909a3d886586696b9d75d291a6a7876db22ce6
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/component-base@npm:~24.1.0": "@vaadin/component-base@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/component-base@npm:24.1.0" resolution: "@vaadin/component-base@npm:24.1.1"
dependencies: dependencies:
"@open-wc/dedupe-mixin": ^1.3.0 "@open-wc/dedupe-mixin": ^1.3.0
"@polymer/polymer": ^3.0.0 "@polymer/polymer": ^3.0.0
"@vaadin/vaadin-development-mode-detector": ^2.0.0 "@vaadin/vaadin-development-mode-detector": ^2.0.0
"@vaadin/vaadin-usage-statistics": ^2.1.0 "@vaadin/vaadin-usage-statistics": ^2.1.0
lit: ^2.0.0 lit: ^2.0.0
checksum: 1df8786d8ef1b3a2a104101c8877464d6fca3ea70fa851b2f99bcf32bb83780ced05f524b6225e95f901edb8ec9379fe625ac18154367a3a684846004277badc checksum: 34c698266897cf7c3c5f8b8798468f5035ae764b1743e6a93f5ea1921b3d29e642db51e4d6d71c6594ba03dee14fa0704b34ccb70b7f50ecbfd07677bde231ac
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/field-base@npm:~24.1.0": "@vaadin/field-base@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/field-base@npm:24.1.0" resolution: "@vaadin/field-base@npm:24.1.1"
dependencies: dependencies:
"@open-wc/dedupe-mixin": ^1.3.0 "@open-wc/dedupe-mixin": ^1.3.0
"@polymer/polymer": ^3.0.0 "@polymer/polymer": ^3.0.0
"@vaadin/a11y-base": ~24.1.0 "@vaadin/a11y-base": ~24.1.1
"@vaadin/component-base": ~24.1.0 "@vaadin/component-base": ~24.1.1
lit: ^2.0.0 lit: ^2.0.0
checksum: 5e37ede91e05dd8eb9fa43749b89670904abfc30e522eebb4ec2225318cf72774dd4e94e49deb1b55daea719818803d90968c4d0f4b87132d24289345e728abd checksum: b16d5579d4a5f43a62df431b7e7e3107bf5a8062ad681b6ce0c1c345dc56ce4f0ae4f0909e2e9ff0fb05d9f2f4b54e12f17b75a680c69b9f24f8f82b63c0b234
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/icon@npm:~24.1.0": "@vaadin/icon@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/icon@npm:24.1.0" resolution: "@vaadin/icon@npm:24.1.1"
dependencies: dependencies:
"@polymer/polymer": ^3.0.0 "@polymer/polymer": ^3.0.0
"@vaadin/component-base": ~24.1.0 "@vaadin/component-base": ~24.1.1
"@vaadin/vaadin-lumo-styles": ~24.1.0 "@vaadin/vaadin-lumo-styles": ~24.1.1
"@vaadin/vaadin-themable-mixin": ~24.1.0 "@vaadin/vaadin-themable-mixin": ~24.1.1
lit: ^2.0.0 lit: ^2.0.0
checksum: 60ee5e3056d175b032c1ad41b3b208b2289c2ef043e4f073e86f691ed135ea98a8780fe0624c9347858f5f0f44a0cc58c345d51eb3795fac47cdafd6cc1a8c59 checksum: be3f8986e04f163791c0fdbc51c5d7c8074b12548f151b58a3f357ab639cc5c0c53b3375ded7936cd8c618df19dcfef4c616735b24e81da6ae1fca753d4c0774
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/input-container@npm:~24.1.0": "@vaadin/input-container@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/input-container@npm:24.1.0" resolution: "@vaadin/input-container@npm:24.1.1"
dependencies: dependencies:
"@polymer/polymer": ^3.0.0 "@polymer/polymer": ^3.0.0
"@vaadin/component-base": ~24.1.0 "@vaadin/component-base": ~24.1.1
"@vaadin/vaadin-lumo-styles": ~24.1.0 "@vaadin/vaadin-lumo-styles": ~24.1.1
"@vaadin/vaadin-material-styles": ~24.1.0 "@vaadin/vaadin-material-styles": ~24.1.1
"@vaadin/vaadin-themable-mixin": ~24.1.0 "@vaadin/vaadin-themable-mixin": ~24.1.1
checksum: d222ec0df6c3e169341d8e1c4bc5b15da6f4324bb962537929b864df389f24474195c0088b1d06d88aaecc18f63938c5f5fa614d8fcda233c8ea5222fc31f183 checksum: b8934fae0f5578b78f4ee05c506b59e66c247e3fdf6d42b1ba7d198d77af170907b1f3cd98ee5ce7bd540d0f5c1f4c08d8febdfa35f7231ed56d289b0eb7432b
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/item@npm:~24.1.0": "@vaadin/item@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/item@npm:24.1.0" resolution: "@vaadin/item@npm:24.1.1"
dependencies: dependencies:
"@open-wc/dedupe-mixin": ^1.3.0 "@open-wc/dedupe-mixin": ^1.3.0
"@polymer/polymer": ^3.0.0 "@polymer/polymer": ^3.0.0
"@vaadin/a11y-base": ~24.1.0 "@vaadin/a11y-base": ~24.1.1
"@vaadin/component-base": ~24.1.0 "@vaadin/component-base": ~24.1.1
"@vaadin/vaadin-lumo-styles": ~24.1.0 "@vaadin/vaadin-lumo-styles": ~24.1.1
"@vaadin/vaadin-material-styles": ~24.1.0 "@vaadin/vaadin-material-styles": ~24.1.1
"@vaadin/vaadin-themable-mixin": ~24.1.0 "@vaadin/vaadin-themable-mixin": ~24.1.1
checksum: 643f47f7e4ae74cffa3e891789c0689063d73552d81aa84dad66d3f415e624e734f13ae0b0123710984fa8390ea2df1f468d9415d7d914150695821045e09ea0 checksum: a87529f5c0c385920d36173b670afbfaaa83d0c171cea048daf7986fbdfb7d82cfef651bc806043ca007a61c1f92b47bba489240b0917d15c75ad49fabab2153
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/lit-renderer@npm:~24.1.0": "@vaadin/lit-renderer@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/lit-renderer@npm:24.1.0" resolution: "@vaadin/lit-renderer@npm:24.1.1"
dependencies: dependencies:
lit: ^2.0.0 lit: ^2.0.0
checksum: 9f0940e0245f608dc613cb33ffdb4f88c275597f7b25fac04892d29ddfc752801fde118fca47bd8445a9d51bca203c339216186ca9b4941b0b6f07a52cc4fc9a checksum: 17a22abce1654c9b6c86e8a113778d61d5780e45164d3362741b00f47e061cfd88521127802f16ce2ad3ba92ed1535829c8b154183cc6f4fbececdbbb70f4233
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/overlay@npm:~24.1.0": "@vaadin/overlay@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/overlay@npm:24.1.0" resolution: "@vaadin/overlay@npm:24.1.1"
dependencies: dependencies:
"@open-wc/dedupe-mixin": ^1.3.0 "@open-wc/dedupe-mixin": ^1.3.0
"@polymer/polymer": ^3.0.0 "@polymer/polymer": ^3.0.0
"@vaadin/a11y-base": ~24.1.0 "@vaadin/a11y-base": ~24.1.1
"@vaadin/component-base": ~24.1.0 "@vaadin/component-base": ~24.1.1
"@vaadin/vaadin-lumo-styles": ~24.1.0 "@vaadin/vaadin-lumo-styles": ~24.1.1
"@vaadin/vaadin-material-styles": ~24.1.0 "@vaadin/vaadin-material-styles": ~24.1.1
"@vaadin/vaadin-themable-mixin": ~24.1.0 "@vaadin/vaadin-themable-mixin": ~24.1.1
checksum: 8362db034347e8186c4397de55fd51b69e645f621614298b68fa383e4957a6ea8290b0770b3d686217ce937a7a18d33ea0ea6844d3da4d3aa3a61d7498210b80 checksum: d0def2106e4fff7d7c49931e9b917c68994f371a0246e076442e33d97ac7a25341d9794aee9e41c7c05c94111dacac74cb5648f1814fb45f11cf37ffe6850fa1
languageName: node languageName: node
linkType: hard linkType: hard
@ -4915,34 +4922,34 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/vaadin-lumo-styles@npm:~24.1.0": "@vaadin/vaadin-lumo-styles@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/vaadin-lumo-styles@npm:24.1.0" resolution: "@vaadin/vaadin-lumo-styles@npm:24.1.1"
dependencies: dependencies:
"@polymer/polymer": ^3.0.0 "@polymer/polymer": ^3.0.0
"@vaadin/icon": ~24.1.0 "@vaadin/icon": ~24.1.1
"@vaadin/vaadin-themable-mixin": ~24.1.0 "@vaadin/vaadin-themable-mixin": ~24.1.1
checksum: 68c55fadb2468048b3fe2ae14c8e5fdb90cb35a171c1a2dc7e33b369d8f79565b6e1c5a93a26dbc5e24b3f7b7e5634b87459fd5528e58bf045cc6c5717840703 checksum: ab344ce558de8f1075de6290517169bd3e95cf5038549b987ca7cfb14a9798ca12573e099958fecd518dd74f2bcfa76030dfc5d3b6e46b34dff10e4d675182c5
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/vaadin-material-styles@npm:~24.1.0": "@vaadin/vaadin-material-styles@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/vaadin-material-styles@npm:24.1.0" resolution: "@vaadin/vaadin-material-styles@npm:24.1.1"
dependencies: dependencies:
"@polymer/polymer": ^3.0.0 "@polymer/polymer": ^3.0.0
"@vaadin/vaadin-themable-mixin": ~24.1.0 "@vaadin/vaadin-themable-mixin": ~24.1.1
checksum: 205e67f5a99dda6cdf1410a0786408d42b8ea48c18c26b3a89d9524fd651b89993db12f3ccfff6635d7981a557416aa8656696d42e6f58fe593d6845b88334ac checksum: 601e345da8858a62804b1afde06a79f93e9b5c41fcc263bb692b6db167937e3dd1b754b502ceeaf4054586d3e04a68a167ba7bc2719ec4ad8ac10005795d9a6e
languageName: node languageName: node
linkType: hard linkType: hard
"@vaadin/vaadin-themable-mixin@npm:24.1.0, @vaadin/vaadin-themable-mixin@npm:~24.1.0": "@vaadin/vaadin-themable-mixin@npm:24.1.1, @vaadin/vaadin-themable-mixin@npm:~24.1.1":
version: 24.1.0 version: 24.1.1
resolution: "@vaadin/vaadin-themable-mixin@npm:24.1.0" resolution: "@vaadin/vaadin-themable-mixin@npm:24.1.1"
dependencies: dependencies:
"@open-wc/dedupe-mixin": ^1.3.0 "@open-wc/dedupe-mixin": ^1.3.0
lit: ^2.0.0 lit: ^2.0.0
checksum: 0abe57312bdda606b52ce93843e82628310e419cbfe4c8bd564c574f7883c8979861b1eb34982bab4a488a82a467dd80cd482e018154ce343310b2918146808d checksum: 5066300dcf6c987e43bb9c2e16d75198188220dfbde0c76d3d875444200f05b4de70d50fd3d082c0ac0b5075953835d1499ed01d51236e06a56d5ca9e6d25e4c
languageName: node languageName: node
linkType: hard linkType: hard
@ -5065,6 +5072,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@vscode/web-custom-data@npm:^0.4.2":
version: 0.4.6
resolution: "@vscode/web-custom-data@npm:0.4.6"
checksum: 2d87f3e50dc6eeacdbbca224f1c8837154acd6e1588f733ff812423e72f0a14d819740bbb91732a9c09978faa0fcfe1be339a17fd0beb130e51784752cef4b4f
languageName: node
linkType: hard
"@vue/compiler-sfc@npm:2.7.14": "@vue/compiler-sfc@npm:2.7.14":
version: 2.7.14 version: 2.7.14
resolution: "@vue/compiler-sfc@npm:2.7.14" resolution: "@vue/compiler-sfc@npm:2.7.14"
@ -6472,13 +6486,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"call-me-maybe@npm:^1.0.1":
version: 1.0.2
resolution: "call-me-maybe@npm:1.0.2"
checksum: 42ff2d0bed5b207e3f0122589162eaaa47ba618f79ad2382fe0ba14d9e49fbf901099a6227440acc5946f86a4953e8aa2d242b330b0a5de4d090bb18f8935cae
languageName: node
linkType: hard
"callsites@npm:^3.0.0": "callsites@npm:^3.0.0":
version: 3.1.0 version: 3.1.0
resolution: "callsites@npm:3.1.0" resolution: "callsites@npm:3.1.0"
@ -8144,14 +8151,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"eslint@npm:8.42.0": "eslint@npm:8.43.0":
version: 8.42.0 version: 8.43.0
resolution: "eslint@npm:8.42.0" resolution: "eslint@npm:8.43.0"
dependencies: dependencies:
"@eslint-community/eslint-utils": ^4.2.0 "@eslint-community/eslint-utils": ^4.2.0
"@eslint-community/regexpp": ^4.4.0 "@eslint-community/regexpp": ^4.4.0
"@eslint/eslintrc": ^2.0.3 "@eslint/eslintrc": ^2.0.3
"@eslint/js": 8.42.0 "@eslint/js": 8.43.0
"@humanwhocodes/config-array": ^0.11.10 "@humanwhocodes/config-array": ^0.11.10
"@humanwhocodes/module-importer": ^1.0.1 "@humanwhocodes/module-importer": ^1.0.1
"@nodelib/fs.walk": ^1.2.8 "@nodelib/fs.walk": ^1.2.8
@ -8189,7 +8196,7 @@ __metadata:
text-table: ^0.2.0 text-table: ^0.2.0
bin: bin:
eslint: bin/eslint.js eslint: bin/eslint.js
checksum: 07105397b5f2ff4064b983b8971e8c379ec04b1dfcc9d918976b3e00377189000161dac991d82ba14f8759e466091b8c71146f602930ca810c290ee3fcb3faf0 checksum: 55654ce00b0d128822b57526e40473d0497c7c6be3886afdc0b41b6b0dfbd34d0eae8159911b18451b4db51a939a0e6d2e117e847ae419086884fc3d4fe23c7c
languageName: node languageName: node
linkType: hard linkType: hard
@ -8494,20 +8501,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"fast-glob@npm:^2.2.6":
version: 2.2.7
resolution: "fast-glob@npm:2.2.7"
dependencies:
"@mrmlnc/readdir-enhanced": ^2.2.1
"@nodelib/fs.stat": ^1.1.2
glob-parent: ^3.1.0
is-glob: ^4.0.0
merge2: ^1.2.3
micromatch: ^3.1.10
checksum: 304ccff1d437fcc44ae0168b0c3899054b92e0fd6af6ad7c3ccc82ab4ddd210b99c7c739d60ee3686da2aa165cd1a31810b31fd91f7c2a575d297342a9fc0534
languageName: node
linkType: hard
"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9": "fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9":
version: 3.2.12 version: 3.2.12
resolution: "fast-glob@npm:3.2.12" resolution: "fast-glob@npm:3.2.12"
@ -9141,13 +9134,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"glob-to-regexp@npm:^0.3.0":
version: 0.3.0
resolution: "glob-to-regexp@npm:0.3.0"
checksum: d34b3219d860042d508c4893b67617cd16e2668827e445ff39cff9f72ef70361d3dc24f429e003cdfb6607c75c9664b8eadc41d2eeb95690af0b0d3113c1b23b
languageName: node
linkType: hard
"glob-to-regexp@npm:^0.4.1": "glob-to-regexp@npm:^0.4.1":
version: 0.4.1 version: 0.4.1
resolution: "glob-to-regexp@npm:0.4.1" resolution: "glob-to-regexp@npm:0.4.1"
@ -9633,9 +9619,9 @@ __metadata:
"@fullcalendar/timegrid": 6.1.8 "@fullcalendar/timegrid": 6.1.8
"@koa/cors": 4.0.0 "@koa/cors": 4.0.0
"@lezer/highlight": 1.1.6 "@lezer/highlight": 1.1.6
"@lit-labs/context": 0.3.2 "@lit-labs/context": 0.3.3
"@lit-labs/motion": 1.0.3 "@lit-labs/motion": 1.0.3
"@lit-labs/virtualizer": 2.0.2 "@lit-labs/virtualizer": 2.0.3
"@lrnwebcomponents/simple-tooltip": 7.0.2 "@lrnwebcomponents/simple-tooltip": 7.0.2
"@material/chips": =14.0.0-canary.53b3cad2f.0 "@material/chips": =14.0.0-canary.53b3cad2f.0
"@material/data-table": =14.0.0-canary.53b3cad2f.0 "@material/data-table": =14.0.0-canary.53b3cad2f.0
@ -9665,9 +9651,9 @@ __metadata:
"@material/web": =1.0.0-pre.10 "@material/web": =1.0.0-pre.10
"@mdi/js": 7.2.96 "@mdi/js": 7.2.96
"@mdi/svg": 7.2.96 "@mdi/svg": 7.2.96
"@octokit/auth-oauth-device": 5.0.0 "@octokit/auth-oauth-device": 5.0.2
"@octokit/plugin-retry": 5.0.3 "@octokit/plugin-retry": 5.0.4
"@octokit/rest": 19.0.11 "@octokit/rest": 19.0.13
"@open-wc/dev-server-hmr": 0.1.4 "@open-wc/dev-server-hmr": 0.1.4
"@polymer/app-layout": 3.1.0 "@polymer/app-layout": 3.1.0
"@polymer/iron-flex-layout": 3.0.1 "@polymer/iron-flex-layout": 3.0.1
@ -9704,8 +9690,8 @@ __metadata:
"@types/webspeechapi": 0.0.29 "@types/webspeechapi": 0.0.29
"@typescript-eslint/eslint-plugin": 5.59.11 "@typescript-eslint/eslint-plugin": 5.59.11
"@typescript-eslint/parser": 5.59.11 "@typescript-eslint/parser": 5.59.11
"@vaadin/combo-box": 24.1.0 "@vaadin/combo-box": 24.1.1
"@vaadin/vaadin-themable-mixin": 24.1.0 "@vaadin/vaadin-themable-mixin": 24.1.1
"@vibrant/color": 3.2.1-alpha.1 "@vibrant/color": 3.2.1-alpha.1
"@vibrant/core": 3.2.1-alpha.1 "@vibrant/core": 3.2.1-alpha.1
"@vibrant/quantizer-mmcq": 3.2.1-alpha.1 "@vibrant/quantizer-mmcq": 3.2.1-alpha.1
@ -9727,7 +9713,7 @@ __metadata:
deep-clone-simple: 1.1.1 deep-clone-simple: 1.1.1
deep-freeze: 0.0.1 deep-freeze: 0.0.1
del: 7.0.0 del: 7.0.0
eslint: 8.42.0 eslint: 8.43.0
eslint-config-airbnb-base: 15.0.0 eslint-config-airbnb-base: 15.0.0
eslint-config-airbnb-typescript: 17.0.0 eslint-config-airbnb-typescript: 17.0.0
eslint-config-prettier: 8.8.0 eslint-config-prettier: 8.8.0
@ -9763,7 +9749,7 @@ __metadata:
leaflet-draw: 1.0.4 leaflet-draw: 1.0.4
lint-staged: 13.2.2 lint-staged: 13.2.2
lit: 2.7.5 lit: 2.7.5
lit-analyzer: 1.2.1 lit-analyzer: 2.0.0-pre.3
lodash.template: 4.5.0 lodash.template: 4.5.0
magic-string: 0.30.0 magic-string: 0.30.0
map-stream: 0.0.7 map-stream: 0.0.7
@ -9796,10 +9782,10 @@ __metadata:
tar: 6.1.15 tar: 6.1.15
terser-webpack-plugin: 5.3.9 terser-webpack-plugin: 5.3.9
tinykeys: 2.1.0 tinykeys: 2.1.0
ts-lit-plugin: 1.2.1 ts-lit-plugin: 2.0.0-pre.1
tsparticles-engine: 2.10.1 tsparticles-engine: 2.10.1
tsparticles-preset-links: 2.10.1 tsparticles-preset-links: 2.10.1
typescript: 4.9.5 typescript: 5.1.3
unfetch: 5.0.0 unfetch: 5.0.0
vinyl-buffer: 1.0.1 vinyl-buffer: 1.0.1
vinyl-source-stream: 2.0.0 vinyl-source-stream: 2.0.0
@ -11367,21 +11353,22 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"lit-analyzer@npm:1.2.1": "lit-analyzer@npm:2.0.0-pre.3, lit-analyzer@npm:^2.0.0-pre.3":
version: 1.2.1 version: 2.0.0-pre.3
resolution: "lit-analyzer@npm:1.2.1" resolution: "lit-analyzer@npm:2.0.0-pre.3"
dependencies: dependencies:
"@vscode/web-custom-data": ^0.4.2
chalk: ^2.4.2 chalk: ^2.4.2
didyoumean2: 4.1.0 didyoumean2: 4.1.0
fast-glob: ^2.2.6 fast-glob: ^3.2.11
parse5: 5.1.0 parse5: 5.1.0
ts-simple-type: ~1.0.5 ts-simple-type: ~2.0.0-next.0
vscode-css-languageservice: 4.3.0 vscode-css-languageservice: 4.3.0
vscode-html-languageservice: 3.1.0 vscode-html-languageservice: 3.1.0
web-component-analyzer: ~1.1.1 web-component-analyzer: ^2.0.0-next.5
bin: bin:
lit-analyzer: cli.js lit-analyzer: cli.js
checksum: b89646033b45262a863bf32d8bf177bfa4f22cde4e2c3f2cd006abdd68aeab434505f67c3c5ed213d8a5936d063ec2845efb15b0968ec9cf9e0863e53e6c118c checksum: c6dcf657a0030342d183fcd9d550753bd5dd0692b478aff271085a5a0e7e08aff39cc6dde3d547ffca72897975ef07aac7de8a97e0060d2db70a85350412efae
languageName: node languageName: node
linkType: hard linkType: hard
@ -11798,7 +11785,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"merge2@npm:^1.2.3, merge2@npm:^1.3.0, merge2@npm:^1.4.1": "merge2@npm:^1.3.0, merge2@npm:^1.4.1":
version: 1.4.1 version: 1.4.1
resolution: "merge2@npm:1.4.1" resolution: "merge2@npm:1.4.1"
checksum: 7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2 checksum: 7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2
@ -15215,19 +15202,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ts-lit-plugin@npm:1.2.1": "ts-lit-plugin@npm:2.0.0-pre.1":
version: 1.2.1 version: 2.0.0-pre.1
resolution: "ts-lit-plugin@npm:1.2.1" resolution: "ts-lit-plugin@npm:2.0.0-pre.1"
dependencies: dependencies:
lit-analyzer: 1.2.1 lit-analyzer: ^2.0.0-pre.3
checksum: 3ba191d8924b18ba1aa1072db82cd10bca19f20693d98735dc1bbf3692ec759f2d4c728b789a1c1ade4d96e5ddf25e574fdba7c5e42b557c7e82da7a1ad298d7 web-component-analyzer: ^2.0.0-next.5
checksum: d9c8b3c0d8867e9564c7a3083accaf9f8b48b04c8f42e32c224806a7a606983abb3b7acb912e57d0d8f90ff650745bb1af18b1e379316b433838e37eddccfe1e
languageName: node languageName: node
linkType: hard linkType: hard
"ts-simple-type@npm:~1.0.5": "ts-simple-type@npm:2.0.0-next.0, ts-simple-type@npm:~2.0.0-next.0":
version: 1.0.7 version: 2.0.0-next.0
resolution: "ts-simple-type@npm:1.0.7" resolution: "ts-simple-type@npm:2.0.0-next.0"
checksum: 3cffb45eab7ab7fd963e2765914c41488d9611dc0619334ac1cf01bc2b02cf9746adf12172d785894474c6c5f3cfbf8212e675e456362373a0c2a61441ad8572 checksum: 025c5c1e4f2f7f2627300b2605a0346d5007f9c3d20d075807b01b3ae8179261e6be2d471f74948f3bae3208ca042203d97e80b984d6cb133396c5c4a3af5301
languageName: node languageName: node
linkType: hard linkType: hard
@ -15440,43 +15428,43 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"typescript@npm:4.9.5": "typescript@npm:5.1.3":
version: 4.9.5 version: 5.1.3
resolution: "typescript@npm:4.9.5" resolution: "typescript@npm:5.1.3"
bin: bin:
tsc: bin/tsc tsc: bin/tsc
tsserver: bin/tsserver tsserver: bin/tsserver
checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db checksum: d9d51862d98efa46534f2800a1071a613751b1585dc78884807d0c179bcd93d6e9d4012a508e276742f5f33c480adefc52ffcafaf9e0e00ab641a14cde9a31c7
languageName: node languageName: node
linkType: hard linkType: hard
"typescript@npm:^3.8.3": "typescript@npm:~4.4.3":
version: 3.9.10 version: 4.4.4
resolution: "typescript@npm:3.9.10" resolution: "typescript@npm:4.4.4"
bin: bin:
tsc: bin/tsc tsc: bin/tsc
tsserver: bin/tsserver tsserver: bin/tsserver
checksum: 46c842e2cd4797b88b66ef06c9c41dd21da48b95787072ccf39d5f2aa3124361bc4c966aa1c7f709fae0509614d76751455b5231b12dbb72eb97a31369e1ff92 checksum: 89ecb8436bb48ef5594d49289f5f89103071716b6e4844278f4fb3362856e31203e187a9c76d205c3f0b674d221a058fd28310dbcbcf5d95e9a57229bb5203f1
languageName: node languageName: node
linkType: hard linkType: hard
"typescript@patch:typescript@4.9.5#~builtin<compat/typescript>": "typescript@patch:typescript@5.1.3#~builtin<compat/typescript>":
version: 4.9.5 version: 5.1.3
resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin<compat/typescript>::version=4.9.5&hash=289587" resolution: "typescript@patch:typescript@npm%3A5.1.3#~builtin<compat/typescript>::version=5.1.3&hash=5da071"
bin: bin:
tsc: bin/tsc tsc: bin/tsc
tsserver: bin/tsserver tsserver: bin/tsserver
checksum: 1f8f3b6aaea19f0f67cba79057674ba580438a7db55057eb89cc06950483c5d632115c14077f6663ea76fd09fce3c190e6414bb98582ec80aa5a4eaf345d5b68 checksum: 6f0a9dca6bf4ce9dcaf4e282aade55ef4c56ecb5fb98d0a4a5c0113398815aea66d871b5611e83353e5953a19ed9ef103cf5a76ac0f276d550d1e7cd5344f61e
languageName: node languageName: node
linkType: hard linkType: hard
"typescript@patch:typescript@^3.8.3#~builtin<compat/typescript>": "typescript@patch:typescript@~4.4.3#~builtin<compat/typescript>":
version: 3.9.10 version: 4.4.4
resolution: "typescript@patch:typescript@npm%3A3.9.10#~builtin<compat/typescript>::version=3.9.10&hash=3bd3d3" resolution: "typescript@patch:typescript@npm%3A4.4.4#~builtin<compat/typescript>::version=4.4.4&hash=bbeadb"
bin: bin:
tsc: bin/tsc tsc: bin/tsc
tsserver: bin/tsserver tsserver: bin/tsserver
checksum: dc7141ab555b23a8650a6787f98845fc11692063d02b75ff49433091b3af2fe3d773650dea18389d7c21f47d620fb3b110ea363dab4ab039417a6ccbbaf96fc2 checksum: 3d1b04449662193544b81d055479d03b4c5dca95f1a82f8922596f089d894c9fefbe16639d1d9dfe26a7054419645530cef44001bc17aed1fe1eb3c237e9b3c7
languageName: node languageName: node
linkType: hard linkType: hard
@ -15978,18 +15966,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"web-component-analyzer@npm:~1.1.1": "web-component-analyzer@npm:^2.0.0-next.5":
version: 1.1.7 version: 2.0.0-next.5
resolution: "web-component-analyzer@npm:1.1.7" resolution: "web-component-analyzer@npm:2.0.0-next.5"
dependencies: dependencies:
fast-glob: ^3.2.2 fast-glob: ^3.2.2
ts-simple-type: ~1.0.5 ts-simple-type: 2.0.0-next.0
typescript: ^3.8.3 typescript: ~4.4.3
yargs: ^15.3.1 yargs: ^15.3.1
bin: bin:
wca: cli.js wca: cli.js
web-component-analyzer: cli.js web-component-analyzer: cli.js
checksum: 6c36521b7b79d5547ffdbc359029651ad1d929df6e09f8adfbafb2a34c23199712b7080f08f941f056b6a989718c11eb9221171d97ad397ed8a20cf08dd78e4b checksum: bc8eaf57884b81d378014376bcab92bce6b127971952831dd5bb06803b590cc99fb6fa17c7476c02ee014dfccfcee80e670d7fdc7aff4aae6aebc2e262055a65
languageName: node languageName: node
linkType: hard linkType: hard