Compare commits

...
Author SHA1 Message Date
Paul Bottein 6d7584828a Refresh the target picker when its filters change 2026-10-22 15:18:02 +02:00
2 changed files with 89 additions and 41 deletions

No files matched your search

@@ -18,6 +18,14 @@ import type { HassServiceTargetWithExclude } from "../../data/target";
import type { HomeAssistant } from "../../types";
import "../ha-target-picker";
type TargetSelectorFilters = NonNullable<TargetSelector["target"]>;
// The pickers hide devices, areas and labels without entities only when they
// get a filter function, so one is always passed.
const ALL_ENTITIES = () => true;
const ALL_DEVICES = () => true;
@customElement("ha-selector-target")
export class HaTargetSelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -80,8 +88,11 @@ export class HaTargetSelector extends LitElement {
.hass=${this.hass}
.value=${this.value}
.helper=${this.helper}
.deviceFilter=${this._filterDevices}
.entityFilter=${this._filterEntities}
.deviceFilter=${this._deviceFilter(this.selector.target?.device)}
.entityFilter=${this._entityFilter(
this.selector.target?.entity,
this._entitySources
)}
.disabled=${this.disabled}
.createDomains=${this._createDomains}
.primaryEntitiesOnly=${this.selector.target?.primary_entities_only}
@@ -89,35 +100,44 @@ export class HaTargetSelector extends LitElement {
></ha-target-picker>`;
}
private _filterEntities = (entity: HassEntity): boolean => {
if (!this.selector.target?.entity) {
return true;
// The picker caches its lists per filter function, so a new function is
// only made when the selector filters change.
private _entityFilter = memoizeOne(
(
filters: TargetSelectorFilters["entity"],
entitySources: EntitySources | undefined
) => {
if (!filters) {
return ALL_ENTITIES;
}
return (entity: HassEntity): boolean =>
ensureArray(filters).some((filter) =>
filterSelectorEntities(
filter,
entity,
entitySources,
this.hass.entities,
this.hass.devices
)
);
}
);
return ensureArray(this.selector.target.entity).some((filter) =>
filterSelectorEntities(
filter,
entity,
this._entitySources,
this.hass.entities,
this.hass.devices
)
);
};
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
if (!this.selector.target?.device) {
return true;
private _deviceFilter = memoizeOne(
(filters: TargetSelectorFilters["device"]) => {
if (!filters) {
return ALL_DEVICES;
}
return (device: DeviceRegistryEntry): boolean => {
const deviceIntegrations = this._hasIntegration(this.selector)
? this._deviceIntegrationLookup(this.hass.entities)
: undefined;
return ensureArray(filters).some((filter) =>
filterSelectorDevices(filter, device, deviceIntegrations)
);
};
}
const deviceIntegrations = this._hasIntegration(this.selector)
? this._deviceIntegrationLookup(this.hass.entities)
: undefined;
return ensureArray(this.selector.target.device).some((filter) =>
filterSelectorDevices(filter, device, deviceIntegrations)
);
};
);
static styles = css`
ha-target-picker {
@@ -164,6 +164,10 @@ export class HaTargetPickerItemRow extends LitElement {
@state() private _entries?: ExtractFromTargetResult;
private _fetchedEntries?: ExtractFromTargetResult;
private _lastFetch = 0;
@state()
@consume({ context: labelsContext, subscribe: true })
_labelRegistry?: LabelRegistryEntry[];
@@ -171,8 +175,21 @@ export class HaTargetPickerItemRow extends LitElement {
private _loadedConfigEntryId?: string;
protected willUpdate(changedProps: PropertyValues<this>) {
if (!this.subEntry && !this.exclusion && changedProps.has("itemId")) {
const resolvesEntries = !this.subEntry && !this.exclusion;
if (
resolvesEntries &&
(changedProps.has("itemId") || changedProps.has("primaryEntitiesOnly"))
) {
this._updateItemData();
} else if (
resolvesEntries &&
this._fetchedEntries &&
(changedProps.has("entityFilter") ||
changedProps.has("deviceFilter") ||
changedProps.has("includeDomains") ||
changedProps.has("includeDeviceClasses"))
) {
this._entries = this._filterEntries(this._fetchedEntries);
}
if (
changedProps.has("itemId") ||
@@ -678,8 +695,25 @@ export class HaTargetPickerItemRow extends LitElement {
</ha-list-item-base>`;
}
private _filterEntries(entries: ExtractFromTargetResult) {
return filterTargetEntries(
this.hass,
{ type: this.type, id: this.itemId },
entries,
{
deviceFilter: this.deviceFilter,
entityFilter: this.entityFilter,
includeDomains: this.includeDomains,
includeDeviceClasses: this.includeDeviceClasses,
primaryEntitiesOnly: this.primaryEntitiesOnly,
}
);
}
private async _updateItemData() {
const requestId = ++this._lastFetch;
if (this.type === "entity") {
this._fetchedEntries = undefined;
this._entries = undefined;
return;
}
@@ -692,18 +726,12 @@ export class HaTargetPickerItemRow extends LitElement {
false,
this.primaryEntitiesOnly
);
this._entries = filterTargetEntries(
this.hass,
{ type: this.type, id: this.itemId },
entries,
{
deviceFilter: this.deviceFilter,
entityFilter: this.entityFilter,
includeDomains: this.includeDomains,
includeDeviceClasses: this.includeDeviceClasses,
primaryEntitiesOnly: this.primaryEntitiesOnly,
}
);
// A newer request was sent while this one was pending.
if (requestId !== this._lastFetch) {
return;
}
this._fetchedEntries = entries;
this._entries = this._filterEntries(entries);
} catch (e) {
// eslint-disable-next-line no-console
console.error("Failed to extract target", e);