Compare commits

..

1 Commits

Author SHA1 Message Date
Wendelin
11f65ef0f7 Add new target selected value view 2025-09-09 15:38:48 +02:00
33 changed files with 1008 additions and 932 deletions

View File

@@ -149,7 +149,7 @@ class HassioAddonConfig extends LitElement {
)
);
private _filteredSchema = memoizeOne(
private _filteredShchema = memoizeOne(
(options: Record<string, unknown>, schema: HaFormSchema[]) =>
schema.filter((entry) => entry.name in options || entry.required)
);
@@ -161,7 +161,7 @@ class HassioAddonConfig extends LitElement {
showForm &&
JSON.stringify(this.addon.schema) !==
JSON.stringify(
this._filteredSchema(this.addon.options, this.addon.schema!)
this._filteredShchema(this.addon.options, this.addon.schema!)
);
return html`
<h1>${this.addon.name}</h1>
@@ -207,7 +207,7 @@ class HassioAddonConfig extends LitElement {
.schema=${this._convertSchema(
this._showOptional
? this.addon.schema!
: this._filteredSchema(
: this._filteredShchema(
this.addon.options,
this.addon.schema!
)

View File

@@ -1,11 +1,3 @@
export default {
trailingComma: "es5",
overrides: [
{
files: "*.globals.ts",
options: {
printWidth: 9999, // Effectively disables line wrapping for these files
},
},
],
};

View File

@@ -38,7 +38,6 @@ export class HaHeaderBar extends LitElement {
.mdc-top-app-bar {
position: static;
color: var(--mdc-theme-on-primary, #fff);
padding: var(--header-bar-padding);
}
.mdc-top-app-bar__section.mdc-top-app-bar__section--align-start {
flex: 1;

View File

@@ -666,7 +666,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
tooltip.style.display = "block";
tooltip.style.position = "fixed";
tooltip.style.top = `${top}px`;
tooltip.style.left = `calc(${item.offsetLeft + item.clientWidth + 8}px + var(--safe-area-inset-left, 0px))`;
tooltip.style.left = `${item.offsetLeft + item.clientWidth + 8}px`;
}
private _hideTooltip() {
@@ -705,10 +705,9 @@ class HaSidebar extends SubscribeMixin(LitElement) {
background-color: var(--sidebar-background-color);
width: 100%;
box-sizing: border-box;
padding-bottom: calc(14px + var(--safe-area-inset-bottom, 0px));
}
.menu {
height: calc(var(--header-height) + var(--safe-area-inset-top, 0px));
height: var(--header-height);
box-sizing: border-box;
display: flex;
padding: 0 4px;
@@ -726,16 +725,12 @@ class HaSidebar extends SubscribeMixin(LitElement) {
);
font-size: var(--ha-font-size-xl);
align-items: center;
padding-left: calc(4px + var(--safe-area-inset-left, 0px));
padding-inline-start: calc(4px + var(--safe-area-inset-left, 0px));
padding-left: calc(4px + var(--safe-area-inset-left));
padding-inline-start: calc(4px + var(--safe-area-inset-left));
padding-inline-end: initial;
padding-top: var(--safe-area-inset-top, 0px);
}
:host([expanded]) .menu {
width: calc(256px + var(--safe-area-inset-left, 0px));
}
:host([narrow][expanded]) .menu {
width: 100%;
width: calc(256px + var(--safe-area-inset-left));
}
.menu ha-icon-button {
color: var(--sidebar-icon-color);
@@ -761,23 +756,22 @@ class HaSidebar extends SubscribeMixin(LitElement) {
ha-fade-in,
ha-md-list {
height: calc(
100% - var(--header-height) - var(--safe-area-inset-top, 0px) -
132px - var(--safe-area-inset-bottom, 0px)
100% - var(--header-height) - 132px - var(--safe-area-inset-bottom)
);
}
ha-fade-in {
padding: 4px 0;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
}
ha-md-list {
padding: 4px 0;
box-sizing: border-box;
overflow-x: hidden;
background: none;
margin-left: var(--safe-area-inset-left, 0px);
margin-left: var(--safe-area-inset-left);
}
ha-md-list-item {
@@ -797,9 +791,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
}
:host([expanded]) ha-md-list-item {
width: 248px;
}
:host([narrow][expanded]) ha-md-list-item {
width: calc(240px - var(--safe-area-inset-left, 0px));
width: calc(248px - var(--safe-area-inset-left));
}
ha-md-list-item.selected {

View File

@@ -1,39 +1,16 @@
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import "@material/mwc-menu/mwc-menu-surface";
import {
mdiClose,
mdiDevices,
mdiHome,
mdiLabel,
mdiPlus,
mdiTextureBox,
mdiUnfoldMoreVertical,
} from "@mdi/js";
import { mdiPlus } from "@mdi/js";
import type { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
import type {
HassEntity,
HassServiceTarget,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import type { HassServiceTarget } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { LitElement, css, html, nothing, unsafeCSS } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ensureArray } from "../common/array/ensure-array";
import { computeCssColor } from "../common/color/compute-color";
import { hex2rgb } from "../common/color/convert-color";
import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation";
import { computeDeviceNameDisplay } from "../common/entity/compute_device_name";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateName } from "../common/entity/compute_state_name";
import { isValidEntityId } from "../common/entity/valid_entity_id";
import type { AreaRegistryEntry } from "../data/area_registry";
import type { DeviceRegistryEntry } from "../data/device_registry";
import type { EntityRegistryDisplayEntry } from "../data/entity_registry";
import type { LabelRegistryEntry } from "../data/label_registry";
import { subscribeLabelRegistry } from "../data/label_registry";
import { SubscribeMixin } from "../mixins/subscribe-mixin";
import type { HomeAssistant } from "../types";
import "./device/ha-device-picker";
@@ -41,12 +18,13 @@ import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import "./entity/ha-entity-picker";
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
import "./ha-area-floor-picker";
import { floorDefaultIconPath } from "./ha-floor-icon";
import "./ha-icon-button";
import "./ha-input-helper-text";
import "./ha-label-picker";
import "./ha-svg-icon";
import "./ha-tooltip";
import "./target-picker/ha-target-picker-item-group";
import type { TargetType } from "./target-picker/ha-target-picker-item-row";
@customElement("ha-target-picker")
export class HaTargetPicker extends SubscribeMixin(LitElement) {
@@ -58,6 +36,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@property() public helper?: string;
@property({ type: Boolean }) public compact = false;
@property({ attribute: false, type: Array }) public createDomains?: string[];
/**
@@ -96,18 +76,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
@query(".add-container", true) private _addContainer?: HTMLDivElement;
@state() private _labels?: LabelRegistryEntry[];
private _opened = false;
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeLabelRegistry(this.hass.connection, (labels) => {
this._labels = labels;
}),
];
}
protected render() {
if (this.addOnTop) {
return html` ${this._renderChips()} ${this._renderItems()} `;
@@ -116,87 +86,82 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
}
private _renderItems() {
if (
!this.value?.floor_id &&
!this.value?.area_id &&
!this.value?.device_id &&
!this.value?.entity_id &&
!this.value?.label_id
) {
return nothing;
}
return html`
<div class="mdc-chip-set items">
<div class="item-groups">
${this.value?.floor_id
? ensureArray(this.value.floor_id).map((floor_id) => {
const floor = this.hass.floors[floor_id];
return this._renderChip(
"floor_id",
floor_id,
floor?.name || floor_id,
undefined,
floor?.icon,
floor ? floorDefaultIconPath(floor) : mdiHome
);
})
: ""}
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@remove-target-group=${this._handleRemoveGroup}
type="floor"
.hass=${this.hass}
.items=${ensureArray(this.value?.floor_id)}
.collapsed=${this.compact}
>
</ha-target-picker-item-group>
`
: nothing}
${this.value?.area_id
? ensureArray(this.value.area_id).map((area_id) => {
const area = this.hass.areas![area_id];
return this._renderChip(
"area_id",
area_id,
area?.name || area_id,
undefined,
area?.icon,
mdiTextureBox
);
})
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@remove-target-group=${this._handleRemoveGroup}
type="area"
.hass=${this.hass}
.items=${ensureArray(this.value?.area_id)}
.collapsed=${this.compact}
>
</ha-target-picker-item-group>
`
: nothing}
${this.value?.device_id
? ensureArray(this.value.device_id).map((device_id) => {
const device = this.hass.devices![device_id];
return this._renderChip(
"device_id",
device_id,
device
? computeDeviceNameDisplay(device, this.hass)
: device_id,
undefined,
undefined,
mdiDevices
);
})
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@remove-target-group=${this._handleRemoveGroup}
type="device"
.hass=${this.hass}
.items=${ensureArray(this.value?.device_id)}
.collapsed=${this.compact}
>
</ha-target-picker-item-group>
`
: nothing}
${this.value?.entity_id
? ensureArray(this.value.entity_id).map((entity_id) => {
const entity = this.hass.states[entity_id];
return this._renderChip(
"entity_id",
entity_id,
entity ? computeStateName(entity) : entity_id,
entity
);
})
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@remove-target-group=${this._handleRemoveGroup}
type="entity"
.hass=${this.hass}
.items=${ensureArray(this.value?.entity_id)}
.collapsed=${this.compact}
>
</ha-target-picker-item-group>
`
: nothing}
${this.value?.label_id
? ensureArray(this.value.label_id).map((label_id) => {
const label = this._labels?.find(
(lbl) => lbl.label_id === label_id
);
let color = label?.color
? computeCssColor(label.color)
: undefined;
if (color?.startsWith("var(")) {
const computedStyles = getComputedStyle(this);
color = computedStyles.getPropertyValue(
color.substring(4, color.length - 1)
);
}
if (color?.startsWith("#")) {
color = hex2rgb(color).join(",");
}
return this._renderChip(
"label_id",
label_id,
label ? label.name : label_id,
undefined,
label?.icon,
mdiLabel,
color
);
})
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
@remove-target-group=${this._handleRemoveGroup}
type="label"
.hass=${this.hass}
.items=${ensureArray(this.value?.label_id)}
.collapsed=${this.compact}
>
</ha-target-picker-item-group>
`
: nothing}
</div>
`;
@@ -299,89 +264,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
this._addMode = ev.currentTarget.type;
}
private _renderChip(
type: "floor_id" | "area_id" | "device_id" | "entity_id" | "label_id",
id: string,
name: string,
entityState?: HassEntity,
icon?: string | null,
fallbackIconPath?: string,
color?: string
) {
return html`
<div
class="mdc-chip ${classMap({
[type]: true,
})}"
style=${color
? `--color: rgb(${color}); --background-color: rgba(${color}, .5)`
: ""}
>
${icon
? html`<ha-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.icon=${icon}
></ha-icon>`
: fallbackIconPath
? html`<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${fallbackIconPath}
></ha-svg-icon>`
: ""}
${entityState
? html`<ha-state-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.hass=${this.hass}
.stateObj=${entityState}
></ha-state-icon>`
: ""}
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text">${name}</span>
</span>
</span>
${type === "entity_id"
? ""
: html`<span role="gridcell">
<ha-tooltip
.content=${this.hass.localize(
`ui.components.target-picker.expand_${type}`
)}
>
<ha-icon-button
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
.label=${this.hass.localize(
"ui.components.target-picker.expand"
)}
.path=${mdiUnfoldMoreVertical}
hide-title
.id=${id}
.type=${type}
@click=${this._handleExpand}
></ha-icon-button>
</ha-tooltip>
</span>`}
<span role="gridcell">
<ha-tooltip
.content=${this.hass.localize(
`ui.components.target-picker.remove_${type}`
)}
>
<ha-icon-button
class="mdc-chip__icon mdc-chip__icon--trailing"
.label=${this.hass.localize("ui.components.target-picker.remove")}
.path=${mdiClose}
hide-title
.id=${id}
.type=${type}
@click=${this._handleRemove}
></ha-icon-button>
</ha-tooltip>
</span>
</div>
`;
}
private _renderPicker() {
if (!this._addMode) {
return nothing;
@@ -524,130 +406,44 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
});
}
private _handleExpand(ev) {
const target = ev.currentTarget as any;
const newAreas: string[] = [];
const newDevices: string[] = [];
const newEntities: string[] = [];
if (target.type === "floor_id") {
Object.values(this.hass.areas).forEach((area) => {
if (
area.floor_id === target.id &&
!this.value!.area_id?.includes(area.area_id) &&
this._areaMeetsFilter(area)
) {
newAreas.push(area.area_id);
}
});
} else if (target.type === "area_id") {
Object.values(this.hass.devices).forEach((device) => {
if (
device.area_id === target.id &&
!this.value!.device_id?.includes(device.id) &&
this._deviceMeetsFilter(device)
) {
newDevices.push(device.id);
}
});
Object.values(this.hass.entities).forEach((entity) => {
if (
entity.area_id === target.id &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else if (target.type === "device_id") {
Object.values(this.hass.entities).forEach((entity) => {
if (
entity.device_id === target.id &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else if (target.type === "label_id") {
Object.values(this.hass.areas).forEach((area) => {
if (
area.labels.includes(target.id) &&
!this.value!.area_id?.includes(area.area_id) &&
this._areaMeetsFilter(area)
) {
newAreas.push(area.area_id);
}
});
Object.values(this.hass.devices).forEach((device) => {
if (
device.labels.includes(target.id) &&
!this.value!.device_id?.includes(device.id) &&
this._deviceMeetsFilter(device)
) {
newDevices.push(device.id);
}
});
Object.values(this.hass.entities).forEach((entity) => {
if (
entity.labels.includes(target.id) &&
!this.value!.entity_id?.includes(entity.entity_id) &&
this._entityRegMeetsFilter(entity)
) {
newEntities.push(entity.entity_id);
}
});
} else {
return;
}
let value = this.value;
if (newEntities.length) {
value = this._addItems(value, "entity_id", newEntities);
}
if (newDevices.length) {
value = this._addItems(value, "device_id", newDevices);
}
if (newAreas.length) {
value = this._addItems(value, "area_id", newAreas);
}
value = this._removeItem(value, target.type, target.id);
fireEvent(this, "value-changed", { value });
}
private _handleRemove(ev) {
const target = ev.currentTarget as any;
const { type, id } = ev.detail;
fireEvent(this, "value-changed", {
value: this._removeItem(this.value, target.type, target.id),
value: this._removeItem(this.value, type, id),
});
}
private _addItems(
value: this["value"],
type: string,
ids: string[]
): this["value"] {
return {
...value,
[type]: value![type] ? ensureArray(value![type])!.concat(ids) : ids,
};
private _handleRemoveGroup(ev) {
const type = ev.detail;
fireEvent(this, "value-changed", {
value: this._removeGroup(type),
});
}
private _removeGroup(type: TargetType): this["value"] {
const newVal = { ...this.value };
delete newVal[`${type}_id`];
return newVal;
}
private _removeItem(
value: this["value"],
type: string,
type: TargetType,
id: string
): this["value"] {
const newVal = ensureArray(value![type])!.filter(
const typeId = `${type}_id`;
const newVal = ensureArray(value![typeId])!.filter(
(val) => String(val) !== id
);
if (newVal.length) {
return {
...value,
[type]: newVal,
[typeId]: newVal,
};
}
const val = { ...value }!;
delete val[type];
delete val[typeId];
if (Object.keys(val).length) {
return val;
}
@@ -679,80 +475,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
ev.preventDefault();
}
private _areaMeetsFilter(area: AreaRegistryEntry): boolean {
const areaDevices = Object.values(this.hass.devices).filter(
(device) => device.area_id === area.area_id
);
if (areaDevices.some((device) => this._deviceMeetsFilter(device))) {
return true;
}
const areaEntities = Object.values(this.hass.entities).filter(
(entity) => entity.area_id === area.area_id
);
if (areaEntities.some((entity) => this._entityRegMeetsFilter(entity))) {
return true;
}
return false;
}
private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean {
const devEntities = Object.values(this.hass.entities).filter(
(entity) => entity.device_id === device.id
);
if (!devEntities.some((entity) => this._entityRegMeetsFilter(entity))) {
return false;
}
if (this.deviceFilter) {
if (!this.deviceFilter(device)) {
return false;
}
}
return true;
}
private _entityRegMeetsFilter(entity: EntityRegistryDisplayEntry): boolean {
if (entity.hidden || entity.entity_category) {
return false;
}
if (
this.includeDomains &&
!this.includeDomains.includes(computeDomain(entity.entity_id))
) {
return false;
}
if (this.includeDeviceClasses) {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
if (
!stateObj.attributes.device_class ||
!this.includeDeviceClasses!.includes(stateObj.attributes.device_class)
) {
return false;
}
}
if (this.entityFilter) {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
if (!this.entityFilter!(stateObj)) {
return false;
}
}
return true;
}
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(chipStyles)}
@@ -811,41 +533,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
margin-inline-end: 0;
margin-inline-start: initial;
}
.mdc-chip.area_id:not(.add),
.mdc-chip.floor_id:not(.add) {
border: 1px solid #fed6a4;
background: var(--card-background-color);
}
.mdc-chip.area_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.area_id.add,
.mdc-chip.floor_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.floor_id.add {
background: #fed6a4;
}
.mdc-chip.device_id:not(.add) {
border: 1px solid #a8e1fb;
background: var(--card-background-color);
}
.mdc-chip.device_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.device_id.add {
background: #a8e1fb;
}
.mdc-chip.entity_id:not(.add) {
border: 1px solid #d2e7b9;
background: var(--card-background-color);
}
.mdc-chip.entity_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.entity_id.add {
background: #d2e7b9;
}
.mdc-chip.label_id:not(.add) {
border: 1px solid var(--color, #e0e0e0);
background: var(--card-background-color);
}
.mdc-chip.label_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.label_id.add {
background: var(--background-color, #e0e0e0);
}
.mdc-chip:hover {
z-index: 5;
}
@@ -865,6 +552,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
ha-tooltip {
--ha-tooltip-arrow-size: 0;
}
.item-groups {
overflow: hidden;
border: 2px solid var(--divider-color);
border-radius: var(--ha-border-radius-lg);
}
`;
}
}
@@ -873,4 +566,12 @@ declare global {
interface HTMLElementTagNameMap {
"ha-target-picker": HaTargetPicker;
}
interface HASSDomEvents {
"remove-target-item": {
type: string;
id: string;
};
"remove-target-group": string;
}
}

View File

@@ -0,0 +1,101 @@
import { mdiBroom, mdiMinus, mdiPlus } from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import type { HomeAssistant } from "../../types";
import "../ha-md-list";
import "./ha-target-picker-item-row";
@customElement("ha-target-picker-item-group")
export class HaTargetPickerItemGroup extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public type!: "entity" | "device" | "area" | "label" | "floor";
@property({ attribute: false }) public items!: string[];
@property({ type: Boolean }) public collapsed = false;
protected render() {
return html` <div class="heading">
${this.hass.localize(
`ui.components.target-picker.selected.${this.type}`,
{
count: this.items.length,
}
)}
<div class="icons">
<ha-icon-button
title=${this.hass.localize(
`ui.components.target-picker.remove_${this.type}s`
)}
.path=${mdiBroom}
@click=${this._removeGroup}
></ha-icon-button>
<ha-icon-button
title=${this.hass.localize(
this.collapsed
? "ui.components.target-picker.expand"
: "ui.components.target-picker.collapse"
)}
.path=${this.collapsed ? mdiPlus : mdiMinus}
@click=${this._toggleCollapsed}
></ha-icon-button>
</div>
</div>
${this.collapsed
? nothing
: html`
<ha-md-list>
${this.items.map(
(item) =>
html`
<ha-target-picker-item-row
.hass=${this.hass}
.type=${this.type}
.item=${item}
></ha-target-picker-item-row>
</ha-md-list-item>`
)}
</ha-md-list>
`}`;
}
private _toggleCollapsed() {
this.collapsed = !this.collapsed;
}
private _removeGroup() {
fireEvent(this, "remove-target-group", this.type);
}
static styles = css`
:host {
display: block;
}
.heading {
background-color: var(--ha-color-fill-neutral-quiet-resting);
padding: 4px 8px;
font-weight: var(--ha-font-weight-bold);
color: var(--secondary-text-color);
display: flex;
justify-content: space-between;
}
.icons {
display: flex;
}
.icons ha-icon-button {
--mdc-icon-size: 16px;
--mdc-icon-button-size: 24px;
}
ha-md-list {
padding: 0;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-target-picker-item-group": HaTargetPickerItemGroup;
}
}

View File

@@ -0,0 +1,359 @@
import {
mdiChevronDown,
mdiClose,
mdiDevices,
mdiHome,
mdiTextureBox,
} from "@mdi/js";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../common/dom/fire_event";
import { computeAreaName } from "../../common/entity/compute_area_name";
import {
computeDeviceName,
computeDeviceNameDisplay,
} from "../../common/entity/compute_device_name";
import { computeEntityName } from "../../common/entity/compute_entity_name";
import { getEntityContext } from "../../common/entity/context/get_entity_context";
import { computeRTL } from "../../common/util/compute_rtl";
import { getConfigEntry } from "../../data/config_entries";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
import { domainToName } from "../../data/integration";
import type { HomeAssistant } from "../../types";
import { brandsUrl } from "../../util/brands-url";
import { floorDefaultIconPath } from "../ha-floor-icon";
import "../ha-icon-button";
import "../ha-md-list";
import "../ha-md-list-item";
import "../ha-state-icon";
export type TargetType = "entity" | "device" | "area" | "label" | "floor";
@customElement("ha-target-picker-item-row")
export class HaTargetPickerItemRow extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public type!: TargetType;
@property() public item!: string;
@state() private _expanded = false;
@state() private _iconImg?: string;
@state() private _domainName?: string;
protected render() {
const { name, context, iconPath, fallbackIconPath, stateObject } =
this._itemData(this.type, this.item);
const showDevices = ["floor", "area", "label"].includes(this.type);
const showEntities = this.type !== "entity";
let devices: DeviceRegistryEntry[] = [];
if (showDevices) {
devices = this._getDevices(this.hass.devices, this.type, this.item);
}
let entities: EntityRegistryDisplayEntry[] = [];
if (showEntities) {
entities = this._getEntities(
devices,
this.hass.entities,
this.type,
this.item
);
}
return html`
<ha-md-list-item>
${this.type !== "entity"
? html`<ha-icon-button
class="expand-button ${classMap({
expanded: entities.length && this._expanded,
})}"
.path=${mdiChevronDown}
slot="start"
@click=${this._toggleExpand}
.disabled=${entities.length === 0}
></ha-icon-button>`
: nothing}
${iconPath
? html`<ha-icon slot="start" .icon=${iconPath}></ha-icon>`
: this._iconImg
? html`<img
slot="start"
alt=${this._domainName || ""}
crossorigin="anonymous"
referrerpolicy="no-referrer"
src=${this._iconImg}
/>`
: fallbackIconPath
? html`<ha-svg-icon
slot="start"
.path=${fallbackIconPath}
></ha-svg-icon>`
: stateObject
? html`
<ha-state-icon
.hass=${this.hass}
.stateObj=${stateObject}
slot="start"
>
</ha-state-icon>
`
: nothing}
<div slot="headline">${name}</div>
${context
? html`<span slot="supporting-text">${context}</span>`
: nothing}
${showEntities || showDevices || this._domainName
? html`
<div slot="end" class="summary">
${showEntities
? html`<span class="main"
>${this.hass.localize(
"ui.components.target-picker.entities_count",
{
count: entities.length,
}
)}</span
>`
: nothing}
${showDevices
? html`<span class="secondary"
>${this.hass.localize(
"ui.components.target-picker.devices_count",
{
count: devices.length,
}
)}</span
>`
: nothing}
${this._domainName && !showDevices
? html`<span class="secondary domain"
>${this._domainName}</span
>`
: nothing}
</div>
`
: nothing}
<ha-icon-button
.path=${mdiClose}
slot="end"
@click=${this._removeItem}
></ha-icon-button>
</ha-md-list-item>
${this._expanded
? html`
${entities.map(
(entity) =>
html`<ha-md-list-item class="indent">
<div slot="headline">
${computeEntityName(
this.hass.states[entity.entity_id],
this.hass
)}
</div>
<span slot="supporting-text">${entity.entity_id}</span>
</ha-md-list-item>`
)}
`
: nothing}
`;
}
private _itemData = memoizeOne((type: TargetType, item: string) => {
if (type === "floor") {
const floor = this.hass.floors?.[item];
return {
name: floor?.name || item,
iconPath: floor?.icon,
fallbackIconPath: floor ? floorDefaultIconPath(floor) : mdiHome,
};
}
if (type === "area") {
const area = this.hass.areas?.[item];
return {
name: area?.name || item,
context: area.floor_id && this.hass.floors?.[area.floor_id]?.name,
iconPath: area?.icon,
fallbackIconPath: mdiTextureBox,
};
}
if (type === "device") {
const device = this.hass.devices?.[item];
if (device.primary_config_entry) {
this._getDomainIcon(device.primary_config_entry);
}
return {
name: device ? computeDeviceNameDisplay(device, this.hass) : item,
context: device?.area_id && this.hass.areas?.[device.area_id]?.name,
fallbackIconPath: mdiDevices,
};
}
if (type === "entity") {
const stateObject = this.hass.states[item];
const entityName = computeEntityName(stateObject, this.hass);
const { area, device } = getEntityContext(stateObject, this.hass);
const deviceName = device ? computeDeviceName(device) : undefined;
const areaName = area ? computeAreaName(area) : undefined;
const context = [areaName, entityName ? deviceName : undefined]
.filter(Boolean)
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
return {
name: entityName || deviceName || item,
context,
stateObject,
};
}
return { name: item };
});
private _removeItem() {
fireEvent(this, "remove-target-item", {
type: this.type,
id: this.item,
});
}
private _getDevices = memoizeOne(
(
devices: HomeAssistant["devices"],
type: TargetType,
item: string
): DeviceRegistryEntry[] =>
Object.values(devices).filter((device) => {
if (!device.area_id) {
return false;
}
if (type === "area") {
return device.area_id === item;
}
if (type === "label") {
return device.labels?.includes(item);
}
return this.hass.areas[device.area_id]?.floor_id === item;
})
);
private _getEntities = memoizeOne(
(
devices: DeviceRegistryEntry[],
entities: HomeAssistant["entities"],
type: TargetType,
item: string
): EntityRegistryDisplayEntry[] => {
const deviceIds = devices.map((device) => device.id);
return Object.values(entities).filter((entity) => {
if (entity.hidden || entity.entity_category) {
return false;
}
if (deviceIds.includes(entity.device_id || "")) {
return true;
}
if (type === "area") {
return entity.area_id === item;
}
if (entity.area_id && type === "floor") {
return this.hass.areas[entity.area_id]?.floor_id === item;
}
if (type === "device") {
return entity.device_id === item;
}
if (type === "label") {
return entity.labels?.includes(item);
}
return false;
});
}
);
private async _getDomainIcon(configEntryId: string) {
try {
const data = await getConfigEntry(this.hass, configEntryId);
const domain = data.config_entry.domain;
this._iconImg = brandsUrl({
domain: domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
});
this._domainName = domain
? domainToName(this.hass.localize, domain)
: undefined;
} catch {
// failed to load config entry -> ignore
}
}
private _toggleExpand() {
this._expanded = !this._expanded;
}
static styles = css`
ha-md-list-item {
--md-list-item-top-space: 0;
--md-list-item-bottom-space: 0;
--md-list-item-trailing-space: 8px;
}
state-badge {
color: var(--ha-color-on-neutral-quiet);
}
img {
width: 24px;
height: 24px;
}
.expand-button {
margin: 0 -12px;
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
.expand-button.expanded {
transform: rotate(180deg);
}
ha-icon-button {
--mdc-icon-button-size: 32px;
}
.summary {
display: flex;
flex-direction: column;
align-items: flex-end;
line-height: var(--ha-line-height-condensed);
}
.summary .main {
font-weight: var(--ha-font-weight-medium);
}
.summary .secondary {
font-size: var(--ha-font-size-s);
color: var(--secondary-text-color);
}
.summary .secondary.domain {
font-family: var(--ha-font-family-code);
}
ha-md-list-item.indent {
padding-left: 32px;
}
ha-md-list-item.indent:last-of-type {
border-bottom: 1px solid var(--ha-color-border-neutral-quiet);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-target-picker-item-row": HaTargetPickerItemRow;
}
}

View File

@@ -1,14 +1,8 @@
import type { HomeAssistant } from "../types";
import type { Selector } from "./selector";
export const enum AITaskEntityFeature {
GENERATE_DATA = 1,
SUPPORT_ATTACHMENTS = 2,
GENERATE_IMAGE = 4,
}
export interface AITaskPreferences {
gen_data_entity_id: string | null;
gen_image_entity_id: string | null;
}
export interface GenDataTask {

View File

@@ -246,7 +246,7 @@ export const clearStatistics = (hass: HomeAssistant, statistic_ids: string[]) =>
});
export const calculateStatisticSumGrowth = (
values?: StatisticValue[]
values: StatisticValue[]
): number | null => {
let growth: number | null = null;

View File

@@ -49,7 +49,7 @@ export class HuiNotificationDrawer extends LitElement {
);
this.style.setProperty(
"--mdc-drawer-width",
`min(100vw, calc(${narrow ? window.innerWidth + "px" : "500px"} + var(--safe-area-inset-left, 0px)))`
narrow ? window.innerWidth + "px" : "500px"
);
this._open = true;
}
@@ -152,40 +152,24 @@ export class HuiNotificationDrawer extends LitElement {
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--primary-background-color);
--header-bar-padding: var(--safe-area-inset-top, 0px) 0 0
var(--safe-area-inset-left, 0px);
border-bottom: 1px solid var(--divider-color);
display: block;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-header-bar {
--header-bar-padding: var(--safe-area-inset-top, 0px)
var(--safe-area-inset-right, 0px) 0 var(--safe-area-inset-left, 0px);
}
}
.notifications {
overflow-y: auto;
padding-top: 16px;
padding-left: var(--safe-area-inset-left, 0px);
padding-inline-start: var(--safe-area-inset-left, 0px);
padding-bottom: var(--safe-area-inset-bottom, 0px);
height: calc(
100% - 1px - var(--header-height) - var(--safe-area-inset-top, 0px)
);
padding-left: var(--safe-area-inset-left);
padding-right: var(--safe-area-inset-right);
padding-inline-start: var(--safe-area-inset-left);
padding-inline-end: var(--safe-area-inset-right);
padding-bottom: var(--safe-area-inset-bottom);
height: calc(100% - 1px - var(--header-height));
box-sizing: border-box;
background-color: var(--primary-background-color);
color: var(--primary-text-color);
}
@media all and (max-width: 450px), all and (max-height: 500px) {
.notifications {
padding-right: var(--safe-area-inset-right, 0px);
padding-inline-end: var(--safe-area-inset-right, 0px);
}
}
.notification {
padding: 0 16px 16px;
}

View File

@@ -144,13 +144,13 @@ export class HomeAssistantMain extends LitElement {
color: var(--primary-text-color);
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
--mdc-drawer-width: calc(56px + var(--safe-area-inset-left, 0px));
--mdc-drawer-width: 56px;
--mdc-top-app-bar-width: calc(100% - var(--mdc-drawer-width));
--safe-area-content-inset-left: 0px;
--safe-area-content-inset-right: var(--safe-area-inset-right);
}
:host([expanded]) {
--mdc-drawer-width: calc(256px + var(--safe-area-inset-left, 0px));
--mdc-drawer-width: calc(256px + var(--safe-area-inset-left));
}
:host([modal]) {
--mdc-drawer-width: unset;

View File

@@ -1,6 +1,5 @@
import type { LitElement } from "lit";
import type { Constructor } from "../types";
import { canOverrideAlphanumericInput } from "../common/dom/can-override-input";
declare global {
type SupportedShortcuts = Record<string, () => void>;
@@ -18,14 +17,6 @@ export const KeyboardShortcutMixin = <T extends Constructor<LitElement>>(
!event.altKey &&
event.key in supportedShortcuts
) {
// Only capture the event if the user is not focused on an input
if (!canOverrideAlphanumericInput(event.composedPath())) {
return;
}
// Don't capture the event if the user is selecting text
if (window.getSelection()?.toString()) {
return;
}
event.preventDefault();
supportedShortcuts[event.key]();
return;

View File

@@ -50,9 +50,7 @@ export default class HaAutomationActionEditor extends LitElement {
class=${classMap({
"card-content": true,
disabled:
!this.indent &&
(this.disabled ||
(this.action.enabled === false && !this.yamlMode)),
this.disabled || (this.action.enabled === false && !this.yamlMode),
yaml: yamlMode,
indent: this.indent,
card: !this.inSidebar,

View File

@@ -53,9 +53,8 @@ export default class HaAutomationConditionEditor extends LitElement {
class=${classMap({
"card-content": true,
disabled:
!this.indent &&
(this.disabled ||
(this.condition.enabled === false && !this.yamlMode)),
this.disabled ||
(this.condition.enabled === false && !this.yamlMode),
yaml: yamlMode,
indent: this.indent,
card: !this.inSidebar,

View File

@@ -44,6 +44,7 @@ export const rowStyles = css`
export const editorStyles = css`
.disabled {
opacity: 0.5;
pointer-events: none;
}

View File

@@ -141,6 +141,7 @@ export default class HaAutomationTriggerEditor extends LitElement {
haStyle,
css`
.disabled {
opacity: 0.5;
pointer-events: none;
}

View File

@@ -125,7 +125,7 @@ export class HaWebhookTrigger extends LitElement {
.path=${mdiContentCopy}
></ha-icon-button>
</ha-textfield>
<ha-button-menu multi @closed=${stopPropagation} fixed>
<ha-button-menu multi @closed=${stopPropagation}>
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize(

View File

@@ -1,6 +1,5 @@
import { mdiHelpCircle, mdiStarFourPoints } from "@mdi/js";
import { css, html, LitElement } from "lit";
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import type { HaProgressButton } from "../../../components/buttons/ha-progress-button";
@@ -9,7 +8,6 @@ import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker
import "../../../components/ha-card";
import "../../../components/ha-settings-row";
import {
AITaskEntityFeature,
fetchAITaskPreferences,
saveAITaskPreferences,
type AITaskPreferences,
@@ -17,15 +15,6 @@ import {
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { documentationUrl } from "../../../util/documentation-url";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
const filterGenData = (entity: HassEntity) =>
computeDomain(entity.entity_id) === "ai_task" &&
supportsFeature(entity, AITaskEntityFeature.GENERATE_DATA);
const filterGenImage = (entity: HassEntity) =>
computeDomain(entity.entity_id) === "ai_task" &&
supportsFeature(entity, AITaskEntityFeature.GENERATE_IMAGE);
@customElement("ai-task-pref")
export class AITaskPref extends LitElement {
@@ -37,8 +26,6 @@ export class AITaskPref extends LitElement {
private _gen_data_entity_id?: string | null;
private _gen_image_entity_id?: string | null;
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
if (!this.hass || !isComponentLoaded(this.hass, "ai_task")) {
@@ -103,27 +90,7 @@ export class AITaskPref extends LitElement {
isComponentLoaded(this.hass, "ai_task")}
.value=${this._gen_data_entity_id ||
this._prefs?.gen_data_entity_id}
.entityFilter=${filterGenData}
@value-changed=${this._handlePrefChange}
></ha-entity-picker>
</ha-settings-row>
<ha-settings-row .narrow=${this.narrow}>
<span slot="heading">
${this.hass!.localize("ui.panel.config.ai_task.gen_image_header")}
</span>
<span slot="description">
${this.hass!.localize(
"ui.panel.config.ai_task.gen_image_description"
)}
</span>
<ha-entity-picker
data-name="gen_image_entity_id"
.hass=${this.hass}
.disabled=${this._prefs === undefined &&
isComponentLoaded(this.hass, "ai_task")}
.value=${this._gen_image_entity_id ||
this._prefs?.gen_image_entity_id}
.entityFilter=${filterGenImage}
.includeDomains=${["ai_task"]}
@value-changed=${this._handlePrefChange}
></ha-entity-picker>
</ha-settings-row>
@@ -154,7 +121,6 @@ export class AITaskPref extends LitElement {
const oldPrefs = this._prefs;
const update: Partial<AITaskPreferences> = {
gen_data_entity_id: this._gen_data_entity_id,
gen_image_entity_id: this._gen_image_entity_id,
};
this._prefs = { ...this._prefs!, ...update };
try {

View File

@@ -226,7 +226,6 @@ export class EnergyBatterySettings extends LitElement {
.label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`,
];

View File

@@ -1024,7 +1024,6 @@ export class HaScriptEditor extends SubscribeMixin(
c: () => this._copySelectedRow(),
x: () => this._cutSelectedRow(),
Delete: () => this._deleteSelectedRow(),
Backspace: () => this._deleteSelectedRow(),
};
}

View File

@@ -182,6 +182,7 @@ class HaPanelHistory extends LitElement {
.disabled=${this._isLoading}
add-on-top
@value-changed=${this._targetsChanged}
compact
></ha-target-picker>
</div>
${this._isLoading

View File

@@ -104,6 +104,7 @@ export class HaPanelLogbook extends LitElement {
.value=${this._targetPickerValue}
add-on-top
@value-changed=${this._targetsChanged}
compact
></ha-target-picker>
</div>

View File

@@ -7,26 +7,6 @@ import type { HomeAssistant } from "../../../../types";
import { INTERVAL } from "../hui-clock-card";
import { resolveTimeZone } from "../../../../common/datetime/resolve-time-zone";
function romanize12HourClock(num: number) {
const numerals = [
"", // 0 (not used)
"I", // 1
"II", // 2
"III", // 3
"IV", // 4
"V", // 5
"VI", // 6
"VII", // 7
"VIII", // 8
"IX", // 9
"X", // 10
"XI", // 11
"XII", // 12
];
if (num < 1 || num > 12) return "";
return numerals[num];
}
@customElement("hui-clock-card-analog")
export class HuiClockCardAnalog extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@@ -121,36 +101,6 @@ export class HuiClockCardAnalog extends LitElement {
? `size-${this.config.clock_size}`
: "";
const isNumbers = this.config?.face_style?.startsWith("numbers");
const isRoman = this.config?.face_style?.startsWith("roman");
const isUpright = this.config?.face_style?.endsWith("upright");
const indicator = (number?: number) => html`
<div
class=${classMap({
line: true,
numbers: isNumbers,
roman: isRoman,
})}
>
${number && this.config?.face_style !== "markers"
? html`<div
class=${classMap({
number: true,
[this.config?.clock_size ?? ""]: true,
upright: isUpright,
})}
>
${isRoman
? romanize12HourClock(number)
: isNumbers
? number
: nothing}
</div>`
: nothing}
</div>
`;
return html`
<div
class="analog-clock ${sizeClass}"
@@ -166,14 +116,14 @@ export class HuiClockCardAnalog extends LitElement {
${this.config.ticks === "quarter"
? Array.from({ length: 4 }, (_, i) => i).map(
(i) =>
// 4 ticks (12, 3, 6, 9) at 0°, 90°, 180°, 270°
// 4 ticks
html`
<div
aria-hidden="true"
aria-hidden
class="tick hour"
style=${`--tick-rotation: ${i * 90}deg;`}
>
${indicator([12, 3, 6, 9][i])}
<div class="line"></div>
</div>
`
)
@@ -181,30 +131,28 @@ export class HuiClockCardAnalog extends LitElement {
this.config.ticks === "hour"
? Array.from({ length: 12 }, (_, i) => i).map(
(i) =>
// 12 ticks (1-12)
// 12 ticks
html`
<div
aria-hidden="true"
aria-hidden
class="tick hour"
style=${`--tick-rotation: ${i * 30}deg;`}
>
${indicator(((i + 11) % 12) + 1)}
<div class="line"></div>
</div>
`
)
: this.config.ticks === "minute"
? Array.from({ length: 60 }, (_, i) => i).map(
(i) =>
// 60 ticks (1-60)
// 60 ticks
html`
<div
aria-hidden="true"
aria-hidden
class="tick ${i % 5 === 0 ? "hour" : "minute"}"
style=${`--tick-rotation: ${i * 6}deg;`}
>
${i % 5 === 0
? indicator(((i / 5 + 11) % 12) + 1)
: indicator()}
<div class="line"></div>
</div>
`
)
@@ -297,42 +245,6 @@ export class HuiClockCardAnalog extends LitElement {
opacity: 0.8;
}
.tick.hour .line.numbers,
.tick.hour .line.roman {
height: calc(var(--clock-size) * 0.03);
}
.tick.minute .line.numbers,
.tick.minute .line.roman {
height: calc(var(--clock-size) * 0.015);
}
.tick .number {
position: absolute;
top: 0%;
left: 50%;
transform: translate(-50%, 35%);
color: var(--primary-text-color);
font-weight: var(--ha-font-weight-medium);
line-height: var(--ha-line-height-condensed);
}
.tick .number.upright {
transform: translate(-50%, 35%) rotate(calc(var(--tick-rotation) * -1));
}
.tick .number.small {
font-size: var(--ha-font-size-s);
}
.tick .number.medium {
font-size: var(--ha-font-size-m);
}
.tick .number.large {
font-size: var(--ha-font-size-l);
}
.center-dot {
position: absolute;
top: 50%;

View File

@@ -225,7 +225,7 @@ class HuiEnergySankeyCard
if (consumption.total.battery_to_grid > 0) {
links.push({
source: "battery",
target: "grid_return",
target: "grid",
value: consumption.total.battery_to_grid,
});
}

View File

@@ -221,36 +221,25 @@ export class HuiEnergySourcesTableCard
let totalGrid = 0;
let totalGridCost = 0;
let totalSolar = 0;
let totalBattery = 0;
let totalGas = 0;
let totalGasCost = 0;
let totalWater = 0;
let totalWaterCost = 0;
let hasGridCost = false;
let hasGasCost = false;
let hasWaterCost = false;
let totalGridCompare = 0;
let totalGridCostCompare = 0;
let totalSolarCompare = 0;
let totalBatteryCompare = 0;
const totals = {
gas: 0,
water: 0,
solar: 0,
};
const totalsCompare = {
gas: 0,
water: 0,
solar: 0,
};
const totalCosts = {
gas: 0,
water: 0,
};
const totalCostsCompare = {
gas: 0,
water: 0,
};
const hasCosts = {
gas: false,
water: false,
};
let totalGasCompare = 0;
let totalGasCostCompare = 0;
let totalWaterCompare = 0;
let totalWaterCostCompare = 0;
const types = energySourcesByType(this._data.prefs);
@@ -277,111 +266,12 @@ export class HuiEnergySourcesTableCard
)
);
const units = {
solar: "kWh",
gas: this._data.gasUnit,
water: this._data.waterUnit,
};
const gasUnit = this._data.gasUnit;
const waterUnit = this._data.waterUnit;
const compare = this._data.statsCompare !== undefined;
const _extractStatData = (
statId: string,
costStatId: string | null
): {
hasData: boolean;
energy: number;
energyCompare: number;
cost: number;
costCompare: number;
} => {
const energy = calculateStatisticSumGrowth(this._data!.stats[statId]);
const compareEnergy =
compare &&
calculateStatisticSumGrowth(this._data!.statsCompare[statId]);
const cost =
(costStatId &&
calculateStatisticSumGrowth(this._data!.stats[costStatId])) ||
0;
const costCompare =
(compare &&
costStatId &&
calculateStatisticSumGrowth(this._data!.statsCompare[costStatId])) ||
0;
if (energy === null && (!compare || compareEnergy === null)) {
return {
hasData: false,
energy: energy || 0,
energyCompare: compareEnergy || 0,
cost,
costCompare,
};
}
return {
hasData: true,
energy: energy || 0,
energyCompare: compareEnergy || 0,
cost,
costCompare,
};
};
const _renderSimpleCategory = (type: "solar" | "gas" | "water") =>
html` ${types[type]?.map((source, idx) => {
const cost_stat =
type in hasCosts &&
(source.stat_cost ||
this._data!.info.cost_sensors[source.stat_energy_from]);
const { hasData, energy, energyCompare, cost, costCompare } =
_extractStatData(source.stat_energy_from, cost_stat || null);
if (!hasData && !cost && !costCompare) {
return nothing;
}
totals[type] += energy;
totalsCompare[type] += energyCompare;
if (cost_stat) {
hasCosts[type] = true;
totalCosts[type] += cost;
totalCostsCompare[type] += costCompare;
}
return this._renderRow(
computedStyles,
type,
source.stat_energy_from,
idx,
energy,
energyCompare,
units[type],
cost_stat ? cost : null,
cost_stat ? costCompare : null,
showCosts,
compare
);
})}
${types[type]
? this._renderTotalRow(
this.hass.localize(
`ui.panel.lovelace.cards.energy.energy_sources_table.${type}_total`
),
totals[type],
totalsCompare[type],
units[type],
hasCosts[type] ? totalCosts[type] : null,
hasCosts[type] ? totalCostsCompare[type] : null,
showCosts,
compare
)
: ""}`;
return html` <ha-card>
${this._config.title
? html`<h1 class="card-header">${this._config.title}</h1>`
@@ -446,24 +336,72 @@ export class HuiEnergySourcesTableCard
</tr>
</thead>
<tbody class="mdc-data-table__content">
${_renderSimpleCategory("solar")}
${types.solar?.map((source, idx) => {
const energy =
calculateStatisticSumGrowth(
this._data!.stats[source.stat_energy_from]
) || 0;
totalSolar += energy;
const compareEnergy =
(compare &&
calculateStatisticSumGrowth(
this._data!.statsCompare[source.stat_energy_from]
)) ||
0;
totalSolarCompare += compareEnergy;
return this._renderRow(
computedStyles,
"solar",
source.stat_energy_from,
idx,
energy,
compareEnergy,
"kWh",
null,
null,
showCosts,
compare
);
})}
${types.solar
? this._renderTotalRow(
this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_sources_table.solar_total"
),
totalSolar,
totalSolarCompare,
"kWh",
null,
null,
showCosts,
compare
)
: ""}
${types.battery?.map((source, idx) => {
const {
hasData: hasFromData,
energy: energyFrom,
energyCompare: energyFromCompare,
} = _extractStatData(source.stat_energy_from, null);
const {
hasData: hasToData,
energy: energyTo,
energyCompare: energyToCompare,
} = _extractStatData(source.stat_energy_to, null);
if (!hasFromData && !hasToData) {
return nothing;
}
const energyFrom =
calculateStatisticSumGrowth(
this._data!.stats[source.stat_energy_from]
) || 0;
const energyTo =
calculateStatisticSumGrowth(
this._data!.stats[source.stat_energy_to]
) || 0;
totalBattery += energyFrom - energyTo;
const energyFromCompare =
(compare &&
calculateStatisticSumGrowth(
this._data!.statsCompare[source.stat_energy_from]
)) ||
0;
const energyToCompare =
(compare &&
calculateStatisticSumGrowth(
this._data!.statsCompare[source.stat_energy_to]
)) ||
0;
totalBatteryCompare += energyFromCompare - energyToCompare;
return html` ${this._renderRow(
@@ -509,39 +447,50 @@ export class HuiEnergySourcesTableCard
${types.grid?.map(
(source) =>
html`${source.flow_from.map((flow, idx) => {
const energy =
calculateStatisticSumGrowth(
this._data!.stats[flow.stat_energy_from]
) || 0;
totalGrid += energy;
const compareEnergy =
(compare &&
calculateStatisticSumGrowth(
this._data!.statsCompare[flow.stat_energy_from]
)) ||
0;
totalGridCompare += compareEnergy;
const cost_stat =
flow.stat_cost ||
this._data!.info.cost_sensors[flow.stat_energy_from];
const {
hasData,
energy,
energyCompare,
cost,
costCompare,
} = _extractStatData(
flow.stat_energy_from,
cost_stat || null
);
if (!hasData && !cost && !costCompare) {
return nothing;
}
totalGrid += energy;
totalGridCompare += energyCompare;
if (cost_stat) {
const cost = cost_stat
? calculateStatisticSumGrowth(
this._data!.stats[cost_stat]
) || 0
: null;
if (cost !== null) {
hasGridCost = true;
totalGridCost += cost;
}
const costCompare =
compare && cost_stat
? calculateStatisticSumGrowth(
this._data!.statsCompare[cost_stat]
) || 0
: null;
if (costCompare !== null) {
totalGridCostCompare += costCompare;
}
return this._renderRow(
computedStyles,
"grid_consumption",
flow.stat_energy_from,
idx,
energy,
energyCompare,
compareEnergy,
"kWh",
cost,
costCompare,
@@ -550,41 +499,52 @@ export class HuiEnergySourcesTableCard
);
})}
${source.flow_to.map((flow, idx) => {
const energy =
(calculateStatisticSumGrowth(
this._data!.stats[flow.stat_energy_to]
) || 0) * -1;
totalGrid += energy;
const cost_stat =
flow.stat_compensation ||
this._data!.info.cost_sensors[flow.stat_energy_to];
const {
hasData,
energy,
energyCompare,
cost,
costCompare,
} = _extractStatData(
flow.stat_energy_to,
cost_stat || null
);
if (!hasData && !cost && !costCompare) {
return nothing;
}
totalGrid -= energy;
totalGridCompare -= energyCompare;
if (cost_stat !== null) {
const cost = cost_stat
? (calculateStatisticSumGrowth(
this._data!.stats[cost_stat]
) || 0) * -1
: null;
if (cost !== null) {
hasGridCost = true;
totalGridCost -= cost;
totalGridCostCompare -= costCompare;
totalGridCost += cost;
}
const energyCompare =
((compare &&
calculateStatisticSumGrowth(
this._data!.statsCompare[flow.stat_energy_to]
)) ||
0) * -1;
totalGridCompare += energyCompare;
const costCompare =
compare && cost_stat
? (calculateStatisticSumGrowth(
this._data!.statsCompare[cost_stat]
) || 0) * -1
: null;
if (costCompare !== null) {
totalGridCostCompare += costCompare;
}
return this._renderRow(
computedStyles,
"grid_return",
flow.stat_energy_to,
idx,
-energy,
-energyCompare,
energy,
energyCompare,
"kWh",
-cost,
-costCompare,
cost,
costCompare,
showCosts,
compare
);
@@ -606,9 +566,138 @@ export class HuiEnergySourcesTableCard
compare
)
: ""}
${_renderSimpleCategory("gas")} ${_renderSimpleCategory("water")}
${[hasCosts.gas, hasCosts.water, hasGridCost].filter(Boolean)
.length > 1
${types.gas?.map((source, idx) => {
const energy =
calculateStatisticSumGrowth(
this._data!.stats[source.stat_energy_from]
) || 0;
totalGas += energy;
const energyCompare =
(compare &&
calculateStatisticSumGrowth(
this._data!.statsCompare[source.stat_energy_from]
)) ||
0;
totalGasCompare += energyCompare;
const cost_stat =
source.stat_cost ||
this._data!.info.cost_sensors[source.stat_energy_from];
const cost = cost_stat
? calculateStatisticSumGrowth(this._data!.stats[cost_stat]) ||
0
: null;
if (cost !== null) {
hasGasCost = true;
totalGasCost += cost;
}
const costCompare =
compare && cost_stat
? calculateStatisticSumGrowth(
this._data!.statsCompare[cost_stat]
) || 0
: null;
if (costCompare !== null) {
totalGasCostCompare += costCompare;
}
return this._renderRow(
computedStyles,
"gas",
source.stat_energy_from,
idx,
energy,
energyCompare,
gasUnit,
cost,
costCompare,
showCosts,
compare
);
})}
${types.gas
? this._renderTotalRow(
this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_sources_table.gas_total"
),
totalGas,
totalGasCompare,
gasUnit,
hasGasCost ? totalGasCost : null,
hasGasCost ? totalGasCostCompare : null,
showCosts,
compare
)
: ""}
${types.water?.map((source, idx) => {
const energy =
calculateStatisticSumGrowth(
this._data!.stats[source.stat_energy_from]
) || 0;
totalWater += energy;
const energyCompare =
(compare &&
calculateStatisticSumGrowth(
this._data!.statsCompare[source.stat_energy_from]
)) ||
0;
totalWaterCompare += energyCompare;
const cost_stat =
source.stat_cost ||
this._data!.info.cost_sensors[source.stat_energy_from];
const cost = cost_stat
? calculateStatisticSumGrowth(this._data!.stats[cost_stat]) ||
0
: null;
if (cost !== null) {
hasWaterCost = true;
totalWaterCost += cost;
}
const costCompare =
compare && cost_stat
? calculateStatisticSumGrowth(
this._data!.statsCompare[cost_stat]
) || 0
: null;
if (costCompare !== null) {
totalWaterCostCompare += costCompare;
}
return this._renderRow(
computedStyles,
"water",
source.stat_energy_from,
idx,
energy,
energyCompare,
waterUnit,
cost,
costCompare,
showCosts,
compare
);
})}
${types.water
? this._renderTotalRow(
this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_sources_table.water_total"
),
totalWater,
totalWaterCompare,
waterUnit,
hasWaterCost ? totalWaterCost : null,
hasWaterCost ? totalWaterCostCompare : null,
showCosts,
compare
)
: ""}
${[hasGasCost, hasWaterCost, hasGridCost].filter(Boolean).length >
1
? this._renderTotalRow(
this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_sources_table.total_costs"
@@ -616,10 +705,10 @@ export class HuiEnergySourcesTableCard
null,
null,
"",
totalCosts.gas + totalGridCost + totalCosts.water,
totalCostsCompare.gas +
totalGasCost + totalGridCost + totalWaterCost,
totalGasCostCompare +
totalGridCostCompare +
totalCostsCompare.water,
totalWaterCostCompare,
showCosts,
compare
)

View File

@@ -381,7 +381,6 @@ export interface ClockCardConfig extends LovelaceCardConfig {
// Analog clock options
border?: boolean;
ticks?: "none" | "quarter" | "hour" | "minute";
face_style?: "markers" | "numbers_upright" | "roman";
}
export interface MediaControlCardConfig extends LovelaceCardConfig {

View File

@@ -52,16 +52,6 @@ const cardConfigStruct = assign(
literal("hour")
)
),
face_style: optional(
defaulted(
union([
literal("markers"),
literal("numbers_upright"),
literal("roman"),
]),
literal("markers")
)
),
})
);
@@ -75,11 +65,7 @@ export class HuiClockCardEditor
@state() private _config?: ClockCardConfig;
private _schema = memoizeOne(
(
localize: LocalizeFunc,
clockStyle: ClockCardConfig["clock_style"],
ticks: ClockCardConfig["ticks"]
) =>
(localize: LocalizeFunc, clockStyle: "digital" | "analog") =>
[
{ name: "title", selector: { text: {} } },
{
@@ -170,37 +156,6 @@ export class HuiClockCardEditor
},
},
},
...(ticks !== "none"
? ([
{
name: "face_style",
description: {
suffix: localize(
`ui.panel.lovelace.editor.card.clock.face_style.description`
),
},
default: "markers",
selector: {
select: {
mode: "dropdown",
options: [
"markers",
"numbers_upright",
"roman",
].map((value) => ({
value,
label: localize(
`ui.panel.lovelace.editor.card.clock.face_style.${value}.label`
),
description: localize(
`ui.panel.lovelace.editor.card.clock.face_style.${value}.description`
),
})),
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
] as const satisfies readonly HaFormSchema[])
: []),
{
@@ -236,7 +191,6 @@ export class HuiClockCardEditor
// Analog clock options
border: false,
ticks: "hour",
face_style: "markers",
...config,
}));
@@ -256,8 +210,8 @@ export class HuiClockCardEditor
.data=${this._data(this._config)}
.schema=${this._schema(
this.hass.localize,
this._data(this._config).clock_style,
this._data(this._config).ticks
(this._data(this._config).clock_style as "digital" | "analog") ??
"digital"
)}
.computeLabel=${this._computeLabelCallback}
.computeHelper=${this._computeHelperCallback}
@@ -277,17 +231,9 @@ export class HuiClockCardEditor
if (ev.detail.value.clock_style === "analog") {
ev.detail.value.border = ev.detail.value.border ?? false;
ev.detail.value.ticks = ev.detail.value.ticks ?? "hour";
ev.detail.value.face_style = ev.detail.value.face_style ?? "markers";
} else {
delete ev.detail.value.border;
delete ev.detail.value.ticks;
delete ev.detail.value.face_style;
}
if (ev.detail.value.ticks !== "none") {
ev.detail.value.face_style = ev.detail.value.face_style ?? "markers";
} else {
delete ev.detail.value.face_style;
}
fireEvent(this, "config-changed", { config: ev.detail.value });
@@ -333,10 +279,6 @@ export class HuiClockCardEditor
return this.hass!.localize(
`ui.panel.lovelace.editor.card.clock.ticks.label`
);
case "face_style":
return this.hass!.localize(
`ui.panel.lovelace.editor.card.clock.face_style.label`
);
default:
return undefined;
}
@@ -354,10 +296,6 @@ export class HuiClockCardEditor
return this.hass!.localize(
`ui.panel.lovelace.editor.card.clock.ticks.description`
);
case "face_style":
return this.hass!.localize(
`ui.panel.lovelace.editor.card.clock.face_style.description`
);
default:
return undefined;
}

View File

@@ -117,7 +117,10 @@ export const colorStyles = css`
/* state color */
--state-active-color: var(--amber-color);
--state-inactive-color: var(--grey-color);
--state-unavailable-color: var(--state-icon-unavailable-color, var(--disabled-text-color));
--state-unavailable-color: var(
--state-icon-unavailable-color,
var(--disabled-text-color)
);
/* state domain colors */
--state-alarm_control_panel-armed_away-color: var(--green-color);
@@ -195,9 +198,15 @@ export const colorStyles = css`
--sidebar-selected-icon-color: var(--primary-color);
--sidebar-icon-color: rgba(var(--rgb-primary-text-color), 0.6);
--switch-checked-color: var(--primary-color);
--switch-checked-button-color: var(--switch-checked-color, var(--primary-background-color));
--switch-checked-button-color: var(
--switch-checked-color,
var(--primary-background-color)
);
--switch-checked-track-color: var(--switch-checked-color, #000000);
--switch-unchecked-button-color: var(--switch-unchecked-color, var(--primary-background-color));
--switch-unchecked-button-color: var(
--switch-unchecked-color,
var(--primary-background-color)
);
--switch-unchecked-track-color: var(--switch-unchecked-color, #000000);
--slider-color: var(--primary-color);
--slider-secondary-color: var(--light-primary-color);
@@ -243,9 +252,15 @@ export const colorStyles = css`
--mdc-text-field-idle-line-color: var(--input-idle-line-color);
--mdc-text-field-hover-line-color: var(--input-hover-line-color);
--mdc-text-field-disabled-line-color: var(--input-disabled-line-color);
--mdc-text-field-outlined-idle-border-color: var(--input-outlined-idle-border-color);
--mdc-text-field-outlined-hover-border-color: var(--input-outlined-hover-border-color);
--mdc-text-field-outlined-disabled-border-color: var(--input-outlined-disabled-border-color);
--mdc-text-field-outlined-idle-border-color: var(
--input-outlined-idle-border-color
);
--mdc-text-field-outlined-hover-border-color: var(
--input-outlined-hover-border-color
);
--mdc-text-field-outlined-disabled-border-color: var(
--input-outlined-disabled-border-color
);
--mdc-text-field-fill-color: var(--input-fill-color);
--mdc-text-field-disabled-fill-color: var(--input-disabled-fill-color);
--mdc-text-field-ink-color: var(--input-ink-color);
@@ -254,9 +269,15 @@ export const colorStyles = css`
--mdc-select-idle-line-color: var(--input-idle-line-color);
--mdc-select-hover-line-color: var(--input-hover-line-color);
--mdc-select-outlined-idle-border-color: var(--input-outlined-idle-border-color);
--mdc-select-outlined-hover-border-color: var(--input-outlined-hover-border-color);
--mdc-select-outlined-disabled-border-color: var(--input-outlined-disabled-border-color);
--mdc-select-outlined-idle-border-color: var(
--input-outlined-idle-border-color
);
--mdc-select-outlined-hover-border-color: var(
--input-outlined-hover-border-color
);
--mdc-select-outlined-disabled-border-color: var(
--input-outlined-disabled-border-color
);
--mdc-select-fill-color: var(--input-fill-color);
--mdc-select-disabled-fill-color: var(--input-disabled-fill-color);
--mdc-select-ink-color: var(--input-ink-color);
@@ -264,8 +285,14 @@ export const colorStyles = css`
--mdc-select-disabled-ink-color: var(--input-disabled-ink-color);
--mdc-select-dropdown-icon-color: var(--input-dropdown-icon-color);
--mdc-select-disabled-dropdown-icon-color: var(--input-disabled-ink-color);
--ha-assist-chip-filled-container-color: rgba(var(--rgb-primary-text-color), 0.15);
--ha-assist-chip-active-container-color: rgba(var(--rgb-primary-color), 0.15);
--ha-assist-chip-filled-container-color: rgba(
var(--rgb-primary-text-color),
0.15
);
--ha-assist-chip-active-container-color: rgba(
var(--rgb-primary-color),
0.15
);
--chip-background-color: rgba(var(--rgb-primary-text-color), 0.15);
/* Vaadin */
@@ -327,7 +354,8 @@ export const darkColorStyles = css`
--codemirror-qualifier: #decb6b;
--codemirror-type: #decb6b;
--energy-grid-return-color: #a280db;
--map-filter: invert(0.9) hue-rotate(170deg) brightness(1.5) contrast(1.2) saturate(0.3);
--map-filter: invert(0.9) hue-rotate(170deg) brightness(1.5) contrast(1.2)
saturate(0.3);
--disabled-color: #464646;
--ha-button-primary-light-color: #4082a040;
@@ -339,5 +367,9 @@ export const darkColorStyles = css`
}
`;
export const DefaultPrimaryColor = extractVar(colorStyles, "primary-color", coreColorVariables);
export const DefaultPrimaryColor = extractVar(
colorStyles,
"primary-color",
coreColorVariables
);
export const DefaultAccentColor = extractVar(colorStyles, "accent-color");

View File

@@ -28,10 +28,22 @@ export const mainStyles = css`
--margin-title-rtl: 0 24px 0 0;
/* safe-area-insets */
--safe-area-inset-top: var(--app-safe-area-inset-top, env(safe-area-inset-top, 0));
--safe-area-inset-bottom: var(--app-safe-area-inset-bottom, env(safe-area-inset-bottom, 0));
--safe-area-inset-left: var(--app-safe-area-inset-left, env(safe-area-inset-left, 0));
--safe-area-inset-right: var(--app-safe-area-inset-right, env(safe-area-inset-right, 0));
--safe-area-inset-top: var(
--app-safe-area-inset-top,
env(safe-area-inset-top, 0)
);
--safe-area-inset-bottom: var(
--app-safe-area-inset-bottom,
env(safe-area-inset-bottom, 0)
);
--safe-area-inset-left: var(
--app-safe-area-inset-left,
env(safe-area-inset-left, 0)
);
--safe-area-inset-right: var(
--app-safe-area-inset-right,
env(safe-area-inset-right, 0)
);
}
`;

View File

@@ -1,5 +1,8 @@
import { css } from "lit";
import { extractDerivedVars, extractVar } from "../../common/style/derived-css-vars";
import {
extractDerivedVars,
extractVar,
} from "../../common/style/derived-css-vars";
export const typographyStyles = css`
html {
@@ -14,10 +17,16 @@ export const typographyStyles = css`
--ha-font-size-m: calc(14px * var(--ha-font-size-scale)); /* 1rem */
--ha-font-size-l: calc(16px * var(--ha-font-size-scale)); /* 1.142857rem */
--ha-font-size-xl: calc(20px * var(--ha-font-size-scale)); /* 1.428571rem */
--ha-font-size-2xl: calc(24px * var(--ha-font-size-scale)); /* 1.714286rem */
--ha-font-size-2xl: calc(
24px * var(--ha-font-size-scale)
); /* 1.714286rem */
--ha-font-size-3xl: calc(28px * var(--ha-font-size-scale)); /* 2rem */
--ha-font-size-4xl: calc(32px * var(--ha-font-size-scale)); /* 2.285714rem */
--ha-font-size-5xl: calc(40px * var(--ha-font-size-scale)); /* 2.857143rem */
--ha-font-size-4xl: calc(
32px * var(--ha-font-size-scale)
); /* 2.285714rem */
--ha-font-size-5xl: calc(
40px * var(--ha-font-size-scale)
); /* 2.857143rem */
--ha-font-weight-light: 300;
--ha-font-weight-normal: 400;
@@ -51,8 +60,17 @@ export const typographyStyles = css`
export const typographyDerivedVariables = extractDerivedVars(typographyStyles);
export const haFontFamilyBody = extractVar(typographyStyles, "ha-font-family-body");
export const haFontFamilyBody = extractVar(
typographyStyles,
"ha-font-family-body"
);
export const haFontSmoothing = extractVar(typographyStyles, "ha-font-smoothing");
export const haFontSmoothing = extractVar(
typographyStyles,
"ha-font-smoothing"
);
export const haMozOsxFontSmoothing = extractVar(typographyStyles, "ha-moz-osx-font-smoothing");
export const haMozOsxFontSmoothing = extractVar(
typographyStyles,
"ha-moz-osx-font-smoothing"
);

View File

@@ -6,7 +6,8 @@ export const waMainStyles = css`
--wa-focus-ring-style: solid;
--wa-focus-ring-width: 2px;
--wa-focus-ring-offset: 2px;
--wa-focus-ring: var(--wa-focus-ring-style) var(--wa-focus-ring-width) var(--wa-focus-ring-color);
--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);

View File

@@ -661,20 +661,35 @@
},
"target-picker": {
"expand": "Expand",
"collapse": "Collapse",
"expand_floor_id": "Split this floor into separate areas.",
"expand_area_id": "Split this area into separate devices and entities.",
"expand_device_id": "Split this device into separate entities.",
"expand_label_id": "Split this label into separate areas, devices and entities.",
"remove": "Remove",
"remove_floor_id": "Remove floor",
"remove_floors": "Remove floors",
"remove_area_id": "Remove area",
"remove_areas": "Remove areas",
"remove_device_id": "Remove device",
"remove_devices": "Remove devices",
"remove_entity_id": "Remove entity",
"remove_entitys": "Remove entities",
"remove_label_id": "Remove label",
"remove_labels": "Remove labels",
"add_area_id": "Choose area",
"add_device_id": "Choose device",
"add_entity_id": "Choose entity",
"add_label_id": "Choose label"
"add_label_id": "Choose label",
"devices_count": "{count} {count, plural,\n one {device}\n other {devices}\n}",
"entities_count": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
"selected": {
"entity": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
"device": "{count} {count, plural,\n one {device}\n other {devices}\n}",
"area": "{count} {count, plural,\n one {area}\n other {areas}\n}",
"label": "{count} {count, plural,\n one {label}\n other {labels}\n}",
"floor": "{count} {count, plural,\n one {floor}\n other {floors}\n}"
}
},
"subpage-data-table": {
"filters": "Filters",
@@ -2312,9 +2327,7 @@
"header": "AI suggestions",
"description": "Home Assistant can use generative AI to help you with tasks. Look for the button with the {button} icon throughout Home Assistant to get suggestions. Select an AI task entity to use this feature.",
"gen_data_header": "Data generation tasks",
"gen_data_description": "Suggest automation names.",
"gen_image_header": "Image generation tasks",
"gen_image_description": "Generate images."
"gen_data_description": "Suggest automation names."
},
"category": {
"caption": "Categories",
@@ -7818,22 +7831,6 @@
"label": "Minute",
"description": "60 ticks (Every minute)"
}
},
"face_style": {
"label": "Clock face style",
"description": "Which kind of indices to use for the clock face",
"markers": {
"label": "Markers",
"description": "Show simple markers around the clock face (default)"
},
"numbers_upright": {
"label": "Numbers",
"description": "Show numbers around the clock face"
},
"roman": {
"label": "Roman numerals",
"description": "Show Roman numerals (I-XII) around the clock face"
}
}
},
"media-control": {

View File

@@ -8505,7 +8505,7 @@ __metadata:
languageName: node
linkType: hard
"fdir@npm:^6.5.0":
"fdir@npm:^6.4.4, fdir@npm:^6.4.6":
version: 6.5.0
resolution: "fdir@npm:6.5.0"
peerDependencies:
@@ -14208,13 +14208,13 @@ __metadata:
languageName: node
linkType: hard
"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15":
version: 0.2.15
resolution: "tinyglobby@npm:0.2.15"
"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.14":
version: 0.2.14
resolution: "tinyglobby@npm:0.2.14"
dependencies:
fdir: "npm:^6.5.0"
picomatch: "npm:^4.0.3"
checksum: 10/d72bd826a8b0fa5fa3929e7fe5ba48fceb2ae495df3a231b6c5408cd7d8c00b58ab5a9c2a76ba56a62ee9b5e083626f1f33599734bed1ffc4b792406408f0ca2
fdir: "npm:^6.4.4"
picomatch: "npm:^4.0.2"
checksum: 10/3d306d319718b7cc9d79fb3f29d8655237aa6a1f280860a217f93417039d0614891aee6fc47c5db315f4fcc6ac8d55eb8e23e2de73b2c51a431b42456d9e5764
languageName: node
linkType: hard
@@ -14934,16 +14934,16 @@ __metadata:
linkType: hard
"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0":
version: 7.1.5
resolution: "vite@npm:7.1.5"
version: 7.1.2
resolution: "vite@npm:7.1.2"
dependencies:
esbuild: "npm:^0.25.0"
fdir: "npm:^6.5.0"
fdir: "npm:^6.4.6"
fsevents: "npm:~2.3.3"
picomatch: "npm:^4.0.3"
postcss: "npm:^8.5.6"
rollup: "npm:^4.43.0"
tinyglobby: "npm:^0.2.15"
tinyglobby: "npm:^0.2.14"
peerDependencies:
"@types/node": ^20.19.0 || >=22.12.0
jiti: ">=1.21.0"
@@ -14984,7 +14984,7 @@ __metadata:
optional: true
bin:
vite: bin/vite.js
checksum: 10/59edeef7e98757a668b2ad8a1731a5657fa83e22a165a36b7359225ea98a9be39b2f486710c0cf5085edb85daee7c8b6b6b0bd85d0ef32a1aa84aef71aabd0f0
checksum: 10/942188896db181bd5f95c576303eb617cd08580eb129d0223bc87a8ed9b8dc0d60d8487c077fd9a01087c55600af132c5fc677e51b4b48a4a54a376056969edb
languageName: node
linkType: hard