Compare commits

..

12 Commits

Author SHA1 Message Date
Bram Kragten
8620653a54 Merge branch 'rc' 2026-05-06 11:19:42 +02:00
Bram Kragten
c4f4cbd323 Bumped version to 20260429.3 2026-05-06 11:18:01 +02:00
Paul Bottein
2e0df00f0f Fix name for battery entities without device (#51879) 2026-05-06 11:17:09 +02:00
Wendelin
ce02f8072d Reduce progress bar default height (#51878)
reduce progress bar default height to 12px
2026-05-06 11:17:08 +02:00
Paul Bottein
c973aa7516 Fix media controls in media player more info dialog (#51877) 2026-05-06 11:17:07 +02:00
Paul Bottein
1e2328707c Fix switch clipping in view visibility editor (#51876) 2026-05-06 11:17:06 +02:00
Wendelin
56368b88cd Remove duplicate definition in semantic colors (#51875)
* Remove duplicate definition in semantic colors

* rearrange surface tokens
2026-05-06 11:17:05 +02:00
Aidan Timson
fcd4f177c1 Fix Safari 14 legacy bundle require errors (#51868)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-06 11:17:04 +02:00
Wendelin
7423ae7316 Fix integration search shrink on mobile (#51867) 2026-05-06 11:17:03 +02:00
Marcin Bauer
4427c581f1 Fix automation row right padding and soften chip highlight animation (#51865)
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 11:17:02 +02:00
Paul Bottein
cf86bb9821 Use ha-switch instead of ha-control-switch in entity toggle (#51852) 2026-05-06 11:17:01 +02:00
karwosts
897802dc16 Change display for uptime sensors (#51830) 2026-05-06 11:17:00 +02:00
24 changed files with 204 additions and 86 deletions

View File

@@ -1,3 +1,4 @@
/* global require, module, __dirname, process */
const path = require("path");
const env = require("./env.cjs");
const paths = require("./paths.cjs");
@@ -176,11 +177,14 @@ module.exports.babelOptions = ({
{
// Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
// Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills
// (otherwise babel-plugin-polyfill-corejs3 injects bare require("core-js/modules/...") calls
// that rspack does not transform, causing ReferenceError in browsers like Safari 14).
sourceType: "unambiguous",
include: /\/node_modules\//,
exclude: [
"element-internals-polyfill",
"@?lit(?:-labs|-element|-html)?",
"@formatjs/(?:ecma402-abstract|intl-\\w+)",
].map((p) => new RegExp(`/node_modules/${p}/`)),
},
],

View File

@@ -0,0 +1,12 @@
/* global module */
// Browser-only replacement for core-js/internals/get-built-in-node-module.
// The original helper evaluates `Function('return require("...")')()`
// when it detects a Node environment, which causes a runtime
// ReferenceError on browsers (notably Safari 14) if environment
// detection mis-classifies the page. Since browser bundles never need to
// access Node built-in modules, return undefined unconditionally.
//
// Wired up via rspack `NormalModuleReplacementPlugin` in build-scripts/rspack.cjs.
module.exports = function () {
return undefined;
};

View File

@@ -1,3 +1,4 @@
/* global require, module, __dirname */
const { existsSync } = require("fs");
const path = require("path");
const rspack = require("@rspack/core");
@@ -173,6 +174,16 @@ const createRspackConfig = ({
path.resolve(paths.root_dir, "src/util/empty.js")
)
: false,
// core-js ships a Node-only helper that evaluates
// `Function('return require("...")')()` when its runtime environment
// detection mis-classifies the page as Node. That produces a
// ReferenceError on browsers (observed on Safari 14). Since browser
// bundles never need to access Node built-in modules, replace it with
// a CommonJS no-op stub matching the helper's API (returns undefined).
new rspack.NormalModuleReplacementPlugin(
/core-js[\\/]internals[\\/]get-built-in-node-module(?:\.js)?$/,
path.resolve(__dirname, "get-built-in-node-module-shim.cjs")
),
!isProdBuild && new LogStartCompilePlugin(),
isProdBuild &&
new StatsWriterPlugin({

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20260429.2"
version = "20260429.3"
license = "Apache-2.0"
license-files = ["LICENSE*"]
description = "The Home Assistant frontend"

View File

@@ -17,6 +17,7 @@ import {
import { blankBeforeUnit } from "../translations/blank_before_unit";
import type { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
import { SENSOR_TIMESTAMP_DEVICE_CLASSES } from "../../data/sensor";
export const computeStateDisplay = (
localize: LocalizeFunc,
@@ -267,8 +268,7 @@ const computeStateToPartsFromEntityAttributes = (
"datetime",
].includes(domain) ||
(domain === "sensor" &&
(attributes.device_class === "timestamp" ||
attributes.device_class === "uptime"))
SENSOR_TIMESTAMP_DEVICE_CLASSES.includes(attributes.device_class))
) {
try {
return [

View File

@@ -53,7 +53,7 @@ export class HaAutomationRowEventChip extends LitElement {
return keyed(
this._highlight,
html`
<wa-animation fill="both" .iterations=${1} name="tada" play
<wa-animation fill="both" .iterations=${1} name="headShake" play
>${base}</wa-animation
>
`

View File

@@ -127,7 +127,7 @@ export class HaAutomationRow extends LitElement {
}
.row {
display: flex;
padding: 0 var(--ha-space-3);
padding: 0 0 0 var(--ha-space-3);
min-height: 48px;
align-items: flex-start;
cursor: pointer;

View File

@@ -13,9 +13,9 @@ import {
} from "../../data/entity/entity";
import { forwardHaptic } from "../../data/haptics";
import type { HomeAssistant } from "../../types";
import "../ha-control-switch";
import "../ha-formfield";
import "../ha-icon-button";
import "../ha-switch";
const isOn = (stateObj?: HassEntity) =>
stateObj !== undefined &&
@@ -35,7 +35,7 @@ export class HaEntityToggle extends LitElement {
protected render(): TemplateResult {
if (!this.stateObj) {
return html`<ha-control-switch disabled></ha-control-switch> `;
return html`<ha-switch disabled></ha-switch> `;
}
if (
@@ -62,14 +62,14 @@ export class HaEntityToggle extends LitElement {
`;
}
const switchTemplate = html`<ha-control-switch
const switchTemplate = html`<ha-switch
aria-label=${`Toggle ${computeStateName(this.stateObj)} ${
this._isOn ? "off" : "on"
}`}
.checked=${this._isOn}
.disabled=${this.stateObj.state === UNAVAILABLE}
@change=${this._toggleChanged}
></ha-control-switch>`;
></ha-switch>`;
if (!this.label) {
return switchTemplate;
@@ -164,10 +164,10 @@ export class HaEntityToggle extends LitElement {
align-items: center;
white-space: nowrap;
}
ha-control-switch {
width: 38px;
--control-switch-thickness: 20px;
--control-switch-off-color: var(--state-inactive-color);
ha-switch {
--ha-switch-width: 38px;
--ha-switch-size: 20px;
--ha-switch-thumb-size: 14px;
}
ha-icon-button {
--ha-icon-button-size: 40px;

View File

@@ -33,6 +33,7 @@ import { forwardHaptic } from "../data/haptics";
* @cssprop --ha-switch-checked-thumb-border-color-hover - Border color of the checked thumb on hover.
* @cssprop --ha-switch-thumb-box-shadow - The box shadow of the thumb. Defaults to `var(--ha-box-shadow-s)`.
* @cssprop --ha-switch-disabled-opacity - Opacity of the switch when disabled. Defaults to `0.2`.
* @cssprop --ha-switch-min-touch-size - Minimum touch target size around the switch. Defaults to `40px`.
* @cssprop --ha-switch-required-marker - The marker shown after the label for required fields. Defaults to `"*"`.
* @cssprop --ha-switch-required-marker-offset - Offset of the required marker. Defaults to `0.1rem`.
*
@@ -89,8 +90,23 @@ export class HaSwitch extends Switch {
}
label {
position: relative;
height: max(var(--thumb-size), var(--wa-form-control-toggle-size));
}
label::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
min-width: var(--ha-switch-min-touch-size, 40px);
min-height: var(--ha-switch-min-touch-size, 40px);
}
label.disabled::before {
pointer-events: none;
}
.switch {
background-color: var(

View File

@@ -44,7 +44,7 @@ export class HaProgressBar extends ProgressBar {
--ha-progress-bar-track-color,
var(--ha-color-fill-neutral-normal-hover)
);
--track-height: var(--ha-progress-bar-track-height, 16px);
--track-height: var(--ha-progress-bar-track-height, 12px);
--wa-transition-slow: var(--ha-animation-duration-slow);
position: relative;
}

View File

@@ -4,6 +4,12 @@ export const SENSOR_DEVICE_CLASS_BATTERY = "battery";
export const SENSOR_DEVICE_CLASS_TIMESTAMP = "timestamp";
export const SENSOR_DEVICE_CLASS_TEMPERATURE = "temperature";
export const SENSOR_DEVICE_CLASS_HUMIDITY = "humidity";
export const SENSOR_DEVICE_CLASS_UPTIME = "uptime";
export const SENSOR_TIMESTAMP_DEVICE_CLASSES: (string | undefined)[] = [
"timestamp",
"uptime",
];
export interface SensorDeviceClassUnits {
units: string[];

View File

@@ -21,9 +21,12 @@ import { isTiltOnly } from "../../../data/cover";
import { isUnavailableState } from "../../../data/entity/entity";
import type { ImageEntity } from "../../../data/image";
import { computeImageUrl } from "../../../data/image";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
import "../../../panels/lovelace/components/hui-timestamp-display";
import type { HomeAssistant } from "../../../types";
import {
SENSOR_DEVICE_CLASS_UPTIME,
SENSOR_TIMESTAMP_DEVICE_CLASSES,
} from "../../../data/sensor";
@customElement("entity-preview-row")
class EntityPreviewRow extends LitElement {
@@ -312,14 +315,19 @@ class EntityPreviewRow extends LitElement {
if (domain === "sensor") {
const showSensor =
stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP &&
!isUnavailableState(stateObj.state);
SENSOR_TIMESTAMP_DEVICE_CLASSES.includes(
stateObj.attributes.device_class
) && !isUnavailableState(stateObj.state);
return html`
${showSensor
? html`
<hui-timestamp-display
.hass=${this.hass}
.ts=${new Date(stateObj.state)}
.format=${stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_UPTIME
? "total"
: undefined}
capitalize
></hui-timestamp-display>
`

View File

@@ -29,7 +29,6 @@ import "../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
import "../../../components/ha-dropdown-item";
import "../../../components/ha-icon-button";
import type { HaIconButton } from "../../../components/ha-icon-button";
import "../../../components/ha-list-item";
import "../../../components/ha-marquee-text";
import "../../../components/ha-select";
@@ -507,7 +506,7 @@ class MoreInfoMediaPlayer extends LitElement {
<ha-icon-button
.id=${`media-control-row-button-${idSuffix}`}
hide-title
.action=${action}
action=${ifDefined(action)}
@click=${clickHandler}
.label=${title}
.path=${icon}
@@ -709,7 +708,7 @@ class MoreInfoMediaPlayer extends LitElement {
handleMediaControlClick(
this.hass!,
this.stateObj!,
(e.currentTarget as HaIconButton & { action: string }).action!
(e.currentTarget as HTMLElement).getAttribute("action")!
);
}

View File

@@ -374,6 +374,7 @@ export class HassTabsSubpage extends LitElement {
}
.main-title {
min-width: 0;
flex: 1;
max-height: var(--header-height);
line-height: var(--ha-line-height-normal);

View File

@@ -8,6 +8,7 @@ import { customElement, property, query, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { deepActiveElement } from "../../../common/dom/deep-active-element";
import {
PROTOCOL_INTEGRATIONS,
protocolIntegrationPicked,
@@ -16,7 +17,6 @@ import { navigate } from "../../../common/navigate";
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
import { extractSearchParam } from "../../../common/url/search-params";
import { nextRender } from "../../../common/util/render-status";
import { deepActiveElement } from "../../../common/dom/deep-active-element";
import "../../../components/ha-button";
import "../../../components/ha-dropdown";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
@@ -1073,9 +1073,11 @@ class HaConfigIntegrationsDashboard extends KeyboardShortcutMixin(
}
ha-input-search {
flex: 1;
min-width: 0;
}
.header {
display: flex;
min-width: 0;
}
.search {
display: flex;

View File

@@ -15,7 +15,10 @@ import type {
CallServiceActionConfig,
MoreInfoActionConfig,
} from "../../../data/lovelace/config/action";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
import {
SENSOR_DEVICE_CLASS_UPTIME,
SENSOR_TIMESTAMP_DEVICE_CLASSES,
} from "../../../data/sensor";
import type { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
import { findEntities } from "../common/find-entities";
@@ -287,14 +290,19 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
? html`
<div>
${computeDomain(entityConf.entity) === "sensor" &&
stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_TIMESTAMP &&
SENSOR_TIMESTAMP_DEVICE_CLASSES.includes(
stateObj.attributes.device_class
) &&
!isUnavailableState(stateObj.state)
? html`
<hui-timestamp-display
.hass=${this.hass}
.ts=${new Date(stateObj.state)}
.format=${entityConf.format}
.format=${entityConf.format ??
(stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_UPTIME
? "total"
: undefined)}
capitalize
></hui-timestamp-display>
`

View File

@@ -2,7 +2,7 @@ import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { DOMAINS_TOGGLE } from "../../../common/const";
import "../../../components/ha-control-switch";
import "../../../components/ha-switch";
import type { HaSwitch } from "../../../components/ha-switch";
import { forwardHaptic } from "../../../data/haptics";
import type { HomeAssistant } from "../../../types";
@@ -33,7 +33,7 @@ class HuiEntitiesToggle extends LitElement {
}
return html`
<ha-control-switch
<ha-switch
aria-label=${this.hass!.localize(
"ui.panel.lovelace.card.entities.toggle"
)}
@@ -42,7 +42,7 @@ class HuiEntitiesToggle extends LitElement {
return stateObj && stateObj.state === "on";
})}
@change=${this._callService}
></ha-control-switch>
></ha-switch>
`;
}
@@ -51,10 +51,10 @@ class HuiEntitiesToggle extends LitElement {
display: flex;
align-items: center;
}
ha-control-switch {
width: 38px;
--control-switch-thickness: 20px;
--control-switch-off-color: var(--state-inactive-color);
ha-switch {
--ha-switch-width: 38px;
--ha-switch-size: 20px;
--ha-switch-thumb-size: 14px;
}
`;

View File

@@ -6,7 +6,8 @@ import { fireEvent } from "../../../../common/dom/fire_event";
import { stringCompare } from "../../../../common/string/compare";
import type { HaSwitch } from "../../../../components/ha-switch";
import "../../../../components/user/ha-user-badge";
import "../../../../components/ha-list-item";
import "../../../../components/ha-md-list";
import "../../../../components/ha-md-list-item";
import "../../../../components/ha-switch";
import type {
LovelaceViewConfig,
@@ -65,24 +66,26 @@ export class HuiViewVisibilityEditor extends LitElement {
"ui.panel.lovelace.editor.edit_view.visibility.select_users"
)}
</p>
${this._sortedUsers(this._users).map(
(user) => html`
<ha-list-item graphic="avatar" hasMeta>
<ha-user-badge
slot="graphic"
.hass=${this.hass}
.user=${user}
></ha-user-badge>
<span>${user.name}</span>
<ha-switch
slot="meta"
.userId=${user.id}
@change=${this._valChange}
.checked=${this.checkUser(user.id)}
></ha-switch>
</ha-list-item>
`
)}
<ha-md-list>
${this._sortedUsers(this._users).map(
(user) => html`
<ha-md-list-item>
<ha-user-badge
slot="start"
.hass=${this.hass}
.user=${user}
></ha-user-badge>
<span slot="headline">${user.name}</span>
<ha-switch
slot="end"
.userId=${user.id}
@change=${this._valChange}
.checked=${this.checkUser(user.id)}
></ha-switch>
</ha-md-list-item>
`
)}
</ha-md-list>
`;
}
@@ -136,6 +139,16 @@ export class HuiViewVisibilityEditor extends LitElement {
:host {
display: block;
}
ha-md-list {
padding: 0;
}
ha-md-list-item {
--md-list-item-leading-space: 0;
--md-list-item-trailing-space: 0;
--md-list-item-top-space: var(--ha-space-1);
--md-list-item-bottom-space: var(--ha-space-1);
--md-list-item-one-line-container-height: 48px;
}
`;
}

View File

@@ -2,7 +2,10 @@ import type { PropertyValues } from "lit";
import { LitElement, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { isUnavailableState } from "../../../data/entity/entity";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
import {
SENSOR_DEVICE_CLASS_UPTIME,
SENSOR_TIMESTAMP_DEVICE_CLASSES,
} from "../../../data/sensor";
import type { HomeAssistant } from "../../../types";
import type { EntitiesCardEntityConfig } from "../cards/types";
import { hasConfigOrEntityChanged } from "../common/has-changed";
@@ -50,13 +53,17 @@ class HuiSensorEntityRow extends LitElement implements LovelaceRow {
return html`
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
${stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP &&
!isUnavailableState(stateObj.state)
${SENSOR_TIMESTAMP_DEVICE_CLASSES.includes(
stateObj.attributes.device_class
) && !isUnavailableState(stateObj.state)
? html`
<hui-timestamp-display
.hass=${this.hass}
.ts=${new Date(stateObj.state)}
.format=${this._config.format}
.format=${this._config.format ??
(stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_UPTIME
? "total"
: undefined)}
capitalize
></hui-timestamp-display>
`

View File

@@ -48,11 +48,18 @@ export const filterUnavailableBatteryEntities = (
return hass.states[entityId]?.state === "unavailable";
});
const computeBatteryTileCard = (entityId: string): TileCardConfig => ({
type: "tile",
entity: entityId,
name: { type: "device" },
});
const computeBatteryTileCard = (
entities: HomeAssistant["entities"],
entityId: string
): TileCardConfig => {
const entity = entities[entityId];
const deviceId = entity?.device_id;
return {
type: "tile",
entity: entityId,
name: { type: deviceId ? "device" : "entity" },
};
};
const processAreasForBattery = (
areaIds: string[],
@@ -72,7 +79,7 @@ const processAreasForBattery = (
const areaCards: LovelaceCardConfig[] = [];
for (const entityId of areaBatteryEntities) {
areaCards.push(computeBatteryTileCard(entityId));
areaCards.push(computeBatteryTileCard(hass.entities, entityId));
}
if (areaCards.length > 0) {
@@ -105,7 +112,7 @@ const processUnassignedEntities = (
const cards: LovelaceCardConfig[] = [];
for (const entityId of unassignedEntities) {
cards.push(computeBatteryTileCard(entityId));
cards.push(computeBatteryTileCard(hass.entities, entityId));
}
return cards;

View File

@@ -17,14 +17,24 @@ const addData = async (
addFunc = "__addLocaleData"
) => {
// Add function will only exist if constructor is polyfilled
if (typeof (Intl[obj] as any)?.[addFunc] === "function") {
const result = await fetch(
`${__STATIC_PATH__}locale-data/intl-${obj.toLowerCase()}/${language}.json`
);
// Ignore if polyfill data does not exist for language
if (typeof (Intl[obj] as any)?.[addFunc] !== "function") {
return;
}
const url = `${__STATIC_PATH__}locale-data/intl-${obj.toLowerCase()}/${language}.json`;
try {
const result = await fetch(url);
// 404 means polyfill data does not exist for the language; ignore silently.
if (result.ok) {
(Intl[obj] as any)[addFunc](await result.json());
}
} catch (err) {
// Network/access-control failures should not block startup, but they
// degrade i18n features so surface a warning for diagnostics.
// eslint-disable-next-line no-console
console.warn(`Failed to load Intl.${obj} locale data for ${language}`, {
url,
error: err,
});
}
};

View File

@@ -161,19 +161,18 @@ export const semanticColorStyles = css`
--ha-color-on-success-loud: var(--white-color);
/* Surfaces */
--ha-color-surface-default: var(--ha-color-neutral-95);
--ha-color-on-surface-default: var(--ha-color-neutral-05);
/* forms */
--ha-color-form-background: var(--ha-color-neutral-95);
--ha-color-form-background-hover: var(--ha-color-neutral-90);
--ha-color-form-background-disabled: var(--ha-color-neutral-80);
--ha-color-surface-default: var(--ha-color-white);
--ha-color-surface-low: var(--ha-color-neutral-95);
--ha-color-surface-lower: var(--ha-color-neutral-90);
--ha-color-surface-default-inverted: var(--ha-color-neutral-10);
--ha-color-surface-low-inverted: var(--ha-color-neutral-05);
--ha-color-surface-lower-inverted: var(--ha-color-black);
--ha-color-on-surface-default: var(--ha-color-neutral-05);
/* forms */
--ha-color-form-background: var(--ha-color-neutral-95);
--ha-color-form-background-hover: var(--ha-color-neutral-90);
--ha-color-form-background-disabled: var(--ha-color-neutral-80);
/* Scrollable fade */
--ha-color-shadow-scrollable-fade: rgba(0, 0, 0, 0.08);
@@ -314,16 +313,16 @@ export const darkSemanticColorStyles = css`
/* Surfaces */
--ha-color-surface-default: var(--ha-color-neutral-10);
--ha-color-surface-low: var(--ha-color-neutral-05);
--ha-color-surface-lower: var(--ha-color-black);
--ha-color-surface-default-inverted: var(--ha-color-white);
--ha-color-surface-low-inverted: var(--ha-color-neutral-95);
--ha-color-surface-lower-inverted: var(--ha-color-90);
--ha-color-on-surface-default: var(--ha-color-neutral-95);
/* forms */
--ha-color-form-background: var(--ha-color-neutral-20);
--ha-color-form-background-hover: var(--ha-color-neutral-30);
--ha-color-form-background-disabled: var(--ha-color-neutral-20);
--ha-color-surface-low: var(--ha-color-neutral-05);
--ha-color-surface-lower: var(--ha-color-black);
--ha-color-surface-default-inverted: var(--ha-color-white);
--ha-color-surface-low-inverted: var(--ha-color-neutral-95);
--ha-color-surface-lower-inverted: var(--ha-color-90);
}
`;

View File

@@ -8,7 +8,10 @@ import { computeStateDomain } from "../common/entity/compute_state_domain";
import { STRINGS_SEPARATOR_DOT } from "../common/const";
import "../components/ha-relative-time";
import { isUnavailableState } from "../data/entity/entity";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../data/sensor";
import {
SENSOR_TIMESTAMP_DEVICE_CLASSES,
SENSOR_DEVICE_CLASS_UPTIME,
} from "../data/sensor";
import type { UpdateEntity } from "../data/update";
import { computeUpdateStateDisplay } from "../data/update";
import "../panels/lovelace/components/hui-timestamp-display";
@@ -90,7 +93,9 @@ class StateDisplay extends LitElement {
return "—";
}
if (
(stateObj.attributes.device_class === SENSOR_DEVICE_CLASS_TIMESTAMP ||
(SENSOR_TIMESTAMP_DEVICE_CLASSES.includes(
this.stateObj.attributes.device_class
) ||
TIMESTAMP_STATE_DOMAINS.includes(domain)) &&
!isUnavailableState(stateObj.state)
) {
@@ -98,7 +103,10 @@ class StateDisplay extends LitElement {
<hui-timestamp-display
.hass=${this.hass}
.ts=${new Date(stateObj.state)}
format="relative"
.format=${this.stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_UPTIME
? "total"
: "relative"}
capitalize
></hui-timestamp-display>
`;

View File

@@ -6,7 +6,10 @@ import { classMap } from "lit/directives/class-map";
import { computeDomain } from "../common/entity/compute_domain";
import "../components/entity/state-info";
import { isUnavailableState } from "../data/entity/entity";
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../data/sensor";
import {
SENSOR_TIMESTAMP_DEVICE_CLASSES,
SENSOR_DEVICE_CLASS_UPTIME,
} from "../data/sensor";
import "../panels/lovelace/components/hui-timestamp-display";
import { haStyle } from "../resources/styles";
import type { HomeAssistant } from "../types";
@@ -38,13 +41,17 @@ class StateCardDisplay extends LitElement {
})}"
>
${computeDomain(this.stateObj.entity_id) === "sensor" &&
this.stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_TIMESTAMP &&
SENSOR_TIMESTAMP_DEVICE_CLASSES.includes(
this.stateObj.attributes.device_class
) &&
!isUnavailableState(this.stateObj.state)
? html`<hui-timestamp-display
.hass=${this.hass}
.ts=${new Date(this.stateObj.state)}
format="datetime"
.format=${this.stateObj.attributes.device_class ===
SENSOR_DEVICE_CLASS_UPTIME
? "total"
: "datetime"}
capitalize
></hui-timestamp-display>`
: this.hass.formatEntityState(this.stateObj)}