20240329.0 (#20277)

This commit is contained in:
Paul Bottein 2024-03-29 19:10:34 +01:00 committed by GitHub
commit d24d29e42f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 581 additions and 264 deletions

View File

@ -185,8 +185,8 @@
"@types/tar": "6.1.11", "@types/tar": "6.1.11",
"@types/ua-parser-js": "0.7.39", "@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29", "@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "7.3.1", "@typescript-eslint/eslint-plugin": "7.4.0",
"@typescript-eslint/parser": "7.3.1", "@typescript-eslint/parser": "7.4.0",
"@web/dev-server": "0.1.38", "@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1", "@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3", "babel-loader": "9.1.3",

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "home-assistant-frontend" name = "home-assistant-frontend"
version = "20240328.0" version = "20240329.0"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "The Home Assistant frontend" description = "The Home Assistant frontend"
readme = "README.md" readme = "README.md"

View File

@ -512,10 +512,6 @@ export class HaDataTable extends LitElement {
items.push({ append: true, content: this.appendRow }); items.push({ append: true, content: this.appendRow });
} }
if (this.hasFab) {
items.push({ empty: true });
}
if (this.groupColumn) { if (this.groupColumn) {
const grouped = groupBy(items, (item) => item[this.groupColumn!]); const grouped = groupBy(items, (item) => item[this.groupColumn!]);
if (grouped.undefined) { if (grouped.undefined) {
@ -555,6 +551,10 @@ export class HaDataTable extends LitElement {
} else { } else {
this._items = items; this._items = items;
} }
if (this.hasFab) {
this._items = [...this._items, { empty: true }];
}
} else { } else {
this._items = data; this._items = data;
} }

View File

@ -157,11 +157,11 @@ export class HaFilterBlueprints extends LitElement {
border-radius: 50%; border-radius: 50%;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 11px;
background-color: var(--accent-color); background-color: var(--primary-color);
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
padding: 0px 2px; padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color)); color: var(--text-primary-color);
} }
`, `,
]; ];

View File

@ -78,13 +78,15 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
class="ha-scrollbar" class="ha-scrollbar"
activatable activatable
> >
<ha-list-item ${this._categories.length > 0
.selected=${!this.value?.length} ? html`<ha-list-item
.activated=${!this.value?.length} .selected=${!this.value?.length}
>${this.hass.localize( .activated=${!this.value?.length}
"ui.panel.config.category.filter.show_all" >${this.hass.localize(
)}</ha-list-item "ui.panel.config.category.filter.show_all"
> )}</ha-list-item
>`
: nothing}
${this._categories.map( ${this._categories.map(
(category) => (category) =>
html`<ha-list-item html`<ha-list-item
@ -142,7 +144,11 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
: nothing} : nothing}
</ha-expansion-panel> </ha-expansion-panel>
${this.expanded ${this.expanded
? html`<ha-list-item graphic="icon" @click=${this._addCategory}> ? html`<ha-list-item
graphic="icon"
@click=${this._addCategory}
class="add"
>
<ha-svg-icon slot="graphic" .path=${mdiPlus}></ha-svg-icon> <ha-svg-icon slot="graphic" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize("ui.panel.config.category.editor.add")} ${this.hass.localize("ui.panel.config.category.editor.add")}
</ha-list-item>` </ha-list-item>`
@ -254,6 +260,7 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
css` css`
:host { :host {
border-bottom: 1px solid var(--divider-color); border-bottom: 1px solid var(--divider-color);
position: relative;
} }
:host([expanded]) { :host([expanded]) {
flex: 1; flex: 1;
@ -277,11 +284,11 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
border-radius: 50%; border-radius: 50%;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 11px;
background-color: var(--accent-color); background-color: var(--primary-color);
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
padding: 0px 2px; padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color)); color: var(--text-primary-color);
} }
mwc-list { mwc-list {
--mdc-list-item-meta-size: auto; --mdc-list-item-meta-size: auto;
@ -291,6 +298,12 @@ export class HaFilterCategories extends SubscribeMixin(LitElement) {
.warning { .warning {
color: var(--error-color); color: var(--error-color);
} }
.add {
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
`, `,
]; ];
} }

View File

@ -185,11 +185,11 @@ export class HaFilterDevices extends LitElement {
border-radius: 50%; border-radius: 50%;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 11px;
background-color: var(--accent-color); background-color: var(--primary-color);
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
padding: 0px 2px; padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color)); color: var(--text-primary-color);
} }
ha-check-list-item { ha-check-list-item {
width: 100%; width: 100%;

View File

@ -199,11 +199,11 @@ export class HaFilterEntities extends LitElement {
border-radius: 50%; border-radius: 50%;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 11px;
background-color: var(--accent-color); background-color: var(--primary-color);
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
padding: 0px 2px; padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color)); color: var(--text-primary-color);
} }
ha-check-list-item { ha-check-list-item {
--mdc-list-item-graphic-margin: 16px; --mdc-list-item-graphic-margin: 16px;

View File

@ -267,11 +267,11 @@ export class HaFilterFloorAreas extends SubscribeMixin(LitElement) {
border-radius: 50%; border-radius: 50%;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 11px;
background-color: var(--accent-color); background-color: var(--primary-color);
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
padding: 0px 2px; padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color)); color: var(--text-primary-color);
} }
ha-check-list-item { ha-check-list-item {
--mdc-list-item-graphic-margin: 16px; --mdc-list-item-graphic-margin: 16px;

View File

@ -165,11 +165,11 @@ export class HaFilterIntegrations extends LitElement {
border-radius: 50%; border-radius: 50%;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 11px;
background-color: var(--accent-color); background-color: var(--primary-color);
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
padding: 0px 2px; padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color)); color: var(--text-primary-color);
} }
`, `,
]; ];

View File

@ -3,10 +3,12 @@ import "@material/mwc-menu/mwc-menu-surface";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { mdiPlus } from "@mdi/js";
import { computeCssColor } from "../common/color/compute-color"; import { computeCssColor } from "../common/color/compute-color";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { import {
LabelRegistryEntry, LabelRegistryEntry,
createLabelRegistryEntry,
subscribeLabelRegistry, subscribeLabelRegistry,
} from "../data/label_registry"; } from "../data/label_registry";
import { SubscribeMixin } from "../mixins/subscribe-mixin"; import { SubscribeMixin } from "../mixins/subscribe-mixin";
@ -16,6 +18,7 @@ import "./ha-check-list-item";
import "./ha-expansion-panel"; import "./ha-expansion-panel";
import "./ha-icon"; import "./ha-icon";
import "./ha-label"; import "./ha-label";
import { showLabelDetailDialog } from "../panels/config/labels/show-dialog-label-detail";
@customElement("ha-filter-labels") @customElement("ha-filter-labels")
export class HaFilterLabels extends SubscribeMixin(LitElement) { export class HaFilterLabels extends SubscribeMixin(LitElement) {
@ -84,6 +87,16 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
` `
: nothing} : nothing}
</ha-expansion-panel> </ha-expansion-panel>
${this.expanded
? html`<ha-list-item
graphic="icon"
@click=${this._addLabel}
class="add"
>
<ha-svg-icon slot="graphic" .path=${mdiPlus}></ha-svg-icon>
${this.hass.localize("ui.panel.config.labels.add_label")}
</ha-list-item>`
: nothing}
`; `;
} }
@ -92,11 +105,17 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
setTimeout(() => { setTimeout(() => {
if (!this.expanded) return; if (!this.expanded) return;
this.renderRoot.querySelector("mwc-list")!.style.height = this.renderRoot.querySelector("mwc-list")!.style.height =
`${this.clientHeight - 49}px`; `${this.clientHeight - (49 + 48)}px`;
}, 300); }, 300);
} }
} }
private _addLabel() {
showLabelDetailDialog(this, {
createEntry: (values) => createLabelRegistryEntry(this.hass, values),
});
}
private _expandedWillChange(ev) { private _expandedWillChange(ev) {
this._shouldRender = ev.detail.expanded; this._shouldRender = ev.detail.expanded;
} }
@ -134,6 +153,7 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
haStyleScrollbar, haStyleScrollbar,
css` css`
:host { :host {
position: relative;
border-bottom: 1px solid var(--divider-color); border-bottom: 1px solid var(--divider-color);
} }
:host([expanded]) { :host([expanded]) {
@ -158,11 +178,11 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
border-radius: 50%; border-radius: 50%;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 11px;
background-color: var(--accent-color); background-color: var(--primary-color);
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
padding: 0px 2px; padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color)); color: var(--text-primary-color);
} }
.warning { .warning {
color: var(--error-color); color: var(--error-color);
@ -171,6 +191,12 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
--ha-label-background-color: var(--color, var(--grey-color)); --ha-label-background-color: var(--color, var(--grey-color));
--ha-label-background-opacity: 0.5; --ha-label-background-opacity: 0.5;
} }
.add {
position: absolute;
bottom: 0;
right: 0;
left: 0;
}
`, `,
]; ];
} }

View File

@ -147,11 +147,11 @@ export class HaFilterStates extends LitElement {
border-radius: 50%; border-radius: 50%;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 11px;
background-color: var(--accent-color); background-color: var(--primary-color);
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
padding: 0px 2px; padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color)); color: var(--text-primary-color);
} }
`, `,
]; ];

View File

@ -38,10 +38,14 @@ import "./ha-list-item";
type ScorableFloorRegistryEntry = ScorableTextItem & FloorRegistryEntry; type ScorableFloorRegistryEntry = ScorableTextItem & FloorRegistryEntry;
const ADD_NEW_ID = "___ADD_NEW___";
const NO_FLOORS_ID = "___NO_FLOORS___";
const ADD_NEW_SUGGESTION_ID = "___ADD_NEW_SUGGESTION___";
const rowRenderer: ComboBoxLitRenderer<FloorRegistryEntry> = (item) => const rowRenderer: ComboBoxLitRenderer<FloorRegistryEntry> = (item) =>
html`<ha-list-item html`<ha-list-item
graphic="icon" graphic="icon"
class=${classMap({ "add-new": item.floor_id === "add_new" })} class=${classMap({ "add-new": item.floor_id === ADD_NEW_ID })}
> >
<ha-floor-icon slot="graphic" .floor=${item}></ha-floor-icon> <ha-floor-icon slot="graphic" .floor=${item}></ha-floor-icon>
${item.name} ${item.name}
@ -146,18 +150,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
noAdd: this["noAdd"], noAdd: this["noAdd"],
excludeFloors: this["excludeFloors"] excludeFloors: this["excludeFloors"]
): FloorRegistryEntry[] => { ): FloorRegistryEntry[] => {
if (!floors.length) {
return [
{
floor_id: "no_floors",
name: this.hass.localize("ui.components.floor-picker.no_floors"),
icon: null,
level: 0,
aliases: [],
},
];
}
let deviceEntityLookup: DeviceEntityDisplayLookup = {}; let deviceEntityLookup: DeviceEntityDisplayLookup = {};
let inputDevices: DeviceRegistryEntry[] | undefined; let inputDevices: DeviceRegistryEntry[] | undefined;
let inputEntities: EntityRegistryDisplayEntry[] | undefined; let inputEntities: EntityRegistryDisplayEntry[] | undefined;
@ -297,10 +289,10 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
if (!outputFloors.length) { if (!outputFloors.length) {
outputFloors = [ outputFloors = [
{ {
floor_id: "no_floors", floor_id: NO_FLOORS_ID,
name: this.hass.localize("ui.components.floor-picker.no_match"), name: this.hass.localize("ui.components.floor-picker.no_floors"),
icon: null, icon: null,
level: 0, level: null,
aliases: [], aliases: [],
}, },
]; ];
@ -311,10 +303,10 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
: [ : [
...outputFloors, ...outputFloors,
{ {
floor_id: "add_new", floor_id: ADD_NEW_ID,
name: this.hass.localize("ui.components.floor-picker.add_new"), name: this.hass.localize("ui.components.floor-picker.add_new"),
icon: "mdi:plus", icon: "mdi:plus",
level: 0, level: null,
aliases: [], aliases: [],
}, },
]; ];
@ -341,7 +333,7 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
this.excludeFloors this.excludeFloors
).map((floor) => ({ ).map((floor) => ({
...floor, ...floor,
strings: [floor.floor_id, floor.name], // ...floor.aliases strings: [floor.floor_id, floor.name, ...floor.aliases],
})); }));
this.comboBox.items = floors; this.comboBox.items = floors;
this.comboBox.filteredItems = floors; this.comboBox.filteredItems = floors;
@ -385,20 +377,36 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
const filteredItems = fuzzyFilterSort<ScorableFloorRegistryEntry>( const filteredItems = fuzzyFilterSort<ScorableFloorRegistryEntry>(
filterString, filterString,
target.items || [] target.items?.filter(
(item) => ![NO_FLOORS_ID, ADD_NEW_ID].includes(item.label_id)
) || []
); );
if (!this.noAdd && filteredItems?.length === 0) { if (filteredItems.length === 0) {
this._suggestion = filterString; if (this.noAdd) {
this.comboBox.filteredItems = [ this.comboBox.filteredItems = [
{ {
floor_id: "add_new_suggestion", floor_id: NO_FLOORS_ID,
name: this.hass.localize( name: this.hass.localize("ui.components.floor-picker.no_floors"),
"ui.components.floor-picker.add_new_sugestion", icon: null,
{ name: this._suggestion } level: null,
), aliases: [],
picture: null, },
}, ] as FloorRegistryEntry[];
]; } else {
this._suggestion = filterString;
this.comboBox.filteredItems = [
{
floor_id: ADD_NEW_SUGGESTION_ID,
name: this.hass.localize(
"ui.components.floor-picker.add_new_sugestion",
{ name: this._suggestion }
),
icon: "mdi:plus",
level: null,
aliases: [],
},
] as FloorRegistryEntry[];
}
} else { } else {
this.comboBox.filteredItems = filteredItems; this.comboBox.filteredItems = filteredItems;
} }
@ -416,11 +424,13 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
ev.stopPropagation(); ev.stopPropagation();
let newValue = ev.detail.value; let newValue = ev.detail.value;
if (newValue === "no_floors") { if (newValue === NO_FLOORS_ID) {
newValue = ""; newValue = "";
this.comboBox.setInputValue("");
return;
} }
if (!["add_new_suggestion", "add_new"].includes(newValue)) { if (![ADD_NEW_SUGGESTION_ID, ADD_NEW_ID].includes(newValue)) {
if (newValue !== this._value) { if (newValue !== this._value) {
this._setValue(newValue); this._setValue(newValue);
} }
@ -438,7 +448,7 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
"ui.components.floor-picker.add_dialog.name" "ui.components.floor-picker.add_dialog.name"
), ),
defaultValue: defaultValue:
newValue === "add_new_suggestion" ? this._suggestion : undefined, newValue === ADD_NEW_SUGGESTION_ID ? this._suggestion : undefined,
confirm: async (name) => { confirm: async (name) => {
if (!name) { if (!name) {
return; return;

View File

@ -385,8 +385,8 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
const filteredItems = fuzzyFilterSort<ScorableLabelItem>( const filteredItems = fuzzyFilterSort<ScorableLabelItem>(
filterString, filterString,
target.items?.filter((item) => target.items?.filter(
[NO_LABELS_ID, ADD_NEW_ID].includes(item.ignoreFilter) (item) => ![NO_LABELS_ID, ADD_NEW_ID].includes(item.label_id)
) || [] ) || []
); );
if (filteredItems.length === 0) { if (filteredItems.length === 0) {

View File

@ -0,0 +1,38 @@
import { MdOutlinedTextField } from "@material/web/textfield/outlined-text-field";
import "element-internals-polyfill";
import { css } from "lit";
import { customElement } from "lit/decorators";
@customElement("ha-outlined-text-field")
export class HaOutlinedTextField extends MdOutlinedTextField {
static override styles = [
...super.styles,
css`
:host {
--md-sys-color-on-surface: var(--primary-text-color);
--md-sys-color-primary: var(--primary-text-color);
--md-outlined-text-field-input-text-color: var(--primary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-outlined-field-outline-color: var(--outline-color);
--md-outlined-field-focus-outline-color: var(--primary-color);
--md-outlined-field-hover-outline-color: var(--outline-hover-color);
}
:host([dense]) {
--md-outlined-field-top-space: 5.5px;
--md-outlined-field-bottom-space: 5.5px;
--md-outlined-field-container-shape-start-start: 10px;
--md-outlined-field-container-shape-start-end: 10px;
--md-outlined-field-container-shape-end-end: 10px;
--md-outlined-field-container-shape-end-start: 10px;
--md-outlined-field-focus-outline-width: 1px;
--mdc-icon-size: var(--md-input-chip-icon-size, 18px);
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-outlined-text-field": HaOutlinedTextField;
}
}

View File

@ -1,11 +1,11 @@
import "@material/web/textfield/outlined-text-field";
import type { MdOutlinedTextField } from "@material/web/textfield/outlined-text-field";
import { mdiMagnify } from "@mdi/js"; import { mdiMagnify } from "@mdi/js";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import "./ha-icon-button"; import "./ha-icon-button";
import "./ha-outlined-text-field";
import type { HaOutlinedTextField } from "./ha-outlined-text-field";
import "./ha-svg-icon"; import "./ha-svg-icon";
@customElement("search-input-outlined") @customElement("search-input-outlined")
@ -30,19 +30,22 @@ class SearchInputOutlined extends LitElement {
this._input?.focus(); this._input?.focus();
} }
@query("md-outlined-text-field", true) private _input!: MdOutlinedTextField; @query("ha-outlined-text-field", true) private _input!: HaOutlinedTextField;
protected render(): TemplateResult { protected render(): TemplateResult {
const placeholder =
this.placeholder || this.hass.localize("ui.common.search");
return html` return html`
<md-outlined-text-field <ha-outlined-text-field
.autofocus=${this.autofocus} .autofocus=${this.autofocus}
.aria-label=${this.label || this.hass.localize("ui.common.search")} .aria-label=${this.label || this.hass.localize("ui.common.search")}
.placeholder=${this.placeholder || .placeholder=${placeholder}
this.hass.localize("ui.common.search")}
.value=${this.filter || ""} .value=${this.filter || ""}
icon icon
.iconTrailing=${this.filter || this.suffix} .iconTrailing=${this.filter || this.suffix}
@input=${this._filterInputChanged} @input=${this._filterInputChanged}
dense
> >
<slot name="prefix" slot="leading-icon"> <slot name="prefix" slot="leading-icon">
<ha-svg-icon <ha-svg-icon
@ -51,7 +54,7 @@ class SearchInputOutlined extends LitElement {
.path=${mdiMagnify} .path=${mdiMagnify}
></ha-svg-icon> ></ha-svg-icon>
</slot> </slot>
</md-outlined-text-field> </ha-outlined-text-field>
`; `;
} }
@ -67,40 +70,21 @@ class SearchInputOutlined extends LitElement {
return css` return css`
:host { :host {
display: inline-flex; display: inline-flex;
/* For iOS */
z-index: 0;
} }
md-outlined-text-field { ha-outlined-text-field {
display: block; display: block;
width: 100%; width: 100%;
--md-sys-color-on-surface: var(--primary-text-color);
--md-sys-color-primary: var(--primary-text-color);
--md-outlined-text-field-input-text-color: var(--primary-text-color);
--md-sys-color-on-surface-variant: var(--secondary-text-color);
--md-outlined-field-top-space: 5.5px;
--md-outlined-field-bottom-space: 5.5px;
--md-outlined-field-outline-color: var(--outline-color);
--md-outlined-field-container-shape-start-start: 10px;
--md-outlined-field-container-shape-start-end: 10px;
--md-outlined-field-container-shape-end-end: 10px;
--md-outlined-field-container-shape-end-start: 10px;
--md-outlined-field-focus-outline-width: 1px;
--md-outlined-field-focus-outline-color: var(--primary-color);
} }
ha-svg-icon, ha-svg-icon,
ha-icon-button { ha-icon-button {
display: flex; display: flex;
--mdc-icon-size: var(--md-input-chip-icon-size, 18px);
color: var(--primary-text-color); color: var(--primary-text-color);
} }
ha-svg-icon { ha-svg-icon {
outline: none; outline: none;
} }
.clear-button {
--mdc-icon-size: 20px;
}
.trailing {
display: flex;
align-items: center;
}
`; `;
} }
} }

View File

@ -10,7 +10,7 @@ import { computeDomain } from "../common/entity/compute_domain";
export { subscribeEntityRegistryDisplay } from "./ws-entity_registry_display"; export { subscribeEntityRegistryDisplay } from "./ws-entity_registry_display";
type entityCategory = "config" | "diagnostic"; type EntityCategory = "config" | "diagnostic";
export interface EntityRegistryDisplayEntry { export interface EntityRegistryDisplayEntry {
entity_id: string; entity_id: string;
@ -20,7 +20,7 @@ export interface EntityRegistryDisplayEntry {
area_id?: string; area_id?: string;
labels: string[]; labels: string[];
hidden?: boolean; hidden?: boolean;
entity_category?: entityCategory; entity_category?: EntityCategory;
translation_key?: string; translation_key?: string;
platform?: string; platform?: string;
display_precision?: number; display_precision?: number;
@ -40,7 +40,7 @@ export interface EntityRegistryDisplayEntryResponse {
hb?: boolean; hb?: boolean;
dp?: number; dp?: number;
}[]; }[];
entity_categories: Record<number, entityCategory>; entity_categories: Record<number, EntityCategory>;
} }
export interface EntityRegistryEntry { export interface EntityRegistryEntry {
@ -55,7 +55,7 @@ export interface EntityRegistryEntry {
labels: string[]; labels: string[];
disabled_by: "user" | "device" | "integration" | "config_entry" | null; disabled_by: "user" | "device" | "integration" | "config_entry" | null;
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">; hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
entity_category: entityCategory | null; entity_category: EntityCategory | null;
has_entity_name: boolean; has_entity_name: boolean;
original_name?: string; original_name?: string;
unique_id: string; unique_id: string;

View File

@ -1,6 +1,9 @@
import { ResizeController } from "@lit-labs/observers/resize-controller"; import { ResizeController } from "@lit-labs/observers/resize-controller";
import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import "@material/web/menu/menu";
import type { MdMenu } from "@material/web/menu/menu";
import "@material/web/menu/menu-item";
import { import {
mdiArrowDown, mdiArrowDown,
mdiArrowUp, mdiArrowUp,
@ -19,6 +22,7 @@ import {
nothing, nothing,
} from "lit"; } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { LocalizeFunc } from "../common/translations/localize"; import { LocalizeFunc } from "../common/translations/localize";
import "../components/chips/ha-assist-chip"; import "../components/chips/ha-assist-chip";
@ -173,6 +177,10 @@ export class HaTabsSubpageDataTable extends LitElement {
@query("ha-data-table", true) private _dataTable!: HaDataTable; @query("ha-data-table", true) private _dataTable!: HaDataTable;
@query("#group-by-menu") private _groupByMenu!: MdMenu;
@query("#sort-by-menu") private _sortByMenu!: MdMenu;
private _showPaneController = new ResizeController(this, { private _showPaneController = new ResizeController(this, {
callback: (entries) => entries[0]?.contentRect.width > 750, callback: (entries) => entries[0]?.contentRect.width > 750,
}); });
@ -187,6 +195,14 @@ export class HaTabsSubpageDataTable extends LitElement {
} }
} }
private _toggleGroupBy() {
this._groupByMenu.open = !this._groupByMenu.open;
}
private _toggleSortBy() {
this._sortByMenu.open = !this._sortByMenu.open;
}
protected render(): TemplateResult { protected render(): TemplateResult {
const localize = this.localizeFunc || this.hass.localize; const localize = this.localizeFunc || this.hass.localize;
const showPane = this._showPaneController.value ?? !this.narrow; const showPane = this._showPaneController.value ?? !this.narrow;
@ -226,73 +242,35 @@ export class HaTabsSubpageDataTable extends LitElement {
</search-input-outlined>`; </search-input-outlined>`;
const sortByMenu = Object.values(this.columns).find((col) => col.sortable) const sortByMenu = Object.values(this.columns).find((col) => col.sortable)
? html`<ha-button-menu fixed> ? html`
<ha-assist-chip <ha-assist-chip
.label=${localize("ui.components.subpage-data-table.sort_by", { .label=${localize("ui.components.subpage-data-table.sort_by", {
sortColumn: this._sortColumn sortColumn: this._sortColumn
? ` ${this.columns[this._sortColumn].title || this.columns[this._sortColumn].label}` ? ` ${this.columns[this._sortColumn].title || this.columns[this._sortColumn].label}`
: "", : "",
})} })}
slot="trigger" id="sort-by-anchor"
@click=${this._toggleSortBy}
> >
<ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon <ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon
></ha-assist-chip> ></ha-assist-chip>
${Object.entries(this.columns).map(([id, column]) => `
column.sortable
? html`<ha-list-item
.value=${id}
@request-selected=${this._handleSortBy}
hasMeta
.activated=${id === this._sortColumn}
>
${this._sortColumn === id
? html`<ha-svg-icon
slot="meta"
.path=${this._sortDirection === "desc"
? mdiArrowDown
: mdiArrowUp}
></ha-svg-icon>`
: nothing}
${column.title || column.label}
</ha-list-item>`
: nothing
)}
</ha-button-menu>`
: nothing; : nothing;
const groupByMenu = Object.values(this.columns).find((col) => col.groupable) const groupByMenu = Object.values(this.columns).find((col) => col.groupable)
? html`<ha-button-menu fixed> ? html`
<ha-assist-chip <ha-assist-chip
.label=${localize("ui.components.subpage-data-table.group_by", { .label=${localize("ui.components.subpage-data-table.group_by", {
groupColumn: this._groupColumn groupColumn: this._groupColumn
? ` ${this.columns[this._groupColumn].title || this.columns[this._groupColumn].label}` ? ` ${this.columns[this._groupColumn].title || this.columns[this._groupColumn].label}`
: "", : "",
})} })}
slot="trigger" id="group-by-anchor"
@click=${this._toggleGroupBy}
> >
<ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon <ha-svg-icon slot="trailing-icon" .path=${mdiMenuDown}></ha-svg-icon
></ha-assist-chip> ></ha-assist-chip>
${Object.entries(this.columns).map(([id, column]) => `
column.groupable
? html`<ha-list-item
.value=${id}
@request-selected=${this._handleGroupBy}
.activated=${id === this._groupColumn}
>
${column.title || column.label}
</ha-list-item> `
: nothing
)}
<li divider role="separator"></li>
<ha-list-item
.value=${undefined}
@request-selected=${this._handleGroupBy}
.activated=${this._groupColumn === undefined}
>${localize(
"ui.components.subpage-data-table.dont_group_by"
)}</ha-list-item
>
</ha-button-menu>`
: nothing; : nothing;
return html` return html`
@ -431,6 +409,58 @@ export class HaTabsSubpageDataTable extends LitElement {
</ha-data-table>`} </ha-data-table>`}
<div slot="fab"><slot name="fab"></slot></div> <div slot="fab"><slot name="fab"></slot></div>
</hass-tabs-subpage> </hass-tabs-subpage>
<md-menu anchor="group-by-anchor" id="group-by-menu" positioning="fixed">
${Object.entries(this.columns).map(([id, column]) =>
column.groupable
? html`
<md-menu-item
.value=${id}
@click=${this._handleGroupBy}
.selected=${id === this._groupColumn}
class=${classMap({ selected: id === this._groupColumn })}
>
${column.title || column.label}
</md-menu-item>
`
: nothing
)}
<li divider role="separator"></li>
<md-menu-item
.value=${undefined}
@click=${this._handleGroupBy}
.selected=${this._groupColumn === undefined}
class=${classMap({ selected: this._groupColumn === undefined })}
>${localize(
"ui.components.subpage-data-table.dont_group_by"
)}</md-menu-item
>
</md-menu>
<md-menu anchor="sort-by-anchor" id="sort-by-menu" positioning="fixed">
${Object.entries(this.columns).map(([id, column]) =>
column.sortable
? html`
<md-menu-item
.value=${id}
@click=${this._handleSortBy}
.selected=${id === this._sortColumn}
class=${classMap({ selected: id === this._sortColumn })}
>
${this._sortColumn === id
? html`
<ha-svg-icon
slot="end"
.path=${this._sortDirection === "desc"
? mdiArrowDown
: mdiArrowUp}
></ha-svg-icon>
`
: nothing}
${column.title || column.label}
</md-menu-item>
`
: nothing
)}
</md-menu>
`; `;
} }
@ -449,6 +479,7 @@ export class HaTabsSubpageDataTable extends LitElement {
private _handleSortBy(ev) { private _handleSortBy(ev) {
ev.stopPropagation(); ev.stopPropagation();
ev.preventDefault();
const columnId = ev.currentTarget.value; const columnId = ev.currentTarget.value;
if (!this._sortDirection || this._sortColumn !== columnId) { if (!this._sortDirection || this._sortColumn !== columnId) {
this._sortDirection = "asc"; this._sortDirection = "asc";
@ -611,11 +642,11 @@ export class HaTabsSubpageDataTable extends LitElement {
border-radius: 50%; border-radius: 50%;
font-weight: 400; font-weight: 400;
font-size: 11px; font-size: 11px;
background-color: var(--accent-color); background-color: var(--primary-color);
line-height: 16px; line-height: 16px;
text-align: center; text-align: center;
padding: 0px 2px; padding: 0px 2px;
color: var(--text-accent-color, var(--text-primary-color)); color: var(--text-primary-color);
} }
.narrow-header-row { .narrow-header-row {
@ -656,13 +687,6 @@ export class HaTabsSubpageDataTable extends LitElement {
ha-assist-chip { ha-assist-chip {
--ha-assist-chip-container-shape: 10px; --ha-assist-chip-container-shape: 10px;
} }
ha-button-menu {
--mdc-list-item-meta-size: 16px;
--mdc-list-item-meta-display: flex;
}
ha-button-menu ha-assist-chip {
--md-assist-chip-trailing-space: 8px;
}
.select-mode-chip { .select-mode-chip {
--md-assist-chip-icon-label-space: 0; --md-assist-chip-icon-label-space: 0;
@ -688,6 +712,25 @@ export class HaTabsSubpageDataTable extends LitElement {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
/* TODO: Migrate to ha-menu and ha-menu-item */
md-menu {
--md-menu-container-color: var(--card-background-color);
}
md-menu-item {
--md-menu-item-label-text-color: var(--primary-text-color);
--mdc-icon-size: 16px;
--md-menu-item-selected-container-color: rgba(
var(--rgb-primary-color),
0.15
);
}
md-menu-item.selected {
--md-menu-item-label-text-color: var(--primary-color);
}
#sort-by-anchor,
#group-by-anchor {
--md-assist-chip-trailing-space: 8px;
}
`; `;
} }
} }

View File

@ -18,6 +18,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { import {
CSSResultGroup, CSSResultGroup,
LitElement, LitElement,
PropertyValues,
TemplateResult, TemplateResult,
css, css,
html, html,
@ -38,13 +39,15 @@ import type {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/data-table/ha-data-table-labels";
import "../../../components/entity/ha-entity-toggle"; import "../../../components/entity/ha-entity-toggle";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-filter-floor-areas";
import "../../../components/ha-filter-blueprints"; import "../../../components/ha-filter-blueprints";
import "../../../components/ha-filter-categories"; import "../../../components/ha-filter-categories";
import "../../../components/ha-filter-devices"; import "../../../components/ha-filter-devices";
import "../../../components/ha-filter-entities"; import "../../../components/ha-filter-entities";
import "../../../components/ha-filter-floor-areas";
import "../../../components/ha-filter-labels";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-icon-overflow-menu"; import "../../../components/ha-icon-overflow-menu";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
@ -64,6 +67,10 @@ import {
import { fullEntitiesContext } from "../../../data/context"; import { fullEntitiesContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
import { EntityRegistryEntry } from "../../../data/entity_registry"; import { EntityRegistryEntry } from "../../../data/entity_registry";
import {
LabelRegistryEntry,
subscribeLabelRegistry,
} from "../../../data/label_registry";
import { findRelated } from "../../../data/search"; import { findRelated } from "../../../data/search";
import { import {
showAlertDialog, showAlertDialog,
@ -77,12 +84,6 @@ import { documentationUrl } from "../../../util/documentation-url";
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category"; import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showNewAutomationDialog } from "./show-dialog-new-automation"; import { showNewAutomationDialog } from "./show-dialog-new-automation";
import "../../../components/data-table/ha-data-table-labels";
import {
LabelRegistryEntry,
subscribeLabelRegistry,
} from "../../../data/label_registry";
import "../../../components/ha-filter-labels";
type AutomationItem = AutomationEntity & { type AutomationItem = AutomationEntity & {
name: string; name: string;
@ -509,6 +510,13 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
`; `;
} }
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("_entityReg")) {
this._applyFilters();
}
}
firstUpdated() { firstUpdated() {
if (this._searchParms.has("blueprint")) { if (this._searchParms.has("blueprint")) {
this._filterBlueprint(); this._filterBlueprint();

View File

@ -179,7 +179,9 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
const filteredItems = fuzzyFilterSort<ScorableCategoryRegistryEntry>( const filteredItems = fuzzyFilterSort<ScorableCategoryRegistryEntry>(
filterString, filterString,
target.items || [] target.items?.filter(
(item) => ![NO_CATEGORIES_ID, ADD_NEW_ID].includes(item.category_id)
) || []
); );
if (filteredItems?.length === 0) { if (filteredItems?.length === 0) {
if (this.noAdd) { if (this.noAdd) {
@ -224,6 +226,8 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
if (newValue === NO_CATEGORIES_ID) { if (newValue === NO_CATEGORIES_ID) {
newValue = ""; newValue = "";
this.comboBox.setInputValue("");
return;
} }
if (![ADD_NEW_SUGGESTION_ID, ADD_NEW_ID].includes(newValue)) { if (![ADD_NEW_SUGGESTION_ID, ADD_NEW_ID].includes(newValue)) {

View File

@ -10,6 +10,7 @@ import {
nothing, nothing,
} from "lit"; } from "lit";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../common/dom/fire_event";
@ -24,16 +25,18 @@ import {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/data-table/ha-data-table-labels";
import "../../../components/entity/ha-battery-icon"; import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-alert";
import "../../../components/ha-button-menu"; import "../../../components/ha-button-menu";
import "../../../components/ha-check-list-item"; import "../../../components/ha-check-list-item";
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-filter-devices"; import "../../../components/ha-filter-devices";
import "../../../components/ha-filter-floor-areas"; import "../../../components/ha-filter-floor-areas";
import "../../../components/ha-filter-integrations"; import "../../../components/ha-filter-integrations";
import "../../../components/ha-filter-labels";
import "../../../components/ha-filter-states"; import "../../../components/ha-filter-states";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-alert";
import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries"; import { ConfigEntry, sortConfigEntries } from "../../../data/config_entries";
import { fullEntitiesContext } from "../../../data/context"; import { fullEntitiesContext } from "../../../data/context";
import { import {
@ -47,7 +50,12 @@ import {
findBatteryEntity, findBatteryEntity,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { IntegrationManifest } from "../../../data/integration"; import { IntegrationManifest } from "../../../data/integration";
import {
LabelRegistryEntry,
subscribeLabelRegistry,
} from "../../../data/label_registry";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { brandsUrl } from "../../../util/brands-url"; import { brandsUrl } from "../../../util/brands-url";
@ -60,10 +68,11 @@ interface DeviceRowData extends DeviceRegistryEntry {
area?: string; area?: string;
integration?: string; integration?: string;
battery_entity?: [string | undefined, string | undefined]; battery_entity?: [string | undefined, string | undefined];
label_entries: EntityRegistryEntry[];
} }
@customElement("ha-config-devices-dashboard") @customElement("ha-config-devices-dashboard")
export class HaConfigDeviceDashboard extends LitElement { export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false; @property({ type: Boolean }) public narrow = false;
@ -91,6 +100,9 @@ export class HaConfigDeviceDashboard extends LitElement {
@state() private _expandedFilter?: string; @state() private _expandedFilter?: string;
@state()
_labels!: LabelRegistryEntry[];
private _ignoreLocationChange = false; private _ignoreLocationChange = false;
public connectedCallback() { public connectedCallback() {
@ -190,11 +202,17 @@ export class HaConfigDeviceDashboard extends LitElement {
string, string,
{ value: string[] | undefined; items: Set<string> | undefined } { value: string[] | undefined; items: Set<string> | undefined }
>, >,
localize: LocalizeFunc localize: LocalizeFunc,
labelReg?: LabelRegistryEntry[]
) => { ) => {
// Some older installations might have devices pointing at invalid entryIDs // Some older installations might have devices pointing at invalid entryIDs
// So we guard for that. // So we guard for that.
let outputDevices: DeviceRowData[] = Object.values(devices); let outputDevices: DeviceRowData[] = Object.values(devices).map(
(device) => ({
...device,
label_entries: [],
})
);
const deviceEntityLookup: DeviceEntityLookup = {}; const deviceEntityLookup: DeviceEntityLookup = {};
for (const entity of entities) { for (const entity of entities) {
@ -221,16 +239,16 @@ export class HaConfigDeviceDashboard extends LitElement {
const filteredDomains = new Set<string>(); const filteredDomains = new Set<string>();
Object.entries(filters).forEach(([key, flter]) => { Object.entries(filters).forEach(([key, filter]) => {
if (key === "config_entry" && flter.value?.length) { if (key === "config_entry" && filter.value?.length) {
outputDevices = outputDevices.filter((device) => outputDevices = outputDevices.filter((device) =>
device.config_entries.some((entryId) => device.config_entries.some((entryId) =>
flter.value?.includes(entryId) filter.value?.includes(entryId)
) )
); );
const configEntries = entries.filter( const configEntries = entries.filter(
(entry) => entry.entry_id && flter.value?.includes(entry.entry_id) (entry) => entry.entry_id && filter.value?.includes(entry.entry_id)
); );
configEntries.forEach((configEntry) => { configEntries.forEach((configEntry) => {
@ -239,17 +257,21 @@ export class HaConfigDeviceDashboard extends LitElement {
if (configEntries.length === 1) { if (configEntries.length === 1) {
filteredConfigEntry = configEntries[0]; filteredConfigEntry = configEntries[0];
} }
} else if (key === "ha-filter-integrations" && flter.value?.length) { } else if (key === "ha-filter-integrations" && filter.value?.length) {
const entryIds = entries const entryIds = entries
.filter((entry) => flter.value!.includes(entry.domain)) .filter((entry) => filter.value!.includes(entry.domain))
.map((entry) => entry.entry_id); .map((entry) => entry.entry_id);
outputDevices = outputDevices.filter((device) => outputDevices = outputDevices.filter((device) =>
device.config_entries.some((entryId) => entryIds.includes(entryId)) device.config_entries.some((entryId) => entryIds.includes(entryId))
); );
flter.value!.forEach((domain) => filteredDomains.add(domain)); filter.value!.forEach((domain) => filteredDomains.add(domain));
} else if (flter.items) { } else if (key === "ha-filter-labels" && filter.value?.length) {
outputDevices = outputDevices.filter((device) => outputDevices = outputDevices.filter((device) =>
flter.items!.has(device.id) device.labels.some((lbl) => filter.value!.includes(lbl))
);
} else if (filter.items) {
outputDevices = outputDevices.filter((device) =>
filter.items!.has(device.id)
); );
} }
}); });
@ -270,6 +292,12 @@ export class HaConfigDeviceDashboard extends LitElement {
.map((entId) => entryLookup[entId]), .map((entId) => entryLookup[entId]),
manifestLookup manifestLookup
); );
const labels = labelReg && device?.labels;
const labelsEntries = (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
);
return { return {
...device, ...device,
name: computeDeviceName( name: computeDeviceName(
@ -306,6 +334,7 @@ export class HaConfigDeviceDashboard extends LitElement {
this.hass.states[ this.hass.states[
this._batteryEntity(device.id, deviceEntityLookup) || "" this._batteryEntity(device.id, deviceEntityLookup) || ""
]?.state, ]?.state,
label_entries: labelsEntries,
}; };
}); });
@ -351,8 +380,15 @@ export class HaConfigDeviceDashboard extends LitElement {
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (device) => html` template: (device) => html`
${device.name} <div style="font-size: 14px;">${device.name}</div>
<div class="secondary">${device.area} | ${device.integration}</div> <div class="secondary">${device.area} | ${device.integration}</div>
${device.label_entries.length
? html`
<ha-data-table-labels
.labels=${device.label_entries}
></ha-data-table-labels>
`
: nothing}
`, `,
}; };
} else { } else {
@ -361,8 +397,18 @@ export class HaConfigDeviceDashboard extends LitElement {
main: true, main: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true,
direction: "asc", direction: "asc",
grows: true,
template: (device) => html`
<div style="font-size: 14px;">${device.name}</div>
${device.label_entries.length
? html`
<ha-data-table-labels
.labels=${device.label_entries}
></ha-data-table-labels>
`
: nothing}
`,
}; };
} }
@ -441,9 +487,25 @@ export class HaConfigDeviceDashboard extends LitElement {
? this.hass.localize("ui.panel.config.devices.disabled") ? this.hass.localize("ui.panel.config.devices.disabled")
: "", : "",
}; };
columns.labels = {
title: "",
hidden: true,
filterable: true,
template: (device) =>
device.label_entries.map((lbl) => lbl.name).join(" "),
};
return columns; return columns;
}); });
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeLabelRegistry(this.hass.connection, (labels) => {
this._labels = labels;
}),
];
}
protected render(): TemplateResult { protected render(): TemplateResult {
const { devicesOutput } = this._devicesAndFilterDomains( const { devicesOutput } = this._devicesAndFilterDomains(
this.hass.devices, this.hass.devices,
@ -452,7 +514,8 @@ export class HaConfigDeviceDashboard extends LitElement {
this.hass.areas, this.hass.areas,
this.manifests, this.manifests,
this._filters, this._filters,
this.hass.localize this.hass.localize,
this._labels
); );
return html` return html`
@ -479,6 +542,7 @@ export class HaConfigDeviceDashboard extends LitElement {
@row-click=${this._handleRowClicked} @row-click=${this._handleRowClicked}
clickable clickable
hasFab hasFab
class=${this.narrow ? "narrow" : ""}
> >
<ha-integration-overflow-menu <ha-integration-overflow-menu
.hass=${this.hass} .hass=${this.hass}
@ -531,6 +595,15 @@ export class HaConfigDeviceDashboard extends LitElement {
.narrow=${this.narrow} .narrow=${this.narrow}
@expanded-changed=${this._filterExpanded} @expanded-changed=${this._filterExpanded}
></ha-filter-states> ></ha-filter-states>
<ha-filter-labels
.hass=${this.hass}
.value=${this._filters["ha-filter-labels"]?.value}
@data-table-filter-changed=${this._filterChanged}
slot="filter-pane"
.expanded=${this._expandedFilter === "ha-filter-labels"}
.narrow=${this.narrow}
@expanded-changed=${this._filterExpanded}
></ha-filter-labels>
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
`; `;
} }
@ -590,8 +663,10 @@ export class HaConfigDeviceDashboard extends LitElement {
this.hass.areas, this.hass.areas,
this.manifests, this.manifests,
this._filters, this._filters,
this.hass.localize this.hass.localize,
this._labels
); );
if ( if (
filteredDomains.size === 1 && filteredDomains.size === 1 &&
(PROTOCOL_INTEGRATIONS as ReadonlyArray<string>).includes( (PROTOCOL_INTEGRATIONS as ReadonlyArray<string>).includes(
@ -611,6 +686,12 @@ export class HaConfigDeviceDashboard extends LitElement {
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
css` css`
hass-tabs-subpage-data-table {
--data-table-row-height: 60px;
}
hass-tabs-subpage-data-table.narrow {
--data-table-row-height: 72px;
}
ha-button-menu { ha-button-menu {
margin-left: 8px; margin-left: 8px;
margin-inline-start: 8px; margin-inline-start: 8px;

View File

@ -10,7 +10,7 @@ import {
mdiRestoreAlert, mdiRestoreAlert,
mdiUndo, mdiUndo,
} from "@mdi/js"; } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { import {
CSSResultGroup, CSSResultGroup,
LitElement, LitElement,
@ -37,16 +37,18 @@ import type {
RowClickedEvent, RowClickedEvent,
SelectionChangedEvent, SelectionChangedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/data-table/ha-data-table-labels";
import "../../../components/ha-alert";
import "../../../components/ha-button-menu"; import "../../../components/ha-button-menu";
import "../../../components/ha-check-list-item"; import "../../../components/ha-check-list-item";
import "../../../components/ha-filter-devices"; import "../../../components/ha-filter-devices";
import "../../../components/ha-filter-floor-areas"; import "../../../components/ha-filter-floor-areas";
import "../../../components/ha-filter-integrations"; import "../../../components/ha-filter-integrations";
import "../../../components/ha-filter-states"; import "../../../components/ha-filter-states";
import "../../../components/ha-filter-labels";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import "../../../components/ha-alert";
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries"; import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
import { fullEntitiesContext } from "../../../data/context"; import { fullEntitiesContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
@ -57,6 +59,10 @@ import {
updateEntityRegistryEntry, updateEntityRegistryEntry,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { entryIcon } from "../../../data/icons"; import { entryIcon } from "../../../data/icons";
import {
LabelRegistryEntry,
subscribeLabelRegistry,
} from "../../../data/label_registry";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@ -65,6 +71,7 @@ import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table"; import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types"; import type { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
@ -86,10 +93,11 @@ export interface EntityRow extends StateEntity {
status: string | undefined; status: string | undefined;
area?: string; area?: string;
localized_platform: string; localized_platform: string;
label_entries: LabelRegistryEntry[];
} }
@customElement("ha-config-entities") @customElement("ha-config-entities")
export class HaConfigEntities extends LitElement { export class HaConfigEntities extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public isWide = false; @property({ type: Boolean }) public isWide = false;
@ -119,6 +127,9 @@ export class HaConfigEntities extends LitElement {
@state() private _expandedFilter?: string; @state() private _expandedFilter?: string;
@state()
_labels!: LabelRegistryEntry[];
@query("hass-tabs-subpage-data-table", true) @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable; private _dataTable!: HaTabsSubpageDataTable;
@ -202,14 +213,21 @@ export class HaConfigEntities extends LitElement {
filterable: true, filterable: true,
direction: "asc", direction: "asc",
grows: true, grows: true,
template: narrow template: (entry) => html`
? (entry) => html` <div style="font-size: 14px;">${entry.name}</div>
${entry.name}<br /> ${narrow
<div class="secondary"> ? html`<div class="secondary">
${entry.entity_id} | ${entry.localized_platform} ${entry.entity_id} | ${entry.localized_platform}
</div> </div>`
` : nothing}
: undefined, ${entry.label_entries.length
? html`
<ha-data-table-labels
.labels=${entry.label_entries}
></ha-data-table-labels>
`
: nothing}
`,
}, },
entity_id: { entity_id: {
title: localize("ui.panel.config.entities.picker.headers.entity_id"), title: localize("ui.panel.config.entities.picker.headers.entity_id"),
@ -301,6 +319,13 @@ export class HaConfigEntities extends LitElement {
` `
: "—", : "—",
}, },
labels: {
title: "",
hidden: true,
filterable: true,
template: (entry) =>
entry.label_entries.map((lbl) => lbl.name).join(" "),
},
}) })
); );
@ -315,7 +340,8 @@ export class HaConfigEntities extends LitElement {
string, string,
{ value: string[] | undefined; items: Set<string> | undefined } { value: string[] | undefined; items: Set<string> | undefined }
>, >,
entries?: ConfigEntry[] entries?: ConfigEntry[],
labelReg?: LabelRegistryEntry[]
) => { ) => {
const result: EntityRow[] = []; const result: EntityRow[] = [];
@ -337,12 +363,12 @@ export class HaConfigEntities extends LitElement {
let filteredConfigEntry: ConfigEntry | undefined; let filteredConfigEntry: ConfigEntry | undefined;
const filteredDomains = new Set<string>(); const filteredDomains = new Set<string>();
Object.entries(filters).forEach(([key, flter]) => { Object.entries(filters).forEach(([key, filter]) => {
if (key === "config_entry" && flter.value?.length) { if (key === "config_entry" && filter.value?.length) {
filteredEntities = filteredEntities.filter( filteredEntities = filteredEntities.filter(
(entity) => (entity) =>
entity.config_entry_id && entity.config_entry_id &&
flter.value?.includes(entity.config_entry_id) filter.value?.includes(entity.config_entry_id)
); );
if (!entries) { if (!entries) {
@ -351,7 +377,7 @@ export class HaConfigEntities extends LitElement {
} }
const configEntries = entries.filter( const configEntries = entries.filter(
(entry) => entry.entry_id && flter.value?.includes(entry.entry_id) (entry) => entry.entry_id && filter.value?.includes(entry.entry_id)
); );
configEntries.forEach((configEntry) => { configEntries.forEach((configEntry) => {
@ -360,23 +386,27 @@ export class HaConfigEntities extends LitElement {
if (configEntries.length === 1) { if (configEntries.length === 1) {
filteredConfigEntry = configEntries[0]; filteredConfigEntry = configEntries[0];
} }
} else if (key === "ha-filter-integrations" && flter.value?.length) { } else if (key === "ha-filter-integrations" && filter.value?.length) {
if (!entries) { if (!entries) {
this._loadConfigEntries(); this._loadConfigEntries();
return; return;
} }
const entryIds = entries const entryIds = entries
.filter((entry) => flter.value!.includes(entry.domain)) .filter((entry) => filter.value!.includes(entry.domain))
.map((entry) => entry.entry_id); .map((entry) => entry.entry_id);
filteredEntities = filteredEntities.filter( filteredEntities = filteredEntities.filter(
(entity) => (entity) =>
entity.config_entry_id && entity.config_entry_id &&
entryIds.includes(entity.config_entry_id) entryIds.includes(entity.config_entry_id)
); );
flter.value!.forEach((domain) => filteredDomains.add(domain)); filter.value!.forEach((domain) => filteredDomains.add(domain));
} else if (flter.items) { } else if (key === "ha-filter-labels" && filter.value?.length) {
filteredEntities = filteredEntities.filter((entity) => filteredEntities = filteredEntities.filter((entity) =>
flter.items!.has(entity.entity_id) entity.labels.some((lbl) => filter.value!.includes(lbl))
);
} else if (filter.items) {
filteredEntities = filteredEntities.filter((entity) =>
filter.items!.has(entity.entity_id)
); );
} }
}); });
@ -404,6 +434,11 @@ export class HaConfigEntities extends LitElement {
continue; continue;
} }
const labels = labelReg && entry?.labels;
const labelsEntries = (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
);
result.push({ result.push({
...entry, ...entry,
entity, entity,
@ -431,6 +466,7 @@ export class HaConfigEntities extends LitElement {
: localize( : localize(
"ui.panel.config.entities.picker.status.available" "ui.panel.config.entities.picker.status.available"
), ),
label_entries: labelsEntries,
}); });
} }
@ -438,6 +474,14 @@ export class HaConfigEntities extends LitElement {
} }
); );
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [
subscribeLabelRegistry(this.hass.connection, (labels) => {
this._labels = labels;
}),
];
}
protected render() { protected render() {
if (!this.hass || this._entities === undefined) { if (!this.hass || this._entities === undefined) {
return html` <hass-loading-screen></hass-loading-screen> `; return html` <hass-loading-screen></hass-loading-screen> `;
@ -451,7 +495,8 @@ export class HaConfigEntities extends LitElement {
this.hass.areas, this.hass.areas,
this._stateEntities, this._stateEntities,
this._filters, this._filters,
this._entries this._entries,
this._labels
); );
const includeAddDeviceFab = const includeAddDeviceFab =
@ -492,6 +537,7 @@ export class HaConfigEntities extends LitElement {
@row-click=${this._openEditEntry} @row-click=${this._openEditEntry}
id="entity_id" id="entity_id"
.hasFab=${includeAddDeviceFab} .hasFab=${includeAddDeviceFab}
class=${this.narrow ? "narrow" : ""}
> >
<ha-integration-overflow-menu <ha-integration-overflow-menu
.hass=${this.hass} .hass=${this.hass}
@ -633,6 +679,15 @@ export class HaConfigEntities extends LitElement {
.narrow=${this.narrow} .narrow=${this.narrow}
@expanded-changed=${this._filterExpanded} @expanded-changed=${this._filterExpanded}
></ha-filter-states> ></ha-filter-states>
<ha-filter-labels
.hass=${this.hass}
.value=${this._filters["ha-filter-labels"]?.value}
@data-table-filter-changed=${this._filterChanged}
slot="filter-pane"
.expanded=${this._expandedFilter === "ha-filter-labels"}
.narrow=${this.narrow}
@expanded-changed=${this._filterExpanded}
></ha-filter-labels>
${includeAddDeviceFab ${includeAddDeviceFab
? html`<ha-fab ? html`<ha-fab
.label=${this.hass.localize("ui.panel.config.devices.add_device")} .label=${this.hass.localize("ui.panel.config.devices.add_device")}
@ -918,7 +973,8 @@ export class HaConfigEntities extends LitElement {
this.hass.areas, this.hass.areas,
this._stateEntities, this._stateEntities,
this._filters, this._filters,
this._entries this._entries,
this._labels
); );
if ( if (
filteredDomains.size === 1 && filteredDomains.size === 1 &&
@ -940,6 +996,12 @@ export class HaConfigEntities extends LitElement {
return [ return [
haStyle, haStyle,
css` css`
hass-tabs-subpage-data-table {
--data-table-row-height: 60px;
}
hass-tabs-subpage-data-table.narrow {
--data-table-row-height: 72px;
}
hass-loading-screen { hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color); --app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color); --app-header-text-color: var(--sidebar-text-color);

View File

@ -1,4 +1,4 @@
import { mdiHelpCircle, mdiPlus } from "@mdi/js"; import { mdiDelete, mdiHelpCircle, mdiPlus } from "@mdi/js";
import { LitElement, PropertyValues, html, nothing } from "lit"; import { LitElement, PropertyValues, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
@ -11,6 +11,7 @@ import {
import "../../../components/ha-fab"; import "../../../components/ha-fab";
import "../../../components/ha-icon-button"; import "../../../components/ha-icon-button";
import "../../../components/ha-relative-time"; import "../../../components/ha-relative-time";
import "../../../components/ha-icon-overflow-menu";
import { import {
LabelRegistryEntry, LabelRegistryEntry,
LabelRegistryEntryMutableParams, LabelRegistryEntryMutableParams,
@ -71,6 +72,26 @@ export class HaConfigLabels extends LitElement {
filterable: true, filterable: true,
grows: true, grows: true,
}, },
actions: {
title: "",
width: "64px",
type: "overflow-menu",
template: (label) => html`
<ha-icon-overflow-menu
.hass=${this.hass}
narrow
.items=${[
{
label: this.hass.localize("ui.common.delete"),
path: mdiDelete,
action: () => this._removeLabel(label),
warning: true,
},
]}
>
</ha-icon-overflow-menu>
`,
},
}; };
return columns; return columns;
}); });
@ -189,6 +210,7 @@ export class HaConfigLabels extends LitElement {
}), }),
dismissText: this.hass!.localize("ui.common.cancel"), dismissText: this.hass!.localize("ui.common.cancel"),
confirmText: this.hass!.localize("ui.common.remove"), confirmText: this.hass!.localize("ui.common.remove"),
destructive: true,
})) }))
) { ) {
return false; return false;

View File

@ -16,6 +16,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { import {
CSSResultGroup, CSSResultGroup,
LitElement, LitElement,
PropertyValues,
TemplateResult, TemplateResult,
css, css,
html, html,
@ -297,6 +298,13 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
} }
); );
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("_entityReg")) {
this._applyFilters();
}
}
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] { protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
return [ return [
subscribeCategoryRegistry(this.hass.connection, "scene", (categories) => { subscribeCategoryRegistry(this.hass.connection, "scene", (categories) => {

View File

@ -15,6 +15,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { import {
CSSResultGroup, CSSResultGroup,
LitElement, LitElement,
PropertyValues,
TemplateResult, TemplateResult,
css, css,
html, html,
@ -560,6 +561,13 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
this._filteredScripts = items ? [...items] : undefined; this._filteredScripts = items ? [...items] : undefined;
} }
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("_entityReg")) {
this._applyFilters();
}
}
firstUpdated() { firstUpdated() {
if (this._searchParms.has("blueprint")) { if (this._searchParms.has("blueprint")) {
this._filterBlueprint(); this._filterBlueprint();

View File

@ -98,10 +98,10 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
display: block; display: block;
padding: 24px 16px 16px; padding: 24px 16px 16px;
} }
:host { #root {
--ha-card-border-radius: inherit !important; --ha-card-border-radius: var(--restore-card-border-radius, inherit);
--ha-card-border-width: inherit !important; --ha-card-border-width: var(--restore-card-border-width, inherit);
--ha-card-box-shadow: inherit !important; --ha-card-box-shadow: var(--restore-card-border-shadow, inherit);
} }
`; `;
} }

View File

@ -108,6 +108,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
const card: LovelaceCard = this.cards[0]; const card: LovelaceCard = this.cards[0];
card.isPanel = true; card.isPanel = true;
card.toggleAttribute("no-border", true);
if (this.isStrategy || !this.lovelace?.editMode) { if (this.isStrategy || !this.lovelace?.editMode) {
card.editMode = false; card.editMode = false;
@ -116,6 +117,7 @@ export class PanelView extends LitElement implements LovelaceViewElement {
} }
const wrapper = document.createElement("hui-card-options"); const wrapper = document.createElement("hui-card-options");
wrapper.toggleAttribute("no-border", true);
wrapper.hass = this.hass; wrapper.hass = this.hass;
wrapper.lovelace = this.lovelace; wrapper.lovelace = this.lovelace;
wrapper.path = [this.index!, 0]; wrapper.path = [this.index!, 0];
@ -130,9 +132,12 @@ export class PanelView extends LitElement implements LovelaceViewElement {
:host { :host {
display: block; display: block;
height: 100%; height: 100%;
--restore-card-border-radius: var(--ha-card-border-radius, 12px);
--restore-card-border-width: var(--ha-card-border-width, 1px);
--restore-card-box-shadow: var(--ha-card-box-shadow, none);
} }
* { [no-border] {
--ha-card-border-radius: 0; --ha-card-border-radius: 0;
--ha-card-border-width: 0; --ha-card-border-width: 0;
--ha-card-box-shadow: none; --ha-card-box-shadow: none;

View File

@ -32,6 +32,7 @@ const mainStyles = css`
--accent-color: ${unsafeCSS(DEFAULT_ACCENT_COLOR)}; --accent-color: ${unsafeCSS(DEFAULT_ACCENT_COLOR)};
--divider-color: rgba(0, 0, 0, 0.12); --divider-color: rgba(0, 0, 0, 0.12);
--outline-color: rgba(0, 0, 0, 0.12); --outline-color: rgba(0, 0, 0, 0.12);
--outline-hover-color: rgba(0, 0, 0, 0.24);
--scrollbar-thumb-color: rgb(194, 194, 194); --scrollbar-thumb-color: rgb(194, 194, 194);

View File

@ -15,6 +15,7 @@ export const darkStyles = {
"switch-unchecked-track-color": "#9b9b9b", "switch-unchecked-track-color": "#9b9b9b",
"divider-color": "rgba(225, 225, 225, .12)", "divider-color": "rgba(225, 225, 225, .12)",
"outline-color": "rgba(225, 225, 225, .12)", "outline-color": "rgba(225, 225, 225, .12)",
"outline-hover-color": "rgba(225, 225, 225, .24)",
"mdc-ripple-color": "#AAAAAA", "mdc-ripple-color": "#AAAAAA",
"mdc-linear-progress-buffer-color": "rgba(255, 255, 255, 0.1)", "mdc-linear-progress-buffer-color": "rgba(255, 255, 255, 0.1)",

View File

@ -1959,7 +1959,11 @@
"labels": { "labels": {
"caption": "Labels", "caption": "Labels",
"description": "Group devices and entities", "description": "Group devices and entities",
"headers": { "name": "Name", "icon": "Icon", "color": "Color" }, "headers": {
"name": "Name",
"icon": "Icon",
"color": "Color"
},
"add_label": "Add label", "add_label": "Add label",
"no_labels": "You don't have any labels", "no_labels": "You don't have any labels",
"introduction": "Labels can help you organize your areas, devices and entities. They can be used to filter in the UI, or use them as a target in automations.", "introduction": "Labels can help you organize your areas, devices and entities. They can be used to filter in the UI, or use them as a target in automations.",
@ -5388,7 +5392,6 @@
"type": "View type", "type": "View type",
"type_warning_sections": "You can not change your view to use the 'sections' view type because migration is not supported yet. Start from scratch with a new view if you want to experiment with the 'sections' view.", "type_warning_sections": "You can not change your view to use the 'sections' view type because migration is not supported yet. Start from scratch with a new view if you want to experiment with the 'sections' view.",
"type_warning_others": "You can not change your view to an other type because migration is not supported yet. Start from scratch with a new view if you want to use another view type.", "type_warning_others": "You can not change your view to an other type because migration is not supported yet. Start from scratch with a new view if you want to use another view type.",
"types": { "types": {
"masonry": "Masonry (default)", "masonry": "Masonry (default)",
"sidebar": "Sidebar", "sidebar": "Sidebar",

104
yarn.lock
View File

@ -4543,15 +4543,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/eslint-plugin@npm:7.3.1": "@typescript-eslint/eslint-plugin@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/eslint-plugin@npm:7.3.1" resolution: "@typescript-eslint/eslint-plugin@npm:7.4.0"
dependencies: dependencies:
"@eslint-community/regexpp": "npm:^4.5.1" "@eslint-community/regexpp": "npm:^4.5.1"
"@typescript-eslint/scope-manager": "npm:7.3.1" "@typescript-eslint/scope-manager": "npm:7.4.0"
"@typescript-eslint/type-utils": "npm:7.3.1" "@typescript-eslint/type-utils": "npm:7.4.0"
"@typescript-eslint/utils": "npm:7.3.1" "@typescript-eslint/utils": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.3.1" "@typescript-eslint/visitor-keys": "npm:7.4.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
graphemer: "npm:^1.4.0" graphemer: "npm:^1.4.0"
ignore: "npm:^5.2.4" ignore: "npm:^5.2.4"
@ -4564,44 +4564,44 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/8ed276113a714d93ab3ababb1179e4785bd9378e6d97726519ea1d2ac502a94475e0be988c2ec427dcfc1e6950329d58da6e64131ee87028fce63493461cc51a checksum: 10/9bd8852c7e4e9608c3fded94f7c60506cc7d2b6d8a8c1cad6d48969a7363751b20282874e55ccdf180635cf204cb10b3e1e5c3d1cff34d4fcd07762be3fc138e
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/parser@npm:7.3.1": "@typescript-eslint/parser@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/parser@npm:7.3.1" resolution: "@typescript-eslint/parser@npm:7.4.0"
dependencies: dependencies:
"@typescript-eslint/scope-manager": "npm:7.3.1" "@typescript-eslint/scope-manager": "npm:7.4.0"
"@typescript-eslint/types": "npm:7.3.1" "@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/typescript-estree": "npm:7.3.1" "@typescript-eslint/typescript-estree": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.3.1" "@typescript-eslint/visitor-keys": "npm:7.4.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/018326010fec1dcefd75809ccac5102a475bf1e052d824b898d707e7c0bf3e51e101164b410d1b2a513628985c96eb412538644d2005e26b99a22db6eb9402df checksum: 10/142a9e1187d305ed43b4fef659c36fa4e28359467198c986f0955c70b4067c9799f4c85d9881fbf099c55dfb265e30666e28b3ef290520e242b45ca7cb8e4ca9
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/scope-manager@npm:7.3.1": "@typescript-eslint/scope-manager@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/scope-manager@npm:7.3.1" resolution: "@typescript-eslint/scope-manager@npm:7.4.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:7.3.1" "@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.3.1" "@typescript-eslint/visitor-keys": "npm:7.4.0"
checksum: 10/7384d1f46d7f3678a1135a1ac0bd8b6dfa2f01e93b19e2510c7082766cf6983a1bf80b4ccf498651199a81d9f2bdb65101fd7a19226a723260514204d0c30b34 checksum: 10/8cf9292444f9731017a707cac34bef5ae0eb33b5cd42ed07fcd046e981d97889d9201d48e02f470f2315123f53771435e10b1dc81642af28a11df5352a8e8be2
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/type-utils@npm:7.3.1": "@typescript-eslint/type-utils@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/type-utils@npm:7.3.1" resolution: "@typescript-eslint/type-utils@npm:7.4.0"
dependencies: dependencies:
"@typescript-eslint/typescript-estree": "npm:7.3.1" "@typescript-eslint/typescript-estree": "npm:7.4.0"
"@typescript-eslint/utils": "npm:7.3.1" "@typescript-eslint/utils": "npm:7.4.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
ts-api-utils: "npm:^1.0.1" ts-api-utils: "npm:^1.0.1"
peerDependencies: peerDependencies:
@ -4609,23 +4609,23 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/fae9003a76a8f2a2a4bb88dc0f82c0a1ca0688633183fac391920e7124a12807aac84bb287a21f61e99523c15223d1c08e7680685ebf21d07429604cba6c420b checksum: 10/a8bd0929d8237679b2b8a7817f070a4b9658ee976882fba8ff37e4a70dd33f87793e1b157771104111fe8054eaa8ad437a010b6aa465072fbdb932647125db2d
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/types@npm:7.3.1": "@typescript-eslint/types@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/types@npm:7.3.1" resolution: "@typescript-eslint/types@npm:7.4.0"
checksum: 10/c9c8eae1cf937cececd99a253bd65eb71b40206e79cf917ad9c3b3ab80cc7ce5fefb2804f9fd2a70e7438951f0d1e63df3031fc61e3a08dfef5fde208a12e0ed checksum: 10/2782c5bf65cd3dfa9cd32bc3023676bbca22144987c3f6c6b67fd96c73d4a60b85a57458c49fd11b9971ac6531824bb3ae0664491e7a6de25d80c523c9be92b7
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/typescript-estree@npm:7.3.1": "@typescript-eslint/typescript-estree@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/typescript-estree@npm:7.3.1" resolution: "@typescript-eslint/typescript-estree@npm:7.4.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:7.3.1" "@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/visitor-keys": "npm:7.3.1" "@typescript-eslint/visitor-keys": "npm:7.4.0"
debug: "npm:^4.3.4" debug: "npm:^4.3.4"
globby: "npm:^11.1.0" globby: "npm:^11.1.0"
is-glob: "npm:^4.0.3" is-glob: "npm:^4.0.3"
@ -4635,34 +4635,34 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
typescript: typescript:
optional: true optional: true
checksum: 10/363ad9864b56394b4000dff7c2b77d0ea52042c3c20e3b86c0f3c66044915632d9890255527c6f3a5ef056886dec72e38fbcfce49d4ad092c160440f54128230 checksum: 10/162ec9d7582f45588342e1be36fdb60e41f50bbdfbc3035c91b517ff5d45244f776921c88d88e543e1c7d0f1e6ada5474a8316b78f1b0e6d2233b101bc45b166
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/utils@npm:7.3.1": "@typescript-eslint/utils@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/utils@npm:7.3.1" resolution: "@typescript-eslint/utils@npm:7.4.0"
dependencies: dependencies:
"@eslint-community/eslint-utils": "npm:^4.4.0" "@eslint-community/eslint-utils": "npm:^4.4.0"
"@types/json-schema": "npm:^7.0.12" "@types/json-schema": "npm:^7.0.12"
"@types/semver": "npm:^7.5.0" "@types/semver": "npm:^7.5.0"
"@typescript-eslint/scope-manager": "npm:7.3.1" "@typescript-eslint/scope-manager": "npm:7.4.0"
"@typescript-eslint/types": "npm:7.3.1" "@typescript-eslint/types": "npm:7.4.0"
"@typescript-eslint/typescript-estree": "npm:7.3.1" "@typescript-eslint/typescript-estree": "npm:7.4.0"
semver: "npm:^7.5.4" semver: "npm:^7.5.4"
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
checksum: 10/234d9d65fe5d0f4a31345bd8f5a6f2879a578b3a531a14c2b3edaa7fb587c71d26249f86c41857382c0405384dc104955c02b588b3cee6fc2734f1ae40aef07b checksum: 10/ffed27e770c486cd000ff892d9049b0afe8b9d6318452a5355b78a37436cbb414bceacae413a2ac813f3e584684825d5e0baa2e6376b7ad6013a108ac91bc19d
languageName: node languageName: node
linkType: hard linkType: hard
"@typescript-eslint/visitor-keys@npm:7.3.1": "@typescript-eslint/visitor-keys@npm:7.4.0":
version: 7.3.1 version: 7.4.0
resolution: "@typescript-eslint/visitor-keys@npm:7.3.1" resolution: "@typescript-eslint/visitor-keys@npm:7.4.0"
dependencies: dependencies:
"@typescript-eslint/types": "npm:7.3.1" "@typescript-eslint/types": "npm:7.4.0"
eslint-visitor-keys: "npm:^3.4.1" eslint-visitor-keys: "npm:^3.4.1"
checksum: 10/163a93597c1d696920a19b3c1627d02368bdd52059f811c0fadd680c38034bb6418ebefe99d8ce26e0dd44ae184f18fab186af775de1a8771256be1a7905c174 checksum: 10/70dc99f2ad116c6e2d9e55af249e4453e06bba2ceea515adef2d2e86e97e557865bb1b1d467667462443eb0d624baba36f7442fd1082f3874339bbc381c26e93
languageName: node languageName: node
linkType: hard linkType: hard
@ -9688,8 +9688,8 @@ __metadata:
"@types/tar": "npm:6.1.11" "@types/tar": "npm:6.1.11"
"@types/ua-parser-js": "npm:0.7.39" "@types/ua-parser-js": "npm:0.7.39"
"@types/webspeechapi": "npm:0.0.29" "@types/webspeechapi": "npm:0.0.29"
"@typescript-eslint/eslint-plugin": "npm:7.3.1" "@typescript-eslint/eslint-plugin": "npm:7.4.0"
"@typescript-eslint/parser": "npm:7.3.1" "@typescript-eslint/parser": "npm:7.4.0"
"@vaadin/combo-box": "npm:24.3.10" "@vaadin/combo-box": "npm:24.3.10"
"@vaadin/vaadin-themable-mixin": "npm:24.3.10" "@vaadin/vaadin-themable-mixin": "npm:24.3.10"
"@vibrant/color": "npm:3.2.1-alpha.1" "@vibrant/color": "npm:3.2.1-alpha.1"