((mode) => ({
+ value: mode,
+ label: this.hass!.formatEntityState(this.stateObj!, mode),
+ path: computeOperationModeIcon(mode as OperationMode),
+ }));
return html`
diff --git a/src/panels/lovelace/card-features/types.ts b/src/panels/lovelace/card-features/types.ts
index 82fa311876..04a0f1df5f 100644
--- a/src/panels/lovelace/card-features/types.ts
+++ b/src/panels/lovelace/card-features/types.ts
@@ -75,6 +75,7 @@ export interface ClimatePresetModesCardFeatureConfig {
export interface SelectOptionsCardFeatureConfig {
type: "select-options";
+ options?: string[];
}
export interface NumericInputCardFeatureConfig {
diff --git a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts
index c4b938230a..288a6976b8 100644
--- a/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-card-features-editor.ts
@@ -21,9 +21,9 @@ import {
import { HomeAssistant } from "../../../../types";
import { supportsAlarmModesCardFeature } from "../../card-features/hui-alarm-modes-card-feature";
import { supportsClimateFanModesCardFeature } from "../../card-features/hui-climate-fan-modes-card-feature";
-import { supportsClimateSwingModesCardFeature } from "../../card-features/hui-climate-swing-modes-card-feature";
import { supportsClimateHvacModesCardFeature } from "../../card-features/hui-climate-hvac-modes-card-feature";
import { supportsClimatePresetModesCardFeature } from "../../card-features/hui-climate-preset-modes-card-feature";
+import { supportsClimateSwingModesCardFeature } from "../../card-features/hui-climate-swing-modes-card-feature";
import { supportsCoverOpenCloseCardFeature } from "../../card-features/hui-cover-open-close-card-feature";
import { supportsCoverPositionCardFeature } from "../../card-features/hui-cover-position-card-feature";
import { supportsCoverTiltCardFeature } from "../../card-features/hui-cover-tilt-card-feature";
@@ -53,13 +53,13 @@ type SupportsFeature = (stateObj: HassEntity) => boolean;
const UI_FEATURE_TYPES = [
"alarm-modes",
"climate-fan-modes",
- "climate-swing-modes",
"climate-hvac-modes",
"climate-preset-modes",
+ "climate-swing-modes",
"cover-open-close",
"cover-position",
- "cover-tilt",
"cover-tilt-position",
+ "cover-tilt",
"fan-preset-modes",
"fan-speed",
"humidifier-modes",
@@ -82,14 +82,15 @@ type UiFeatureTypes = (typeof UI_FEATURE_TYPES)[number];
const EDITABLES_FEATURE_TYPES = new Set([
"alarm-modes",
- "climate-hvac-modes",
"climate-fan-modes",
- "climate-swing-modes",
+ "climate-hvac-modes",
"climate-preset-modes",
+ "climate-swing-modes",
"fan-preset-modes",
"humidifier-modes",
"lawn-mower-commands",
"numeric-input",
+ "select-options",
"update-actions",
"vacuum-commands",
"water-heater-operation-modes",
diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts
index 3316c26b5c..85c5059135 100644
--- a/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-climate-fan-modes-card-feature-editor.ts
@@ -17,6 +17,10 @@ import {
} from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types";
+type ClimateFanModesCardFeatureData = ClimateFanModesCardFeatureConfig & {
+ customize_modes: boolean;
+};
+
@customElement("hui-climate-fan-modes-card-feature-editor")
export class HuiClimateFanModesCardFeatureEditor
extends LitElement
@@ -36,7 +40,8 @@ export class HuiClimateFanModesCardFeatureEditor
(
localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc,
- stateObj?: HassEntity
+ stateObj: HassEntity | undefined,
+ customizeModes: boolean
) =>
[
{
@@ -55,19 +60,33 @@ export class HuiClimateFanModesCardFeatureEditor
},
},
{
- name: "fan_modes",
+ name: "customize_modes",
selector: {
- select: {
- multiple: true,
- mode: "list",
- options:
- stateObj?.attributes.fan_modes?.map((mode) => ({
- value: mode,
- label: formatEntityAttributeValue(stateObj, "fan_mode", mode),
- })) || [],
- },
+ boolean: {},
},
},
+ ...(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[]
);
@@ -80,16 +99,17 @@ export class HuiClimateFanModesCardFeatureEditor
? this.hass.states[this.context?.entity_id]
: undefined;
- const data: ClimateFanModesCardFeatureConfig = {
+ const data: ClimateFanModesCardFeatureData = {
style: "dropdown",
- fan_modes: [],
...this._config,
+ customize_modes: this._config.fan_modes !== undefined,
};
const schema = this._schema(
this.hass.localize,
this.hass.formatEntityAttributeValue,
- stateObj
+ stateObj,
+ data.customize_modes
);
return html`
@@ -104,7 +124,21 @@ export class HuiClimateFanModesCardFeatureEditor
}
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 = (
@@ -113,6 +147,7 @@ export class HuiClimateFanModesCardFeatureEditor
switch (schema.name) {
case "style":
case "fan_modes":
+ case "customize_modes":
return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.climate-fan-modes.${schema.name}`
);
diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts
index 228a43740d..87663228aa 100644
--- a/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-climate-hvac-modes-card-feature-editor.ts
@@ -6,8 +6,11 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import type { FormatEntityStateFunc } from "../../../../common/translations/entity-state";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-form/ha-form";
-import type { SchemaUnion } from "../../../../components/ha-form/types";
-import { HVAC_MODES } from "../../../../data/climate";
+import type {
+ HaFormSchema,
+ SchemaUnion,
+} from "../../../../components/ha-form/types";
+import { compareClimateHvacModes } from "../../../../data/climate";
import type { HomeAssistant } from "../../../../types";
import {
ClimateHvacModesCardFeatureConfig,
@@ -15,6 +18,10 @@ import {
} from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types";
+type ClimateHvacModesCardFeatureData = ClimateHvacModesCardFeatureConfig & {
+ customize_modes: boolean;
+};
+
@customElement("hui-climate-hvac-modes-card-feature-editor")
export class HuiClimateHvacModesCardFeatureEditor
extends LitElement
@@ -34,7 +41,8 @@ export class HuiClimateHvacModesCardFeatureEditor
(
localize: LocalizeFunc,
formatEntityState: FormatEntityStateFunc,
- stateObj?: HassEntity
+ stateObj: HassEntity | undefined,
+ customizeModes: boolean
) =>
[
{
@@ -53,21 +61,34 @@ export class HuiClimateHvacModesCardFeatureEditor
},
},
{
- name: "hvac_modes",
+ name: "customize_modes",
selector: {
- select: {
- multiple: true,
- mode: "list",
- options: HVAC_MODES.filter((mode) =>
- stateObj?.attributes.hvac_modes?.includes(mode)
- ).map((mode) => ({
- value: mode,
- label: stateObj ? formatEntityState(stateObj, mode) : mode,
- })),
- },
+ boolean: {},
},
},
- ] 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() {
@@ -79,16 +100,17 @@ export class HuiClimateHvacModesCardFeatureEditor
? this.hass.states[this.context?.entity_id]
: undefined;
- const data: ClimateHvacModesCardFeatureConfig = {
+ const data: ClimateHvacModesCardFeatureData = {
style: "icons",
- hvac_modes: [],
...this._config,
+ customize_modes: this._config.hvac_modes !== undefined,
};
const schema = this._schema(
this.hass.localize,
this.hass.formatEntityState,
- stateObj
+ stateObj,
+ data.customize_modes
);
return html`
@@ -103,7 +125,24 @@ export class HuiClimateHvacModesCardFeatureEditor
}
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 = (
@@ -112,6 +151,7 @@ export class HuiClimateHvacModesCardFeatureEditor
switch (schema.name) {
case "hvac_modes":
case "style":
+ case "customize_modes":
return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.climate-hvac-modes.${schema.name}`
);
diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts
index 52955c1627..344fc4da84 100644
--- a/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-climate-preset-modes-card-feature-editor.ts
@@ -17,6 +17,10 @@ import {
} from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types";
+type ClimatePresetModesCardFeatureData = ClimatePresetModesCardFeatureConfig & {
+ customize_modes: boolean;
+};
+
@customElement("hui-climate-preset-modes-card-feature-editor")
export class HuiClimatePresetModesCardFeatureEditor
extends LitElement
@@ -36,7 +40,8 @@ export class HuiClimatePresetModesCardFeatureEditor
(
localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc,
- stateObj?: HassEntity
+ stateObj: HassEntity | undefined,
+ customizeModes: boolean
) =>
[
{
@@ -55,23 +60,33 @@ export class HuiClimatePresetModesCardFeatureEditor
},
},
{
- name: "preset_modes",
+ name: "customize_modes",
selector: {
- select: {
- multiple: true,
- mode: "list",
- options:
- stateObj?.attributes.preset_modes?.map((mode) => ({
- value: mode,
- label: formatEntityAttributeValue(
- stateObj,
- "preset_mode",
- mode
- ),
- })) || [],
- },
+ boolean: {},
},
},
+ ...(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[]
);
@@ -84,16 +99,17 @@ export class HuiClimatePresetModesCardFeatureEditor
? this.hass.states[this.context?.entity_id]
: undefined;
- const data: ClimatePresetModesCardFeatureConfig = {
+ const data: ClimatePresetModesCardFeatureData = {
style: "dropdown",
- preset_modes: [],
...this._config,
+ customize_modes: this._config.preset_modes !== undefined,
};
const schema = this._schema(
this.hass.localize,
this.hass.formatEntityAttributeValue,
- stateObj
+ stateObj,
+ data.customize_modes
);
return html`
@@ -108,7 +124,21 @@ export class HuiClimatePresetModesCardFeatureEditor
}
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 = (
@@ -117,6 +147,7 @@ export class HuiClimatePresetModesCardFeatureEditor
switch (schema.name) {
case "style":
case "preset_modes":
+ case "customize_modes":
return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.climate-preset-modes.${schema.name}`
);
diff --git a/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts
index 812c5c03cb..a8c4f4c09c 100644
--- a/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-climate-swing-modes-card-feature-editor.ts
@@ -17,6 +17,10 @@ import {
} from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types";
+type ClimateSwingModesCardFeatureData = ClimateSwingModesCardFeatureConfig & {
+ customize_modes: boolean;
+};
+
@customElement("hui-climate-swing-modes-card-feature-editor")
export class HuiClimateSwingModesCardFeatureEditor
extends LitElement
@@ -36,7 +40,8 @@ export class HuiClimateSwingModesCardFeatureEditor
(
localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc,
- stateObj?: HassEntity
+ stateObj: HassEntity | undefined,
+ customizeModes: boolean
) =>
[
{
@@ -55,23 +60,33 @@ export class HuiClimateSwingModesCardFeatureEditor
},
},
{
- name: "swing_modes",
+ name: "customize_modes",
selector: {
- select: {
- multiple: true,
- mode: "list",
- options:
- stateObj?.attributes.swing_modes?.map((mode) => ({
- value: mode,
- label: formatEntityAttributeValue(
- stateObj,
- "swing_mode",
- mode
- ),
- })) || [],
- },
+ boolean: {},
},
},
+ ...(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[]
);
@@ -84,16 +99,17 @@ export class HuiClimateSwingModesCardFeatureEditor
? this.hass.states[this.context?.entity_id]
: undefined;
- const data: ClimateSwingModesCardFeatureConfig = {
+ const data: ClimateSwingModesCardFeatureData = {
style: "dropdown",
- swing_modes: [],
...this._config,
+ customize_modes: this._config.swing_modes !== undefined,
};
const schema = this._schema(
this.hass.localize,
this.hass.formatEntityAttributeValue,
- stateObj
+ stateObj,
+ data.customize_modes
);
return html`
@@ -108,7 +124,21 @@ export class HuiClimateSwingModesCardFeatureEditor
}
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 = (
@@ -117,6 +147,7 @@ export class HuiClimateSwingModesCardFeatureEditor
switch (schema.name) {
case "style":
case "swing_modes":
+ case "customize_modes":
return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.climate-swing-modes.${schema.name}`
);
diff --git a/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts
index 35cfd7b8a6..acc3cc1831 100644
--- a/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-fan-preset-modes-card-feature-editor.ts
@@ -17,6 +17,10 @@ import {
} from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types";
+type FanPresetModesCardFeatureData = FanPresetModesCardFeatureConfig & {
+ customize_modes: boolean;
+};
+
@customElement("hui-fan-preset-modes-card-feature-editor")
export class HuiFanPresetModesCardFeatureEditor
extends LitElement
@@ -36,7 +40,8 @@ export class HuiFanPresetModesCardFeatureEditor
(
localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc,
- stateObj?: HassEntity
+ stateObj: HassEntity | undefined,
+ customizeModes: boolean
) =>
[
{
@@ -55,23 +60,33 @@ export class HuiFanPresetModesCardFeatureEditor
},
},
{
- name: "preset_modes",
+ name: "customize_modes",
selector: {
- select: {
- multiple: true,
- mode: "list",
- options:
- stateObj?.attributes.preset_modes?.map((mode) => ({
- value: mode,
- label: formatEntityAttributeValue(
- stateObj,
- "preset_mode",
- mode
- ),
- })) || [],
- },
+ boolean: {},
},
},
+ ...(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[]
);
@@ -84,16 +99,17 @@ export class HuiFanPresetModesCardFeatureEditor
? this.hass.states[this.context?.entity_id]
: undefined;
- const data: FanPresetModesCardFeatureConfig = {
+ const data: FanPresetModesCardFeatureData = {
style: "dropdown",
- preset_modes: [],
...this._config,
+ customize_modes: this._config.preset_modes !== undefined,
};
const schema = this._schema(
this.hass.localize,
this.hass.formatEntityAttributeValue,
- stateObj
+ stateObj,
+ data.customize_modes
);
return html`
@@ -108,7 +124,21 @@ export class HuiFanPresetModesCardFeatureEditor
}
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 = (
@@ -117,6 +147,7 @@ export class HuiFanPresetModesCardFeatureEditor
switch (schema.name) {
case "style":
case "preset_modes":
+ case "customize_modes":
return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.fan-preset-modes.${schema.name}`
);
diff --git a/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts
index a462f19e49..92e6f778bb 100644
--- a/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-humidifier-modes-card-feature-editor.ts
@@ -17,6 +17,10 @@ import {
} from "../../card-features/types";
import type { LovelaceCardFeatureEditor } from "../../types";
+type HumidifierModesCardFeatureData = HumidifierModesCardFeatureConfig & {
+ customize_modes: boolean;
+};
+
@customElement("hui-humidifier-modes-card-feature-editor")
export class HuiHumidifierModesCardFeatureEditor
extends LitElement
@@ -36,7 +40,8 @@ export class HuiHumidifierModesCardFeatureEditor
(
localize: LocalizeFunc,
formatEntityAttributeValue: FormatEntityAttributeValueFunc,
- stateObj?: HassEntity
+ stateObj: HassEntity | undefined,
+ customizeModes: boolean
) =>
[
{
@@ -55,19 +60,33 @@ export class HuiHumidifierModesCardFeatureEditor
},
},
{
- name: "modes",
+ name: "customize_modes",
selector: {
- select: {
- multiple: true,
- mode: "list",
- options:
- stateObj?.attributes.available_modes?.map((mode) => ({
- value: mode,
- label: formatEntityAttributeValue(stateObj, "mode", mode),
- })) || [],
- },
+ boolean: {},
},
},
+ ...(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[]
);
@@ -80,16 +99,17 @@ export class HuiHumidifierModesCardFeatureEditor
? this.hass.states[this.context?.entity_id]
: undefined;
- const data: HumidifierModesCardFeatureConfig = {
+ const data: HumidifierModesCardFeatureData = {
style: "dropdown",
- modes: [],
...this._config,
+ customize_modes: this._config.modes !== undefined,
};
const schema = this._schema(
this.hass.localize,
this.hass.formatEntityAttributeValue,
- stateObj
+ stateObj,
+ data.customize_modes
);
return html`
@@ -104,7 +124,21 @@ export class HuiHumidifierModesCardFeatureEditor
}
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 = (
@@ -113,6 +147,7 @@ export class HuiHumidifierModesCardFeatureEditor
switch (schema.name) {
case "style":
case "modes":
+ case "customize_modes":
return this.hass!.localize(
`ui.panel.lovelace.editor.features.types.humidifier-modes.${schema.name}`
);
diff --git a/src/panels/lovelace/editor/config-elements/hui-select-options-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-select-options-card-feature-editor.ts
new file mode 100644
index 0000000000..65b4dc6faf
--- /dev/null
+++ b/src/panels/lovelace/editor/config-elements/hui-select-options-card-feature-editor.ts
@@ -0,0 +1,140 @@
+import { HassEntity } from "home-assistant-js-websocket";
+import { html, LitElement, nothing } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import memoizeOne from "memoize-one";
+import { fireEvent } from "../../../../common/dom/fire_event";
+import { FormatEntityStateFunc } from "../../../../common/translations/entity-state";
+import "../../../../components/ha-form/ha-form";
+import type {
+ HaFormSchema,
+ SchemaUnion,
+} from "../../../../components/ha-form/types";
+import type { HomeAssistant } from "../../../../types";
+import {
+ LovelaceCardFeatureContext,
+ SelectOptionsCardFeatureConfig,
+} from "../../card-features/types";
+import type { LovelaceCardFeatureEditor } from "../../types";
+
+type SelectOptionsCardFeatureData = SelectOptionsCardFeatureConfig & {
+ customize_options: boolean;
+};
+
+@customElement("hui-select-options-card-feature-editor")
+export class HuiSelectOptionsCardFeatureEditor
+ extends LitElement
+ implements LovelaceCardFeatureEditor
+{
+ @property({ attribute: false }) public hass?: HomeAssistant;
+
+ @property({ attribute: false }) public context?: LovelaceCardFeatureContext;
+
+ @state() private _config?: SelectOptionsCardFeatureConfig;
+
+ public setConfig(config: SelectOptionsCardFeatureConfig): void {
+ this._config = config;
+ }
+
+ private _schema = memoizeOne(
+ (
+ formatEntityState: FormatEntityStateFunc,
+ stateObj: HassEntity | undefined,
+ customizeOptions: boolean
+ ) =>
+ [
+ {
+ name: "customize_options",
+ selector: {
+ boolean: {},
+ },
+ },
+ ...(customizeOptions
+ ? ([
+ {
+ name: "options",
+ selector: {
+ select: {
+ multiple: true,
+ reorder: true,
+ options:
+ stateObj?.attributes.options?.map((option) => ({
+ value: option,
+ label: formatEntityState(stateObj, option),
+ })) || [],
+ },
+ },
+ },
+ ] as const satisfies readonly HaFormSchema[])
+ : []),
+ ] as const satisfies readonly HaFormSchema[]
+ );
+
+ protected render() {
+ if (!this.hass || !this._config) {
+ return nothing;
+ }
+
+ const stateObj = this.context?.entity_id
+ ? this.hass.states[this.context?.entity_id]
+ : undefined;
+
+ const data: SelectOptionsCardFeatureData = {
+ ...this._config,
+ customize_options: this._config.options !== undefined,
+ };
+
+ const schema = this._schema(
+ this.hass.formatEntityState,
+ stateObj,
+ data.customize_options
+ );
+
+ return html`
+
+ `;
+ }
+
+ private _valueChanged(ev: CustomEvent): void {
+ const { customize_options, ...config } = ev.detail
+ .value as SelectOptionsCardFeatureData;
+
+ const stateObj = this.context?.entity_id
+ ? this.hass!.states[this.context?.entity_id]
+ : undefined;
+
+ if (customize_options && !config.options) {
+ config.options = stateObj?.attributes.options || [];
+ }
+ if (!customize_options && config.options) {
+ delete config.options;
+ }
+
+ fireEvent(this, "config-changed", { config: config });
+ }
+
+ private _computeLabelCallback = (
+ schema: SchemaUnion>
+ ) => {
+ switch (schema.name) {
+ case "options":
+ case "customize_options":
+ return this.hass!.localize(
+ `ui.panel.lovelace.editor.features.types.select-options.${schema.name}`
+ );
+ default:
+ return "";
+ }
+ };
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-select-options-card-feature-editor": HuiSelectOptionsCardFeatureEditor;
+ }
+}
diff --git a/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts
index 6e1a5f43ab..aad60f9034 100644
--- a/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-water-heater-operation-modes-card-feature-editor.ts
@@ -5,14 +5,22 @@ import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { FormatEntityStateFunc } from "../../../../common/translations/entity-state";
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 {
WaterHeaterOperationModesCardFeatureConfig,
LovelaceCardFeatureContext,
} from "../../card-features/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")
export class HuiWaterHeaterOperationModesCardFeatureEditor
@@ -30,24 +38,41 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor
}
private _schema = memoizeOne(
- (formatEntityState: FormatEntityStateFunc, stateObj?: HassEntity) =>
+ (
+ formatEntityState: FormatEntityStateFunc,
+ stateObj: HassEntity | undefined,
+ customizeModes: boolean
+ ) =>
[
{
- name: "operation_modes",
+ name: "customize_modes",
selector: {
- select: {
- multiple: true,
- mode: "list",
- options: OPERATION_MODES.filter((mode) =>
- stateObj?.attributes.operation_list?.includes(mode)
- ).map((mode) => ({
- value: mode,
- label: stateObj ? formatEntityState(stateObj, mode) : mode,
- })),
- },
+ boolean: {},
},
},
- ] 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() {
@@ -59,12 +84,21 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor
? this.hass.states[this.context?.entity_id]
: 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`
{
switch (schema.name) {
case "operation_modes":
+ case "customize_modes":
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:
- return this.hass!.localize(
- `ui.panel.lovelace.editor.card.generic.${schema.name}`
- );
+ return "";
}
};
}
diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts
index 6226897809..e95cdf1861 100644
--- a/src/panels/lovelace/hui-root.ts
+++ b/src/panels/lovelace/hui-root.ts
@@ -1013,8 +1013,6 @@ class HUIRoot extends LitElement {
padding-inline-start: env(safe-area-inset-left);
padding-inline-end: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
- }
- hui-view {
background: var(
--lovelace-background,
var(--primary-background-color)
diff --git a/src/translations/en.json b/src/translations/en.json
index e6c7dadff1..3bde569c1b 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -1889,6 +1889,7 @@
"check_updates": "Check for updates",
"no_new_updates": "No new updates found",
"updates_refreshed": "{count} {count, plural,\n one {update}\n other {updates}\n} refreshed",
+ "checking_updates": "Checking for updates...",
"title": "{count} {count, plural,\n one {update}\n other {updates}\n}",
"unable_to_fetch": "Unable to load updates",
"more_updates": "Show all updates",
@@ -4061,12 +4062,14 @@
"search": "Search {number} entities",
"unnamed_entity": "Unnamed entity",
"status": {
- "restored": "Restored",
"available": "Available",
"unavailable": "Unavailable",
+ "enabled": "Enabled",
"disabled": "Disabled",
- "readonly": "Read-only",
- "hidden": "Hidden"
+ "visible": "Visible",
+ "hidden": "Hidden",
+ "not_provided": "Not provided",
+ "unmanageable": "Unmanageable"
},
"headers": {
"state_icon": "State icon",
@@ -4076,7 +4079,10 @@
"area": "Area",
"disabled_by": "Disabled by",
"status": "Status",
- "domain": "Domain"
+ "domain": "Domain",
+ "availability": "Availability",
+ "visibility": "Visibility",
+ "enabled": "Enabled"
},
"selected": "{number} selected",
"enable_selected": {
@@ -5996,16 +6002,18 @@
"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%]"
},
+ "customize_modes": "Customize fan modes",
"fan_modes": "Fan modes"
},
"climate-swing-modes": {
"label": "Climate swing modes",
+ "swing_modes": "Swing modes",
"style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]",
"style_list": {
"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%]"
},
- "swing_modes": "Swing modes"
+ "customize_modes": "Customize swing modes"
},
"climate-hvac-modes": {
"label": "Climate HVAC modes",
@@ -6014,7 +6022,8 @@
"style_list": {
"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%]"
- }
+ },
+ "customize_modes": "Customize HVAC modes"
},
"climate-preset-modes": {
"label": "Climate preset modes",
@@ -6023,6 +6032,7 @@
"dropdown": "Dropdown",
"icons": "Icons"
},
+ "customize_modes": "Customize preset modes",
"preset_modes": "Preset modes"
},
"fan-preset-modes": {
@@ -6032,6 +6042,7 @@
"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%]"
},
+ "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%]"
},
"humidifier-toggle": {
@@ -6044,10 +6055,13 @@
"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%]"
},
+ "customize_modes": "Customize modes",
"modes": "Modes"
},
"select-options": {
- "label": "Select options"
+ "label": "Select options",
+ "options": "Options",
+ "customize_options": "Customize options"
},
"numeric-input": {
"label": "Numeric input",
@@ -6065,7 +6079,8 @@
},
"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": {
"label": "Lawn mower commands",