Compare commits

..

10 Commits

Author SHA1 Message Date
Aidan Timson e9c16fd72a Typing improvement 2026-05-28 16:25:31 +01:00
Aidan Timson 559dc0a98e Fix popstate on dialog launch/close 2026-05-28 16:09:38 +01:00
Aidan Timson 22c3025d8f Typing 2026-05-28 15:46:59 +01:00
Aidan Timson 27273e18d0 Fix order of operations 2026-05-28 15:45:12 +01:00
Aidan Timson a4f30f9086 Match signatures with lazy context provider 2026-05-28 15:39:37 +01:00
Aidan Timson 787212b5f0 Cleanup 2026-05-28 15:33:41 +01:00
Aidan Timson 43eb7fd9c9 Move context provider into class and use in context mixin 2026-05-28 15:26:23 +01:00
Aidan Timson 8b76d97e3a Direct use consume 2026-05-28 15:16:32 +01:00
Aidan Timson cbae26ebec Provider mixin and dont pass sets when context can be used 2026-05-28 15:14:11 +01:00
Aidan Timson f2792024c1 Create shared related context for quick bar and add automation element 2026-05-28 14:59:07 +01:00
24 changed files with 444 additions and 531 deletions
+1 -1
View File
@@ -198,7 +198,7 @@
"terser-webpack-plugin": "5.6.0",
"ts-lit-plugin": "2.0.2",
"typescript": "6.0.3",
"typescript-eslint": "8.60.0",
"typescript-eslint": "8.59.4",
"vite-tsconfig-paths": "6.1.1",
"vitest": "4.1.7",
"webpack-stats-plugin": "1.1.3",
+19 -67
View File
@@ -11,7 +11,6 @@ import {
} from "../../data/context";
import type { EntityRegistryDisplayEntry } from "../../data/entity/entity_registry";
import type { LocalizeFunc } from "../translations/localize";
import { ensureArray } from "../array/ensure-array";
import { transform } from "./transform";
interface ConsumeEntryConfig {
@@ -27,28 +26,6 @@ const resolveAtPath = (host: unknown, path: readonly string[]) => {
return cur;
};
/** Reuse `previous` when every entry still references the same `HassEntity`. */
export const preserveUnchangedEntityStatesRecord = <
T extends Record<string, HassEntity | undefined>,
>(
previous: T | undefined,
next: T
): T => {
if (!previous) {
return next;
}
const nextKeys = Object.keys(next);
if (Object.keys(previous).length !== nextKeys.length) {
return next;
}
for (const key of nextKeys) {
if (previous[key] !== next[key]) {
return next;
}
}
return previous;
};
const composeDecorator = <T, V>(
context: Parameters<typeof consume>[0]["context"],
watchKey: string | undefined,
@@ -86,52 +63,27 @@ export const consumeEntityState = (config: ConsumeEntryConfig) =>
);
/**
* Like {@link consumeEntityState} but for one or more entity IDs at
* `entityIdPath` (a string or string array; wrapped with {@link ensureArray}).
* Resolves to a record keyed by entity ID containing the currently-available
* entities (missing entities and non-string IDs are filtered out). Returns the
* previous record when none of the selected entities changed.
* Like {@link consumeEntityState} but for an array of entity IDs at
* `entityIdPath`. Resolves to a `HassEntity[]` containing one entry per
* currently-available entity (missing entities and non-string IDs are
* filtered out; original order is preserved).
*/
export const consumeEntityStates = (config: ConsumeEntryConfig) => {
const watchKey = config.entityIdPath[0];
const buildRecord = function (this: unknown, states: HassEntities) {
const ids = ensureArray(resolveAtPath(this, config.entityIdPath));
if (!ids || !states) return undefined;
const result: Record<string, HassEntity> = {};
for (const id of ids) {
if (typeof id !== "string") continue;
const state = states[id];
if (state !== undefined) result[id] = state;
export const consumeEntityStates = (config: ConsumeEntryConfig) =>
composeDecorator<HassEntities, HassEntity[]>(
statesContext,
config.entityIdPath[0],
function (states) {
const ids = resolveAtPath(this, config.entityIdPath);
if (!Array.isArray(ids) || !states) return undefined;
const result: HassEntity[] = [];
for (const id of ids) {
if (typeof id !== "string") continue;
const state = states[id];
if (state !== undefined) result.push(state);
}
return result;
}
return result;
};
return (proto: unknown, propertyKey: string) => {
const key = String(propertyKey);
const transformDec = transform<
HassEntities,
Record<string, HassEntity> | undefined
>({
transformer: function (this: unknown, states: HassEntities) {
const next = buildRecord.call(this, states);
if (next === undefined) {
return undefined;
}
const previous = (this as Record<string, unknown>)[
`__transform_${key}`
] as Record<string, HassEntity> | undefined;
return preserveUnchangedEntityStatesRecord(previous, next);
},
watch: watchKey ? [watchKey] : [],
});
const consumeDec = consume<any>({
context: statesContext,
subscribe: true,
});
transformDec(proto as never, propertyKey);
consumeDec(proto as never, propertyKey);
};
};
);
/**
* Consumes `entitiesContext` and narrows it to the
+37
View File
@@ -0,0 +1,37 @@
import type { PickerComboBoxItem } from "../../components/ha-picker-combo-box";
import type { RelatedResult } from "../../data/search";
export interface RelatedIdSets {
areas: Set<string>;
devices: Set<string>;
entities: Set<string>;
}
/**
* Build a set of related IDs for a given related result.
* @param related - The related result to build the sets from.
* @returns The related ID sets.
*/
export const buildRelatedIdSets = (related?: RelatedResult): RelatedIdSets => ({
areas: new Set(related?.area || []),
devices: new Set(related?.device || []),
entities: new Set(related?.entity || []),
});
/**
* Stable partition sort: related items float to the top,
* preserving relative order (e.g. Fuse score) within each group.
* @param items - The items to sort.
* @returns The sorted items.
*/
export const sortRelatedFirst = (
items: PickerComboBoxItem[]
): PickerComboBoxItem[] =>
[...items].sort((a, b) => {
const aRelated = Boolean(a.isRelated);
const bRelated = Boolean(b.isRelated);
if (aRelated === bRelated) {
return 0;
}
return aRelated ? -1 : 1;
});
+2 -34
View File
@@ -1,16 +1,11 @@
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
import {
mdiChartLine,
mdiHelpCircleOutline,
mdiPencil,
mdiShape,
} from "@mdi/js";
import { mdiChartLine, mdiHelpCircleOutline, mdiShape } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing, type PropertyValues } from "lit";
import { customElement, property, query } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array";
import { type HASSDomEvent, fireEvent } from "../../common/dom/fire_event";
import { fireEvent } from "../../common/dom/fire_event";
import { computeEntityNameList } from "../../common/entity/compute_entity_name_display";
import { computeStateName } from "../../common/entity/compute_state_name";
import { computeRTL } from "../../common/util/compute_rtl";
@@ -58,16 +53,6 @@ const SEARCH_KEYS = [
{ name: "id", weight: 2 },
];
export interface StatisticElementChangedEvent {
statisticId: string;
}
declare global {
interface HASSDomEvents {
"edit-statistics-element": StatisticElementChangedEvent;
}
}
@customElement("ha-statistic-picker")
export class HaStatisticPicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -145,8 +130,6 @@ export class HaStatisticPicker extends LitElement {
@query("ha-generic-picker") private _picker?: HaGenericPicker;
@property({ attribute: "can-edit", type: Boolean }) public canEdit?: boolean;
public willUpdate(changedProps: PropertyValues<this>) {
if (
(!this.hasUpdated && !this.statisticIds) ||
@@ -358,15 +341,6 @@ export class HaStatisticPicker extends LitElement {
${item.secondary
? html`<span slot="supporting-text">${item.secondary}</span>`
: nothing}
${this.canEdit
? html`<ha-icon-button
slot="end"
.value=${statisticId}
.label=${this.hass.localize("ui.common.edit")}
.path=${mdiPencil}
@click=${this._editItem}
></ha-icon-button>`
: nothing}
`;
}
@@ -376,12 +350,6 @@ export class HaStatisticPicker extends LitElement {
private _valueRenderer: PickerValueRenderer = this._makeValueRenderer();
private _editItem(ev: HASSDomEvent<StatisticElementChangedEvent>) {
ev.stopPropagation();
const statisticId = (ev.currentTarget as any).value;
fireEvent(this, "edit-statistics-element", { statisticId });
}
private _computeItem(statisticId: string): StatisticComboBoxItem {
const stateObj = this.hass.states[statisticId];
+1 -17
View File
@@ -1,10 +1,9 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import { type HASSDomEvent, fireEvent } from "../../common/dom/fire_event";
import { fireEvent } from "../../common/dom/fire_event";
import type { ValueChangedEvent, HomeAssistant } from "../../types";
import "./ha-statistic-picker";
import type { StatisticElementChangedEvent } from "./ha-statistic-picker";
@customElement("ha-statistics-picker")
class HaStatisticsPicker extends LitElement {
@@ -60,8 +59,6 @@ class HaStatisticsPicker extends LitElement {
})
public ignoreRestrictionsOnFirstStatistic = false;
@property({ attribute: "can-edit", type: Boolean }) public canEdit?;
protected render() {
if (!this.hass) {
return nothing;
@@ -102,9 +99,7 @@ class HaStatisticsPicker extends LitElement {
.statisticIds=${this.statisticIds}
.excludeStatistics=${this.value}
.allowCustomEntity=${this.allowCustomEntity}
.canEdit=${this.canEdit}
@value-changed=${this._statisticChanged}
@edit-statistics-element=${this._editItem}
></ha-statistic-picker>
</div>
`
@@ -127,17 +122,6 @@ class HaStatisticsPicker extends LitElement {
`;
}
private _editItem(ev: HASSDomEvent<StatisticElementChangedEvent>) {
const statisticId = ev.detail.statisticId;
const index = this._currentStatistics!.findIndex((e) => e === statisticId);
fireEvent(this, "edit-detail-element", {
subElementConfig: {
index,
type: "row",
},
});
}
private get _currentStatistics() {
return this.value || [];
}
@@ -1,13 +1,11 @@
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ensureArray } from "../../common/array/ensure-array";
import { consumeEntityStates } from "../../common/decorators/consume-context-entry";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import type { AttributeSelector } from "../../data/selector";
import type { HomeAssistant } from "../../types";
import "../entity/ha-entity-attribute-picker";
import { ensureArray } from "../../common/array/ensure-array";
@customElement("ha-selector-attribute")
export class HaSelectorAttribute extends LitElement {
@@ -29,10 +27,6 @@ export class HaSelectorAttribute extends LitElement {
filter_entity?: string | string[];
};
@state()
@consumeEntityStates({ entityIdPath: ["context", "filter_entity"] })
private _filterEntityStates?: Record<string, HassEntity>;
protected render() {
return html`
<ha-entity-attribute-picker
@@ -79,7 +73,7 @@ export class HaSelectorAttribute extends LitElement {
const entityIds = ensureArray(this.context.filter_entity);
invalid = !entityIds.some((entityId) => {
const stateObj = this._filterEntityStates?.[entityId];
const stateObj = this.hass.states[entityId];
return (
stateObj &&
this.value in stateObj.attributes &&
-1
View File
@@ -22,7 +22,6 @@ export const haTopAppBarFixedStyles = css`
padding-top: var(--safe-area-inset-top);
padding-right: var(--safe-area-inset-right);
transition:
box-shadow var(--ha-animation-duration-short) ease,
width var(--ha-animation-duration-normal) ease,
padding-left var(--ha-animation-duration-normal) ease,
padding-right var(--ha-animation-duration-normal) ease;
-1
View File
@@ -256,7 +256,6 @@ export const normalizeSubscriptionEventData = (
dtstart: eventStart,
dtend: eventEnd,
description: eventData.description ?? undefined,
location: eventData.location ?? undefined,
uid: eventData.uid ?? undefined,
recurrence_id: eventData.recurrence_id ?? undefined,
rrule: eventData.rrule ?? undefined,
+30
View File
@@ -1,5 +1,6 @@
import { createContext } from "@lit/context";
import type { HassConfig } from "home-assistant-js-websocket";
import type { HASSDomEvent } from "../../common/dom/fire_event";
import type {
HomeAssistant,
HomeAssistantApi,
@@ -10,10 +11,12 @@ import type {
HomeAssistantRegistries,
HomeAssistantUI,
} from "../../types";
import type { RelatedIdSets } from "../../common/search/related-context";
import type { ConfigEntry } from "../config_entries";
import type { EntityRegistryEntry } from "../entity/entity_registry";
import type { DomainManifestLookup } from "../integration";
import type { LabelRegistryEntry } from "../label/label_registry";
import type { ItemType } from "../search";
/**
* Entity, device, area, and floor registries
@@ -162,3 +165,30 @@ export const panelsContext = createContext<HomeAssistant["panels"]>("panels");
export const authContext = createContext<HomeAssistant["auth"]>("auth");
// #endregion deprecated-contexts
// #region related-context
export interface RelatedContextItem {
itemType: ItemType;
itemId: string;
}
/**
* Resolved related entities/devices/areas for the current page context.
* Set by `RelatedContextProvider` when a page fires `hass-related-context`.
* Cleared on navigation.
*/
export const relatedContext = createContext<RelatedIdSets | undefined>(
"related"
);
declare global {
interface HASSDomEvents {
"hass-related-context": RelatedContextItem | undefined;
}
interface HTMLElementEventMap {
"hass-related-context": HASSDomEvent<RelatedContextItem | undefined>;
}
}
// #endregion related-context
+2 -2
View File
@@ -36,11 +36,11 @@ export type ItemType =
| "script_blueprint";
export const findRelated = (
hass: HomeAssistant,
hass: Pick<HomeAssistant, "callWS">,
itemType: ItemType,
itemId: string
): Promise<RelatedResult> =>
hass.callWS({
hass.callWS<RelatedResult>({
type: "search/related",
item_type: itemType,
item_id: itemId,
+22 -33
View File
@@ -1,4 +1,5 @@
import { mdiDevices } from "@mdi/js";
import { consume } from "@lit/context";
import Fuse from "fuse.js";
import type { CSSResultGroup, PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
@@ -47,7 +48,9 @@ import {
type ActionCommandComboBoxItem,
type NavigationComboBoxItem,
} from "../../data/quick_bar";
import type { RelatedResult } from "../../data/search";
import type { RelatedIdSets } from "../../common/search/related-context";
import { sortRelatedFirst } from "../../common/search/related-context";
import { relatedContext } from "../../data/context";
import {
multiTermSortedSearch,
type FuseWeightedKey,
@@ -70,6 +73,10 @@ const SEPARATOR = "________";
export class QuickBar extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state()
@consume({ context: relatedContext, subscribe: true })
private _relatedIdSets?: RelatedIdSets;
@state() private _open = false;
@state() private _loading = true;
@@ -80,8 +87,6 @@ export class QuickBar extends LitElement {
@state() private _opened = false;
@state() private _relatedResult?: RelatedResult;
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
private get _showEntityId() {
@@ -108,8 +113,6 @@ export class QuickBar extends LitElement {
this._selectedSection = effectiveQuickBarMode(this.hass.user, params.mode);
this._showHint = params.showHint ?? false;
this._relatedResult = params.contextItem ? params.related : undefined;
this._open = true;
}
@@ -432,7 +435,7 @@ export class QuickBar extends LitElement {
this._selectedSection = section as QuickBarSection | undefined;
return this._getItemsMemoized(
this._configEntryLookup,
this._relatedResult,
this._relatedIdSets,
searchString,
this._selectedSection
);
@@ -441,12 +444,11 @@ export class QuickBar extends LitElement {
private _getItemsMemoized = memoizeOne(
(
configEntryLookup: Record<string, ConfigEntry>,
relatedResult: RelatedResult | undefined,
relatedIdSets: RelatedIdSets | undefined,
filter?: string,
section?: QuickBarSection
) => {
const items: (string | PickerComboBoxItem)[] = [];
const relatedIdSets = this._getRelatedIdSets(relatedResult);
if (!section || section === "navigate") {
let navigateItems = this._generateNavigationCommandsMemoized(
@@ -498,7 +500,7 @@ export class QuickBar extends LitElement {
let entityItems = this._getEntitiesMemoized(this.hass);
// Mark related items
if (relatedIdSets.entities.size > 0) {
if (relatedIdSets?.entities.size) {
entityItems = entityItems.map((item) => ({
...item,
isRelated: relatedIdSets.entities.has(
@@ -508,7 +510,7 @@ export class QuickBar extends LitElement {
}
if (filter) {
entityItems = this._sortRelatedFirst(
entityItems = sortRelatedFirst(
this._filterGroup(
"entity",
entityItems,
@@ -537,7 +539,7 @@ export class QuickBar extends LitElement {
);
// Mark related items
if (relatedIdSets.devices.size > 0) {
if (relatedIdSets?.devices.size) {
deviceItems = deviceItems.map((item) => {
const deviceId = item.id.split(SEPARATOR)[1];
return {
@@ -548,7 +550,7 @@ export class QuickBar extends LitElement {
}
if (filter) {
deviceItems = this._sortRelatedFirst(
deviceItems = sortRelatedFirst(
this._filterGroup("device", deviceItems, filter, deviceComboBoxKeys)
);
} else {
@@ -569,7 +571,7 @@ export class QuickBar extends LitElement {
let areaItems = this._getAreasMemoized(this.hass);
// Mark related items
if (relatedIdSets.areas.size > 0) {
if (relatedIdSets?.areas.size) {
areaItems = areaItems.map((item) => {
const areaId = item.id.split(SEPARATOR)[1];
return {
@@ -580,7 +582,7 @@ export class QuickBar extends LitElement {
}
if (filter) {
areaItems = this._sortRelatedFirst(
areaItems = sortRelatedFirst(
this._filterGroup("area", areaItems, filter, areaComboBoxKeys)
);
} else {
@@ -601,12 +603,6 @@ export class QuickBar extends LitElement {
}
);
private _getRelatedIdSets = memoizeOne((related?: RelatedResult) => ({
entities: new Set(related?.entity || []),
devices: new Set(related?.device || []),
areas: new Set(related?.area || []),
}));
private _getEntitiesMemoized = memoizeOne((hass: HomeAssistant) =>
getEntities(
hass,
@@ -705,10 +701,13 @@ export class QuickBar extends LitElement {
);
}
private _sortBySortingLabel = (entityA, entityB) =>
private _sortBySortingLabel = (
entityA: PickerComboBoxItem,
entityB: PickerComboBoxItem
) =>
caseInsensitiveStringCompare(
(entityA as PickerComboBoxItem).sorting_label!,
(entityB as PickerComboBoxItem).sorting_label!,
entityA.sorting_label!,
entityB.sorting_label!,
this.hass.locale.language
);
@@ -719,16 +718,6 @@ export class QuickBar extends LitElement {
return this._sortBySortingLabel(a, b);
});
private _sortRelatedFirst = (items: PickerComboBoxItem[]) =>
[...items].sort((a, b) => {
const aRelated = Boolean(a.isRelated);
const bRelated = Boolean(b.isRelated);
if (aRelated === bRelated) {
return 0;
}
return aRelated ? -1 : 1;
});
// #endregion data
// #region interaction
@@ -1,5 +1,4 @@
import { fireEvent } from "../../common/dom/fire_event";
import type { ItemType, RelatedResult } from "../../data/search";
import type { HomeAssistant } from "../../types";
import { closeDialog } from "../make-dialog-manager";
@@ -10,17 +9,10 @@ export type QuickBarSection =
| "navigate"
| "command";
export interface QuickBarContextItem {
itemType: ItemType;
itemId: string;
}
export interface QuickBarParams {
entityFilter?: string;
mode?: QuickBarSection;
showHint?: boolean;
contextItem?: QuickBarContextItem;
related?: RelatedResult;
}
/** Non-admin users cannot scope the bar to command, device, or area (those sections are admin-only). */
@@ -48,6 +48,8 @@ import type { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry
const cropOptions: CropOptions = {
round: false,
type: "image/jpeg",
quality: 0.75,
};
const SENSOR_DOMAINS = ["sensor"];
@@ -20,6 +20,10 @@ import type {
LocalizeKeys,
} from "../../../../common/translations/localize";
import { computeRTL } from "../../../../common/util/compute_rtl";
import {
sortRelatedFirst,
type RelatedIdSets,
} from "../../../../common/search/related-context";
import "../../../../components/chips/ha-chip-set";
import "../../../../components/chips/ha-filter-chip";
import "../../../../components/entity/state-badge";
@@ -40,7 +44,7 @@ import {
} from "../../../../data/area_floor_picker";
import { CONDITION_BUILDING_BLOCKS_GROUP } from "../../../../data/condition";
import type { ConfigEntry } from "../../../../data/config_entries";
import { labelsContext } from "../../../../data/context";
import { labelsContext, relatedContext } from "../../../../data/context";
import {
deviceComboBoxKeys,
getDevices,
@@ -129,6 +133,10 @@ export class HaAutomationAddSearch extends LitElement {
| "condition"
| "action";
@state()
@consume({ context: relatedContext, subscribe: true })
private _relatedIdSets?: RelatedIdSets;
@state() private _searchSectionTitle?: string;
@state() private _selectedSearchSection?: SearchSection;
@@ -194,7 +202,8 @@ export class HaAutomationAddSearch extends LitElement {
this.configEntryLookup,
this.items,
this.newTriggersAndConditions,
this._selectedSearchSection
this._selectedSearchSection,
this._relatedIdSets
);
let emptySearchTranslation: string | undefined;
@@ -487,7 +496,8 @@ export class HaAutomationAddSearch extends LitElement {
configEntryLookup: Record<string, ConfigEntry>,
automationItems: AddAutomationElementListItem[],
newTriggersAndConditions: boolean,
selectedSection?: SearchSection
selectedSection?: SearchSection,
relatedIdSets?: RelatedIdSets
) => {
const resultItems: (
| string
@@ -568,13 +578,26 @@ export class HaAutomationAddSearch extends LitElement {
`entity${TARGET_SEPARATOR}`
);
if (relatedIdSets?.entities.size) {
entityItems = entityItems.map((item) => ({
...item,
isRelated: relatedIdSets.entities.has(
(item as EntityComboBoxItem).stateObj?.entity_id || ""
),
})) as EntityComboBoxItem[];
}
if (searchTerm) {
entityItems = this._filterGroup(
"entity",
entityItems,
searchTerm,
entityComboBoxKeys
entityItems = sortRelatedFirst(
this._filterGroup(
"entity",
entityItems,
searchTerm,
entityComboBoxKeys
)
) as EntityComboBoxItem[];
} else if (relatedIdSets?.entities.size) {
entityItems = sortRelatedFirst(entityItems) as EntityComboBoxItem[];
}
if (!selectedSection && entityItems.length) {
@@ -601,13 +624,26 @@ export class HaAutomationAddSearch extends LitElement {
`device${TARGET_SEPARATOR}`
);
if (relatedIdSets?.devices.size) {
deviceItems = deviceItems.map((item) => ({
...item,
isRelated: relatedIdSets.devices.has(
item.id.split(TARGET_SEPARATOR)[1] || ""
),
}));
}
if (searchTerm) {
deviceItems = this._filterGroup(
"device",
deviceItems,
searchTerm,
deviceComboBoxKeys
deviceItems = sortRelatedFirst(
this._filterGroup(
"device",
deviceItems,
searchTerm,
deviceComboBoxKeys
)
);
} else if (relatedIdSets?.devices.size) {
deviceItems = sortRelatedFirst(deviceItems);
}
if (!selectedSection && deviceItems.length) {
@@ -639,13 +675,31 @@ export class HaAutomationAddSearch extends LitElement {
undefined
);
if (relatedIdSets?.areas.size) {
areasAndFloors = areasAndFloors.map((item) => ({
...item,
isRelated:
item.type === "area"
? relatedIdSets.areas.has(
item.id.split(TARGET_SEPARATOR)[1] || ""
)
: false,
})) as FloorComboBoxItem[];
}
if (searchTerm) {
areasAndFloors = this._filterGroup(
"area",
areasAndFloors,
searchTerm,
areaFloorComboBoxKeys,
false
areasAndFloors = sortRelatedFirst(
this._filterGroup(
"area",
areasAndFloors,
searchTerm,
areaFloorComboBoxKeys,
false
)
) as FloorComboBoxItem[];
} else if (relatedIdSets?.areas.size) {
areasAndFloors = sortRelatedFirst(
areasAndFloors
) as FloorComboBoxItem[];
}
@@ -1,8 +1,14 @@
import { consume } from "@lit/context";
import type { CSSResult, LitElement, TemplateResult } from "lit";
import type {
CSSResult,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { css, html } from "lit";
import { property, state } from "lit/decorators";
import { transform } from "../../../common/decorators/transform";
import { fireEvent } from "../../../common/dom/fire_event";
import { goBack, navigate } from "../../../common/navigate";
import { afterNextRender } from "../../../common/util/render-status";
import "../../../components/animation/ha-fade-in";
@@ -136,6 +142,21 @@ export const AutomationScriptEditorMixin = <TConfig extends BaseEditorConfig>(
value: PromiseLike<EntityRegistryEntry> | EntityRegistryEntry
) => void;
protected willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (changedProps.has("registryEntry")) {
const areaId = this.registryEntry?.area_id;
if (areaId) {
fireEvent(this, "hass-related-context", {
itemType: "area",
itemId: areaId,
});
} else {
fireEvent(this, "hass-related-context", undefined);
}
}
}
protected renderLoading(): TemplateResult {
return html`
<ha-fade-in .delay=${500}>
@@ -379,7 +379,7 @@ export class HaConfigDevicePage extends LitElement {
if (changedProps.has("deviceId")) {
this._findRelated();
// Broadcast device context for quick bar
fireEvent(this, "hass-quick-bar-context", {
fireEvent(this, "hass-related-context", {
itemType: "device",
itemId: this.deviceId,
});
@@ -142,26 +142,6 @@ export class DialogEnergyBatterySettings
>
${this._error ? html`<p class="error">${this._error}</p>` : nothing}
<ha-statistic-picker
.hass=${this.hass}
.helpMissingEntityUrl=${energyStatisticHelpUrl}
.includeUnitClass=${energyUnitClasses}
.value=${this._source.stat_energy_from}
.label=${this.hass.localize(
"ui.panel.config.energy.battery.dialog.energy_out_of_battery"
)}
.excludeStatistics=${[
...(this._excludeList || []),
this._source.stat_energy_to,
]}
@value-changed=${this._statisticFromChanged}
.helper=${this.hass.localize(
"ui.panel.config.energy.battery.dialog.energy_helper_out",
{ unit: this._energy_units?.join(", ") || "" }
)}
autofocus
></ha-statistic-picker>
<ha-statistic-picker
.hass=${this.hass}
.helpMissingEntityUrl=${energyStatisticHelpUrl}
@@ -179,6 +159,26 @@ export class DialogEnergyBatterySettings
"ui.panel.config.energy.battery.dialog.energy_helper_into",
{ unit: this._energy_units?.join(", ") || "" }
)}
autofocus
></ha-statistic-picker>
<ha-statistic-picker
.hass=${this.hass}
.helpMissingEntityUrl=${energyStatisticHelpUrl}
.includeUnitClass=${energyUnitClasses}
.value=${this._source.stat_energy_from}
.label=${this.hass.localize(
"ui.panel.config.energy.battery.dialog.energy_out_of_battery"
)}
.excludeStatistics=${[
...(this._excludeList || []),
this._source.stat_energy_to,
]}
@value-changed=${this._statisticFromChanged}
.helper=${this.hass.localize(
"ui.panel.config.energy.battery.dialog.energy_helper_out",
{ unit: this._energy_units?.join(", ") || "" }
)}
></ha-statistic-picker>
<ha-input
@@ -33,10 +33,8 @@ const DOMAIN_VARIANTS: Record<string, TileVariant[]> = {
TILE_VARIANT,
["cover-open-close"],
["cover-position"],
["cover-position-favorite"],
["cover-tilt"],
["cover-tilt-position"],
["cover-tilt-favorite"],
],
climate: [
TILE_VARIANT,
@@ -73,12 +71,7 @@ const DOMAIN_VARIANTS: Record<string, TileVariant[]> = {
],
vacuum: [TILE_VARIANT, ["vacuum-commands"]],
lawn_mower: [TILE_VARIANT, ["lawn-mower-commands"]],
valve: [
TILE_VARIANT,
["valve-open-close"],
["valve-position"],
["valve-position-favorite"],
],
valve: [TILE_VARIANT, ["valve-open-close"], ["valve-position"]],
alarm_control_panel: [TILE_VARIANT, ["alarm-modes"]],
counter: [TILE_VARIANT, ["counter-actions"]],
input_select: SELECT_VARIANTS,
@@ -1,13 +1,8 @@
import { consume } from "@lit/context";
import type { HassEntities, HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, state, property } from "lit/decorators";
import { preserveUnchangedEntityStatesRecord } from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/entity/state-badge";
import { statesContext } from "../../../data/context";
import type { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
import type { HomeAssistant } from "../../../types";
import type { EntitiesCardEntityConfig } from "../cards/types";
@@ -22,38 +17,21 @@ import { haStyleScrollbar } from "../../../resources/styles";
@customElement("hui-buttons-base")
export class HuiButtonsBase extends LitElement {
@property({ attribute: false })
public hass!: HomeAssistant;
@state() public hass!: HomeAssistant;
@property({ attribute: false })
public configEntities?: EntitiesCardEntityConfig[];
@state()
@consume({ context: statesContext, subscribe: true })
@transform<HassEntities, Record<string, HassEntity | undefined>>({
transformer: function (this: HuiButtonsBase, states) {
const next: Record<string, HassEntity | undefined> = {};
if (states) {
for (const entityConf of this.configEntities || []) {
next[entityConf.entity] = states[entityConf.entity];
}
}
return preserveUnchangedEntityStatesRecord(this._entityStates, next);
},
watch: ["configEntities"],
})
private _entityStates: Record<string, HassEntity | undefined> = {};
protected render(): TemplateResult {
return html`
<ha-chip-set class="ha-scrollbar">
${(this.configEntities || []).map((entityConf) => {
const stateObj = this._entityStates[entityConf.entity];
const stateObj = this.hass.states[entityConf.entity];
const name =
(entityConf.show_name && stateObj) ||
(entityConf.name && entityConf.show_name !== false)
? entityConf.name || (stateObj ? computeStateName(stateObj) : "")
? entityConf.name || computeStateName(stateObj)
: "";
return html`
@@ -15,10 +15,7 @@ import {
union,
} from "superstruct";
import { ensureArray } from "../../../../common/array/ensure-array";
import {
type HASSDomEvent,
fireEvent,
} from "../../../../common/dom/fire_event";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import { deepEqual } from "../../../../common/util/deep-equal";
import { supportedStatTypeMap } from "../../../../components/chart/statistics-chart";
@@ -35,19 +32,13 @@ import {
isExternalStatistic,
statisticsMetaHasType,
} from "../../../../data/recorder";
import type { EntityConfig } from "../../entity-rows/types";
import type { HomeAssistant } from "../../../../types";
import { DEFAULT_DAYS_TO_SHOW } from "../../cards/hui-statistics-graph-card";
import type {
GraphEntityConfig,
StatisticsGraphCardConfig,
} from "../../cards/types";
import type { StatisticsGraphCardConfig } from "../../cards/types";
import { processConfigEntities } from "../../common/process-config-entities";
import type { LovelaceCardEditor } from "../../types";
import "../hui-sub-element-editor";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { graphEntitiesConfigStruct } from "../structs/entities-struct";
import type { EditDetailElementEvent, SubElementEditorConfig } from "../types";
import { orderPropertiesGraphCard } from "./order-properties/order-properties-graph";
const statTypeStruct = union([
@@ -123,8 +114,6 @@ export class HuiStatisticsGraphCardEditor
@state() private _metaDatas?: StatisticsMetaData[];
@state() private _subElementEditorConfig?: SubElementEditorConfig;
public setConfig(config: StatisticsGraphCardConfig): void {
assert(config, cardConfigStruct);
this._config = config;
@@ -345,54 +334,11 @@ export class HuiStatisticsGraphCardEditor
}
);
private _subForm = memoizeOne((localize: LocalizeFunc) => ({
schema: [
{ name: "entity", required: true, selector: { statistic: {} } },
{
name: "name",
selector: { entity_name: {} },
context: {
entity: "entity",
},
},
{
name: "color",
selector: { ui_color: {} },
},
] as const,
computeLabel: (item: HaFormSchema) => {
switch (item.name) {
case "entity":
return localize(
"ui.panel.lovelace.editor.card.statistics-graph.picked_statistic"
);
case "name":
case "color":
return localize(`ui.panel.lovelace.editor.card.generic.${item.name}`);
default:
return undefined;
}
},
}));
protected render() {
if (!this.hass || !this._config) {
return nothing;
}
if (this._subElementEditorConfig) {
return html`
<hui-sub-element-editor
.hass=${this.hass}
.config=${this._subElementEditorConfig}
.form=${this._subForm(this.hass.localize)}
@go-back=${this._goBack}
@config-changed=${this._handleSubEntityChanged}
>
</hui-sub-element-editor>
`;
}
const schema = this._schema(
this.hass.localize,
this._configEntities,
@@ -442,29 +388,11 @@ export class HuiStatisticsGraphCardEditor
.ignoreRestrictionsOnFirstStatistic=${true}
.value=${this._configEntities}
.configValue=${"entities"}
can-edit
@value-changed=${this._entitiesChanged}
@edit-detail-element=${this._editDetailElement}
></ha-statistics-picker>
`;
}
private _goBack(): void {
this._subElementEditorConfig = undefined;
}
private _editDetailElement(ev: HASSDomEvent<EditDetailElementEvent>): void {
const index = ev.detail.subElementConfig.index!;
let elementConfig = this._config!.entities[index];
if (typeof elementConfig === "string") {
elementConfig = { entity: elementConfig };
}
this._subElementEditorConfig = {
...ev.detail.subElementConfig,
...{ elementConfig: elementConfig as EntityConfig },
};
}
private _valueChanged(ev: CustomEvent): void {
const config = this._orderProperties(ev.detail.value);
fireEvent(this, "config-changed", { config });
@@ -482,66 +410,15 @@ export class HuiStatisticsGraphCardEditor
});
let config = { ...this._config!, entities: newEntities };
// remove inappropriate stat options dependently on entities
config = await this._cleanConfig(config);
// normalize a generated yaml code
config = this._orderProperties(config);
fireEvent(this, "config-changed", {
config,
});
}
private async _handleSubEntityChanged(ev: CustomEvent): Promise<void> {
ev.stopPropagation();
// get updated entity config
const newEntityConfig = ev.detail.config as GraphEntityConfig;
// update card config with updated entity config
const index = this._subElementEditorConfig!.index!;
const newEntities = [...this._config!.entities];
newEntities[index] = newEntityConfig;
let config = this._config!;
config = { ...config, entities: newEntities };
// remove inappropriate stat options dependently on entities
config = await this._cleanConfig(config);
// normalize a generated yaml code
config = this._orderProperties(config);
this._config = config;
// update sub-element editor config
this._subElementEditorConfig = {
...this._subElementEditorConfig!,
elementConfig: {
...(this._config!.entities[index] as GraphEntityConfig),
},
};
fireEvent(this, "config-changed", { config });
}
// remove inappropriate stat options dependently on entities
private async _cleanConfig(
config: StatisticsGraphCardConfig
): Promise<StatisticsGraphCardConfig> {
const entityIds = config.entities.map((entityConf) => {
if (typeof entityConf === "string") {
return entityConf;
}
return entityConf.entity ?? undefined;
});
if (
entityIds.some((statistic_id) => isExternalStatistic(statistic_id)) &&
newEntityIds?.some((statistic_id) => isExternalStatistic(statistic_id)) &&
config.period === "5minute"
) {
delete config.period;
}
const metadata =
config.stat_types || config.unit
? await getStatisticMetadata(this.hass!, entityIds)
? await getStatisticMetadata(this.hass!, newEntityIds)
: undefined;
if (config.stat_types && config.entities.length) {
config.stat_types = ensureArray(config.stat_types).filter((stat_type) =>
@@ -561,8 +438,10 @@ export class HuiStatisticsGraphCardEditor
) {
delete config.unit;
}
return config;
config = this._orderProperties(config);
fireEvent(this, "config-changed", {
config,
});
}
// normalize a generated yaml code by placing lines in a consistent order
+6
View File
@@ -39,6 +39,7 @@ import { subscribeLabelRegistry } from "../data/label/label_registry";
import type { Constructor, HomeAssistant } from "../types";
import type { HassBaseEl } from "./hass-base-mixin";
import { LazyContextProvider } from "./lazy-context-provider";
import { RelatedContextProvider } from "./related-context-provider";
export const contextMixin = <T extends Constructor<HassBaseEl>>(
superClass: T
@@ -201,6 +202,8 @@ export const contextMixin = <T extends Constructor<HassBaseEl>>(
}),
};
private __relatedContextProvider = new RelatedContextProvider(this);
protected hassConnected() {
super.hassConnected();
for (const [key, value] of Object.entries(this.hass!)) {
@@ -214,6 +217,8 @@ export const contextMixin = <T extends Constructor<HassBaseEl>>(
for (const provider of Object.values(this.__lazyContextProviders)) {
provider.setConnection(connection);
}
this.__relatedContextProvider.connect();
}
protected _updateHass(obj: Partial<HomeAssistant>) {
@@ -244,5 +249,6 @@ export const contextMixin = <T extends Constructor<HassBaseEl>>(
for (const provider of Object.values(this.__lazyContextProviders)) {
provider.unsubscribe();
}
this.__relatedContextProvider.disconnect();
}
};
+2 -82
View File
@@ -5,12 +5,7 @@ import { canOverrideAlphanumericInput } from "../common/dom/can-override-input";
import { mainWindow } from "../common/dom/get_main_window";
import { ShortcutManager } from "../common/keyboard/shortcuts";
import { extractSearchParamsObject } from "../common/url/search-params";
import { findRelated, type RelatedResult } from "../data/search";
import type {
QuickBarContextItem,
QuickBarParams,
QuickBarSection,
} from "../dialogs/quick-bar/show-dialog-quick-bar";
import type { QuickBarSection } from "../dialogs/quick-bar/show-dialog-quick-bar";
import {
closeQuickBar,
showQuickBar,
@@ -24,10 +19,8 @@ import type { HassElement } from "./hass-element";
declare global {
interface HASSDomEvents {
"hass-quick-bar": QuickBarParams;
"hass-quick-bar-trigger": KeyboardEvent;
"hass-enable-shortcuts": HomeAssistant["enableShortcuts"];
"hass-quick-bar-context": QuickBarContextItem | undefined;
}
}
@@ -35,52 +28,6 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
class extends superClass {
private _quickBarOpen = false;
private _quickBarContext?: QuickBarContextItem;
private _quickBarContextRelated?: RelatedResult;
private _fetchRelatedMemoized = memoizeOne(
(itemType: QuickBarContextItem["itemType"], itemId: string) =>
findRelated(this.hass!, itemType, itemId)
);
private _clearQuickBarContext = () => {
this._quickBarContext = undefined;
this._quickBarContextRelated = undefined;
};
private _contextMatches = (context?: QuickBarContextItem) =>
context?.itemType === this._quickBarContext?.itemType &&
context?.itemId === this._quickBarContext?.itemId;
private _prefetchQuickBarContext = async (
context?: QuickBarContextItem
) => {
this._quickBarContextRelated = undefined;
if (!context) {
return;
}
try {
const related = await this._fetchRelatedMemoized(
context.itemType,
context.itemId
);
if (this._contextMatches(context)) {
this._quickBarContextRelated = related;
}
} catch (err) {
// eslint-disable-next-line no-console
console.warn("Error prefetching quick bar related items", err);
if (this._contextMatches(context)) {
this._quickBarContextRelated = undefined;
}
}
};
protected firstUpdated(changedProps: PropertyValues<this>) {
super.firstUpdated(changedProps);
@@ -111,20 +58,6 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
}
});
this.addEventListener("hass-quick-bar-context", (ev) => {
this._quickBarContext =
ev.detail && "itemType" in ev.detail && "itemId" in ev.detail
? ev.detail
: undefined;
this._prefetchQuickBarContext(this._quickBarContext);
});
mainWindow.addEventListener(
"location-changed",
this._clearQuickBarContext
);
mainWindow.addEventListener("popstate", this._clearQuickBarContext);
mainWindow.addEventListener("hass-quick-bar-trigger", (ev) => {
switch (ev.detail.key) {
case "e":
@@ -161,15 +94,6 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
}
}
public disconnectedCallback() {
super.disconnectedCallback();
mainWindow.removeEventListener(
"location-changed",
this._clearQuickBarContext
);
mainWindow.removeEventListener("popstate", this._clearQuickBarContext);
}
private _registerShortcut() {
const shortcutManager = new ShortcutManager();
shortcutManager.add({
@@ -238,11 +162,7 @@ export default <T extends Constructor<HassElement>>(superClass: T) =>
}
e.preventDefault();
showQuickBar(this, {
mode,
contextItem: this._quickBarContext,
related: this._quickBarContextRelated,
});
showQuickBar(this, { mode });
}
private _toggleQuickBar(e: KeyboardEvent, mode?: QuickBarSection) {
+116
View File
@@ -0,0 +1,116 @@
import { ContextProvider } from "@lit/context";
import memoizeOne from "memoize-one";
import type { HASSDomEvent } from "../common/dom/fire_event";
import { mainWindow } from "../common/dom/get_main_window";
import { buildRelatedIdSets } from "../common/search/related-context";
import { relatedContext, type RelatedContextItem } from "../data/context";
import { findRelated } from "../data/search";
import type { HassBaseEl } from "./hass-base-mixin";
/**
* Standalone context provider for `relatedContext`.
*
* Listens for `hass-related-context` events fired by child components,
* resolves the related entities/devices/areas via `findRelated`, and
* provides the resolved `RelatedIdSets` to context consumers.
*
* Clears on actual page navigation (pathname change), not on dialog
* history manipulation (`popstate` from dialog close).
*
* Instantiated from `context-mixin.ts` alongside other providers.
*/
export class RelatedContextProvider {
private _relatedContext?: RelatedContextItem;
private _provider: ContextProvider<typeof relatedContext>;
private _contextPathname?: string;
private _fetchRelatedMemoized = memoizeOne(
(itemType: RelatedContextItem["itemType"], itemId: string) =>
findRelated(this._host.hass!, itemType, itemId)
);
constructor(private _host: HassBaseEl) {
this._provider = new ContextProvider(_host, { context: relatedContext });
}
/**
* Set up event listeners. Call from `firstUpdated` or `hassConnected`.
*/
public connect(): void {
this._host.addEventListener("hass-related-context", this._onRelatedContext);
mainWindow.addEventListener(
"location-changed",
this._maybeClearRelatedContext
);
mainWindow.addEventListener("popstate", this._maybeClearRelatedContext);
}
/**
* Clean up event listeners. Call from `disconnectedCallback`.
*/
public disconnect(): void {
this._host.removeEventListener(
"hass-related-context",
this._onRelatedContext
);
mainWindow.removeEventListener(
"location-changed",
this._maybeClearRelatedContext
);
mainWindow.removeEventListener("popstate", this._maybeClearRelatedContext);
}
private _onRelatedContext = (
ev: HASSDomEvent<RelatedContextItem | undefined>
): void => {
this._relatedContext = ev.detail;
this._contextPathname = mainWindow.location.pathname;
this._resolveRelatedContext(this._relatedContext);
};
/**
* Only clear context when the actual page pathname changes.
* Dialog open/close manipulates history state without changing the URL,
* so we ignore those popstate/location-changed events.
*/
private _maybeClearRelatedContext = (): void => {
if (
this._contextPathname &&
mainWindow.location.pathname === this._contextPathname
) {
return;
}
this._relatedContext = undefined;
this._contextPathname = undefined;
this._provider.setValue(undefined);
};
private _contextMatches = (context?: RelatedContextItem): boolean =>
context?.itemType === this._relatedContext?.itemType &&
context?.itemId === this._relatedContext?.itemId;
private _resolveRelatedContext = async (
context?: RelatedContextItem
): Promise<void> => {
if (!context || !this._host.hass) {
this._provider.setValue(undefined);
return;
}
try {
const related = await this._fetchRelatedMemoized(
context.itemType,
context.itemId
);
if (this._contextMatches(context)) {
this._provider.setValue(buildRelatedIdSets(related));
}
} catch (_err) {
if (this._contextMatches(context)) {
this._provider.setValue(undefined);
}
}
};
}
+73 -73
View File
@@ -4599,105 +4599,105 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:8.60.0":
version: 8.60.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.60.0"
"@typescript-eslint/eslint-plugin@npm:8.59.4":
version: 8.59.4
resolution: "@typescript-eslint/eslint-plugin@npm:8.59.4"
dependencies:
"@eslint-community/regexpp": "npm:^4.12.2"
"@typescript-eslint/scope-manager": "npm:8.60.0"
"@typescript-eslint/type-utils": "npm:8.60.0"
"@typescript-eslint/utils": "npm:8.60.0"
"@typescript-eslint/visitor-keys": "npm:8.60.0"
"@typescript-eslint/scope-manager": "npm:8.59.4"
"@typescript-eslint/type-utils": "npm:8.59.4"
"@typescript-eslint/utils": "npm:8.59.4"
"@typescript-eslint/visitor-keys": "npm:8.59.4"
ignore: "npm:^7.0.5"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.5.0"
peerDependencies:
"@typescript-eslint/parser": ^8.60.0
"@typescript-eslint/parser": ^8.59.4
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/aec6f08be04ad0014c80e5cf3bd8ec83d59c44244c9ca357c4cf182b6f0debdd690e64daa88215e937183e97c4bdee6749dbf4162191c5851ae9c648439c8a96
checksum: 10/75348fdfc5a69bf9837c7ee3a5997bd7e9176eee00682735e46e199e5b67e4748440dd23c65677860a6caaa1c05b67cfafa57a8623da65a17c2832e322f6b7c4
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:8.60.0":
version: 8.60.0
resolution: "@typescript-eslint/parser@npm:8.60.0"
"@typescript-eslint/parser@npm:8.59.4":
version: 8.59.4
resolution: "@typescript-eslint/parser@npm:8.59.4"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.60.0"
"@typescript-eslint/types": "npm:8.60.0"
"@typescript-eslint/typescript-estree": "npm:8.60.0"
"@typescript-eslint/visitor-keys": "npm:8.60.0"
"@typescript-eslint/scope-manager": "npm:8.59.4"
"@typescript-eslint/types": "npm:8.59.4"
"@typescript-eslint/typescript-estree": "npm:8.59.4"
"@typescript-eslint/visitor-keys": "npm:8.59.4"
debug: "npm:^4.4.3"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/f55fa3547e3d0a0ec88bcb886b9bf6cef9b425c016dfa47e2ad7fbcbaa854640ba3f501cc0115824b58f33be4bf8bdf544505847988688906d11c154b600c54d
checksum: 10/6d07a206cac1aa168f9973ac41c3c3a05d7d391a5fa720b4adab5f7c278b62806269ed4e48fcb5706d87213d64bb25f040c9df4824162f8eabb180bee9a2fc23
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.60.0":
version: 8.60.0
resolution: "@typescript-eslint/project-service@npm:8.60.0"
"@typescript-eslint/project-service@npm:8.59.4":
version: 8.59.4
resolution: "@typescript-eslint/project-service@npm:8.59.4"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.60.0"
"@typescript-eslint/types": "npm:^8.60.0"
"@typescript-eslint/tsconfig-utils": "npm:^8.59.4"
"@typescript-eslint/types": "npm:^8.59.4"
debug: "npm:^4.4.3"
peerDependencies:
typescript: ">=4.8.4 <6.1.0"
checksum: 10/21e233d1292775753861aad32b30448f9fb5508f53d5a12c8ce7e75613df236757377fa877c738cc858ac863f2f8259a1f63bfb15a32ee9c5476fe9b2d12fbb0
checksum: 10/abd5ac32f8792c9bdabdb510d7ba9f708c7760994f3d4a2c741ecf5baecccf7a111e9706eb46a28a3678998671cdc9912f4e8f2423e091bbaea2b6836e07c14d
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.60.0":
version: 8.60.0
resolution: "@typescript-eslint/scope-manager@npm:8.60.0"
"@typescript-eslint/scope-manager@npm:8.59.4":
version: 8.59.4
resolution: "@typescript-eslint/scope-manager@npm:8.59.4"
dependencies:
"@typescript-eslint/types": "npm:8.60.0"
"@typescript-eslint/visitor-keys": "npm:8.60.0"
checksum: 10/c08274fdb38be51d2d655ee32bed271cfedf5f5775709da98b3d6cf5f7eb419e98228fb087b48f4a591f4dd71ebcb27a8bd716fa831442c7cad708288625e454
"@typescript-eslint/types": "npm:8.59.4"
"@typescript-eslint/visitor-keys": "npm:8.59.4"
checksum: 10/945a3498a61e27109ef78fa41fbf6ebca76dcc75c4595019bbc131892658b8204e9872239d4a1347fec2b3ca3c3e26460edabf7941cf727e9a35105d6f383c5d
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.60.0, @typescript-eslint/tsconfig-utils@npm:^8.60.0":
version: 8.60.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.60.0"
"@typescript-eslint/tsconfig-utils@npm:8.59.4, @typescript-eslint/tsconfig-utils@npm:^8.59.4":
version: 8.59.4
resolution: "@typescript-eslint/tsconfig-utils@npm:8.59.4"
peerDependencies:
typescript: ">=4.8.4 <6.1.0"
checksum: 10/d82cac7dec0366c0e680d002b4d20bc2564a198a2d9a80099f4fa7ee2b2f394dd2d47df03f1c4b276c4de6c7b8684a50e7bad0ddd5b33907188e90cc203a9593
checksum: 10/c74a356e67f17aa2b61bffbe145548a5b37e6db34077922fb74c5f07393cd3a1ef28742f9a3e3acc53ac0e95c8af6e959bdf44988e036d634492d79a9dd219b1
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.60.0":
version: 8.60.0
resolution: "@typescript-eslint/type-utils@npm:8.60.0"
"@typescript-eslint/type-utils@npm:8.59.4":
version: 8.59.4
resolution: "@typescript-eslint/type-utils@npm:8.59.4"
dependencies:
"@typescript-eslint/types": "npm:8.60.0"
"@typescript-eslint/typescript-estree": "npm:8.60.0"
"@typescript-eslint/utils": "npm:8.60.0"
"@typescript-eslint/types": "npm:8.59.4"
"@typescript-eslint/typescript-estree": "npm:8.59.4"
"@typescript-eslint/utils": "npm:8.59.4"
debug: "npm:^4.4.3"
ts-api-utils: "npm:^2.5.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/4b29dcc1ee7a006b2df8a50700b43701bedd4f8380e94311a8988102d98fdd89244c233a8063a800cbdee86278bdc98874bfa6a8a3c71f1b278be1be6698961b
checksum: 10/6c513a9ab5e9da3fafe165b64da73c984696e3f583ae4a5c7d188f16f9425ec488ed4aff39d8b419a742e92078fb1cb4384dce9e1efa7bb193e3aa0cdcff84c8
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.60.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.60.0":
version: 8.60.0
resolution: "@typescript-eslint/types@npm:8.60.0"
checksum: 10/8c6967503b3a370af10fea7bfec9adc7a4152e0e8aaa72ee790f105f08721683f6e8829acf610de82bfcdeb56bdf07f6795ccec394edbdac222fd3a1d76fe9cd
"@typescript-eslint/types@npm:8.59.4, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.59.4":
version: 8.59.4
resolution: "@typescript-eslint/types@npm:8.59.4"
checksum: 10/43e7ab668f1649dee76829a5b1eb0807b7576b39aee5b23fe10936d99536cba350ec1e377c2ea2242971435d460e28f3d3d4307357ac9f8184403573ece85ca6
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.60.0":
version: 8.60.0
resolution: "@typescript-eslint/typescript-estree@npm:8.60.0"
"@typescript-eslint/typescript-estree@npm:8.59.4":
version: 8.59.4
resolution: "@typescript-eslint/typescript-estree@npm:8.59.4"
dependencies:
"@typescript-eslint/project-service": "npm:8.60.0"
"@typescript-eslint/tsconfig-utils": "npm:8.60.0"
"@typescript-eslint/types": "npm:8.60.0"
"@typescript-eslint/visitor-keys": "npm:8.60.0"
"@typescript-eslint/project-service": "npm:8.59.4"
"@typescript-eslint/tsconfig-utils": "npm:8.59.4"
"@typescript-eslint/types": "npm:8.59.4"
"@typescript-eslint/visitor-keys": "npm:8.59.4"
debug: "npm:^4.4.3"
minimatch: "npm:^10.2.2"
semver: "npm:^7.7.3"
@@ -4705,32 +4705,32 @@ __metadata:
ts-api-utils: "npm:^2.5.0"
peerDependencies:
typescript: ">=4.8.4 <6.1.0"
checksum: 10/ad02384fd48152a7d9bb5db1aa5d6cbda1cfa9e549a2d529d801ec1401d1d7d011c5e071f5b4d99c5ed656c95e5e97c46a783b45dcc7c016df7fee37ab5bdc0a
checksum: 10/6ca75d11bd5e1f44ddc0cd8ef4f4833257bcb1d48b82485b55008cd6761f4dba0671754098ce5b2910432ba05f5f9247c34942f3e8de2857cb5ab0d25b866876
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.60.0":
version: 8.60.0
resolution: "@typescript-eslint/utils@npm:8.60.0"
"@typescript-eslint/utils@npm:8.59.4":
version: 8.59.4
resolution: "@typescript-eslint/utils@npm:8.59.4"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.9.1"
"@typescript-eslint/scope-manager": "npm:8.60.0"
"@typescript-eslint/types": "npm:8.60.0"
"@typescript-eslint/typescript-estree": "npm:8.60.0"
"@typescript-eslint/scope-manager": "npm:8.59.4"
"@typescript-eslint/types": "npm:8.59.4"
"@typescript-eslint/typescript-estree": "npm:8.59.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/9fc8bc7a62deacd3823d957de8e8ca2012ffa90715734cd89d0e3a62c2c9e2775d3ba9da80e419339893a44af8674d690488cb195c981e8de9fd9dfa4948956d
checksum: 10/90d779f1af27e34df7e44f93135c08908e98c4b830329be1220d5edd0e024272f6d5a021506dae7fe6446a8bf05ccd0a5e7fb3ed36399dcf4f52959bb302c62d
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.60.0":
version: 8.60.0
resolution: "@typescript-eslint/visitor-keys@npm:8.60.0"
"@typescript-eslint/visitor-keys@npm:8.59.4":
version: 8.59.4
resolution: "@typescript-eslint/visitor-keys@npm:8.59.4"
dependencies:
"@typescript-eslint/types": "npm:8.60.0"
"@typescript-eslint/types": "npm:8.59.4"
eslint-visitor-keys: "npm:^5.0.0"
checksum: 10/4854d08416e2c97837cc1ecf8dacb50b3337ebb34bd6d703ad40b6585fdf78243074e56994ddc90650586146cebd6ad7390b6fa3ddda4e3532be4b872dd8f541
checksum: 10/b620ba3a702817cc1cff6ac654ef5538be0b5076a478546c9bf42ece25bc3b08a33a4d003b9b42d10bd1041ea246f108863aceca0683b4389cf18d55dcdbee10
languageName: node
linkType: hard
@@ -8658,7 +8658,7 @@ __metadata:
tinykeys: "npm:4.0.0"
ts-lit-plugin: "npm:2.0.2"
typescript: "npm:6.0.3"
typescript-eslint: "npm:8.60.0"
typescript-eslint: "npm:8.59.4"
vite-tsconfig-paths: "npm:6.1.1"
vitest: "npm:4.1.7"
webpack-stats-plugin: "npm:1.1.3"
@@ -13641,18 +13641,18 @@ __metadata:
languageName: node
linkType: hard
"typescript-eslint@npm:8.60.0":
version: 8.60.0
resolution: "typescript-eslint@npm:8.60.0"
"typescript-eslint@npm:8.59.4":
version: 8.59.4
resolution: "typescript-eslint@npm:8.59.4"
dependencies:
"@typescript-eslint/eslint-plugin": "npm:8.60.0"
"@typescript-eslint/parser": "npm:8.60.0"
"@typescript-eslint/typescript-estree": "npm:8.60.0"
"@typescript-eslint/utils": "npm:8.60.0"
"@typescript-eslint/eslint-plugin": "npm:8.59.4"
"@typescript-eslint/parser": "npm:8.59.4"
"@typescript-eslint/typescript-estree": "npm:8.59.4"
"@typescript-eslint/utils": "npm:8.59.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: ">=4.8.4 <6.1.0"
checksum: 10/625e49e6d06e32adcfe903087d1fb2adc3be925adafe1f4e57f690bb196b35e2aac01760a3d5e17a53ea2feb6fef3a13da4b8faa214f628ec56e64f99f20e4ad
checksum: 10/f065a4ae6abfad6af0a09de7052315cd8fa97a2e8ff0985aa8ad6ba0e059a662ef3b22751fbf58302de634b712a7d019f10f488d8e43b0510842a4f6460e57f4
languageName: node
linkType: hard