mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-07 01:43:13 +00:00
Compare commits
12 Commits
20260429.2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8620653a54 | ||
|
|
c4f4cbd323 | ||
|
|
2e0df00f0f | ||
|
|
ce02f8072d | ||
|
|
c973aa7516 | ||
|
|
1e2328707c | ||
|
|
56368b88cd | ||
|
|
fcd4f177c1 | ||
|
|
7423ae7316 | ||
|
|
4427c581f1 | ||
|
|
cf86bb9821 | ||
|
|
897802dc16 |
@@ -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}/`)),
|
||||
},
|
||||
],
|
||||
|
||||
12
build-scripts/get-built-in-node-module-shim.cjs
Normal file
12
build-scripts/get-built-in-node-module-shim.cjs
Normal 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;
|
||||
};
|
||||
@@ -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({
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 [
|
||||
|
||||
@@ -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
|
||||
>
|
||||
`
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
|
||||
@@ -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")!
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user