Compare commits

..

11 Commits

Author SHA1 Message Date
Jan-Philipp Benecke
8093f7f4cb Add background opacity option to section styling (#26651)
* Add background opacity option to section styling

* Run updated prettier
2025-08-22 18:58:47 +02:00
Jan-Philipp Benecke
b26c914ff9 Use dedicated translation key for none option in section background type (#26652) 2025-08-22 18:13:04 +02:00
Jan-Philipp Benecke
b2fa97b6dc Add background styling options to section settings (#26369)
* Add background styling options to section settings

* Improve

* Fix linter

* Move to subkey

* Use hex instead of rgb to save in yaml

* Remove or
2025-08-22 12:51:07 +02:00
renovate[bot]
8a8bba422a Update dependency typescript-eslint to v8.40.0 (#26648)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-22 08:34:15 +03:00
Aidan Timson
76ca66b1b5 Change home and other zones edit dialog title to location name (#26637)
* Change home and other zone edit dialog title to location name

* Remove no longer used translations

* Use name as context

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>

* Use name as context

* Add translation

* Use name as context

* Actually use the right key

* Use name as context

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
2025-08-22 08:13:10 +03:00
Paul Bottein
280dbfc958 Add "add" button to lovelace dashboard toolbar (#26644)
* Add add button to lovelace dashboard toolbar

* Add person and area

* Fix typing
2025-08-21 20:45:57 +02:00
karwosts
0b10ad3e78 No multiline strings in state-history-chart-timeline (#26646) 2025-08-21 17:39:54 +02:00
Aidan Timson
04d0aa2f22 Remove "hassio" option from commands in quick bar (#26640) 2025-08-21 16:42:40 +03:00
Aidan Timson
4b901101da Fix voice assist setup success step translations (#26638) 2025-08-21 16:41:16 +03:00
Aidan Timson
4960284e2d Media player playback controls card feature (#26608)
* Setup

* Add buttons

* Fix

* Move to function

* Clean

* Cleanup

* Check client size

* Get width from host component

* Fix

* Spacing

* use current target

* Ensure state updates update render

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/panels/lovelace/card-features/hui-media-player-playback-card-feature.ts

* Resolve code suggestion type errors

* Resize observer not needed

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-21 10:28:43 +00:00
renovate[bot]
11d32300e9 Update dependency eslint-plugin-unused-imports to v4.2.0 (#26641)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-21 13:26:40 +03:00
23 changed files with 854 additions and 250 deletions

View File

@@ -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.1.4",
"eslint-plugin-unused-imports": "4.2.0",
"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.39.1",
"typescript-eslint": "8.40.0",
"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;
const text = (api.value(3) as string).replaceAll("\n", " ");
const textWidth = measureTextWidth(text, 12);
const LABEL_PADDING = 4;
if (textWidth < rectShape.width - LABEL_PADDING * 2) {

View File

@@ -2,10 +2,16 @@ 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,6 +12,7 @@ export interface Zone {
}
export interface HomeZoneMutableParams {
name?: string;
latitude: number;
longitude: number;
radius: number;

View File

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

View File

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

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) {
if (this._params!.entry && this._params!.updateEntry) {
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,
systemLogDetailParams: PersonDetailDialogParams
params: PersonDetailDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-person-detail",
dialogImport: loadPersonDetailDialog,
dialogParams: systemLogDetailParams,
dialogParams: params,
});
};

View File

@@ -34,6 +34,7 @@ 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,
@@ -63,7 +64,7 @@ class DialogHomeZoneDetail extends LitElement {
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this.hass!.localize("ui.panel.config.zone.edit_home")
this.hass!.localize("ui.common.edit_item", { name: this._data.name })
)}
>
<div>

View File

@@ -85,7 +85,9 @@ class DialogZoneDetail extends LitElement {
.heading=${createCloseHeading(
this.hass,
this._params.entry
? this._params.entry.name
? this.hass!.localize("ui.common.edit_item", {
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.panel.config.zone.edit_zone"
)}
.label=${hass.localize("ui.common.edit_item", {
name: entry.name,
})}
></ha-icon-button>
</div>
`
@@ -218,9 +218,9 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
this._canEditCore
? mdiPencil
: mdiPencilOff}
.label=${stateObject.entity_id === "zone.home"
? hass.localize("ui.panel.config.zone.edit_home")
: hass.localize("ui.panel.config.zone.edit_zone")}
.label=${hass.localize("ui.common.edit_item", {
name: hass.config.location_name,
})}
@click=${this._editHomeZone}
></ha-icon-button>
</ha-tooltip>`}

View File

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

View File

@@ -163,8 +163,20 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
return html`
<ha-card>
<div
class="image-container"
<hui-image
.hass=${this.hass}
.image=${image}
.stateImage=${this._config.state_image}
.stateFilter=${this._config.state_filter}
.cameraImage=${domain === "camera"
? this._config.entity
: this._config.camera_image}
.cameraView=${this._config.camera_view}
.entity=${this._config.entity}
.aspectRatio=${ignoreAspectRatio
? undefined
: this._config.aspect_ratio}
.fitMode=${this._config.fit_mode}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
@@ -175,28 +187,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
? "0"
: undefined
)}
role=${ifDefined(
hasAction(this._config.tap_action) || this._config.entity
? "0"
: undefined
)}
>
<hui-image
.hass=${this.hass}
.image=${image}
.stateImage=${this._config.state_image}
.stateFilter=${this._config.state_filter}
.cameraImage=${domain === "camera"
? this._config.entity
: this._config.camera_image}
.cameraView=${this._config.camera_view}
.entity=${this._config.entity}
.aspectRatio=${ignoreAspectRatio
? undefined
: this._config.aspect_ratio}
.fitMode=${this._config.fit_mode}
></hui-image>
</div>
></hui-image>
${footer}
</ha-card>
`;
@@ -211,14 +202,8 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
box-sizing: border-box;
}
.image-container {
height: 100%;
cursor: pointer;
}
hui-image {
cursor: pointer;
pointer-events: none;
height: 100%;
}

View File

@@ -219,13 +219,13 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
return html`
<ha-card>
<div
class="image-container ${classMap({
<hui-image
class=${classMap({
clickable:
hasTapAction ||
hasAction(this._config.hold_action) ||
hasAction(this._config.double_tap_action),
})}"
})}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasTap: hasTapAction,
@@ -233,23 +233,19 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
hasDoubleClick: hasAction(this._config.double_tap_action),
})}
tabindex=${ifDefined(hasTapAction ? "0" : undefined)}
role=${ifDefined(hasTapAction ? "button" : undefined)}
.config=${this._config}
>
<hui-image
.hass=${this.hass}
.image=${image}
.stateImage=${this._config.state_image}
.stateFilter=${this._config.state_filter}
.cameraImage=${this._config.camera_image}
.cameraView=${this._config.camera_view}
.entity=${this._config.entity}
.fitMode=${this._config.fit_mode}
.aspectRatio=${ignoreAspectRatio
? undefined
: this._config.aspect_ratio}
></hui-image>
</div>
.hass=${this.hass}
.image=${image}
.stateImage=${this._config.state_image}
.stateFilter=${this._config.state_filter}
.cameraImage=${this._config.camera_image}
.cameraView=${this._config.camera_view}
.entity=${this._config.entity}
.fitMode=${this._config.fit_mode}
.aspectRatio=${ignoreAspectRatio
? undefined
: this._config.aspect_ratio}
></hui-image>
<div class="box">
${this._config.title
? html`<div class="title">${this._config.title}</div>`
@@ -346,16 +342,12 @@ class HuiPictureGlanceCard extends LitElement implements LovelaceCard {
height: 100%;
box-sizing: border-box;
}
.image-container {
height: 100%;
}
.image-container.clickable {
cursor: pointer;
}
hui-image {
pointer-events: none;
height: 100%;
}
hui-image.clickable {
cursor: pointer;
}
.box {
position: absolute;
left: 0;

View File

@@ -22,6 +22,7 @@ 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";
@@ -70,6 +71,7 @@ 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

@@ -42,6 +42,7 @@ 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";
@@ -95,6 +96,7 @@ const UI_FEATURE_TYPES = [
"light-color-temp",
"lock-commands",
"lock-open-door",
"media-player-playback",
"media-player-volume-slider",
"numeric-input",
"select-options",
@@ -162,6 +164,7 @@ 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

@@ -1,6 +1,8 @@
import type { PropertyValues } from "lit";
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { mdiFormatColorFill } from "@mdi/js";
import { fireEvent } from "../../../../common/dom/fire_event";
import type {
HaFormSchema,
@@ -10,9 +12,13 @@ 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")
@@ -23,8 +29,10 @@ export class HuiDialogEditSection extends LitElement {
@property({ attribute: false }) public viewConfig!: LovelaceViewConfig;
@state() private _selectorBackgroundType: "none" | "color" = "none";
private _schema = memoizeOne(
(maxColumns: number) =>
(maxColumns: number, enableBackground: boolean) =>
[
{
name: "column_span",
@@ -36,15 +44,91 @@ 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);
const schema = this._schema(
this.viewConfig.max_columns || 4,
this._selectorBackgroundType === "color"
);
return html`
<ha-form
@@ -76,11 +160,27 @@ 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

@@ -51,7 +51,19 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
}
return html`
<div
<hui-image
.hass=${this.hass}
.entity=${this._config.entity}
.image=${stateObj ? computeImageUrl(stateObj) : this._config.image}
.stateImage=${this._config.state_image}
.cameraImage=${this._config.camera_image}
.cameraView=${this._config.camera_view}
.filter=${this._config.filter}
.stateFilter=${this._config.state_filter}
.title=${computeTooltip(this.hass, this._config)}
.aspectRatio=${this._config.aspect_ratio}
.darkModeImage=${this._config.dark_mode_image}
.darkModeFilter=${this._config.dark_mode_filter}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
@@ -60,25 +72,7 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
tabindex=${ifDefined(
hasAction(this._config.tap_action) ? "0" : undefined
)}
role=${ifDefined(
hasAction(this._config.tap_action) ? "button" : undefined
)}
>
<hui-image
.hass=${this.hass}
.entity=${this._config.entity}
.image=${stateObj ? computeImageUrl(stateObj) : this._config.image}
.stateImage=${this._config.state_image}
.cameraImage=${this._config.camera_image}
.cameraView=${this._config.camera_view}
.filter=${this._config.filter}
.stateFilter=${this._config.state_filter}
.title=${computeTooltip(this.hass, this._config)}
.aspectRatio=${this._config.aspect_ratio}
.darkModeImage=${this._config.dark_mode_image}
.darkModeFilter=${this._config.dark_mode_filter}
></hui-image>
</div>
></hui-image>
`;
}
@@ -90,12 +84,9 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
}
hui-image {
-webkit-user-select: none !important;
pointer-events: none;
}
div:focus {
hui-image:focus {
outline: none;
}
div:focus hui-image {
background: var(--divider-color);
border-radius: 100%;
}

View File

@@ -1,7 +1,9 @@
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
import {
mdiAccount,
mdiCodeBraces,
mdiCommentProcessingOutline,
mdiDevices,
mdiDotsVertical,
mdiFileMultiple,
mdiFormatListBulletedTriangle,
@@ -10,7 +12,9 @@ import {
mdiPencil,
mdiPlus,
mdiRefresh,
mdiRobot,
mdiShape,
mdiSofa,
mdiViewDashboard,
} from "@mdi/js";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
@@ -52,6 +56,7 @@ import {
updateDashboard,
} from "../../data/lovelace/dashboard";
import { getPanelTitle } from "../../data/panel";
import { createPerson } from "../../data/person";
import {
showAlertDialog,
showConfirmationDialog,
@@ -66,7 +71,10 @@ 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";
@@ -78,6 +86,28 @@ 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 {
@@ -145,16 +175,7 @@ class HUIRoot extends LitElement {
);
}
const items: {
icon: string;
key: LocalizeKeys;
overflowAction?: any;
buttonAction?: any;
visible: boolean | undefined;
overflow: boolean;
overflow_can_promote?: boolean;
suffix?: string;
}[] = [
const items: ActionItem[] = [
{
icon: mdiFormatListBulletedTriangle,
key: "ui.panel.lovelace.unused_entities.title",
@@ -183,13 +204,45 @@ 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: this.narrow,
overflow: false,
suffix: this.hass.enableShortcuts ? "(E)" : undefined,
},
{
@@ -199,7 +252,7 @@ class HUIRoot extends LitElement {
overflowAction: this._handleShowVoiceCommandDialog,
visible:
!this._editMode && this._conversation(this.hass.config.components),
overflow: this.narrow,
overflow: false,
suffix: this.hass.enableShortcuts ? "(A)" : undefined,
},
{
@@ -247,20 +300,50 @@ class HUIRoot extends LitElement {
(i) => i.visible && (!i.overflow || overflowCanPromote)
);
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>`
);
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);
});
if (overflowItems.length && !overflowCanPromote) {
const listItems: TemplateResult[] = [];
overflowItems.forEach((i) => {
@@ -373,10 +456,15 @@ 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">
@@ -399,7 +487,7 @@ class HUIRoot extends LitElement {
<div class="action-items">${this._renderActionItems()}</div>
`
: html`
${curViewConfig?.subview
${isSubview
? html`
<ha-icon-button-arrow-prev
slot="navigationIcon"
@@ -413,34 +501,37 @@ class HUIRoot extends LitElement {
.narrow=${this.narrow}
></ha-menu-button>
`}
${curViewConfig?.subview
${isSubview
? html`<div class="main-title">${curViewConfig.title}</div>`
: views.filter((view) => !view.subview).length > 1
: hasTabViews && !showTabBar
? tabs
: html`
<div class="main-title">
${views[0]?.title ?? dashboardTitle}
${curViewConfig?.title ?? dashboardTitle}
</div>
`}
<div class="action-items">${this._renderActionItems()}</div>
`}
</div>
${this._editMode
${showTabBar
? html`<div class="edit-tab-bar">
${tabs}
<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>
${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}
</div>`
: nothing}
</div>
<hui-view-container
class=${showTabBar ? "has-tab-bar" : ""}
.hass=${this.hass}
.theme=${curViewConfig?.theme}
id="view"
@@ -696,6 +787,79 @@ 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;
@@ -1050,6 +1214,14 @@ 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;
@@ -1114,9 +1286,6 @@ 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);
}
@@ -1188,9 +1357,10 @@ class HUIRoot extends LitElement {
/**
* In edit mode we have the tab bar on a new line *
*/
.edit-mode hui-view-container {
hui-view-container.has-tab-bar {
padding-top: calc(
var(--header-height) + 48px + var(--safe-area-inset-top)
var(--header-height) + calc(var(--header-height, 56px) - 2px) +
var(--safe-area-inset-top)
);
}
.hide-tab {

View File

@@ -20,6 +20,7 @@ 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,
@@ -86,6 +87,12 @@ 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}
@@ -103,7 +110,11 @@ 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,
@@ -250,6 +261,10 @@ 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

@@ -413,6 +413,7 @@
"add": "Add",
"create": "Create",
"edit": "Edit",
"edit_item": "Edit {name}",
"submit": "Submit",
"rename": "Rename",
"search": "[%key:ui::components::data-table::search%]",
@@ -439,7 +440,8 @@
"replace": "Replace",
"append": "Append",
"supports_markdown": "Supports {markdown_help_link}",
"markdown": "Markdown"
"markdown": "Markdown",
"none": "None"
},
"components": {
"selectors": {
@@ -5337,8 +5339,6 @@
"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,7 +6968,16 @@
"assist_tooltip": "Assist",
"reload_resources": "Reload resources",
"exit_edit_mode": "Done",
"close": "Close"
"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"
},
"reload_resources": {
"refresh_header": "Do you want to refresh?",
@@ -7249,7 +7258,13 @@
"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)"
"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"
},
"visibility": {
"explanation": "The section will be shown when ALL conditions below are fulfilled. If no conditions are set, the section will always be shown."
@@ -7905,6 +7920,9 @@
"lock-open-door": {
"label": "Lock open door"
},
"media-player-playback": {
"label": "Media player playback controls"
},
"media-player-volume-slider": {
"label": "Media player volume slider"
},

160
yarn.lock
View File

@@ -4994,76 +4994,67 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/eslint-plugin@npm:8.39.1"
"@typescript-eslint/eslint-plugin@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.40.0"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.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"
"@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"
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.39.1
"@typescript-eslint/parser": ^8.40.0
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/446050aa43d54c0107c7c927ae1f68a4384c2bba514d5c22edabbe355426cb37bd5bb5a3faf240a6be8ef06f68de6099c2a53d9cbb1849ed35a152fb156171e2
checksum: 10/9df4d4ac58734a34964b791622dcb94ffc6c49c1d0f4fd0480b3fc0e026527df7167ff78a4f8bbd29089d605756c28c1a90b2f0653df34b40ac8b969bc6c92e9
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/parser@npm:8.39.1"
"@typescript-eslint/parser@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/parser@npm:8.40.0"
dependencies:
"@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"
"@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"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/ff45ce76353ed564e0f9db47b02b4b20895c96182b3693c610ef3dbceda373c476037a99f90d9f28633c192f301e5d554c89e1ba72da216763f960648ddf1f34
checksum: 10/1e60f70e9d02f930553db7f4684c27c376fadf345db155414a22d1a32cd21def7d36496bd63c1acbf3afbec9fb8794947e880f88c1143b83e1d3c45146cec41a
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/project-service@npm:8.39.1"
"@typescript-eslint/project-service@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/project-service@npm:8.40.0"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.39.1"
"@typescript-eslint/types": "npm:^8.39.1"
"@typescript-eslint/tsconfig-utils": "npm:^8.40.0"
"@typescript-eslint/types": "npm:^8.40.0"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/1970633d1a338190f0125e186beaa39b3ef912f287e4815934faf64b72f140e87fdf7d861962683635a450d270dd76faf0c865d72bfd57b471a36739f943676b
checksum: 10/86491aa65c1dd78c9784dddd8467601aef8be652c5fb3a901e8b1995cf07c1dbe11d0ab4610d770e3f4063c0c254a6c6aa5fb7cf724bf12fa4ee56f47f3a2955
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/scope-manager@npm:8.39.1"
"@typescript-eslint/scope-manager@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/scope-manager@npm:8.40.0"
dependencies:
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/visitor-keys": "npm:8.39.1"
checksum: 10/8874f7479043b3fc878f2c04b2c565051deceb7e425a8e4e79a7f40f1ee696bb979bd91fff619e016fe6793f537b30609c0ee8a5c40911c4829fa264863f7a70
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/visitor-keys": "npm:8.40.0"
checksum: 10/0c5aa10208bfbb506bf3925a420c3de667298064bde400f03ee52c19cd0785dd05c2c820e05724d005737e2920d925aff0318ec3308156f9b81c84736a1fe46b
languageName: node
linkType: hard
"@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":
"@typescript-eslint/tsconfig-utils@npm:8.40.0, @typescript-eslint/tsconfig-utils@npm:^8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.40.0"
peerDependencies:
@@ -5072,44 +5063,37 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/type-utils@npm:8.39.1"
"@typescript-eslint/type-utils@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/type-utils@npm:8.40.0"
dependencies:
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/typescript-estree": "npm:8.39.1"
"@typescript-eslint/utils": "npm:8.39.1"
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/typescript-estree": "npm:8.40.0"
"@typescript-eslint/utils": "npm:8.40.0"
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/1195d65970f79f820558810f7e1edf0ea360bbeee55841fdbb71b5b40c09f1a65741b67a70b85c2834ae1f9a027b82da4234d01f42ab4e85dceef3eea84bfdaa
checksum: 10/296c718330b2ac4408840258c30c01072de01ffe1c009be00c5049be1b19a71cbb2e258363ae349150760bcd2d34799df305b4cfc4d7f3b2fa9760ac8ffb3f75
languageName: node
linkType: hard
"@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":
"@typescript-eslint/types@npm:8.40.0, @typescript-eslint/types@npm:^8.40.0":
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.39.1":
version: 8.39.1
resolution: "@typescript-eslint/typescript-estree@npm:8.39.1"
"@typescript-eslint/typescript-estree@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/typescript-estree@npm:8.40.0"
dependencies:
"@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"
"@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"
debug: "npm:^4.3.4"
fast-glob: "npm:^3.3.2"
is-glob: "npm:^4.0.3"
@@ -5118,32 +5102,32 @@ __metadata:
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10/07ed9d7ab4d146ee3ce6cf82ffebf947e045a9289b01522e11b3985b64f590c00cac0ca10366df828ca213bf08216a67c7b2b76e7c8be650df2511a7e6385425
checksum: 10/2e61ecfecb933f644799a7c11e4c7a730df57290c8d0482082cff7739b2401b0cf3b1ebef7b08a54a90285978957a49850d1a53061e8770164da651172ebee32
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/utils@npm:8.39.1"
"@typescript-eslint/utils@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/utils@npm:8.40.0"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.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/scope-manager": "npm:8.40.0"
"@typescript-eslint/types": "npm:8.40.0"
"@typescript-eslint/typescript-estree": "npm:8.40.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/39bb105f26aa1ba234ad7d284c277cbd66df9d51e245094892db140aac80d3656d0480f133b2db54e87af3ef9c371a12973120c9cfbff71e8e85865f9e1d0077
checksum: 10/b4cd1e6a4f55cc6475189de12e6bd418080a227e794745a2af304ab21655b031c28dae6387d4e9b54dd2f420696cec4f77cca9c66db405ed2281e0e09c95ba1c
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.39.1":
version: 8.39.1
resolution: "@typescript-eslint/visitor-keys@npm:8.39.1"
"@typescript-eslint/visitor-keys@npm:8.40.0":
version: 8.40.0
resolution: "@typescript-eslint/visitor-keys@npm:8.40.0"
dependencies:
"@typescript-eslint/types": "npm:8.39.1"
"@typescript-eslint/types": "npm:8.40.0"
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10/6d4e4d0b19ebb3f21b692bbb0dcf9961876ca28cdf502296888a78eb4cd802a2ec8d3d5721d19970411edfd1c06f3e272e4057014c859ee1f0546804d07945e3
checksum: 10/191f47998001a5e9cdde7491b0215d9c6c45c637aedd7d32cafd35ce2a4a0f4b8582edab015e09238f48e025a788b99efd8e70e4e3200e32143f91c95112abcd
languageName: node
linkType: hard
@@ -8157,16 +8141,16 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-unused-imports@npm:4.1.4":
version: 4.1.4
resolution: "eslint-plugin-unused-imports@npm:4.1.4"
"eslint-plugin-unused-imports@npm:4.2.0":
version: 4.2.0
resolution: "eslint-plugin-unused-imports@npm:4.2.0"
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/8e987028ad925ce1e04c01dcae70adbf44c2878a8b15c4327b33a2861e471d7fe00f6fe213fbd2b936f3fcefc8ccabb0d778aa1d6e0e0387a3dc7fe150cd4ed4
checksum: 10/99285bd61a46ad5eaacd54525c04564feed3529dc2ae7800c736b7f4f011daa370133adb481677ad840f020681c0f409f3edfe3bc8f9f234c42bf4674bc4b576
languageName: node
linkType: hard
@@ -9517,7 +9501,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.1.4"
eslint-plugin-unused-imports: "npm:4.2.0"
eslint-plugin-wc: "npm:3.0.1"
fancy-log: "npm:2.0.0"
fs-extra: "npm:11.3.1"
@@ -9571,7 +9555,7 @@ __metadata:
tinykeys: "npm:3.0.0"
ts-lit-plugin: "npm:2.0.2"
typescript: "npm:5.9.2"
typescript-eslint: "npm:8.39.1"
typescript-eslint: "npm:8.40.0"
ua-parser-js: "npm:2.0.4"
vite-tsconfig-paths: "npm:5.1.4"
vitest: "npm:3.2.4"
@@ -14619,18 +14603,18 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.39.1":
version: 8.39.1
resolution: "typescript-eslint@npm:8.39.1"
"typescript-eslint@npm:8.40.0":
version: 8.40.0
resolution: "typescript-eslint@npm:8.40.0"
dependencies:
"@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"
"@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"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10/1c29c18f2e93b8b74d019590196b158006d7c65be87a56a4c953e52a9c4c40280a42f8ff1464fea870b3a1a4d54925b5cb7d54b4dc86b23cdf65a9b7787585b5
checksum: 10/b96dc4e70bd551e5399b928e946957cce622cba89f0ff87521f9e93f223acbe406930a1ebee845b158f586959cb7d85f15ea2250b97341aa87f50a3c987d068a
languageName: node
linkType: hard