mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-13 11:19:25 +00:00
Compare commits
10 Commits
button-tog
...
dev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d41d524850 | ||
![]() |
4f05f6305a | ||
![]() |
ba0b1239be | ||
![]() |
708b68f35d | ||
![]() |
3108e98b97 | ||
![]() |
ba7609cc2c | ||
![]() |
506fd7d480 | ||
![]() |
9767ebe1fb | ||
![]() |
539e89e7b5 | ||
![]() |
a7eef81272 |
15
package.json
15
package.json
@@ -46,12 +46,12 @@
|
||||
"@formatjs/intl-numberformat": "8.15.4",
|
||||
"@formatjs/intl-pluralrules": "5.4.4",
|
||||
"@formatjs/intl-relativetimeformat": "11.4.11",
|
||||
"@fullcalendar/core": "6.1.18",
|
||||
"@fullcalendar/daygrid": "6.1.18",
|
||||
"@fullcalendar/interaction": "6.1.18",
|
||||
"@fullcalendar/list": "6.1.18",
|
||||
"@fullcalendar/luxon3": "6.1.18",
|
||||
"@fullcalendar/timegrid": "6.1.18",
|
||||
"@fullcalendar/core": "6.1.19",
|
||||
"@fullcalendar/daygrid": "6.1.19",
|
||||
"@fullcalendar/interaction": "6.1.19",
|
||||
"@fullcalendar/list": "6.1.19",
|
||||
"@fullcalendar/luxon3": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@lit-labs/motion": "1.0.9",
|
||||
"@lit-labs/observers": "2.0.6",
|
||||
@@ -61,6 +61,7 @@
|
||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/mwc-base": "0.27.0",
|
||||
"@material/mwc-button": "0.27.0",
|
||||
"@material/mwc-checkbox": "0.27.0",
|
||||
"@material/mwc-dialog": "0.27.0",
|
||||
"@material/mwc-drawer": "0.27.0",
|
||||
@@ -231,7 +232,7 @@
|
||||
"lit-html": "3.3.1",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "2.1.1",
|
||||
"@fullcalendar/daygrid": "6.1.18",
|
||||
"@fullcalendar/daygrid": "6.1.19",
|
||||
"globals": "16.3.0",
|
||||
"tslib": "2.8.1",
|
||||
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch",
|
||||
|
@@ -397,7 +397,7 @@ export class HaChartBase extends LitElement {
|
||||
...axis.axisPointer,
|
||||
status: "show",
|
||||
handle: {
|
||||
color: style.getPropertyValue("primary-color"),
|
||||
color: style.getPropertyValue("--primary-color"),
|
||||
margin: 0,
|
||||
size: 20,
|
||||
...axis.axisPointer?.handle,
|
||||
|
@@ -1,82 +0,0 @@
|
||||
import ButtonGroup from "@awesome.me/webawesome/dist/components/button-group/button-group";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { HaButton } from "./ha-button";
|
||||
import { StateSet } from "../resources/polyfills/stateset";
|
||||
|
||||
export type Appearance = "accent" | "filled" | "outlined" | "plain";
|
||||
|
||||
/**
|
||||
* Finds an ha-button element either as the current element or within its descendants.
|
||||
* @param el - The HTML element to search from
|
||||
* @returns The found HaButton element, or null if not found
|
||||
*/
|
||||
function findButton(el: HTMLElement) {
|
||||
const selector = "ha-button";
|
||||
return (el.closest(selector) ?? el.querySelector(selector)) as HaButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* @element ha-button-group
|
||||
* @extends {ButtonGroup}
|
||||
* @summary
|
||||
* Group buttons. Extend Webawesome to be able to work with ha-button tags
|
||||
*
|
||||
* @documentation https://webawesome.com/components/button-group
|
||||
*/
|
||||
@customElement("ha-button-group") // @ts-expect-error Intentionally overriding private methods
|
||||
export class HaButtonGroup extends ButtonGroup {
|
||||
attachInternals() {
|
||||
const internals = super.attachInternals();
|
||||
Object.defineProperty(internals, "states", {
|
||||
value: new StateSet(this, internals.states),
|
||||
});
|
||||
return internals;
|
||||
}
|
||||
|
||||
// @ts-expect-error updateClassNames is used in super class
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
private override updateClassNames() {
|
||||
const slottedElements = [
|
||||
...this.defaultSlot.assignedElements({ flatten: true }),
|
||||
] as HTMLElement[];
|
||||
this.hasOutlined = false;
|
||||
|
||||
slottedElements.forEach((el) => {
|
||||
const index = slottedElements.indexOf(el);
|
||||
const button = findButton(el);
|
||||
|
||||
if (button) {
|
||||
if ((button as HaButton).appearance === "outlined")
|
||||
this.hasOutlined = true;
|
||||
if (this.size) button.setAttribute("size", this.size);
|
||||
button.classList.add("wa-button-group__button");
|
||||
button.classList.toggle(
|
||||
"wa-button-group__horizontal",
|
||||
this.orientation === "horizontal"
|
||||
);
|
||||
button.classList.toggle(
|
||||
"wa-button-group__vertical",
|
||||
this.orientation === "vertical"
|
||||
);
|
||||
button.classList.toggle("wa-button-group__button-first", index === 0);
|
||||
button.classList.toggle(
|
||||
"wa-button-group__button-inner",
|
||||
index > 0 && index < slottedElements.length - 1
|
||||
);
|
||||
button.classList.toggle(
|
||||
"wa-button-group__button-last",
|
||||
index === slottedElements.length - 1
|
||||
);
|
||||
|
||||
// use button-group variant
|
||||
button.setAttribute("variant", this.variant);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-button-group": HaButtonGroup;
|
||||
}
|
||||
}
|
@@ -1,72 +1,141 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import type { Button } from "@material/mwc-button/mwc-button";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, queryAll } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { ToggleButton } from "../types";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-button";
|
||||
import "./ha-button-group";
|
||||
import "./ha-icon-button";
|
||||
|
||||
/**
|
||||
* @element ha-button-toggle-group
|
||||
*
|
||||
* @summary
|
||||
* A button-group with one active selection.
|
||||
*
|
||||
* @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 {("brand"|"neutral"|"success"|"warning"|"danger")} variant - The variant of the buttons in the group.
|
||||
*
|
||||
* @fires value-changed - Dispatched when the active button changes.
|
||||
*/
|
||||
@customElement("ha-button-toggle-group")
|
||||
export class HaButtonToggleGroup extends LitElement {
|
||||
@property({ attribute: false }) public buttons!: ToggleButton[];
|
||||
|
||||
@property() public active?: string;
|
||||
|
||||
@property({ reflect: true }) size: "small" | "medium" = "medium";
|
||||
@property({ attribute: "full-width", type: Boolean })
|
||||
public fullWidth = false;
|
||||
|
||||
@property() public variant:
|
||||
| "brand"
|
||||
| "neutral"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "danger" = "brand";
|
||||
@property({ type: Boolean }) public dense = false;
|
||||
|
||||
@queryAll("mwc-button") private _buttons?: Button[];
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-button-group .variant=${this.variant} .size=${this.size}>
|
||||
${this.buttons.map(
|
||||
(button) =>
|
||||
html`<ha-button
|
||||
class="icon"
|
||||
.value=${button.value}
|
||||
@click=${this._handleClick}
|
||||
.title=${button.label}
|
||||
.appearance=${this.active === button.value ? "accent" : "filled"}
|
||||
>
|
||||
${button.iconPath
|
||||
? html`<ha-svg-icon
|
||||
aria-label=${button.label}
|
||||
.path=${button.iconPath}
|
||||
></ha-svg-icon>`
|
||||
: button.label}
|
||||
</ha-button>`
|
||||
<div>
|
||||
${this.buttons.map((button) =>
|
||||
button.iconPath
|
||||
? html`<ha-icon-button
|
||||
.label=${button.label}
|
||||
.path=${button.iconPath}
|
||||
.value=${button.value}
|
||||
?active=${this.active === button.value}
|
||||
@click=${this._handleClick}
|
||||
></ha-icon-button>`
|
||||
: html`<mwc-button
|
||||
style=${styleMap({
|
||||
width: this.fullWidth
|
||||
? `${100 / this.buttons.length}%`
|
||||
: "initial",
|
||||
})}
|
||||
outlined
|
||||
.dense=${this.dense}
|
||||
.value=${button.value}
|
||||
?active=${this.active === button.value}
|
||||
@click=${this._handleClick}
|
||||
>${button.label}</mwc-button
|
||||
>`
|
||||
)}
|
||||
</ha-button-group>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated() {
|
||||
// Work around Safari default margin that is not reset in mwc-button as of aug 2021
|
||||
this._buttons?.forEach(async (button) => {
|
||||
await button.updateComplete;
|
||||
(
|
||||
button.shadowRoot!.querySelector("button") as HTMLButtonElement
|
||||
).style.margin = "0";
|
||||
});
|
||||
}
|
||||
|
||||
private _handleClick(ev): void {
|
||||
this.active = ev.currentTarget.value;
|
||||
fireEvent(this, "value-changed", { value: this.active });
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
div {
|
||||
display: flex;
|
||||
--mdc-icon-button-size: var(--button-toggle-size, 36px);
|
||||
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
||||
direction: ltr;
|
||||
}
|
||||
mwc-button {
|
||||
flex: 1;
|
||||
--mdc-shape-small: 0;
|
||||
--mdc-button-outline-width: 1px 0 1px 1px;
|
||||
--mdc-button-outline-color: var(--primary-color);
|
||||
}
|
||||
ha-icon-button {
|
||||
border: 1px solid var(--primary-color);
|
||||
border-right-width: 0px;
|
||||
}
|
||||
ha-icon-button,
|
||||
mwc-button {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-icon-button::before,
|
||||
mwc-button::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
transition:
|
||||
opacity 15ms linear,
|
||||
background-color 15ms linear;
|
||||
}
|
||||
ha-icon-button[active]::before,
|
||||
mwc-button[active]::before {
|
||||
opacity: 1;
|
||||
}
|
||||
ha-icon-button[active] {
|
||||
--icon-primary-color: var(--text-primary-color);
|
||||
}
|
||||
mwc-button[active] {
|
||||
--mdc-theme-primary: var(--text-primary-color);
|
||||
}
|
||||
ha-icon-button:first-child,
|
||||
mwc-button:first-child {
|
||||
--mdc-shape-small: 4px 0 0 4px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
--mdc-button-outline-width: 1px;
|
||||
}
|
||||
mwc-button:first-child::before {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
ha-icon-button:last-child,
|
||||
mwc-button:last-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-right-width: 1px;
|
||||
--mdc-shape-small: 0 4px 4px 0;
|
||||
--mdc-button-outline-width: 1px;
|
||||
}
|
||||
mwc-button:last-child::before {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
ha-icon-button:only-child,
|
||||
mwc-button:only-child {
|
||||
--mdc-shape-small: 4px;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ export type Appearance = "accent" | "filled" | "outlined" | "plain";
|
||||
* @attr {boolean} loading - shows a loading indicator instead of the buttons label and disable buttons click.
|
||||
* @attr {boolean} disabled - Disables the button and prevents user interaction.
|
||||
*/
|
||||
@customElement("ha-button") // @ts-expect-error Intentionally overriding private methods
|
||||
@customElement("ha-button")
|
||||
export class HaButton extends Button {
|
||||
variant: "brand" | "neutral" | "success" | "warning" | "danger" = "brand";
|
||||
|
||||
@@ -47,42 +47,6 @@ export class HaButton extends Button {
|
||||
return internals;
|
||||
}
|
||||
|
||||
// @ts-expect-error handleLabelSlotChange is used in super class
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
private override handleLabelSlotChange() {
|
||||
const nodes = this.labelSlot.assignedNodes({ flatten: true });
|
||||
let hasIconLabel = false;
|
||||
let hasIcon = false;
|
||||
let text = "";
|
||||
|
||||
// If there's only an icon and no text, it's an icon button
|
||||
[...nodes].forEach((node) => {
|
||||
if (
|
||||
node.nodeType === Node.ELEMENT_NODE &&
|
||||
(node as HTMLElement).localName === "ha-svg-icon"
|
||||
) {
|
||||
hasIcon = true;
|
||||
if (!hasIconLabel)
|
||||
hasIconLabel = (node as HTMLElement).hasAttribute("aria-label");
|
||||
}
|
||||
|
||||
// Concatenate text nodes
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
text += node.textContent;
|
||||
}
|
||||
});
|
||||
|
||||
this.isIconButton = text.trim() === "" && hasIcon;
|
||||
|
||||
if (this.isIconButton && !hasIconLabel) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
'Icon buttons must have a label for screen readers. Add <ha-svg-icon label="..."> to remove this warning.',
|
||||
this
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
Button.styles,
|
||||
@@ -216,11 +180,6 @@ export class HaButton extends Button {
|
||||
color: var(--wa-color-on-normal);
|
||||
}
|
||||
}
|
||||
:host([appearance~="filled"]) .button {
|
||||
color: var(--wa-color-on-normal);
|
||||
background-color: var(--wa-color-fill-normal);
|
||||
border-color: transparent;
|
||||
}
|
||||
:host([appearance~="filled"])
|
||||
.button:not(.disabled):not(.loading):active {
|
||||
background-color: var(--button-color-fill-normal-active);
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import Spinner from "@shoelace-style/shoelace/dist/components/spinner/spinner.component";
|
||||
import spinnerStyles from "@shoelace-style/shoelace/dist/components/spinner/spinner.styles";
|
||||
import type { PropertyValues } from "lit";
|
||||
import Spinner from "@awesome.me/webawesome/dist/components/spinner/spinner";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
import { StateSet } from "../resources/polyfills/stateset";
|
||||
|
||||
@customElement("ha-spinner")
|
||||
export class HaSpinner extends Spinner {
|
||||
@property() public size?: "tiny" | "small" | "medium" | "large";
|
||||
@@ -32,21 +33,31 @@ export class HaSpinner extends Spinner {
|
||||
}
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
spinnerStyles,
|
||||
css`
|
||||
:host {
|
||||
--indicator-color: var(
|
||||
--ha-spinner-indicator-color,
|
||||
var(--primary-color)
|
||||
);
|
||||
--track-color: var(--ha-spinner-divider-color, var(--divider-color));
|
||||
--track-width: 4px;
|
||||
--speed: 3.5s;
|
||||
font-size: var(--ha-spinner-size, 48px);
|
||||
}
|
||||
`,
|
||||
];
|
||||
attachInternals() {
|
||||
const internals = super.attachInternals();
|
||||
Object.defineProperty(internals, "states", {
|
||||
value: new StateSet(this, internals.states),
|
||||
});
|
||||
return internals;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
Spinner.styles,
|
||||
css`
|
||||
:host {
|
||||
--indicator-color: var(
|
||||
--ha-spinner-indicator-color,
|
||||
var(--primary-color)
|
||||
);
|
||||
--track-color: var(--ha-spinner-divider-color, var(--divider-color));
|
||||
--track-width: 4px;
|
||||
--speed: 3.5s;
|
||||
font-size: var(--ha-spinner-size, 48px);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -211,6 +211,7 @@ export class HaYamlEditor extends LitElement {
|
||||
}
|
||||
ha-code-editor {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -249,15 +249,17 @@ class MoreInfoUpdate extends LitElement {
|
||||
<hr />
|
||||
${this._markdownLoading ? this._renderLoader() : nothing}
|
||||
`
|
||||
: html`
|
||||
<hr />
|
||||
<ha-markdown
|
||||
@content-resize=${this._markdownLoaded}
|
||||
.content=${this._releaseNotes}
|
||||
class=${this._markdownLoading ? "hidden" : ""}
|
||||
></ha-markdown>
|
||||
${this._markdownLoading ? this._renderLoader() : nothing}
|
||||
`
|
||||
: this._releaseNotes
|
||||
? html`
|
||||
<hr />
|
||||
<ha-markdown
|
||||
@content-resize=${this._markdownLoaded}
|
||||
.content=${this._releaseNotes}
|
||||
class=${this._markdownLoading ? "hidden" : ""}
|
||||
></ha-markdown>
|
||||
${this._markdownLoading ? this._renderLoader() : nothing}
|
||||
`
|
||||
: nothing
|
||||
: this.stateObj.attributes.release_summary
|
||||
? html`
|
||||
<hr />
|
||||
|
@@ -385,22 +385,30 @@ export class HAFullCalendar extends LitElement {
|
||||
if (!this._viewButtons) {
|
||||
this._viewButtons = [
|
||||
{
|
||||
label: localize("ui.components.calendar.views.dayGridMonth"),
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.card.calendar.views.dayGridMonth"
|
||||
),
|
||||
value: "dayGridMonth",
|
||||
iconPath: mdiViewModule,
|
||||
},
|
||||
{
|
||||
label: localize("ui.components.calendar.views.dayGridWeek"),
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.card.calendar.views.dayGridWeek"
|
||||
),
|
||||
value: "dayGridWeek",
|
||||
iconPath: mdiViewWeek,
|
||||
},
|
||||
{
|
||||
label: localize("ui.components.calendar.views.dayGridDay"),
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.card.calendar.views.dayGridDay"
|
||||
),
|
||||
value: "dayGridDay",
|
||||
iconPath: mdiViewDay,
|
||||
},
|
||||
{
|
||||
label: localize("ui.components.calendar.views.listWeek"),
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.card.calendar.views.listWeek"
|
||||
),
|
||||
value: "listWeek",
|
||||
iconPath: mdiViewAgenda,
|
||||
},
|
||||
@@ -485,6 +493,10 @@ export class HAFullCalendar extends LitElement {
|
||||
--mdc-icon-button-size: 32px;
|
||||
}
|
||||
|
||||
ha-button-toggle-group {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
ha-fab {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
|
@@ -7,6 +7,7 @@ import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-combo-box";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-fade-in";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-password-field";
|
||||
import "../../../components/ha-spinner";
|
||||
@@ -82,7 +83,7 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params || !this._domains) {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
const selectedDomainName = this._params.selectedDomain
|
||||
@@ -101,144 +102,159 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
)
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert> `
|
||||
: ""}
|
||||
${this._params.selectedDomain && !this._description
|
||||
? html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.missing_credentials",
|
||||
{
|
||||
integration: selectedDomainName,
|
||||
}
|
||||
)}
|
||||
${this._manifest?.is_built_in || this._manifest?.documentation
|
||||
? html`<a
|
||||
href=${this._manifest.is_built_in
|
||||
? documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${this._domain}`
|
||||
)
|
||||
: this._manifest.documentation}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${!this._config
|
||||
? html`<ha-fade-in .delay=${500}>
|
||||
<ha-spinner size="large"></ha-spinner>
|
||||
</ha-fade-in>`
|
||||
: html`<div>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error"
|
||||
>${this._error}</ha-alert
|
||||
> `
|
||||
: nothing}
|
||||
${this._params.selectedDomain && !this._description
|
||||
? html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.missing_credentials_domain_link",
|
||||
"ui.panel.config.application_credentials.editor.missing_credentials",
|
||||
{
|
||||
integration: selectedDomainName,
|
||||
}
|
||||
)}
|
||||
<ha-svg-icon .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</a>`
|
||||
: ""}
|
||||
</p>`
|
||||
: ""}
|
||||
${!this._params.selectedDomain || !this._description
|
||||
? html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.description"
|
||||
)}
|
||||
<a
|
||||
href=${documentationUrl(
|
||||
this.hass!,
|
||||
"/integrations/application_credentials"
|
||||
${this._manifest?.is_built_in ||
|
||||
this._manifest?.documentation
|
||||
? html`<a
|
||||
href=${this._manifest.is_built_in
|
||||
? documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${this._domain}`
|
||||
)
|
||||
: this._manifest.documentation}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.missing_credentials_domain_link",
|
||||
{
|
||||
integration: selectedDomainName,
|
||||
}
|
||||
)}
|
||||
<ha-svg-icon .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</a>`
|
||||
: nothing}
|
||||
</p>`
|
||||
: nothing}
|
||||
${!this._params.selectedDomain || !this._description
|
||||
? html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.description"
|
||||
)}
|
||||
<a
|
||||
href=${documentationUrl(
|
||||
this.hass!,
|
||||
"/integrations/application_credentials"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.application_credentials.editor.view_documentation"
|
||||
)}
|
||||
<ha-svg-icon .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</a>
|
||||
</p>`
|
||||
: nothing}
|
||||
${this._params.selectedDomain
|
||||
? nothing
|
||||
: html`<ha-combo-box
|
||||
name="domain"
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.domain"
|
||||
)}
|
||||
.value=${this._domain}
|
||||
.items=${this._domains}
|
||||
item-id-path="id"
|
||||
item-value-path="id"
|
||||
item-label-path="name"
|
||||
required
|
||||
@value-changed=${this._handleDomainPicked}
|
||||
></ha-combo-box>`}
|
||||
${this._description
|
||||
? html`<ha-markdown
|
||||
breaks
|
||||
.content=${this._description}
|
||||
></ha-markdown>`
|
||||
: nothing}
|
||||
<ha-textfield
|
||||
class="name"
|
||||
name="name"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.name"
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.application_credentials.editor.view_documentation"
|
||||
.value=${this._name}
|
||||
required
|
||||
@input=${this._handleValueChanged}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.common.error_required"
|
||||
)}
|
||||
<ha-svg-icon .path=${mdiOpenInNew}></ha-svg-icon>
|
||||
</a>
|
||||
</p>`
|
||||
: ""}
|
||||
${this._params.selectedDomain
|
||||
? ""
|
||||
: html`<ha-combo-box
|
||||
name="domain"
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.domain"
|
||||
)}
|
||||
.value=${this._domain}
|
||||
.items=${this._domains}
|
||||
item-id-path="id"
|
||||
item-value-path="id"
|
||||
item-label-path="name"
|
||||
required
|
||||
@value-changed=${this._handleDomainPicked}
|
||||
></ha-combo-box>`}
|
||||
${this._description
|
||||
? html`<ha-markdown
|
||||
breaks
|
||||
.content=${this._description}
|
||||
></ha-markdown>`
|
||||
: ""}
|
||||
<ha-textfield
|
||||
class="name"
|
||||
name="name"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.name"
|
||||
)}
|
||||
.value=${this._name}
|
||||
required
|
||||
@input=${this._handleValueChanged}
|
||||
.validationMessage=${this.hass.localize("ui.common.error_required")}
|
||||
dialogInitialFocus
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
class="clientId"
|
||||
name="clientId"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.client_id"
|
||||
)}
|
||||
.value=${this._clientId}
|
||||
required
|
||||
@input=${this._handleValueChanged}
|
||||
.validationMessage=${this.hass.localize("ui.common.error_required")}
|
||||
dialogInitialFocus
|
||||
.helper=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.client_id_helper"
|
||||
)}
|
||||
helperPersistent
|
||||
></ha-textfield>
|
||||
<ha-password-field
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.client_secret"
|
||||
)}
|
||||
name="clientSecret"
|
||||
.value=${this._clientSecret}
|
||||
required
|
||||
@input=${this._handleValueChanged}
|
||||
.validationMessage=${this.hass.localize("ui.common.error_required")}
|
||||
.helper=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.client_secret_helper"
|
||||
)}
|
||||
helperPersistent
|
||||
></ha-password-field>
|
||||
</div>
|
||||
dialogInitialFocus
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
class="clientId"
|
||||
name="clientId"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.client_id"
|
||||
)}
|
||||
.value=${this._clientId}
|
||||
required
|
||||
@input=${this._handleValueChanged}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.common.error_required"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
.helper=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.client_id_helper"
|
||||
)}
|
||||
helperPersistent
|
||||
></ha-textfield>
|
||||
<ha-password-field
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.client_secret"
|
||||
)}
|
||||
name="clientSecret"
|
||||
.value=${this._clientSecret}
|
||||
required
|
||||
@input=${this._handleValueChanged}
|
||||
.validationMessage=${this.hass.localize(
|
||||
"ui.common.error_required"
|
||||
)}
|
||||
.helper=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.client_secret_helper"
|
||||
)}
|
||||
helperPersistent
|
||||
></ha-password-field>
|
||||
</div>
|
||||
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this._abortDialog}
|
||||
.disabled=${this._loading}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
.disabled=${!this._domain || !this._clientId || !this._clientSecret}
|
||||
@click=${this._addApplicationCredential}
|
||||
.loading=${this._loading}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.add"
|
||||
)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="secondaryAction"
|
||||
@click=${this._abortDialog}
|
||||
.disabled=${this._loading}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
.disabled=${!this._domain ||
|
||||
!this._clientId ||
|
||||
!this._clientSecret}
|
||||
@click=${this._addApplicationCredential}
|
||||
.loading=${this._loading}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.add"
|
||||
)}
|
||||
</ha-button>`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
@@ -341,6 +357,11 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
ha-markdown {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-fade-in {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiHelpCircle, mdiStarFourPoints } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
|
@@ -0,0 +1,225 @@
|
||||
import { mdiStop, mdiValveClosed, mdiValveOpen } from "@mdi/js";
|
||||
import { html, LitElement, nothing, css } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import {
|
||||
canClose,
|
||||
canOpen,
|
||||
canStop,
|
||||
ValveEntityFeature,
|
||||
type ValveEntity,
|
||||
} from "../../../data/valve";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature } from "../types";
|
||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
||||
import type {
|
||||
ValveOpenCloseCardFeatureConfig,
|
||||
LovelaceCardFeatureContext,
|
||||
} from "./types";
|
||||
import "../../../components/ha-control-switch";
|
||||
|
||||
export const supportsValveOpenCloseCardFeature = (
|
||||
hass: HomeAssistant,
|
||||
context: LovelaceCardFeatureContext
|
||||
) => {
|
||||
const stateObj = context.entity_id
|
||||
? hass.states[context.entity_id]
|
||||
: undefined;
|
||||
if (!stateObj) return false;
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
return (
|
||||
domain === "valve" &&
|
||||
(supportsFeature(stateObj, ValveEntityFeature.OPEN) ||
|
||||
supportsFeature(stateObj, ValveEntityFeature.CLOSE))
|
||||
);
|
||||
};
|
||||
|
||||
@customElement("hui-valve-open-close-card-feature")
|
||||
class HuiValveOpenCloseCardFeature
|
||||
extends LitElement
|
||||
implements LovelaceCardFeature
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@state() private _config?: ValveOpenCloseCardFeatureConfig;
|
||||
|
||||
private get _stateObj() {
|
||||
if (!this.hass || !this.context || !this.context.entity_id) {
|
||||
return undefined;
|
||||
}
|
||||
return this.hass.states[this.context.entity_id!] as ValveEntity | undefined;
|
||||
}
|
||||
|
||||
static getStubConfig(): ValveOpenCloseCardFeatureConfig {
|
||||
return {
|
||||
type: "valve-open-close",
|
||||
};
|
||||
}
|
||||
|
||||
public setConfig(config: ValveOpenCloseCardFeatureConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
private _onOpenValve(): void {
|
||||
this.hass!.callService("valve", "open_valve", {
|
||||
entity_id: this._stateObj!.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _onCloseValve(): void {
|
||||
this.hass!.callService("valve", "close_valve", {
|
||||
entity_id: this._stateObj!.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _onOpenTap(ev): void {
|
||||
ev.stopPropagation();
|
||||
this._onOpenValve();
|
||||
}
|
||||
|
||||
private _onCloseTap(ev): void {
|
||||
ev.stopPropagation();
|
||||
this._onCloseValve();
|
||||
}
|
||||
|
||||
private _onStopTap(ev): void {
|
||||
ev.stopPropagation();
|
||||
this.hass!.callService("valve", "stop_valve", {
|
||||
entity_id: this._stateObj!.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev): void {
|
||||
ev.stopPropagation();
|
||||
const checked = ev.target.checked as boolean;
|
||||
|
||||
if (checked) {
|
||||
this._onOpenValve();
|
||||
} else {
|
||||
this._onCloseValve();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this.context ||
|
||||
!this._stateObj ||
|
||||
!supportsValveOpenCloseCardFeature(this.hass, this.context)
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
// Determine colors and active states for toggle-style UI
|
||||
const openColor = stateColorCss(this._stateObj, "open");
|
||||
const closedColor = stateColorCss(this._stateObj, "closed");
|
||||
const openIcon = mdiValveOpen;
|
||||
const closedIcon = mdiValveClosed;
|
||||
|
||||
const isOpen =
|
||||
this._stateObj.state === "open" ||
|
||||
this._stateObj.state === "closing" ||
|
||||
this._stateObj.state === "opening";
|
||||
const isClosed = this._stateObj.state === "closed";
|
||||
|
||||
if (
|
||||
this._stateObj.attributes.assumed_state ||
|
||||
this._stateObj.state === UNKNOWN
|
||||
) {
|
||||
return html`
|
||||
<ha-control-button-group>
|
||||
${supportsFeature(this._stateObj, ValveEntityFeature.CLOSE)
|
||||
? html`
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.valve.close_valve")}
|
||||
@click=${this._onCloseTap}
|
||||
.disabled=${!canClose(this._stateObj)}
|
||||
class=${classMap({
|
||||
active: isClosed,
|
||||
})}
|
||||
style=${styleMap({
|
||||
"--color": closedColor,
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiValveClosed}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`
|
||||
: nothing}
|
||||
${supportsFeature(this._stateObj, ValveEntityFeature.STOP)
|
||||
? html`
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.valve.stop_valve")}
|
||||
@click=${this._onStopTap}
|
||||
.disabled=${!canStop(this._stateObj)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`
|
||||
: nothing}
|
||||
${supportsFeature(this._stateObj, ValveEntityFeature.OPEN)
|
||||
? html`
|
||||
<ha-control-button
|
||||
.label=${this.hass.localize("ui.card.valve.open_valve")}
|
||||
@click=${this._onOpenTap}
|
||||
.disabled=${!canOpen(this._stateObj)}
|
||||
class=${classMap({
|
||||
active: isOpen,
|
||||
})}
|
||||
style=${styleMap({
|
||||
"--color": openColor,
|
||||
})}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiValveOpen}></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-control-button-group>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-control-switch
|
||||
.pathOn=${openIcon}
|
||||
.pathOff=${closedIcon}
|
||||
.checked=${isOpen}
|
||||
@change=${this._valueChanged}
|
||||
.label=${this.hass.localize("ui.card.common.toggle")}
|
||||
.disabled=${this._stateObj.state === UNAVAILABLE}
|
||||
>
|
||||
</ha-control-switch>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
cardFeatureStyles,
|
||||
css`
|
||||
ha-control-button.active {
|
||||
--control-button-icon-color: white;
|
||||
--control-button-background-color: var(--color);
|
||||
--control-button-background-opacity: 1;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-valve-open-close-card-feature": HuiValveOpenCloseCardFeature;
|
||||
}
|
||||
}
|
@@ -153,6 +153,10 @@ export interface VacuumCommandsCardFeatureConfig {
|
||||
commands?: VacuumCommand[];
|
||||
}
|
||||
|
||||
export interface ValveOpenCloseCardFeatureConfig {
|
||||
type: "valve-open-close";
|
||||
}
|
||||
|
||||
export const LAWN_MOWER_COMMANDS = ["start_pause", "dock"] as const;
|
||||
|
||||
export type LawnMowerCommand = (typeof LAWN_MOWER_COMMANDS)[number];
|
||||
@@ -223,6 +227,7 @@ export type LovelaceCardFeatureConfig =
|
||||
| ToggleCardFeatureConfig
|
||||
| UpdateActionsCardFeatureConfig
|
||||
| VacuumCommandsCardFeatureConfig
|
||||
| ValveOpenCloseCardFeatureConfig
|
||||
| WaterHeaterOperationModesCardFeatureConfig
|
||||
| AreaControlsCardFeatureConfig;
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { EnergyData } from "../../../../data/energy";
|
||||
import {
|
||||
computeConsumptionData,
|
||||
energySourcesByType,
|
||||
getEnergyDataCollection,
|
||||
getSummedData,
|
||||
@@ -92,6 +93,10 @@ class HuiEnergySankeyCard
|
||||
const prefs = this._data.prefs;
|
||||
const types = energySourcesByType(prefs);
|
||||
const { summedData, compareSummedData: _ } = getSummedData(this._data);
|
||||
const { consumption, compareConsumption: __ } = computeConsumptionData(
|
||||
summedData,
|
||||
undefined
|
||||
);
|
||||
|
||||
const computedStyle = getComputedStyle(this);
|
||||
|
||||
@@ -103,12 +108,60 @@ class HuiEnergySankeyCard
|
||||
label: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.home"
|
||||
),
|
||||
value: 0,
|
||||
value: Math.max(0, consumption.total.used_total),
|
||||
color: computedStyle.getPropertyValue("--primary-color"),
|
||||
index: 1,
|
||||
};
|
||||
nodes.push(homeNode);
|
||||
|
||||
if (types.battery) {
|
||||
const totalBatteryOut = summedData.total.from_battery ?? 0;
|
||||
const totalBatteryIn = summedData.total.to_battery ?? 0;
|
||||
|
||||
// Add battery source
|
||||
nodes.push({
|
||||
id: "battery",
|
||||
label: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.battery"
|
||||
),
|
||||
value: totalBatteryOut,
|
||||
tooltip: `${formatNumber(totalBatteryOut, this.hass.locale)} kWh`,
|
||||
color: computedStyle.getPropertyValue("--energy-battery-out-color"),
|
||||
index: 0,
|
||||
});
|
||||
links.push({
|
||||
source: "battery",
|
||||
target: "home",
|
||||
value: consumption.total.used_battery,
|
||||
});
|
||||
|
||||
// Add battery sink
|
||||
nodes.push({
|
||||
id: "battery_in",
|
||||
label: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.battery"
|
||||
),
|
||||
value: totalBatteryIn,
|
||||
tooltip: `${formatNumber(totalBatteryIn, this.hass.locale)} kWh`,
|
||||
color: computedStyle.getPropertyValue("--energy-battery-in-color"),
|
||||
index: 1,
|
||||
});
|
||||
if (consumption.total.grid_to_battery > 0) {
|
||||
links.push({
|
||||
source: "grid",
|
||||
target: "battery_in",
|
||||
value: consumption.total.grid_to_battery,
|
||||
});
|
||||
}
|
||||
if (consumption.total.solar_to_battery > 0) {
|
||||
links.push({
|
||||
source: "solar",
|
||||
target: "battery_in",
|
||||
value: consumption.total.solar_to_battery,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (types.grid) {
|
||||
const totalFromGrid = summedData.total.from_grid ?? 0;
|
||||
|
||||
@@ -128,6 +181,7 @@ class HuiEnergySankeyCard
|
||||
links.push({
|
||||
source: "grid",
|
||||
target: "home",
|
||||
value: consumption.total.used_grid,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -149,57 +203,7 @@ class HuiEnergySankeyCard
|
||||
links.push({
|
||||
source: "solar",
|
||||
target: "home",
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate total home consumption from all producers
|
||||
homeNode.value = nodes
|
||||
.filter((node) => node.index === 0)
|
||||
.reduce((sum, node) => sum + (node.value || 0), 0);
|
||||
|
||||
if (types.battery) {
|
||||
// Add battery source
|
||||
const totalBatteryOut = summedData.total.from_battery ?? 0;
|
||||
const totalBatteryIn = summedData.total.to_battery ?? 0;
|
||||
const netBattery = totalBatteryOut - totalBatteryIn;
|
||||
const netBatteryOut = Math.max(netBattery, 0);
|
||||
const netBatteryIn = Math.max(-netBattery, 0);
|
||||
homeNode.value += netBattery;
|
||||
|
||||
nodes.push({
|
||||
id: "battery",
|
||||
label: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.battery"
|
||||
),
|
||||
value: netBatteryOut,
|
||||
tooltip: `${formatNumber(netBatteryOut, this.hass.locale)} kWh`,
|
||||
color: computedStyle.getPropertyValue("--energy-battery-out-color"),
|
||||
index: 0,
|
||||
});
|
||||
links.push({
|
||||
source: "battery",
|
||||
target: "home",
|
||||
});
|
||||
|
||||
// Add battery sink
|
||||
nodes.push({
|
||||
id: "battery_in",
|
||||
label: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.battery"
|
||||
),
|
||||
value: netBatteryIn,
|
||||
tooltip: `${formatNumber(netBatteryIn, this.hass.locale)} kWh`,
|
||||
color: computedStyle.getPropertyValue("--energy-battery-in-color"),
|
||||
index: 1,
|
||||
});
|
||||
nodes.forEach((node) => {
|
||||
// Link all sources to battery_in
|
||||
if (node.index === 0) {
|
||||
links.push({
|
||||
source: node.id,
|
||||
target: "battery_in",
|
||||
});
|
||||
}
|
||||
value: consumption.total.used_solar,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -217,17 +221,20 @@ class HuiEnergySankeyCard
|
||||
color: computedStyle.getPropertyValue("--energy-grid-return-color"),
|
||||
index: 1,
|
||||
});
|
||||
nodes.forEach((node) => {
|
||||
// Link all non-grid sources to grid_return
|
||||
if (node.index === 0 && node.id !== "grid") {
|
||||
links.push({
|
||||
source: node.id,
|
||||
target: "grid_return",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
homeNode.value -= totalToGrid;
|
||||
if (consumption.total.battery_to_grid > 0) {
|
||||
links.push({
|
||||
source: "battery",
|
||||
target: "grid",
|
||||
value: consumption.total.battery_to_grid,
|
||||
});
|
||||
}
|
||||
if (consumption.total.solar_to_grid > 0) {
|
||||
links.push({
|
||||
source: "solar",
|
||||
target: "grid_return",
|
||||
value: consumption.total.solar_to_grid,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let untrackedConsumption = homeNode.value;
|
||||
@@ -370,9 +377,6 @@ class HuiEnergySankeyCard
|
||||
target: "untracked",
|
||||
value: untrackedConsumption,
|
||||
});
|
||||
} else if (untrackedConsumption < 0) {
|
||||
// if untracked consumption is negative, then the sources are not enough
|
||||
homeNode.value -= untrackedConsumption;
|
||||
}
|
||||
homeNode.tooltip = `${formatNumber(homeNode.value, this.hass.locale)} kWh`;
|
||||
|
||||
|
@@ -28,6 +28,7 @@ import "../card-features/hui-target-temperature-card-feature";
|
||||
import "../card-features/hui-toggle-card-feature";
|
||||
import "../card-features/hui-update-actions-card-feature";
|
||||
import "../card-features/hui-vacuum-commands-card-feature";
|
||||
import "../card-features/hui-valve-open-close-card-feature";
|
||||
import "../card-features/hui-water-heater-operation-modes-card-feature";
|
||||
import "../card-features/hui-area-controls-card-feature";
|
||||
|
||||
@@ -69,6 +70,7 @@ const TYPES = new Set<LovelaceCardFeatureConfig["type"]>([
|
||||
"toggle",
|
||||
"update-actions",
|
||||
"vacuum-commands",
|
||||
"valve-open-close",
|
||||
"water-heater-operation-modes",
|
||||
]);
|
||||
|
||||
|
@@ -48,6 +48,7 @@ import { supportsTargetTemperatureCardFeature } from "../../card-features/hui-ta
|
||||
import { supportsToggleCardFeature } from "../../card-features/hui-toggle-card-feature";
|
||||
import { supportsUpdateActionsCardFeature } from "../../card-features/hui-update-actions-card-feature";
|
||||
import { supportsVacuumCommandsCardFeature } from "../../card-features/hui-vacuum-commands-card-feature";
|
||||
import { supportsValveOpenCloseCardFeature } from "../../card-features/hui-valve-open-close-card-feature";
|
||||
import { supportsWaterHeaterOperationModesCardFeature } from "../../card-features/hui-water-heater-operation-modes-card-feature";
|
||||
import type {
|
||||
LovelaceCardFeatureConfig,
|
||||
@@ -94,6 +95,7 @@ const UI_FEATURE_TYPES = [
|
||||
"toggle",
|
||||
"update-actions",
|
||||
"vacuum-commands",
|
||||
"valve-open-close",
|
||||
"water-heater-operation-modes",
|
||||
] as const satisfies readonly FeatureType[];
|
||||
|
||||
@@ -155,6 +157,7 @@ const SUPPORTS_FEATURE_TYPES: Record<
|
||||
toggle: supportsToggleCardFeature,
|
||||
"update-actions": supportsUpdateActionsCardFeature,
|
||||
"vacuum-commands": supportsVacuumCommandsCardFeature,
|
||||
"valve-open-close": supportsValveOpenCloseCardFeature,
|
||||
"water-heater-operation-modes": supportsWaterHeaterOperationModesCardFeature,
|
||||
};
|
||||
|
||||
|
@@ -150,6 +150,10 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
font-size: var(--ha-font-size-l);
|
||||
}
|
||||
|
||||
ha-code-editor {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.save-button {
|
||||
opacity: 0;
|
||||
font-size: var(--ha-font-size-m);
|
||||
|
@@ -1168,12 +1168,6 @@
|
||||
},
|
||||
"summary": "Summary",
|
||||
"description": "Description"
|
||||
},
|
||||
"views": {
|
||||
"dayGridMonth": "[%key:ui::panel::lovelace::editor::card::calendar::views::dayGridMonth%]",
|
||||
"dayGridWeek": "[%key:ui::panel::lovelace::editor::card::calendar::views::dayGridWeek%]",
|
||||
"dayGridDay": "[%key:ui::panel::lovelace::editor::card::calendar::views::dayGridDay%]",
|
||||
"listWeek": "[%key:ui::panel::lovelace::editor::card::calendar::views::listWeek%]"
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
@@ -7891,6 +7885,9 @@
|
||||
"return_home": "[%key:ui::dialogs::more_info_control::vacuum::return_home%]"
|
||||
}
|
||||
},
|
||||
"valve-open-close": {
|
||||
"label": "Valve open/close"
|
||||
},
|
||||
"climate-fan-modes": {
|
||||
"label": "Climate fan modes",
|
||||
"style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]",
|
||||
|
75
yarn.lock
75
yarn.lock
@@ -1848,60 +1848,60 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fullcalendar/core@npm:6.1.18":
|
||||
version: 6.1.18
|
||||
resolution: "@fullcalendar/core@npm:6.1.18"
|
||||
"@fullcalendar/core@npm:6.1.19":
|
||||
version: 6.1.19
|
||||
resolution: "@fullcalendar/core@npm:6.1.19"
|
||||
dependencies:
|
||||
preact: "npm:~10.12.1"
|
||||
checksum: 10/0e29608599f1b4f42c0e6381a29739a1aafb226e37f085ba0405ee806c2c1417fa3dc013b375749e1a284eb94e4949b9573990816145874cc10aaaaacc7d8756
|
||||
checksum: 10/5584da8eb2196085f7bd1a7cf37e2654d8960dd14e0eceddc8a41029b4c19fbcdb993cc517d31587991c095611bddc9d29468be6fb73fd53a92c4e6bfe935bac
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fullcalendar/daygrid@npm:6.1.18":
|
||||
version: 6.1.18
|
||||
resolution: "@fullcalendar/daygrid@npm:6.1.18"
|
||||
"@fullcalendar/daygrid@npm:6.1.19":
|
||||
version: 6.1.19
|
||||
resolution: "@fullcalendar/daygrid@npm:6.1.19"
|
||||
peerDependencies:
|
||||
"@fullcalendar/core": ~6.1.18
|
||||
checksum: 10/a91dc05445b7ad9210fb964d0e3bd378c74660a9f4d32ca3b28d84a79060795335ca8fd3b01277513e912eaeac37156d50976df281abf78a8ec5c79b5c93952e
|
||||
"@fullcalendar/core": ~6.1.19
|
||||
checksum: 10/1d1f15685e53fe73713ed523a497d9d5c8660d20e08c50a0c4bf040902144c9e571f536932a6b5c9ccc60c00fe2127879963bf05fbda736eaced439ddc06a1b3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fullcalendar/interaction@npm:6.1.18":
|
||||
version: 6.1.18
|
||||
resolution: "@fullcalendar/interaction@npm:6.1.18"
|
||||
"@fullcalendar/interaction@npm:6.1.19":
|
||||
version: 6.1.19
|
||||
resolution: "@fullcalendar/interaction@npm:6.1.19"
|
||||
peerDependencies:
|
||||
"@fullcalendar/core": ~6.1.18
|
||||
checksum: 10/6958abef8a3a677c10fb8900f744019c35a7afe62dd0f031f9cb91812303cd2b3bbca6eb1443573a4d9e37a25f64472668e38247ffbed980416245867c8689f8
|
||||
"@fullcalendar/core": ~6.1.19
|
||||
checksum: 10/a9df02f92301548036603a291a03fe3383baab4b17eeb4db59f89cb18a605c70fb6710f3fcfb9391e899ca76eea384a6686928bd1d848e503c1dc8558c0b688e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fullcalendar/list@npm:6.1.18":
|
||||
version: 6.1.18
|
||||
resolution: "@fullcalendar/list@npm:6.1.18"
|
||||
"@fullcalendar/list@npm:6.1.19":
|
||||
version: 6.1.19
|
||||
resolution: "@fullcalendar/list@npm:6.1.19"
|
||||
peerDependencies:
|
||||
"@fullcalendar/core": ~6.1.18
|
||||
checksum: 10/5d352a3b2311d9dda84a72ef5f3c990ede7f57f64ae9374629db8139bac26584748600b7af7686d39e7e88d10c16bfc60208fa9dd5727c8e343c6652e81a9ac0
|
||||
"@fullcalendar/core": ~6.1.19
|
||||
checksum: 10/c988707056122f55b8f20fda0f2b26d82e2ae96feb257e7537a7579aaed1ea600ee043ba11c4f5945a3d8ab231083978b5f89a8e2ee86d3ce61e65fdd126aca3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fullcalendar/luxon3@npm:6.1.18":
|
||||
version: 6.1.18
|
||||
resolution: "@fullcalendar/luxon3@npm:6.1.18"
|
||||
"@fullcalendar/luxon3@npm:6.1.19":
|
||||
version: 6.1.19
|
||||
resolution: "@fullcalendar/luxon3@npm:6.1.19"
|
||||
peerDependencies:
|
||||
"@fullcalendar/core": ~6.1.18
|
||||
"@fullcalendar/core": ~6.1.19
|
||||
luxon: ^3.0.0
|
||||
checksum: 10/e7426e5dd4386977adf1469e5f52178e5e6b87d7c1931e8b928699db7c9a759c4e564971614f01d8463896e2c31767f2f104d7b3a0a74c08ca568396afeac2bd
|
||||
checksum: 10/af834fc8bc2528be07baf3a1c15f2a6e09a662044545ad6838e305ff0098c85866ba83770912ea1d1a1db86b97ca7cd9f93d2517f6f4fb5914d711bd5c2e1078
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fullcalendar/timegrid@npm:6.1.18":
|
||||
version: 6.1.18
|
||||
resolution: "@fullcalendar/timegrid@npm:6.1.18"
|
||||
"@fullcalendar/timegrid@npm:6.1.19":
|
||||
version: 6.1.19
|
||||
resolution: "@fullcalendar/timegrid@npm:6.1.19"
|
||||
dependencies:
|
||||
"@fullcalendar/daygrid": "npm:~6.1.18"
|
||||
"@fullcalendar/daygrid": "npm:~6.1.19"
|
||||
peerDependencies:
|
||||
"@fullcalendar/core": ~6.1.18
|
||||
checksum: 10/6eff8fff54fd5452fc47430bd52c28e70ee5eb58780de4a98671e290911ad0c265fa763452272c65e2bd304d7ac4aecb92d3c526b265f222ebb60274a366199f
|
||||
"@fullcalendar/core": ~6.1.19
|
||||
checksum: 10/8d8e1b7253528c592d1ce7ffa4c76f61480c725ffee55b9b2ec3bba156bf386250f27e3b6fff66073005e2c413efecc22fa97f1ec0bc762e4ae880633e641cb2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2666,7 +2666,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@material/mwc-button@npm:^0.27.0":
|
||||
"@material/mwc-button@npm:0.27.0, @material/mwc-button@npm:^0.27.0":
|
||||
version: 0.27.0
|
||||
resolution: "@material/mwc-button@npm:0.27.0"
|
||||
dependencies:
|
||||
@@ -9366,12 +9366,12 @@ __metadata:
|
||||
"@formatjs/intl-numberformat": "npm:8.15.4"
|
||||
"@formatjs/intl-pluralrules": "npm:5.4.4"
|
||||
"@formatjs/intl-relativetimeformat": "npm:11.4.11"
|
||||
"@fullcalendar/core": "npm:6.1.18"
|
||||
"@fullcalendar/daygrid": "npm:6.1.18"
|
||||
"@fullcalendar/interaction": "npm:6.1.18"
|
||||
"@fullcalendar/list": "npm:6.1.18"
|
||||
"@fullcalendar/luxon3": "npm:6.1.18"
|
||||
"@fullcalendar/timegrid": "npm:6.1.18"
|
||||
"@fullcalendar/core": "npm:6.1.19"
|
||||
"@fullcalendar/daygrid": "npm:6.1.19"
|
||||
"@fullcalendar/interaction": "npm:6.1.19"
|
||||
"@fullcalendar/list": "npm:6.1.19"
|
||||
"@fullcalendar/luxon3": "npm:6.1.19"
|
||||
"@fullcalendar/timegrid": "npm:6.1.19"
|
||||
"@lezer/highlight": "npm:1.2.1"
|
||||
"@lit-labs/motion": "npm:1.0.9"
|
||||
"@lit-labs/observers": "npm:2.0.6"
|
||||
@@ -9382,6 +9382,7 @@ __metadata:
|
||||
"@material/chips": "npm:=14.0.0-canary.53b3cad2f.0"
|
||||
"@material/data-table": "npm:=14.0.0-canary.53b3cad2f.0"
|
||||
"@material/mwc-base": "npm:0.27.0"
|
||||
"@material/mwc-button": "npm:0.27.0"
|
||||
"@material/mwc-checkbox": "npm:0.27.0"
|
||||
"@material/mwc-dialog": "npm:0.27.0"
|
||||
"@material/mwc-drawer": "npm:0.27.0"
|
||||
|
Reference in New Issue
Block a user