1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-02 00:27:49 +01:00

Hide behavior selector for single target in triggers and conditions (labs) (#30145)

This commit is contained in:
Aidan Timson
2026-03-18 15:26:34 +00:00
committed by GitHub
parent a794a80228
commit 8a5bcd67ab
3 changed files with 219 additions and 89 deletions

View File

@@ -47,13 +47,34 @@ export interface ExtractFromTargetResultReferenced {
export const extractFromTarget = async (
hass: HomeAssistant,
target: HassServiceTarget
target: HassServiceTarget,
expandGroup = false
) =>
hass.callWS<ExtractFromTargetResult>({
type: "extract_from_target",
target,
expand_group: expandGroup,
});
export const getResolvedTargetEntityCount = async (
hass: HomeAssistant,
target?: HassServiceTarget
): Promise<number | undefined> => {
if (!target) {
return undefined;
}
try {
return (await extractFromTarget(hass, target, true)).referenced_entities
.length;
} catch (err) {
// eslint-disable-next-line no-console
console.error("Error resolving target entity count", err);
}
return undefined;
};
export const getTriggersForTarget = async (
callWS: HomeAssistant["callWS"],
target: HassServiceTarget,

View File

@@ -16,6 +16,7 @@ import {
import type { IntegrationManifest } from "../../../../../data/integration";
import { fetchIntegrationManifest } from "../../../../../data/integration";
import type { TargetSelector } from "../../../../../data/selector";
import { getResolvedTargetEntityCount } from "../../../../../data/target";
import type { HomeAssistant } from "../../../../../types";
import { documentationUrl } from "../../../../../util/documentation-url";
@@ -38,6 +39,8 @@ export class HaPlatformCondition extends LitElement {
@state() private _manifest?: IntegrationManifest;
@state() private _resolvedTargetEntityCount?: number;
public static get defaultConfig(): PlatformCondition {
return { condition: "" };
}
@@ -107,6 +110,10 @@ export class HaPlatformCondition extends LitElement {
});
}
}
if (oldValue?.target !== this.condition?.target) {
this._updateResolvedTargetEntityCount(this.condition?.target);
}
}
protected render() {
@@ -214,51 +221,57 @@ export class HaPlatformCondition extends LitElement {
const showOptional = showOptionalToggle(dataField);
return dataField.selector
? html`<ha-settings-row narrow>
${!showOptional
? hasOptional
? html`<div slot="prefix" class="checkbox-spacer"></div>`
: nothing
: html`<ha-checkbox
.key=${fieldName}
.checked=${this._checkedKeys.has(fieldName) ||
(this.condition?.options &&
this.condition.options[fieldName] !== undefined)}
.disabled=${this.disabled}
@change=${this._checkboxChanged}
slot="prefix"
></ha-checkbox>`}
<span slot="heading"
>${this.hass.localize(
`component.${domain}.conditions.${conditionName}.fields.${fieldName}.name`
) || conditionName}</span
>
<span slot="description"
>${this.hass.localize(
`component.${domain}.conditions.${conditionName}.fields.${fieldName}.description`
)}</span
>
<ha-selector
.disabled=${this.disabled ||
(showOptional &&
!this._checkedKeys.has(fieldName) &&
(!this.condition?.options ||
this.condition.options[fieldName] === undefined))}
.hass=${this.hass}
.selector=${selector}
.context=${this._generateContext(dataField)}
if (!dataField.selector) {
return nothing;
}
if (fieldName === "behavior" && this._resolvedTargetEntityCount === 1) {
return nothing;
}
return html`<ha-settings-row narrow>
${!showOptional
? hasOptional
? html`<div slot="prefix" class="checkbox-spacer"></div>`
: nothing
: html`<ha-checkbox
.key=${fieldName}
@value-changed=${this._dataChanged}
.value=${this.condition?.options
? this.condition.options[fieldName]
: undefined}
.placeholder=${dataField.default}
.localizeValue=${this._localizeValueCallback}
.required=${dataField.required}
></ha-selector>
</ha-settings-row>`
: nothing;
.checked=${this._checkedKeys.has(fieldName) ||
(this.condition?.options &&
this.condition.options[fieldName] !== undefined)}
.disabled=${this.disabled}
@change=${this._checkboxChanged}
slot="prefix"
></ha-checkbox>`}
<span slot="heading"
>${this.hass.localize(
`component.${domain}.conditions.${conditionName}.fields.${fieldName}.name`
) || conditionName}</span
>
<span slot="description"
>${this.hass.localize(
`component.${domain}.conditions.${conditionName}.fields.${fieldName}.description`
)}</span
>
<ha-selector
.disabled=${this.disabled ||
(showOptional &&
!this._checkedKeys.has(fieldName) &&
(!this.condition?.options ||
this.condition.options[fieldName] === undefined))}
.hass=${this.hass}
.selector=${selector}
.context=${this._generateContext(dataField)}
.key=${fieldName}
@value-changed=${this._dataChanged}
.value=${this.condition?.options
? this.condition.options[fieldName]
: undefined}
.placeholder=${dataField.default}
.localizeValue=${this._localizeValueCallback}
.required=${dataField.required}
></ha-selector>
</ha-settings-row>`;
};
private _generateContext(
@@ -395,6 +408,47 @@ export class HaPlatformCondition extends LitElement {
}
}
private _resolveTargetEntityCount = memoizeOne(
async (target: PlatformCondition["target"]) =>
getResolvedTargetEntityCount(this.hass, target)
);
private async _updateResolvedTargetEntityCount(
target: PlatformCondition["target"]
) {
this._resolvedTargetEntityCount =
await this._resolveTargetEntityCount(target);
if (
this._resolvedTargetEntityCount === 1 &&
this.condition.options?.behavior !== undefined
) {
const options = { ...this.condition.options };
delete options.behavior;
fireEvent(this, "value-changed", {
value: {
...this.condition,
options,
},
});
} else if (
this._resolvedTargetEntityCount !== undefined &&
this._resolvedTargetEntityCount > 1 &&
this.condition.options?.behavior === undefined
) {
const behaviorDefault = this.description?.fields?.behavior?.default;
if (behaviorDefault !== undefined) {
fireEvent(this, "value-changed", {
value: {
...this.condition,
options: { ...this.condition.options, behavior: behaviorDefault },
},
});
}
}
}
static styles = css`
:host {
display: block;

View File

@@ -11,6 +11,7 @@ import type { PlatformTrigger } from "../../../../../data/automation";
import type { IntegrationManifest } from "../../../../../data/integration";
import { fetchIntegrationManifest } from "../../../../../data/integration";
import type { TargetSelector } from "../../../../../data/selector";
import { getResolvedTargetEntityCount } from "../../../../../data/target";
import {
getTriggerDomain,
getTriggerObjectId,
@@ -48,6 +49,8 @@ export class HaPlatformTrigger extends LitElement {
@state() private _manifest?: IntegrationManifest;
@state() private _resolvedTargetEntityCount?: number;
public static get defaultConfig(): PlatformTrigger {
return { trigger: "" };
}
@@ -143,6 +146,10 @@ export class HaPlatformTrigger extends LitElement {
});
}
}
if (oldValue?.target !== this.trigger?.target) {
this._updateResolvedTargetEntityCount(this.trigger?.target);
}
}
protected render() {
@@ -250,51 +257,58 @@ export class HaPlatformTrigger extends LitElement {
const showOptional = showOptionalToggle(dataField);
return dataField.selector
? html`<ha-settings-row narrow>
${!showOptional
? hasOptional
? html`<div slot="prefix" class="checkbox-spacer"></div>`
: nothing
: html`<ha-checkbox
.key=${fieldName}
.checked=${this._checkedKeys.has(fieldName) ||
(this.trigger?.options &&
this.trigger.options[fieldName] !== undefined)}
.disabled=${this.disabled}
@change=${this._checkboxChanged}
slot="prefix"
></ha-checkbox>`}
<span slot="heading"
>${this.hass.localize(
`component.${domain}.triggers.${triggerName}.fields.${fieldName}.name`
) || triggerName}</span
>
<span slot="description"
>${this.hass.localize(
`component.${domain}.triggers.${triggerName}.fields.${fieldName}.description`
)}</span
>
<ha-selector
.disabled=${this.disabled ||
(showOptional &&
!this._checkedKeys.has(fieldName) &&
(!this.trigger?.options ||
this.trigger.options[fieldName] === undefined))}
.hass=${this.hass}
.selector=${selector}
.context=${this._generateContext(dataField)}
if (!dataField.selector) {
return nothing;
}
// Hide behavior when the target is a single direct entity.
if (fieldName === "behavior" && this._resolvedTargetEntityCount === 1) {
return nothing;
}
return html`<ha-settings-row narrow>
${!showOptional
? hasOptional
? html`<div slot="prefix" class="checkbox-spacer"></div>`
: nothing
: html`<ha-checkbox
.key=${fieldName}
@value-changed=${this._dataChanged}
.value=${this.trigger?.options
? this.trigger.options[fieldName]
: undefined}
.placeholder=${dataField.default}
.localizeValue=${this._localizeValueCallback}
.required=${dataField.required}
></ha-selector>
</ha-settings-row>`
: nothing;
.checked=${this._checkedKeys.has(fieldName) ||
(this.trigger?.options &&
this.trigger.options[fieldName] !== undefined)}
.disabled=${this.disabled}
@change=${this._checkboxChanged}
slot="prefix"
></ha-checkbox>`}
<span slot="heading"
>${this.hass.localize(
`component.${domain}.triggers.${triggerName}.fields.${fieldName}.name`
) || triggerName}</span
>
<span slot="description"
>${this.hass.localize(
`component.${domain}.triggers.${triggerName}.fields.${fieldName}.description`
)}</span
>
<ha-selector
.disabled=${this.disabled ||
(showOptional &&
!this._checkedKeys.has(fieldName) &&
(!this.trigger?.options ||
this.trigger.options[fieldName] === undefined))}
.hass=${this.hass}
.selector=${selector}
.context=${this._generateContext(dataField)}
.key=${fieldName}
@value-changed=${this._dataChanged}
.value=${this.trigger?.options
? this.trigger.options[fieldName]
: undefined}
.placeholder=${dataField.default}
.localizeValue=${this._localizeValueCallback}
.required=${dataField.required}
></ha-selector>
</ha-settings-row>`;
};
private _generateContext(
@@ -431,6 +445,47 @@ export class HaPlatformTrigger extends LitElement {
}
}
private _resolveTargetEntityCount = memoizeOne(
async (target: PlatformTrigger["target"]) =>
getResolvedTargetEntityCount(this.hass, target)
);
private async _updateResolvedTargetEntityCount(
target: PlatformTrigger["target"]
) {
this._resolvedTargetEntityCount =
await this._resolveTargetEntityCount(target);
if (
this._resolvedTargetEntityCount === 1 &&
this.trigger.options?.behavior !== undefined
) {
const options = { ...this.trigger.options };
delete options.behavior;
fireEvent(this, "value-changed", {
value: {
...this.trigger,
options,
},
});
} else if (
this._resolvedTargetEntityCount !== undefined &&
this._resolvedTargetEntityCount > 1 &&
this.trigger.options?.behavior === undefined
) {
const behaviorDefault = this.description?.fields?.behavior?.default;
if (behaviorDefault !== undefined) {
fireEvent(this, "value-changed", {
value: {
...this.trigger,
options: { ...this.trigger.options, behavior: behaviorDefault },
},
});
}
}
}
static styles = css`
:host {
display: block;