Add customize mode option to card features with modes (#20670)

* Add customize mode options to card features with modes

* Better type

* Fix water heater and humidifier

* Clean schema
This commit is contained in:
Paul Bottein 2024-04-30 18:38:51 +02:00 committed by GitHub
parent 334c245b65
commit 7120ad99b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 403 additions and 154 deletions

View File

@ -2,6 +2,6 @@ export const filterModes = (
supportedModes: string[] | undefined, supportedModes: string[] | undefined,
selectedModes: string[] | undefined selectedModes: string[] | undefined
): string[] => ): string[] =>
(selectedModes || []).length selectedModes
? selectedModes!.filter((mode) => (supportedModes || []).includes(mode)) ? selectedModes.filter((mode) => (supportedModes || []).includes(mode))
: supportedModes || []; : supportedModes || [];

View File

@ -45,7 +45,6 @@ class HuiClimateFanModesCardFeature
return { return {
type: "climate-fan-modes", type: "climate-fan-modes",
style: "dropdown", style: "dropdown",
fan_modes: [],
}; };
} }

View File

@ -19,8 +19,8 @@ import {
import { UNAVAILABLE } from "../../../data/entity"; import { UNAVAILABLE } from "../../../data/entity";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { ClimateHvacModesCardFeatureConfig } from "./types";
import { filterModes } from "./common/filter-modes"; import { filterModes } from "./common/filter-modes";
import { ClimateHvacModesCardFeatureConfig } from "./types";
export const supportsClimateHvacModesCardFeature = (stateObj: HassEntity) => { export const supportsClimateHvacModesCardFeature = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id); const domain = computeDomain(stateObj.entity_id);
@ -46,7 +46,6 @@ class HuiClimateHvacModesCardFeature
static getStubConfig(): ClimateHvacModesCardFeatureConfig { static getStubConfig(): ClimateHvacModesCardFeatureConfig {
return { return {
type: "climate-hvac-modes", type: "climate-hvac-modes",
hvac_modes: [],
}; };
} }
@ -120,10 +119,12 @@ class HuiClimateHvacModesCardFeature
const color = stateColorCss(this.stateObj); const color = stateColorCss(this.stateObj);
const ordererHvacModes = (this.stateObj.attributes.hvac_modes || [])
.concat()
.sort(compareClimateHvacModes);
const options = filterModes( const options = filterModes(
[...(this.stateObj?.attributes.hvac_modes || [])].sort( ordererHvacModes,
compareClimateHvacModes
),
this._config.hvac_modes this._config.hvac_modes
).map<ControlSelectOption>((mode) => ({ ).map<ControlSelectOption>((mode) => ({
value: mode, value: mode,

View File

@ -45,7 +45,6 @@ class HuiClimatePresetModesCardFeature
return { return {
type: "climate-preset-modes", type: "climate-preset-modes",
style: "dropdown", style: "dropdown",
preset_modes: [],
}; };
} }

View File

@ -45,7 +45,6 @@ class HuiClimateSwingModesCardFeature
return { return {
type: "climate-swing-modes", type: "climate-swing-modes",
style: "dropdown", style: "dropdown",
swing_modes: [],
}; };
} }

View File

@ -44,7 +44,6 @@ class HuiFanPresetModesCardFeature
return { return {
type: "fan-preset-modes", type: "fan-preset-modes",
style: "dropdown", style: "dropdown",
preset_modes: [],
}; };
} }

View File

@ -48,7 +48,6 @@ class HuiHumidifierModesCardFeature
return { return {
type: "humidifier-modes", type: "humidifier-modes",
style: "dropdown", style: "dropdown",
modes: [],
}; };
} }

View File

@ -44,7 +44,6 @@ class HuiWaterHeaterOperationModeCardFeature
static getStubConfig(): WaterHeaterOperationModesCardFeatureConfig { static getStubConfig(): WaterHeaterOperationModesCardFeatureConfig {
return { return {
type: "water-heater-operation-modes", type: "water-heater-operation-modes",
operation_modes: [],
}; };
} }
@ -105,10 +104,12 @@ class HuiWaterHeaterOperationModeCardFeature
const color = stateColorCss(this.stateObj); const color = stateColorCss(this.stateObj);
const orderedModes = (this.stateObj.attributes.operation_list || [])
.concat()
.sort(compareWaterHeaterOperationMode);
const options = filterModes( const options = filterModes(
[...(this.stateObj?.attributes.operation_list || [])].sort( orderedModes,
compareWaterHeaterOperationMode
),
this._config.operation_modes this._config.operation_modes
).map<ControlSelectOption>((mode) => ({ ).map<ControlSelectOption>((mode) => ({
value: mode, value: mode,

View File

@ -17,6 +17,10 @@ import {
} from "../../card-features/types"; } from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types"; import type { LovelaceCardFeatureEditor } from "../../types";
type ClimateFanModesCardFeatureData = ClimateFanModesCardFeatureConfig & {
customize_modes: boolean;
};
@customElement("hui-climate-fan-modes-card-feature-editor") @customElement("hui-climate-fan-modes-card-feature-editor")
export class HuiClimateFanModesCardFeatureEditor export class HuiClimateFanModesCardFeatureEditor
extends LitElement extends LitElement
@ -36,7 +40,8 @@ export class HuiClimateFanModesCardFeatureEditor
( (
localize: LocalizeFunc, localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc, formatEntityAttributeValue: FormatEntityAttributeValueFunc,
stateObj?: HassEntity stateObj: HassEntity | undefined,
customizeModes: boolean
) => ) =>
[ [
{ {
@ -55,20 +60,33 @@ export class HuiClimateFanModesCardFeatureEditor
}, },
}, },
{ {
name: "fan_modes", name: "customize_modes",
selector: { selector: {
select: { boolean: {},
multiple: true,
reorder: true,
mode: "list",
options:
stateObj?.attributes.fan_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(stateObj, "fan_mode", mode),
})) || [],
},
}, },
}, },
...(customizeModes
? ([
{
name: "fan_modes",
selector: {
select: {
multiple: true,
reorder: true,
options:
stateObj?.attributes.fan_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(
stateObj,
"fan_mode",
mode
),
})) || [],
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
] as const satisfies readonly HaFormSchema[] ] as const satisfies readonly HaFormSchema[]
); );
@ -81,16 +99,17 @@ export class HuiClimateFanModesCardFeatureEditor
? this.hass.states[this.context?.entity_id] ? this.hass.states[this.context?.entity_id]
: undefined; : undefined;
const data: ClimateFanModesCardFeatureConfig = { const data: ClimateFanModesCardFeatureData = {
style: "dropdown", style: "dropdown",
fan_modes: [],
...this._config, ...this._config,
customize_modes: this._config.fan_modes !== undefined,
}; };
const schema = this._schema( const schema = this._schema(
this.hass.localize, this.hass.localize,
this.hass.formatEntityAttributeValue, this.hass.formatEntityAttributeValue,
stateObj stateObj,
data.customize_modes
); );
return html` return html`
@ -105,7 +124,21 @@ export class HuiClimateFanModesCardFeatureEditor
} }
private _valueChanged(ev: CustomEvent): void { private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value }); const { customize_modes, ...config } = ev.detail
.value as ClimateFanModesCardFeatureData;
const stateObj = this.context?.entity_id
? this.hass!.states[this.context?.entity_id]
: undefined;
if (customize_modes && !config.fan_modes) {
config.fan_modes = stateObj?.attributes.fan_modes || [];
}
if (!customize_modes && config.fan_modes) {
delete config.fan_modes;
}
fireEvent(this, "config-changed", { config: config });
} }
private _computeLabelCallback = ( private _computeLabelCallback = (
@ -114,6 +147,7 @@ export class HuiClimateFanModesCardFeatureEditor
switch (schema.name) { switch (schema.name) {
case "style": case "style":
case "fan_modes": case "fan_modes":
case "customize_modes":
return this.hass!.localize( return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.climate-fan-modes.${schema.name}` `ui.panel.lovelace.editor.features.types.climate-fan-modes.${schema.name}`
); );

View File

@ -6,8 +6,11 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import type { FormatEntityStateFunc } from "../../../../common/translations/entity-state"; import type { FormatEntityStateFunc } from "../../../../common/translations/entity-state";
import type { LocalizeFunc } from "../../../../common/translations/localize"; import type { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-form/ha-form"; import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types"; import type {
import { HVAC_MODES } from "../../../../data/climate"; HaFormSchema,
SchemaUnion,
} from "../../../../components/ha-form/types";
import { compareClimateHvacModes } from "../../../../data/climate";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { import {
ClimateHvacModesCardFeatureConfig, ClimateHvacModesCardFeatureConfig,
@ -15,6 +18,10 @@ import {
} from "../../card-features/types"; } from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types"; import type { LovelaceCardFeatureEditor } from "../../types";
type ClimateHvacModesCardFeatureData = ClimateHvacModesCardFeatureConfig & {
customize_modes: boolean;
};
@customElement("hui-climate-hvac-modes-card-feature-editor") @customElement("hui-climate-hvac-modes-card-feature-editor")
export class HuiClimateHvacModesCardFeatureEditor export class HuiClimateHvacModesCardFeatureEditor
extends LitElement extends LitElement
@ -34,7 +41,8 @@ export class HuiClimateHvacModesCardFeatureEditor
( (
localize: LocalizeFunc, localize: LocalizeFunc,
formatEntityState: FormatEntityStateFunc, formatEntityState: FormatEntityStateFunc,
stateObj?: HassEntity stateObj: HassEntity | undefined,
customizeModes: boolean
) => ) =>
[ [
{ {
@ -53,22 +61,34 @@ export class HuiClimateHvacModesCardFeatureEditor
}, },
}, },
{ {
name: "hvac_modes", name: "customize_modes",
selector: { selector: {
select: { boolean: {},
multiple: true,
reorder: true,
mode: "list",
options: HVAC_MODES.filter((mode) =>
stateObj?.attributes.hvac_modes?.includes(mode)
).map((mode) => ({
value: mode,
label: stateObj ? formatEntityState(stateObj, mode) : mode,
})),
},
}, },
}, },
] as const ...(customizeModes
? ([
{
name: "hvac_modes",
selector: {
select: {
reorder: true,
multiple: true,
options: (stateObj?.attributes.hvac_modes || [])
.concat()
.sort(compareClimateHvacModes)
.map((mode) => ({
value: mode,
label: stateObj
? formatEntityState(stateObj, mode)
: mode,
})),
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
] as const satisfies readonly HaFormSchema[]
); );
protected render() { protected render() {
@ -80,16 +100,17 @@ export class HuiClimateHvacModesCardFeatureEditor
? this.hass.states[this.context?.entity_id] ? this.hass.states[this.context?.entity_id]
: undefined; : undefined;
const data: ClimateHvacModesCardFeatureConfig = { const data: ClimateHvacModesCardFeatureData = {
style: "icons", style: "icons",
hvac_modes: [],
...this._config, ...this._config,
customize_modes: this._config.hvac_modes !== undefined,
}; };
const schema = this._schema( const schema = this._schema(
this.hass.localize, this.hass.localize,
this.hass.formatEntityState, this.hass.formatEntityState,
stateObj stateObj,
data.customize_modes
); );
return html` return html`
@ -104,7 +125,24 @@ export class HuiClimateHvacModesCardFeatureEditor
} }
private _valueChanged(ev: CustomEvent): void { private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value }); const { customize_modes, ...config } = ev.detail
.value as ClimateHvacModesCardFeatureData;
const stateObj = this.context?.entity_id
? this.hass!.states[this.context?.entity_id]
: undefined;
if (customize_modes && !config.hvac_modes) {
const ordererHvacModes = (stateObj?.attributes.hvac_modes || [])
.concat()
.sort(compareClimateHvacModes);
config.hvac_modes = ordererHvacModes;
}
if (!customize_modes && config.hvac_modes) {
delete config.hvac_modes;
}
fireEvent(this, "config-changed", { config: config });
} }
private _computeLabelCallback = ( private _computeLabelCallback = (
@ -113,6 +151,7 @@ export class HuiClimateHvacModesCardFeatureEditor
switch (schema.name) { switch (schema.name) {
case "hvac_modes": case "hvac_modes":
case "style": case "style":
case "customize_modes":
return this.hass!.localize( return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.climate-hvac-modes.${schema.name}` `ui.panel.lovelace.editor.features.types.climate-hvac-modes.${schema.name}`
); );

View File

@ -17,6 +17,10 @@ import {
} from "../../card-features/types"; } from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types"; import type { LovelaceCardFeatureEditor } from "../../types";
type ClimatePresetModesCardFeatureData = ClimatePresetModesCardFeatureConfig & {
customize_modes: boolean;
};
@customElement("hui-climate-preset-modes-card-feature-editor") @customElement("hui-climate-preset-modes-card-feature-editor")
export class HuiClimatePresetModesCardFeatureEditor export class HuiClimatePresetModesCardFeatureEditor
extends LitElement extends LitElement
@ -36,7 +40,8 @@ export class HuiClimatePresetModesCardFeatureEditor
( (
localize: LocalizeFunc, localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc, formatEntityAttributeValue: FormatEntityAttributeValueFunc,
stateObj?: HassEntity stateObj: HassEntity | undefined,
customizeModes: boolean
) => ) =>
[ [
{ {
@ -55,24 +60,33 @@ export class HuiClimatePresetModesCardFeatureEditor
}, },
}, },
{ {
name: "preset_modes", name: "customize_modes",
selector: { selector: {
select: { boolean: {},
multiple: true,
reorder: true,
mode: "list",
options:
stateObj?.attributes.preset_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(
stateObj,
"preset_mode",
mode
),
})) || [],
},
}, },
}, },
...(customizeModes
? ([
{
name: "preset_modes",
selector: {
select: {
reorder: true,
multiple: true,
options:
stateObj?.attributes.preset_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(
stateObj,
"preset_mode",
mode
),
})) || [],
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
] as const satisfies readonly HaFormSchema[] ] as const satisfies readonly HaFormSchema[]
); );
@ -85,16 +99,17 @@ export class HuiClimatePresetModesCardFeatureEditor
? this.hass.states[this.context?.entity_id] ? this.hass.states[this.context?.entity_id]
: undefined; : undefined;
const data: ClimatePresetModesCardFeatureConfig = { const data: ClimatePresetModesCardFeatureData = {
style: "dropdown", style: "dropdown",
preset_modes: [],
...this._config, ...this._config,
customize_modes: this._config.preset_modes !== undefined,
}; };
const schema = this._schema( const schema = this._schema(
this.hass.localize, this.hass.localize,
this.hass.formatEntityAttributeValue, this.hass.formatEntityAttributeValue,
stateObj stateObj,
data.customize_modes
); );
return html` return html`
@ -109,7 +124,21 @@ export class HuiClimatePresetModesCardFeatureEditor
} }
private _valueChanged(ev: CustomEvent): void { private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value }); const { customize_modes, ...config } = ev.detail
.value as ClimatePresetModesCardFeatureData;
const stateObj = this.context?.entity_id
? this.hass!.states[this.context?.entity_id]
: undefined;
if (customize_modes && !config.preset_modes) {
config.preset_modes = stateObj?.attributes.preset_modes || [];
}
if (!customize_modes && config.preset_modes) {
delete config.preset_modes;
}
fireEvent(this, "config-changed", { config: config });
} }
private _computeLabelCallback = ( private _computeLabelCallback = (
@ -118,6 +147,7 @@ export class HuiClimatePresetModesCardFeatureEditor
switch (schema.name) { switch (schema.name) {
case "style": case "style":
case "preset_modes": case "preset_modes":
case "customize_modes":
return this.hass!.localize( return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.climate-preset-modes.${schema.name}` `ui.panel.lovelace.editor.features.types.climate-preset-modes.${schema.name}`
); );

View File

@ -17,6 +17,10 @@ import {
} from "../../card-features/types"; } from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types"; import type { LovelaceCardFeatureEditor } from "../../types";
type ClimateSwingModesCardFeatureData = ClimateSwingModesCardFeatureConfig & {
customize_modes: boolean;
};
@customElement("hui-climate-swing-modes-card-feature-editor") @customElement("hui-climate-swing-modes-card-feature-editor")
export class HuiClimateSwingModesCardFeatureEditor export class HuiClimateSwingModesCardFeatureEditor
extends LitElement extends LitElement
@ -36,7 +40,8 @@ export class HuiClimateSwingModesCardFeatureEditor
( (
localize: LocalizeFunc, localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc, formatEntityAttributeValue: FormatEntityAttributeValueFunc,
stateObj?: HassEntity stateObj: HassEntity | undefined,
customizeModes: boolean
) => ) =>
[ [
{ {
@ -55,24 +60,33 @@ export class HuiClimateSwingModesCardFeatureEditor
}, },
}, },
{ {
name: "swing_modes", name: "customize_modes",
selector: { selector: {
select: { boolean: {},
multiple: true,
reorder: true,
mode: "list",
options:
stateObj?.attributes.swing_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(
stateObj,
"swing_mode",
mode
),
})) || [],
},
}, },
}, },
...(customizeModes
? ([
{
name: "swing_modes",
selector: {
select: {
reorder: true,
multiple: true,
options:
stateObj?.attributes.swing_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(
stateObj,
"swing_mode",
mode
),
})) || [],
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
] as const satisfies readonly HaFormSchema[] ] as const satisfies readonly HaFormSchema[]
); );
@ -85,16 +99,17 @@ export class HuiClimateSwingModesCardFeatureEditor
? this.hass.states[this.context?.entity_id] ? this.hass.states[this.context?.entity_id]
: undefined; : undefined;
const data: ClimateSwingModesCardFeatureConfig = { const data: ClimateSwingModesCardFeatureData = {
style: "dropdown", style: "dropdown",
swing_modes: [],
...this._config, ...this._config,
customize_modes: this._config.swing_modes !== undefined,
}; };
const schema = this._schema( const schema = this._schema(
this.hass.localize, this.hass.localize,
this.hass.formatEntityAttributeValue, this.hass.formatEntityAttributeValue,
stateObj stateObj,
data.customize_modes
); );
return html` return html`
@ -109,7 +124,21 @@ export class HuiClimateSwingModesCardFeatureEditor
} }
private _valueChanged(ev: CustomEvent): void { private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value }); const { customize_modes, ...config } = ev.detail
.value as ClimateSwingModesCardFeatureData;
const stateObj = this.context?.entity_id
? this.hass!.states[this.context?.entity_id]
: undefined;
if (customize_modes && !config.swing_modes) {
config.swing_modes = stateObj?.attributes.swing_modes || [];
}
if (!customize_modes && config.swing_modes) {
delete config.swing_modes;
}
fireEvent(this, "config-changed", { config: config });
} }
private _computeLabelCallback = ( private _computeLabelCallback = (
@ -118,6 +147,7 @@ export class HuiClimateSwingModesCardFeatureEditor
switch (schema.name) { switch (schema.name) {
case "style": case "style":
case "swing_modes": case "swing_modes":
case "customize_modes":
return this.hass!.localize( return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.climate-swing-modes.${schema.name}` `ui.panel.lovelace.editor.features.types.climate-swing-modes.${schema.name}`
); );

View File

@ -17,6 +17,10 @@ import {
} from "../../card-features/types"; } from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types"; import type { LovelaceCardFeatureEditor } from "../../types";
type FanPresetModesCardFeatureData = FanPresetModesCardFeatureConfig & {
customize_modes: boolean;
};
@customElement("hui-fan-preset-modes-card-feature-editor") @customElement("hui-fan-preset-modes-card-feature-editor")
export class HuiFanPresetModesCardFeatureEditor export class HuiFanPresetModesCardFeatureEditor
extends LitElement extends LitElement
@ -36,7 +40,8 @@ export class HuiFanPresetModesCardFeatureEditor
( (
localize: LocalizeFunc, localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc, formatEntityAttributeValue: FormatEntityAttributeValueFunc,
stateObj?: HassEntity stateObj: HassEntity | undefined,
customizeModes: boolean
) => ) =>
[ [
{ {
@ -55,24 +60,33 @@ export class HuiFanPresetModesCardFeatureEditor
}, },
}, },
{ {
name: "preset_modes", name: "customize_modes",
selector: { selector: {
select: { boolean: {},
multiple: true,
reorder: true,
mode: "list",
options:
stateObj?.attributes.preset_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(
stateObj,
"preset_mode",
mode
),
})) || [],
},
}, },
}, },
...(customizeModes
? ([
{
name: "preset_modes",
selector: {
select: {
reorder: true,
multiple: true,
options:
stateObj?.attributes.preset_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(
stateObj,
"preset_mode",
mode
),
})) || [],
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
] as const satisfies readonly HaFormSchema[] ] as const satisfies readonly HaFormSchema[]
); );
@ -85,16 +99,17 @@ export class HuiFanPresetModesCardFeatureEditor
? this.hass.states[this.context?.entity_id] ? this.hass.states[this.context?.entity_id]
: undefined; : undefined;
const data: FanPresetModesCardFeatureConfig = { const data: FanPresetModesCardFeatureData = {
style: "dropdown", style: "dropdown",
preset_modes: [],
...this._config, ...this._config,
customize_modes: this._config.preset_modes !== undefined,
}; };
const schema = this._schema( const schema = this._schema(
this.hass.localize, this.hass.localize,
this.hass.formatEntityAttributeValue, this.hass.formatEntityAttributeValue,
stateObj stateObj,
data.customize_modes
); );
return html` return html`
@ -109,7 +124,21 @@ export class HuiFanPresetModesCardFeatureEditor
} }
private _valueChanged(ev: CustomEvent): void { private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value }); const { customize_modes, ...config } = ev.detail
.value as FanPresetModesCardFeatureData;
const stateObj = this.context?.entity_id
? this.hass!.states[this.context?.entity_id]
: undefined;
if (customize_modes && !config.preset_modes) {
config.preset_modes = stateObj?.attributes.preset_modes || [];
}
if (!customize_modes && config.preset_modes) {
delete config.preset_modes;
}
fireEvent(this, "config-changed", { config: config });
} }
private _computeLabelCallback = ( private _computeLabelCallback = (
@ -118,6 +147,7 @@ export class HuiFanPresetModesCardFeatureEditor
switch (schema.name) { switch (schema.name) {
case "style": case "style":
case "preset_modes": case "preset_modes":
case "customize_modes":
return this.hass!.localize( return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.fan-preset-modes.${schema.name}` `ui.panel.lovelace.editor.features.types.fan-preset-modes.${schema.name}`
); );

View File

@ -17,6 +17,10 @@ import {
} from "../../card-features/types"; } from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types"; import type { LovelaceCardFeatureEditor } from "../../types";
type HumidifierModesCardFeatureData = HumidifierModesCardFeatureConfig & {
customize_modes: boolean;
};
@customElement("hui-humidifier-modes-card-feature-editor") @customElement("hui-humidifier-modes-card-feature-editor")
export class HuiHumidifierModesCardFeatureEditor export class HuiHumidifierModesCardFeatureEditor
extends LitElement extends LitElement
@ -36,7 +40,8 @@ export class HuiHumidifierModesCardFeatureEditor
( (
localize: LocalizeFunc, localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc, formatEntityAttributeValue: FormatEntityAttributeValueFunc,
stateObj?: HassEntity stateObj: HassEntity | undefined,
customizeModes: boolean
) => ) =>
[ [
{ {
@ -55,20 +60,33 @@ export class HuiHumidifierModesCardFeatureEditor
}, },
}, },
{ {
name: "modes", name: "customize_modes",
selector: { selector: {
select: { boolean: {},
multiple: true,
reorder: true,
mode: "list",
options:
stateObj?.attributes.available_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(stateObj, "mode", mode),
})) || [],
},
}, },
}, },
...(customizeModes
? ([
{
name: "modes",
selector: {
select: {
reorder: true,
multiple: true,
options:
stateObj?.attributes.available_modes?.map((mode) => ({
value: mode,
label: formatEntityAttributeValue(
stateObj,
"mode",
mode
),
})) || [],
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
] as const satisfies readonly HaFormSchema[] ] as const satisfies readonly HaFormSchema[]
); );
@ -81,16 +99,17 @@ export class HuiHumidifierModesCardFeatureEditor
? this.hass.states[this.context?.entity_id] ? this.hass.states[this.context?.entity_id]
: undefined; : undefined;
const data: HumidifierModesCardFeatureConfig = { const data: HumidifierModesCardFeatureData = {
style: "dropdown", style: "dropdown",
modes: [],
...this._config, ...this._config,
customize_modes: this._config.modes !== undefined,
}; };
const schema = this._schema( const schema = this._schema(
this.hass.localize, this.hass.localize,
this.hass.formatEntityAttributeValue, this.hass.formatEntityAttributeValue,
stateObj stateObj,
data.customize_modes
); );
return html` return html`
@ -105,7 +124,21 @@ export class HuiHumidifierModesCardFeatureEditor
} }
private _valueChanged(ev: CustomEvent): void { private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value }); const { customize_modes, ...config } = ev.detail
.value as HumidifierModesCardFeatureData;
const stateObj = this.context?.entity_id
? this.hass!.states[this.context?.entity_id]
: undefined;
if (customize_modes && !config.modes) {
config.modes = stateObj?.attributes.available_modes || [];
}
if (!customize_modes && config.modes) {
delete config.modes;
}
fireEvent(this, "config-changed", { config: config });
} }
private _computeLabelCallback = ( private _computeLabelCallback = (
@ -114,6 +147,7 @@ export class HuiHumidifierModesCardFeatureEditor
switch (schema.name) { switch (schema.name) {
case "style": case "style":
case "modes": case "modes":
case "customize_modes":
return this.hass!.localize( return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.humidifier-modes.${schema.name}` `ui.panel.lovelace.editor.features.types.humidifier-modes.${schema.name}`
); );

View File

@ -5,14 +5,22 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import type { FormatEntityStateFunc } from "../../../../common/translations/entity-state"; import type { FormatEntityStateFunc } from "../../../../common/translations/entity-state";
import "../../../../components/ha-form/ha-form"; import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types"; import type {
HaFormSchema,
SchemaUnion,
} from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { import {
WaterHeaterOperationModesCardFeatureConfig, WaterHeaterOperationModesCardFeatureConfig,
LovelaceCardFeatureContext, LovelaceCardFeatureContext,
} from "../../card-features/types"; } from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types"; import type { LovelaceCardFeatureEditor } from "../../types";
import { OPERATION_MODES } from "../../../../data/water_heater"; import { compareWaterHeaterOperationMode } from "../../../../data/water_heater";
type WaterHeaterOperationModesCardFeatureData =
WaterHeaterOperationModesCardFeatureConfig & {
customize_modes: boolean;
};
@customElement("hui-water-heater-operation-modes-card-feature-editor") @customElement("hui-water-heater-operation-modes-card-feature-editor")
export class HuiWaterHeaterOperationModesCardFeatureEditor export class HuiWaterHeaterOperationModesCardFeatureEditor
@ -30,25 +38,41 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor
} }
private _schema = memoizeOne( private _schema = memoizeOne(
(formatEntityState: FormatEntityStateFunc, stateObj?: HassEntity) => (
formatEntityState: FormatEntityStateFunc,
stateObj: HassEntity | undefined,
customizeModes: boolean
) =>
[ [
{ {
name: "operation_modes", name: "customize_modes",
selector: { selector: {
select: { boolean: {},
multiple: true,
reorder: true,
mode: "list",
options: OPERATION_MODES.filter((mode) =>
stateObj?.attributes.operation_list?.includes(mode)
).map((mode) => ({
value: mode,
label: stateObj ? formatEntityState(stateObj, mode) : mode,
})),
},
}, },
}, },
] as const ...(customizeModes
? ([
{
name: "operation_modes",
selector: {
select: {
reorder: true,
multiple: true,
options: (stateObj?.attributes.operation_list || [])
.concat()
.sort(compareWaterHeaterOperationMode)
.map((mode) => ({
value: mode,
label: stateObj
? formatEntityState(stateObj, mode)
: mode,
})),
},
},
},
] as const satisfies readonly HaFormSchema[])
: []),
] as const satisfies readonly HaFormSchema[]
); );
protected render() { protected render() {
@ -60,12 +84,21 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor
? this.hass.states[this.context?.entity_id] ? this.hass.states[this.context?.entity_id]
: undefined; : undefined;
const schema = this._schema(this.hass.formatEntityState, stateObj); const data: WaterHeaterOperationModesCardFeatureData = {
...this._config,
customize_modes: this._config.operation_modes !== undefined,
};
const schema = this._schema(
this.hass.formatEntityState,
stateObj,
data.customize_modes
);
return html` return html`
<ha-form <ha-form
.hass=${this.hass} .hass=${this.hass}
.data=${this._config} .data=${data}
.schema=${schema} .schema=${schema}
.computeLabel=${this._computeLabelCallback} .computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged} @value-changed=${this._valueChanged}
@ -74,7 +107,23 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor
} }
private _valueChanged(ev: CustomEvent): void { private _valueChanged(ev: CustomEvent): void {
fireEvent(this, "config-changed", { config: ev.detail.value }); const { customize_modes, ...config } = ev.detail
.value as WaterHeaterOperationModesCardFeatureData;
const stateObj = this.context?.entity_id
? this.hass!.states[this.context?.entity_id]
: undefined;
if (customize_modes && !config.operation_modes) {
config.operation_modes = (stateObj?.attributes.operation_list || [])
.concat()
.sort(compareWaterHeaterOperationMode);
}
if (!customize_modes && config.operation_modes) {
delete config.operation_modes;
}
fireEvent(this, "config-changed", { config: config });
} }
private _computeLabelCallback = ( private _computeLabelCallback = (
@ -82,13 +131,12 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor
) => { ) => {
switch (schema.name) { switch (schema.name) {
case "operation_modes": case "operation_modes":
case "customize_modes":
return this.hass!.localize( return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.water-heater-modes.${schema.name}` `ui.panel.lovelace.editor.features.types.water-heater-operation-modes.${schema.name}`
); );
default: default:
return this.hass!.localize( return "";
`ui.panel.lovelace.editor.card.generic.${schema.name}`
);
} }
}; };
} }

View File

@ -5996,16 +5996,18 @@
"dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]",
"icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]"
}, },
"customize_modes": "Customize fan modes",
"fan_modes": "Fan modes" "fan_modes": "Fan modes"
}, },
"climate-swing-modes": { "climate-swing-modes": {
"label": "Climate swing modes", "label": "Climate swing modes",
"swing_modes": "Swing modes",
"style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]", "style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]",
"style_list": { "style_list": {
"dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]",
"icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]"
}, },
"swing_modes": "Swing modes" "customize_modes": "Customize swing modes"
}, },
"climate-hvac-modes": { "climate-hvac-modes": {
"label": "Climate HVAC modes", "label": "Climate HVAC modes",
@ -6014,7 +6016,8 @@
"style_list": { "style_list": {
"dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]",
"icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]"
} },
"customize_modes": "Customize HVAC modes"
}, },
"climate-preset-modes": { "climate-preset-modes": {
"label": "Climate preset modes", "label": "Climate preset modes",
@ -6023,6 +6026,7 @@
"dropdown": "Dropdown", "dropdown": "Dropdown",
"icons": "Icons" "icons": "Icons"
}, },
"customize_modes": "Customize preset modes",
"preset_modes": "Preset modes" "preset_modes": "Preset modes"
}, },
"fan-preset-modes": { "fan-preset-modes": {
@ -6032,6 +6036,7 @@
"dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]",
"icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]"
}, },
"customize_modes": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::customize_modes%]",
"preset_modes": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::preset_modes%]" "preset_modes": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::preset_modes%]"
}, },
"humidifier-toggle": { "humidifier-toggle": {
@ -6044,6 +6049,7 @@
"dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]", "dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]",
"icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]" "icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]"
}, },
"customize_modes": "Customize modes",
"modes": "Modes" "modes": "Modes"
}, },
"select-options": { "select-options": {
@ -6065,7 +6071,8 @@
}, },
"water-heater-operation-modes": { "water-heater-operation-modes": {
"label": "Water heater operation modes", "label": "Water heater operation modes",
"operation_modes": "Operation modes" "operation_modes": "Operation modes",
"customize_modes": "Customize operation modes"
}, },
"lawn-mower-commands": { "lawn-mower-commands": {
"label": "Lawn mower commands", "label": "Lawn mower commands",