Compare commits

...

9 Commits

Author SHA1 Message Date
Aidan Timson bd7c9f9bb5 Only show overflow item when expansion panel is not present 2026-02-24 09:28:43 +00:00
Aidan Timson ca75ba8334 Remove "extra" from naming 2026-02-24 08:30:23 +00:00
Aidan Timson 1072093dd7 Use more accurate string for "extra attributes" as this is not all 2026-02-18 10:29:36 +00:00
Aidan Timson 434796d437 Spread 2026-02-18 10:20:59 +00:00
Aidan Timson 03f90139a1 Use map for extra-filters attr 2026-02-18 10:17:09 +00:00
Aidan Timson 0b47837416 Show as all attributes when an accordion is showing 2026-02-18 09:59:57 +00:00
Aidan Timson 7c05016498 Show attributes option when there are more to show 2026-02-18 09:55:05 +00:00
Aidan Timson 11459eb484 Update visual style of accordion to view 2026-02-18 09:41:57 +00:00
Aidan Timson 7623064e4d Restore more info attributes accordion 2026-02-18 09:39:33 +00:00
16 changed files with 280 additions and 18 deletions
+14 -7
View File
@@ -107,31 +107,38 @@ class HaAttributes extends LitElement {
haStyle,
css`
.attribute-container {
margin-bottom: 8px;
direction: ltr;
padding: var(--ha-space-2) var(--ha-space-4);
}
.data-entry {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: var(--ha-space-2) 0;
border-bottom: 1px solid var(--divider-color);
}
.data-entry:last-of-type {
border-bottom: none;
}
.data-entry .value {
max-width: 60%;
overflow-wrap: break-word;
text-align: right;
}
.key {
flex-grow: 1;
color: var(--secondary-text-color);
}
.attribution {
color: var(--secondary-text-color);
text-align: center;
margin-top: 16px;
}
hr {
border-color: var(--divider-color);
border-bottom: none;
margin: 16px 0;
margin-top: var(--ha-space-4);
font-size: var(--ha-font-size-s);
}
`,
];
+70
View File
@@ -190,6 +190,46 @@ export const NON_NUMERIC_ATTRIBUTES = [
"xy_color",
];
export const MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS = {
default: [],
cover: ["current_position", "current_tilt_position"],
input_boolean: [],
light: [
"brightness",
"color_temp",
"color_temp_kelvin",
"white_value",
"effect_list",
"effect",
"hs_color",
"rgb_color",
"rgbw_color",
"rgbww_color",
"xy_color",
"min_mireds",
"max_mireds",
"min_color_temp_kelvin",
"max_color_temp_kelvin",
"entity_id",
"supported_color_modes",
"color_mode",
],
lock: ["code_format"],
person: ["id", "user_id", "editable", "device_trackers"],
remote: ["activity_list", "current_activity"],
siren: [],
switch: [],
timer: ["remaining", "restore"],
vacuum: [
"fan_speed",
"fan_speed_list",
"status",
"battery_level",
"battery_icon",
],
valve: ["current_position", "current_tilt_position"],
} as const satisfies Record<string, readonly string[]>;
export const computeShownAttributes = (stateObj: HassEntity) => {
const domain = computeStateDomain(stateObj);
const filtersArray = STATE_ATTRIBUTES.concat(
@@ -201,3 +241,33 @@ export const computeShownAttributes = (stateObj: HassEntity) => {
(key) => filtersArray.indexOf(key) === -1
);
};
export const computeMainViewShownAttributes = (
stateObj: HassEntity,
moreInfoType: string
) => {
const mainViewExtraFilters =
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS[moreInfoType];
if (!mainViewExtraFilters) {
return [];
}
return computeShownAttributes(stateObj).filter(
(attribute) => mainViewExtraFilters.indexOf(attribute) === -1
);
};
export const computeAdditionalMoreInfoAttributes = (
stateObj: HassEntity,
moreInfoType: string
) => {
const shownAttributes = computeShownAttributes(stateObj);
const mainViewAttributes = new Set(
computeMainViewShownAttributes(stateObj, moreInfoType)
);
return shownAttributes.filter(
(attribute) => !mainViewAttributes.has(attribute)
);
};
@@ -3,9 +3,11 @@ import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import "../../../components/ha-icon-button-group";
import "../../../components/ha-icon-button-toggle";
import type { CoverEntity } from "../../../data/cover";
import { MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS } from "../../../data/entity/entity_attributes";
import {
CoverEntityFeature,
computeCoverPositionStateDisplay,
@@ -20,6 +22,9 @@ import { moreInfoControlStyle } from "../components/more-info-control-style";
type Mode = "position" | "button";
const EXTRA_FILTERS =
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS.cover.join(",");
@customElement("more-info-cover")
class MoreInfoCover extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -175,6 +180,11 @@ class MoreInfoCover extends LitElement {
}
</div>
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
.extraFilters=${EXTRA_FILTERS}
></ha-attributes>
`;
}
@@ -1,6 +1,7 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { LitElement, nothing } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-attributes";
import type { HomeAssistant } from "../../../types";
@customElement("more-info-default")
@@ -14,7 +15,10 @@ class MoreInfoDefault extends LitElement {
return nothing;
}
return nothing;
return html`<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>`;
}
}
@@ -3,6 +3,7 @@ import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { LitElement, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-attributes";
import "../../../state-control/ha-state-control-toggle";
import type { HomeAssistant } from "../../../types";
import "../components/ha-more-info-state-header";
@@ -32,6 +33,10 @@ class MoreInfoInputBoolean extends LitElement {
.iconPathOff=${mdiPowerOff}
></ha-state-control-toggle>
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
`;
}
@@ -11,11 +11,13 @@ import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-attributes";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-icon-button-group";
import "../../../components/ha-icon-button-toggle";
import "../../../components/ha-list-item";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS } from "../../../data/entity/entity_attributes";
import type { ExtEntityRegistryEntry } from "../../../data/entity/entity_registry";
import { forwardHaptic } from "../../../data/haptics";
import type { LightEntity } from "../../../data/light";
@@ -41,6 +43,9 @@ import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
type MainControl = "brightness" | "color_temp" | "color";
const EXTRA_FILTERS =
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS.light.join(",");
@customElement("more-info-light")
class MoreInfoLight extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -286,6 +291,11 @@ class MoreInfoLight extends LitElement {
`
: nothing}
</ha-more-info-control-select-container>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
.extraFilters=${EXTRA_FILTERS}
></ha-attributes>
</div>
`;
}
@@ -5,10 +5,12 @@ import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-outlined-icon-button";
import "../../../components/ha-state-icon";
import { MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS } from "../../../data/entity/entity_attributes";
import type { LockEntity } from "../../../data/lock";
import {
LockEntityFeature,
@@ -26,6 +28,9 @@ const DONE_TIMEOUT_SECOND = 2;
type ButtonState = "normal" | "confirm" | "done";
const EXTRA_FILTERS =
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS.lock.join(",");
@customElement("more-info-lock")
class MoreInfoLock extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -150,6 +155,11 @@ class MoreInfoLock extends LitElement {
</ha-control-button-group>
`
: nothing}
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
.extraFilters=${EXTRA_FILTERS}
></ha-attributes>
</div>
`;
}
@@ -185,6 +195,9 @@ class MoreInfoLock extends LitElement {
max-width: 400px;
margin: 0 auto;
}
ha-control-button-group + ha-attributes:not([empty]) {
margin-top: var(--ha-space-4);
}
@keyframes pulse {
0% {
opacity: 1;
@@ -3,11 +3,16 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-attributes";
import "../../../components/ha-button";
import "../../../components/map/ha-map";
import { MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS } from "../../../data/entity/entity_attributes";
import { showZoneEditor } from "../../../data/zone";
import type { HomeAssistant } from "../../../types";
const EXTRA_FILTERS =
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS.person.join(",");
@customElement("more-info-person")
class MoreInfoPerson extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -49,6 +54,11 @@ class MoreInfoPerson extends LitElement {
</div>
`
: ""}
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
.extraFilters=${EXTRA_FILTERS}
></ha-attributes>
`;
}
@@ -1,12 +1,17 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import type { HaSelectSelectEvent } from "../../../components/ha-select";
import "../../../components/ha-select";
import { MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS } from "../../../data/entity/entity_attributes";
import type { RemoteEntity } from "../../../data/remote";
import { REMOTE_SUPPORT_ACTIVITY } from "../../../data/remote";
import type { HomeAssistant } from "../../../types";
const EXTRA_FILTERS =
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS.remote.join(",");
@customElement("more-info-remote")
class MoreInfoRemote extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -41,6 +46,12 @@ class MoreInfoRemote extends LitElement {
</ha-select>
`
: nothing}
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
.extraFilters=${EXTRA_FILTERS}
></ha-attributes>
`;
}
@@ -3,6 +3,7 @@ import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { LitElement, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-attributes";
import "../../../state-control/ha-state-control-toggle";
import "../../../components/ha-button";
import type { HomeAssistant } from "../../../types";
@@ -59,6 +60,10 @@ class MoreInfoSiren extends LitElement {
</ha-button>`
: nothing}
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
`;
}
@@ -3,6 +3,7 @@ import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { LitElement, html, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-attributes";
import "../../../state-control/ha-state-control-toggle";
import type { HomeAssistant } from "../../../types";
import "../components/ha-more-info-state-header";
@@ -32,6 +33,10 @@ class MoreInfoSwitch extends LitElement {
.iconPathOff=${mdiPowerOff}
></ha-state-control-toggle>
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
></ha-attributes>
`;
}
@@ -1,9 +1,14 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-attributes";
import "../../../components/ha-button";
import { MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS } from "../../../data/entity/entity_attributes";
import type { TimerEntity } from "../../../data/timer";
import type { HomeAssistant } from "../../../types";
const EXTRA_FILTERS =
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS.timer.join(",");
@customElement("more-info-timer")
class MoreInfoTimer extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -62,6 +67,11 @@ class MoreInfoTimer extends LitElement {
`
: ""}
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
.extraFilters=${EXTRA_FILTERS}
></ha-attributes>
`;
}
@@ -14,11 +14,13 @@ import memoizeOne from "memoize-one";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/entity/ha-battery-icon";
import "../../../components/ha-attributes";
import type { HaSelectSelectEvent } from "../../../components/ha-select";
import "../../../components/ha-icon";
import "../../../components/ha-icon-button";
import "../../../components/ha-select";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS } from "../../../data/entity/entity_attributes";
import type { EntityRegistryDisplayEntry } from "../../../data/entity/entity_registry";
import {
findBatteryChargingEntity,
@@ -95,6 +97,9 @@ const VACUUM_COMMANDS: VacuumCommand[] = [
},
];
const EXTRA_FILTERS =
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS.vacuum.join(",");
@customElement("more-info-vacuum")
class MoreInfoVacuum extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -199,6 +204,12 @@ class MoreInfoVacuum extends LitElement {
</div>
`
: ""}
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
.extraFilters=${EXTRA_FILTERS}
></ha-attributes>
`;
}
@@ -3,8 +3,10 @@ import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import "../../../components/ha-icon-button-group";
import "../../../components/ha-icon-button-toggle";
import { MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS } from "../../../data/entity/entity_attributes";
import type { ValveEntity } from "../../../data/valve";
import {
ValveEntityFeature,
@@ -19,6 +21,9 @@ import { moreInfoControlStyle } from "../components/more-info-control-style";
type Mode = "position" | "button";
const EXTRA_FILTERS =
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS.valve.join(",");
@customElement("more-info-valve")
class MoreInfoValve extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -154,6 +159,11 @@ class MoreInfoValve extends LitElement {
}
</div>
</div>
<ha-attributes
.hass=${this.hass}
.stateObj=${this.stateObj}
.extraFilters=${EXTRA_FILTERS}
></ha-attributes>
`;
}
+34 -8
View File
@@ -44,7 +44,10 @@ import "../../components/ha-dropdown-item";
import "../../components/ha-icon-button";
import "../../components/ha-icon-button-prev";
import "../../components/ha-related-items";
import { computeShownAttributes } from "../../data/entity/entity_attributes";
import {
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS,
computeShownAttributes,
} from "../../data/entity/entity_attributes";
import type {
EntityRegistryEntry,
ExtEntityRegistryEntry,
@@ -75,6 +78,7 @@ import "./ha-more-info-history-and-logbook";
import "./ha-more-info-info";
import "./ha-more-info-settings";
import "./more-info-content";
import { stateMoreInfoType } from "./state_more_info_control";
export interface MoreInfoDialogParams {
entityId: string | null;
@@ -358,15 +362,36 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
};
}
private _hasDisplayableAttributes(): boolean {
if (!this._entityId) {
return false;
}
const stateObj = this.hass.states[this._entityId];
private _computeAttributesMenuData(
stateObj: HassEntity | undefined
): boolean {
if (!stateObj) {
return false;
}
return computeShownAttributes(stateObj).length > 0;
const shownAttributes = computeShownAttributes(stateObj);
if (shownAttributes.length === 0) {
return false;
}
const moreInfoType = stateMoreInfoType(stateObj);
const hasMainViewAttributesSection = Object.prototype.hasOwnProperty.call(
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS,
moreInfoType
);
if (!hasMainViewAttributesSection) {
return true;
}
const mainViewExtraFilters =
MORE_INFO_MAIN_VIEW_EXTRA_ATTRIBUTE_FILTERS[moreInfoType] || [];
const hasVisibleMainViewAttributes = shownAttributes.some(
(attribute) => mainViewExtraFilters.indexOf(attribute) === -1
);
return !hasVisibleMainViewAttributes;
}
private _goToAddEntityTo(ev) {
@@ -394,6 +419,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
const stateObj = this.hass.states[entityId] as HassEntity | undefined;
const domain = computeDomain(entityId);
const showAttributesMenuItem = this._computeAttributesMenuData(stateObj);
const isAdmin = this.hass.user!.is_admin;
@@ -590,7 +616,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
"ui.dialogs.more_info_control.related"
)}
</ha-dropdown-item>
${this._hasDisplayableAttributes()
${showAttributesMenuItem
? html`
<ha-dropdown-item value="attributes">
<ha-svg-icon
+56 -1
View File
@@ -1,7 +1,10 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { describe, expect, it } from "vitest";
import { computeShownAttributes } from "../../src/data/entity/entity_attributes";
import {
computeAdditionalMoreInfoAttributes,
computeShownAttributes,
} from "../../src/data/entity/entity_attributes";
describe("computeShownAttributes", () => {
it("filters globally hidden attributes", () => {
@@ -50,3 +53,55 @@ describe("computeShownAttributes", () => {
]);
});
});
describe("computeAdditionalMoreInfoAttributes", () => {
it("returns no additional attributes when main view already shows all", () => {
const stateObj = {
entity_id: "sensor.temperature",
attributes: {
friendly_name: "Office temperature",
unit_of_measurement: "°C",
temperature: 21,
custom_value: "shown",
},
} as unknown as HassEntity;
expect(computeAdditionalMoreInfoAttributes(stateObj, "default")).toEqual(
[]
);
});
it("returns attributes hidden in the main view", () => {
const stateObj = {
entity_id: "light.office",
attributes: {
friendly_name: "Office light",
brightness: 120,
color_mode: "rgb",
custom_value: "shown",
},
} as unknown as HassEntity;
expect(computeAdditionalMoreInfoAttributes(stateObj, "light")).toEqual([
"brightness",
"color_mode",
]);
});
it("returns all shown attributes when main view has no attributes section", () => {
const stateObj = {
entity_id: "climate.office",
attributes: {
temperature: 21,
hvac_mode: "heat",
custom_value: "shown",
},
} as unknown as HassEntity;
expect(computeAdditionalMoreInfoAttributes(stateObj, "climate")).toEqual([
"temperature",
"hvac_mode",
"custom_value",
]);
});
});