Compare commits

..

15 Commits

Author SHA1 Message Date
Paul Bottein 9536b885c0 Migrate deprecated Web Awesome size values to short form (s/m/l) 2026-06-03 14:47:44 +02:00
Paul Bottein 04a986cd2c Restore search field autofocus in card and badge pickers (#52387) 2026-06-03 10:49:50 +01:00
Wendelin 5b09b1475d Landingpage download progress (#52359)
* Simplify and improve landingpage

* add core download progress

* reduce to 2 seconds

* Use round to display full integer as progress percentage

* Use find to get the job object

* Don't show progress label when progress is at 0

Before download starts, progress is at 0. At this point we may trying
to reach a server (and error out), so we aren't really in downloading
phase just yet. Simply treat 0 as "not started" and hide the progress
label until we have a real progress value.

---------

Co-authored-by: Stefan Agner <stefan@agner.ch>
2026-06-03 12:07:01 +03:00
Aidan Timson b1b9ac23cf Fix automation building block action icon style (#52382) 2026-06-03 10:21:48 +02:00
Paul Bottein aa19ca91a3 Add registryDependencies to strategies for selective regeneration (#30102) 2026-06-03 08:56:26 +02:00
Jan-Philipp Benecke 1388aa56ea Use consumeLocalize instead of hass in ha-icon-button-arrow-prev (#52377)
Use context in ha-icon-button-arrow-prev
2026-06-03 08:13:44 +03:00
renovate[bot] 6ca7ac1ca5 Update dependency fuse.js to v7.4.0 (#52379)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-03 08:13:03 +03:00
renovate[bot] d4380248c2 Update tsparticles to v4.1.1 (#52380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-03 08:12:39 +03:00
Jan-Philipp Benecke 14617aaf3c Fix hass-tabs-subpage narrow toggle (#52375) 2026-06-02 19:53:31 +02:00
Bram Kragten 42e1051d9c Add tags in app store too, plus show if addon is installed already (#52373) 2026-06-02 18:48:09 +02:00
Marcin Bauer cfe30114f0 Move live-test indicator to badge on condition icon (#52352)
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Wendelin <w@pe8.at>
2026-06-02 14:19:35 +00:00
Petar Petrov 288c03c248 Fix raw div tag showing in Sankey chart tooltips (#52365)
Fix raw div tag showing in sankey chart tooltips
2026-06-02 16:12:38 +02:00
renovate[bot] 8cd9a5adf6 Update dependency lint-staged to v17.0.6 (#52363)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-02 13:17:09 +00:00
Bram Kragten 4d3437b491 Matter add device: change how main entity is found (#52361)
Don't search for a entity based on main entity but use entity_category
2026-06-02 15:13:07 +02:00
Bram Kragten ceb51714be Migrate trigger behavior (#52360)
* Migrate trigger behavior

* Apply suggestions from code review

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2026-06-02 15:00:34 +02:00
215 changed files with 1170 additions and 1064 deletions
+2 -2
View File
@@ -78,13 +78,13 @@ const alerts: {
title: "Error with action",
description: "This is a test error alert with action",
type: "error",
actionSlot: html`<ha-button size="small" slot="action">restart</ha-button>`,
actionSlot: html`<ha-button size="s" slot="action">restart</ha-button>`,
},
{
title: "Unsaved data",
description: "You have unsaved data",
type: "warning",
actionSlot: html`<ha-button size="small" slot="action">save</ha-button>`,
actionSlot: html`<ha-button size="s" slot="action">save</ha-button>`,
},
{
title: "Slotted icon",
@@ -26,7 +26,7 @@ title: Button
filled button
</ha-button>
<ha-button size="small">
<ha-button size="s">
small
</ha-button>
</div>
@@ -34,7 +34,7 @@ title: Button
```html
<ha-button> simple button </ha-button>
<ha-button size="small"> small </ha-button>
<ha-button size="s"> small </ha-button>
```
### API
@@ -57,7 +57,7 @@ Check the [webawesome documentation](https://webawesome.com/docs/components/butt
| ---------- | ---------------------------------------------- | -------- | --------------------------------------------------------------------------------- |
| appearance | "accent"/"filled"/"plain" | "accent" | Sets the button appearance. |
| variants | "brand"/"danger"/"neutral"/"warning"/"success" | "brand" | Sets the button color variant. "brand" is default. |
| size | "small"/"medium"/"large" | "medium" | Sets the button size. |
| size | "xs"/"s"/"m"/"l"/"xl" | "m" | Sets the button size. |
| loading | Boolean | false | Shows a loading indicator instead of the buttons label and disable buttons click. |
| disabled | Boolean | false | Disables the button and prevents user interaction. |
+2 -2
View File
@@ -49,7 +49,7 @@ export class DemoHaButton extends LitElement {
<ha-button
.appearance=${appearance}
.variant=${variant}
size="small"
size="s"
>
${titleCase(`${variant} ${appearance}`)}
</ha-button>
@@ -100,7 +100,7 @@ export class DemoHaButton extends LitElement {
<ha-button
.variant=${variant}
.appearance=${appearance}
size="small"
size="s"
disabled
>
${titleCase(`${appearance}`)}
@@ -17,13 +17,13 @@ subtitle: A slider component for selecting a value from a range.
### Example Usage
<div class="wrapper">
<ha-slider size="small" with-markers min="0" max="8" value="4"></ha-slider>
<ha-slider size="medium"></ha-slider>
<ha-slider size="s" with-markers min="0" max="8" value="4"></ha-slider>
<ha-slider size="m"></ha-slider>
</div>
```html
<ha-slider size="small" with-markers min="0" max="8" value="4"></ha-slider>
<ha-slider size="medium"></ha-slider>
<ha-slider size="s" with-markers min="0" max="8" value="4"></ha-slider>
<ha-slider size="m"></ha-slider>
```
### API
+2 -2
View File
@@ -29,7 +29,7 @@ export class DemoHaSlider extends LitElement {
></ha-slider>
<span>Small</span>
<ha-slider
size="small"
size="s"
min="0"
max="8"
value="4"
@@ -37,7 +37,7 @@ export class DemoHaSlider extends LitElement {
></ha-slider>
<span>Medium</span>
<ha-slider
size="medium"
size="m"
min="0"
max="8"
value="4"
+31
View File
@@ -13,6 +13,28 @@ export interface NetworkInfo {
supervisor_internet: boolean;
}
interface SupervisorJob {
name: string;
reference: string | null;
uuid: string;
progress: number; // float, 0100
stage: string | null;
done: boolean;
errors: {
type: string;
message: string;
stage: string | null;
}[];
created: string; // ISO datetime string
extra: Record<string, unknown> | null;
child_jobs: SupervisorJob[];
}
export interface SupervisorJobInfo {
ignore_conditions: string[];
jobs: SupervisorJob[];
}
export const ALTERNATIVE_DNS_SERVERS: {
ipv4: string[];
ipv6: string[];
@@ -57,6 +79,15 @@ export async function getSupervisorNetworkInfo(): Promise<NetworkInfo> {
return responseData?.data;
}
export async function getSupervisorJobsInfo(): Promise<
HassioResponse<SupervisorJobInfo>
> {
const responseData = await handleFetchPromise<
HassioResponse<SupervisorJobInfo>
>(fetch("/supervisor-api/jobs/info"));
return responseData;
}
export const setSupervisorNetworkDns = async (
dnsServerIndex: number,
primaryInterface: string
+58 -6
View File
@@ -2,9 +2,9 @@ import { mdiOpenInNew } from "@mdi/js";
import { css, html, nothing, type PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { extractSearchParam } from "../../src/common/url/search-params";
import "../../src/components/animation/ha-fade-in";
import "../../src/components/ha-alert";
import "../../src/components/ha-button";
import "../../src/components/animation/ha-fade-in";
import "../../src/components/ha-spinner";
import "../../src/components/ha-svg-icon";
import "../../src/components/progress/ha-progress-bar";
@@ -15,6 +15,7 @@ import { haStyle } from "../../src/resources/styles";
import "./components/landing-page-logs";
import "./components/landing-page-network";
import {
getSupervisorJobsInfo,
getSupervisorNetworkInfo,
pingSupervisor,
type NetworkInfo,
@@ -24,6 +25,7 @@ import { LandingPageBaseElement } from "./landing-page-base-element";
export const ASSUME_CORE_START_SECONDS = 60;
const SCHEDULE_CORE_CHECK_SECONDS = 1;
const SCHEDULE_FETCH_NETWORK_INFO_SECONDS = 5;
const SCHEDULE_FETCH_JOBS_INFO_SECONDS = 2;
@customElement("ha-landing-page")
class HaLandingPage extends LandingPageBaseElement {
@@ -39,6 +41,8 @@ class HaLandingPage extends LandingPageBaseElement {
@state() private _coreCheckActive = false;
@state() private _progress = -1;
private _mobileApp =
extractSearchParam("redirect_uri") === "homeassistant://auth-callback";
@@ -60,7 +64,14 @@ class HaLandingPage extends LandingPageBaseElement {
${!networkIssue && !this._supervisorError
? html`
<p>${this.localize("subheader")}</p>
<ha-progress-bar indeterminate></ha-progress-bar>
<ha-progress-bar
.indeterminate=${this._progress <= 0}
.value=${this._progress > 0 ? this._progress : undefined}
.loading=${this._progress >= 0}
>${this._progress > 0
? `${Math.round(this._progress)}%`
: nothing}</ha-progress-bar
>
`
: nothing}
${networkIssue || this._networkInfoError
@@ -126,6 +137,7 @@ class HaLandingPage extends LandingPageBaseElement {
import("../../src/components/ha-language-picker");
this._fetchSupervisorInfo(true);
this._fetchSupervisorJobsInfo();
}
private _scheduleFetchSupervisorInfo() {
@@ -138,6 +150,13 @@ class HaLandingPage extends LandingPageBaseElement {
);
}
private _scheduleFetchSupervisorJobsInfo() {
setTimeout(
() => this._fetchSupervisorJobsInfo(),
SCHEDULE_FETCH_JOBS_INFO_SECONDS * 1000
);
}
private _scheduleTurnOffCoreCheck() {
setTimeout(() => {
this._coreCheckActive = false;
@@ -165,7 +184,7 @@ class HaLandingPage extends LandingPageBaseElement {
// assume supervisor update if ping fails -> don't show an error
if (!this._coreCheckActive && err.message !== "ping-failed") {
// eslint-disable-next-line no-console
console.error(err);
console.error("Failed to fetch supervisor info", err);
this._networkInfoError = true;
}
}
@@ -175,6 +194,33 @@ class HaLandingPage extends LandingPageBaseElement {
}
}
private async _fetchSupervisorJobsInfo() {
try {
const jobsInfo = await getSupervisorJobsInfo();
const coreInstallJob =
jobsInfo.result === "ok"
? jobsInfo.data.jobs.find(
(job) => job.name === "home_assistant_core_install"
)
: undefined;
if (coreInstallJob) {
this._progress = coreInstallJob.progress;
} else {
this._progress = -1;
}
} catch (err: any) {
await this._checkCoreAvailability();
if (!this._coreCheckActive) {
this._progress = -1;
// eslint-disable-next-line no-console
console.error("Failed to fetch supervisor jobs info", err);
}
}
this._scheduleFetchSupervisorJobsInfo();
}
private async _checkCoreAvailability() {
try {
const response = await fetch("/manifest.json");
@@ -222,21 +268,27 @@ class HaLandingPage extends LandingPageBaseElement {
flex-direction: column;
gap: var(--ha-space-4);
}
ha-language-picker {
min-width: 200px;
}
ha-alert p {
text-align: unset;
}
.footer ha-svg-icon {
--mdc-icon-size: var(--ha-space-5);
}
ha-language-picker {
margin-inline-start: calc(-1 * var(--ha-space-4));
}
ha-button {
margin-inline-end: calc(-1 * var(--ha-space-2));
}
ha-fade-in {
min-height: calc(100vh - 64px - 88px);
display: flex;
justify-content: center;
align-items: center;
}
ha-progress-bar {
--ha-progress-bar-track-height: 20px;
}
`,
];
}
+4 -4
View File
@@ -70,8 +70,8 @@
"@replit/codemirror-indentation-markers": "6.5.3",
"@swc/helpers": "0.5.23",
"@thomasloven/round-slider": "0.6.0",
"@tsparticles/engine": "4.1.0",
"@tsparticles/preset-links": "4.1.0",
"@tsparticles/engine": "4.1.1",
"@tsparticles/preset-links": "4.1.1",
"@vibrant/color": "4.0.4",
"@webcomponents/scoped-custom-element-registry": "0.0.10",
"@webcomponents/webcomponentsjs": "2.8.0",
@@ -88,7 +88,7 @@
"dialog-polyfill": "0.5.6",
"echarts": "6.1.0",
"element-internals-polyfill": "3.0.2",
"fuse.js": "7.3.0",
"fuse.js": "7.4.0",
"google-timezones-json": "1.2.0",
"gulp-zopfli-green": "7.0.0",
"hls.js": "1.6.16",
@@ -180,7 +180,7 @@
"jsdom": "29.1.1",
"jszip": "3.10.1",
"license-checker-rseidelsohn": "5.0.1",
"lint-staged": "17.0.5",
"lint-staged": "17.0.6",
"lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2",
"lodash.template": "4.18.1",
@@ -1,6 +1,5 @@
import { LitElement, css, html, nothing } from "lit";
import { LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import "../ha-tooltip";
export type LiveTestState = "pass" | "fail" | "invalid" | "unknown";
@@ -13,7 +12,6 @@ export type LiveTestState = "pass" | "fail" | "invalid" | "unknown";
*
* @attr {"pass"|"fail"|"invalid"|"unknown"} state - The current live-test state. Defaults to `unknown`.
* @attr {string} label - Accessible label announced by assistive technology.
* @attr {string} message - Optional tooltip body shown on hover/focus.
*/
@customElement("ha-automation-row-live-test")
export class HaAutomationRowLiveTest extends LitElement {
@@ -21,8 +19,6 @@ export class HaAutomationRowLiveTest extends LitElement {
@property() public label = "";
@property() public message?: string;
protected render() {
return html`
<div
@@ -31,39 +27,38 @@ export class HaAutomationRowLiveTest extends LitElement {
tabindex="0"
aria-label=${this.label}
></div>
${this.message
? html`<ha-tooltip for="indicator">${this.message}</ha-tooltip>`
: nothing}
`;
}
static styles = css`
:host {
position: absolute;
top: -5px;
inset-inline-end: -6px;
display: inline-block;
}
#indicator {
width: 12px;
height: 12px;
width: 10px;
height: 10px;
border-radius: var(--ha-border-radius-circle);
border: 3px solid;
border: var(--ha-border-width-md) solid;
box-sizing: border-box;
background-color: var(--card-background-color);
box-shadow: 0 0 0 2px var(--card-background-color);
transition: all var(--ha-animation-duration-normal) ease-in-out;
}
:host([state="pass"]) #indicator {
background-color: var(--ha-color-fill-success-loud-resting);
border-color: var(--ha-color-fill-success-loud-resting);
background-color: var(--ha-color-green-60);
border-color: var(--ha-color-green-60);
}
:host([state="fail"]) #indicator {
border-color: var(--ha-color-fill-warning-loud-resting);
border-color: var(--ha-color-orange-60);
}
:host([state="invalid"]) #indicator {
border-color: var(--ha-color-fill-danger-loud-resting);
border-color: var(--ha-color-red-60);
}
:host([state="unknown"]) #indicator {
border-color: var(--ha-color-fill-neutral-loud-resting);
border-color: var(--ha-color-neutral-60);
}
`;
}
@@ -165,7 +165,8 @@ export class HaAutomationRow extends LitElement {
::slotted([slot="leading-icon"]) {
color: var(--ha-color-on-neutral-quiet);
}
:host([building-block]) ::slotted([slot="leading-icon"]) {
:host([building-block]) ::slotted([slot="leading-icon"].action-icon),
:host([building-block]) ::slotted(#condition-icon) {
--mdc-icon-size: var(--ha-space-5);
color: var(--white-color);
transform: rotate(-45deg);
+6 -2
View File
@@ -101,18 +101,22 @@ export class HaSankeyChart extends LitElement {
const value = this.valueFormatter
? this.valueFormatter(data.value)
: data.value;
// Keep numbers and units left-to-right, even in RTL locales.
const formattedValue = html`<div style="direction:ltr; display: inline;">
${value}
</div>`;
if (data.id) {
const node = this.data.nodes.find((n) => n.id === data.id);
return html`<ha-chart-tooltip-marker
.color=${String(params.color ?? "")}
></ha-chart-tooltip-marker>
${node?.label ?? data.id}<br />${value}`;
${node?.label ?? data.id}<br />${formattedValue}`;
}
if (data.source && data.target) {
const source = this.data.nodes.find((n) => n.id === data.source);
const target = this.data.nodes.find((n) => n.id === data.target);
return html`${source?.label ?? data.source}
${target?.label ?? data.target}<br />${value}`;
${target?.label ?? data.target}<br />${formattedValue}`;
}
return null;
};
+1 -1
View File
@@ -167,7 +167,7 @@ export class StateHistoryCharts extends LitElement {
)}`}
${this.syncCharts && this._hasZoomedCharts
? html`<ha-button
size="large"
size="l"
class="reset-button"
@click=${this._handleGlobalZoomReset}
>
@@ -117,7 +117,7 @@ export class HaEntityNamePicker extends LitElement {
<div class="header">
${this.label ? html`<label>${this.label}</label>` : nothing}
<ha-button-toggle-group
size="small"
size="s"
.buttons=${modeButtons}
.active=${this._mode}
.disabled=${this.disabled}
+2 -2
View File
@@ -15,7 +15,7 @@ import "./ha-svg-icon";
*
* @attr {ToggleButton[]} buttons - the button config
* @attr {string} active - The value of the currently active button.
* @attr {("small"|"medium")} size - The size of the buttons in the group.
* @attr {("s"|"m")} size - The size of the buttons in the group.
* @attr {("brand"|"neutral"|"success"|"warning"|"danger")} variant - The variant of the buttons in the group.
*
* @fires value-changed - Dispatched when the active button changes.
@@ -26,7 +26,7 @@ export class HaButtonToggleGroup extends LitElement {
@property() public active?: string;
@property({ reflect: true }) size: "small" | "medium" = "medium";
@property({ reflect: true }) size: "s" | "m" = "m";
@property({ type: Boolean, reflect: true, attribute: "no-wrap" })
public nowrap = false;
+3 -3
View File
@@ -27,7 +27,7 @@ export type Appearance = "accent" | "filled" | "outlined" | "plain";
* @cssprop --ha-button-height - The height of the button.
* @cssprop --ha-button-border-radius - The border radius of the button. defaults to `var(--ha-border-radius-pill)`.
*
* @attr {("small"|"medium"|"large")} size - Sets the button size.
* @attr {("xs"|"s"|"m"|"l"|"xl")} size - Sets the button size.
* @attr {("brand"|"neutral"|"danger"|"warning"|"success")} variant - Sets the button color variant. "primary" is default.
* @attr {("accent"|"filled"|"plain")} appearance - Sets the button appearance.
* @attr {boolean} loading - shows a loading indicator instead of the buttons label and disable buttons click.
@@ -65,7 +65,7 @@ export class HaButton extends Button {
box-shadow: var(--ha-button-box-shadow);
}
:host([size="small"]) .button {
:host([size="s"]) .button {
--wa-form-control-height: var(
--ha-button-height,
var(--button-height, 32px)
@@ -74,7 +74,7 @@ export class HaButton extends Button {
--wa-form-control-padding-inline: var(--ha-space-3);
}
:host([size="large"]) .button {
:host([size="l"]) .button {
--wa-form-control-height: var(
--ha-button-height,
var(--button-height, 48px)
+1 -1
View File
@@ -61,7 +61,7 @@ export class HaDurationInput extends LitElement {
${this.allowNegative
? html`
<ha-button-toggle-group
size="small"
size="s"
.buttons=${[
{ label: "+", iconPath: mdiPlusThick, value: "+" },
{ label: "-", iconPath: mdiMinusThick, value: "-" },
+1 -1
View File
@@ -120,7 +120,7 @@ export class HaFileUpload extends LitElement {
@dragend=${this._handleDragEnd}
>${!this.value
? html`<ha-button
size="small"
size="s"
appearance="filled"
@click=${this._openFilePicker}
>
@@ -129,7 +129,7 @@ export class HaFormOptionalActions extends LitElement implements HaFormElement {
@wa-select=${this._handleAddAction}
@closed=${stopPropagation}
>
<ha-button slot="trigger" appearance="filled" size="small">
<ha-button slot="trigger" appearance="filled" size="s">
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
${this.localize?.("ui.components.form-optional-actions.add") ||
"Add interaction"}
+1 -1
View File
@@ -174,7 +174,7 @@ export class HaGenericPicker extends PickerMixin(LitElement) {
<slot name="field">
${this.addButtonLabel && !this.value
? html`<ha-button
size="small"
size="s"
appearance="filled"
@click=${this.open}
.disabled=${this.disabled}
+7 -4
View File
@@ -3,13 +3,12 @@ import type { TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { mainWindow } from "../common/dom/get_main_window";
import type { HomeAssistant } from "../types";
import "./ha-icon-button";
import { consumeLocalize } from "../common/decorators/consume-context-entry";
import type { LocalizeFunc } from "../common/translations/localize";
@customElement("ha-icon-button-arrow-prev")
export class HaIconButtonArrowPrev extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public disabled = false;
@property() public label?: string;
@@ -25,11 +24,15 @@ export class HaIconButtonArrowPrev extends LitElement {
@state() private _icon =
mainWindow.document.dir === "rtl" ? mdiArrowRight : mdiArrowLeft;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
protected render(): TemplateResult {
return html`
<ha-icon-button
.disabled=${this.disabled}
.label=${this.label || this.hass?.localize("ui.common.back") || "Back"}
.label=${this.label || this._localize("ui.common.back") || "Back"}
.path=${this._icon}
.href=${this.href}
.target=${this.target}
+1 -1
View File
@@ -171,7 +171,7 @@ export class HaLabelsPicker extends LitElement {
: nothing}
<ha-button
id="picker"
size="small"
size="s"
appearance="filled"
@click=${this._openPicker}
.disabled=${this.disabled}
@@ -53,7 +53,7 @@ class HaLawnMowerActionButton extends LitElement {
appearance="plain"
@click=${this.callService}
.service=${action.service}
size="small"
size="s"
>
${this.hass.localize(`ui.card.lawn_mower.actions.${action.action}`)}
</ha-button>
+1 -1
View File
@@ -102,7 +102,7 @@ export class HaPictureUpload extends LitElement {
<div>
<ha-button
appearance="plain"
size="small"
size="s"
variant="danger"
@click=${this._handleChangeClick}
>
@@ -63,7 +63,7 @@ export class HaChooseSelector extends LitElement {
return html`<div class="multi-header">
<span>${this.label}${this.required ? "*" : ""}</span>
<ha-button-toggle-group
size="small"
size="s"
.buttons=${this._toggleButtons(
this.selector.choose.choices,
this.selector.choose.translation_key,
@@ -190,7 +190,7 @@ export class HaMediaSelector extends LitElement {
? html`<div>
<ha-button
appearance="plain"
size="small"
size="s"
variant="danger"
@click=${this._clearValue}
>
@@ -307,7 +307,7 @@ export class HaNumericThresholdSelector extends LitElement {
>`
: nothing}
<ha-button-toggle-group
size="small"
size="s"
.buttons=${choiceToggleButtons}
.active=${activeChoice}
.disabled=${this.disabled}
+3 -3
View File
@@ -5,7 +5,7 @@ import { mainWindow } from "../common/dom/get_main_window";
@customElement("ha-slider")
export class HaSlider extends Slider {
@property({ reflect: true }) size: "small" | "medium" = "small";
@property({ reflect: true }) size: "s" | "m" = "s";
@property({ type: Boolean, attribute: "with-tooltip" }) withTooltip = true;
@@ -110,12 +110,12 @@ export class HaSlider extends Slider {
);
}
:host([size="medium"]) {
:host([size="m"]) {
--thumb-width: 20px;
--thumb-height: 20px;
}
:host([size="small"]) {
:host([size="s"]) {
--thumb-width: 16px;
--thumb-height: 16px;
}
+1 -1
View File
@@ -43,7 +43,7 @@ class HaTracePicker extends LitElement {
slot="field"
appearance="filled"
variant="neutral"
size="small"
size="s"
@click=${this._openPicker}
>
${this._renderTracePickerValue(this.value!)}
+1 -1
View File
@@ -58,7 +58,7 @@ export class HaVacuumState extends LitElement {
return html`
<ha-button
appearance="plain"
size="small"
size="s"
@click=${this._callService}
.disabled=${!interceptable}
>
+1 -1
View File
@@ -99,7 +99,7 @@ export class HaInputCopy extends LitElement {
: nothing}
</ha-input>
</div>
<ha-button @click=${this._copy} appearance="plain" size="small">
<ha-button @click=${this._copy} appearance="plain" size="s">
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
${this.label || this._i18n.localize("ui.common.copy")}
</ha-button>
+1 -1
View File
@@ -130,7 +130,7 @@ class HaInputMulti extends LitElement {
</ha-sortable>
<div class="layout horizontal">
<ha-button
size="small"
size="s"
appearance="filled"
@click=${this._addItem}
.disabled=${this.disabled ||
@@ -38,7 +38,7 @@ class MediaManageButton extends LitElement {
return nothing;
}
return html`
<ha-button appearance="filled" size="small" @click=${this._manage}>
<ha-button appearance="filled" size="s" @click=${this._manage}>
<ha-svg-icon .path=${mdiFolderEdit} slot="start"></ha-svg-icon>
${this.hass.localize(
"ui.components.media-browser.file_management.manage"
+11
View File
@@ -485,6 +485,17 @@ export const migrateAutomationTrigger = (
}
delete trigger.platform;
}
if ("options" in trigger) {
if (trigger.options && "behavior" in trigger.options) {
if (trigger.options.behavior === "any") {
trigger.options.behavior = "each";
} else if (trigger.options.behavior === "last") {
trigger.options.behavior = "all";
}
}
}
return trigger;
};
-21
View File
@@ -1,21 +0,0 @@
import { createContext } from "@lit/context";
export interface DirtyStateContext<State = unknown> {
/** Whether current state differs from the initial snapshot */
isDirty: boolean;
/** Current tracked state */
state: State;
/** Update the tracked state — triggers dirty comparison */
setState: (state: State) => void;
/** Reset initial snapshot to current state (marks clean) */
markClean: () => void;
}
/**
* Singleton context key for dirty-state tracking.
*
* Because Lit context keys are singletons, the value type is
* `DirtyStateContext<unknown>`. The provider mixin and consumer controller
* supply type-safe APIs on top of this boundary.
*/
export const dirtyStateContext = createContext<DirtyStateContext>("dirtyState");
@@ -114,7 +114,7 @@ class EntityPreviewRow extends LitElement {
if (domain === "button") {
return html`
<ha-button appearance="plain" size="small" .disabled=${disabled}>
<ha-button appearance="plain" size="s" .disabled=${disabled}>
${this.hass.localize("ui.card.button.press")}
</ha-button>
`;
@@ -237,7 +237,7 @@ class EntityPreviewRow extends LitElement {
.disabled=${disabled}
class="text-content"
appearance="plain"
size="small"
size="s"
>
${stateObj.state === "locked"
? this.hass!.localize("ui.card.lock.unlock")
@@ -214,7 +214,7 @@ export class HaMoreInfoViewVacuumCleanAreas extends LitElement {
? html`
<ha-button
appearance="plain"
size="small"
size="s"
@click=${this._openSegmentMapping}
>
<ha-svg-icon
@@ -31,7 +31,7 @@ class MoreInfoAutomation extends LitElement {
<div class="actions">
<ha-button
appearance="plain"
size="small"
size="s"
@click=${this._runActions}
.disabled=${this.stateObj!.state === UNAVAILABLE}
>
@@ -22,7 +22,7 @@ class MoreInfoCounter extends LitElement {
<div class="actions">
<ha-button
appearance="plain"
size="small"
size="s"
.action=${"increment"}
@click=${this._handleActionClick}
.disabled=${disabled ||
@@ -32,7 +32,7 @@ class MoreInfoCounter extends LitElement {
</ha-button>
<ha-button
appearance="plain"
size="small"
size="s"
.action=${"decrement"}
@click=${this._handleActionClick}
.disabled=${disabled ||
@@ -42,7 +42,7 @@ class MoreInfoCounter extends LitElement {
</ha-button>
<ha-button
appearance="plain"
size="small"
size="s"
.action=${"reset"}
@click=${this._handleActionClick}
.disabled=${disabled}
@@ -431,7 +431,7 @@ class MoreInfoMediaPlayer extends LitElement {
? html`<ha-button
variant="brand"
appearance="filled"
size="medium"
size="m"
action=${action}
@click=${this._handleClick}
class="center-control"
@@ -39,7 +39,7 @@ class MoreInfoPerson extends LitElement {
<div class="actions">
<ha-button
appearance="plain"
size="small"
size="s"
@click=${this._handleAction}
>
${this.hass.localize(
@@ -52,7 +52,7 @@ class MoreInfoSiren extends LitElement {
${allowAdvanced
? html`<ha-button
appearance="plain"
size="small"
size="s"
@click=${this._showAdvancedControlsDialog}
>
${this.hass.localize("ui.components.siren.advanced_controls")}
@@ -21,7 +21,7 @@ class MoreInfoTimer extends LitElement {
? html`
<ha-button
appearance="plain"
size="small"
size="s"
.action=${"start"}
@click=${this._handleActionClick}
>
@@ -33,7 +33,7 @@ class MoreInfoTimer extends LitElement {
? html`
<ha-button
appearance="plain"
size="small"
size="s"
.action=${"pause"}
@click=${this._handleActionClick}
>
@@ -45,7 +45,7 @@ class MoreInfoTimer extends LitElement {
? html`
<ha-button
appearance="plain"
size="small"
size="s"
.action=${"cancel"}
@click=${this._handleActionClick}
>
@@ -53,7 +53,7 @@ class MoreInfoTimer extends LitElement {
</ha-button>
<ha-button
appearance="plain"
size="small"
size="s"
.action=${"finish"}
@click=${this._handleActionClick}
>
+3 -11
View File
@@ -63,7 +63,6 @@ import { subscribeLabFeature } from "../../data/labs";
import type { ItemType } from "../../data/search";
import { SearchableDomains } from "../../data/search";
import { getSensorNumericDeviceClasses } from "../../data/sensor";
import { DirtyStateProviderMixin } from "../../mixins/dirty-state-provider-mixin";
import { ScrollableFadeMixin } from "../../mixins/scrollable-fade-mixin";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import {
@@ -122,8 +121,8 @@ declare global {
const DEFAULT_VIEW: MoreInfoView = "info";
@customElement("ha-more-info-dialog")
export class MoreInfoDialog extends DirtyStateProviderMixin()(
SubscribeMixin(ScrollableFadeMixin(LitElement))
export class MoreInfoDialog extends SubscribeMixin(
ScrollableFadeMixin(LitElement)
) {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -641,8 +640,7 @@ export class MoreInfoDialog extends DirtyStateProviderMixin()(
@closed=${this._dialogClosed}
@opened=${this._handleOpened}
@show-child-view=${this._showChildView}
.preventScrimClose=${(this._currView === "settings" &&
this.isDirtyState) ||
.preventScrimClose=${this._currView === "settings" ||
!this._isEscapeEnabled}
flexcontent
>
@@ -959,12 +957,6 @@ export class MoreInfoDialog extends DirtyStateProviderMixin()(
}
}
if (changedProps.has("_currView") || changedProps.has("_entry")) {
if (this._currView === "settings" && this._entry) {
this._initDirtyTracking({ type: "deep" });
}
}
if (changedProps.has("_currView")) {
this._infoEditMode = false;
this._detailsYamlMode = false;
@@ -95,10 +95,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
"ui.panel.config.voice_assistants.satellite_wizard.local.failed_secondary"
)}
</p>
<ha-button
appearance="plain"
size="small"
@click=${this._prevStep}
<ha-button appearance="plain" size="s" @click=${this._prevStep}
>${this.hass.localize("ui.common.back")}</ha-button
>
<ha-button
@@ -108,7 +105,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
)}
target="_blank"
rel="noreferrer noopener"
size="small"
size="s"
appearance="plain"
>
<ha-svg-icon .path=${mdiOpenInNew} slot="start"></ha-svg-icon>
@@ -131,10 +128,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
"ui.panel.config.voice_assistants.satellite_wizard.local.not_supported_secondary"
)}
</p>
<ha-button
appearance="plain"
size="small"
@click=${this._prevStep}
<ha-button appearance="plain" size="s" @click=${this._prevStep}
>${this.hass.localize("ui.common.back")}</ha-button
>
<ha-button
@@ -145,7 +139,7 @@ export class HaVoiceAssistantSetupStepLocal extends LitElement {
target="_blank"
rel="noreferrer noopener"
appearance="plain"
size="small"
size="s"
>
<ha-svg-icon .path=${mdiOpenInNew} slot="start"></ha-svg-icon>
${this.hass.localize(
@@ -131,7 +131,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
></ha-select>
<ha-button
appearance="plain"
size="small"
size="s"
@click=${this._testWakeWord}
>
<ha-svg-icon
@@ -166,7 +166,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
</ha-select>
<ha-button
appearance="plain"
size="small"
size="s"
@click=${this._openPipeline}
>
<ha-svg-icon slot="start" .path=${mdiCog}></ha-svg-icon>
@@ -186,11 +186,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
@value-changed=${this._voicePicked}
@closed=${stopPropagation}
></ha-tts-voice-picker>
<ha-button
appearance="plain"
size="small"
@click=${this._testTts}
>
<ha-button appearance="plain" size="s" @click=${this._testTts}>
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.voice_assistants.satellite_wizard.success.try_tts"
@@ -150,7 +150,7 @@ export class HaVoiceAssistantSetupStepWakeWord extends LitElement {
? html`<div class="footer centered">
<ha-button
appearance="plain"
size="small"
size="s"
@click=${this._changeWakeWord}
>${this.hass.localize(
"ui.panel.config.voice_assistants.satellite_wizard.wake_word.change_wake_word"
@@ -117,7 +117,7 @@ export class HaVoiceCommandDialog extends LitElement {
slot="trigger"
appearance="plain"
variant="neutral"
size="small"
size="s"
.loading=${!this._pipelines}
>
${this._pipeline?.name}
+1 -1
View File
@@ -23,7 +23,7 @@ class HaInitPage extends LitElement {
<p class="retry-text">
Retrying in ${this._retryInSeconds} seconds...
</p>
<ha-button size="small" appearance="plain" @click=${this._retry}
<ha-button size="s" appearance="plain" @click=${this._retry}
>Retry now</ha-button
>
${location.host.includes("ui.nabu.casa")
+1 -2
View File
@@ -33,7 +33,6 @@ class HassErrorScreen extends LitElement {
`
: html`
<ha-icon-button-arrow-prev
.hass=${this.hass}
@click=${this._handleBack}
></ha-icon-button-arrow-prev>
`}
@@ -42,7 +41,7 @@ class HassErrorScreen extends LitElement {
<div class="content">
<ha-alert alert-type="error">${this.error}</ha-alert>
<slot>
<ha-button appearance="plain" size="small" @click=${this._handleBack}>
<ha-button appearance="plain" size="s" @click=${this._handleBack}>
${this.hass?.localize("ui.common.back")}
</ha-button>
</slot>
-1
View File
@@ -35,7 +35,6 @@ class HassLoadingScreen extends LitElement {
`
: html`
<ha-icon-button-arrow-prev
.hass=${this.hass}
@click=${this._handleBack}
></ha-icon-button-arrow-prev>
`}
-2
View File
@@ -43,12 +43,10 @@ class HassSubpage extends LitElement {
? html`
<ha-icon-button-arrow-prev
href=${this.backPath}
.hass=${this.hass}
></ha-icon-button-arrow-prev>
`
: html`
<ha-icon-button-arrow-prev
.hass=${this.hass}
@click=${this._backTapped}
></ha-icon-button-arrow-prev>
`}
+2 -2
View File
@@ -139,6 +139,8 @@ export class HassTabsSubpage extends LitElement {
);
public willUpdate(changedProperties: PropertyValues<this>) {
this.toggleAttribute("narrow", this._narrow);
if (changedProperties.has("route")) {
const currentPath = `${this.route.prefix}${this.route.path}`;
this._activeTab = this.tabs.find((tab) =>
@@ -173,12 +175,10 @@ export class HassTabsSubpage extends LitElement {
? html`
<ha-icon-button-arrow-prev
.href=${this.backPath}
.hass=${this.hass}
></ha-icon-button-arrow-prev>
`
: html`
<ha-icon-button-arrow-prev
.hass=${this.hass}
@click=${this._backTapped}
></ha-icon-button-arrow-prev>
`}
+1 -1
View File
@@ -97,7 +97,7 @@ class NotificationManager extends LitElement {
? html`
<ha-button
appearance="plain"
size="small"
size="s"
slot="action"
@click=${this._buttonClicked}
>
-193
View File
@@ -1,193 +0,0 @@
import { provide } from "@lit/context";
import type { LitElement } from "lit";
import { state } from "lit/decorators";
import { deepEqual } from "../common/util/deep-equal";
import { shallowEqual } from "../common/util/shallow-equal";
import {
dirtyStateContext,
type DirtyStateContext,
} from "../data/context/dirty-state";
import type { Constructor } from "../types";
export type CompareStrategy<State> =
| { type: "deep" }
| { type: "shallow" }
| { type: "custom"; compare: (a: State, b: State) => boolean };
function resolveCompare<State>(
strategy: CompareStrategy<State>
): (a: State, b: State) => boolean {
switch (strategy.type) {
case "deep":
return (a, b) => deepEqual(a, b);
case "shallow":
return (a, b) => shallowEqual(a, b);
default:
return strategy.compare;
}
}
/**
* Mixin that provides dirty-state tracking via Lit context.
*
* Uses the `@provide` decorator so any descendant component can consume
* dirty-state with `@consume({ context: dirtyStateContext, subscribe: true })`.
*
* Curried generic pattern: `State` is explicitly provided while `Base` is
* inferred from the superclass argument.
*
* @example Eager init (state known upfront, e.g. dialog open):
* ```ts
* interface MyDialogState { name: string; icon: string }
*
* class MyDialog extends DirtyStateProviderMixin<MyDialogState>()(LitElement) {
* open() {
* this._initDirtyTracking({ type: "shallow" }, { name: "", icon: "" });
* }
* }
* ```
*
* @example Deferred init (child consumer reports initial state):
* ```ts
* class MyPage extends DirtyStateProviderMixin<FormState>()(LitElement) {
* connectedCallback() {
* super.connectedCallback();
* this._initDirtyTracking({ type: "deep" });
* // First setState from a child consumer sets the baseline
* }
* }
* ```
*
* Child consumers:
* ```ts
* @consume({ context: dirtyStateContext, subscribe: true })
* @state()
* private _dirtyState?: DirtyStateContext;
*
* // Read: this._dirtyState?.isDirty
* // Write: this._dirtyState?.setState(newState)
* ```
*/
export const DirtyStateProviderMixin =
<State = unknown>() =>
<Base extends Constructor<LitElement>>(superClass: Base) => {
class DirtyStateProviderMixinClass extends superClass {
private _dirtyInitialState: State | undefined;
private _dirtyCurrentState: State | undefined;
private _dirtyCompareFn: (a: State, b: State) => boolean = deepEqual;
@provide({ context: dirtyStateContext })
@state()
private _dirtyStateContext: DirtyStateContext = this._buildContextValue(
undefined,
false
);
/**
* Build the context value object for the provider.
*
* The returned type is `DirtyStateContext` (i.e. `DirtyStateContext<unknown>`)
* because the singleton context key is typed at `unknown`. The single
* `unknown → State` narrowing cast in `setState` is the only unsafe boundary
* and is confined here.
*/
private _buildContextValue(
currentState: State | undefined,
isDirty: boolean
): DirtyStateContext {
return {
isDirty,
state: currentState,
setState: (incoming: unknown) => {
this._updateDirtyState(incoming as State);
},
markClean: () => {
this._markDirtyStateClean();
},
};
}
/**
* Initialize dirty state tracking.
*
* When `initialState` is provided, tracking starts immediately.
* When omitted (deferred mode), the first `_updateDirtyState` /
* `setState` call from a consumer becomes the baseline snapshot.
*
* Call again to reset (e.g. when the underlying entity changes).
*/
protected _initDirtyTracking(
strategy: CompareStrategy<State>,
initialState?: State
): void {
this._dirtyCompareFn = resolveCompare(strategy);
if (initialState !== undefined) {
this._dirtyInitialState = initialState;
this._dirtyCurrentState = initialState;
this._dirtyStateContext = this._buildContextValue(
initialState,
false
);
} else {
this._dirtyInitialState = undefined;
this._dirtyCurrentState = undefined;
this._dirtyStateContext = this._buildContextValue(undefined, false);
}
}
/**
* Update the tracked state. Triggers dirty comparison against initial snapshot.
*
* If called before `_initDirtyTracking` provided an initial state (deferred
* mode), the first call sets the baseline and reports clean.
*
* Guarded: no-ops if the computed dirty status and state reference are
* unchanged, preventing render loops when called from `updated()`.
*/
protected _updateDirtyState(newState: State): void {
// Deferred init: first state becomes the baseline
if (this._dirtyInitialState === undefined) {
this._dirtyInitialState = newState;
this._dirtyCurrentState = newState;
this._dirtyStateContext = this._buildContextValue(newState, false);
return;
}
const isDirty = !this._dirtyCompareFn(
this._dirtyInitialState,
newState
);
if (
this._dirtyCurrentState !== undefined &&
this._dirtyCompareFn(this._dirtyCurrentState, newState) &&
this._dirtyStateContext.isDirty === isDirty
) {
return;
}
this._dirtyCurrentState = newState;
this._dirtyStateContext = this._buildContextValue(newState, isDirty);
}
/**
* Reset the initial snapshot to the current state, marking the state as clean.
* Call this after a successful save.
*/
protected _markDirtyStateClean(): void {
this._dirtyInitialState = this._dirtyCurrentState;
this._dirtyStateContext = this._buildContextValue(
this._dirtyCurrentState,
false
);
}
/**
* Whether the current state differs from the initial snapshot.
*/
public get isDirtyState(): boolean {
return this._dirtyStateContext.isDirty;
}
}
return DirtyStateProviderMixinClass;
};
+19 -30
View File
@@ -1,39 +1,20 @@
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import type { LocalizeFunc } from "../../common/translations/localize";
import { fireEvent } from "../../common/dom/fire_event";
import { customElement } from "lit/decorators";
import "../../components/ha-dialog";
import { DialogMixin } from "../../dialogs/dialog-mixin";
import type { AppDialogParams } from "./show-app-dialog";
@customElement("app-dialog")
class DialogApp extends LitElement {
@property({ attribute: false }) public localize?: LocalizeFunc;
@state() private _open = false;
public async showDialog(params: { localize: LocalizeFunc }): Promise<void> {
this.localize = params.localize;
this._open = true;
}
public closeDialog(): void {
this._open = false;
}
private _dialogClosed(): void {
this.localize = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
class DialogApp extends DialogMixin<AppDialogParams>(LitElement) {
protected render() {
if (!this.localize) {
if (!this.params?.localize) {
return nothing;
}
return html`<ha-dialog
.open=${this._open}
header-title=${this.localize(
open
header-title=${this.params.localize(
"ui.panel.page-onboarding.welcome.download_app"
) || "Click here to download the app"}
@closed=${this._dialogClosed}
>
<div>
<div class="app-qr">
@@ -45,13 +26,17 @@ class DialogApp extends LitElement {
<img
loading="lazy"
src="/static/images/appstore.svg"
alt=${this.localize("ui.panel.page-onboarding.welcome.appstore")}
alt=${this.params.localize(
"ui.panel.page-onboarding.welcome.appstore"
)}
class="icon"
/>
<img
loading="lazy"
src="/static/images/qr-appstore.svg"
alt=${this.localize("ui.panel.page-onboarding.welcome.appstore")}
alt=${this.params.localize(
"ui.panel.page-onboarding.welcome.appstore"
)}
/>
</a>
<a
@@ -62,13 +47,17 @@ class DialogApp extends LitElement {
<img
loading="lazy"
src="/static/images/playstore.svg"
alt=${this.localize("ui.panel.page-onboarding.welcome.playstore")}
alt=${this.params.localize(
"ui.panel.page-onboarding.welcome.playstore"
)}
class="icon"
/>
<img
loading="lazy"
src="/static/images/qr-playstore.svg"
alt=${this.localize("ui.panel.page-onboarding.welcome.playstore")}
alt=${this.params.localize(
"ui.panel.page-onboarding.welcome.playstore"
)}
/>
</a>
</div>
+59 -75
View File
@@ -1,103 +1,87 @@
import { mdiAccountGroup, mdiOpenInNew } from "@mdi/js";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import type { LocalizeFunc } from "../../common/translations/localize";
import { customElement } from "lit/decorators";
import "../../components/ha-dialog";
import "../../components/ha-list";
import "../../components/ha-list-item";
import "../../components/ha-svg-icon";
import "../../components/item/ha-list-item-button";
import "../../components/list/ha-list-nav";
import { DialogMixin } from "../../dialogs/dialog-mixin";
import type { CommunityDialogParams } from "./show-community-dialog";
@customElement("community-dialog")
class DialogCommunity extends LitElement {
@property({ attribute: false }) public localize?: LocalizeFunc;
@state() private _open = false;
public async showDialog(params): Promise<void> {
this.localize = params.localize;
this._open = true;
}
public closeDialog(): void {
this._open = false;
}
private _dialogClosed(): void {
this.localize = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
class DialogCommunity extends DialogMixin<CommunityDialogParams>(LitElement) {
protected render() {
if (!this.localize) {
if (!this.params?.localize) {
return nothing;
}
return html`<ha-dialog
.open=${this._open}
header-title=${this.localize(
open
header-title=${this.params.localize(
"ui.panel.page-onboarding.welcome.community"
)}
@closed=${this._dialogClosed}
>
<ha-list>
<a
<ha-list-nav>
<ha-list-item-button
target="_blank"
rel="noreferrer noopener"
href="https://community.home-assistant.io/"
>
<ha-list-item hasMeta graphic="icon">
<img
src="/static/icons/favicon-192x192.png"
slot="graphic"
alt="Home Assistant Logo"
/>
${this.localize("ui.panel.page-onboarding.welcome.forums")}
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-list-item>
</a>
<a
<img
src="/static/icons/favicon-192x192.png"
slot="start"
alt="Home Assistant Logo"
/>
<span slot="headline">
${this.params.localize("ui.panel.page-onboarding.welcome.forums")}
</span>
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-list-item-button>
<ha-list-item-button
target="_blank"
rel="noreferrer noopener"
href="https://newsletter.openhomefoundation.org/"
>
<ha-list-item hasMeta graphic="icon">
<img
src="/static/icons/logo_ohf.svg"
slot="graphic"
alt="Open Home Foundation Logo"
/>
${this.localize(
<img
src="/static/icons/logo_ohf.svg"
slot="start"
alt="Open Home Foundation Logo"
/>
<span slot="headline">
${this.params.localize(
"ui.panel.page-onboarding.welcome.open_home_newsletter"
)}
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-list-item>
</a>
<a
</span>
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-list-item-button>
<ha-list-item-button
target="_blank"
rel="noreferrer noopener"
href="https://www.home-assistant.io/join-chat"
>
<ha-list-item hasMeta graphic="icon">
<img
src="/static/images/logo_discord.png"
slot="graphic"
alt="Discord Logo"
/>
${this.localize("ui.panel.page-onboarding.welcome.discord")}
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-list-item>
</a>
<a
<img
src="/static/images/logo_discord.png"
slot="start"
alt="Discord Logo"
/>
<span slot="headline">
${this.params.localize("ui.panel.page-onboarding.welcome.discord")}
</span>
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-list-item-button>
<ha-list-item-button
target="_blank"
rel="noreferrer noopener"
href="https://fosstodon.org/@homeassistant"
>
<ha-list-item hasMeta graphic="icon">
<ha-svg-icon .path=${mdiAccountGroup} slot="graphic"></ha-svg-icon>
${this.localize("ui.panel.page-onboarding.welcome.social_media")}
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-list-item>
</a>
</ha-list>
<ha-svg-icon .path=${mdiAccountGroup} slot="start"></ha-svg-icon>
<span slot="headline">
${this.params.localize(
"ui.panel.page-onboarding.welcome.social_media"
)}
</span>
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-list-item-button>
</ha-list-nav>
</ha-dialog>`;
}
@@ -105,12 +89,12 @@ class DialogCommunity extends LitElement {
ha-dialog {
--dialog-content-padding: 0;
}
ha-list-item {
height: 56px;
--mdc-list-item-meta-size: 20px;
img {
width: 32px;
height: 32px;
}
a {
text-decoration: none;
ha-svg-icon {
color: var(--ha-color-text-secondary);
}
`;
}
+6 -1
View File
@@ -3,13 +3,18 @@ import type { LocalizeFunc } from "../../common/translations/localize";
export const loadAppDialog = () => import("./app-dialog");
export interface AppDialogParams {
localize: LocalizeFunc;
}
export const showAppDialog = (
element: HTMLElement,
params: { localize: LocalizeFunc }
params: AppDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "app-dialog",
dialogImport: loadAppDialog,
dialogParams: params,
addHistory: false,
});
};
@@ -3,13 +3,18 @@ import type { LocalizeFunc } from "../../common/translations/localize";
export const loadCommunityDialog = () => import("./community-dialog");
export interface CommunityDialogParams {
localize: LocalizeFunc;
}
export const showCommunityDialog = (
element: HTMLElement,
params: { localize: LocalizeFunc }
params: CommunityDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "community-dialog",
dialogImport: loadCommunityDialog,
dialogParams: params,
addHistory: false,
});
};
@@ -132,7 +132,7 @@ class OnboardingRestoreBackupRestore extends LitElement {
href="https://www.home-assistant.io/installation/#advanced-installation-methods"
target="_blank"
rel="noreferrer noopener"
size="small"
size="s"
>
${this.localize(
"ui.panel.page-onboarding.restore.ha-cloud.learn_more"
+5 -5
View File
@@ -147,7 +147,7 @@ export class HAFullCalendar extends LitElement {
<div class="navigation">
<ha-button
appearance="filled"
size="small"
size="s"
class="today"
@click=${this._handleToday}
>${this.hass.localize(
@@ -171,7 +171,7 @@ export class HAFullCalendar extends LitElement {
<ha-button-toggle-group
.buttons=${viewToggleButtons}
.active=${this._activeView}
size="small"
size="s"
no-wrap
@value-changed=${this._handleView}
></ha-button-toggle-group>
@@ -197,7 +197,7 @@ export class HAFullCalendar extends LitElement {
<div class="controls buttons">
<ha-button
appearance="plain"
size="small"
size="s"
class="today"
@click=${this._handleToday}
>${this.hass.localize(
@@ -207,7 +207,7 @@ export class HAFullCalendar extends LitElement {
<ha-button-toggle-group
.buttons=${viewToggleButtons}
.active=${this._activeView}
size="small"
size="s"
no-wrap
@value-changed=${this._handleView}
></ha-button-toggle-group>
@@ -219,7 +219,7 @@ export class HAFullCalendar extends LitElement {
<div id="calendar"></div>
${this.addFab && this._hasMutableCalendars
? html`<ha-button size="large" slot="fab" @click=${this._createEvent}>
? html`<ha-button size="l" slot="fab" @click=${this._createEvent}>
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize("ui.components.calendar.event.add")}
</ha-button>`
@@ -175,7 +175,7 @@ export class HaConfigApplicationCredentials extends LitElement {
? html`
<ha-button
appearance="plain"
size="small"
size="s"
@click=${this._deleteSelected}
variant="danger"
>${this.hass.localize(
@@ -199,11 +199,7 @@ export class HaConfigApplicationCredentials extends LitElement {
</ha-help-tooltip>
`}
</div>
<ha-button
slot="fab"
size="large"
@click=${this._addApplicationCredential}
>
<ha-button slot="fab" size="l" @click=${this._addApplicationCredential}>
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.application_credentials.picker.add_application_credential"
@@ -1,5 +1,5 @@
import "@home-assistant/webawesome/dist/components/tag/tag";
import { mdiHelpCircleOutline } from "@mdi/js";
import { mdiCheckCircle, mdiHelpCircleOutline } from "@mdi/js";
import type { TemplateResult } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
@@ -25,7 +25,9 @@ class SupervisorAppsCardContent extends LitElement {
@property() public stage: AddonStage = "stable";
@property() public state: AddonState = null;
@property() public state?: AddonState;
@property({ type: Boolean }) public installed = false;
@property() public description?: string;
@@ -77,13 +79,23 @@ class SupervisorAppsCardContent extends LitElement {
</div>
</div>
</div>
${this.tags?.length || this.state
${this.tags?.length || this.state !== undefined || this.installed
? html`
<div class="footer">
<supervisor-apps-state
.state=${this.state || "unknown"}
></supervisor-apps-state>
${this.state !== undefined
? html`<supervisor-apps-state
.state=${this.state || "unknown"}
></supervisor-apps-state>`
: this.installed
? html`<div class="installed">
<ha-svg-icon .path=${mdiCheckCircle}></ha-svg-icon>
<span
>${this.hass.localize(
"ui.panel.config.apps.state.installed"
)}</span
>
</div>`
: html`<span></span>`}
${this.tags?.length
? html`<div class="tags">
${this.tags.map(
@@ -159,6 +171,17 @@ class SupervisorAppsCardContent extends LitElement {
display: flex;
gap: var(--ha-space-2);
}
.installed {
display: inline-flex;
align-items: center;
gap: var(--ha-space-2);
color: var(--ha-color-text-secondary);
font-size: var(--ha-font-size-m);
}
.installed ha-svg-icon {
--mdc-icon-size: 16px;
color: var(--ha-color-on-success-normal);
}
`;
}
@@ -156,7 +156,7 @@ export class HaConfigAppsInstalled extends LitElement {
)}
</div>
<ha-button size="large" href="/config/apps/available">
<ha-button size="l" href="/config/apps/available">
<ha-svg-icon slot="start" .path=${mdiStorePlus}></ha-svg-icon>
${this.hass.localize("ui.panel.config.apps.installed.add_app")}
</ha-button>
@@ -295,7 +295,7 @@ export class HaConfigAppsInstalled extends LitElement {
cursor: pointer;
}
ha-button[size="large"] {
ha-button[size="l"] {
position: fixed;
right: calc(var(--ha-space-4) + var(--safe-area-inset-right));
bottom: calc(var(--ha-space-4) + var(--safe-area-inset-bottom));
@@ -116,7 +116,7 @@ export class HaConfigAppsRegistries extends LitElement {
id="registry"
has-fab
></ha-data-table>
<ha-button size="large" @click=${this._showAddRegistryDialog}>
<ha-button size="l" @click=${this._showAddRegistryDialog}>
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize("ui.panel.config.apps.registries.add")}
</ha-button>
@@ -184,7 +184,7 @@ export class HaConfigAppsRegistries extends LitElement {
ha-icon-button.delete {
color: var(--error-color);
}
ha-button[size="large"] {
ha-button[size="l"] {
position: fixed;
right: calc(var(--ha-space-4) + var(--safe-area-inset-right));
bottom: calc(var(--ha-space-4) + var(--safe-area-inset-bottom));
@@ -195,7 +195,7 @@ export class HaConfigAppsRepositories extends LitElement {
id="slug"
has-fab
></ha-data-table>
<ha-button size="large" @click=${this._showAddRepositoryDialog}>
<ha-button size="l" @click=${this._showAddRepositoryDialog}>
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize("ui.panel.config.apps.repositories.add")}
</ha-button>
@@ -292,7 +292,7 @@ export class HaConfigAppsRepositories extends LitElement {
ha-icon-button.delete {
color: var(--error-color);
}
ha-button[size="large"] {
ha-button[size="l"] {
position: fixed;
right: calc(var(--ha-space-4) + var(--safe-area-inset-right));
bottom: calc(var(--ha-space-4) + var(--safe-area-inset-bottom));
@@ -1,7 +1,14 @@
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import {
mdiAlertDecagramOutline,
mdiArrowUpBoldCircle,
mdiArrowUpBoldCircleOutline,
mdiFlask,
mdiPuzzle,
} from "@mdi/js";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { navigate } from "../../../common/navigate";
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
@@ -10,6 +17,7 @@ import type { HassioAddonRepository } from "../../../data/hassio/addon";
import type { StoreAddon } from "../../../data/supervisor/store";
import type { HomeAssistant } from "../../../types";
import "./components/supervisor-apps-card-content";
import type { AppTag } from "./components/supervisor-apps-card-content";
import { filterAndSort } from "./components/supervisor-apps-filter";
import { supervisorAppsStyle } from "./resources/supervisor-apps-style";
@@ -54,21 +62,29 @@ export class SupervisorAppsRepositoryEl extends LitElement {
<div class="content">
<h1>${repo.name}</h1>
<div class="card-group">
${addons.map(
(addon) => html`
${addons.map((addon) => {
const tags = this._getAppTags(addon);
return html`
<ha-card
outlined
.addon=${addon}
class=${addon.available ? "" : "not_available"}
@click=${this._addonTapped}
>
<div class="card-content">
<div
class=${classMap({
"card-content": true,
"has-footer": tags.length > 0 || addon.installed,
})}
>
<supervisor-apps-card-content
.hass=${this.hass}
.title=${addon.name}
.stage=${addon.stage}
.description=${addon.description}
.available=${addon.available}
.installed=${addon.installed}
.tags=${tags}
.icon=${addon.installed && addon.update_available
? mdiArrowUpBoldCircle
: mdiPuzzle}
@@ -108,8 +124,8 @@ export class SupervisorAppsRepositoryEl extends LitElement {
></supervisor-apps-card-content>
</div>
</ha-card>
`
)}
`;
})}
</div>
</div>
`;
@@ -119,6 +135,32 @@ export class SupervisorAppsRepositoryEl extends LitElement {
navigate(`/config/app/${ev.currentTarget.addon.slug}/info?store=true`);
}
private _getAppTags(addon: StoreAddon): AppTag[] {
const labels: AppTag[] = [];
if (addon.installed && addon.update_available) {
labels.push({
label: this.hass.localize(
`ui.panel.config.apps.state.update_available`
),
variant: "brand",
iconPath: mdiArrowUpBoldCircleOutline,
});
}
if (addon.stage !== "stable") {
labels.push({
label: this.hass.localize(
`ui.panel.config.apps.dashboard.capability.stages.${addon.stage}`
),
variant: addon.stage === "experimental" ? "warning" : "danger",
iconPath:
addon.stage === "experimental" ? mdiFlask : mdiAlertDecagramOutline,
});
}
return labels;
}
static get styles(): CSSResultGroup {
return [
supervisorAppsStyle,
@@ -127,6 +169,9 @@ export class SupervisorAppsRepositoryEl extends LitElement {
cursor: pointer;
overflow: hidden;
}
.card-content.has-footer {
padding: var(--ha-space-4) var(--ha-space-4) var(--ha-space-2);
}
.not_available {
opacity: 0.6;
}
@@ -318,7 +318,7 @@ export class HaConfigAreasDashboard extends LitElement {
: nothing}
</div>
<ha-dropdown slot="fab" @wa-select=${this._handleCreateAction}>
<ha-button slot="trigger" id="fab" size="large">
<ha-button slot="trigger" id="fab" size="l">
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize("ui.common.add")}
</ha-button>
@@ -120,7 +120,7 @@ export default class HaAutomationAction extends AutomationSortableListMixin<Acti
.disabled=${this.disabled}
@click=${this._addActionDialog}
.appearance=${this.root ? "accent" : "filled"}
.size=${this.root ? "medium" : "small"}
.size=${this.root ? "m" : "s"}
>
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
${this.hass.localize(
@@ -728,7 +728,7 @@ class DialogAddAutomationElement
active-variant="brand"
.buttons=${tabButtons}
.active=${this._tab}
size="small"
size="s"
full-width
@value-changed=${this._switchTab}
></ha-button-toggle-group>`
@@ -35,7 +35,7 @@ export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
)}
<ha-button
appearance="plain"
size="small"
size="s"
slot="action"
@click=${this._enable}
>
@@ -57,7 +57,7 @@ export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
<ha-button
slot="fab"
size="large"
size="l"
class=${this.dirty ? "dirty" : ""}
.disabled=${this.saving}
@click=${this._saveAutomation}
@@ -52,6 +52,7 @@ import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
import "../../../../components/ha-dropdown-item";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-icon-button";
import "../../../../components/ha-tooltip";
import type {
AutomationClipboard,
Condition,
@@ -211,11 +212,27 @@ export default class HaAutomationConditionRow extends LitElement {
);
return html`
<ha-condition-icon
slot="leading-icon"
.hass=${this.hass}
.condition=${this.condition.condition}
></ha-condition-icon>
<div id="condition-icon" class="icon-badge-wrapper" slot="leading-icon">
<ha-condition-icon
.hass=${this.hass}
.condition=${this.condition.condition}
></ha-condition-icon>
${this.optionsInSidebar && this.condition.condition !== "trigger"
? html`<ha-automation-row-live-test
.state=${this._liveTestResult.state}
.label=${this.hass.localize(
`ui.panel.config.automation.editor.conditions.live_test_state.${this._liveTestResult.state}`
)}
></ha-automation-row-live-test>`
: nothing}
</div>
${this.optionsInSidebar &&
this.condition.condition !== "trigger" &&
this._liveTestResult.message
? html`<ha-tooltip for="condition-icon" slot="leading-icon"
>${this._liveTestResult.message}</ha-tooltip
>`
: nothing}
<h3 slot="header">
${capitalizeFirstLetter(
describeCondition(this.condition, this.hass, this._entityReg)
@@ -531,17 +548,7 @@ export default class HaAutomationConditionRow extends LitElement {
@click=${this._toggleSidebar}
@toggle-collapsed=${this._toggleCollapse}
>${this._renderRow()}
<ha-automation-row-live-test
slot="icons"
.state=${this.condition.condition !== "trigger"
? this._liveTestResult.state
: "unknown"}
.label=${this.hass.localize(
`ui.panel.config.automation.editor.conditions.live_test_state.${this.condition.condition !== "trigger" ? this._liveTestResult.state : "unknown"}`
)}
.message=${this._liveTestResult.message}
></ha-automation-row-live-test
></ha-automation-row>`
</ha-automation-row>`
: html`
<ha-expansion-panel
left-chevron
@@ -294,7 +294,7 @@ export default class HaAutomationCondition extends AutomationSortableListMixin<C
.disabled=${this.disabled}
@click=${this._addConditionDialog}
.appearance=${this.root ? "accent" : "filled"}
.size=${this.root ? "medium" : "small"}
.size=${this.root ? "m" : "s"}
>
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
${this.hass.localize(
@@ -237,7 +237,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
? html`
<ha-button
appearance="plain"
size="small"
size="s"
@click=${this._showTrace}
slot="toolbar-icon"
>
@@ -490,7 +490,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
)}
<ha-button
appearance="filled"
size="small"
size="s"
variant="warning"
slot="action"
@click=${this._duplicate}
@@ -508,7 +508,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
"ui.panel.config.automation.editor.disabled"
)}
<ha-button
size="small"
size="s"
slot="action"
@click=${this._toggle}
>
@@ -533,7 +533,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
)}
<ha-button
appearance="filled"
size="small"
size="s"
slot="action"
@click=${this._toggle}
>
@@ -553,7 +553,7 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
></ha-yaml-editor>
<ha-button
slot="fab"
size="large"
size="l"
class=${this.dirty ? "dirty" : ""}
.disabled=${this.saving}
@click=${this._handleSaveAutomation}
@@ -23,11 +23,7 @@ export class HaAutomationNote extends LitElement {
"ui.panel.config.automation.editor.note.label"
)}
</span>
<ha-button
@click=${this._handleClick}
size="small"
appearance="plain"
>
<ha-button @click=${this._handleClick} size="s" appearance="plain">
${this._i18n.localize("ui.common.edit")}
</ha-button>
</div>
@@ -695,14 +695,14 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
target="_blank"
appearance="plain"
rel="noreferrer"
size="small"
size="s"
>
${this.hass.localize("ui.panel.config.common.learn_more")}
<ha-svg-icon slot="end" .path=${mdiOpenInNew}> </ha-svg-icon>
</ha-button>
</div>`
: nothing}
<ha-button slot="fab" size="large" @click=${this._createNew}>
<ha-button slot="fab" size="l" @click=${this._createNew}>
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.automation.picker.add_automation"
@@ -110,7 +110,7 @@ export class HaAutomationTrace extends LitElement {
? html`
<ha-button
appearance="plain"
size="small"
size="s"
class="trace-link"
@click=${this._navigateToAutomation}
slot="toolbar-icon"
@@ -114,7 +114,7 @@ export const ManualEditorMixin = <TConfig>(
<div class="fab-positioner">
<ha-button
slot="fab"
size="large"
size="l"
class=${this.dirty ? "dirty" : ""}
.disabled=${this.saving}
@click=${this.saveConfig}
@@ -92,7 +92,7 @@ export default class HaAutomationOption extends AutomationSortableListMixin<Opti
<div class="buttons">
<ha-button
appearance="filled"
size="small"
size="s"
.disabled=${this.disabled}
@click=${this._addOption}
>
@@ -104,7 +104,7 @@ export default class HaAutomationOption extends AutomationSortableListMixin<Opti
${!this.showDefaultActions
? html`<ha-button
appearance="plain"
size="small"
size="s"
.disabled=${this.disabled}
@click=${this._showDefaultActions}
>
+5
View File
@@ -53,6 +53,11 @@ export const rowStyles = css`
position: absolute;
}
.icon-badge-wrapper {
position: relative;
display: inline-flex;
}
.note-indicator {
color: var(--ha-color-on-neutral-normal);
}
@@ -179,7 +179,7 @@ export default class HaAutomationTrigger extends AutomationSortableListMixin<Tri
.disabled=${this.disabled}
@click=${this._addTriggerDialog}
.appearance=${this.root ? "accent" : "filled"}
.size=${this.root ? "medium" : "small"}
.size=${this.root ? "m" : "s"}
>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.add"
@@ -40,7 +40,7 @@ class HaBackupConfigEncryptionKey extends LitElement {
appearance="plain"
slot="end"
@click=${this._download}
size="small"
size="s"
>
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
${this.hass.localize(
@@ -63,7 +63,7 @@ class HaBackupConfigEncryptionKey extends LitElement {
appearance="plain"
slot="end"
@click=${this._show}
size="small"
size="s"
>
${this.hass.localize(
"ui.panel.config.backup.encryption_key.show_encryption_key_action"
@@ -84,7 +84,7 @@ class HaBackupConfigEncryptionKey extends LitElement {
<ha-button
appearance="plain"
variant="danger"
size="small"
size="s"
slot="end"
@click=${this._change}
>
@@ -156,7 +156,7 @@ class HaBackupConfigEncryptionKey extends LitElement {
ha-list-item-base::part(supporting-text) {
white-space: wrap;
}
ha-button[size="small"] ha-svg-icon {
ha-button[size="s"] ha-svg-icon {
--mdc-icon-size: 16px;
}
`;
@@ -379,7 +379,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
)}
</span>
<ha-button
size="small"
size="s"
appearance="plain"
slot="end"
@click=${this._downloadKey}
@@ -146,7 +146,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
)}
</span>
<ha-button
size="small"
size="s"
appearance="plain"
slot="end"
@click=${this._download}
@@ -533,7 +533,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
? html`
<ha-button
slot="fab"
size="large"
size="l"
?disabled=${backupInProgress}
@click=${this._newBackup}
>
@@ -161,7 +161,7 @@ class HaConfigBackupDetails extends LitElement {
slot="end"
rel="noreferrer noopener"
appearance="plain"
size="small"
size="s"
>
${this.hass.localize(
"ui.panel.config.backup.location.encryption.location_encrypted_cloud_learn_more"
@@ -323,7 +323,7 @@ class HaConfigBackupOverview extends LitElement {
<ha-button
slot="fab"
size="large"
size="l"
.loading=${backupInProgress}
@click=${this._newBackup}
>
@@ -289,7 +289,7 @@ class HaConfigBackupSettings extends LitElement {
: nothing}
<div class="card-actions">
<ha-button
size="small"
size="s"
href=${documentationUrl(this.hass, "/integrations/#backup")}
target="_blank"
rel="noreferrer"
@@ -302,7 +302,7 @@ class HaConfigBackupSettings extends LitElement {
</ha-button>
${supervisor
? html`<ha-button
size="small"
size="s"
appearance="plain"
href="/config/storage"
>
@@ -478,7 +478,7 @@ class HaConfigBackupSettings extends LitElement {
justify-content: space-between;
}
ha-button[size="small"] ha-svg-icon {
ha-button[size="s"] ha-svg-icon {
--mdc-icon-size: 16px;
}
`;
@@ -174,7 +174,7 @@ class DialogImportBlueprint extends LitElement {
)}
</p>
<ha-button
size="small"
size="s"
appearance="plain"
href=${documentationUrl(this.hass, "/get-blueprints")}
target="_blank"
@@ -349,7 +349,7 @@ class HaBlueprintOverview extends LitElement {
href=${documentationUrl(this.hass, "/get-blueprints")}
target="_blank"
rel="noreferrer noopener"
size="small"
size="s"
>
${this.hass.localize(
"ui.panel.config.blueprint.overview.discover_more"
@@ -375,7 +375,7 @@ class HaBlueprintOverview extends LitElement {
.path=${mdiHelpCircleOutline}
@click=${this._showHelp}
></ha-icon-button>
<ha-button slot="fab" size="large" @click=${this._addBlueprintClicked}>
<ha-button slot="fab" size="l" @click=${this._addBlueprintClicked}>
<ha-svg-icon slot="start" .path=${mdiDownload}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.blueprint.overview.add_blueprint"
@@ -15,19 +15,13 @@ import type {
} from "../../../data/category_registry";
import { internationalizationContext } from "../../../data/context";
import { DialogMixin } from "../../../dialogs/dialog-mixin";
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
import { haStyleDialog } from "../../../resources/styles";
import type { ValueChangedEvent } from "../../../types";
import type { CategoryRegistryDetailDialogParams } from "./show-dialog-category-registry-detail";
interface CategoryFormState {
name: string;
icon: string | null;
}
@customElement("dialog-category-registry-detail")
class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
DialogMixin<CategoryRegistryDetailDialogParams>(LitElement)
class DialogCategoryDetail extends DialogMixin<CategoryRegistryDetailDialogParams>(
LitElement
) {
@state()
@consume({ context: internationalizationContext, subscribe: true })
@@ -50,10 +44,6 @@ class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
this._name = this.params?.suggestedName || "";
this._icon = null;
}
this._initDirtyTracking(
{ type: "shallow" },
{ name: this._name, icon: this._icon }
);
}
protected render() {
@@ -62,14 +52,13 @@ class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
}
const entry = this.params.entry;
const nameInvalid = !this._isNameValid();
const isCreate = !entry;
return html`
<ha-dialog
open
header-title=${entry
? this._i18n.localize("ui.panel.config.category.editor.edit")
: this._i18n.localize("ui.panel.config.category.editor.create")}
.preventScrimClose=${this.isDirtyState}
prevent-scrim-close
>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
@@ -107,9 +96,7 @@ class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
<ha-button
slot="primaryAction"
@click=${this._updateEntry}
.disabled=${nameInvalid ||
!!this._submitting ||
(!isCreate && !this.isDirtyState)}
.disabled=${nameInvalid || !!this._submitting}
>
${entry
? this._i18n.localize("ui.common.save")
@@ -127,17 +114,15 @@ class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
private _nameChanged(ev: InputEvent) {
this._error = undefined;
this._name = (ev.target as HaInput).value ?? "";
this._updateDirtyState({ name: this._name, icon: this._icon });
}
private _iconChanged(ev: ValueChangedEvent<string>) {
this._error = undefined;
this._icon = ev.detail.value;
this._updateDirtyState({ name: this._name, icon: this._icon });
}
private async _updateEntry() {
const create = !this.params?.entry;
const create = !this.params!.entry;
this._submitting = true;
let newValue: CategoryRegistryEntry | undefined;
try {
@@ -146,11 +131,10 @@ class DialogCategoryDetail extends DirtyStateProviderMixin<CategoryFormState>()(
icon: this._icon || (create ? undefined : null),
};
if (create) {
newValue = await this.params?.createEntry?.(values);
newValue = await this.params!.createEntry!(values);
} else {
newValue = await this.params?.updateEntry?.(values);
newValue = await this.params!.updateEntry!(values);
}
this._markDirtyStateClean();
this.closeDialog();
} catch (err: any) {
this._error =
@@ -187,7 +187,7 @@ export class CloudRemotePref extends LitElement {
<ha-button
slot="end"
appearance="plain"
size="small"
size="s"
@click=${this._openCertInfo}
>
${this.hass.localize(
@@ -95,7 +95,7 @@ export class CloudWebhooks extends LitElement {
<ha-button
slot="end"
appearance="plain"
size="small"
size="s"
@click=${this._handleManageButton}
>
${this.hass!.localize(
@@ -123,7 +123,7 @@ class ConfigAnalytics extends SubscribeMixin(LitElement) {
</div>
<div class="card-actions">
<ha-button
size="small"
size="s"
appearance="plain"
@click=${this._downloadDeviceInfo}
>
@@ -217,7 +217,7 @@ class HaConfigSectionUpdates extends LitElement {
? html`
<ha-button
appearance="plain"
size="small"
size="s"
.group=${group}
.disabled=${group.entities.every((entity) =>
updateIsInstalling(entity)
@@ -167,7 +167,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
)}
</div>
<ha-button-toggle-group
size="small"
size="s"
class="yaml-mode-toggle"
.buttons=${modeButtons}
.active=${this._yamlMode ? "yaml" : "ui"}
@@ -36,7 +36,6 @@ class PanelDeveloperTools extends LitElement {
<div class="toolbar">
<ha-icon-button-arrow-prev
slot="navigationIcon"
.hass=${this.hass}
@click=${this._handleBack}
></ha-icon-button-arrow-prev>
<div class="main-title">
@@ -274,7 +274,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
@click=${this._fixIssue}
.data=${statistic.issues}
appearance="plain"
size="small"
size="s"
>
${localize(
statistic.issues.some((issue) =>
@@ -519,7 +519,7 @@ export class HaConfigDevicePage extends LitElement {
<div class="card-actions" slot="actions">
<ha-button
variant="warning"
size="small"
size="s"
@click=${this._enableDevice}
>
${this.hass.localize("ui.common.enable")}
@@ -811,7 +811,7 @@ export class HaConfigDeviceDashboard extends LitElement {
.hass=${this.hass}
slot="toolbar-icon"
></ha-integration-overflow-menu>
<ha-button slot="fab" size="large" @click=${this._addDevice}>
<ha-button slot="fab" size="l" @click=${this._addDevice}>
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize("ui.panel.config.devices.add_device")}
</ha-button>
@@ -140,11 +140,7 @@ export class EnergyBatterySettings extends LitElement {
`
: ""}
<div class="row">
<ha-button
@click=${this._addSource}
appearance="filled"
size="small"
>
<ha-button @click=${this._addSource} appearance="filled" size="s">
<ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.energy.battery.add_battery_system"

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