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:
18
src/common/util/order-properties.ts
Normal file
18
src/common/util/order-properties.ts
Normal 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;
|
||||
}
|
||||
@@ -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 = (
|
||||
|
||||
167
test/common/util/order-properties.test.ts
Normal file
167
test/common/util/order-properties.test.ts
Normal 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("");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user