Compare commits

...

2 Commits

Author SHA1 Message Date
Bram Kragten
2ad4b32fd7 Use mode option instead 2026-03-25 16:03:03 +01:00
Bram Kragten
0f92d55122 Add automation behavior selector 2026-03-25 14:47:42 +01:00
6 changed files with 210 additions and 14 deletions

View File

@@ -0,0 +1,132 @@
import { LitElement, css, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import type { LocalizeKeys } from "../../common/translations/localize";
import type {
AutomationBehavior,
AutomationBehaviorConditionMode,
AutomationBehaviorSelector,
AutomationBehaviorTriggerMode,
} from "../../data/selector";
import type { HomeAssistant } from "../../types";
import "../ha-formfield";
import "../ha-input-helper-text";
import "../ha-radio";
const TRIGGER_BEHAVIORS: AutomationBehaviorTriggerMode[] = [
"any",
"first",
"last",
];
const CONDITION_BEHAVIORS: AutomationBehaviorConditionMode[] = ["any", "all"];
@customElement("ha-selector-automation_behavior")
export class HaSelectorAutomationBehavior extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false })
public selector!: AutomationBehaviorSelector;
@property() public value?: AutomationBehavior;
@property() public label?: string;
@property() public helper?: string;
@property({ attribute: false })
public localizeValue?: (key: string) => string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
protected render() {
const behaviors = this._behaviors();
return html`
${this.label ? html`<span class="label">${this.label}</span>` : nothing}
<div class="container">
${behaviors.map(
(behavior) => html`
<ha-formfield
.label=${this._localizeOption(behavior)}
.disabled=${this.disabled}
>
<ha-radio
.checked=${behavior === this.value}
.value=${behavior}
.disabled=${this.disabled}
@change=${this._radioChanged}
></ha-radio>
</ha-formfield>
`
)}
</div>
${this.helper
? html`<ha-input-helper-text .disabled=${this.disabled}
>${this.helper}</ha-input-helper-text
>`
: nothing}
`;
}
private _behaviors(): AutomationBehavior[] {
const mode = this.selector.automation_behavior?.mode;
return mode === "condition" ? CONDITION_BEHAVIORS : TRIGGER_BEHAVIORS;
}
private _localizeOption(behavior: AutomationBehavior): string {
const { translation_key: translationKey, mode } =
this.selector.automation_behavior ?? {};
if (this.localizeValue && translationKey) {
const translated = this.localizeValue(
`${translationKey}.options.${behavior}`
);
if (translated) {
return translated;
}
}
return (
this.hass.localize(
`ui.components.selectors.automation_behavior.${mode ?? "trigger"}.options.${behavior}` as LocalizeKeys
) || behavior
);
}
private _radioChanged(ev: Event) {
ev.stopPropagation();
const value = (ev.target as HTMLInputElement).value as AutomationBehavior;
if (this.disabled || value === this.value) {
return;
}
fireEvent(this, "value-changed", { value });
}
static styles = css`
.label {
display: block;
margin-bottom: var(--ha-space-2);
color: var(--secondary-text-color);
font-size: var(--ha-font-size-s);
}
.container {
border: 1px solid var(--divider-color);
border-radius: var(--ha-border-radius-lg);
padding: var(--ha-space-2) var(--ha-space-4);
padding-left: 0;
}
ha-formfield {
display: block;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-selector-automation_behavior": HaSelectorAutomationBehavior;
}
}

View File

@@ -13,6 +13,7 @@ import type { HomeAssistant } from "../../types";
const LOAD_ELEMENTS = {
action: () => import("./ha-selector-action"),
addon: () => import("./ha-selector-addon"),
automation_behavior: () => import("./ha-selector-automation-behavior"),
app: () => import("./ha-selector-app"),
area: () => import("./ha-selector-area"),
areas_display: () => import("./ha-selector-areas-display"),

View File

@@ -31,6 +31,7 @@ export type Selector =
| AreaSelector
| AreasDisplaySelector
| AttributeSelector
| AutomationBehaviorSelector
| BooleanSelector
| ButtonToggleSelector
| ChooseSelector
@@ -122,6 +123,21 @@ export interface BooleanSelector {
boolean: {} | null;
}
export type AutomationBehaviorTriggerMode = "first" | "last" | "any";
export type AutomationBehaviorConditionMode = "all" | "any";
export type AutomationBehavior =
| AutomationBehaviorTriggerMode
| AutomationBehaviorConditionMode;
export interface AutomationBehaviorSelector {
automation_behavior: {
mode: "trigger" | "condition";
translation_key?: string;
} | null;
}
export interface ButtonToggleSelector {
button_toggle: {
options: readonly string[] | readonly SelectOption[];

View File

@@ -97,7 +97,8 @@ export class HaPlatformCondition extends LitElement {
field.default !== undefined &&
updatedOptions[key] === undefined &&
!(
key === "behavior" &&
field.selector &&
"automation_behavior" in field.selector &&
this.description?.target &&
!this.condition?.target
)
@@ -231,7 +232,7 @@ export class HaPlatformCondition extends LitElement {
}
if (
fieldName === "behavior" &&
"automation_behavior" in selector &&
this.description?.target &&
(!this.condition?.target ||
(this._resolvedTargetEntityCount !== undefined &&
@@ -430,14 +431,26 @@ export class HaPlatformCondition extends LitElement {
this._resolvedTargetEntityCount =
await this._resolveTargetEntityCount(target);
const behaviorFieldEntry = Object.entries(
this.description?.fields ?? {}
).find(
([, field]) => field.selector && "automation_behavior" in field.selector
);
if (!behaviorFieldEntry) {
return;
}
const [behaviorFieldName, behaviorField] = behaviorFieldEntry;
if (
(!target ||
(this._resolvedTargetEntityCount !== undefined &&
this._resolvedTargetEntityCount <= 1)) &&
this.condition.options?.behavior !== undefined
this.condition.options?.[behaviorFieldName] !== undefined
) {
const options = { ...this.condition.options };
delete options.behavior;
delete options[behaviorFieldName];
fireEvent(this, "value-changed", {
value: {
@@ -449,14 +462,17 @@ export class HaPlatformCondition extends LitElement {
target &&
this._resolvedTargetEntityCount !== undefined &&
this._resolvedTargetEntityCount > 1 &&
this.condition.options?.behavior === undefined
this.condition.options?.[behaviorFieldName] === undefined
) {
const behaviorDefault = this.description?.fields?.behavior?.default;
const behaviorDefault = behaviorField.default;
if (behaviorDefault !== undefined) {
fireEvent(this, "value-changed", {
value: {
...this.condition,
options: { ...this.condition.options, behavior: behaviorDefault },
options: {
...this.condition.options,
[behaviorFieldName]: behaviorDefault,
},
},
});
}

View File

@@ -132,7 +132,8 @@ export class HaPlatformTrigger extends LitElement {
field.default !== undefined &&
updatedOptions[key] === undefined &&
!(
key === "behavior" &&
field.selector &&
"automation_behavior" in field.selector &&
this.description?.target &&
!this.trigger?.target
)
@@ -267,7 +268,7 @@ export class HaPlatformTrigger extends LitElement {
}
if (
fieldName === "behavior" &&
"automation_behavior" in selector &&
this.description?.target &&
(!this.trigger?.target ||
(this._resolvedTargetEntityCount !== undefined &&
@@ -466,14 +467,26 @@ export class HaPlatformTrigger extends LitElement {
this._resolvedTargetEntityCount =
await this._resolveTargetEntityCount(target);
const behaviorFieldEntry = Object.entries(
this.description?.fields ?? {}
).find(
([, field]) => field.selector && "automation_behavior" in field.selector
);
if (!behaviorFieldEntry) {
return;
}
const [behaviorFieldName, behaviorField] = behaviorFieldEntry;
if (
(!target ||
(this._resolvedTargetEntityCount !== undefined &&
this._resolvedTargetEntityCount <= 1)) &&
this.trigger.options?.behavior !== undefined
this.trigger.options?.[behaviorFieldName] !== undefined
) {
const options = { ...this.trigger.options };
delete options.behavior;
delete options[behaviorFieldName];
fireEvent(this, "value-changed", {
value: {
@@ -485,14 +498,17 @@ export class HaPlatformTrigger extends LitElement {
target &&
this._resolvedTargetEntityCount !== undefined &&
this._resolvedTargetEntityCount > 1 &&
this.trigger.options?.behavior === undefined
this.trigger.options?.[behaviorFieldName] === undefined
) {
const behaviorDefault = this.description?.fields?.behavior?.default;
const behaviorDefault = behaviorField.default;
if (behaviorDefault !== undefined) {
fireEvent(this, "value-changed", {
value: {
...this.trigger,
options: { ...this.trigger.options, behavior: behaviorDefault },
options: {
...this.trigger.options,
[behaviorFieldName]: behaviorDefault,
},
},
});
}

View File

@@ -583,6 +583,21 @@
"between": "In range",
"outside": "Outside range"
}
},
"automation_behavior": {
"trigger": {
"options": {
"any": "When any entity gets in the required state",
"first": "When the first entity gets in the required state",
"last": "When the last entity gets in the required state"
}
},
"condition": {
"options": {
"any": "When any entity is in the required state",
"all": "When all entities are in the required state"
}
}
}
},
"logbook": {