1
0
mirror of https://github.com/home-assistant/frontend.git synced 2025-12-26 21:56:37 +00:00

Order tile card config according to struct (#27060)

This commit is contained in:
Paul Bottein
2025-09-16 14:13:37 +02:00
committed by GitHub
parent 29b02a3c99
commit bd88b91071
3 changed files with 198 additions and 7 deletions

View File

@@ -0,0 +1,18 @@
/**
* Orders object properties according to a specified key order.
* Properties not in the order array will be placed at the end.
*/
export function orderProperties<T extends Record<string, any>>(
obj: T,
keys: readonly string[]
): T {
const orderedEntries = keys
.filter((key) => key in obj)
.map((key) => [key, obj[key]] as const);
const extraEntries = Object.entries(obj).filter(
([key]) => !keys.includes(key)
);
return Object.fromEntries([...orderedEntries, ...extraEntries]) as T;
}

View File

@@ -17,6 +17,7 @@ import {
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import { fireEvent } from "../../../../common/dom/fire_event";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import { orderProperties } from "../../../../common/util/order-properties";
import "../../../../components/ha-expansion-panel";
import "../../../../components/ha-form/ha-form";
import type {
@@ -44,10 +45,10 @@ const cardConfigStruct = assign(
entity: optional(string()),
name: optional(string()),
icon: optional(string()),
hide_state: optional(boolean()),
state_content: optional(union([string(), array(string())])),
color: optional(string()),
show_entity_picture: optional(boolean()),
hide_state: optional(boolean()),
state_content: optional(union([string(), array(string())])),
vertical: optional(boolean()),
tap_action: optional(actionConfigStruct),
hold_action: optional(actionConfigStruct),
@@ -60,6 +61,8 @@ const cardConfigStruct = assign(
})
);
export const fieldOrder = Object.keys(cardConfigStruct.schema);
@customElement("hui-tile-card-editor")
export class HuiTileCardEditor
extends LitElement
@@ -328,7 +331,7 @@ export class HuiTileCardEditor
const newConfig = ev.detail.value as TileCardConfig;
const config: TileCardConfig = {
let config: TileCardConfig = {
features: this._config.features,
...newConfig,
};
@@ -347,6 +350,8 @@ export class HuiTileCardEditor
delete config.content_layout;
}
config = orderProperties(config, fieldOrder);
fireEvent(this, "config-changed", { config });
}
@@ -388,10 +393,11 @@ export class HuiTileCardEditor
private _updateFeature(index: number, feature: LovelaceCardFeatureConfig) {
const features = this._config!.features!.concat();
features[index] = feature;
const config = { ...this._config!, features };
fireEvent(this, "config-changed", {
config: config,
});
let config = { ...this._config!, features };
config = orderProperties(config, fieldOrder);
fireEvent(this, "config-changed", { config });
}
private _computeLabelCallback = (

View File

@@ -0,0 +1,167 @@
import { describe, it, expect } from "vitest";
import { orderProperties } from "../../../src/common/util/order-properties";
describe("orderProperties", () => {
it("should order properties according to the specified order", () => {
const obj = {
c: "third",
a: "first",
b: "second",
};
const order = ["a", "b", "c"];
const result = orderProperties(obj, order);
expect(Object.keys(result)).toEqual(["a", "b", "c"]);
expect(result).toEqual({
a: "first",
b: "second",
c: "third",
});
});
it("should place properties not in order at the end", () => {
const obj = {
z: "last",
a: "first",
x: "extra",
b: "second",
};
const order = ["a", "b"];
const result = orderProperties(obj, order);
expect(Object.keys(result)).toEqual(["a", "b", "z", "x"]);
expect(result).toEqual({
a: "first",
b: "second",
z: "last",
x: "extra",
});
});
it("should handle empty objects", () => {
const obj = {};
const order = ["a", "b", "c"];
const result = orderProperties(obj, order);
expect(Object.keys(result)).toEqual([]);
expect(result).toEqual({});
});
it("should handle empty order array", () => {
const obj = {
c: "third",
a: "first",
b: "second",
};
const order: string[] = [];
const result = orderProperties(obj, order);
// Should preserve original order when no ordering is specified
expect(Object.keys(result)).toEqual(["c", "a", "b"]);
expect(result).toEqual({
c: "third",
a: "first",
b: "second",
});
});
it("should skip keys in order that don't exist in object", () => {
const obj = {
b: "second",
d: "fourth",
};
const order = ["a", "b", "c", "d"];
const result = orderProperties(obj, order);
expect(Object.keys(result)).toEqual(["b", "d"]);
expect(result).toEqual({
b: "second",
d: "fourth",
});
});
it("should preserve type information", () => {
const obj = {
num: 42,
str: "hello",
bool: true,
arr: [1, 2, 3],
obj: { nested: "value" },
};
const order = ["str", "num", "bool"];
const result = orderProperties(obj, order);
expect(result.num).toBe(42);
expect(result.str).toBe("hello");
expect(result.bool).toBe(true);
expect(result.arr).toEqual([1, 2, 3]);
expect(result.obj).toEqual({ nested: "value" });
});
it("should work with complex card config-like objects", () => {
const config = {
features: ["feature1"],
entity: "sensor.test",
vertical: false,
name: "Test Card",
icon: "mdi:test",
type: "tile",
};
const order = ["type", "entity", "name", "icon", "vertical"];
const result = orderProperties(config, order);
expect(Object.keys(result)).toEqual([
"type",
"entity",
"name",
"icon",
"vertical",
"features", // extra property at the end
]);
expect(result.type).toBe("tile");
expect(result.entity).toBe("sensor.test");
expect(result.features).toEqual(["feature1"]);
});
it("should handle readonly order arrays", () => {
const obj = { c: 3, a: 1, b: 2 };
const order = ["a", "b", "c"] as const;
const result = orderProperties(obj, order);
expect(Object.keys(result)).toEqual(["a", "b", "c"]);
expect(result).toEqual({ a: 1, b: 2, c: 3 });
});
it("should handle objects with undefined and null values", () => {
const obj = {
defined: "value",
nullValue: null,
undefinedValue: undefined,
zero: 0,
emptyString: "",
};
const order = ["nullValue", "defined", "zero"];
const result = orderProperties(obj, order);
expect(Object.keys(result)).toEqual([
"nullValue",
"defined",
"zero",
"undefinedValue",
"emptyString",
]);
expect(result.nullValue).toBeNull();
expect(result.undefinedValue).toBeUndefined();
expect(result.zero).toBe(0);
expect(result.emptyString).toBe("");
});
});