Compare commits

..

4 Commits

Author SHA1 Message Date
Wendelin
14932f754f Fix initial max-height 2025-08-21 16:42:46 +02:00
Wendelin
2e629aefec Add dialog bottom sheet 2025-08-21 16:35:08 +02:00
Wendelin
777ede87d6 Merge branch 'dev' of github.com:home-assistant/frontend into automation-bottom-sheet 2025-08-21 12:23:31 +02:00
Wendelin
367907d62c Add 1st version of bottom sheet 2025-08-20 16:24:56 +02:00
53 changed files with 1053 additions and 1275 deletions

View File

@@ -158,7 +158,7 @@
"@octokit/auth-oauth-device": "8.0.1",
"@octokit/plugin-retry": "8.0.1",
"@octokit/rest": "22.0.0",
"@rsdoctor/rspack-plugin": "1.2.3",
"@rsdoctor/rspack-plugin": "1.2.2",
"@rspack/cli": "1.4.11",
"@rspack/core": "1.4.11",
"@types/babel__plugin-transform-runtime": "7.9.5",
@@ -191,7 +191,7 @@
"eslint-plugin-import": "2.32.0",
"eslint-plugin-lit": "2.1.1",
"eslint-plugin-lit-a11y": "5.1.1",
"eslint-plugin-unused-imports": "4.2.0",
"eslint-plugin-unused-imports": "4.1.4",
"eslint-plugin-wc": "3.0.1",
"fancy-log": "2.0.0",
"fs-extra": "11.3.1",
@@ -218,7 +218,7 @@
"terser-webpack-plugin": "5.3.14",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.2",
"typescript-eslint": "8.40.0",
"typescript-eslint": "8.39.1",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.4",
"webpack-stats-plugin": "1.1.3",

View File

@@ -101,7 +101,7 @@ export class StateHistoryChartTimeline extends LitElement {
fill: api.value(4) as string,
},
};
const text = (api.value(3) as string).replaceAll("\n", " ");
const text = api.value(3) as string;
const textWidth = measureTextWidth(text, 12);
const LABEL_PADDING = 4;
if (textWidth < rectShape.width - LABEL_PADDING * 2) {

View File

@@ -0,0 +1,243 @@
import { css, html, LitElement, type PropertyValues } from "lit";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
const ANIMATION_DURATION_MS = 300;
@customElement("ha-bottom-sheet")
export class HaBottomSheet extends LitElement {
@query("dialog") private _dialog!: HTMLDialogElement;
@property({ attribute: false }) public contentHash?: string;
private _dragging = false;
private _dragStartY = 0;
private _initialSize = 0;
private _open = false;
render() {
return html`<dialog open>
<div class="handle-wrapper">
<div
@mousedown=${this._handleMouseDown}
@touchstart=${this._handleTouchStart}
class="handle"
></div>
</div>
<slot></slot>
</dialog>`;
}
protected firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this._openSheet();
}
protected willUpdate(changedProperties: PropertyValues): void {
if (changedProperties.has("contentHash") && this._open) {
fireEvent(this, "bottom-sheet-opened");
}
}
private _openSheet() {
requestAnimationFrame(async () => {
this._dialog.classList.add("show");
setTimeout(() => {
this._dialog.style.setProperty(
"height",
`${(this._dialog.offsetHeight / window.innerHeight) * 100}vh`
);
this._dialog.style.setProperty("max-height", "90vh");
this._open = true;
fireEvent(this, "bottom-sheet-opened");
}, ANIMATION_DURATION_MS);
});
}
public closeSheet() {
requestAnimationFrame(() => {
this._dialog.classList.remove("show");
setTimeout(() => {
this._dialog.close();
fireEvent(this, "bottom-sheet-closed");
}, ANIMATION_DURATION_MS);
});
}
connectedCallback() {
super.connectedCallback();
document.addEventListener("mousemove", this._handleMouseMove);
document.addEventListener("mouseup", this._handleMouseUp);
// Use non-passive listeners so we can call preventDefault to block
// browser pull-to-refresh and page scrolling while dragging.
document.addEventListener("touchmove", this._handleTouchMove, {
passive: false,
});
document.addEventListener("touchend", this._handleTouchEnd);
document.addEventListener("touchcancel", this._handleTouchEnd);
}
disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener("mousemove", this._handleMouseMove);
document.removeEventListener("mouseup", this._handleMouseUp);
document.removeEventListener("touchmove", this._handleTouchMove);
document.removeEventListener("touchend", this._handleTouchEnd);
document.removeEventListener("touchcancel", this._handleTouchEnd);
}
private _handleMouseDown = (ev: MouseEvent) => {
this._startDrag(ev.clientY);
};
private _handleTouchStart = (ev: TouchEvent) => {
// Prevent the browser from interpreting this as a scroll/PTR gesture.
ev.preventDefault();
this._startDrag(ev.touches[0].clientY);
};
private _startDrag(clientY: number) {
this._dragging = true;
this._dragStartY = clientY;
this._initialSize = (this._dialog.offsetHeight / window.innerHeight) * 100;
document.body.style.cursor = "grabbing";
}
private _handleMouseMove = (ev: MouseEvent) => {
if (!this._dragging) return;
this._updateSize(ev.clientY);
};
private _handleTouchMove = (ev: TouchEvent) => {
if (!this._dragging) return;
ev.preventDefault(); // Prevent scrolling
this._updateSize(ev.touches[0].clientY);
};
private _updateSize(clientY: number) {
const deltaY = this._dragStartY - clientY;
const viewportHeight = window.innerHeight;
const deltaVh = (deltaY / viewportHeight) * 100;
// Calculate new size and clamp between 10vh and 90vh
let newSize = this._initialSize + deltaVh;
newSize = Math.max(10, Math.min(90, newSize));
// on drag down and below 20vh
if (newSize < 20 && deltaY < 0) {
this.closeSheet();
return;
}
this._dialog.style.setProperty("height", `${newSize}vh`);
}
private _handleMouseUp = () => {
this._endDrag();
};
private _handleTouchEnd = () => {
this._endDrag();
};
private _endDrag() {
if (!this._dragging) return;
this._dragging = false;
document.body.style.cursor = "";
}
static styles = css`
.handle-wrapper {
position: absolute;
top: 0;
width: 100%;
padding-bottom: 2px;
display: flex;
justify-content: center;
align-items: center;
cursor: grab;
touch-action: none;
}
.handle-wrapper .handle {
height: 20px;
width: 200px;
display: flex;
justify-content: center;
align-items: center;
z-index: 20;
}
.handle-wrapper .handle::after {
content: "";
border-radius: 8px;
height: 4px;
background: var(--divider-color, #e0e0e0);
width: 80px;
}
.handle-wrapper .handle:active::after {
cursor: grabbing;
}
dialog {
height: auto;
max-height: 70vh;
min-height: 30vh;
background-color: var(
--ha-dialog-surface-background,
var(--mdc-theme-surface, #fff)
);
display: flex;
flex-direction: column;
top: 0;
inset-inline-start: 0;
position: fixed;
width: calc(100% - 4px);
max-width: 100%;
overflow: hidden;
border: none;
box-shadow: var(--wa-shadow-l);
overflow: auto;
padding: 0;
margin: 0;
top: auto;
inset-inline-end: auto;
bottom: 0;
inset-inline-start: 0;
box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
border-top-left-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-2xl)
);
border-top-right-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-2xl)
);
transform: translateY(100%);
transition: transform ${ANIMATION_DURATION_MS}ms ease;
border-top-width: var(--ha-bottom-sheet-border-width);
border-right-width: var(--ha-bottom-sheet-border-width);
border-left-width: var(--ha-bottom-sheet-border-width);
border-bottom-width: 0;
border-style: var(--ha-bottom-sheet-border-style);
border-color: var(--ha-bottom-sheet-border-color);
}
dialog.show {
transform: translateY(0);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-bottom-sheet": HaBottomSheet;
}
interface HASSDomEvents {
"bottom-sheet-closed": undefined;
"bottom-sheet-opened": undefined;
}
}

View File

@@ -15,6 +15,16 @@ import { migrateAutomationAction } from "./script";
export const AUTOMATION_DEFAULT_MODE: (typeof MODES)[number] = "single";
export const AUTOMATION_DEFAULT_MAX = 10;
declare global {
interface HASSDomEvents {
/**
* Dispatched to open the automation editor.
* Used by custom cards/panels to trigger the editor view.
*/
"show-automation-editor": ShowAutomationEditorParams;
}
}
export interface AutomationEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & {
id?: string;
@@ -558,6 +568,7 @@ export interface AutomationClipboard {
export interface BaseSidebarConfig {
toggleYamlMode: () => boolean;
delete: () => void;
scrollIntoView: () => void;
}
export interface TriggerSidebarConfig extends BaseSidebarConfig {

View File

@@ -2,16 +2,10 @@ import type { Condition } from "../../../panels/lovelace/common/validate-conditi
import type { LovelaceCardConfig } from "./card";
import type { LovelaceStrategyConfig } from "./strategy";
export interface LovelaceSectionStyleConfig {
background_color?: string;
background_opacity?: number;
}
export interface LovelaceBaseSectionConfig {
visibility?: Condition[];
column_span?: number;
row_span?: number;
style?: LovelaceSectionStyleConfig;
/**
* @deprecated Use heading card instead.
*/

View File

@@ -12,7 +12,6 @@ export interface Zone {
}
export interface HomeZoneMutableParams {
name?: string;
latitude: number;
longitude: number;
radius: number;

View File

@@ -856,9 +856,7 @@ export class QuickBar extends LitElement {
private _generateNavigationPanelCommands(): BaseNavigationCommand[] {
return Object.keys(this.hass.panels)
.filter(
(panelKey) => panelKey !== "_my_redirect" && panelKey !== "hassio"
)
.filter((panelKey) => panelKey !== "_my_redirect")
.map((panelKey) => {
const panel = this.hass.panels[panelKey];
const translationKey = getPanelNameTranslationKey(panel);

View File

@@ -112,9 +112,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
this.assistConfiguration.available_wake_words.length > 1
? html`<div class="row">
<ha-select
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.form.wake_word_id"
)}
.label=${"Wake word"}
@closed=${stopPropagation}
fixedMenuPosition
naturalMenuWidth
@@ -146,9 +144,7 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
${pipelineEntity
? html`<div class="row">
<ha-select
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.devices.pipeline"
)}
.label=${"Assistant"}
@closed=${stopPropagation}
.value=${pipelineEntity?.state}
fixedMenuPosition

View File

@@ -641,10 +641,14 @@ export default class HaAutomationActionRow extends LitElement {
this.openSidebar();
}
private _scrollIntoView = () => {
this.scrollIntoView({
block: "start",
behavior: "smooth",
});
};
public openSidebar(action?: Action): void {
if (this.narrow) {
this.scrollIntoView();
}
const sidebarAction = action ?? this.action;
const actionType = getAutomationActionType(sidebarAction);
@@ -668,6 +672,7 @@ export default class HaAutomationActionRow extends LitElement {
config: sidebarAction,
uiSupported: actionType ? this._uiSupported(actionType) : false,
yamlMode: this._yamlMode,
scrollIntoView: this._scrollIntoView,
} satisfies ActionSidebarConfig);
this._selected = true;
}

View File

@@ -167,9 +167,6 @@ export default class HaAutomationAction extends LitElement {
} else if (!this.optionsInSidebar) {
row.expand();
}
if (this.narrow) {
row.scrollIntoView();
}
row.focus();
});
}

View File

@@ -598,11 +598,14 @@ export default class HaAutomationConditionRow extends LitElement {
this.openSidebar();
}
public openSidebar(condition?: Condition): void {
if (this.narrow) {
this.scrollIntoView();
}
private _scrollIntoView = () => {
this.scrollIntoView({
block: "start",
behavior: "smooth",
});
};
public openSidebar(condition?: Condition): void {
const sidebarCondition = condition || this.condition;
fireEvent(this, "open-sidebar", {
save: (value) => {
@@ -624,6 +627,7 @@ export default class HaAutomationConditionRow extends LitElement {
config: sidebarCondition,
uiSupported: this._uiSupported(sidebarCondition.condition),
yamlMode: this._yamlMode,
scrollIntoView: this._scrollIntoView,
} satisfies ConditionSidebarConfig);
this._selected = true;
}

View File

@@ -111,9 +111,6 @@ export default class HaAutomationCondition extends LitElement {
} else if (!this.optionsInSidebar) {
row.expand();
}
if (this.narrow) {
row.scrollIntoView();
}
row.focus();
});
}

View File

@@ -1,6 +1,8 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-bottom-sheet";
import type { HaBottomSheet } from "../../../components/ha-bottom-sheet";
import {
isCondition,
isScriptField,
@@ -13,14 +15,12 @@ import {
} from "../../../data/automation";
import { isTriggerList } from "../../../data/trigger";
import type { HomeAssistant } from "../../../types";
import type HaAutomationConditionEditor from "./condition/ha-automation-condition-editor";
import "./sidebar/ha-automation-sidebar-action";
import "./sidebar/ha-automation-sidebar-condition";
import "./sidebar/ha-automation-sidebar-option";
import "./sidebar/ha-automation-sidebar-script-field";
import "./sidebar/ha-automation-sidebar-script-field-selector";
import "./sidebar/ha-automation-sidebar-trigger";
import type HaAutomationTriggerEditor from "./trigger/ha-automation-trigger-editor";
@customElement("ha-automation-sidebar")
export default class HaAutomationSidebar extends LitElement {
@@ -32,92 +32,95 @@ export default class HaAutomationSidebar extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public narrow = false;
@state() private _yamlMode = false;
@query(".sidebar-editor")
public editor?: HaAutomationTriggerEditor | HaAutomationConditionEditor;
protected render() {
if (!this.config) {
return nothing;
}
@query("ha-bottom-sheet") private _bottomSheetElement?: HaBottomSheet;
private _renderContent() {
// get config type
const type = this._getType();
if (type === "trigger") {
return html`
<ha-automation-sidebar-trigger
class="sidebar-content"
.hass=${this.hass}
.config=${this.config}
.isWide=${this.isWide}
.disabled=${this.disabled}
.yamlMode=${this._yamlMode}
@toggle-yaml-mode=${this._toggleYamlMode}
@close-sidebar=${this._closeSidebar}
@close-sidebar=${this._handleCloseSidebar}
></ha-automation-sidebar-trigger>
`;
}
if (type === "condition") {
return html`
<ha-automation-sidebar-condition
class="sidebar-content"
.hass=${this.hass}
.config=${this.config}
.isWide=${this.isWide}
.disabled=${this.disabled}
.yamlMode=${this._yamlMode}
@toggle-yaml-mode=${this._toggleYamlMode}
@close-sidebar=${this._closeSidebar}
@close-sidebar=${this._handleCloseSidebar}
></ha-automation-sidebar-condition>
`;
}
if (type === "action") {
return html`
<ha-automation-sidebar-action
class="sidebar-content"
.hass=${this.hass}
.config=${this.config}
.isWide=${this.isWide}
.disabled=${this.disabled}
.yamlMode=${this._yamlMode}
@toggle-yaml-mode=${this._toggleYamlMode}
@close-sidebar=${this._closeSidebar}
@close-sidebar=${this._handleCloseSidebar}
></ha-automation-sidebar-action>
`;
}
if (type === "option") {
return html`
<ha-automation-sidebar-option
class="sidebar-content"
.hass=${this.hass}
.config=${this.config}
.isWide=${this.isWide}
.disabled=${this.disabled}
@close-sidebar=${this._closeSidebar}
@close-sidebar=${this._handleCloseSidebar}
></ha-automation-sidebar-option>
`;
}
if (type === "script-field-selector") {
return html`
<ha-automation-sidebar-script-field-selector
class="sidebar-content"
.hass=${this.hass}
.config=${this.config}
.isWide=${this.isWide}
.disabled=${this.disabled}
.yamlMode=${this._yamlMode}
@toggle-yaml-mode=${this._toggleYamlMode}
@close-sidebar=${this._closeSidebar}
@close-sidebar=${this._handleCloseSidebar}
></ha-automation-sidebar-script-field-selector>
`;
}
if (type === "script-field") {
return html`
<ha-automation-sidebar-script-field
class="sidebar-content"
.hass=${this.hass}
.config=${this.config}
.isWide=${this.isWide}
.disabled=${this.disabled}
.yamlMode=${this._yamlMode}
@toggle-yaml-mode=${this._toggleYamlMode}
@close-sidebar=${this._closeSidebar}
@close-sidebar=${this._handleCloseSidebar}
></ha-automation-sidebar-script-field>
`;
}
@@ -125,6 +128,26 @@ export default class HaAutomationSidebar extends LitElement {
return nothing;
}
protected render() {
if (!this.config) {
return nothing;
}
if (this.narrow) {
return html`
<ha-bottom-sheet
.contentHash=${JSON.stringify(this.config)}
@bottom-sheet-closed=${this._closeSidebar}
@bottom-sheet-opened=${this._scrollRowIntoView}
>
${this._renderContent()}
</ha-bottom-sheet>
`;
}
return this._renderContent();
}
private _getType() {
if (
(this.config as TriggerSidebarConfig)?.config &&
@@ -157,8 +180,17 @@ export default class HaAutomationSidebar extends LitElement {
return undefined;
}
private _closeSidebar(ev: CustomEvent) {
private _handleCloseSidebar(ev: CustomEvent) {
ev.stopPropagation();
if (this.narrow) {
this._bottomSheetElement?.closeSheet();
return;
}
this._closeSidebar();
}
private _closeSidebar() {
this.config?.close();
}
@@ -172,6 +204,10 @@ export default class HaAutomationSidebar extends LitElement {
});
};
private _scrollRowIntoView = () => {
this.config?.scrollIntoView();
};
static styles = css`
:host {
height: 100%;
@@ -180,6 +216,15 @@ export default class HaAutomationSidebar extends LitElement {
var(--ha-border-radius-2xl)
);
border-radius: var(--ha-card-border-radius);
--ha-bottom-sheet-border-width: 2px;
--ha-bottom-sheet-border-style: solid;
--ha-bottom-sheet-border-color: var(--primary-color);
}
@media all and (max-width: 870px) {
.sidebar-content {
max-height: 100%;
}
}
`;
}

View File

@@ -273,10 +273,11 @@ export class HaManualAutomationEditor extends LitElement {
class=${classMap({
sidebar: true,
hidden: !this._sidebarConfig,
overlay: !this.isWide,
overlay: !this.isWide && !this.narrow,
})}
.isWide=${this.isWide}
.hass=${this.hass}
.narrow=${this.narrow}
.config=${this._sidebarConfig}
@value-changed=${this._sidebarConfigChanged}
.disabled=${this.disabled}
@@ -612,26 +613,23 @@ export class HaManualAutomationEditor extends LitElement {
.sidebar.overlay {
position: fixed;
bottom: 0;
right: 0;
height: calc(100% - 64px);
bottom: 8px;
right: 8px;
height: calc(100% - 70px);
padding: 0;
z-index: 5;
box-shadow: -8px 0 16px rgba(0, 0, 0, 0.2);
}
@media all and (max-width: 870px) {
.sidebar.overlay {
max-height: 70vh;
max-height: 70dvh;
height: auto;
width: 100%;
box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
.split-view {
gap: 0;
margin-right: -8px;
}
}
@media all and (max-width: 870px) {
.sidebar.overlay.hidden {
.sidebar {
height: 0;
width: 0;
flex: 0;
}
}

View File

@@ -342,11 +342,14 @@ export default class HaAutomationOptionRow extends LitElement {
this.openSidebar();
}
public openSidebar(): void {
if (this.narrow) {
this.scrollIntoView();
}
private _scrollIntoView = () => {
this.scrollIntoView({
block: "start",
behavior: "smooth",
});
};
public openSidebar(): void {
fireEvent(this, "open-sidebar", {
close: () => {
this._selected = false;
@@ -357,6 +360,7 @@ export default class HaAutomationOptionRow extends LitElement {
},
toggleYamlMode: () => false, // no yaml mode for options
delete: this._removeOption,
scrollIntoView: this._scrollIntoView,
} satisfies OptionSidebarConfig);
this._selected = true;
}

View File

@@ -133,10 +133,6 @@ export default class HaAutomationOption extends LitElement {
if (!this.optionsInSidebar) {
row.expand();
}
if (this.narrow) {
row.scrollIntoView();
}
row.focus();
});
}

View File

@@ -0,0 +1,220 @@
import { css, html, LitElement } from "lit";
import { customElement, query, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
const ANIMATION_DURATION_MS = 300;
@customElement("ha-automation-sidebar-bottom-sheet")
export class HaAutomationSidebarBottomSheet extends LitElement {
@state() private _size = 30;
@query("dialog") private _dialog!: HTMLDialogElement;
private _dragging = false;
private _dragStartY = 0;
private _initialSize = 0;
public showDialog(): void {
this._openSheet();
}
public closeDialog(): void {
this.closeSheet();
}
render() {
return html`<dialog open style=${`--size: ${this._size}vh;`}>
<div
class="handle-wrapper"
@mousedown=${this._handleMouseDown}
@touchstart=${this._handleTouchStart}
>
<div class="handle"></div>
</div>
<slot></slot>
</dialog>`;
}
protected firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this._openSheet();
}
private _openSheet() {
requestAnimationFrame(() => {
this._dialog.classList.add("show");
});
}
public closeSheet() {
requestAnimationFrame(() => {
this._dialog.classList.remove("show");
setTimeout(() => {
this._dialog.close();
fireEvent(this, "dialog-closed", { dialog: this.localName });
}, ANIMATION_DURATION_MS);
});
}
connectedCallback() {
super.connectedCallback();
document.addEventListener("mousemove", this._handleMouseMove);
document.addEventListener("mouseup", this._handleMouseUp);
// Use non-passive listeners so we can call preventDefault to block
// browser pull-to-refresh and page scrolling while dragging.
document.addEventListener("touchmove", this._handleTouchMove, {
passive: false,
});
document.addEventListener("touchend", this._handleTouchEnd);
document.addEventListener("touchcancel", this._handleTouchEnd);
}
disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener("mousemove", this._handleMouseMove);
document.removeEventListener("mouseup", this._handleMouseUp);
document.removeEventListener("touchmove", this._handleTouchMove);
document.removeEventListener("touchend", this._handleTouchEnd);
document.removeEventListener("touchcancel", this._handleTouchEnd);
}
private _handleMouseDown = (ev: MouseEvent) => {
this._startDrag(ev.clientY);
};
private _handleTouchStart = (ev: TouchEvent) => {
// Prevent the browser from interpreting this as a scroll/PTR gesture.
ev.preventDefault();
this._startDrag(ev.touches[0].clientY);
};
private _startDrag(clientY: number) {
this._dragging = true;
this._dragStartY = clientY;
this._initialSize = this._size;
document.body.style.cursor = "grabbing";
}
private _handleMouseMove = (ev: MouseEvent) => {
if (!this._dragging) return;
this._updateSize(ev.clientY);
};
private _handleTouchMove = (ev: TouchEvent) => {
if (!this._dragging) return;
ev.preventDefault(); // Prevent scrolling
this._updateSize(ev.touches[0].clientY);
};
private _updateSize(clientY: number) {
const deltaY = this._dragStartY - clientY;
const viewportHeight = window.innerHeight;
const deltaVh = (deltaY / viewportHeight) * 100;
// Calculate new size and clamp between 10vh and 90vh
let newSize = this._initialSize + deltaVh;
newSize = Math.max(10, Math.min(90, newSize));
this._size = newSize;
if (newSize < 20) {
this.closeSheet();
}
}
private _handleMouseUp = () => {
this._endDrag();
};
private _handleTouchEnd = () => {
this._endDrag();
};
private _endDrag() {
if (!this._dragging) return;
this._dragging = false;
document.body.style.cursor = "";
}
static styles = css`
:host {
--size: 30vh;
--size: 30dvh;
overscroll-behavior: contain;
}
.handle-wrapper {
width: 100%;
height: 32px;
padding-bottom: 2px;
display: flex;
justify-content: center;
align-items: center;
cursor: grab;
touch-action: none;
}
.handle-wrapper:active {
cursor: grabbing;
}
.handle-wrapper .handle {
border-radius: 8px;
height: 4px;
background: var(--divider-color, #e0e0e0);
width: 80px;
transition: background-color 0.2s ease;
pointer-events: none;
}
dialog {
background-color: var(
--ha-dialog-surface-background,
var(--mdc-theme-surface, #fff)
);
display: flex;
flex-direction: column;
top: 0;
inset-inline-start: 0;
position: fixed;
width: 100%;
max-width: 100%;
max-height: 100%;
overflow: hidden;
border: none;
box-shadow: var(--wa-shadow-l);
overflow: auto;
padding: 0;
margin: 0;
top: auto;
inset-inline-end: auto;
bottom: 0;
inset-inline-start: 0;
height: var(--size);
box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
border-top-left-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-2xl)
);
border-top-right-radius: var(
--ha-dialog-border-radius,
var(--ha-border-radius-2xl)
);
transform: translateY(100%);
transition: transform ${ANIMATION_DURATION_MS}ms ease;
}
dialog.show {
transform: translateY(0);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-sidebar-bottom-sheet": HaAutomationSidebarBottomSheet;
}
interface HASSDomEvents {
"bottom-sheet-closed": undefined;
}
}

View File

@@ -1,6 +1,6 @@
import { mdiClose, mdiDotsVertical } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
@@ -33,6 +33,12 @@ export default class HaAutomationSidebarCard extends LitElement {
@property({ attribute: false }) public warnings?: string[];
@property({ type: Boolean }) public narrow = false;
@state() private _contentScrolled = false;
@query(".card-content") private _contentElement?: HTMLDivElement;
protected render() {
return html`
<ha-card
@@ -42,7 +48,9 @@ export default class HaAutomationSidebarCard extends LitElement {
yaml: this.yamlMode,
})}
>
<ha-dialog-header>
<ha-dialog-header
class=${classMap({ scrolled: this._contentScrolled })}
>
<ha-icon-button
slot="navigationIcon"
.label=${this.hass.localize("ui.common.close")}
@@ -81,6 +89,25 @@ export default class HaAutomationSidebarCard extends LitElement {
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this._contentElement?.addEventListener("scroll", this._onScroll, {
passive: true,
});
this._onScroll();
}
disconnectedCallback(): void {
super.disconnectedCallback();
this._contentElement?.removeEventListener("scroll", this._onScroll);
}
private _onScroll = () => {
const top = this._contentElement?.scrollTop ?? 0;
this._contentScrolled = top > 0;
};
private _closeSidebar() {
fireEvent(this, "close-sidebar");
}
@@ -98,25 +125,33 @@ export default class HaAutomationSidebarCard extends LitElement {
border-width: 2px;
display: block;
}
ha-card.mobile {
border-bottom-right-radius: var(--ha-border-radius-square);
border-bottom-left-radius: var(--ha-border-radius-square);
}
@media all and (max-width: 870px) {
ha-card.mobile {
max-height: 70vh;
max-height: 70dvh;
border-width: 2px 2px 0;
border: none;
}
ha-card.mobile.yaml {
height: 70vh;
height: 70dvh;
ha-card.mobile {
border-bottom-right-radius: var(--ha-border-radius-square);
border-bottom-left-radius: var(--ha-border-radius-square);
}
}
ha-dialog-header {
border-radius: var(--ha-card-border-radius);
box-shadow: none;
transition: box-shadow 180ms ease-in-out;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
z-index: 10;
position: relative;
background-color: var(
--ha-dialog-surface-background,
var(--mdc-theme-surface, #fff)
);
}
ha-dialog-header.scrolled {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
.card-content {
@@ -130,17 +165,6 @@ export default class HaAutomationSidebarCard extends LitElement {
overflow: auto;
}
}
@media all and (max-width: 870px) {
ha-card.mobile .card-content {
max-height: calc(
70vh - 88px - max(var(--safe-area-inset-bottom), 16px)
);
max-height: calc(
70dvh - 88px - max(var(--safe-area-inset-bottom), 16px)
);
}
}
`;
}

View File

@@ -0,0 +1,12 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import "./ha-automation-sidebar-bottom-sheet";
export const showAutomationSidebarBottomSheet = (
element: HTMLElement
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "ha-automation-sidebar-bottom-sheet",
dialogImport: () => import("./ha-automation-sidebar-bottom-sheet"),
dialogParams: {},
});
};

View File

@@ -468,10 +468,14 @@ export default class HaAutomationTriggerRow extends LitElement {
this.openSidebar();
}
private _scrollIntoView = () => {
this.scrollIntoView({
block: "start",
behavior: "smooth",
});
};
public openSidebar(trigger?: Trigger): void {
if (this.narrow) {
this.scrollIntoView();
}
fireEvent(this, "open-sidebar", {
save: (value) => {
fireEvent(this, "value-changed", { value });
@@ -492,6 +496,7 @@ export default class HaAutomationTriggerRow extends LitElement {
config: trigger || this.trigger,
uiSupported: this._uiSupported(this._getType(trigger || this.trigger)),
yamlMode: this._yamlMode,
scrollIntoView: this._scrollIntoView,
} satisfies TriggerSidebarConfig);
this._selected = true;
}

View File

@@ -180,9 +180,6 @@ export default class HaAutomationTrigger extends LitElement {
} else {
row.expand();
}
if (this.narrow) {
row.scrollIntoView();
}
row.focus();
});
}

View File

@@ -15,6 +15,8 @@ import type { CloudStatus } from "../../../data/cloud";
import { fetchCloudStatus } from "../../../data/cloud";
import type { HardwareInfo } from "../../../data/hardware";
import { BOARD_NAMES } from "../../../data/hardware";
import type { HassioBackup } from "../../../data/hassio/backup";
import { fetchHassioBackups } from "../../../data/hassio/backup";
import type {
HassioHassOSInfo,
HassioHostInfo,
@@ -42,7 +44,7 @@ class HaConfigSystemNavigation extends LitElement {
@property({ attribute: false }) public showAdvanced = false;
@state() private _latestBackupDate?: Date;
@state() private _latestBackupDate?: string;
@state() private _boardName?: string;
@@ -61,7 +63,7 @@ class HaConfigSystemNavigation extends LitElement {
description = this._latestBackupDate
? this.hass.localize("ui.panel.config.backup.description", {
relative_time: relativeTime(
this._latestBackupDate,
new Date(this._latestBackupDate),
this.hass.locale
),
})
@@ -153,24 +155,26 @@ class HaConfigSystemNavigation extends LitElement {
this._fetchNetworkStatus();
const isHassioLoaded = isComponentLoaded(this.hass, "hassio");
this._fetchBackupInfo();
this._fetchBackupInfo(isHassioLoaded);
this._fetchHardwareInfo(isHassioLoaded);
if (isHassioLoaded) {
this._fetchStorageInfo();
}
}
private async _fetchBackupInfo() {
const backups: BackupContent[] = isComponentLoaded(this.hass, "backup")
? await fetchBackupInfo(this.hass).then(
(backupData) => backupData.backups
)
: [];
private async _fetchBackupInfo(isHassioLoaded: boolean) {
const backups: BackupContent[] | HassioBackup[] = isHassioLoaded
? await fetchHassioBackups(this.hass)
: isComponentLoaded(this.hass, "backup")
? await fetchBackupInfo(this.hass).then(
(backupData) => backupData.backups
)
: [];
if (backups.length > 0) {
this._latestBackupDate = backups
.map((backup) => new Date(backup.date))
.reduce((a, b) => (a > b ? a : b));
this._latestBackupDate = (backups as any[]).reduce((a, b) =>
a.date > b.date ? a : b
).date;
}
}

View File

@@ -85,7 +85,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
this._deviceTrackers = this._params.entry.device_trackers || [];
this._picture = this._params.entry.picture || null;
this._user = this._userId
? this._params.users?.find((user) => user.id === this._userId)
? this._params.users.find((user) => user.id === this._userId)
: undefined;
this._isAdmin = this._user?.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
this._localOnly = this._user?.local_only;
@@ -372,10 +372,10 @@ class DialogPersonDetail extends LitElement implements HassDialog {
userAddedCallback: async (user?: User) => {
if (user) {
target.checked = true;
if (this._params!.entry && this._params!.updateEntry) {
if (this._params!.entry) {
await this._params!.updateEntry({ user_id: user.id });
}
this._params?.refreshUsers?.();
this._params?.refreshUsers();
this._user = user;
this._userId = user.id;
this._isAdmin = user.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
@@ -403,7 +403,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
return;
}
await deleteUser(this.hass, this._userId);
this._params?.refreshUsers?.();
this._params?.refreshUsers();
this._userId = undefined;
this._user = undefined;
this._isAdmin = undefined;
@@ -466,7 +466,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
if (newUsername) {
try {
await adminChangeUsername(this.hass, this._user.id, newUsername);
this._params?.refreshUsers?.();
this._params?.refreshUsers();
this._user = { ...this._user, username: newUsername };
showAlertDialog(this, {
text: this.hass.localize(
@@ -500,7 +500,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
],
local_only: this._localOnly,
});
this._params?.refreshUsers?.();
this._params?.refreshUsers();
}
const values: PersonMutableParams = {
name: this._name.trim(),
@@ -509,9 +509,9 @@ class DialogPersonDetail extends LitElement implements HassDialog {
picture: this._picture,
};
if (this._params!.entry) {
await this._params!.updateEntry?.(values);
await this._params!.updateEntry(values);
} else {
await this._params!.createEntry?.(values);
await this._params!.createEntry(values);
this._personExists = true;
}
this.closeDialog();
@@ -525,7 +525,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
private async _deleteEntry() {
this._submitting = true;
try {
if (await this._params!.removeEntry?.()) {
if (await this._params!.removeEntry()) {
if (this._params!.entry!.user_id) {
deleteUser(this.hass, this._params!.entry!.user_id);
}

View File

@@ -4,22 +4,22 @@ import type { User } from "../../../data/user";
export interface PersonDetailDialogParams {
entry?: Person;
users?: User[];
refreshUsers?: () => void;
createEntry?: (values: PersonMutableParams) => Promise<unknown>;
updateEntry?: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
removeEntry?: () => Promise<boolean>;
users: User[];
refreshUsers: () => void;
createEntry: (values: PersonMutableParams) => Promise<unknown>;
updateEntry: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
removeEntry: () => Promise<boolean>;
}
export const loadPersonDetailDialog = () => import("./dialog-person-detail");
export const showPersonDetailDialog = (
element: HTMLElement,
params: PersonDetailDialogParams
systemLogDetailParams: PersonDetailDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-person-detail",
dialogImport: loadPersonDetailDialog,
dialogParams: params,
dialogParams: systemLogDetailParams,
});
};

View File

@@ -183,13 +183,17 @@ export default class HaScriptFieldRow extends LitElement {
});
}
private _scrollIntoView = () => {
this.scrollIntoView({
block: "start",
behavior: "smooth",
});
};
public openSidebar(selectorEditor = false): void {
if (!selectorEditor) {
this._selected = true;
}
if (this.narrow) {
this.scrollIntoView();
}
fireEvent(this, "open-sidebar", {
save: (value) => {
@@ -215,6 +219,7 @@ export default class HaScriptFieldRow extends LitElement {
excludeKeys: this.excludeKeys,
},
yamlMode: this._yamlMode,
scrollIntoView: this._scrollIntoView,
} satisfies ScriptFieldSidebarConfig);
}

View File

@@ -75,10 +75,6 @@ export default class HaScriptFields extends LitElement {
)!;
row.updateComplete.then(() => {
row.openSidebar();
if (this.narrow) {
row.scrollIntoView();
}
row.focus();
});
}

View File

@@ -34,7 +34,6 @@ class DialogHomeZoneDetail extends LitElement {
this._params = params;
this._error = undefined;
this._data = {
name: this.hass.config.location_name,
latitude: this.hass.config.latitude,
longitude: this.hass.config.longitude,
radius: this.hass.config.radius,
@@ -64,7 +63,7 @@ class DialogHomeZoneDetail extends LitElement {
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this.hass!.localize("ui.common.edit_item", { name: this._data.name })
this.hass!.localize("ui.panel.config.zone.edit_home")
)}
>
<div>

View File

@@ -85,9 +85,7 @@ class DialogZoneDetail extends LitElement {
.heading=${createCloseHeading(
this.hass,
this._params.entry
? this.hass!.localize("ui.common.edit_item", {
name: this._params.entry.name,
})
? this._params.entry.name
: this.hass!.localize("ui.panel.config.zone.detail.new_zone")
)}
>

View File

@@ -165,9 +165,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
.entry=${entry}
@click=${this._openEditEntry}
.path=${mdiPencil}
.label=${hass.localize("ui.common.edit_item", {
name: entry.name,
})}
.label=${hass.localize(
"ui.panel.config.zone.edit_zone"
)}
></ha-icon-button>
</div>
`
@@ -218,9 +218,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
this._canEditCore
? mdiPencil
: mdiPencilOff}
.label=${hass.localize("ui.common.edit_item", {
name: hass.config.location_name,
})}
.label=${stateObject.entity_id === "zone.home"
? hass.localize("ui.panel.config.zone.edit_home")
: hass.localize("ui.panel.config.zone.edit_zone")}
@click=${this._editHomeZone}
></ha-icon-button>
</ha-tooltip>`}

View File

@@ -1,323 +0,0 @@
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
mdiPause,
mdiPlay,
mdiPlayPause,
mdiPower,
mdiSkipNext,
mdiSkipPrevious,
mdiStop,
} from "@mdi/js";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
LovelaceCardFeatureContext,
MediaPlayerPlaybackCardFeatureConfig,
} from "./types";
import type {
ControlButton,
MediaPlayerEntity,
} from "../../../data/media-player";
import { MediaPlayerEntityFeature } from "../../../data/media-player";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { stateActive } from "../../../common/entity/state_active";
import { isUnavailableState } from "../../../data/entity";
import { hasConfigChanged } from "../common/has-changed";
import "../../../components/ha-control-button-group";
import "../../../components/ha-control-button";
import "../../../components/ha-icon-button";
import "../../../components/ha-icon";
export const supportsMediaPlayerPlaybackCardFeature = (
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 === "media_player";
};
@customElement("hui-media-player-playback-card-feature")
class HuiMediaPlayerPlaybackCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@property({ attribute: false }) public color?: string;
@state() private _config?: MediaPlayerPlaybackCardFeatureConfig;
@state() private _narrow?: boolean = false;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as
| MediaPlayerEntity
| undefined;
}
static getStubConfig(): MediaPlayerPlaybackCardFeatureConfig {
return {
type: "media-player-playback",
};
}
public setConfig(config: MediaPlayerPlaybackCardFeatureConfig): void {
if (!config) {
throw new Error("Invalid configuration");
}
this._config = config;
}
public willUpdate(): void {
if (!this.hasUpdated) {
this._measureCard();
}
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
const entityId = this.context?.entity_id;
return (
hasConfigChanged(this, changedProps) ||
(changedProps.has("hass") &&
(!oldHass ||
!entityId ||
oldHass.states[entityId] !== this.hass!.states[entityId]))
);
}
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!supportsMediaPlayerPlaybackCardFeature(this.hass, this.context) ||
!this._stateObj
) {
return nothing;
}
const buttons = this._computeButtons(this._stateObj);
return html`
<ha-control-button-group>
${supportsFeature(this._stateObj, MediaPlayerEntityFeature.TURN_OFF) &&
stateActive(this._stateObj)
? html`
<ha-control-button
.label=${this.hass.localize("ui.card.media_player.turn_off")}
@click=${this._togglePower}
>
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
</ha-control-button>
`
: ""}
${supportsFeature(this._stateObj, MediaPlayerEntityFeature.TURN_ON) &&
!stateActive(this._stateObj) &&
!isUnavailableState(this._stateObj.state)
? html`
<ha-control-button
.label=${this.hass.localize("ui.card.media_player.turn_on")}
@click=${this._togglePower}
>
<ha-svg-icon .path=${mdiPower}></ha-svg-icon>
</ha-control-button>
`
: buttons.map(
(button) => html`
<ha-control-button
key=${button.action}
.label=${this.hass?.localize(
`ui.card.media_player.${button.action}`
)}
@click=${this._action}
>
<ha-svg-icon .path=${button.icon}></ha-svg-icon>
</ha-control-button>
`
)}
</ha-control-button-group>
`;
}
private _measureCard() {
if (!this.isConnected) {
return;
}
const host = (this.getRootNode() as ShadowRoot).host as
| HTMLElement
| undefined;
const width = host?.clientWidth ?? this.clientWidth ?? 0;
this._narrow = width < 300;
}
private _computeControlButton(stateObj: MediaPlayerEntity): ControlButton {
return stateObj.state === "on"
? { icon: mdiPlayPause, action: "media_play_pause" }
: stateObj.state !== "playing"
? { icon: mdiPlay, action: "media_play" }
: supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE)
? { icon: mdiPause, action: "media_pause" }
: { icon: mdiStop, action: "media_stop" };
}
private _computeButtons(stateObj: MediaPlayerEntity): ControlButton[] {
const controlButton = this._computeControlButton(stateObj);
const assumedState = stateObj.attributes.assumed_state === true;
const controls: ControlButton[] = [];
if (
!this._narrow &&
(stateObj.state === "playing" || assumedState) &&
supportsFeature(stateObj, MediaPlayerEntityFeature.PREVIOUS_TRACK)
) {
controls.push({ icon: mdiSkipPrevious, action: "media_previous_track" });
}
if (
!assumedState &&
((stateObj.state === "playing" &&
(supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE) ||
supportsFeature(stateObj, MediaPlayerEntityFeature.STOP))) ||
((stateObj.state === "paused" || stateObj.state === "idle") &&
supportsFeature(stateObj, MediaPlayerEntityFeature.PLAY)) ||
(stateObj.state === "on" &&
(supportsFeature(stateObj, MediaPlayerEntityFeature.PLAY) ||
supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE))))
) {
controls.push({ icon: controlButton.icon, action: controlButton.action });
}
if (assumedState) {
if (supportsFeature(stateObj, MediaPlayerEntityFeature.PLAY)) {
controls.push({ icon: mdiPlay, action: "media_play" });
}
if (supportsFeature(stateObj, MediaPlayerEntityFeature.PAUSE)) {
controls.push({ icon: mdiPause, action: "media_pause" });
}
if (supportsFeature(stateObj, MediaPlayerEntityFeature.STOP)) {
controls.push({ icon: mdiStop, action: "media_stop" });
}
}
if (
(stateObj.state === "playing" || assumedState) &&
supportsFeature(stateObj, MediaPlayerEntityFeature.NEXT_TRACK)
) {
controls.push({ icon: mdiSkipNext, action: "media_next_track" });
}
return controls;
}
private _togglePower(): void {
if (!this._stateObj) return;
this.hass!.callService(
"media_player",
stateActive(this._stateObj) ? "turn_off" : "turn_on",
{
entity_id: this._stateObj.entity_id,
}
);
}
private _action(e: Event): void {
const action = (e.currentTarget as HTMLElement).getAttribute("key");
if (!action) return;
switch (action) {
case "media_play_pause":
this._playPauseStop();
break;
case "media_play":
this._play();
break;
case "media_pause":
this._pause();
break;
case "media_stop":
this._stop();
break;
case "media_previous_track":
this._previousTrack();
break;
case "media_next_track":
this._nextTrack();
break;
}
}
private _playPauseStop(): void {
if (!this._stateObj) return;
const service =
this._stateObj.state !== "playing"
? "media_play"
: supportsFeature(this._stateObj, MediaPlayerEntityFeature.PAUSE)
? "media_pause"
: "media_stop";
this.hass!.callService("media_player", service, {
entity_id: this._stateObj.entity_id,
});
}
private _play(): void {
if (!this._stateObj) return;
this.hass!.callService("media_player", "media_play", {
entity_id: this._stateObj.entity_id,
});
}
private _pause(): void {
if (!this._stateObj) return;
this.hass!.callService("media_player", "media_pause", {
entity_id: this._stateObj.entity_id,
});
}
private _stop(): void {
if (!this._stateObj) return;
this.hass!.callService("media_player", "media_stop", {
entity_id: this._stateObj.entity_id,
});
}
private _previousTrack(): void {
if (!this._stateObj) return;
this.hass!.callService("media_player", "media_previous_track", {
entity_id: this._stateObj.entity_id,
});
}
private _nextTrack(): void {
if (!this._stateObj) return;
this.hass!.callService("media_player", "media_next_track", {
entity_id: this._stateObj.entity_id,
});
}
static styles = cardFeatureStyles;
}
declare global {
interface HTMLElementTagNameMap {
"hui-media-player-playback-card-feature": HuiMediaPlayerPlaybackCardFeature;
}
}

View File

@@ -39,10 +39,6 @@ export interface LockOpenDoorCardFeatureConfig {
type: "lock-open-door";
}
export interface MediaPlayerPlaybackCardFeatureConfig {
type: "media-player-playback";
}
export interface MediaPlayerVolumeSliderCardFeatureConfig {
type: "media-player-volume-slider";
}
@@ -246,7 +242,6 @@ export type LovelaceCardFeatureConfig =
| LightColorTempCardFeatureConfig
| LockCommandsCardFeatureConfig
| LockOpenDoorCardFeatureConfig
| MediaPlayerPlaybackCardFeatureConfig
| MediaPlayerVolumeSliderCardFeatureConfig
| NumericInputCardFeatureConfig
| SelectOptionsCardFeatureConfig

View File

@@ -9,7 +9,6 @@ import {
type TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
@@ -85,10 +84,8 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
const displayType =
config.display_type || (config.show_camera ? "camera" : "picture");
const vertical = displayType === "compact" ? config.vertical : false;
this._config = {
...config,
vertical,
display_type: displayType,
};
@@ -112,7 +109,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
const featuresCount = this._config?.features?.length || 0;
return (
1 +
(displayType === "compact" ? (this._config?.vertical ? 1 : 0) : 2) +
(displayType === "compact" ? 0 : 2) +
(featuresPosition === "inline" ? 0 : featuresCount)
);
}
@@ -136,11 +133,6 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
const displayType = this._config?.display_type || "picture";
if (this._config?.vertical) {
rows++;
min_columns = 3;
}
if (displayType !== "compact") {
if (featurePosition === "inline" && featuresCount > 0) {
rows += 3;
@@ -405,12 +397,9 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
return sensorStates;
}
private _featurePosition = memoizeOne((config: AreaCardConfig) => {
if (config.vertical) {
return "bottom";
}
return config.features_position || "bottom";
});
private _featurePosition = memoizeOne(
(config: AreaCardConfig) => config.features_position || "bottom"
);
private _displayedFeatures = memoizeOne((config: AreaCardConfig) => {
const features = config.features || [];
@@ -450,8 +439,6 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
`;
}
const contentClasses = { vertical: Boolean(this._config.vertical) };
const icon = area.icon;
const name = this._config.name || computeAreaName(area);
@@ -531,7 +518,7 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
</div>
`}
<div class="container ${containerOrientationClass}">
<div class="content ${classMap(contentClasses)}">
<div class="content">
<ha-tile-icon>
${displayType === "compact"
? this._renderAlertSensorBadge()
@@ -669,16 +656,6 @@ export class HuiAreaCard extends LitElement implements LovelaceCard {
gap: 10px;
}
.vertical {
flex-direction: column;
text-align: center;
justify-content: center;
}
.vertical ha-tile-info {
width: 100%;
flex: none;
}
ha-tile-icon {
--tile-icon-color: var(--tile-color);
position: relative;

View File

@@ -251,6 +251,8 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
const entityId = this._config.entity;
const stateObj = entityId ? this.hass.states[entityId] : undefined;
const contentClasses = { vertical: Boolean(this._config.vertical) };
if (!stateObj) {
return html`
<hui-warning .hass=${this.hass}>
@@ -259,8 +261,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
`;
}
const contentClasses = { vertical: Boolean(this._config.vertical) };
const name = this._config.name || computeStateName(stateObj);
const active = stateActive(stateObj);
const color = this._computeStateColor(stateObj, this._config.color);

View File

@@ -104,13 +104,12 @@ export interface EntitiesCardConfig extends LovelaceCardConfig {
state_color?: boolean;
}
export type AreaCardDisplayType = "compact" | "icon" | "picture" | "camera";
export interface AreaCardConfig extends LovelaceCardConfig {
area?: string;
name?: string;
color?: string;
navigation_path?: string;
display_type?: AreaCardDisplayType;
display_type?: "compact" | "icon" | "picture" | "camera";
/** @deprecated Use `display_type` instead */
show_camera?: boolean;
camera_view?: HuiImage["cameraView"];

View File

@@ -22,7 +22,6 @@ import "../card-features/hui-light-brightness-card-feature";
import "../card-features/hui-light-color-temp-card-feature";
import "../card-features/hui-lock-commands-card-feature";
import "../card-features/hui-lock-open-door-card-feature";
import "../card-features/hui-media-player-playback-card-feature";
import "../card-features/hui-media-player-volume-slider-card-feature";
import "../card-features/hui-numeric-input-card-feature";
import "../card-features/hui-select-options-card-feature";
@@ -71,7 +70,6 @@ const TYPES = new Set<LovelaceCardFeatureConfig["type"]>([
"light-color-temp",
"lock-commands",
"lock-open-door",
"media-player-playback",
"media-player-volume-slider",
"numeric-input",
"select-options",

View File

@@ -36,7 +36,7 @@ import {
DEVICE_CLASSES,
type AreaCardFeatureContext,
} from "../../cards/hui-area-card";
import type { AreaCardConfig, AreaCardDisplayType } from "../../cards/types";
import type { AreaCardConfig } from "../../cards/types";
import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import type { EditDetailElementEvent, EditSubElementEvent } from "../types";
@@ -52,7 +52,6 @@ const cardConfigStruct = assign(
navigation_path: optional(string()),
show_camera: optional(boolean()),
display_type: optional(enums(["compact", "icon", "picture", "camera"])),
vertical: optional(boolean()),
camera_view: optional(string()),
alert_classes: optional(array(string())),
sensor_classes: optional(array(string())),
@@ -79,7 +78,7 @@ export class HuiAreaCardEditor
private _schema = memoizeOne(
(
localize: LocalizeFunc,
displayType: AreaCardDisplayType,
showCamera: boolean,
binaryClasses: SelectOption[],
sensorClasses: SelectOption[]
) =>
@@ -114,28 +113,7 @@ export class HuiAreaCardEditor
},
},
},
...(displayType === "compact"
? ([
{
name: "content_layout",
required: true,
selector: {
select: {
mode: "dropdown",
options: ["horizontal", "vertical"].map(
(value) => ({
label: localize(
`ui.panel.lovelace.editor.card.area.content_layout_options.${value}`
),
value,
})
),
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
...(displayType === "camera"
...(showCamera
? ([
{
name: "camera_view",
@@ -304,7 +282,7 @@ export class HuiAreaCardEditor
}
private _featuresSchema = memoizeOne(
(localize: LocalizeFunc, vertical: boolean) =>
(localize: LocalizeFunc) =>
[
{
name: "features_position",
@@ -325,7 +303,6 @@ export class HuiAreaCardEditor
src_dark: `/static/images/form/tile_features_position_${value}_dark.svg`,
flip_rtl: true,
},
disabled: vertical && value === "inline",
})),
},
},
@@ -361,35 +338,31 @@ export class HuiAreaCardEditor
this._config.sensor_classes || DEVICE_CLASSES.sensor
);
const showCamera = this._config.display_type === "camera";
const displayType =
this._config.display_type ||
(this._config.show_camera ? "camera" : "picture");
this._config.display_type || this._config.show_camera
? "camera"
: "picture";
const schema = this._schema(
this.hass.localize,
displayType,
showCamera,
binarySelectOptions,
sensorSelectOptions
);
const vertical = this._config.vertical && displayType === "compact";
const featuresSchema = this._featuresSchema(this.hass.localize, vertical);
const featuresSchema = this._featuresSchema(this.hass.localize);
const data = {
camera_view: "auto",
alert_classes: DEVICE_CLASSES.binary_sensor,
sensor_classes: DEVICE_CLASSES.sensor,
features_position: "bottom",
display_type: displayType,
content_layout: vertical ? "vertical" : "horizontal",
...this._config,
};
// Default features position to bottom and force it to bottom in vertical mode
if (!data.features_position || vertical) {
data.features_position = "bottom";
}
const hasCompatibleFeatures = this._hasCompatibleFeatures(
this._featureContext
);
@@ -447,12 +420,6 @@ export class HuiAreaCardEditor
delete config.camera_view;
}
// Convert content_layout to vertical
if (config.content_layout) {
config.vertical = config.content_layout === "vertical";
delete config.content_layout;
}
fireEvent(this, "config-changed", { config });
}

View File

@@ -42,7 +42,6 @@ import { supportsLightBrightnessCardFeature } from "../../card-features/hui-ligh
import { supportsLightColorTempCardFeature } from "../../card-features/hui-light-color-temp-card-feature";
import { supportsLockCommandsCardFeature } from "../../card-features/hui-lock-commands-card-feature";
import { supportsLockOpenDoorCardFeature } from "../../card-features/hui-lock-open-door-card-feature";
import { supportsMediaPlayerPlaybackCardFeature } from "../../card-features/hui-media-player-playback-card-feature";
import { supportsMediaPlayerVolumeSliderCardFeature } from "../../card-features/hui-media-player-volume-slider-card-feature";
import { supportsNumericInputCardFeature } from "../../card-features/hui-numeric-input-card-feature";
import { supportsSelectOptionsCardFeature } from "../../card-features/hui-select-options-card-feature";
@@ -96,7 +95,6 @@ const UI_FEATURE_TYPES = [
"light-color-temp",
"lock-commands",
"lock-open-door",
"media-player-playback",
"media-player-volume-slider",
"numeric-input",
"select-options",
@@ -164,7 +162,6 @@ const SUPPORTS_FEATURE_TYPES: Record<
"light-color-temp": supportsLightColorTempCardFeature,
"lock-commands": supportsLockCommandsCardFeature,
"lock-open-door": supportsLockOpenDoorCardFeature,
"media-player-playback": supportsMediaPlayerPlaybackCardFeature,
"media-player-volume-slider": supportsMediaPlayerVolumeSliderCardFeature,
"numeric-input": supportsNumericInputCardFeature,
"select-options": supportsSelectOptionsCardFeature,

View File

@@ -261,17 +261,18 @@ export class HuiTileCardEditor
this._config.hide_state ?? false
);
const vertical = this._config.vertical ?? false;
const featuresSchema = this._featuresSchema(this.hass.localize, vertical);
const featuresSchema = this._featuresSchema(
this.hass.localize,
this._config.vertical ?? false
);
const data = {
...this._config,
content_layout: vertical ? "vertical" : "horizontal",
content_layout: this._config.vertical ? "vertical" : "horizontal",
};
// Default features position to bottom and force it to bottom in vertical mode
if (!data.features_position || vertical) {
if (!data.features_position || data.vertical) {
data.features_position = "bottom";
}

View File

@@ -1,10 +1,5 @@
import type { ActionDetail } from "@material/mwc-list";
import {
mdiClose,
mdiDotsVertical,
mdiFileMoveOutline,
mdiPlaylistEdit,
} from "@mdi/js";
import { mdiClose, mdiDotsVertical, mdiPlaylistEdit } from "@mdi/js";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
@@ -21,10 +16,7 @@ import "../../../../components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import "../../../../components/sl-tab-group";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import {
isStrategyView,
type LovelaceViewConfig,
} from "../../../../data/lovelace/config/view";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
@@ -35,12 +27,6 @@ import {
import "./hui-section-settings-editor";
import "./hui-section-visibility-editor";
import type { EditSectionDialogParams } from "./show-edit-section-dialog";
import { showSelectViewDialog } from "../select-view/show-select-view-dialog";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import { saveConfig } from "../../../../data/lovelace/config/types";
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
import { addSection, deleteSection, moveSection } from "../config-util";
import type { Lovelace } from "../../types";
const TABS = ["tab-settings", "tab-visibility"] as const;
@@ -51,8 +37,6 @@ export class HuiDialogEditSection
{
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public lovelace?: Lovelace;
@state() private _params?: EditSectionDialogParams;
@state() private _config?: LovelaceSectionRawConfig;
@@ -77,8 +61,6 @@ export class HuiDialogEditSection
public async showDialog(params: EditSectionDialogParams): Promise<void> {
this._params = params;
this.lovelace = params.lovelace;
this._config = findLovelaceContainer(this._params.lovelaceConfig, [
this._params.viewIndex,
this._params.sectionIndex,
@@ -183,15 +165,6 @@ export class HuiDialogEditSection
.path=${mdiPlaylistEdit}
></ha-svg-icon>
</ha-list-item>
<ha-list-item graphic="icon">
${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.move_to_view"
)}
<ha-svg-icon
slot="graphic"
.path=${mdiFileMoveOutline}
></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
${!this._yamlMode
? html`
@@ -249,138 +222,9 @@ export class HuiDialogEditSection
case 0:
this._yamlMode = !this._yamlMode;
break;
case 1:
this._openSelectView();
break;
}
}
private _openSelectView(): void {
if (!this._params || !this.lovelace) {
return;
}
showSelectViewDialog(this, {
lovelaceConfig: this._params.lovelaceConfig,
urlPath: this.lovelace.urlPath,
allowDashboardChange: true,
header: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.header"
),
viewSelectedCallback: this._moveSectionToView,
});
}
private _moveSectionToView = async (
urlPath: string | null,
selectedDashConfig: LovelaceConfig,
viewIndex: number
) => {
if (!this._params || !this.lovelace) {
return;
}
const toView = selectedDashConfig.views[viewIndex];
if (isStrategyView(toView)) {
showAlertDialog(this, {
title: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.error_title"
),
text: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.error_text_strategy"
),
warning: true,
});
return;
}
const fromViewIndex = this._params.viewIndex;
const fromSectionIndex = this._params.sectionIndex;
// Same dashboard
if (urlPath === this.lovelace.urlPath) {
const oldConfig = this.lovelace.config;
const toIndex = toView.sections?.length ?? 0;
try {
await this.lovelace.saveConfig(
moveSection(
oldConfig,
[fromViewIndex, fromSectionIndex],
[viewIndex, toIndex]
)
);
this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.success"
),
duration: 4000,
action: {
action: async () => {
await this.lovelace!.saveConfig(oldConfig);
},
text: this.hass!.localize("ui.common.undo"),
},
});
this.closeDialog();
} catch (err: any) {
this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.error"
),
});
// eslint-disable-next-line no-console
console.error(err);
}
return;
}
// Cross dashboard
const oldFromConfig = this.lovelace.config;
const oldToConfig = selectedDashConfig;
try {
const section = findLovelaceContainer(oldFromConfig, [
fromViewIndex,
fromSectionIndex,
]) as LovelaceSectionRawConfig;
await saveConfig(
this.hass!,
urlPath,
addSection(oldToConfig, viewIndex, section)
);
await this.lovelace.saveConfig(
deleteSection(oldFromConfig, fromViewIndex, fromSectionIndex)
);
this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.success"
),
duration: 4000,
action: {
action: async () => {
await saveConfig(this.hass!, urlPath, oldToConfig);
await this.lovelace!.saveConfig(oldFromConfig);
},
text: this.hass!.localize("ui.common.undo"),
},
});
this.closeDialog();
} catch (err: any) {
this.lovelace.showToast({
message: this.hass!.localize(
"ui.panel.lovelace.editor.move_section.error"
),
});
// eslint-disable-next-line no-console
console.error(err);
}
};
private _viewYamlChanged(ev: CustomEvent) {
ev.stopPropagation();
if (!ev.detail.isValid) {

View File

@@ -1,8 +1,6 @@
import type { PropertyValues } from "lit";
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { mdiFormatColorFill } from "@mdi/js";
import { fireEvent } from "../../../../common/dom/fire_event";
import type {
HaFormSchema,
@@ -12,13 +10,9 @@ import "../../../../components/ha-form/ha-form";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
import { hex2rgb, rgb2hex } from "../../../../common/color/convert-color";
interface SettingsData {
column_span?: number;
background_type: "none" | "color";
background_color?: number[];
background_opacity?: number;
}
@customElement("hui-section-settings-editor")
@@ -29,10 +23,8 @@ export class HuiDialogEditSection extends LitElement {
@property({ attribute: false }) public viewConfig!: LovelaceViewConfig;
@state() private _selectorBackgroundType: "none" | "color" = "none";
private _schema = memoizeOne(
(maxColumns: number, enableBackground: boolean) =>
(maxColumns: number) =>
[
{
name: "column_span",
@@ -44,91 +36,15 @@ export class HuiDialogEditSection extends LitElement {
},
},
},
{
name: "styling",
type: "expandable",
flatten: true,
iconPath: mdiFormatColorFill,
schema: [
{
name: "background_settings",
flatten: true,
type: "grid",
schema: [
{
name: "background_type",
required: true,
selector: {
select: {
mode: "dropdown",
options: [
{
value: "none",
label: this.hass.localize(
"ui.panel.lovelace.editor.edit_section.settings.background_type_none_option"
),
},
{
value: "color",
label: this.hass.localize(
"ui.panel.lovelace.editor.edit_section.settings.background_type_color_option"
),
},
],
},
},
},
{
name: "background_color",
selector: {
color_rgb: {},
},
disabled: !enableBackground,
},
{
name: "background_opacity",
selector: {
number: {
min: 0,
max: 100,
step: 1,
mode: "slider",
unit_of_measurement: "%",
},
},
disabled: !enableBackground,
},
],
},
],
},
] as const satisfies HaFormSchema[]
);
protected firstUpdated(_changedProperties: PropertyValues) {
super.firstUpdated(_changedProperties);
if (this.config.style?.background_color) {
this._selectorBackgroundType = "color";
} else {
this._selectorBackgroundType = "none";
}
}
render() {
const data: SettingsData = {
column_span: this.config.column_span || 1,
background_type: this._selectorBackgroundType,
background_color: this.config.style?.background_color
? hex2rgb(this.config.style?.background_color as any)
: [],
background_opacity: this.config.style?.background_opacity || 100,
};
const schema = this._schema(
this.viewConfig.max_columns || 4,
this._selectorBackgroundType === "color"
);
const schema = this._schema(this.viewConfig.max_columns || 4);
return html`
<ha-form
@@ -160,27 +76,11 @@ export class HuiDialogEditSection extends LitElement {
ev.stopPropagation();
const newData = ev.detail.value as SettingsData;
this._selectorBackgroundType = newData.background_type;
const newConfig: LovelaceSectionRawConfig = {
...this.config,
column_span: newData.column_span,
};
if (newData.background_type === "color") {
newConfig.style = {
...newConfig.style,
background_color: rgb2hex(newData.background_color as any),
background_opacity: newData.background_opacity,
};
} else {
newConfig.style = {
...newConfig.style,
background_color: undefined,
background_opacity: undefined,
};
}
fireEvent(this, "value-changed", { value: newConfig });
}
}

View File

@@ -1,9 +1,7 @@
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LovelaceConfig } from "../../../../data/lovelace/config/types";
import type { Lovelace } from "../../types";
export interface EditSectionDialogParams {
lovelace: Lovelace;
lovelaceConfig: LovelaceConfig;
saveConfig: (config: LovelaceConfig) => void;
viewIndex: number;

View File

@@ -1,9 +1,7 @@
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import {
mdiAccount,
mdiCodeBraces,
mdiCommentProcessingOutline,
mdiDevices,
mdiDotsVertical,
mdiFileMultiple,
mdiFormatListBulletedTriangle,
@@ -12,9 +10,7 @@ import {
mdiPencil,
mdiPlus,
mdiRefresh,
mdiRobot,
mdiShape,
mdiSofa,
mdiViewDashboard,
} from "@mdi/js";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
@@ -56,7 +52,6 @@ import {
updateDashboard,
} from "../../data/lovelace/dashboard";
import { getPanelTitle } from "../../data/panel";
import { createPerson } from "../../data/person";
import {
showAlertDialog,
showConfirmationDialog,
@@ -71,10 +66,7 @@ import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-
import { haStyle } from "../../resources/styles";
import type { HomeAssistant, PanelInfo } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import { showNewAutomationDialog } from "../config/automation/show-dialog-new-automation";
import { showAddIntegrationDialog } from "../config/integrations/show-add-integration-dialog";
import { showDashboardDetailDialog } from "../config/lovelace/dashboards/show-dialog-lovelace-dashboard-detail";
import { showPersonDetailDialog } from "../config/person/show-dialog-person-detail";
import { swapView } from "./editor/config-util";
import { showDashboardStrategyEditorDialog } from "./editor/dashboard-strategy-editor/dialogs/show-dialog-dashboard-strategy-editor";
import { showSaveDialog } from "./editor/show-save-config-dialog";
@@ -86,28 +78,6 @@ import "./views/hui-view";
import type { HUIView } from "./views/hui-view";
import "./views/hui-view-background";
import "./views/hui-view-container";
import { showAreaRegistryDetailDialog } from "../config/areas/show-dialog-area-registry-detail";
import { createAreaRegistryEntry } from "../../data/area_registry";
import { showToast } from "../../util/toast";
interface ActionItem {
icon: string;
key: LocalizeKeys;
overflowAction?: any;
buttonAction?: any;
visible: boolean | undefined;
overflow: boolean;
overflow_can_promote?: boolean;
suffix?: string;
subItems?: SubActionItem[];
}
interface SubActionItem {
icon: string;
key: LocalizeKeys;
action?: any;
visible: boolean | undefined;
}
@customElement("hui-root")
class HUIRoot extends LitElement {
@@ -175,7 +145,16 @@ class HUIRoot extends LitElement {
);
}
const items: ActionItem[] = [
const items: {
icon: string;
key: LocalizeKeys;
overflowAction?: any;
buttonAction?: any;
visible: boolean | undefined;
overflow: boolean;
overflow_can_promote?: boolean;
suffix?: string;
}[] = [
{
icon: mdiFormatListBulletedTriangle,
key: "ui.panel.lovelace.unused_entities.title",
@@ -204,45 +183,13 @@ class HUIRoot extends LitElement {
visible: this._editMode && this.hass.userData?.showAdvanced,
overflow: true,
},
{
icon: mdiPlus,
key: "ui.panel.lovelace.menu.add",
visible: !this._editMode && this.hass.user?.is_admin,
overflow: false,
subItems: [
{
icon: mdiDevices,
key: "ui.panel.lovelace.menu.add_device",
visible: true,
action: this._handleAddDevice,
},
{
icon: mdiRobot,
key: "ui.panel.lovelace.menu.create_automation",
visible: true,
action: this._handleACreateAutomation,
},
{
icon: mdiSofa,
key: "ui.panel.lovelace.menu.add_area",
visible: true,
action: this._handleAddArea,
},
{
icon: mdiAccount,
key: "ui.panel.lovelace.menu.invite_person",
visible: true,
action: this._handleInvitePerson,
},
],
},
{
icon: mdiMagnify,
key: "ui.panel.lovelace.menu.search_entities",
buttonAction: this._showQuickBar,
overflowAction: this._handleShowQuickBar,
visible: !this._editMode,
overflow: false,
overflow: this.narrow,
suffix: this.hass.enableShortcuts ? "(E)" : undefined,
},
{
@@ -252,7 +199,7 @@ class HUIRoot extends LitElement {
overflowAction: this._handleShowVoiceCommandDialog,
visible:
!this._editMode && this._conversation(this.hass.config.components),
overflow: false,
overflow: this.narrow,
suffix: this.hass.enableShortcuts ? "(A)" : undefined,
},
{
@@ -300,50 +247,20 @@ class HUIRoot extends LitElement {
(i) => i.visible && (!i.overflow || overflowCanPromote)
);
buttonItems.forEach((item) => {
const label = [this.hass!.localize(item.key), item.suffix].join(" ");
const button = item.subItems
? html`
<ha-button-menu
slot="actionItems"
corner="BOTTOM_END"
menu-corner="END"
>
<ha-icon-button
.label=${label}
.path=${item.icon}
slot="trigger"
></ha-icon-button>
${item.subItems
.filter((subItem) => subItem.visible)
.map(
(subItem) => html`
<ha-list-item
graphic="icon"
.key=${subItem.key}
@request-selected=${subItem.action}
>
${this.hass!.localize(subItem.key)}
<ha-svg-icon
slot="graphic"
.path=${subItem.icon}
></ha-svg-icon>
</ha-list-item>
`
)}
</ha-button-menu>
`
: html`
<ha-tooltip slot="actionItems" placement="bottom" .content=${label}>
<ha-icon-button
.path=${item.icon}
@click=${item.buttonAction}
></ha-icon-button>
</ha-tooltip>
`;
result.push(button);
buttonItems.forEach((i) => {
result.push(
html`<ha-tooltip
slot="actionItems"
placement="bottom"
.content=${[this.hass!.localize(i.key), i.suffix].join(" ")}
>
<ha-icon-button
.path=${i.icon}
@click=${i.buttonAction}
></ha-icon-button>
</ha-tooltip>`
);
});
if (overflowItems.length && !overflowCanPromote) {
const listItems: TemplateResult[] = [];
overflowItems.forEach((i) => {
@@ -456,15 +373,10 @@ class HUIRoot extends LitElement {
})}
</sl-tab-group>`;
const isSubview = curViewConfig?.subview;
const hasTabViews = views.filter((view) => !view.subview).length > 1;
const showTabBar = this._editMode || (!isSubview && hasTabViews);
return html`
<div
class=${classMap({
"edit-mode": this._editMode,
narrow: this.narrow,
})}
>
<div class="header">
@@ -487,7 +399,7 @@ class HUIRoot extends LitElement {
<div class="action-items">${this._renderActionItems()}</div>
`
: html`
${isSubview
${curViewConfig?.subview
? html`
<ha-icon-button-arrow-prev
slot="navigationIcon"
@@ -501,37 +413,34 @@ class HUIRoot extends LitElement {
.narrow=${this.narrow}
></ha-menu-button>
`}
${isSubview
${curViewConfig?.subview
? html`<div class="main-title">${curViewConfig.title}</div>`
: hasTabViews && !showTabBar
: views.filter((view) => !view.subview).length > 1
? tabs
: html`
<div class="main-title">
${curViewConfig?.title ?? dashboardTitle}
${views[0]?.title ?? dashboardTitle}
</div>
`}
<div class="action-items">${this._renderActionItems()}</div>
`}
</div>
${showTabBar
${this._editMode
? html`<div class="edit-tab-bar">
${tabs}
${this._editMode
? html`<ha-icon-button
slot="nav"
id="add-view"
@click=${this._addView}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.add"
)}
.path=${mdiPlus}
></ha-icon-button>`
: nothing}
<ha-icon-button
slot="nav"
id="add-view"
@click=${this._addView}
.label=${this.hass!.localize(
"ui.panel.lovelace.editor.edit_view.add"
)}
.path=${mdiPlus}
></ha-icon-button>
</div>`
: nothing}
</div>
<hui-view-container
class=${showTabBar ? "has-tab-bar" : ""}
.hass=${this.hass}
.theme=${curViewConfig?.theme}
id="view"
@@ -787,79 +696,6 @@ class HUIRoot extends LitElement {
}
}
private async _handleAddDevice(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
await this.hass.loadFragmentTranslation("config");
showAddIntegrationDialog(this);
}
private async _handleACreateAutomation(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
await this.hass.loadFragmentTranslation("config");
showNewAutomationDialog(this, { mode: "automation" });
}
private async _handleAddArea(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
await this.hass.loadFragmentTranslation("config");
showAreaRegistryDetailDialog(this, {
createEntry: async (values) => {
const area = await createAreaRegistryEntry(this.hass, values);
showToast(this, {
message: this.hass.localize(
"ui.panel.lovelace.menu.add_area_success"
),
action: {
action: () => {
navigate(`/config/areas/area/${area.area_id}`);
},
text: this.hass.localize("ui.panel.lovelace.menu.add_area_action"),
},
});
},
});
}
private async _handleInvitePerson(
ev: CustomEvent<RequestSelectedDetail>
): Promise<void> {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
}
await this.hass.loadFragmentTranslation("config");
showPersonDetailDialog(this, {
users: [],
createEntry: async (values) => {
await createPerson(this.hass!, values);
showToast(this, {
message: this.hass.localize(
"ui.panel.lovelace.menu.add_person_success"
),
action: {
action: () => {
navigate(`/config/person`);
},
text: this.hass.localize(
"ui.panel.lovelace.menu.add_person_action"
),
},
});
},
});
}
private _handleRawEditor(ev: CustomEvent<RequestSelectedDetail>): void {
if (!shouldHandleRequestSelectedEvent(ev)) {
return;
@@ -1214,14 +1050,6 @@ class HUIRoot extends LitElement {
margin: var(--margin-title);
line-height: var(--ha-line-height-normal);
flex-grow: 1;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
min-width: 0;
}
.narrow .main-title {
margin: 0;
margin-inline-start: 8px;
}
.action-items {
white-space: nowrap;
@@ -1286,6 +1114,9 @@ class HUIRoot extends LitElement {
--ha-tab-active-text-color: var(--app-header-edit-text-color, #fff);
--ha-tab-indicator-color: var(--app-header-edit-text-color, #fff);
}
.edit-mode sl-tab {
height: 54px;
}
sl-tab {
height: calc(var(--header-height, 56px) - 2px);
}
@@ -1357,10 +1188,9 @@ class HUIRoot extends LitElement {
/**
* In edit mode we have the tab bar on a new line *
*/
hui-view-container.has-tab-bar {
.edit-mode hui-view-container {
padding-top: calc(
var(--header-height) + calc(var(--header-height, 56px) - 2px) +
var(--safe-area-inset-top)
var(--header-height) + 48px + var(--safe-area-inset-top)
);
}
.hide-tab {

View File

@@ -20,7 +20,6 @@ import "../components/hui-card-edit-mode";
import { moveCard } from "../editor/config-util";
import type { LovelaceCardPath } from "../editor/lovelace-path";
import type { Lovelace } from "../types";
import { hex2rgb } from "../../../common/color/convert-color";
const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
delay: 100,
@@ -87,12 +86,6 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
? IMPORT_MODE_CARD_SORTABLE_OPTIONS
: CARD_SORTABLE_OPTIONS;
const backgroundOpacity =
(this._config.style?.background_opacity || 100) / 100;
const background = this._config.style?.background_color
? `rgba(${hex2rgb(this._config.style.background_color)}, ${backgroundOpacity})`
: undefined;
return html`
<ha-sortable
.disabled=${!editMode}
@@ -110,11 +103,7 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
class="container ${classMap({
"edit-mode": editMode,
"import-only": this.importOnly,
"has-background": Boolean(background),
})}"
style=${styleMap({
background: background,
})}
>
${repeat(
cardsConfig,
@@ -261,10 +250,6 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
border: none;
padding: 0 !important;
}
.container.has-background {
padding: 8px;
border-radius: var(--ha-card-border-radius, 12px);
}
.card {
border-radius: var(--ha-card-border-radius, 12px);

View File

@@ -3,7 +3,6 @@ import { customElement } from "lit/decorators";
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
import { clamp } from "../../../../common/number/clamp";
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
@@ -12,11 +11,11 @@ import {
getAreas,
getFloors,
} from "../areas/helpers/areas-strategy-helper";
import { getHomeStructure } from "./helpers/overview-home-structure";
import {
findEntities,
OVERVIEW_SUMMARIES_FILTERS,
} from "./helpers/overview-summaries";
import { getHomeStructure } from "./helpers/overview-home-structure";
export interface OverviewClimateViewStrategyConfig {
type: "overview-climate";
@@ -25,9 +24,10 @@ export interface OverviewClimateViewStrategyConfig {
const processAreasForClimate = (
areaIds: string[],
hass: HomeAssistant,
entities: string[]
): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = [];
entities: string[],
computeTileCard: (entityId: string) => any
): any[] => {
const cards: any[] = [];
for (const areaId of areaIds) {
const area = hass.areas[areaId];
@@ -38,8 +38,6 @@ const processAreasForClimate = (
});
const areaEntities = entities.filter(areaFilter);
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
if (areaEntities.length > 0) {
cards.push({
heading_style: "subtitle",
@@ -83,6 +81,8 @@ export class OverviewClimateViewStrategy extends ReactiveElement {
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
// Process floors
for (const floorStructure of home.floors) {
const floorId = floorStructure.id;
@@ -100,7 +100,12 @@ export class OverviewClimateViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForClimate(areaIds, hass, entities);
const areaCards = processAreasForClimate(
areaIds,
hass,
entities,
computeTileCard
);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);
@@ -121,7 +126,12 @@ export class OverviewClimateViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForClimate(home.areas, hass, entities);
const areaCards = processAreasForClimate(
home.areas,
hass,
entities,
computeTileCard
);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);

View File

@@ -1,16 +1,22 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import { clamp } from "../../../../common/number/clamp";
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
import type { AreaRegistryEntry } from "../../../../data/area_registry";
import type { LovelaceSectionConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
import { getAreaControlEntities } from "../../card-features/hui-area-controls-card-feature";
import { AREA_CONTROLS, type AreaControl } from "../../card-features/types";
import type {
AreaCardConfig,
ButtonCardConfig,
HeadingCardConfig,
MarkdownCardConfig,
TileCardConfig,
} from "../../cards/types";
import { getAreas } from "../areas/helpers/areas-strategy-helper";
import { getAreas, getFloors } from "../areas/helpers/areas-strategy-helper";
import { getHomeStructure } from "./helpers/overview-home-structure";
import { OVERVIEW_SUMMARIES_ICONS } from "./helpers/overview-summaries";
export interface OverviewHomeViewStrategyConfig {
@@ -25,22 +31,42 @@ const computeAreaCard = (
const area = hass.areas[areaId] as AreaRegistryEntry | undefined;
const path = `areas-${areaId}`;
const controls: AreaControl[] = AREA_CONTROLS.filter(
(a) => a !== "switch" // Exclude switches control for areas as we don't know what the switches control
);
const controlEntities = getAreaControlEntities(controls, areaId, [], hass);
const filteredControls = controls.filter(
(control) => controlEntities[control].length > 0
);
const sensorClasses: string[] = [];
if (area?.temperature_entity_id) {
sensorClasses.push("temperature");
}
if (area?.humidity_entity_id) {
sensorClasses.push("humidity");
}
return {
type: "area",
area: areaId,
display_type: "compact",
sensor_classes: sensorClasses,
navigation_path: path,
vertical: true,
features: filteredControls.length
? [
{
type: "area-controls",
controls: filteredControls,
},
]
: [],
grid_options: {
rows: 2,
columns: 4,
rows: 1,
columns: 12,
},
features_position: "inline",
navigation_path: path,
};
};
@@ -50,25 +76,58 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
config: OverviewHomeViewStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceViewConfig> {
const floors = getFloors(hass.floors);
const areas = getAreas(hass.areas);
const areasSection: LovelaceSectionConfig = {
type: "grid",
column_span: 2,
cards: [
{
const home = getHomeStructure(floors, areas);
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
const floorsSections: LovelaceSectionConfig[] = home.floors.map(
(floorStructure) => {
const floorId = floorStructure.id;
const areaIds = floorStructure.areas;
const floor = hass.floors[floorId];
const headingCard: HeadingCardConfig = {
type: "heading",
heading_style: "title",
heading: "Areas",
},
...areas.map<AreaCardConfig>((area) =>
computeAreaCard(area.area_id, hass)
),
],
};
heading: floorCount > 1 ? floor.name : "Areas",
icon: floor.icon || floorDefaultIcon(floor),
};
const areasCards = areaIds.map<AreaCardConfig>((areaId) =>
computeAreaCard(areaId, hass)
);
return {
max_columns: 3,
type: "grid",
cards: [headingCard, ...areasCards],
};
}
);
if (home.areas.length > 0) {
floorsSections.push({
type: "grid",
max_columns: 3,
cards: [
{
type: "heading",
heading_style: "title",
icon: "mdi:home",
heading: floorCount > 1 ? "Other areas" : "Areas",
},
...home.areas.map<AreaCardConfig>((areaId) =>
computeAreaCard(areaId, hass)
),
],
} as LovelaceSectionConfig);
}
// Allow between 2 and 3 columns (the max should be set to define the width of the header)
const maxColumns = 2;
const maxColumns = clamp(floorsSections.length, 2, 3);
const favoriteSection: LovelaceSectionConfig = {
type: "grid",
@@ -84,8 +143,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
favoriteSection.cards!.push(
{
type: "heading",
heading: "",
heading_style: "subtitle",
heading: "Quick actions",
},
...favoriteEntities.map(
(entityId) =>
@@ -109,7 +167,6 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
type: "button",
icon: OVERVIEW_SUMMARIES_ICONS.lights,
name: "Lights",
icon_height: "24px",
grid_options: {
rows: 2,
columns: 4,
@@ -123,7 +180,6 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
type: "button",
icon: OVERVIEW_SUMMARIES_ICONS.climate,
name: "Climate",
icon_height: "30px",
grid_options: {
rows: 2,
columns: 4,
@@ -137,7 +193,6 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
type: "button",
icon: OVERVIEW_SUMMARIES_ICONS.security,
name: "Security",
icon_height: "30px",
grid_options: {
rows: 2,
columns: 4,
@@ -151,7 +206,6 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
type: "button",
icon: OVERVIEW_SUMMARIES_ICONS.media_players,
name: "Media Players",
icon_height: "30px",
grid_options: {
rows: 2,
columns: 4,
@@ -165,7 +219,6 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
type: "button",
icon: "mdi:lightning-bolt",
name: "Energy",
icon_height: "30px",
grid_options: {
rows: 2,
columns: 4,
@@ -181,7 +234,7 @@ export class OverviewHomeViewStrategy extends ReactiveElement {
const sections = [
...(favoriteSection.cards ? [favoriteSection] : []),
summarySection,
areasSection,
...floorsSections,
];
return {
type: "sections",

View File

@@ -3,7 +3,6 @@ import { customElement } from "lit/decorators";
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
import { clamp } from "../../../../common/number/clamp";
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
@@ -12,11 +11,11 @@ import {
getAreas,
getFloors,
} from "../areas/helpers/areas-strategy-helper";
import { getHomeStructure } from "./helpers/overview-home-structure";
import {
findEntities,
OVERVIEW_SUMMARIES_FILTERS,
} from "./helpers/overview-summaries";
import { getHomeStructure } from "./helpers/overview-home-structure";
export interface OverviewLightsViewStrategyConfig {
type: "overview-lights";
@@ -25,9 +24,10 @@ export interface OverviewLightsViewStrategyConfig {
const processAreasForLights = (
areaIds: string[],
hass: HomeAssistant,
entities: string[]
): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = [];
entities: string[],
computeTileCard: (entityId: string) => any
): any[] => {
const cards: any[] = [];
for (const areaId of areaIds) {
const area = hass.areas[areaId];
@@ -38,8 +38,6 @@ const processAreasForLights = (
});
const areaLights = entities.filter(areaFilter);
const computeTileCard = computeAreaTileCardConfig(hass, "", false);
if (areaLights.length > 0) {
cards.push({
heading_style: "subtitle",
@@ -83,6 +81,8 @@ export class OverviewLightsViewStrategy extends ReactiveElement {
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
// Process floors
for (const floorStructure of home.floors) {
const floorId = floorStructure.id;
@@ -100,7 +100,12 @@ export class OverviewLightsViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForLights(areaIds, hass, entities);
const areaCards = processAreasForLights(
areaIds,
hass,
entities,
computeTileCard
);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);
@@ -121,7 +126,12 @@ export class OverviewLightsViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForLights(home.areas, hass, entities);
const areaCards = processAreasForLights(
home.areas,
hass,
entities,
computeTileCard
);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);

View File

@@ -3,17 +3,16 @@ import { customElement } from "lit/decorators";
import { generateEntityFilter } from "../../../../common/entity/entity_filter";
import { clamp } from "../../../../common/number/clamp";
import { floorDefaultIcon } from "../../../../components/ha-floor-icon";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
import type { LovelaceSectionRawConfig } from "../../../../data/lovelace/config/section";
import type { LovelaceViewConfig } from "../../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../../types";
import type { MediaControlCardConfig } from "../../cards/types";
import { getAreas, getFloors } from "../areas/helpers/areas-strategy-helper";
import { getHomeStructure } from "./helpers/overview-home-structure";
import {
findEntities,
OVERVIEW_SUMMARIES_FILTERS,
} from "./helpers/overview-summaries";
import { getHomeStructure } from "./helpers/overview-home-structure";
export interface OvervieMediaPlayersViewStrategyConfig {
type: "overview-media-players";
@@ -23,8 +22,8 @@ const processAreasForMediaPlayers = (
areaIds: string[],
hass: HomeAssistant,
entities: string[]
): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = [];
): any[] => {
const cards: any[] = [];
for (const areaId of areaIds) {
const area = hass.areas[areaId];

View File

@@ -16,7 +16,6 @@ import {
OVERVIEW_SUMMARIES_FILTERS,
} from "./helpers/overview-summaries";
import { getHomeStructure } from "./helpers/overview-home-structure";
import type { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
export interface OverviewSecurityViewStrategyConfig {
type: "overview-security";
@@ -25,9 +24,10 @@ export interface OverviewSecurityViewStrategyConfig {
const processAreasForSecurity = (
areaIds: string[],
hass: HomeAssistant,
entities: string[]
): LovelaceCardConfig[] => {
const cards: LovelaceCardConfig[] = [];
entities: string[],
computeTileCard: (entityId: string) => any
): any[] => {
const cards: any[] = [];
for (const areaId of areaIds) {
const area = hass.areas[areaId];
@@ -38,8 +38,6 @@ const processAreasForSecurity = (
});
const areaEntities = entities.filter(areaFilter);
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
if (areaEntities.length > 0) {
cards.push({
heading_style: "subtitle",
@@ -83,6 +81,8 @@ export class OverviewSecurityViewStrategy extends ReactiveElement {
const floorCount = home.floors.length + (home.areas.length ? 1 : 0);
const computeTileCard = computeAreaTileCardConfig(hass, "", true);
// Process floors
for (const floorStructure of home.floors) {
const floorId = floorStructure.id;
@@ -100,7 +100,12 @@ export class OverviewSecurityViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForSecurity(areaIds, hass, entities);
const areaCards = processAreasForSecurity(
areaIds,
hass,
entities,
computeTileCard
);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);
@@ -121,7 +126,12 @@ export class OverviewSecurityViewStrategy extends ReactiveElement {
],
};
const areaCards = processAreasForSecurity(home.areas, hass, entities);
const areaCards = processAreasForSecurity(
home.areas,
hass,
entities,
computeTileCard
);
if (areaCards.length > 0) {
section.cards!.push(...areaCards);

View File

@@ -379,7 +379,6 @@ export class SectionsView extends LitElement implements LovelaceViewElement {
const index = ev.currentTarget.index;
showEditSectionDialog(this, {
lovelace: this.lovelace!,
lovelaceConfig: this.lovelace!.config,
saveConfig: (newConfig) => {
this.lovelace!.saveConfig(newConfig);

View File

@@ -8,6 +8,10 @@ export const waMainStyles = css`
--wa-focus-ring-offset: 2px;
--wa-focus-ring: var(--wa-focus-ring-style) var(--wa-focus-ring-width)
var(--wa-focus-ring-color);
--wa-space-l: 24px;
--wa-shadow-l: 0 8px 8px -4px rgba(0, 0, 0, 0.2);
--wa-form-control-padding-block: 0.75em;
}
`;

View File

@@ -7,21 +7,11 @@ import {
import type { Constructor } from "../types";
import type { HassBaseEl } from "./hass-base-mixin";
declare global {
interface HASSDomEvents {
/**
* Dispatched to open the automation editor.
* Used by custom cards/panels to trigger the editor view.
*/
"hass-automation-editor": ShowAutomationEditorParams;
}
}
export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
class extends superClass {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.addEventListener("hass-automation-editor", (ev) =>
this.addEventListener("show-automation-editor", (ev) =>
this._handleShowAutomationEditor(
ev as HASSDomEvent<ShowAutomationEditorParams>
)

View File

@@ -413,7 +413,6 @@
"add": "Add",
"create": "Create",
"edit": "Edit",
"edit_item": "Edit {name}",
"submit": "Submit",
"rename": "Rename",
"search": "[%key:ui::components::data-table::search%]",
@@ -440,8 +439,7 @@
"replace": "Replace",
"append": "Append",
"supports_markdown": "Supports {markdown_help_link}",
"markdown": "Markdown",
"none": "None"
"markdown": "Markdown"
},
"components": {
"selectors": {
@@ -5339,6 +5337,8 @@
"introduction": "Zones allow you to specify certain regions on Earth. When a person is within a zone, the state will take the name from the zone. Zones can also be used as a trigger or condition inside automation setups.",
"no_zones_created_yet": "Looks like you have not created any zones yet.",
"create_zone": "Create zone",
"edit_zone": "Edit zone",
"edit_home": "Edit home",
"confirm_delete": "Are you sure you want to delete this zone?",
"can_not_edit": "Unable to edit zone",
"configured_in_yaml": "Zones configured via configuration.yaml cannot be edited via the UI.",
@@ -6968,16 +6968,7 @@
"assist_tooltip": "Assist",
"reload_resources": "Reload resources",
"exit_edit_mode": "Done",
"close": "Close",
"add": "Add to Home Assistant",
"add_device": "Add device",
"create_automation": "Create automation",
"add_area": "Add area",
"add_area_success": "Area added",
"add_area_action": "View area",
"add_person_success": "Person added",
"add_person_action": "View persons",
"invite_person": "Invite person"
"close": "Close"
},
"reload_resources": {
"refresh_header": "Do you want to refresh?",
@@ -7104,8 +7095,7 @@
"error_same_url": "You cannot save a view with the same URL as a different existing view.",
"error_invalid_path": "URL contains invalid/reserved characters. Please enter a simple string only for the path of this view.",
"error_number": "URL may not be a number.",
"move_to_dashboard": "Move to dashboard",
"move_to_view": "Move to view"
"move_to_dashboard": "Move to dashboard"
},
"edit_view_header": {
"add_title": "Add title",
@@ -7211,13 +7201,6 @@
"header": "[%key:ui::panel::lovelace::editor::suggest_card::header%]",
"add": "[%key:ui::panel::lovelace::editor::suggest_card::add%]"
},
"move_section": {
"header": "Choose a view to move the section to",
"strategy_error_title": "Impossible to move the section",
"strategy_error_text_strategy": "Moving a section to an auto-generated view is not supported.",
"success": "Section moved successfully",
"error": "Error while moving section"
},
"move_card": {
"header": "Choose a view to move the card to",
"strategy_error_title": "Impossible to move the card",
@@ -7266,13 +7249,7 @@
"edit_yaml": "[%key:ui::panel::lovelace::editor::edit_view::edit_yaml%]",
"settings": {
"column_span": "Width",
"column_span_helper": "Larger sections will be made smaller to fit the display. (e.g. on mobile devices)",
"styling": "Styling",
"background_type": "Background type",
"background_type_none_option": "None",
"background_type_color_option": "Color",
"background_color": "Background color",
"background_opacity": "Background opacity"
"column_span_helper": "Larger sections will be made smaller to fit the display. (e.g. on mobile devices)"
},
"visibility": {
"explanation": "The section will be shown when ALL conditions below are fulfilled. If no conditions are set, the section will always be shown."
@@ -7386,11 +7363,6 @@
"sensor_classes": "Sensor classes",
"description": "The Area card automatically displays entities of a specific area.",
"display_type": "Display type",
"content_layout": "[%key:ui::panel::lovelace::editor::card::tile::content_layout%]",
"content_layout_options": {
"horizontal": "[%key:ui::panel::lovelace::editor::card::tile::content_layout_options::horizontal%]",
"vertical": "[%key:ui::panel::lovelace::editor::card::tile::content_layout_options::vertical%]"
},
"display_type_options": {
"compact": "Compact",
"icon": "Area icon",
@@ -7933,9 +7905,6 @@
"lock-open-door": {
"label": "Lock open door"
},
"media-player-playback": {
"label": "Media player playback controls"
},
"media-player-volume-slider": {
"label": "Media player volume slider"
},

253
yarn.lock
View File

@@ -3869,22 +3869,22 @@ __metadata:
languageName: node
linkType: hard
"@rsdoctor/client@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/client@npm:1.2.3"
checksum: 10/4e02c72ded9f7cfa5dc9e224513d4f0cb4d36908fbd62353170148516ad742cd4c9573e2ee90b335d243f8c56f33b2bbd0cc51a3b65000f1a166c2b4c8dd9bd7
"@rsdoctor/client@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/client@npm:1.2.2"
checksum: 10/ae2e8dbbf17522c63ab1042391514706116cce73e9435293c1cced593bcacc5034b85cb7b0bcc09cf36d6350ff21680cc4e14d08728a95d98119db07c163f2d4
languageName: node
linkType: hard
"@rsdoctor/core@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/core@npm:1.2.3"
"@rsdoctor/core@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/core@npm:1.2.2"
dependencies:
"@rsbuild/plugin-check-syntax": "npm:1.3.0"
"@rsdoctor/graph": "npm:1.2.3"
"@rsdoctor/sdk": "npm:1.2.3"
"@rsdoctor/types": "npm:1.2.3"
"@rsdoctor/utils": "npm:1.2.3"
"@rsdoctor/graph": "npm:1.2.2"
"@rsdoctor/sdk": "npm:1.2.2"
"@rsdoctor/types": "npm:1.2.2"
"@rsdoctor/utils": "npm:1.2.2"
axios: "npm:^1.11.0"
browserslist-load-config: "npm:^1.0.0"
enhanced-resolve: "npm:5.12.0"
@@ -3894,67 +3894,70 @@ __metadata:
path-browserify: "npm:1.0.1"
semver: "npm:^7.7.2"
source-map: "npm:^0.7.4"
checksum: 10/45c39b966fb879f8770ced60d47d16b42098fc6642064bc528c6b1a9cd0724210b0af5f17f031ae9738884c75b034ea402f34f3d2737e608ce3ebef08fb6909c
source-map-js: "npm:^1.2.1"
checksum: 10/85fe4ef72d1ca7223a6df638ae7b8ca491665d2df37b115dc229569b3a6fe9b6855f7895e4975c48f3a8056ccc66e4cc941c1b2c5c58f3bb7c1061434e24983a
languageName: node
linkType: hard
"@rsdoctor/graph@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/graph@npm:1.2.3"
"@rsdoctor/graph@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/graph@npm:1.2.2"
dependencies:
"@rsdoctor/types": "npm:1.2.3"
"@rsdoctor/utils": "npm:1.2.3"
"@rsdoctor/types": "npm:1.2.2"
"@rsdoctor/utils": "npm:1.2.2"
lodash.unionby: "npm:^4.8.0"
socket.io: "npm:4.8.1"
source-map: "npm:^0.7.4"
checksum: 10/fea12dfdad880f440ea2c3a06c95a92dcbcd78efac6ff03af50c56e40007239b63303ae267ca00e1360e2af559513173cdd0743c7635721cb8ac16e19cda170d
checksum: 10/e17b30fb96e8fb5f9344fe08edcfc7a8a37dd4d8e111cbd1fe5ee0f5748143beb1c77a98d90b9fa1a50eb636c8a427e35560a548ec8a55cacea8ec79a58d3852
languageName: node
linkType: hard
"@rsdoctor/rspack-plugin@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/rspack-plugin@npm:1.2.3"
"@rsdoctor/rspack-plugin@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/rspack-plugin@npm:1.2.2"
dependencies:
"@rsdoctor/core": "npm:1.2.3"
"@rsdoctor/graph": "npm:1.2.3"
"@rsdoctor/sdk": "npm:1.2.3"
"@rsdoctor/types": "npm:1.2.3"
"@rsdoctor/utils": "npm:1.2.3"
"@rsdoctor/core": "npm:1.2.2"
"@rsdoctor/graph": "npm:1.2.2"
"@rsdoctor/sdk": "npm:1.2.2"
"@rsdoctor/types": "npm:1.2.2"
"@rsdoctor/utils": "npm:1.2.2"
lodash: "npm:^4.17.21"
peerDependencies:
"@rspack/core": "*"
peerDependenciesMeta:
"@rspack/core":
optional: true
checksum: 10/59abb902046fb791a754e02b0eff5e1674417cfc3f6556d164fc6bed0af0eb0aa52bc087c0b0e8d1c42659d4cbaffd497550376799cbedbae4f4f76882b26924
checksum: 10/040cd21f9c9382edbbea6b1e9b9c621956a8a56271321a7de55fff138ab49f3eb75e1dd26414487bca7d2034c975ecc9ecb1b394b1466c97f6592721ae76fb4e
languageName: node
linkType: hard
"@rsdoctor/sdk@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/sdk@npm:1.2.3"
"@rsdoctor/sdk@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/sdk@npm:1.2.2"
dependencies:
"@rsdoctor/client": "npm:1.2.3"
"@rsdoctor/graph": "npm:1.2.3"
"@rsdoctor/types": "npm:1.2.3"
"@rsdoctor/utils": "npm:1.2.3"
"@rsdoctor/client": "npm:1.2.2"
"@rsdoctor/graph": "npm:1.2.2"
"@rsdoctor/types": "npm:1.2.2"
"@rsdoctor/utils": "npm:1.2.2"
"@types/fs-extra": "npm:^11.0.4"
body-parser: "npm:1.20.3"
cors: "npm:2.8.5"
dayjs: "npm:1.11.13"
fs-extra: "npm:^11.1.1"
json-cycle: "npm:^1.5.0"
lodash: "npm:^4.17.21"
open: "npm:^8.4.2"
sirv: "npm:2.0.4"
socket.io: "npm:4.8.1"
source-map: "npm:^0.7.4"
tapable: "npm:2.2.2"
checksum: 10/3d39ff6a4cd074080b517a6daccf480d88b4f75bd0148748b64f5bd074a05e30a1de74c526d1d6e764a7b7763abc83e53e9ac717eb09cf786eb721b411609a5c
checksum: 10/15aa9298c01747dce5732698ec646b7db54df3a7753db417b14c2eaaaf55c769080334db07598039d0dda97017f7c127277ca69531de4756c24e8c8049486262
languageName: node
linkType: hard
"@rsdoctor/types@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/types@npm:1.2.3"
"@rsdoctor/types@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/types@npm:1.2.2"
dependencies:
"@types/connect": "npm:3.4.38"
"@types/estree": "npm:1.0.5"
@@ -3968,16 +3971,16 @@ __metadata:
optional: true
webpack:
optional: true
checksum: 10/e2971d683ee0b138387e383f7d171c573e97ad2b91a906dc7a2e091a3b17cf83a4d5c921f69de6929d408c1c87cc9b2b409235ad5b02d7b8a15506137b04e568
checksum: 10/67d340e25fcc060eacff632ac5c5556df646f91853d423776ae3a1ee677e2a6a14a17e5838e6e9487256646efbabb168ca96acfb27f4a4d147f4a3fdd0f31ef4
languageName: node
linkType: hard
"@rsdoctor/utils@npm:1.2.3":
version: 1.2.3
resolution: "@rsdoctor/utils@npm:1.2.3"
"@rsdoctor/utils@npm:1.2.2":
version: 1.2.2
resolution: "@rsdoctor/utils@npm:1.2.2"
dependencies:
"@babel/code-frame": "npm:7.26.2"
"@rsdoctor/types": "npm:1.2.3"
"@rsdoctor/types": "npm:1.2.2"
"@types/estree": "npm:1.0.5"
acorn: "npm:^8.10.0"
acorn-import-attributes: "npm:^1.9.5"
@@ -3993,7 +3996,7 @@ __metadata:
picocolors: "npm:^1.1.1"
rslog: "npm:^1.2.9"
strip-ansi: "npm:^6.0.1"
checksum: 10/4e6bcce13fd81872766e18eea97439d9bd117b2aba275e2a5a9ff7ec4d65c3ad44b8535ee3adcec70d16969865f4a53144dc692af6f66aaacedd656f63b2da57
checksum: 10/7290b2239a3bba1207171b4942c3598ac19ced09667cf6ca682af2e96b3e578b6611a36861f68a186a74e2a983087c1b3436bf5dbfdd0edbc43d5042c6a08d1e
languageName: node
linkType: hard
@@ -4991,67 +4994,76 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.40.0"
"@typescript-eslint/eslint-plugin@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/eslint-plugin@npm:8.39.1"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.40.0"
"@typescript-eslint/type-utils": "npm:8.40.0"
"@typescript-eslint/utils": "npm:8.40.0"
"@typescript-eslint/visitor-keys": "npm:8.40.0"
"@typescript-eslint/scope-manager": "npm:8.39.1"
"@typescript-eslint/type-utils": "npm:8.39.1"
"@typescript-eslint/utils": "npm:8.39.1"
"@typescript-eslint/visitor-keys": "npm:8.39.1"
graphemer: "npm:^1.4.0"
ignore: "npm:^7.0.0"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
"@typescript-eslint/parser": ^8.40.0
"@typescript-eslint/parser": ^8.39.1
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/9df4d4ac58734a34964b791622dcb94ffc6c49c1d0f4fd0480b3fc0e026527df7167ff78a4f8bbd29089d605756c28c1a90b2f0653df34b40ac8b969bc6c92e9
checksum: 10/446050aa43d54c0107c7c927ae1f68a4384c2bba514d5c22edabbe355426cb37bd5bb5a3faf240a6be8ef06f68de6099c2a53d9cbb1849ed35a152fb156171e2
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/parser@npm:8.40.0"
"@typescript-eslint/parser@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/parser@npm:8.39.1"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.40.0"
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/typescript-estree": "npm:8.40.0"
"@typescript-eslint/visitor-keys": "npm:8.40.0"
"@typescript-eslint/scope-manager": "npm:8.39.1"
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/typescript-estree": "npm:8.39.1"
"@typescript-eslint/visitor-keys": "npm:8.39.1"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/1e60f70e9d02f930553db7f4684c27c376fadf345db155414a22d1a32cd21def7d36496bd63c1acbf3afbec9fb8794947e880f88c1143b83e1d3c45146cec41a
checksum: 10/ff45ce76353ed564e0f9db47b02b4b20895c96182b3693c610ef3dbceda373c476037a99f90d9f28633c192f301e5d554c89e1ba72da216763f960648ddf1f34
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/project-service@npm:8.40.0"
"@typescript-eslint/project-service@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/project-service@npm:8.39.1"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.40.0"
"@typescript-eslint/types": "npm:^8.40.0"
"@typescript-eslint/tsconfig-utils": "npm:^8.39.1"
"@typescript-eslint/types": "npm:^8.39.1"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/86491aa65c1dd78c9784dddd8467601aef8be652c5fb3a901e8b1995cf07c1dbe11d0ab4610d770e3f4063c0c254a6c6aa5fb7cf724bf12fa4ee56f47f3a2955
checksum: 10/1970633d1a338190f0125e186beaa39b3ef912f287e4815934faf64b72f140e87fdf7d861962683635a450d270dd76faf0c865d72bfd57b471a36739f943676b
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/scope-manager@npm:8.40.0"
"@typescript-eslint/scope-manager@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/scope-manager@npm:8.39.1"
dependencies:
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/visitor-keys": "npm:8.40.0"
checksum: 10/0c5aa10208bfbb506bf3925a420c3de667298064bde400f03ee52c19cd0785dd05c2c820e05724d005737e2920d925aff0318ec3308156f9b81c84736a1fe46b
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/visitor-keys": "npm:8.39.1"
checksum: 10/8874f7479043b3fc878f2c04b2c565051deceb7e425a8e4e79a7f40f1ee696bb979bd91fff619e016fe6793f537b30609c0ee8a5c40911c4829fa264863f7a70
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.40.0, @typescript-eslint/tsconfig-utils@npm:^8.40.0":
"@typescript-eslint/tsconfig-utils@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/tsconfig-utils@npm:8.39.1"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/38c1e1982504e606e525ad0ce47fdb4c7acc686a28a94c2b30fe988c439977e991ce69cb88a1724a41a8096fc2d18d7ced7fe8725e42879d841515ff36a37ecf
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:^8.39.1":
version: 8.40.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.40.0"
peerDependencies:
@@ -5060,37 +5072,44 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/type-utils@npm:8.40.0"
"@typescript-eslint/type-utils@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/type-utils@npm:8.39.1"
dependencies:
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/typescript-estree": "npm:8.40.0"
"@typescript-eslint/utils": "npm:8.40.0"
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/typescript-estree": "npm:8.39.1"
"@typescript-eslint/utils": "npm:8.39.1"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/296c718330b2ac4408840258c30c01072de01ffe1c009be00c5049be1b19a71cbb2e258363ae349150760bcd2d34799df305b4cfc4d7f3b2fa9760ac8ffb3f75
checksum: 10/1195d65970f79f820558810f7e1edf0ea360bbeee55841fdbb71b5b40c09f1a65741b67a70b85c2834ae1f9a027b82da4234d01f42ab4e85dceef3eea84bfdaa
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.40.0, @typescript-eslint/types@npm:^8.40.0":
"@typescript-eslint/types@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/types@npm:8.39.1"
checksum: 10/8013f4f48a98da0de270d5fef1ff28b35407de82fce5acf3efa212fce60bc92a81bbb15b4b358d9facf4f161e49feec856fbf1a6d96f5027d013b542f2fe1bcc
languageName: node
linkType: hard
"@typescript-eslint/types@npm:^8.39.1":
version: 8.40.0
resolution: "@typescript-eslint/types@npm:8.40.0"
checksum: 10/f3931d0920a42b3bc69e9cdeb67a0c710597271cdd9d7c736302bdc52d21df1c962082df7cd712eeabd2c47658415d0a4b7d72f819cb38f82f4e234b48dbaa57
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/typescript-estree@npm:8.40.0"
"@typescript-eslint/typescript-estree@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/typescript-estree@npm:8.39.1"
dependencies:
"@typescript-eslint/project-service": "npm:8.40.0"
"@typescript-eslint/tsconfig-utils": "npm:8.40.0"
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/visitor-keys": "npm:8.40.0"
"@typescript-eslint/project-service": "npm:8.39.1"
"@typescript-eslint/tsconfig-utils": "npm:8.39.1"
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/visitor-keys": "npm:8.39.1"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@@ -5099,32 +5118,32 @@ __metadata:
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/2e61ecfecb933f644799a7c11e4c7a730df57290c8d0482082cff7739b2401b0cf3b1ebef7b08a54a90285978957a49850d1a53061e8770164da651172ebee32
checksum: 10/07ed9d7ab4d146ee3ce6cf82ffebf947e045a9289b01522e11b3985b64f590c00cac0ca10366df828ca213bf08216a67c7b2b76e7c8be650df2511a7e6385425
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/utils@npm:8.40.0"
"@typescript-eslint/utils@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/utils@npm:8.39.1"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.0"
"@typescript-eslint/scope-manager": "npm:8.40.0"
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/typescript-estree": "npm:8.40.0"
"@typescript-eslint/scope-manager": "npm:8.39.1"
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/typescript-estree": "npm:8.39.1"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/b4cd1e6a4f55cc6475189de12e6bd418080a227e794745a2af304ab21655b031c28dae6387d4e9b54dd2f420696cec4f77cca9c66db405ed2281e0e09c95ba1c
checksum: 10/39bb105f26aa1ba234ad7d284c277cbd66df9d51e245094892db140aac80d3656d0480f133b2db54e87af3ef9c371a12973120c9cfbff71e8e85865f9e1d0077
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/visitor-keys@npm:8.40.0"
"@typescript-eslint/visitor-keys@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/visitor-keys@npm:8.39.1"
dependencies:
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/types": "npm:8.39.1"
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10/191f47998001a5e9cdde7491b0215d9c6c45c637aedd7d32cafd35ce2a4a0f4b8582edab015e09238f48e025a788b99efd8e70e4e3200e32143f91c95112abcd
checksum: 10/6d4e4d0b19ebb3f21b692bbb0dcf9961876ca28cdf502296888a78eb4cd802a2ec8d3d5721d19970411edfd1c06f3e272e4057014c859ee1f0546804d07945e3
languageName: node
linkType: hard
@@ -8138,16 +8157,16 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-unused-imports@npm:4.2.0":
version: 4.2.0
resolution: "eslint-plugin-unused-imports@npm:4.2.0"
"eslint-plugin-unused-imports@npm:4.1.4":
version: 4.1.4
resolution: "eslint-plugin-unused-imports@npm:4.1.4"
peerDependencies:
"@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0
eslint: ^9.0.0 || ^8.0.0
peerDependenciesMeta:
"@typescript-eslint/eslint-plugin":
optional: true
checksum: 10/99285bd61a46ad5eaacd54525c04564feed3529dc2ae7800c736b7f4f011daa370133adb481677ad840f020681c0f409f3edfe3bc8f9f234c42bf4674bc4b576
checksum: 10/8e987028ad925ce1e04c01dcae70adbf44c2878a8b15c4327b33a2861e471d7fe00f6fe213fbd2b936f3fcefc8ccabb0d778aa1d6e0e0387a3dc7fe150cd4ed4
languageName: node
linkType: hard
@@ -9440,7 +9459,7 @@ __metadata:
"@octokit/plugin-retry": "npm:8.0.1"
"@octokit/rest": "npm:22.0.0"
"@replit/codemirror-indentation-markers": "npm:6.5.3"
"@rsdoctor/rspack-plugin": "npm:1.2.3"
"@rsdoctor/rspack-plugin": "npm:1.2.2"
"@rspack/cli": "npm:1.4.11"
"@rspack/core": "npm:1.4.11"
"@shoelace-style/shoelace": "npm:2.20.1"
@@ -9498,7 +9517,7 @@ __metadata:
eslint-plugin-import: "npm:2.32.0"
eslint-plugin-lit: "npm:2.1.1"
eslint-plugin-lit-a11y: "npm:5.1.1"
eslint-plugin-unused-imports: "npm:4.2.0"
eslint-plugin-unused-imports: "npm:4.1.4"
eslint-plugin-wc: "npm:3.0.1"
fancy-log: "npm:2.0.0"
fs-extra: "npm:11.3.1"
@@ -9552,7 +9571,7 @@ __metadata:
tinykeys: "npm:3.0.0"
ts-lit-plugin: "npm:2.0.2"
typescript: "npm:5.9.2"
typescript-eslint: "npm:8.40.0"
typescript-eslint: "npm:8.39.1"
ua-parser-js: "npm:2.0.4"
vite-tsconfig-paths: "npm:5.1.4"
vitest: "npm:3.2.4"
@@ -14600,18 +14619,18 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.40.0":
version: 8.40.0
resolution: "typescript-eslint@npm:8.40.0"
"typescript-eslint@npm:8.39.1":
version: 8.39.1
resolution: "typescript-eslint@npm:8.39.1"
dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.40.0"
"@typescript-eslint/parser": "npm:8.40.0"
"@typescript-eslint/typescript-estree": "npm:8.40.0"
"@typescript-eslint/utils": "npm:8.40.0"
"@typescript-eslint/eslint-plugin": "npm:8.39.1"
"@typescript-eslint/parser": "npm:8.39.1"
"@typescript-eslint/typescript-estree": "npm:8.39.1"
"@typescript-eslint/utils": "npm:8.39.1"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/b96dc4e70bd551e5399b928e946957cce622cba89f0ff87521f9e93f223acbe406930a1ebee845b158f586959cb7d85f15ea2250b97341aa87f50a3c987d068a
checksum: 10/1c29c18f2e93b8b74d019590196b158006d7c65be87a56a4c953e52a9c4c40280a42f8ff1464fea870b3a1a4d54925b5cb7d54b4dc86b23cdf65a9b7787585b5
languageName: node
linkType: hard