Handle predefined options in Z-Wave config panel (#26097)

* Handle predefined options in Z-Wave config panel

* use ha-combo-box

* lint

* display invalid status on the input

* show number and label

* compute items outside of render
This commit is contained in:
Petar Petrov 2025-07-19 09:18:27 +03:00 committed by GitHub
parent 259e8a14da
commit 9e597d22a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 75 additions and 7 deletions

View File

@ -205,7 +205,7 @@ export class HaComboBox extends LitElement {
role="button"
tabindex="-1"
aria-label=${ifDefined(this.hass?.localize("ui.common.clear"))}
class="clear-button"
class=${`clear-button ${this.label ? "" : "no-label"}`}
.path=${mdiClose}
@click=${this._clearValue}
></ha-svg-icon>`
@ -215,7 +215,7 @@ export class HaComboBox extends LitElement {
tabindex="-1"
aria-label=${ifDefined(this.label)}
aria-expanded=${this.opened ? "true" : "false"}
class="toggle-button"
class=${`toggle-button ${this.label ? "" : "no-label"}`}
.path=${this.opened ? mdiMenuUp : mdiMenuDown}
?disabled=${this.disabled}
@click=${this._toggleOpen}
@ -397,6 +397,9 @@ export class HaComboBox extends LitElement {
color: var(--disabled-text-color);
pointer-events: none;
}
.toggle-button.no-label {
top: -3px;
}
.clear-button {
--mdc-icon-size: 20px;
top: -7px;
@ -405,6 +408,9 @@ export class HaComboBox extends LitElement {
inset-inline-end: 36px;
direction: var(--direction);
}
.clear-button.no-label {
top: 0;
}
ha-input-helper-text {
margin-top: 4px;
}

View File

@ -10,6 +10,7 @@ import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { computeDeviceNameDisplay } from "../../../../../common/entity/compute_device_name";
import { groupBy } from "../../../../../common/util/group-by";
@ -23,6 +24,7 @@ import "../../../../../components/ha-selector/ha-selector-boolean";
import "../../../../../components/ha-settings-row";
import "../../../../../components/ha-svg-icon";
import "../../../../../components/ha-textfield";
import "../../../../../components/ha-combo-box";
import type {
ZWaveJSNodeCapabilities,
ZWaveJSNodeConfigParam,
@ -55,7 +57,7 @@ const icons = {
@customElement("zwave_js-node-config")
class ZWaveJSNodeConfig extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
public hass!: HomeAssistant;
@property({ attribute: false }) public route!: Route;
@ -294,9 +296,10 @@ class ZWaveJSNodeConfig extends LitElement {
? this.hass.localize(
item.metadata.default === 1 ? "ui.common.yes" : "ui.common.no"
)
: item.configuration_value_type === "enumerated"
? item.metadata.states[item.metadata.default] ||
item.metadata.default
: item.metadata.states?.[item.metadata.default]
? item.configuration_value_type === "manual_entry"
? `${item.metadata.default} - ${item.metadata.states[item.metadata.default]}`
: item.metadata.states[item.metadata.default]
: item.metadata.default
}`
: "";
@ -319,8 +322,30 @@ class ZWaveJSNodeConfig extends LitElement {
</div>
`;
}
if (item.configuration_value_type === "manual_entry") {
if (
item.metadata.states &&
item.metadata.min != null &&
item.metadata.max != null &&
item.metadata.max - item.metadata.min <= 100
) {
return html`
${labelAndDescription}
<ha-combo-box
.hass=${this.hass}
.value=${item.value?.toString()}
allow-custom-value
hide-clear-icon
.items=${this._getComboBoxOptions(item.metadata.states)}
.disabled=${!item.metadata.writeable}
.invalid=${result?.status === "error"}
.placeholder=${item.metadata.unit}
.helper=${`${this.hass.localize("ui.panel.config.zwave_js.node_config.between_min_max", { min: item.metadata.min, max: item.metadata.max })}${defaultLabel ? `, ${defaultLabel}` : ""}`}
@value-changed=${this._getComboBoxValueChangedCallback(id, item)}
>
</ha-combo-box>
`;
}
return html`${labelAndDescription}
<ha-textfield
type="number"
@ -424,6 +449,15 @@ class ZWaveJSNodeConfig extends LitElement {
if (Number(this._config![ev.target.key].value) === value) {
return;
}
if (isNaN(value)) {
this._setError(
ev.target.key,
this.hass.localize(
"ui.panel.config.zwave_js.node_config.error_not_numeric"
)
);
return;
}
if (
(ev.target.min !== undefined && value < ev.target.min) ||
(ev.target.max !== undefined && value > ev.target.max)
@ -441,6 +475,33 @@ class ZWaveJSNodeConfig extends LitElement {
this._updateConfigParameter(ev.target, value);
}
private _getComboBoxOptions = memoizeOne((states: Record<string, string>) =>
Object.entries(states).map(([value, label]) => ({
value,
label: `${value} - ${label}`,
}))
);
private _getComboBoxValueChangedCallback(
id: string,
item: ZWaveJSNodeConfigParam
) {
return (ev: CustomEvent<{ value: number }>) =>
this._numericInputChanged({
...ev,
target: {
...ev.target,
key: id,
min: item.metadata.min,
max: item.metadata.max,
value: ev.detail.value,
property: item.property,
endpoint: item.endpoint,
propertyKey: item.property_key,
},
});
}
private async _updateConfigParameter(target, value) {
try {
const result = await setZwaveNodeConfigParameter(

View File

@ -6072,6 +6072,7 @@
"parameter_is_read_only": "This parameter is read-only.",
"between_min_max": "Between {min} and {max}",
"error_not_in_range": "Value must be between {min} and {max}",
"error_not_numeric": "Value must be a number",
"error_required": "{entity} is required",
"error_device_not_found": "Device not found",
"set_param_accepted": "The parameter has been updated.",