Compare commits

..

101 Commits

Author SHA1 Message Date
Aidan Timson
66d86620de Tooltips should render within the component 2025-10-01 13:26:23 +01:00
Aidan Timson
4249035dc7 Align header to design 2025-10-01 09:14:43 +01:00
Aidan Timson
8502d073fa Add design notes 2025-09-30 16:35:05 +01:00
Aidan Timson
b0568fac8b Use medium weight 2025-09-30 16:25:00 +01:00
Aidan Timson
ed44c905e6 Passthrough header subtitle position 2025-09-30 16:03:27 +01:00
Aidan Timson
f94343c5f1 Make subtitle position above or below based on prop 2025-09-30 15:59:15 +01:00
Aidan Timson
6c2d802b83 Use query 2025-09-30 15:54:07 +01:00
Aidan Timson
c8736bca0e Make reusable 2025-09-30 15:53:28 +01:00
Aidan Timson
546b52f89a Simplify 2025-09-30 15:51:34 +01:00
Aidan Timson
7c1c0b6a4a Update comment 2025-09-30 15:46:35 +01:00
Aidan Timson
d8abb8ea7b Clean 2025-09-30 15:46:35 +01:00
Aidan Timson
699da04965 Fix scrollbar style and dialog max height 2025-09-30 15:46:35 +01:00
Aidan Timson
fe8c655cef % 2025-09-30 15:46:35 +01:00
Aidan Timson
2f9754446d Invert 2025-09-30 15:46:35 +01:00
Aidan Timson
3d241084db minmax 2025-09-30 15:46:35 +01:00
Aidan Timson
8c8910dd35 % not vh 2025-09-30 15:46:35 +01:00
Aidan Timson
21449842c3 Use padding for safe areas 2025-09-30 15:46:35 +01:00
Aidan Timson
a182d566df 95vw for desktop, 100vw - safe areas for mobile 2025-09-30 15:46:35 +01:00
Aidan Timson
5d42740d92 Revert instead of resetting 2025-09-30 15:46:35 +01:00
Aidan Timson
eaed098256 Use core color variables 2025-09-30 15:46:35 +01:00
Aidan Timson
b15262ce83 Fix z-index application 2025-09-30 15:46:35 +01:00
Aidan Timson
ad48a0c4b0 Remove 2025-09-30 15:46:35 +01:00
Aidan Timson
20ad9054ce Cleanup 2025-09-30 15:46:35 +01:00
Aidan Timson
788447ebe7 Docstring 2025-09-30 15:46:34 +01:00
Aidan Timson
847e8c14e0 Use ha variables 2025-09-30 15:46:34 +01:00
Aidan Timson
94e756bd79 Remove comment 2025-09-30 15:46:34 +01:00
Aidan Timson
065069f21e Update 2025-09-30 15:46:34 +01:00
Aidan Timson
c72c1e66f5 Update 2025-09-30 15:46:34 +01:00
Aidan Timson
6470ddbf5a Update 2025-09-30 15:46:34 +01:00
Aidan Timson
e736d56ff3 Add note 2025-09-30 15:46:34 +01:00
Aidan Timson
bb6d5add9a Use themeable variables 2025-09-30 15:46:34 +01:00
Aidan Timson
f401dce7c7 Remove enter key handling for initial PR 2025-09-30 15:46:34 +01:00
Aidan Timson
70a417a51a Set on disconnected 2025-09-30 15:46:34 +01:00
Aidan Timson
213b53b596 Use query, simplify 2025-09-30 15:46:34 +01:00
Aidan Timson
228362b836 Use fireEvent 2025-09-30 15:46:34 +01:00
Aidan Timson
0a64af26eb Update src/components/ha-wa-dialog.ts
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2025-09-30 15:46:34 +01:00
Aidan Timson
76bbd1f51c Use state for dialog width control 2025-09-30 15:46:34 +01:00
Aidan Timson
1c82e8b316 Dismissable by default 2025-09-30 15:46:34 +01:00
Aidan Timson
7f438b57b7 Upd 2025-09-30 15:46:34 +01:00
Aidan Timson
2d09edd8d2 Rename to width 2025-09-30 15:46:34 +01:00
Aidan Timson
b2bb71d7f8 Docstring 2025-09-30 15:46:34 +01:00
Aidan Timson
1e60e1fb9c No override
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2025-09-30 15:46:34 +01:00
Aidan Timson
1df53fe23d Apply suggestion
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2025-09-30 15:46:34 +01:00
Aidan Timson
3b66db5631 Update src/components/ha-wa-dialog.ts
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2025-09-30 15:46:34 +01:00
Aidan Timson
dc772ebe85 Update src/components/ha-wa-dialog.ts
Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com>
2025-09-30 15:46:34 +01:00
Aidan Timson
0337f68986 Remove dupe from dev rebase 2025-09-30 15:46:34 +01:00
Aidan Timson
77997c7382 Remove todo 2025-09-30 15:46:34 +01:00
Aidan Timson
6fc69df64b Trigger primary action on enter pressed when ha-dialog-footer is used with a primaryAction slot 2025-09-30 15:46:34 +01:00
Aidan Timson
77a0e16f4e Add dialog footer 2025-09-30 15:46:34 +01:00
Aidan Timson
717be78457 Constrain overflow 2025-09-30 15:46:34 +01:00
Aidan Timson
d8638983d7 Use scroll lock styles from webawesome 2025-09-30 15:46:34 +01:00
Aidan Timson
d47e181607 Query dialogIntiialFocus and focus the element if found 2025-09-30 15:46:34 +01:00
Aidan Timson
39a5249406 Remove extra padding 2025-09-30 15:46:34 +01:00
Aidan Timson
525cfd1929 Use var 2025-09-30 15:46:34 +01:00
Aidan Timson
4db9f2652c Transisitons and width 2025-09-30 15:46:34 +01:00
Aidan Timson
a994f21acf Animate transition 2025-09-30 15:46:34 +01:00
Aidan Timson
f7499acfb5 Sizing 2025-09-30 15:46:34 +01:00
Aidan Timson
a32c6f956b Fix 2025-09-30 15:46:34 +01:00
Aidan Timson
9b95f87f73 Fix 2025-09-30 15:46:34 +01:00
Aidan Timson
892a09ecd5 Fix 2025-09-30 15:46:34 +01:00
Aidan Timson
4671074cfd Improve 2025-09-30 15:46:34 +01:00
Aidan Timson
236d469f7d Back action 2025-09-30 15:46:34 +01:00
Aidan Timson
ec7056606b Fix footer 2025-09-30 15:46:34 +01:00
Aidan Timson
677020d4cd Clean 2025-09-30 15:46:34 +01:00
Aidan Timson
7bf7b9c52f Center title if no subtitle is used 2025-09-30 15:46:34 +01:00
Aidan Timson
a9241a8009 Simplify and use more native features 2025-09-30 15:46:34 +01:00
Aidan Timson
e84f83d5ec Restore 2025-09-30 15:46:34 +01:00
Aidan Timson
0d73d3f1f7 Rename 2025-09-30 15:46:34 +01:00
Aidan Timson
d48a08df40 Cleanup 2025-09-30 15:46:34 +01:00
Aidan Timson
e2574e99bf Cleanup 2025-09-30 15:46:34 +01:00
Aidan Timson
8a276c9b6a Match original naming for scrim click action 2025-09-30 15:46:34 +01:00
Aidan Timson
50843ba43b Safe area fix for top 2025-09-30 15:46:34 +01:00
Aidan Timson
462277b61b Handle scrolling when open 2025-09-30 15:46:34 +01:00
Aidan Timson
d8dd7fa03f Add gap to footer actions 2025-09-30 15:46:34 +01:00
Aidan Timson
9996655b21 Set 2025-09-30 15:46:34 +01:00
Aidan Timson
0344ba31c1 Rename 2025-09-30 15:46:33 +01:00
Aidan Timson
b912bfec61 Allow for custom close action 2025-09-30 15:46:33 +01:00
Aidan Timson
7e16834c22 Test with more info dialog 2025-09-30 15:46:33 +01:00
Aidan Timson
3076365819 Setup for use with header (more info etc) 2025-09-30 15:46:33 +01:00
Aidan Timson
58e623c86c Test with basic form 2025-09-30 15:46:33 +01:00
Aidan Timson
9cb6f762b6 Handle enter key 2025-09-30 15:46:33 +01:00
Aidan Timson
93d8f7f87b More closely follow MD3 guidelines 2025-09-30 15:46:33 +01:00
Aidan Timson
9ad0c6000e Remove border 2025-09-30 15:46:33 +01:00
Aidan Timson
e7c3895745 Use custom close button and closure actions 2025-09-30 15:46:33 +01:00
Aidan Timson
5f93224055 Square when mobile 2025-09-30 15:46:33 +01:00
Aidan Timson
a38811ff57 Use builtin width 2025-09-30 15:46:33 +01:00
Aidan Timson
87df38a969 Move to new component 2025-09-30 15:46:33 +01:00
Aidan Timson
e670df2c68 Restore 2025-09-30 15:46:33 +01:00
Aidan Timson
065e071cc3 Ensure dialog follows safe areas 2025-09-30 15:46:33 +01:00
Aidan Timson
d2ae3c6867 Autofocus element with dialogInitialFocus 2025-09-30 15:46:33 +01:00
Aidan Timson
405eb5f870 Setup 2025-09-30 15:46:33 +01:00
Bram Kragten
994c1b5751 Fix intl polyfill loading (#27261) 2025-09-30 16:47:12 +03:00
Aidan Timson
6823c647b6 Fix calendar card height (#27052) 2025-09-30 15:27:46 +02:00
Jan-Philipp Benecke
866b478dc0 Use local entity picture if available in media player more info (#27252) 2025-09-30 14:36:39 +02:00
renovate[bot]
d746dc5752 Pin Node.js to 22.20.0 (#27258)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-30 11:53:51 +02:00
Wendelin
5f53e1e71c Update WA to fix tab group scrolling (#27255) 2025-09-30 11:36:17 +02:00
Wendelin
3da82df093 Update node nvm to latest LTS (#27256) 2025-09-30 11:35:48 +02:00
Jan-Philipp Benecke
4cedfffb71 Let text scroll in markdown card (#27250)
* Let text overflow in markdown card

* Update src/panels/lovelace/cards/hui-markdown-card.ts

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2025-09-30 09:37:15 +02:00
dcapslock
1e1514e7da forwardHaptic on node rather than window. (#27251)
forwardHaptic on node rather than window. Allows for capturing for custom cards.
2025-09-30 07:49:32 +03:00
Jan-Philipp Benecke
60e07075bc Refactor ha-config-labels to use styleMap (#27248) 2025-09-29 21:05:17 +02:00
Jan-Philipp Benecke
c998086474 Fix --ha-space-13 spacing token (#27246) 2025-09-29 20:16:29 +02:00
39 changed files with 854 additions and 387 deletions

2
.nvmrc
View File

@@ -1 +1 @@
lts/iron
22.20.0

View File

@@ -52,7 +52,7 @@
"@fullcalendar/list": "6.1.19",
"@fullcalendar/luxon3": "6.1.19",
"@fullcalendar/timegrid": "6.1.19",
"@home-assistant/webawesome": "3.0.0-beta.4.ha.3",
"@home-assistant/webawesome": "3.0.0-beta.6.ha.0",
"@lezer/highlight": "1.2.1",
"@lit-labs/motion": "1.0.9",
"@lit-labs/observers": "2.0.6",

View File

@@ -0,0 +1,7 @@
/**
* Check if a slot has any assigned content
* @param slot - The HTMLSlotElement to check
* @returns true if the slot has any assigned nodes
*/
export const hasSlotContent = (slot: HTMLSlotElement | null): boolean =>
slot ? slot.assignedNodes({ flatten: true }).length > 0 : false;

View File

@@ -586,15 +586,19 @@ export class HaChartBase extends LitElement {
const isMobile = window.matchMedia(
"all and (max-width: 450px), all and (max-height: 500px)"
).matches;
if (isMobile && options.tooltip) {
// mobile charts are full width so we need to confine the tooltip to the chart
if (options.tooltip) {
const tooltips = Array.isArray(options.tooltip)
? options.tooltip
: [options.tooltip];
tooltips.forEach((tooltip) => {
tooltip.confine = true;
tooltip.appendTo = undefined;
tooltip.triggerOn = "click";
// Tooltips should render within the component, not in the body of the html
tooltip.appendTo = () =>
this.renderRoot.querySelector(".chart-container") as HTMLElement;
// Mobile charts are full width so we need to confine the tooltip to the chart
if (isMobile) {
tooltip.confine = true;
tooltip.triggerOn = "click";
}
});
options.tooltip = tooltips;
}

View File

@@ -112,7 +112,7 @@ export class HaEntityToggle extends LitElement {
if (!this.hass || !this.stateObj) {
return;
}
forwardHaptic("light");
forwardHaptic(this, "light");
const stateDomain = computeStateDomain(this.stateObj);
let serviceDomain;
let service;

View File

@@ -0,0 +1,52 @@
import { css, html, LitElement } from "lit";
import { customElement } from "lit/decorators";
/**
* Home Assistant dialog footer component
*
* @element ha-dialog-footer
* @extends {LitElement}
*
* @summary
* A simple footer container for dialog actions,
* typically used as the `footer` slot in `ha-wa-dialog`.
*
* @slot primaryAction - Primary action button(s), aligned to the end.
* @slot secondaryAction - Secondary action button(s), placed before the primary action.
*
* @remarks
* **Button Styling Guidance:**
* - `primaryAction` slot: Use `variant="accent"`
* - `secondaryAction` slot: Use `variant="plain"`
*/
@customElement("ha-dialog-footer")
export class HaDialogFooter extends LitElement {
protected render() {
return html`
<footer class="footer">
<slot name="secondaryAction"></slot>
<slot name="primaryAction"></slot>
</footer>
`;
}
static get styles() {
return [
css`
.footer {
display: flex;
gap: 12px;
justify-content: flex-end;
align-items: center;
width: 100%;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-dialog-footer": HaDialogFooter;
}
}

View File

@@ -1,22 +1,47 @@
import { css, html, LitElement } from "lit";
import { customElement } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { hasSlotContent } from "../common/dom/has-slot-content";
@customElement("ha-dialog-header")
export class HaDialogHeader extends LitElement {
@property({ type: String, attribute: "subtitle-position" })
public subtitlePosition: "above" | "below" = "below";
@state()
private _hasSubtitle = false;
@query('slot[name="subtitle"]')
private _subtitleSlot!: HTMLSlotElement;
private _checkSubtitleContent() {
this._hasSubtitle = hasSlotContent(this._subtitleSlot);
}
protected render() {
const titleSlot = html`<div class="header-title">
<slot name="title"></slot>
</div>`;
const subtitleSlot = html`<div class="header-subtitle">
<slot name="subtitle" @slotchange=${this._checkSubtitleContent}></slot>
</div>`;
return html`
<header class="header">
<div class="header-bar">
<div
class=${classMap({
"header-bar": true,
"no-subtitle": !this._hasSubtitle,
})}
>
<section class="header-navigation-icon">
<slot name="navigationIcon"></slot>
</section>
<section class="header-content">
<div class="header-title">
<slot name="title"></slot>
</div>
<div class="header-subtitle">
<slot name="subtitle"></slot>
</div>
${this.subtitlePosition === "above"
? html`${subtitleSlot}${titleSlot}`
: html`${titleSlot}${subtitleSlot}`}
</section>
<section class="header-action-items">
<slot name="actionItems"></slot>
@@ -32,6 +57,7 @@ export class HaDialogHeader extends LitElement {
css`
:host {
display: block;
min-height: 48px;
}
:host([show-border]) {
border-bottom: 1px solid
@@ -44,6 +70,9 @@ export class HaDialogHeader extends LitElement {
padding: 4px;
box-sizing: border-box;
}
.header-bar.no-subtitle {
align-items: center;
}
.header-content {
flex: 1;
padding: 10px 4px;
@@ -55,11 +84,11 @@ export class HaDialogHeader extends LitElement {
.header-title {
font-size: var(--ha-font-size-xl);
line-height: var(--ha-line-height-condensed);
font-weight: var(--ha-font-weight-normal);
font-weight: var(--ha-font-weight-medium);
}
.header-subtitle {
font-size: var(--ha-font-size-m);
line-height: 20px;
line-height: var(--ha-line-height-normal);
color: var(--secondary-text-color);
}
@media all and (min-width: 450px) and (min-height: 500px) {

View File

@@ -100,6 +100,8 @@ export class HaDialog extends DialogBase {
}
.mdc-dialog__container {
align-items: var(--vertical-align-dialog, center);
padding-top: var(--safe-area-inset-top);
padding-bottom: var(--safe-area-inset-bottom);
}
.mdc-dialog__title {
padding: 16px 16px 0 16px;
@@ -121,7 +123,11 @@ export class HaDialog extends DialogBase {
position: var(--dialog-surface-position, relative);
top: var(--dialog-surface-top);
margin-top: var(--dialog-surface-margin-top);
min-width: var(--mdc-dialog-min-width, 100vw);
min-width: calc(
var(--mdc-dialog-min-width, 100vw) - var(
--safe-area-inset-left
) - var(--safe-area-inset-right)
);
min-height: var(--mdc-dialog-min-height, auto);
border-radius: var(--ha-dialog-border-radius, 24px);
-webkit-backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none);
@@ -138,12 +144,18 @@ export class HaDialog extends DialogBase {
@media all and (max-width: 450px), all and (max-height: 500px) {
.mdc-dialog .mdc-dialog__surface {
min-height: 100vh;
max-height: 100vh;
padding-top: var(--safe-area-inset-top);
padding-bottom: var(--safe-area-inset-bottom);
padding-left: var(--safe-area-inset-left);
padding-right: var(--safe-area-inset-right);
min-height: calc(
100vh - var(--safe-area-inset-top, 0px) - var(
--safe-area-inset-bottom,
0px
)
);
max-height: calc(
100vh - var(--safe-area-inset-top, 0px) - var(
--safe-area-inset-bottom,
0px
)
);
}
}

View File

@@ -15,7 +15,7 @@ export class HaSwitch extends SwitchBase {
super.firstUpdated();
this.addEventListener("change", () => {
if (this.haptic) {
forwardHaptic("light");
forwardHaptic(this, "light");
}
});
}

View File

@@ -0,0 +1,366 @@
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import "@home-assistant/webawesome/dist/components/dialog/dialog";
import { mdiClose } from "@mdi/js";
import "./ha-dialog-header";
import "./ha-icon-button";
import type { HomeAssistant } from "../types";
import { fireEvent } from "../common/dom/fire_event";
import { haStyleScrollbar } from "../resources/styles";
export type DialogWidth = "small" | "medium" | "large" | "full";
export type DialogWidthOnTitleClick = DialogWidth | "none";
/**
* Home Assistant dialog component
*
* @element ha-wa-dialog
* @extends {LitElement}
*
* @summary
* A stylable dialog built using the `wa-dialog` component, providing a standardized header (ha-dialog-header),
* body, and footer (preferably using `ha-dialog-footer`) with slots
*
* You can open and close the dialog declaratively by using the `data-dialog="close"` attribute.
* @see https://webawesome.com/docs/components/dialog/#opening-and-closing-dialogs-declaratively
* @see src/dialogs/more-info/ha-more-info-dialog.ts#L366
*
* @slot heading - Replace the entire header area.
* @slot navigationIcon - Leading header action (e.g. close/back button).
* @slot title - Header title. Click can toggle width if `width-on-title-click` is not "none".
* @slot subtitle - Header subtitle, shown under the title.
* @slot actionItems - Trailing header actions (e.g. buttons, menus).
* @slot - Dialog content body.
* @slot footer - Dialog footer content.
*
* @csspart dialog - The dialog surface.
* @csspart header - The header container.
* @csspart body - The scrollable body container.
* @csspart footer - The footer container.
*
* @cssprop --dialog-content-padding - Padding for the dialog content sections. Defaults to 24px.
* @cssprop --ha-dialog-show-duration - Show animation duration. Defaults to 200ms.
* @cssprop --ha-dialog-hide-duration - Hide animation duration. Defaults to 200ms.
* @cssprop --ha-dialog-surface-background - Dialog background color. Defaults to surface.
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface. Defaults to 24px.
* @cssprop --dialog-z-index - Z-index for the dialog. Defaults to 8.
* @cssprop --dialog-surface-position - CSS position of the dialog surface. Defaults to relative.
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface. Defaults to auto.
* @cssprop --ha-dialog-expand-duration - Duration for width transitions when changing width. Defaults to 200ms.
*
* @attr {boolean} open - Controls the dialog open state.
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium".
* @attr {("none"|"small"|"medium"|"large"|"full")} width-on-title-click - Target width when clicking the title. "none" disables.
* @attr {boolean} prevent-scrim-close - Prevents closing the dialog by clicking the scrim/overlay. Defaults to false.
* @attr {string} header-title - Header title text when no custom title slot is provided.
* @attr {string} header-subtitle - Header subtitle text when no custom subtitle slot is provided.
* @attr {("above"|"below")} header-subtitle-position - Position of the subtitle relative to the title. Defaults to "below".
* @attr {boolean} flexcontent - Makes the dialog body a flex container for flexible layouts.
*
* @event opened - Fired when the dialog is shown.
* @event closed - Fired after the dialog is hidden.
*/
@customElement("ha-wa-dialog")
export class HaWaDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, reflect: true })
public open = false;
@property({ type: String, reflect: true, attribute: "width" })
public width: DialogWidth = "medium";
@property({
type: String,
reflect: true,
attribute: "width-on-title-click",
})
public widthOnTitleClick: DialogWidthOnTitleClick = "none";
@property({ type: Boolean, reflect: true, attribute: "prevent-scrim-close" })
public preventScrimClose = false;
@property({ type: String, attribute: "header-title" })
public headerTitle = "";
@property({ type: String, attribute: "header-subtitle" })
public headerSubtitle = "";
@property({ type: String, attribute: "header-subtitle-position" })
public headerSubtitlePosition: "above" | "below" = "below";
@property({ type: Boolean, reflect: true, attribute: "flexcontent" })
public flexContent = false;
@state()
private _open = false;
@state()
private _sizeChanged = false;
protected updated(
changedProperties: Map<string | number | symbol, unknown>
): void {
super.updated(changedProperties);
if (changedProperties.has("open")) {
if (this.open) {
this._open = this.open;
}
}
if (changedProperties.has("width")) {
this._sizeChanged = false;
}
this.classList.toggle("size-changed", this._sizeChanged);
}
protected render() {
return html`
<wa-dialog
.open=${this._open}
.lightDismiss=${!this.preventScrimClose}
without-header
@wa-show=${this._handleShow}
@wa-after-hide=${this._handleAfterHide}
>
<slot name="heading">
<ha-dialog-header .subtitlePosition=${this.headerSubtitlePosition}>
<slot name="navigationIcon" slot="navigationIcon">
<ha-icon-button
data-dialog="close"
.label=${this.hass?.localize("ui.common.close") ?? "Close"}
.path=${mdiClose}
></ha-icon-button>
</slot>
<slot name="title" slot="title">
<span @click=${this.toggleWidth} class="title">
${this.headerTitle}
</span>
</slot>
<slot name="subtitle" slot="subtitle">
<span>${this.headerSubtitle}</span>
</slot>
<slot name="actionItems" slot="actionItems"></slot>
</ha-dialog-header>
</slot>
<div class="body ha-scrollbar">
<slot></slot>
</div>
<slot name="footer" slot="footer"></slot>
</wa-dialog>
`;
}
private _handleShow = () => {
this._open = true;
fireEvent(this, "opened");
this.updateComplete.then(() => {
const focusElement = this.querySelector(
"[dialogInitialFocus]"
) as HTMLElement;
focusElement?.focus();
});
};
private _handleAfterHide = () => {
this._open = false;
fireEvent(this, "closed");
};
public disconnectedCallback(): void {
super.disconnectedCallback();
this._open = false;
}
public toggleWidth = () => {
if (this.widthOnTitleClick === "none") {
return;
}
this._sizeChanged = !this._sizeChanged;
};
static styles = [
haStyleScrollbar,
css`
:host([scrolled]) wa-dialog::part(header) {
max-width: 100%;
border-bottom: 1px solid
var(--dialog-scroll-divider-color, var(--divider-color));
}
wa-dialog {
--full-width: min(
95vw,
calc(
100vw - var(--safe-area-inset-left, 0px) - var(
--safe-area-inset-right,
0px
)
)
);
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
--spacing: var(--dialog-content-padding, 24px);
--show-duration: var(--ha-dialog-show-duration, 200ms);
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
--ha-dialog-surface-background: var(
--card-background-color,
var(--ha-color-surface-default)
);
--wa-color-surface-raised: var(
--ha-dialog-surface-background,
var(--card-background-color, var(--ha-color-surface-default))
);
--wa-panel-border-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-3xl)
);
max-width: 100%;
}
:host([width="small"]),
:host(.size-changed[width-on-title-click="small"]) wa-dialog {
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
}
:host([width="large"]),
:host(.size-changed[width-on-title-click="large"]) wa-dialog {
--width: min(var(--ha-dialog-width-lg, 720px), var(--full-width));
}
:host([width="full"]),
:host(.size-changed[width-on-title-click="full"]) wa-dialog {
--width: var(--full-width);
}
wa-dialog::part(dialog) {
min-width: var(--width, var(--full-width));
max-width: var(--width, var(--full-width));
max-height: calc(100% - 80px);
position: var(--dialog-surface-position, relative);
margin-top: var(--dialog-surface-margin-top, auto);
transition:
min-width var(--ha-dialog-expand-duration, 200ms) ease-in-out,
max-width var(--ha-dialog-expand-duration, 200ms) ease-in-out;
display: flex;
flex-direction: column;
overflow: hidden;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
:host {
--ha-dialog-border-radius: 0px;
}
wa-dialog {
--full-width: 100vw;
}
wa-dialog::part(dialog) {
min-height: 100%;
padding-top: var(--safe-area-inset-top, 0px);
padding-bottom: var(--safe-area-inset-bottom, 0px);
padding-left: var(--safe-area-inset-left, 0px);
padding-right: var(--safe-area-inset-right, 0px);
}
}
wa-dialog::part(header) {
max-width: 100%;
overflow: hidden;
display: flex;
align-items: center;
padding: 24px 24px 16px 24px;
gap: 4px;
}
:host([has-custom-heading]) wa-dialog::part(header) {
max-width: 100%;
padding: 0;
}
wa-dialog::part(close-button),
wa-dialog::part(close-button__base) {
display: none;
}
.header-title-container {
display: flex;
align-items: center;
}
.header-title {
margin: 0;
margin-bottom: 0;
color: var(
--ha-dialog-header-title-color,
var(--ha-color-on-surface-default, var(--primary-text-color))
);
font-size: var(
--ha-dialog-header-title-font-size,
var(--ha-font-size-2xl)
);
line-height: var(
--ha-dialog-header-title-line-height,
var(--ha-line-height-condensed)
);
font-weight: var(
--ha-dialog-header-title-font-weight,
var(--ha-font-weight-normal)
);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 12px;
}
wa-dialog::part(body) {
padding: 0;
display: flex;
flex-direction: column;
max-width: 100%;
overflow: hidden;
}
.body {
position: var(--dialog-content-position, relative);
padding: 0 var(--dialog-content-padding, 24px)
var(--dialog-content-padding, 24px)
var(--dialog-content-padding, 24px);
overflow: auto;
flex-grow: 1;
}
:host([flexcontent]) .body {
max-width: 100%;
display: flex;
flex-direction: column;
}
wa-dialog::part(footer) {
padding: 0;
}
::slotted([slot="footer"]) {
display: flex;
padding: 12px 16px 16px 16px;
gap: 12px;
justify-content: flex-end;
align-items: center;
width: 100%;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-wa-dialog": HaWaDialog;
}
interface HASSDomEvents {
opened: undefined;
closed: undefined;
}
}

View File

@@ -28,6 +28,6 @@ declare global {
}
}
export const forwardHaptic = (hapticType: HapticType) => {
fireEvent(window, "haptic", hapticType);
export const forwardHaptic = (node: HTMLElement, hapticType: HapticType) => {
fireEvent(node, "haptic", hapticType);
};

View File

@@ -42,7 +42,7 @@ class MoreInfoFan extends LitElement {
private _toggle = () => {
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
forwardHaptic("light");
forwardHaptic(this, "light");
this.hass.callService("fan", service, {
entity_id: this.stateObj!.entity_id,
});

View File

@@ -310,7 +310,7 @@ class MoreInfoLight extends LitElement {
private _toggle = () => {
const service = this.stateObj?.state === "on" ? "turn_off" : "turn_on";
forwardHaptic("light");
forwardHaptic(this, "light");
this.hass.callService("light", service, {
entity_id: this.stateObj!.entity_id,
});

View File

@@ -258,7 +258,10 @@ class MoreInfoMediaPlayer extends LitElement {
const stateObj = this.stateObj;
const controls = computeMediaControls(stateObj, true);
const coverUrl = stateObj.attributes.entity_picture || "";
const coverUrl =
stateObj.attributes.entity_picture_local ||
stateObj.attributes.entity_picture ||
"";
const playerObj = new HassMediaPlayerEntity(this.hass, this.stateObj);
const position = Math.max(Math.floor(playerObj.currentProgress || 0), 0);

View File

@@ -28,8 +28,7 @@ import { getEntityEntryContext } from "../../common/entity/context/get_entity_co
import { shouldHandleRequestSelectedEvent } from "../../common/mwc/handle-request-selected-event";
import { navigate } from "../../common/navigate";
import "../../components/ha-button-menu";
import "../../components/ha-dialog";
import "../../components/ha-dialog-header";
import "../../components/ha-wa-dialog";
import "../../components/ha-icon-button";
import "../../components/ha-icon-button-prev";
import "../../components/ha-list-item";
@@ -91,8 +90,6 @@ const DEFAULT_VIEW: View = "info";
export class MoreInfoDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean, reflect: true }) public large = false;
@state() private _parentEntityIds: string[] = [];
@state() private _entityId?: string | null;
@@ -124,7 +121,6 @@ export class MoreInfoDialog extends LitElement {
this._currView = params.view || DEFAULT_VIEW;
this._initialView = params.view || DEFAULT_VIEW;
this._childView = undefined;
this.large = false;
this._loadEntityRegistryEntry();
}
@@ -350,200 +346,201 @@ export class MoreInfoDialog extends LitElement {
const title = this._childView?.viewTitle || breadcrumb.pop() || entityId;
return html`
<ha-dialog
<ha-wa-dialog
open
@closed=${this.closeDialog}
@opened=${this._handleOpened}
.escapeKeyAction=${this._isEscapeEnabled ? undefined : ""}
.heading=${title}
hideActions
.backLabel=${this.hass.localize(
"ui.dialogs.more_info_control.back_to_info"
)}
.headerTitle=${title}
.preventScrimClose=${!this._isEscapeEnabled}
width-on-title-click="full"
flexContent
>
<ha-dialog-header slot="heading">
${showCloseIcon
? html`
<ha-icon-button
slot="navigationIcon"
dialogAction="cancel"
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
`
: html`
<ha-icon-button-prev
slot="navigationIcon"
@click=${this._goBack}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.back_to_info"
)}
></ha-icon-button-prev>
`}
<span slot="title" @click=${this._enlarge} class="title">
${breadcrumb.length > 0
? !__DEMO__ && isAdmin
? html`
<button
class="breadcrumb"
@click=${this._breadcrumbClick}
aria-label=${breadcrumb.join(" > ")}
>
${join(breadcrumb, html`<ha-icon-next></ha-icon-next>`)}
</button>
`
: html`
<p class="breadcrumb">
${join(breadcrumb, html`<ha-icon-next></ha-icon-next>`)}
</p>
`
: nothing}
<p class="main">${title}</p>
</span>
${isDefaultView
? html`
${this._shouldShowHistory(domain)
? html`
<ha-icon-button
slot="actionItems"
.label=${this.hass.localize(
"ui.dialogs.more_info_control.history"
)}
.path=${mdiChartBoxOutline}
@click=${this._goToHistory}
></ha-icon-button>
`
: nothing}
${!__DEMO__ && isAdmin
? html`
<ha-icon-button
slot="actionItems"
.label=${this.hass.localize(
"ui.dialogs.more_info_control.settings"
)}
.path=${mdiCogOutline}
@click=${this._goToSettings}
></ha-icon-button>
<ha-button-menu
corner="BOTTOM_END"
menu-corner="END"
slot="actionItems"
@closed=${stopPropagation}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
${deviceId
? html`
<ha-list-item
graphic="icon"
@request-selected=${this._goToDevice}
>
${this.hass.localize(
"ui.dialogs.more_info_control.device_or_service_info",
{
type: this.hass.localize(
`ui.dialogs.more_info_control.device_type.${deviceType}`
),
}
)}
<ha-svg-icon
slot="graphic"
.path=${deviceType === "service"
? mdiTransitConnectionVariant
: mdiDevices}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
${this._shouldShowEditIcon(domain, stateObj)
? html`
<ha-list-item
graphic="icon"
@request-selected=${this._goToEdit}
>
${this.hass.localize(
"ui.dialogs.more_info_control.edit"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiPencilOutline}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
${this._entry &&
stateObj &&
domain === "light" &&
lightSupportsFavoriteColors(stateObj)
? html`
<ha-list-item
graphic="icon"
@request-selected=${this._toggleInfoEditMode}
>
${this._infoEditMode
? this.hass.localize(
`ui.dialogs.more_info_control.exit_edit_mode`
)
: this.hass.localize(
`ui.dialogs.more_info_control.${domain}.edit_mode`
)}
<ha-svg-icon
slot="graphic"
.path=${this._infoEditMode
? mdiPencilOff
: mdiPencil}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
<ha-list-item
graphic="icon"
@request-selected=${this._goToRelated}
>
${this.hass.localize(
"ui.dialogs.more_info_control.related"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
`
: nothing}
`
: isSpecificInitialView
${showCloseIcon
? html`
<ha-icon-button
slot="navigationIcon"
data-dialog="close"
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
`
: html`
<ha-icon-button-prev
slot="navigationIcon"
@click=${this._goBack}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.back_to_info"
)}
></ha-icon-button-prev>
`}
<span slot="title" @click=${this._toggleSize} class="title">
${breadcrumb.length > 0
? !__DEMO__ && isAdmin
? html`
<ha-button-menu
corner="BOTTOM_END"
menu-corner="END"
slot="actionItems"
@closed=${stopPropagation}
fixed
<button
class="breadcrumb"
@click=${this._breadcrumbClick}
aria-label=${breadcrumb.join(" > ")}
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item
graphic="icon"
@request-selected=${this._resetInitialView}
>
${this.hass.localize("ui.dialogs.more_info_control.info")}
<ha-svg-icon
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
${join(breadcrumb, html`<ha-icon-next></ha-icon-next>`)}
</button>
`
: nothing}
</ha-dialog-header>
: html`
<p class="breadcrumb">
${join(breadcrumb, html`<ha-icon-next></ha-icon-next>`)}
</p>
`
: nothing}
<p class="main">${title}</p>
</span>
${isDefaultView
? html`
${this._shouldShowHistory(domain)
? html`
<ha-icon-button
slot="actionItems"
.label=${this.hass.localize(
"ui.dialogs.more_info_control.history"
)}
.path=${mdiChartBoxOutline}
@click=${this._goToHistory}
></ha-icon-button>
`
: nothing}
${!__DEMO__ && isAdmin
? html`
<ha-icon-button
slot="actionItems"
.label=${this.hass.localize(
"ui.dialogs.more_info_control.settings"
)}
.path=${mdiCogOutline}
@click=${this._goToSettings}
></ha-icon-button>
<ha-button-menu
corner="BOTTOM_END"
menu-corner="END"
slot="actionItems"
@closed=${stopPropagation}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
${deviceId
? html`
<ha-list-item
graphic="icon"
@request-selected=${this._goToDevice}
>
${this.hass.localize(
"ui.dialogs.more_info_control.device_or_service_info",
{
type: this.hass.localize(
`ui.dialogs.more_info_control.device_type.${deviceType}`
),
}
)}
<ha-svg-icon
slot="graphic"
.path=${deviceType === "service"
? mdiTransitConnectionVariant
: mdiDevices}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
${this._shouldShowEditIcon(domain, stateObj)
? html`
<ha-list-item
graphic="icon"
@request-selected=${this._goToEdit}
>
${this.hass.localize(
"ui.dialogs.more_info_control.edit"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiPencilOutline}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
${this._entry &&
stateObj &&
domain === "light" &&
lightSupportsFavoriteColors(stateObj)
? html`
<ha-list-item
graphic="icon"
@request-selected=${this._toggleInfoEditMode}
>
${this._infoEditMode
? this.hass.localize(
`ui.dialogs.more_info_control.exit_edit_mode`
)
: this.hass.localize(
`ui.dialogs.more_info_control.${domain}.edit_mode`
)}
<ha-svg-icon
slot="graphic"
.path=${this._infoEditMode
? mdiPencilOff
: mdiPencil}
></ha-svg-icon>
</ha-list-item>
`
: nothing}
<ha-list-item
graphic="icon"
@request-selected=${this._goToRelated}
>
${this.hass.localize(
"ui.dialogs.more_info_control.related"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
`
: nothing}
`
: isSpecificInitialView
? html`
<ha-button-menu
corner="BOTTOM_END"
menu-corner="END"
slot="actionItems"
@closed=${stopPropagation}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item
graphic="icon"
@request-selected=${this._resetInitialView}
>
${this.hass.localize("ui.dialogs.more_info_control.info")}
<ha-svg-icon
slot="graphic"
.path=${mdiInformationOutline}
></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
`
: nothing}
${keyed(
this._entityId,
html`
@@ -608,7 +605,7 @@ export class MoreInfoDialog extends LitElement {
</div>
`
)}
</ha-dialog>
</ha-wa-dialog>
`;
}
@@ -630,8 +627,8 @@ export class MoreInfoDialog extends LitElement {
this._entry = ev.detail;
}
private _enlarge() {
this.large = !this.large;
private _toggleSize() {
this.shadowRoot?.querySelector("ha-wa-dialog")?.toggleWidth();
}
private _handleOpened() {
@@ -664,7 +661,7 @@ export class MoreInfoDialog extends LitElement {
return [
haStyleDialog,
css`
ha-dialog {
ha-wa-dialog {
/* Set the top top of the dialog to a fixed position, so it doesnt jump when the content changes size */
--vertical-align-dialog: flex-start;
--dialog-surface-margin-top: max(
@@ -693,34 +690,21 @@ export class MoreInfoDialog extends LitElement {
}
@media all and (max-width: 450px), all and (max-height: 500px) {
/* When in fullscreen dialog should be attached to top */
ha-dialog {
--dialog-surface-margin-top: 0px;
ha-wa-dialog {
--dialog-surface-margin-top: initial;
}
}
@media all and (min-width: 600px) and (min-height: 501px) {
ha-dialog {
--mdc-dialog-min-width: 580px;
--mdc-dialog-max-width: 580px;
--mdc-dialog-max-height: calc(100% - 72px);
}
.main-title {
cursor: default;
}
:host([large]) ha-dialog {
--mdc-dialog-min-width: 90vw;
--mdc-dialog-max-width: 90vw;
}
}
.title {
display: flex;
flex-direction: column;
align-items: flex-start;
margin: 0 0 -10px 0;
}
.title p {

View File

@@ -5,8 +5,9 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDeviceNameDisplay } from "../../../../common/entity/compute_device_name";
import "../../../../components/ha-alert";
import "../../../../components/ha-area-picker";
import "../../../../components/ha-dialog";
import "../../../../components/ha-button";
import "../../../../components/ha-dialog-footer";
import "../../../../components/ha-wa-dialog";
import "../../../../components/ha-labels-picker";
import type { HaSwitch } from "../../../../components/ha-switch";
import "../../../../components/ha-textfield";
@@ -57,10 +58,11 @@ class DialogDeviceRegistryDetail extends LitElement {
}
const device = this._params.device;
return html`
<ha-dialog
<ha-wa-dialog
open
@closed=${this.closeDialog}
.heading=${computeDeviceNameDisplay(device, this.hass)}
.headerTitle=${computeDeviceNameDisplay(device, this.hass)}
.preventScrimClose=${true}
>
<div>
${this._error
@@ -131,22 +133,24 @@ class DialogDeviceRegistryDetail extends LitElement {
</div>
</div>
</div>
<ha-button
slot="secondaryAction"
@click=${this.closeDialog}
.disabled=${this._submitting}
appearance="plain"
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button
slot="primaryAction"
@click=${this._updateEntry}
.disabled=${this._submitting}
>
${this.hass.localize("ui.dialogs.device-registry-detail.update")}
</ha-button>
</ha-dialog>
<ha-dialog-footer slot="footer">
<ha-button
slot="secondaryAction"
appearance="plain"
data-dialog="close"
.disabled=${this._submitting}
>
${this.hass.localize("ui.common.cancel")}
</ha-button>
<ha-button
slot="primaryAction"
.disabled=${this._submitting}
@click=${this._updateEntry}
>
${this.hass.localize("ui.dialogs.device-registry-detail.update")}
</ha-button>
</ha-dialog-footer>
</ha-wa-dialog>
`;
}

View File

@@ -210,10 +210,10 @@ export class ZHAClusterAttributes extends LitElement {
this._readingAttribute = true;
try {
this._attributeValue = await readAttributeValue(this.hass, data);
forwardHaptic("success");
forwardHaptic(this, "success");
button.actionSuccess();
} catch (_err: any) {
forwardHaptic("failure");
forwardHaptic(this, "failure");
button.actionError();
} finally {
this._readingAttribute = false;

View File

@@ -1,7 +1,6 @@
import {
mdiDelete,
mdiDevices,
mdiDotsVertical,
mdiHelpCircle,
mdiPlus,
mdiRobot,
@@ -9,8 +8,9 @@ import {
} from "@mdi/js";
import type { PropertyValues } from "lit";
import { LitElement, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { styleMap } from "lit/directives/style-map";
import { computeCssColor } from "../../../common/color/compute-color";
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { storage } from "../../../common/decorators/storage";
@@ -23,6 +23,7 @@ import type {
} from "../../../components/data-table/ha-data-table";
import "../../../components/ha-fab";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-relative-time";
import type {
LabelRegistryEntry,
@@ -42,7 +43,6 @@ import "../../../layouts/hass-tabs-subpage-data-table";
import type { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config";
import { showLabelDetailDialog } from "./show-dialog-label-detail";
import type { HaMdMenu } from "../../../components/ha-md-menu";
@customElement("ha-config-labels")
export class HaConfigLabels extends LitElement {
@@ -86,10 +86,6 @@ export class HaConfigLabels extends LitElement {
})
private _activeHiddenColumns?: string[];
@query("#overflow-menu") private _overflowMenu!: HaMdMenu;
private _overflowLabel!: LabelRegistryEntry;
private _columns = memoizeOne((localize: LocalizeFunc, narrow: boolean) => {
const columns: DataTableColumnContainer<LabelRegistryEntry> = {
icon: {
@@ -109,13 +105,14 @@ export class HaConfigLabels extends LitElement {
template: (label) =>
label.color
? html`<div
style="
background-color: ${computeCssColor(label.color)};
border-radius: 10px;
border: 1px solid var(--outline-color);
box-sizing: border-box;
width: 20px;
height: 20px;"
style=${styleMap({
backgroundColor: computeCssColor(label.color),
borderRadius: "10px",
border: "1px solid var(--outline-color)",
boxSizing: "border-box",
width: "20px",
height: "20px",
})}
></div>`
: nothing,
},
@@ -176,12 +173,34 @@ export class HaConfigLabels extends LitElement {
hideable: false,
type: "overflow-menu",
template: (label) => html`
<ha-icon-button
.selected=${label}
.label=${this.hass.localize("ui.common.overflow_menu")}
.path=${mdiDotsVertical}
@click=${this._showOverflowMenu}
></ha-icon-button>
<ha-icon-overflow-menu
.hass=${this.hass}
narrow
.items=${[
{
label: this.hass.localize("ui.panel.config.entities.caption"),
path: mdiShape,
action: () => this._navigateEntities(label),
},
{
label: this.hass.localize("ui.panel.config.devices.caption"),
path: mdiDevices,
action: () => this._navigateDevices(label),
},
{
label: this.hass.localize("ui.panel.config.automation.caption"),
path: mdiRobot,
action: () => this._navigateAutomations(label),
},
{
label: this.hass.localize("ui.common.delete"),
path: mdiDelete,
action: () => this._removeLabel(label),
warning: true,
},
]}
>
</ha-icon-overflow-menu>
`,
},
};
@@ -195,19 +214,6 @@ export class HaConfigLabels extends LitElement {
}))
);
private _showOverflowMenu = (ev) => {
if (
this._overflowMenu.open &&
ev.target === this._overflowMenu.anchorElement
) {
this._overflowMenu.close();
return;
}
this._overflowLabel = ev.target.selected;
this._overflowMenu.anchorElement = ev.target;
this._overflowMenu.show();
};
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
this._fetchLabels();
@@ -251,32 +257,6 @@ export class HaConfigLabels extends LitElement {
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab>
</hass-tabs-subpage-data-table>
<ha-md-menu id="overflow-menu" positioning="fixed">
<ha-md-menu-item .clickAction=${this._navigateEntities}>
<ha-svg-icon slot="start" .path=${mdiShape}></ha-svg-icon>
${this.hass.localize("ui.panel.config.entities.caption")}
</ha-md-menu-item>
<ha-md-menu-item .clickAction=${this._navigateDevices}>
<ha-svg-icon slot="start" .path=${mdiDevices}></ha-svg-icon>
${this.hass.localize("ui.panel.config.devices.caption")}
</ha-md-menu-item>
<ha-md-menu-item .clickAction=${this._navigateAutomations}>
<ha-svg-icon slot="start" .path=${mdiRobot}></ha-svg-icon>
${this.hass.localize("ui.panel.config.automation.caption")}
</ha-md-menu-item>
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
<ha-md-menu-item
class="warning"
.clickAction=${this._handleRemoveLabelClick}
>
<ha-svg-icon
slot="start"
class="warning"
.path=${mdiDelete}
></ha-svg-icon>
${this.hass.localize("ui.common.delete")}
</ha-md-menu-item>
</ha-md-menu>
`;
}
@@ -337,10 +317,6 @@ export class HaConfigLabels extends LitElement {
return updated;
}
private _handleRemoveLabelClick = () => {
this._removeLabel(this._overflowLabel);
};
private async _removeLabel(selectedLabel: LabelRegistryEntry) {
if (
!(await showConfirmationDialog(this, {
@@ -368,23 +344,19 @@ export class HaConfigLabels extends LitElement {
}
}
private _navigateEntities = () => {
navigate(
`/config/entities?historyBack=1&label=${this._overflowLabel.label_id}`
);
};
private _navigateEntities(label: LabelRegistryEntry) {
navigate(`/config/entities?historyBack=1&label=${label.label_id}`);
}
private _navigateDevices = () => {
navigate(
`/config/devices/dashboard?historyBack=1&label=${this._overflowLabel.label_id}`
);
};
private _navigateDevices(label: LabelRegistryEntry) {
navigate(`/config/devices/dashboard?historyBack=1&label=${label.label_id}`);
}
private _navigateAutomations = () => {
private _navigateAutomations(label: LabelRegistryEntry) {
navigate(
`/config/automation/dashboard?historyBack=1&label=${this._overflowLabel.label_id}`
`/config/automation/dashboard?historyBack=1&label=${label.label_id}`
);
};
}
private _handleSortingChanged(ev: CustomEvent) {
this._activeSorting = ev.detail;

View File

@@ -1087,7 +1087,7 @@ ${rejected
name: computeStateName(scene),
}),
});
forwardHaptic("light");
forwardHaptic(this, "light");
};
private _deleteConfirm(scene: SceneEntity): void {

View File

@@ -443,7 +443,7 @@ class HaPanelDevAction extends LitElement {
const button = ev.currentTarget as HaProgressButton;
if (this._yamlMode && !this._yamlValid) {
forwardHaptic("failure");
forwardHaptic(this, "failure");
button.actionError();
this._error = this.hass.localize(
"ui.panel.developer-tools.tabs.actions.errors.yaml.invalid_yaml"
@@ -465,7 +465,7 @@ class HaPanelDevAction extends LitElement {
);
if (this._error !== undefined) {
forwardHaptic("failure");
forwardHaptic(this, "failure");
button.actionError();
return;
}
@@ -534,7 +534,7 @@ class HaPanelDevAction extends LitElement {
) {
return;
}
forwardHaptic("failure");
forwardHaptic(this, "failure");
button.actionError();
let localizedErrorMessage: string | undefined;

View File

@@ -177,7 +177,7 @@ class HuiAreaControlsCardFeature
.map((entityId) => this.hass!.states[entityId] as HassEntity | undefined)
.filter((v): v is HassEntity => Boolean(v));
forwardHaptic("light");
forwardHaptic(this, "light");
toggleGroupEntities(this.hass, entities);
}

View File

@@ -69,7 +69,7 @@ class HuiLockCommandsCardFeature
if (!this.hass || !this._stateObj || !service) {
return;
}
forwardHaptic("light");
forwardHaptic(this, "light");
callProtectedLockService(this, this.hass, this._stateObj, service);
}

View File

@@ -112,7 +112,7 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
if (!this.hass || !this._stateObj) {
return;
}
forwardHaptic("light");
forwardHaptic(this, "light");
const stateDomain = computeDomain(this._stateObj.entity_id);
const serviceDomain = stateDomain;
const service = turnOn ? "turn_on" : "turn_off";

View File

@@ -1,5 +1,6 @@
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
import { getColorByIndex } from "../../../common/color/colors";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
@@ -16,7 +17,11 @@ import type {
import "../../calendar/ha-full-calendar";
import { findEntities } from "../common/find-entities";
import "../components/hui-warning";
import type { LovelaceCard, LovelaceCardEditor } from "../types";
import type {
LovelaceCard,
LovelaceCardEditor,
LovelaceGridOptions,
} from "../types";
import type { CalendarCardConfig } from "./types";
@customElement("hui-calendar-card")
@@ -48,6 +53,8 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public layout?: string;
@state() private _events: CalendarEvent[] = [];
@state() private _config?: CalendarCardConfig;
@@ -88,7 +95,16 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
}
public getCardSize(): number {
return this._config?.header ? 1 : 0 + 11;
return 12;
}
public getGridOptions(): LovelaceGridOptions {
return {
rows: 6,
columns: 12,
min_columns: 4,
min_rows: 4,
};
}
public connectedCallback(): void {
@@ -118,6 +134,10 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
<ha-card>
<div class="header">${this._config.title}</div>
<ha-full-calendar
class=${classMap({
"is-grid": this.layout === "grid",
"is-panel": this.layout === "panel",
})}
.narrow=${this._narrow}
.events=${this._events}
.hass=${this.hass}
@@ -224,6 +244,11 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
ha-full-calendar {
--calendar-height: 400px;
}
ha-full-calendar.is-grid,
ha-full-calendar.is-panel {
height: calc(100% - 16px);
}
`;
}

View File

@@ -231,6 +231,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
static styles = css`
ha-card {
height: 100%;
overflow-y: auto;
}
ha-alert {
margin-bottom: 8px;

View File

@@ -53,7 +53,7 @@ export const handleAction = async (
(e) => e.user === hass!.user?.id
))
) {
forwardHaptic("warning");
forwardHaptic(node, "warning");
let serviceName;
if (
@@ -107,7 +107,7 @@ export const handleAction = async (
"ui.panel.lovelace.cards.actions.no_entity_more_info"
),
});
forwardHaptic("failure");
forwardHaptic(node, "failure");
}
break;
}
@@ -122,7 +122,7 @@ export const handleAction = async (
"ui.panel.lovelace.cards.actions.no_navigation_path"
),
});
forwardHaptic("failure");
forwardHaptic(node, "failure");
}
break;
case "url": {
@@ -132,21 +132,21 @@ export const handleAction = async (
showToast(node, {
message: hass.localize("ui.panel.lovelace.cards.actions.no_url"),
});
forwardHaptic("failure");
forwardHaptic(node, "failure");
}
break;
}
case "toggle": {
if (config.entity) {
toggleEntity(hass, config.entity!);
forwardHaptic("light");
forwardHaptic(node, "light");
} else {
showToast(node, {
message: hass.localize(
"ui.panel.lovelace.cards.actions.no_entity_toggle"
),
});
forwardHaptic("failure");
forwardHaptic(node, "failure");
}
break;
}
@@ -156,7 +156,7 @@ export const handleAction = async (
showToast(node, {
message: hass.localize("ui.panel.lovelace.cards.actions.no_action"),
});
forwardHaptic("failure");
forwardHaptic(node, "failure");
return;
}
const [domain, service] = (actionConfig.perform_action ||
@@ -167,7 +167,7 @@ export const handleAction = async (
actionConfig.data ?? actionConfig.service_data,
actionConfig.target
);
forwardHaptic("light");
forwardHaptic(node, "light");
break;
}
case "assist": {

View File

@@ -58,7 +58,7 @@ class HuiEntitiesToggle extends LitElement {
`;
private _callService(ev: MouseEvent): void {
forwardHaptic("light");
forwardHaptic(this, "light");
const turnOn = (ev.target as HaSwitch).checked;
turnOnOffEntities(this.hass!, this._toggleEntities!, turnOn!);
}

View File

@@ -103,7 +103,7 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow {
return;
}
forwardHaptic("light");
forwardHaptic(this, "light");
setInputSelectOption(this.hass!, stateObj.entity_id, option);
}

View File

@@ -104,7 +104,7 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
return;
}
forwardHaptic("light");
forwardHaptic(this, "light");
setSelectOption(this.hass!, stateObj.entity_id, option);
}

View File

@@ -39,7 +39,7 @@ class HaSetVibrateRow extends LitElement {
fireEvent(this, "hass-vibrate", {
vibrate,
});
forwardHaptic("light");
forwardHaptic(this, "light");
}
}

View File

@@ -14,7 +14,15 @@ import {
polyfillTimeZoneData,
} from "./locale-data-polyfill";
let polyfilled = false;
const _polyfillTimeZoneData = polyfillTimeZoneData;
const polyfillIntl = async () => {
if (polyfilled) {
return;
}
polyfilled = true;
const locale = getLocalLanguage();
const polyfills: Promise<unknown>[] = [];
if (shouldPolyfillGetCanonicalLocales()) {
@@ -26,7 +34,7 @@ const polyfillIntl = async () => {
if (shouldPolyfillDateTimeFormat(locale)) {
polyfills.push(
import("@formatjs/intl-datetimeformat/polyfill-force").then(() =>
polyfillTimeZoneData()
_polyfillTimeZoneData()
)
);
}
@@ -58,7 +66,7 @@ const polyfillIntl = async () => {
if (polyfills.length === 0) {
return;
}
await Promise.all(polyfills).then(() =>
await Promise.allSettled(polyfills).then(() =>
// Load the default language
polyfillLocaleData(locale)
);

View File

@@ -152,6 +152,10 @@ export const semanticColorStyles = css`
--ha-color-on-success-quiet: var(--ha-color-green-50);
--ha-color-on-success-normal: var(--ha-color-green-40);
--ha-color-on-success-loud: var(--white-color);
/* Surfaces */
--ha-color-surface-default: var(--ha-color-neutral-95);
--ha-color-on-surface-default: var(--ha-color-neutral-05);
}
`;
@@ -280,5 +284,9 @@ export const darkSemanticColorStyles = css`
--ha-color-on-success-quiet: var(--ha-color-green-70);
--ha-color-on-success-normal: var(--ha-color-green-60);
--ha-color-on-success-loud: var(--white-color);
/* Surfaces */
--ha-color-surface-default: var(--ha-color-neutral-10);
--ha-color-on-surface-default: var(--ha-color-neutral-95);
}
`;

View File

@@ -19,8 +19,8 @@ export const coreStyles = css`
--ha-border-radius-pill: 9999px;
--ha-border-radius-circle: 50%;
--ha-border-radius-square: 0;
# Spacing
/* Spacing */
--ha-space-0: 0px;
--ha-space-1: 4px;
--ha-space-2: 8px;
@@ -34,7 +34,7 @@ export const coreStyles = css`
--ha-space-10: 40px;
--ha-space-11: 44px;
--ha-space-12: 48px;
--ha-space-13: 44px;
--ha-space-13: 52px;
--ha-space-14: 56px;
--ha-space-15: 60px;
--ha-space-16: 64px;

View File

@@ -40,7 +40,7 @@ export class HaStateControlCoverToggle extends LitElement {
if (!this.hass || !this.stateObj) {
return;
}
forwardHaptic("light");
forwardHaptic(this, "light");
await this.hass.callService(
"cover",

View File

@@ -46,7 +46,7 @@ export class HaStateControlToggle extends LitElement {
if (!this.hass || !this.stateObj) {
return;
}
forwardHaptic("light");
forwardHaptic(this, "light");
const stateDomain = computeDomain(this.stateObj.entity_id);
let serviceDomain;
let service;

View File

@@ -68,7 +68,7 @@ export class HaStateControlLockToggle extends LitElement {
if (!this.hass || !this.stateObj) {
return;
}
forwardHaptic("light");
forwardHaptic(this, "light");
fireEvent(this, "lock-service-called");
callProtectedLockService(
this,

View File

@@ -40,7 +40,7 @@ export class HaStateControlValveToggle extends LitElement {
if (!this.hass || !this.stateObj) {
return;
}
forwardHaptic("light");
forwardHaptic(this, "light");
await this.hass.callService(
"valve",

View File

@@ -127,7 +127,7 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
);
}
if (notifyOnError) {
forwardHaptic("failure");
forwardHaptic(this, "failure");
const lokalize = await this.hass!.loadBackendTranslation(
"exceptions",
err.translation_domain

View File

@@ -1351,10 +1351,10 @@ __metadata:
languageName: node
linkType: hard
"@ctrl/tinycolor@npm:^4.1.0":
version: 4.2.0
resolution: "@ctrl/tinycolor@npm:4.2.0"
checksum: 10/1be14de7d7e8184c0bc5c8d7e3486cc8186e6702e8ca899c7239f328bb1df9a15d1575e2af7b4c6ba020727fa78f5a9f887555971f30a2890cece9e4253a9d3a
"@ctrl/tinycolor@npm:4.1.0":
version: 4.1.0
resolution: "@ctrl/tinycolor@npm:4.1.0"
checksum: 10/e64569399139ef0abd2eb0ec9fb7267dfd7820f7ad7d4567a63e5fc35e5cfdcb8ecdb3bad65cb9244b47ba6c77bc51085826c00e981acf263a3221dc89343adc
languageName: node
linkType: hard
@@ -1940,11 +1940,11 @@ __metadata:
languageName: node
linkType: hard
"@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3":
version: 3.0.0-beta.4.ha.3
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3"
"@home-assistant/webawesome@npm:3.0.0-beta.6.ha.0":
version: 3.0.0-beta.6.ha.0
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.6.ha.0"
dependencies:
"@ctrl/tinycolor": "npm:^4.1.0"
"@ctrl/tinycolor": "npm:4.1.0"
"@floating-ui/dom": "npm:^1.6.13"
"@lit/react": "npm:^1.0.8"
"@shoelace-style/animations": "npm:^1.2.0"
@@ -1953,8 +1953,7 @@ __metadata:
lit: "npm:^3.2.1"
nanoid: "npm:^5.1.5"
qr-creator: "npm:^1.0.0"
style-observer: "npm:^0.0.7"
checksum: 10/b9241821ed471ccbad86b0ea4697a2d41395f05fdc26f46e5edbc7f6b5eeab5d248251ef702326312ded00d5bf850ce0dcdcf7cd5e2e542b9d9cb9a84f3726da
checksum: 10/ec9d74585b544e5755f7b2644a0d7f9318b5776bedf51430c8f8729918fddb6e54cce46acace674960383385362846cc4c0f2da5245fa622bce8c54733a31865
languageName: node
linkType: hard
@@ -9195,7 +9194,7 @@ __metadata:
"@fullcalendar/list": "npm:6.1.19"
"@fullcalendar/luxon3": "npm:6.1.19"
"@fullcalendar/timegrid": "npm:6.1.19"
"@home-assistant/webawesome": "npm:3.0.0-beta.4.ha.3"
"@home-assistant/webawesome": "npm:3.0.0-beta.6.ha.0"
"@lezer/highlight": "npm:1.2.1"
"@lit-labs/motion": "npm:1.0.9"
"@lit-labs/observers": "npm:2.0.6"
@@ -13725,13 +13724,6 @@ __metadata:
languageName: node
linkType: hard
"style-observer@npm:^0.0.7":
version: 0.0.7
resolution: "style-observer@npm:0.0.7"
checksum: 10/bb57f98bae4463c1e1b57234f8ffe72ec0de27fb08b032c1919910129c210aacd1ddd615432b9453d491e10d3b719cf6c2a68a97165ca55d6fc9b86c0fca37fb
languageName: node
linkType: hard
"style-observer@npm:^0.0.8":
version: 0.0.8
resolution: "style-observer@npm:0.0.8"