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

Add mode option to numeric threshold selector (#30311)

This commit is contained in:
Bram Kragten
2026-03-25 14:04:21 +01:00
committed by GitHub
parent 9381bbd656
commit eee6f79639
3 changed files with 112 additions and 58 deletions

View File

@@ -1,21 +1,31 @@
import memoizeOne from "memoize-one";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
mdiArrowCollapseVertical,
mdiArrowExpandVertical,
mdiGreaterThan,
mdiLessThan,
} from "@mdi/js";
import { fireEvent } from "../../common/dom/fire_event";
import type { NumericThresholdSelector } from "../../data/selector";
import type {
NumericThresholdSelector,
ThresholdMode,
} from "../../data/selector";
import type { HomeAssistant } from "../../types";
import "../ha-button-toggle-group";
import "../ha-input-helper-text";
import "../ha-select";
import "./ha-selector";
type ThresholdType = "above" | "below" | "between" | "outside";
const iconThresholdAbove =
"M 2 2 L 2 22 L 22 22 L 22 20 L 4 20 L 4 2 L 2 2 z M 17.976562 6 C 17.965863 6.00017 17.951347 6.0014331 17.935547 6.0019531 C 17.903847 6.0030031 17.862047 6.0043225 17.810547 6.0078125 C 17.707247 6.0148425 17.564772 6.0273144 17.388672 6.0527344 C 17.036572 6.1035743 16.54829 6.2035746 15.962891 6.3964844 C 14.788292 6.783584 13.232027 7.5444846 11.611328 9.0332031 C 10.753918 9.820771 9.8854345 10.808987 9.0449219 12.042969 C 7.881634 12.257047 7 13.274809 7 14.5 C 7 15.880699 8.1192914 17 9.5 17 C 10.880699 17 12 15.880699 12 14.5 C 12 13.732663 11.653544 13.046487 11.109375 12.587891 C 11.732682 11.74814 12.359503 11.061942 12.964844 10.505859 C 14.359842 9.2245207 15.662945 8.6023047 16.589844 8.296875 C 17.054643 8.1437252 17.426428 8.0689231 17.673828 8.0332031 C 17.797428 8.0153531 17.891466 8.0076962 17.947266 8.0039062 C 17.974966 8.0020263 17.992753 8.0003 18.001953 8 L 17.998047 6 L 17.976562 6 z";
const iconThresholdBelow =
"M 2 2 L 2 22 L 22 22 L 22 20 L 4 20 L 4 2 L 2 2 z M 9.5 7 C 8.1192914 7 7 8.1192914 7 9.5 C 7 10.880699 8.1192914 12 9.5 12 C 9.598408 12 9.6955741 11.993483 9.7910156 11.982422 C 10.39444 12.754246 11.005767 13.410563 11.611328 13.966797 C 13.232027 15.455495 14.788292 16.216416 15.962891 16.603516 C 16.54829 16.796415 17.036572 16.896366 17.388672 16.947266 C 17.564772 16.972666 17.707247 16.985188 17.810547 16.992188 C 17.862047 16.995687 17.903847 16.997047 17.935547 16.998047 C 17.951347 16.998547 17.965863 16.9998 17.976562 17 L 17.998047 17 L 18.001953 15 C 17.992753 14.9997 17.974966 14.999947 17.947266 14.998047 C 17.891466 14.994247 17.797428 14.984597 17.673828 14.966797 C 17.426428 14.931097 17.054643 14.856325 16.589844 14.703125 C 15.662945 14.397725 14.359842 13.775439 12.964844 12.494141 C 12.496227 12.063656 12.015935 11.551532 11.533203 10.955078 C 11.826929 10.545261 12 10.042666 12 9.5 C 12 8.1192914 10.880699 7 9.5 7 z";
const iconThresholdBetween =
"M 2 2 L 2 22 L 22 22 L 22 20 L 4 20 L 4 2 L 2 2 z M 16.5 4 C 15.119301 4 14 5.1192914 14 6.5 C 14 6.8572837 14.075904 7.196497 14.210938 7.5039062 C 13.503071 8.3427071 12.800578 9.3300361 12.130859 10.501953 C 11.718781 11.223082 11.287475 11.849823 10.845703 12.394531 C 10.457136 12.145771 9.9956073 12 9.5 12 C 8.1192914 12 7 13.119301 7 14.5 C 7 15.880699 8.1192914 17 9.5 17 C 10.880699 17 12 15.880699 12 14.5 C 12 14.38201 11.990422 14.26598 11.974609 14.152344 C 12.636605 13.409426 13.276156 12.531884 13.869141 11.494141 C 14.462491 10.455789 15.073208 9.5905169 15.681641 8.8613281 C 15.938115 8.9501682 16.213303 9 16.5 9 C 17.880699 9 19 7.8807086 19 6.5 C 19 5.1192914 17.880699 4 16.5 4 z";
const iconThresholdOutside =
"M 2 2 L 2 22 L 22 22 L 22 20 L 4 20 L 4 19.046875 C 4.226574 19.041905 4.4812768 19.028419 4.7597656 19 C 5.8832145 18.8854 7.4011147 18.537974 9.0019531 17.609375 L 8.8847656 17.408203 C 9.320466 17.777433 9.8841605 18 10.5 18 C 11.880699 18 13 16.880699 13 15.5 C 13 14.119301 11.880699 13 10.5 13 C 9.1192914 13 8 14.119301 8 15.5 C 8 15.654727 8.0141099 15.806171 8.0410156 15.953125 L 7.9980469 15.876953 C 6.6882482 16.636752 5.4555097 16.918066 4.5566406 17.009766 C 4.3512557 17.030705 4.166436 17.040275 4 17.044922 L 4 2 L 2 2 z M 21.976562 4 C 21.965863 4.00017 21.951347 4.0014331 21.935547 4.0019531 C 21.903847 4.0030031 21.862047 4.0043225 21.810547 4.0078125 C 21.707247 4.0148425 21.564772 4.0273144 21.388672 4.0527344 C 21.036572 4.1035743 20.54829 4.2035846 19.962891 4.3964844 C 19.34193 4.6011277 18.613343 4.9149715 17.826172 5.3808594 C 17.441793 5.1398775 16.987134 5 16.5 5 C 15.119301 5 14 6.1192914 14 7.5 C 14 8.8807086 15.119301 10 16.5 10 C 17.880699 10 19 8.8807086 19 7.5 C 19 7.3403872 18.983669 7.1845035 18.955078 7.0332031 C 19.569666 6.6795942 20.126994 6.4493921 20.589844 6.296875 C 21.054643 6.1437252 21.426428 6.0689231 21.673828 6.0332031 C 21.797428 6.0153531 21.891466 6.0076962 21.947266 6.0039062 C 21.974966 6.0020263 21.992753 6.0003 22.001953 6 L 21.998047 4 L 21.976562 4 z";
type ThresholdType = "above" | "below" | "between" | "outside" | "any";
interface ThresholdValueEntry {
active_choice?: string;
@@ -31,6 +41,12 @@ interface NumericThresholdValue {
value_max?: ThresholdValueEntry;
}
const DEFAULT_TYPE: Record<ThresholdMode, ThresholdType> = {
crossed: "above",
changed: "any",
is: "above",
};
@customElement("ha-selector-numeric_threshold")
export class HaNumericThresholdSelector extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -49,9 +65,14 @@ export class HaNumericThresholdSelector extends LitElement {
@state() private _type?: ThresholdType;
private _getMode(): ThresholdMode {
return this.selector.numeric_threshold?.mode ?? "crossed";
}
protected willUpdate(changedProperties: PropertyValues): void {
if (changedProperties.has("value")) {
this._type = this.value?.type || "above";
if (changedProperties.has("value") || changedProperties.has("selector")) {
const mode = this._getMode();
this._type = this.value?.type || DEFAULT_TYPE[mode];
}
}
@@ -83,41 +104,13 @@ export class HaNumericThresholdSelector extends LitElement {
}
protected render() {
const type = this._type || "above";
const mode = this._getMode();
const type = this._type || DEFAULT_TYPE[mode];
const showSingleValue = type === "above" || type === "below";
const showRangeValues = type === "between" || type === "outside";
const unitOptions = this._getUnitOptions();
const typeOptions = [
{
value: "above",
label: this.hass.localize(
"ui.components.selectors.numeric_threshold.above"
),
iconPath: mdiGreaterThan,
},
{
value: "below",
label: this.hass.localize(
"ui.components.selectors.numeric_threshold.below"
),
iconPath: mdiLessThan,
},
{
value: "between",
label: this.hass.localize(
"ui.components.selectors.numeric_threshold.in_range"
),
iconPath: mdiArrowCollapseVertical,
},
{
value: "outside",
label: this.hass.localize(
"ui.components.selectors.numeric_threshold.outside_range"
),
iconPath: mdiArrowExpandVertical,
},
];
const typeOptions = this._buildTypeOptions(this.hass.localize, mode);
const choiceToggleButtons = [
{
@@ -134,6 +127,16 @@ export class HaNumericThresholdSelector extends LitElement {
},
];
// Value-row label for single-value types (above/below).
const singleValueLabel = this.hass.localize(
`ui.components.selectors.numeric_threshold.${mode}.${type as "above" | "below"}`
);
// Determine which type-select label to use per mode
const typeSelectLabel = this.hass.localize(
`ui.components.selectors.numeric_threshold.${mode}.type`
);
return html`
<div class="container">
${this.label
@@ -141,9 +144,7 @@ export class HaNumericThresholdSelector extends LitElement {
: nothing}
<div class="inputs">
<ha-select
.label=${this.hass.localize(
"ui.components.selectors.numeric_threshold.type"
)}
.label=${typeSelectLabel}
.value=${type}
.options=${typeOptions}
.disabled=${this.disabled}
@@ -152,11 +153,7 @@ export class HaNumericThresholdSelector extends LitElement {
${showSingleValue
? this._renderValueRow(
this.hass.localize(
type === "above"
? "ui.components.selectors.numeric_threshold.above"
: "ui.components.selectors.numeric_threshold.below"
),
singleValueLabel,
this.value?.value,
this._valueChanged,
this._valueChoiceChanged,
@@ -199,6 +196,39 @@ export class HaNumericThresholdSelector extends LitElement {
`;
}
private _buildTypeOptions = memoizeOne(
(localize: HomeAssistant["localize"], mode: ThresholdMode) => {
const baseOptions = (
[
{ value: "above", iconPath: iconThresholdAbove },
{ value: "below", iconPath: iconThresholdBelow },
{ value: "between", iconPath: iconThresholdBetween },
{ value: "outside", iconPath: iconThresholdOutside },
] as const
).map(({ value, iconPath }) => ({
value,
iconPath,
label: localize(
`ui.components.selectors.numeric_threshold.${mode}.${value}`
),
}));
if (mode !== "changed") {
return baseOptions;
}
return [
{
value: "any",
label: localize(
"ui.components.selectors.numeric_threshold.changed.any"
),
},
...baseOptions,
];
}
);
private _renderUnitSelect(
entry: ThresholdValueEntry | undefined,
handler: (ev: CustomEvent) => void,
@@ -255,9 +285,11 @@ export class HaNumericThresholdSelector extends LitElement {
return html`
<div class="value-row">
<div class="value-header">
<span class="value-label"
>${rowLabel}${this.required ? "*" : ""}</span
>
${rowLabel
? html`<span class="value-label"
>${rowLabel}${this.required ? "*" : ""}</span
>`
: nothing}
<ha-button-toggle-group
size="small"
.buttons=${choiceToggleButtons}
@@ -302,6 +334,7 @@ export class HaNumericThresholdSelector extends LitElement {
newValue.value_min = this.value?.value_min ?? this.value?.value;
newValue.value_max = this.value?.value_max;
}
// "any" type has no value fields
fireEvent(this, "value-changed", { value: newValue });
}
@@ -428,6 +461,7 @@ export class HaNumericThresholdSelector extends LitElement {
.inputs,
.value-row {
--ha-input-padding-bottom: 0;
display: flex;
flex-direction: column;
gap: var(--ha-space-2);

View File

@@ -22,6 +22,8 @@ import type {
} from "./entity/entity_registry";
import type { EntitySources } from "./entity/entity_sources";
export type ThresholdMode = "crossed" | "changed" | "is";
export type Selector =
| ActionSelector
| AddonSelector
@@ -366,6 +368,7 @@ export interface NumberSelector {
export interface NumericThresholdSelector {
numeric_threshold: {
mode?: ThresholdMode;
unit_of_measurement?: readonly string[];
number?: NumberSelector["number"];
entity?: EntitySelectorFilter | readonly EntitySelectorFilter[];

View File

@@ -556,16 +556,33 @@
"hide_password": "Hide password"
},
"numeric_threshold": {
"type": "Threshold type",
"above": "Above",
"below": "Below",
"in_range": "In range",
"outside_range": "Outside range",
"unit": "Unit",
"number": "Number",
"entity": "Entity",
"from": "From",
"to": "To"
"to": "To",
"crossed": {
"type": "Fire when the value goes",
"above": "Above",
"below": "Below",
"between": "In range",
"outside": "Outside range"
},
"changed": {
"type": "Fire when the value changes",
"any": "Any change",
"above": "Above",
"below": "Below",
"between": "In range",
"outside": "Outside range"
},
"is": {
"type": "Check if the value is",
"above": "Above",
"below": "Below",
"between": "In range",
"outside": "Outside range"
}
}
},
"logbook": {