Compare commits

..

3 Commits

Author SHA1 Message Date
Petar Petrov 09fa4d2236 Remove redundant connectedCallback re-observe in ha-input 2026-07-02 08:35:33 +03:00
Petar Petrov ccc15df5e6 Merge remote-tracking branch 'origin/dev' into fix-52921 2026-07-01 13:59:53 +03:00
Petar Petrov e4ffc56ce7 Fix search icon overlapping label in ha-input on Safari 2026-07-01 13:57:17 +03:00
93 changed files with 1382 additions and 2147 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
<!--
If your issue is about how an entity is shown in the UI, please add the state
and attributes for all situations with a screenshot of the UI.
You can find this information at `/config/tools/state`
You can find this information at `/config/developer-tools/state`
-->
```yaml
+2 -2
View File
@@ -94,8 +94,8 @@ body:
label: State of relevant entities
description: >
If your issue is about how an entity is shown in the UI, please add the
state and attributes for all situations. You can find this
information in the Details view of the More info dialog.
state and attributes for all situations. You can find this information
at Developer Tools -> States.
render: txt
- type: textarea
attributes:
+3 -3
View File
@@ -102,7 +102,7 @@
"gulp-zopfli-green": "7.0.0",
"hls.js": "1.6.16",
"home-assistant-js-websocket": "9.6.0",
"idb-keyval": "6.2.6",
"idb-keyval": "6.2.5",
"intl-messageformat": "11.2.9",
"js-yaml": "5.2.0",
"leaflet": "1.9.4",
@@ -147,7 +147,7 @@
"@octokit/plugin-retry": "8.1.0",
"@octokit/rest": "22.0.1",
"@playwright/test": "1.61.1",
"@rsdoctor/rspack-plugin": "1.5.17",
"@rsdoctor/rspack-plugin": "1.5.16",
"@rspack/core": "2.1.1",
"@rspack/dev-server": "2.1.0",
"@types/babel__plugin-transform-runtime": "7.9.5",
@@ -172,7 +172,7 @@
"eslint": "10.6.0",
"eslint-config-prettier": "10.1.8",
"eslint-import-resolver-webpack": "0.13.11",
"eslint-plugin-import-x": "4.17.1",
"eslint-plugin-import-x": "4.17.0",
"eslint-plugin-lit": "2.3.1",
"eslint-plugin-lit-a11y": "5.1.1",
"eslint-plugin-unused-imports": "4.4.1",
+2 -2
View File
@@ -525,10 +525,10 @@ export class HaServiceControl extends LitElement {
this._manifest
? html` <a
href=${
this._manifest.is_built_in && this._value?.action
this._manifest.is_built_in
? documentationUrl(
this.hass,
`/actions/${this._value.action}`
`/integrations/${this._manifest.domain}`
)
: this._manifest.documentation
}
+27
View File
@@ -127,6 +127,8 @@ export class HaInput extends WaInputMixin(LitElement) {
@query("wa-input")
private _input?: WaInput;
private _startSlotResizeObserver?: ResizeObserver;
@state()
@consume({ context: internationalizationContext, subscribe: true })
protected i18n?: ContextType<typeof internationalizationContext>;
@@ -167,9 +169,15 @@ export class HaInput extends WaInputMixin(LitElement) {
// Wait for wa-input to finish its first render
await this._input?.updateComplete;
this._syncStartSlotWidth();
this._observeStartSlot();
}
}
public override disconnectedCallback(): void {
super.disconnectedCallback();
this._startSlotResizeObserver?.disconnect();
}
protected render() {
const hasLabelSlot = this.label
? false
@@ -291,6 +299,25 @@ export class HaInput extends WaInputMixin(LitElement) {
return nothing;
}
// Safari can report the start-slot width as 0 during the first render, which
// leaves the floating label overlapping the start icon (e.g. the magnify icon
// in ha-input-search). Re-sync whenever the wrapper's size changes
// (0 -> icon width, or hidden -> shown) so the label padding stays correct.
private _observeStartSlot() {
if (typeof ResizeObserver === "undefined") {
return;
}
const startEl = this._input?.shadowRoot?.querySelector('[part~="start"]');
if (!startEl) {
return;
}
this._startSlotResizeObserver?.disconnect();
this._startSlotResizeObserver = new ResizeObserver(() =>
this._syncStartSlotWidth()
);
this._startSlotResizeObserver.observe(startEl);
}
private _syncStartSlotWidth = () => {
const startEl = this._input?.shadowRoot?.querySelector(
'[part~="start"]'
+2 -2
View File
@@ -57,8 +57,8 @@ export const CONFIG_SUB_ROUTES: Record<
translationKey: "ui.components.navigation-picker.route.scripts",
iconPath: mdiScriptText,
},
tools: {
translationKey: "ui.components.navigation-picker.route.tools",
"developer-tools": {
translationKey: "ui.components.navigation-picker.route.developer_tools",
iconPath: mdiHammer,
},
integrations: {
+6 -16
View File
@@ -27,7 +27,6 @@ export interface LogbookEntry {
source?: string; // The trigger source (English phrase, parsed for the cause)
domain?: string;
state?: string; // The state of the entity
attributes?: { event_type?: string }; // Selected attributes the backend surfaces
// Context data
context_id?: string;
context_user_id?: string;
@@ -245,13 +244,13 @@ export const parseTriggerSource = (source: string): ParsedTriggerSource => {
};
// Short label shown instead of the bare timestamp for each timestamp-state
// domain. Typed to TIMESTAMP_STATE_DOMAINS minus datetime (a real value) and
// event (handled separately via its event type), so a new timestamp domain
// won't compile until it gets a label here.
// domain. Typed to TIMESTAMP_STATE_DOMAINS minus datetime (a real value), so a
// new timestamp domain won't compile until it gets a label here.
type LogbookActionMessage =
| "pressed"
| "activated"
| "scanned"
| "detected_event_no_type"
| "updated"
| "sent"
| "detected"
@@ -262,13 +261,14 @@ type LogbookActionMessage =
| "command_sent";
const STATE_ACTION_MESSAGES: Record<
Exclude<TimestampStateDomain, "datetime" | "event">,
Exclude<TimestampStateDomain, "datetime">,
LogbookActionMessage
> = {
button: "pressed",
input_button: "pressed",
scene: "activated",
tag: "scanned",
event: "detected_event_no_type",
image: "updated",
notify: "sent",
wake_word: "detected",
@@ -284,18 +284,8 @@ export const localizeStateMessage = (
hass: HomeAssistant,
state: string,
stateObj: HassEntity,
domain: string,
attributes?: LogbookEntry["attributes"]
domain: string
): string => {
// Events show the triggered event type, falling back to a generic label when
// the type is unknown (the timestamp state is meaningless on its own).
if (domain === "event") {
const eventType = attributes?.event_type;
if (eventType != null) {
return hass.formatEntityAttributeValue(stateObj, "event_type", eventType);
}
return hass.localize(`${LOGBOOK_LOCALIZE_PATH}.detected_event_no_type`);
}
const actionKey: LogbookActionMessage | undefined =
STATE_ACTION_MESSAGES[domain as keyof typeof STATE_ACTION_MESSAGES];
if (actionKey) {
+5 -17
View File
@@ -28,21 +28,6 @@ const useHash = __DEMO__;
const curPath = () =>
useHash ? location.hash.substring(1) : location.pathname;
// Developer tools was renamed to Tools (/config/tools) in 2026.8; it had moved
// from /developer-tools to /config in 2026.2. Redirect both old locations to
// the new one. Applied on the initial route and on every navigation so
// bookmarks and external links to the old URLs resolve too, not just in-app
// navigation.
const redirectLegacyToolsPath = (path: string): string => {
if (path.startsWith("/config/developer-tools")) {
return path.replace("/config/developer-tools", "/config/tools");
}
if (path.startsWith("/developer-tools")) {
return path.replace("/developer-tools", "/config/tools");
}
return path;
};
const panelUrl = (path: string) => {
const dividerPos = path.indexOf("/", 1);
return dividerPos === -1 ? path.substring(1) : path.substring(1, dividerPos);
@@ -65,7 +50,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
constructor() {
super();
const path = redirectLegacyToolsPath(curPath());
const path = curPath();
this._route = {
prefix: "",
@@ -121,7 +106,10 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
// Navigation
const updateRoute = (path = curPath()) => {
path = redirectLegacyToolsPath(path);
// Developer tools panel was moved to config in 2026.2
if (path.startsWith("/developer-tools")) {
path = path.replace("/developer-tools", "/config/developer-tools");
}
if (this._route && path === this._route.path) {
return;
}
@@ -195,7 +195,7 @@ export class HaPlatformCondition extends LitElement {
this._manifest.is_built_in
? documentationUrl(
this.hass,
`/conditions/${this.condition.condition}`
`/integrations/${this._manifest.domain}`
)
: this._manifest.documentation
}
@@ -190,7 +190,7 @@ export class HaPlatformTrigger extends LitElement {
this._manifest.is_built_in
? documentationUrl(
this.hass,
`/triggers/${this.trigger.trigger}`
`/integrations/${this._manifest.domain}`
)
: this._manifest.documentation
}
+5 -5
View File
@@ -211,8 +211,8 @@ export const configSections: Record<string, PageNavigation[]> = {
adminOnly: true,
},
{
path: "/config/tools",
translationKey: "tools",
path: "/config/developer-tools",
translationKey: "developer_tools",
iconPath: mdiHammer,
iconColor: "#7A5AA6",
core: true,
@@ -328,10 +328,10 @@ export const configSections: Record<string, PageNavigation[]> = {
adminOnly: true,
},
],
tools: [
developer_tools: [
{
path: "/config/tools",
translationKey: "ui.panel.config.dashboard.tools.main",
path: "/config/developer-tools",
translationKey: "ui.panel.config.dashboard.developer_tools.main",
iconPath: mdiHammer,
iconColor: "#7A5AA6",
core: true,
@@ -48,7 +48,7 @@ import { resolveMediaSource } from "../../../../data/media_source";
import { MatchMinHeightMixin } from "../../../../mixins/match-min-height-mixin";
import { withViewTransition } from "../../../../common/util/view-transition";
@customElement("tools-action")
@customElement("developer-tools-action")
class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -130,12 +130,14 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
const modeButtons: ToggleButton[] = [
{
label: this.hass.localize("ui.panel.config.tools.tabs.actions.ui_mode"),
label: this.hass.localize(
"ui.panel.config.developer-tools.tabs.actions.ui_mode"
),
value: "ui",
},
{
label: this.hass.localize(
"ui.panel.config.tools.tabs.actions.yaml_mode"
"ui.panel.config.developer-tools.tabs.actions.yaml_mode"
),
value: "yaml",
},
@@ -161,7 +163,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
<div class="header-row">
<div class="header-title">
${this.hass.localize(
"ui.panel.config.tools.tabs.actions.title"
"ui.panel.config.developer-tools.tabs.actions.title"
)}
</div>
<ha-button-toggle-group
@@ -175,7 +177,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
</div>
<p class="secondary">
${this.hass.localize(
"ui.panel.config.tools.tabs.actions.description"
"ui.panel.config.developer-tools.tabs.actions.description"
)}
</p>
</div>
@@ -218,14 +220,14 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
!this._uiAvailable
? html`<span class="error"
>${this.hass.localize(
"ui.panel.config.tools.tabs.actions.no_template_ui_support"
"ui.panel.config.developer-tools.tabs.actions.no_template_ui_support"
)}</span
>`
: nothing
}
<ha-progress-button raised @click=${this._callService}>
${this.hass.localize(
"ui.panel.config.tools.tabs.actions.call_service"
"ui.panel.config.developer-tools.tabs.actions.call_service"
)}
</ha-progress-button>
</div>
@@ -236,7 +238,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
? html`<div class="content response">
<ha-card
.header=${this.hass.localize(
"ui.panel.config.tools.tabs.actions.response"
"ui.panel.config.developer-tools.tabs.actions.response"
)}
>
<div class="card-content">
@@ -251,7 +253,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
slot="extra-actions"
@click=${this._copyTemplate}
>${this.hass.localize(
"ui.panel.config.tools.tabs.actions.copy_clipboard_template"
"ui.panel.config.developer-tools.tabs.actions.copy_clipboard_template"
)}</ha-button
>
</ha-yaml-editor>
@@ -268,10 +270,10 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
.header=${
this._yamlMode
? this.hass.localize(
"ui.panel.config.tools.tabs.actions.all_parameters"
"ui.panel.config.developer-tools.tabs.actions.all_parameters"
)
: this.hass.localize(
"ui.panel.config.tools.tabs.actions.yaml_parameters"
"ui.panel.config.developer-tools.tabs.actions.yaml_parameters"
)
}
outlined
@@ -285,7 +287,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
target
? html`
${this.hass.localize(
"ui.panel.config.tools.tabs.actions.accepts_target"
"ui.panel.config.developer-tools.tabs.actions.accepts_target"
)}
`
: ""
@@ -320,17 +322,17 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
<tr>
<th>
${this.hass.localize(
"ui.panel.config.tools.tabs.actions.column_parameter"
"ui.panel.config.developer-tools.tabs.actions.column_parameter"
)}
</th>
<th>
${this.hass.localize(
"ui.panel.config.tools.tabs.actions.column_description"
"ui.panel.config.developer-tools.tabs.actions.column_description"
)}
</th>
<th>
${this.hass.localize(
"ui.panel.config.tools.tabs.actions.column_example"
"ui.panel.config.developer-tools.tabs.actions.column_example"
)}
</th>
</tr>
@@ -369,7 +371,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
appearance="plain"
@click=${this._fillExampleData}
>${this.hass.localize(
"ui.panel.config.tools.tabs.actions.fill_example_data"
"ui.panel.config.developer-tools.tabs.actions.fill_example_data"
)}</ha-button
>`
: ""
@@ -404,14 +406,14 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
const errorCategory = yamlMode ? "yaml" : "ui";
if (!serviceData?.action) {
return localize(
`ui.panel.config.tools.tabs.actions.errors.${errorCategory}.no_action`
`ui.panel.config.developer-tools.tabs.actions.errors.${errorCategory}.no_action`
);
}
const domain = computeDomain(serviceData.action);
const service = computeObjectId(serviceData.action);
if (!domain || !service) {
return localize(
`ui.panel.config.tools.tabs.actions.errors.${errorCategory}.invalid_action`
`ui.panel.config.developer-tools.tabs.actions.errors.${errorCategory}.invalid_action`
);
}
const dataIsTemplate =
@@ -425,7 +427,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
!serviceData.data?.area_id
) {
return localize(
`ui.panel.config.tools.tabs.actions.errors.${errorCategory}.no_target`
`ui.panel.config.developer-tools.tabs.actions.errors.${errorCategory}.no_target`
);
}
for (const field of fields) {
@@ -435,7 +437,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
(!serviceData.data || serviceData.data[field.key] === undefined)
) {
return localize(
`ui.panel.config.tools.tabs.actions.errors.${errorCategory}.missing_required_field`,
`ui.panel.config.developer-tools.tabs.actions.errors.${errorCategory}.missing_required_field`,
{ key: field.key }
);
}
@@ -494,7 +496,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
forwardHaptic(this, "failure");
button.actionError();
this._error = this.hass.localize(
"ui.panel.config.tools.tabs.actions.errors.yaml.invalid_yaml"
"ui.panel.config.developer-tools.tabs.actions.errors.yaml.invalid_yaml"
);
return;
}
@@ -567,7 +569,7 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
rel="noreferrer"
><ha-button>
${this.hass.localize(
"ui.panel.config.tools.tabs.actions.open_media"
"ui.panel.config.developer-tools.tabs.actions.open_media"
)}
</ha-button></a
>
@@ -827,6 +829,6 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
declare global {
interface HTMLElementTagNameMap {
"tools-action": HaPanelDevAction;
"developer-tools-action": HaPanelDevAction;
}
}
@@ -25,7 +25,7 @@ interface SentenceParsingResult {
result: AssistDebugResult | null;
}
@customElement("tools-assist")
@customElement("developer-tools-assist")
class HaPanelDevAssist extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -118,14 +118,14 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
<div class="content">
<ha-card
.header=${this.hass.localize(
"ui.panel.config.tools.tabs.assist.title"
"ui.panel.config.developer-tools.tabs.assist.title"
)}
class="form"
>
<div class="card-content">
<p class="description">
${this.hass.localize(
"ui.panel.config.tools.tabs.assist.description"
"ui.panel.config.developer-tools.tabs.assist.description"
)}
</p>
${
@@ -143,7 +143,7 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
<ha-textarea
resize="auto"
.label=${this.hass.localize(
"ui.panel.config.tools.tabs.assist.sentences"
"ui.panel.config.developer-tools.tabs.assist.sentences"
)}
id="sentences-input"
@input=${this._textAreaInput}
@@ -157,7 +157,7 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
.disabled=${!this._language || !this._validInput}
>
${this.hass.localize(
"ui.panel.config.tools.tabs.assist.parse_sentences"
"ui.panel.config.developer-tools.tabs.assist.parse_sentences"
)}
</ha-button>
</div>
@@ -184,7 +184,7 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
.path=${mdiDownload}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.tools.tabs.assist.download_results"
"ui.panel.config.developer-tools.tabs.assist.download_results"
)}
</ha-button>
</div>
@@ -204,7 +204,7 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
</div>
<div class="info">
${this.hass.localize(
"ui.panel.config.tools.tabs.assist.language"
"ui.panel.config.developer-tools.tabs.assist.language"
)}:
${formatLanguageCode(language, this.hass.locale)}
(${language})
@@ -221,7 +221,7 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
`
: html`<ha-alert alert-type="error">
${this.hass.localize(
"ui.panel.config.tools.tabs.assist.no_match"
"ui.panel.config.developer-tools.tabs.assist.no_match"
)}
</ha-alert>`
}
@@ -304,6 +304,6 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
declare global {
interface HTMLElementTagNameMap {
"tools-assist": HaPanelDevAssist;
"developer-tools-assist": HaPanelDevAssist;
}
}
@@ -20,7 +20,7 @@ import "./ha-debug-connection-row";
import "./ha-debug-disable-view-transition-row";
import "./ha-debug-viewport-environment-card";
@customElement("tools-debug")
@customElement("developer-tools-debug")
class HaPanelDevDebug extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -31,7 +31,7 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) {
<div class="content">
<ha-card
.header=${this.hass.localize(
"ui.panel.config.tools.tabs.debug.title"
"ui.panel.config.developer-tools.tabs.debug.title"
)}
>
<ha-list-base>
@@ -45,13 +45,13 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) {
</ha-card>
<ha-card
.header=${this.hass.localize(
"ui.panel.config.tools.tabs.debug.entity_diagnostic.title"
"ui.panel.config.developer-tools.tabs.debug.entity_diagnostic.title"
)}
>
<div class="card-content">
<ha-entity-picker
.helper=${this.hass.localize(
"ui.panel.config.tools.tabs.debug.entity_diagnostic.description"
"ui.panel.config.developer-tools.tabs.debug.entity_diagnostic.description"
)}
@value-changed=${this._entityPicked}
></ha-entity-picker>
@@ -62,7 +62,7 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) {
appearance="filled"
.disabled=${!this._entityId}
>${this.hass.localize(
"ui.panel.config.tools.tabs.debug.entity_diagnostic.copy_to_clipboard"
"ui.panel.config.developer-tools.tabs.debug.entity_diagnostic.copy_to_clipboard"
)}</ha-button
>
</div>
@@ -136,6 +136,6 @@ class HaPanelDevDebug extends SubscribeMixin(LitElement) {
declare global {
interface HTMLElementTagNameMap {
"tools-debug": HaPanelDevDebug;
"developer-tools-debug": HaPanelDevDebug;
}
}
@@ -17,12 +17,12 @@ class HaDebugConnectionRow extends LitElement {
<ha-list-item-base>
<span slot="headline"
>${this.hass.localize(
"ui.panel.config.tools.tabs.debug.debug_connection.title"
"ui.panel.config.developer-tools.tabs.debug.debug_connection.title"
)}</span
>
<span slot="supporting-text"
>${this.hass.localize(
"ui.panel.config.tools.tabs.debug.debug_connection.description"
"ui.panel.config.developer-tools.tabs.debug.debug_connection.description"
)}</span
>
<ha-switch
@@ -20,12 +20,12 @@ class HaDebugDisableViewTransitionRow extends LitElement {
<ha-list-item-base>
<span slot="headline"
>${this.hass.localize(
"ui.panel.config.tools.tabs.debug.disable_view_transition.title"
"ui.panel.config.developer-tools.tabs.debug.disable_view_transition.title"
)}</span
>
<span slot="supporting-text"
>${this.hass.localize(
"ui.panel.config.tools.tabs.debug.disable_view_transition.description"
"ui.panel.config.developer-tools.tabs.debug.disable_view_transition.description"
)}</span
>
<ha-switch
@@ -230,13 +230,13 @@ export class HaDebugViewportEnvironmentCard extends LitElement {
return html`
<ha-card
.header=${this.hass.localize(
"ui.panel.config.tools.tabs.debug.viewport_environment.title"
"ui.panel.config.developer-tools.tabs.debug.viewport_environment.title"
)}
>
<div class="card-content">
<p class="explanation">
${this.hass.localize(
"ui.panel.config.tools.tabs.debug.viewport_environment.description"
"ui.panel.config.developer-tools.tabs.debug.viewport_environment.description"
)}
</p>
<ha-code-editor
@@ -3,8 +3,8 @@ import type { RouterOptions } from "../../../layouts/hass-router-page";
import { HassRouterPage } from "../../../layouts/hass-router-page";
import type { HomeAssistant } from "../../../types";
@customElement("tools-router")
class ToolsRouter extends HassRouterPage {
@customElement("developer-tools-router")
class DeveloperToolsRouter extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@@ -22,37 +22,37 @@ class ToolsRouter extends HassRouterPage {
showLoading: true,
routes: {
event: {
tag: "tools-event",
load: () => import("./event/tools-event"),
tag: "developer-tools-event",
load: () => import("./event/developer-tools-event"),
},
service: "action",
action: {
tag: "tools-action",
load: () => import("./action/tools-action"),
tag: "developer-tools-action",
load: () => import("./action/developer-tools-action"),
},
state: {
tag: "tools-state",
load: () => import("./state/tools-state"),
tag: "developer-tools-state",
load: () => import("./state/developer-tools-state"),
},
template: {
tag: "tools-template",
load: () => import("./template/tools-template"),
tag: "developer-tools-template",
load: () => import("./template/developer-tools-template"),
},
statistics: {
tag: "tools-statistics",
load: () => import("./statistics/tools-statistics"),
tag: "developer-tools-statistics",
load: () => import("./statistics/developer-tools-statistics"),
},
yaml: {
tag: "tools-yaml-config",
load: () => import("./yaml_configuration/tools-yaml-config"),
tag: "developer-yaml-config",
load: () => import("./yaml_configuration/developer-yaml-config"),
},
assist: {
tag: "tools-assist",
load: () => import("./assist/tools-assist"),
tag: "developer-tools-assist",
load: () => import("./assist/developer-tools-assist"),
},
debug: {
tag: "tools-debug",
load: () => import("./debug/tools-debug"),
tag: "developer-tools-debug",
load: () => import("./debug/developer-tools-debug"),
},
},
};
@@ -77,6 +77,6 @@ class ToolsRouter extends HassRouterPage {
declare global {
interface HTMLElementTagNameMap {
"tools-router": ToolsRouter;
"developer-tools-router": DeveloperToolsRouter;
}
}
@@ -14,7 +14,7 @@ import { documentationUrl } from "../../../../util/documentation-url";
import "./event-subscribe-card";
import "./events-list";
@customElement("tools-event")
@customElement("developer-tools-event")
class HaPanelDevEvent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -40,7 +40,7 @@ class HaPanelDevEvent extends LitElement {
<div class="card-content">
<p>
${this.hass.localize(
"ui.panel.config.tools.tabs.events.description"
"ui.panel.config.developer-tools.tabs.events.description"
)}
<a
href=${documentationUrl(
@@ -51,14 +51,14 @@ class HaPanelDevEvent extends LitElement {
rel="noreferrer"
>
${this.hass.localize(
"ui.panel.config.tools.tabs.events.documentation"
"ui.panel.config.developer-tools.tabs.events.documentation"
)}
</a>
</p>
<div class="inputs">
<ha-input
.label=${this.hass.localize(
"ui.panel.config.tools.tabs.events.type"
"ui.panel.config.developer-tools.tabs.events.type"
)}
autofocus
required
@@ -67,7 +67,7 @@ class HaPanelDevEvent extends LitElement {
></ha-input>
<p>
${this.hass.localize(
"ui.panel.config.tools.tabs.events.data"
"ui.panel.config.developer-tools.tabs.events.data"
)}
</p>
</div>
@@ -85,7 +85,7 @@ class HaPanelDevEvent extends LitElement {
appearance="filled"
.disabled=${!this._isValid}
>${this.hass.localize(
"ui.panel.config.tools.tabs.events.fire_event"
"ui.panel.config.developer-tools.tabs.events.fire_event"
)}</ha-button
>
</div>
@@ -101,7 +101,7 @@ class HaPanelDevEvent extends LitElement {
<div>
<h2>
${this.hass.localize(
"ui.panel.config.tools.tabs.events.active_listeners"
"ui.panel.config.developer-tools.tabs.events.active_listeners"
)}
</h2>
<events-list
@@ -131,7 +131,7 @@ class HaPanelDevEvent extends LitElement {
if (!this._eventType) {
showAlertDialog(this, {
text: this.hass.localize(
"ui.panel.config.tools.tabs.events.alert_event_type"
"ui.panel.config.developer-tools.tabs.events.alert_event_type"
),
});
return;
@@ -143,7 +143,7 @@ class HaPanelDevEvent extends LitElement {
);
fireEvent(this, "hass-notification", {
message: this.hass.localize(
"ui.panel.config.tools.tabs.events.notification_event_fired",
"ui.panel.config.developer-tools.tabs.events.notification_event_fired",
{ type: this._eventType }
),
});
@@ -221,6 +221,6 @@ class HaPanelDevEvent extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"tools-event": HaPanelDevEvent;
"developer-tools-event": HaPanelDevEvent;
}
}
@@ -76,7 +76,7 @@ class EventSubscribeCard extends LitElement {
return html`
<ha-card
header=${this.hass!.localize(
"ui.panel.config.tools.tabs.events.listen_to_events"
"ui.panel.config.developer-tools.tabs.events.listen_to_events"
)}
>
<div class="card-content">
@@ -84,10 +84,10 @@ class EventSubscribeCard extends LitElement {
.label=${
this._subscribed
? this.hass!.localize(
"ui.panel.config.tools.tabs.events.listening_to"
"ui.panel.config.developer-tools.tabs.events.listening_to"
)
: this.hass!.localize(
"ui.panel.config.tools.tabs.events.subscribe_to"
"ui.panel.config.developer-tools.tabs.events.subscribe_to"
)
}
.disabled=${this._subscribed !== undefined}
@@ -96,11 +96,11 @@ class EventSubscribeCard extends LitElement {
></ha-input>
<ha-input
.label=${this.hass!.localize(
"ui.panel.config.tools.tabs.events.filter_events"
"ui.panel.config.developer-tools.tabs.events.filter_events"
)}
.value=${this._eventFilter}
.disabled=${this._subscribed !== undefined}
.hint=${`${this.hass!.localize("ui.panel.config.tools.tabs.events.filter_helper")}${this._ignoredEventsCount ? ` ${this.hass!.localize("ui.panel.config.tools.tabs.events.filter_ignored", { count: this._ignoredEventsCount })}` : ""}`}
.hint=${`${this.hass!.localize("ui.panel.config.developer-tools.tabs.events.filter_helper")}${this._ignoredEventsCount ? ` ${this.hass!.localize("ui.panel.config.developer-tools.tabs.events.filter_ignored", { count: this._ignoredEventsCount })}` : ""}`}
@input=${this._filterChanged}
></ha-input>
${
@@ -118,10 +118,10 @@ class EventSubscribeCard extends LitElement {
${
this._subscribed
? this.hass!.localize(
"ui.panel.config.tools.tabs.events.stop_listening"
"ui.panel.config.developer-tools.tabs.events.stop_listening"
)
: this.hass!.localize(
"ui.panel.config.tools.tabs.events.start_listening"
"ui.panel.config.developer-tools.tabs.events.start_listening"
)
}
</ha-button>
@@ -131,7 +131,7 @@ class EventSubscribeCard extends LitElement {
@click=${this._clearEvents}
>
${this.hass!.localize(
"ui.panel.config.tools.tabs.events.clear_events"
"ui.panel.config.developer-tools.tabs.events.clear_events"
)}
</ha-button>
</div>
@@ -144,10 +144,10 @@ class EventSubscribeCard extends LitElement {
if (!this._events.length) {
const message = this._subscribed
? this.hass!.localize(
"ui.panel.config.tools.tabs.events.waiting_for_events"
"ui.panel.config.developer-tools.tabs.events.waiting_for_events"
)
: this.hass!.localize(
"ui.panel.config.tools.tabs.events.subscribe_prompt"
"ui.panel.config.developer-tools.tabs.events.subscribe_prompt"
);
return html`
<ha-card class="events-card">
@@ -172,7 +172,7 @@ class EventSubscribeCard extends LitElement {
.path=${mdiChevronDoubleLeft}
.disabled=${index >= bufferTotal - 1}
.label=${this.hass!.localize(
"ui.panel.config.tools.tabs.events.oldest_event"
"ui.panel.config.developer-tools.tabs.events.oldest_event"
)}
@click=${this._showOldest}
></ha-icon-button>
@@ -180,13 +180,13 @@ class EventSubscribeCard extends LitElement {
.path=${mdiChevronLeft}
.disabled=${index >= bufferTotal - 1}
.label=${this.hass!.localize(
"ui.panel.config.tools.tabs.events.older_event"
"ui.panel.config.developer-tools.tabs.events.older_event"
)}
@click=${this._showOlder}
></ha-icon-button>
<div class="event-info">
${this.hass!.localize(
"ui.panel.config.tools.tabs.events.event_fired",
"ui.panel.config.developer-tools.tabs.events.event_fired",
{
name: position,
time: formatTimeWithSeconds(
@@ -208,7 +208,7 @@ class EventSubscribeCard extends LitElement {
<ha-tooltip for="buffer-info" placement="bottom">
<span class="buffer-tooltip">
${this.hass!.localize(
"ui.panel.config.tools.tabs.events.buffer_disclaimer",
"ui.panel.config.developer-tools.tabs.events.buffer_disclaimer",
{ count: MAX_BUFFERED_EVENTS }
)}
</span>
@@ -221,7 +221,7 @@ class EventSubscribeCard extends LitElement {
.path=${mdiChevronRight}
.disabled=${atNewest}
.label=${this.hass!.localize(
"ui.panel.config.tools.tabs.events.newer_event"
"ui.panel.config.developer-tools.tabs.events.newer_event"
)}
@click=${this._showNewer}
></ha-icon-button>
@@ -229,7 +229,7 @@ class EventSubscribeCard extends LitElement {
.path=${mdiChevronDoubleRight}
.disabled=${atNewest}
.label=${this.hass!.localize(
"ui.panel.config.tools.tabs.events.newest_event"
"ui.panel.config.developer-tools.tabs.events.newest_event"
)}
@click=${this._showNewest}
></ha-icon-button>
@@ -362,13 +362,13 @@ class EventSubscribeCard extends LitElement {
}, this._eventType);
} catch (error) {
this._error = this.hass!.localize(
"ui.panel.config.tools.tabs.events.subscribe_failed",
"ui.panel.config.developer-tools.tabs.events.subscribe_failed",
{
error:
error instanceof Error
? error.message
: this.hass!.localize(
"ui.panel.config.tools.tabs.events.unknown_error"
"ui.panel.config.developer-tools.tabs.events.unknown_error"
),
}
);
@@ -27,7 +27,7 @@ class EventsList extends LitElement {
>
<span>
${this.hass.localize(
"ui.panel.config.tools.tabs.events.count_listeners",
"ui.panel.config.developer-tools.tabs.events.count_listeners",
{
count: event.listener_count,
}
@@ -12,42 +12,42 @@ import "../../../components/ha-tab-group";
import "../../../components/ha-tab-group-tab";
import "../../../components/ha-top-app-bar-fixed";
import type { HomeAssistant, Route } from "../../../types";
import "./tools-router";
import "./developer-tools-router";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
const TOOLS_TABS = [
const DEVELOPER_TOOLS_TABS = [
{
panel: "yaml",
translationKey: "ui.panel.config.tools.tabs.yaml.title",
translationKey: "ui.panel.config.developer-tools.tabs.yaml.title",
},
{
panel: "state",
translationKey: "ui.panel.config.tools.tabs.states.title",
translationKey: "ui.panel.config.developer-tools.tabs.states.title",
},
{
panel: "action",
translationKey: "ui.panel.config.tools.tabs.actions.title",
translationKey: "ui.panel.config.developer-tools.tabs.actions.title",
},
{
panel: "template",
translationKey: "ui.panel.config.tools.tabs.templates.title",
translationKey: "ui.panel.config.developer-tools.tabs.templates.title",
},
{
panel: "event",
translationKey: "ui.panel.config.tools.tabs.events.title",
translationKey: "ui.panel.config.developer-tools.tabs.events.title",
},
{
panel: "statistics",
translationKey: "ui.panel.config.tools.tabs.statistics.title",
translationKey: "ui.panel.config.developer-tools.tabs.statistics.title",
},
{
panel: "assist",
translationKey: "ui.panel.config.tools.tabs.assist.tab",
translationKey: "ui.panel.config.developer-tools.tabs.assist.tab",
},
] as const;
@customElement("ha-panel-tools")
class PanelTools extends LitElement {
@customElement("ha-panel-developer-tools")
class PanelDeveloperTools extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public route!: Route;
@@ -68,7 +68,9 @@ class PanelTools extends LitElement {
@click=${this._handleBack}
></ha-icon-button-arrow-prev>
<div slot="title">
${this.hass.localize("ui.panel.config.dashboard.tools.main")}
${this.hass.localize(
"ui.panel.config.dashboard.developer_tools.main"
)}
</div>
<ha-dropdown slot="actionItems" @wa-select=${this._handleMenuAction}>
<ha-icon-button
@@ -77,11 +79,13 @@ class PanelTools extends LitElement {
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="debug">
${this.hass.localize("ui.panel.config.tools.tabs.debug.title")}
${this.hass.localize(
"ui.panel.config.developer-tools.tabs.debug.title"
)}
</ha-dropdown-item>
</ha-dropdown>
<ha-tab-group @wa-tab-show=${this._handlePageSelected} slot="subRow">
${TOOLS_TABS.map(
${DEVELOPER_TOOLS_TABS.map(
(tab) => html`
<ha-tab-group-tab
slot="nav"
@@ -89,7 +93,7 @@ class PanelTools extends LitElement {
.active=${page === tab.panel}
>
<a
href="/config/tools/${tab.panel}"
href="/config/developer-tools/${tab.panel}"
@click=${this._handleTabAnchorClick}
>${this.hass.localize(tab.translationKey)}</a
>
@@ -97,11 +101,11 @@ class PanelTools extends LitElement {
`
)}
</ha-tab-group>
<tools-router
<developer-tools-router
.route=${this.route}
.narrow=${this.narrow}
.hass=${this.hass}
></tools-router>
></developer-tools-router>
</ha-top-app-bar-fixed>
`;
}
@@ -120,7 +124,7 @@ class PanelTools extends LitElement {
return;
}
if (newPage !== this._page) {
navigate(`/config/tools/${newPage}`);
navigate(`/config/developer-tools/${newPage}`);
} else {
scrollTo({ behavior: "smooth", top: 0 });
}
@@ -129,7 +133,7 @@ class PanelTools extends LitElement {
private async _handleMenuAction(ev: HaDropdownSelectEvent) {
const action = ev.detail.item.value;
if (action === "debug") {
navigate(`/config/tools/debug`);
navigate(`/config/developer-tools/debug`);
}
}
@@ -142,7 +146,7 @@ class PanelTools extends LitElement {
}
static readonly styles: CSSResultGroup = css`
tools-router {
developer-tools-router {
display: block;
height: 100%;
}
@@ -166,6 +170,6 @@ class PanelTools extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"ha-panel-tools": PanelTools;
"ha-panel-developer-tools": PanelDeveloperTools;
}
}
@@ -24,7 +24,7 @@ import { haStyle } from "../../../../resources/styles";
import { loadVirtualizer } from "../../../../resources/virtualizer";
import { showToast } from "../../../../util/toast";
@customElement("tools-state-renderer")
@customElement("developer-tools-state-renderer")
class HaPanelDevStateRenderer extends LitElement {
@property({ attribute: false }) public entities: HassEntity[] = [];
@@ -79,12 +79,16 @@ class HaPanelDevStateRenderer extends LitElement {
<div class="row" role="row" aria-rowindex="1">
<div class="header" role="columnheader">
<span class="padded">
${this._i18n.localize("ui.panel.config.tools.tabs.states.entity")}
${this._i18n.localize(
"ui.panel.config.developer-tools.tabs.states.entity"
)}
</span>
</div>
<div class="header" role="columnheader">
<span class="padded">
${this._i18n.localize("ui.panel.config.tools.tabs.states.state")}
${this._i18n.localize(
"ui.panel.config.developer-tools.tabs.states.state"
)}
</span>
</div>
<div class="header" role="columnheader" ?hidden=${!showDevice}>
@@ -102,7 +106,7 @@ class HaPanelDevStateRenderer extends LitElement {
<div class="header" role="columnheader">
<span class="padded">
${this._i18n.localize(
"ui.panel.config.tools.tabs.states.attributes"
"ui.panel.config.developer-tools.tabs.states.attributes"
)}
</span>
</div>
@@ -130,7 +134,7 @@ class HaPanelDevStateRenderer extends LitElement {
<div class="cell" role="cell" aria-colspan="5">
<span class="padded">
${this._i18n.localize(
"ui.panel.config.tools.tabs.states.no_entities"
"ui.panel.config.developer-tools.tabs.states.no_entities"
)}
</span>
</div>
@@ -191,10 +195,10 @@ class HaPanelDevStateRenderer extends LitElement {
@click=${this._copyEntity}
.entity=${item}
alt=${this._i18n.localize(
"ui.panel.config.tools.tabs.states.copy_id"
"ui.panel.config.developer-tools.tabs.states.copy_id"
)}
title=${this._i18n.localize(
"ui.panel.config.tools.tabs.states.copy_id"
"ui.panel.config.developer-tools.tabs.states.copy_id"
)}
.path=${mdiClipboardTextMultipleOutline}
></ha-svg-icon>
@@ -207,10 +211,10 @@ class HaPanelDevStateRenderer extends LitElement {
@click=${this._entityMoreInfo}
.entity=${item}
alt=${this._i18n.localize(
"ui.panel.config.tools.tabs.states.more_info"
"ui.panel.config.developer-tools.tabs.states.more_info"
)}
title=${this._i18n.localize(
"ui.panel.config.tools.tabs.states.more_info"
"ui.panel.config.developer-tools.tabs.states.more_info"
)}
.path=${mdiInformationOutline}
></ha-svg-icon>
@@ -434,7 +438,7 @@ class HaPanelDevStateRenderer extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"tools-state-renderer": HaPanelDevStateRenderer;
"developer-tools-state-renderer": HaPanelDevStateRenderer;
}
interface HASSDomEvents {
@@ -42,7 +42,7 @@ import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant, HomeAssistantRegistries } from "../../../../types";
import { showToast } from "../../../../util/toast";
import "./tools-state-renderer";
import "./developer-tools-state-renderer";
// Use virtualizer after threshold to avoid performance issues
// NOTE: If virtualizer is used when filtered entiity state
@@ -51,7 +51,7 @@ import "./tools-state-renderer";
// virtualized list, an undesirable effect.
const VIRTUALIZE_THRESHOLD = 100;
@customElement("tools-state")
@customElement("developer-tools-state")
class HaPanelDevState extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -165,7 +165,7 @@ class HaPanelDevState extends LitElement {
<div class="heading">
<h1>
${this._i18n.localize(
"ui.panel.config.tools.tabs.states.current_entities"
"ui.panel.config.developer-tools.tabs.states.current_entities"
)}
</h1>
${
@@ -192,7 +192,7 @@ class HaPanelDevState extends LitElement {
@change=${this._saveAttributeCheckboxState}
>
${this._i18n.localize(
"ui.panel.config.tools.tabs.states.attributes"
"ui.panel.config.developer-tools.tabs.states.attributes"
)}
</ha-checkbox>
`
@@ -201,7 +201,7 @@ class HaPanelDevState extends LitElement {
</div>
<ha-expansion-panel
.header=${this._i18n.localize(
"ui.panel.config.tools.tabs.states.set_state"
"ui.panel.config.developer-tools.tabs.states.set_state"
)}
outlined
.expanded=${this._expanded}
@@ -209,10 +209,10 @@ class HaPanelDevState extends LitElement {
>
<p>
${this._i18n.localize(
"ui.panel.config.tools.tabs.states.description1"
"ui.panel.config.developer-tools.tabs.states.description1"
)}<br />
${this._i18n.localize(
"ui.panel.config.tools.tabs.states.description2"
"ui.panel.config.developer-tools.tabs.states.description2"
)}
</p>
${
@@ -237,7 +237,7 @@ class HaPanelDevState extends LitElement {
.path=${mdiContentCopy}
@click=${this._copyStateEntity}
title=${this._i18n.localize(
"ui.panel.config.tools.tabs.states.copy_id"
"ui.panel.config.developer-tools.tabs.states.copy_id"
)}
></ha-icon-button>
</div>
@@ -246,7 +246,7 @@ class HaPanelDevState extends LitElement {
}
<ha-input
.label=${this._i18n.localize(
"ui.panel.config.tools.tabs.states.state"
"ui.panel.config.developer-tools.tabs.states.state"
)}
required
autocapitalize="none"
@@ -259,7 +259,7 @@ class HaPanelDevState extends LitElement {
></ha-input>
<p>
${this._i18n.localize(
"ui.panel.config.tools.tabs.states.state_attributes"
"ui.panel.config.developer-tools.tabs.states.state_attributes"
)}
</p>
<ha-yaml-editor
@@ -274,7 +274,7 @@ class HaPanelDevState extends LitElement {
.disabled=${!this._validJSON}
raised
>${this._i18n.localize(
"ui.panel.config.tools.tabs.states.set_state"
"ui.panel.config.developer-tools.tabs.states.set_state"
)}</ha-button
>
<ha-icon-button
@@ -290,7 +290,7 @@ class HaPanelDevState extends LitElement {
? html`<p>
<b
>${this._i18n.localize(
"ui.panel.config.tools.tabs.states.last_changed"
"ui.panel.config.developer-tools.tabs.states.last_changed"
)}:</b
><br />
<a href=${this._historyFromLastChanged(this._entity)}
@@ -300,7 +300,7 @@ class HaPanelDevState extends LitElement {
<p>
<b
>${this._i18n.localize(
"ui.panel.config.tools.tabs.states.last_updated"
"ui.panel.config.developer-tools.tabs.states.last_updated"
)}:</b
><br />
<a href=${this._historyFromLastUpdated(this._entity)}
@@ -312,7 +312,7 @@ class HaPanelDevState extends LitElement {
</div>
</div>
</ha-expansion-panel>
<tools-state-renderer
<developer-tools-state-renderer
.narrow=${this.narrow}
.entities=${entities}
.virtualize=${entities.length > VIRTUALIZE_THRESHOLD}
@@ -324,7 +324,7 @@ class HaPanelDevState extends LitElement {
<ha-input-search
slot="filter-entities"
.label=${this._i18n.localize(
"ui.panel.config.tools.tabs.states.filter_entities"
"ui.panel.config.developer-tools.tabs.states.filter_entities"
)}
.value=${this._entityFilter}
@input=${this._entityFilterChanged}
@@ -332,7 +332,7 @@ class HaPanelDevState extends LitElement {
<ha-input-search
slot="filter-states"
.label=${this._i18n.localize(
"ui.panel.config.tools.tabs.states.filter_states"
"ui.panel.config.developer-tools.tabs.states.filter_states"
)}
type="search"
.value=${this._stateFilter}
@@ -357,13 +357,13 @@ class HaPanelDevState extends LitElement {
<ha-input-search
slot="filter-attributes"
.label=${this._i18n.localize(
"ui.panel.config.tools.tabs.states.filter_attributes"
"ui.panel.config.developer-tools.tabs.states.filter_attributes"
)}
type="search"
.value=${this._attributeFilter}
@input=${this._attributeFilterChanged}
></ha-input-search>
</tools-state-renderer>
</developer-tools-state-renderer>
`;
}
@@ -469,7 +469,7 @@ class HaPanelDevState extends LitElement {
if (!this._entityId) {
showAlertDialog(this, {
text: this._i18n.localize(
"ui.panel.config.tools.tabs.states.alert_entity_field"
"ui.panel.config.developer-tools.tabs.states.alert_entity_field"
),
});
return;
@@ -758,6 +758,6 @@ class HaPanelDevState extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"tools-state": HaPanelDevState;
"developer-tools-state": HaPanelDevState;
}
}
@@ -98,7 +98,7 @@ type DisplayedStatisticData = StatisticData & {
issues_string?: string;
};
@customElement("tools-statistics")
@customElement("developer-tools-statistics")
class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
@property({ type: Boolean, reflect: true }) public narrow = false;
@@ -188,7 +188,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
?.map(
(issue) =>
localize(
`ui.panel.config.tools.tabs.statistics.issues.${issue.type}`,
`ui.panel.config.developer-tools.tabs.statistics.issues.${issue.type}`,
issue.data
) || issue.type
)
@@ -204,7 +204,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
): DataTableColumnContainer<DisplayedStatisticData> => ({
displayName: {
title: localize(
"ui.panel.config.tools.tabs.statistics.data_table.name"
"ui.panel.config.developer-tools.tabs.statistics.data_table.name"
),
main: true,
sortable: true,
@@ -225,7 +225,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
area: getAreaTableColumn(localize),
statistic_id: {
title: localize(
"ui.panel.config.tools.tabs.statistics.data_table.statistic_id"
"ui.panel.config.developer-tools.tabs.statistics.data_table.statistic_id"
),
sortable: true,
filterable: true,
@@ -233,7 +233,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
},
statistics_unit_of_measurement: {
title: localize(
"ui.panel.config.tools.tabs.statistics.data_table.statistics_unit"
"ui.panel.config.developer-tools.tabs.statistics.data_table.statistics_unit"
),
sortable: true,
filterable: true,
@@ -241,7 +241,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
},
source: {
title: localize(
"ui.panel.config.tools.tabs.statistics.data_table.source"
"ui.panel.config.developer-tools.tabs.statistics.data_table.source"
),
sortable: true,
filterable: true,
@@ -249,7 +249,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
},
issues_string: {
title: localize(
"ui.panel.config.tools.tabs.statistics.data_table.issue"
"ui.panel.config.developer-tools.tabs.statistics.data_table.issue"
),
sortable: true,
filterable: true,
@@ -259,12 +259,14 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
template: (statistic) =>
html`${
statistic.issues_string ??
localize("ui.panel.config.tools.tabs.statistics.no_issue")
localize("ui.panel.config.developer-tools.tabs.statistics.no_issue")
}`,
},
fix: {
title: "",
label: localize("ui.panel.config.tools.tabs.statistics.fix_issue.fix"),
label: localize(
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.fix"
),
type: "icon",
template: (statistic) =>
html`${
@@ -279,8 +281,8 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
statistic.issues.some((issue) =>
FIXABLE_ISSUES.includes(issue.type)
)
? "ui.panel.config.tools.tabs.statistics.fix_issue.fix"
: "ui.panel.config.tools.tabs.statistics.fix_issue.info"
? "ui.panel.config.developer-tools.tabs.statistics.fix_issue.fix"
: "ui.panel.config.developer-tools.tabs.statistics.fix_issue.info"
)}
</ha-button>`
: "—"
@@ -291,7 +293,9 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
},
actions: {
title: "",
label: localize("ui.panel.config.tools.tabs.statistics.adjust_sum"),
label: localize(
"ui.panel.config.developer-tools.tabs.statistics.adjust_sum"
),
type: "icon-button",
showNarrow: true,
template: (statistic) =>
@@ -299,7 +303,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
? html`
<ha-icon-button
.label=${localize(
"ui.panel.config.tools.tabs.statistics.adjust_sum"
"ui.panel.config.developer-tools.tabs.statistics.adjust_sum"
)}
.path=${mdiSlopeUphill}
.statistic=${statistic}
@@ -496,7 +500,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
</ha-dropdown-item>
<ha-dropdown-item @click=${this._selectAllIssues}>
${this._i18n.localize(
"ui.panel.config.tools.tabs.statistics.data_table.select_all_issues"
"ui.panel.config.developer-tools.tabs.statistics.data_table.select_all_issues"
)}
</ha-dropdown-item>
<ha-dropdown-item @click=${this._selectNone}>
@@ -525,7 +529,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
</div>
<ha-assist-chip
.label=${this._i18n.localize(
"ui.panel.config.tools.tabs.statistics.delete_selected"
"ui.panel.config.developer-tools.tabs.statistics.delete_selected"
)}
.disabled=${!this._selected.length}
@click=${this._clearSelected}
@@ -559,7 +563,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
this._registries.areas
)}
.noDataText=${this._i18n.localize(
"ui.panel.config.tools.tabs.statistics.data_table.no_statistics"
"ui.panel.config.developer-tools.tabs.statistics.data_table.no_statistics"
)}
.filter=${this.filter}
.selectable=${this._selectMode}
@@ -766,10 +770,10 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
await showConfirmationDialog(this, {
title: this._i18n.localize(
"ui.panel.config.tools.tabs.statistics.multi_delete.title"
"ui.panel.config.developer-tools.tabs.statistics.multi_delete.title"
),
text: html`${this._i18n.localize(
"ui.panel.config.tools.tabs.statistics.multi_delete.info_text",
"ui.panel.config.developer-tools.tabs.statistics.multi_delete.info_text",
{ statistic_count: deletableIds.length }
)}`,
confirmText: this._i18n.localize("ui.common.delete"),
@@ -921,6 +925,6 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
declare global {
interface HTMLElementTagNameMap {
"tools-statistics": HaPanelDevStatistics;
"developer-tools-statistics": HaPanelDevStatistics;
}
}
@@ -132,7 +132,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
@click=${this._fetchOutliers}
>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.outliers"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.outliers"
)}
</ha-button>
<ha-button slot="primaryAction" data-dialog="close">
@@ -156,7 +156,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
@click=${this._fixIssue}
>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.adjust"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.adjust"
)}</ha-button
>
`;
@@ -166,7 +166,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
<ha-dialog
.open=${this._open}
header-title=${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.title"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.title"
)}
.preventScrimClose=${this.isDirtyState}
@closed=${this._dialogClosed}
@@ -194,7 +194,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
} else if (this._statsHour.length < 1 && this._stats5min.length < 1) {
stats = html`<p>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.no_statistics_found"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.no_statistics_found"
)}
</p>`;
} else {
@@ -234,20 +234,20 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
return html`
<div class="text-content">
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.info_text_1"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.info_text_1"
)}
</div>
<div class="text-content">
<b
>${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.statistic"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.statistic"
)}</b
>
${this._params!.statistic.statistic_id}
</div>
<ha-selector-datetime
.label=${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.pick_a_time"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.pick_a_time"
)}
.hass=${this.hass}
.selector=${this._dateTimeSelector}
@@ -289,7 +289,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
<div class="text-content">
<b
>${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.statistic"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.statistic"
)}</b
>
${this._params!.statistic.statistic_id}
@@ -298,7 +298,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
<div class="table-row">
<span
>${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.start"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.start"
)}</span
>
<span
@@ -313,7 +313,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
<div class="table-row">
<span
>${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.end"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.end"
)}</span
>
<span
@@ -327,7 +327,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
<ha-selector-number
.label=${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.new_value"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.new_value"
)}
.hass=${this.hass}
.selector=${this._amountSelector(unit || undefined, this._precision)}
@@ -506,7 +506,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
this._busy = false;
showAlertDialog(this, {
text: this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.error_sum_adjusted",
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.error_sum_adjusted",
{ message: err.message || err }
),
});
@@ -514,7 +514,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends DirtyStateProvid
}
showToast(this, {
message: this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.adjust_sum.sum_adjusted"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.adjust_sum.sum_adjusted"
),
});
this._markDirtyStateClean();
@@ -53,13 +53,13 @@ export class DialogStatisticsFixUnitsChanged extends LitElement {
<ha-dialog
.open=${this._open}
header-title=${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.units_changed.title"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.units_changed.title"
)}
@closed=${this._dialogClosed}
>
<p>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.units_changed.info_text_1",
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.units_changed.info_text_1",
{
name: getStatisticLabel(
this.hass,
@@ -72,16 +72,16 @@ export class DialogStatisticsFixUnitsChanged extends LitElement {
}
)}<br />
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.units_changed.info_text_2"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.units_changed.info_text_2"
)}<br />
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.units_changed.info_text_3"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.units_changed.info_text_3"
)}
</p>
<ha-radio-group
.label=${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.units_changed.how_to_fix"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.units_changed.how_to_fix"
)}
.value=${this._action}
name="action"
@@ -89,13 +89,13 @@ export class DialogStatisticsFixUnitsChanged extends LitElement {
>
<ha-radio-option value="update" autofocus>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.units_changed.update",
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.units_changed.update",
this._params.issue.data
)}
</ha-radio-option>
<ha-radio-option value="clear">
${this.hass.localize(
`ui.panel.config.tools.tabs.statistics.fix_issue.units_changed.clear`
`ui.panel.config.developer-tools.tabs.statistics.fix_issue.units_changed.clear`
)}
</ha-radio-option>
</ha-radio-group>
@@ -110,7 +110,7 @@ export class DialogStatisticsFixUnitsChanged extends LitElement {
</ha-button>
<ha-button slot="primaryAction" @click=${this._fixIssue}>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.fix"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.fix"
)}
</ha-button>
</ha-dialog-footer>
@@ -50,13 +50,13 @@ export class DialogStatisticsFix extends LitElement {
<ha-dialog
.open=${this._open}
header-title=${this.hass.localize(
`ui.panel.config.tools.tabs.statistics.fix_issue.${issue.type}.title`
`ui.panel.config.developer-tools.tabs.statistics.fix_issue.${issue.type}.title`
)}
@closed=${this._dialogClosed}
>
<p>
${this.hass.localize(
`ui.panel.config.tools.tabs.statistics.fix_issue.${issue.type}.info_text_1`,
`ui.panel.config.developer-tools.tabs.statistics.fix_issue.${issue.type}.info_text_1`,
{
name: getStatisticLabel(
this.hass,
@@ -67,24 +67,24 @@ export class DialogStatisticsFix extends LitElement {
...(issue.type === "mean_type_changed"
? {
metadata_mean_type: this.hass.localize(
`ui.panel.config.tools.tabs.statistics.mean_type.${issue.data.metadata_mean_type}`
`ui.panel.config.developer-tools.tabs.statistics.mean_type.${issue.data.metadata_mean_type}`
),
state_mean_type: this.hass.localize(
`ui.panel.config.tools.tabs.statistics.mean_type.${issue.data.state_mean_type}`
`ui.panel.config.developer-tools.tabs.statistics.mean_type.${issue.data.state_mean_type}`
),
}
: {}),
}
)}<br /><br />
${this.hass.localize(
`ui.panel.config.tools.tabs.statistics.fix_issue.${issue.type}.info_text_2`,
`ui.panel.config.developer-tools.tabs.statistics.fix_issue.${issue.type}.info_text_2`,
{ statistic_id: issue.data.statistic_id }
)}
${
issue.type === "mean_type_changed"
? html`<br /><br />
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.mean_type_changed.info_text_3",
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.mean_type_changed.info_text_3",
{ statistic_id: issue.data.statistic_id }
)}`
: issue.type === "entity_not_recorded"
@@ -98,7 +98,7 @@ export class DialogStatisticsFix extends LitElement {
rel="noreferrer noopener"
>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.entity_not_recorded.info_text_3_link"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.entity_not_recorded.info_text_3_link"
)}</a
>`
: issue.type === "entity_no_longer_recorded"
@@ -111,22 +111,22 @@ export class DialogStatisticsFix extends LitElement {
rel="noreferrer noopener"
>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.entity_no_longer_recorded.info_text_3_link"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.entity_no_longer_recorded.info_text_3_link"
)}</a
><br /><br />
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.entity_no_longer_recorded.info_text_4"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.entity_no_longer_recorded.info_text_4"
)}`
: issue.type === "state_class_removed"
? html`<ul>
<li>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.state_class_removed.info_text_3"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.state_class_removed.info_text_3"
)}
</li>
<li>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.state_class_removed.info_text_4"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.state_class_removed.info_text_4"
)}
<a
href="https://developers.home-assistant.io/docs/core/entity/sensor/#long-term-statistics"
@@ -134,18 +134,18 @@ export class DialogStatisticsFix extends LitElement {
rel="noreferrer noopener"
>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.state_class_removed.info_text_4_link"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.state_class_removed.info_text_4_link"
)}</a
>
</li>
<li>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.state_class_removed.info_text_5"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.state_class_removed.info_text_5"
)}
</li>
</ul>
${this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.state_class_removed.info_text_6",
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.state_class_removed.info_text_6",
{ statistic_id: issue.data.statistic_id }
)}`
: nothing
@@ -195,15 +195,15 @@ export class DialogStatisticsFix extends LitElement {
title:
err.code === "timeout"
? this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.clearing_timeout_title"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.clearing_timeout_title"
)
: this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.clearing_failed"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.clearing_failed"
),
text:
err.code === "timeout"
? this.hass.localize(
"ui.panel.config.tools.tabs.statistics.fix_issue.clearing_timeout_text"
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.clearing_timeout_text"
)
: err.message,
});
@@ -4,7 +4,6 @@ import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
import type { LocalizeKeys } from "../../../../common/translations/localize";
import { debounce } from "../../../../common/util/debounce";
import "../../../../components/ha-alert";
import "../../../../components/ha-button";
@@ -41,16 +40,7 @@ For loop example getting entity values in the weather domain:
{{ state.name | lower }} is {{state.state_with_unit}}
{%- endfor %}.`;
// key resolves the label/description translation keys; path is passed through
// documentationUrl().
const TEMPLATE_DOCS_LINKS: { key: string; path: string }[] = [
{ key: "docs_introduction", path: "/docs/templating/introduction/" },
{ key: "docs_states", path: "/docs/templating/states/" },
{ key: "docs_debugging", path: "/docs/templating/debugging/" },
{ key: "docs_functions", path: "/template-functions/" },
];
@customElement("tools-template")
@customElement("developer-tools-template")
class HaPanelDevTemplate extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -118,7 +108,7 @@ class HaPanelDevTemplate extends LitElement {
<div class="content">
<ha-expansion-panel
.header=${this.hass.localize(
"ui.panel.config.tools.tabs.templates.about"
"ui.panel.config.developer-tools.tabs.templates.about"
)}
outlined
.expanded=${this._descriptionExpanded}
@@ -127,39 +117,34 @@ class HaPanelDevTemplate extends LitElement {
<div class="description">
<p>
${this.hass.localize(
"ui.panel.config.tools.tabs.templates.description"
"ui.panel.config.developer-tools.tabs.templates.description"
)}
</p>
<p>
${this.hass.localize(
"ui.panel.config.tools.tabs.templates.engine_info"
)}
</p>
<h3>
${this.hass.localize(
"ui.panel.config.tools.tabs.templates.learn_more"
)}
</h3>
<ul>
${TEMPLATE_DOCS_LINKS.map(
(link) => html`
<li>
<a
href=${documentationUrl(this.hass, link.path)}
target="_blank"
rel="noreferrer"
>${this.hass.localize(
`ui.panel.config.tools.tabs.templates.${link.key}` as LocalizeKeys
)}</a
>
<span class="link-description"
>${this.hass.localize(
`ui.panel.config.tools.tabs.templates.${link.key}_description` as LocalizeKeys
)}</span
>
</li>
`
)}
<li>
<a
href="https://jinja.palletsprojects.com/en/latest/templates/"
target="_blank"
rel="noreferrer"
>${this.hass.localize(
"ui.panel.config.developer-tools.tabs.templates.jinja_documentation"
)}
</a>
</li>
<li>
<a
href=${documentationUrl(
this.hass,
"/docs/configuration/templating/"
)}
target="_blank"
rel="noreferrer"
>
${this.hass.localize(
"ui.panel.config.developer-tools.tabs.templates.template_extensions"
)}</a
>
</li>
</ul>
</div>
</ha-expansion-panel>
@@ -174,7 +159,7 @@ class HaPanelDevTemplate extends LitElement {
<ha-card
class="edit-pane"
header=${this.hass.localize(
"ui.panel.config.tools.tabs.templates.editor"
"ui.panel.config.developer-tools.tabs.templates.editor"
)}
>
<div class="card-content">
@@ -192,7 +177,7 @@ class HaPanelDevTemplate extends LitElement {
<div class="card-actions">
<ha-button appearance="plain" @click=${this._restoreDemo}>
${this.hass.localize(
"ui.panel.config.tools.tabs.templates.reset"
"ui.panel.config.developer-tools.tabs.templates.reset"
)}
</ha-button>
<ha-button appearance="plain" @click=${this._clear}>
@@ -201,7 +186,7 @@ class HaPanelDevTemplate extends LitElement {
</div>
<ha-tip>
${this.hass.localize(
"ui.panel.config.tools.tabs.templates.keyboard_tip",
"ui.panel.config.developer-tools.tabs.templates.keyboard_tip",
{
autocomplete: html`<kbd>Ctrl</kbd>+<kbd>Space</kbd>`,
}
@@ -212,7 +197,7 @@ class HaPanelDevTemplate extends LitElement {
<ha-card
class="render-pane"
header=${this.hass.localize(
"ui.panel.config.tools.tabs.templates.result"
"ui.panel.config.developer-tools.tabs.templates.result"
)}
>
<div class="card-content ha-scrollbar">
@@ -246,7 +231,7 @@ ${
}</pre>
<p>
${this.hass.localize(
"ui.panel.config.tools.tabs.templates.result_type"
"ui.panel.config.developer-tools.tabs.templates.result_type"
)}:
${resultType}
</p>
@@ -255,7 +240,7 @@ ${
? html`
<p>
${this.hass.localize(
"ui.panel.config.tools.tabs.templates.time"
"ui.panel.config.developer-tools.tabs.templates.time"
)}
</p>
`
@@ -268,7 +253,7 @@ ${
? html`
<p class="all_listeners">
${this.hass.localize(
"ui.panel.config.tools.tabs.templates.all_listeners"
"ui.panel.config.developer-tools.tabs.templates.all_listeners"
)}
</p>
`
@@ -277,7 +262,7 @@ ${
? html`
<p>
${this.hass.localize(
"ui.panel.config.tools.tabs.templates.listeners"
"ui.panel.config.developer-tools.tabs.templates.listeners"
)}
</p>
<ul>
@@ -288,7 +273,7 @@ ${
<li>
<b
>${this.hass.localize(
"ui.panel.config.tools.tabs.templates.domain"
"ui.panel.config.developer-tools.tabs.templates.domain"
)}</b
>: ${domain}
</li>
@@ -301,7 +286,7 @@ ${
<li>
<b
>${this.hass.localize(
"ui.panel.config.tools.tabs.templates.entity"
"ui.panel.config.developer-tools.tabs.templates.entity"
)}</b
>: ${entity_id}
</li>
@@ -312,7 +297,7 @@ ${
: !this._templateResult.listeners.time
? html`<span class="all_listeners">
${this.hass.localize(
"ui.panel.config.tools.tabs.templates.no_listeners"
"ui.panel.config.developer-tools.tabs.templates.no_listeners"
)}
</span>`
: nothing
@@ -456,17 +441,6 @@ ${
margin-block-start: var(--ha-space-1);
margin-block-end: var(--ha-space-1);
}
.description > h3 {
font-size: var(--ha-font-size-m);
font-weight: var(--ha-font-weight-medium);
margin-block-end: var(--ha-space-1);
}
.description li {
margin-block-end: var(--ha-space-1);
}
.description .link-description {
color: var(--secondary-text-color);
}
.render-pane .card-content {
user-select: text;
@@ -622,7 +596,7 @@ ${
if (
!(await showConfirmationDialog(this, {
text: this.hass.localize(
"ui.panel.config.tools.tabs.templates.confirm_reset"
"ui.panel.config.developer-tools.tabs.templates.confirm_reset"
),
warning: true,
}))
@@ -638,7 +612,7 @@ ${
if (
!(await showConfirmationDialog(this, {
text: this.hass.localize(
"ui.panel.config.tools.tabs.templates.confirm_clear"
"ui.panel.config.developer-tools.tabs.templates.confirm_clear"
),
warning: true,
}))
@@ -658,6 +632,6 @@ ${
declare global {
interface HTMLElementTagNameMap {
"tools-template": HaPanelDevTemplate;
"developer-tools-template": HaPanelDevTemplate;
}
}
@@ -16,7 +16,7 @@ import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant, Route, TranslationDict } from "../../../../types";
type ReloadableDomain = Exclude<
keyof TranslationDict["ui"]["panel"]["config"]["tools"]["tabs"]["yaml"]["section"]["reloading"],
keyof TranslationDict["ui"]["panel"]["config"]["developer-tools"]["tabs"]["yaml"]["section"]["reloading"],
"heading" | "introduction" | "reload"
>;
@@ -25,8 +25,8 @@ interface TranslatedReloadableDomain {
name: string;
}
@customElement("tools-yaml-config")
export class ToolsYamlConfig extends LitElement {
@customElement("developer-yaml-config")
export class DeveloperYamlConfig extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
@@ -61,10 +61,10 @@ export class ToolsYamlConfig extends LitElement {
domain,
name:
this.hass.localize(
`ui.panel.config.tools.tabs.yaml.section.reloading.${domain}`
`ui.panel.config.developer-tools.tabs.yaml.section.reloading.${domain}`
) ||
this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.reloading.reload",
"ui.panel.config.developer-tools.tabs.yaml.section.reloading.reload",
{ domain: domainToName(this.hass.localize, domain) }
),
}))
@@ -80,12 +80,12 @@ export class ToolsYamlConfig extends LitElement {
<ha-card
outlined
header=${this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.validation.heading"
"ui.panel.config.developer-tools.tabs.yaml.section.validation.heading"
)}
>
<div class="card-content">
${this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.validation.introduction"
"ui.panel.config.developer-tools.tabs.yaml.section.validation.introduction"
)}
${
!this._validateResult
@@ -103,10 +103,10 @@ export class ToolsYamlConfig extends LitElement {
${
this._validateResult.result === "valid"
? this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.validation.valid"
"ui.panel.config.developer-tools.tabs.yaml.section.validation.valid"
)
: this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.validation.invalid"
"ui.panel.config.developer-tools.tabs.yaml.section.validation.invalid"
)
}
</div>
@@ -116,7 +116,7 @@ export class ToolsYamlConfig extends LitElement {
? html`<ha-alert
alert-type="error"
.title=${this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.validation.errors"
"ui.panel.config.developer-tools.tabs.yaml.section.validation.errors"
)}
>
<!-- prettier-ignore -->
@@ -131,7 +131,7 @@ export class ToolsYamlConfig extends LitElement {
? html`<ha-alert
alert-type="warning"
.title=${this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.validation.warnings"
"ui.panel.config.developer-tools.tabs.yaml.section.validation.warnings"
)}
>
<!-- prettier-ignore -->
@@ -148,7 +148,7 @@ export class ToolsYamlConfig extends LitElement {
<div class="card-actions">
<ha-button appearance="plain" @click=${this._validateConfig}>
${this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.validation.check_config"
"ui.panel.config.developer-tools.tabs.yaml.section.validation.check_config"
)}
</ha-button>
<ha-button
@@ -158,7 +158,7 @@ export class ToolsYamlConfig extends LitElement {
.disabled=${this._validateResult?.result === "invalid"}
>
${this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.server_management.restart"
"ui.panel.config.developer-tools.tabs.yaml.section.server_management.restart"
)}
</ha-button>
</div>
@@ -166,18 +166,18 @@ export class ToolsYamlConfig extends LitElement {
<ha-card
outlined
header=${this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.reloading.heading"
"ui.panel.config.developer-tools.tabs.yaml.section.reloading.heading"
)}
>
<div class="card-content">
${this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.reloading.introduction"
"ui.panel.config.developer-tools.tabs.yaml.section.reloading.introduction"
)}
</div>
<div class="card-actions">
<ha-call-service-button domain="homeassistant" service="reload_all"
>${this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.reloading.all"
"ui.panel.config.developer-tools.tabs.yaml.section.reloading.all"
)}
</ha-call-service-button>
</div>
@@ -186,7 +186,7 @@ export class ToolsYamlConfig extends LitElement {
domain="homeassistant"
service="reload_core_config"
>${this.hass.localize(
"ui.panel.config.tools.tabs.yaml.section.reloading.core"
"ui.panel.config.developer-tools.tabs.yaml.section.reloading.core"
)}
</ha-call-service-button>
</div>
@@ -269,6 +269,6 @@ export class ToolsYamlConfig extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"tools-yaml-config": ToolsYamlConfig;
"developer-yaml-config": DeveloperYamlConfig;
}
}
+3 -3
View File
@@ -70,9 +70,9 @@ class HaPanelConfig extends HassRouterPage {
tag: "ha-config-system-navigation",
load: () => import("./core/ha-config-system-navigation"),
},
tools: {
tag: "ha-panel-tools",
load: () => import("./tools/ha-panel-tools"),
"developer-tools": {
tag: "ha-panel-developer-tools",
load: () => import("./developer-tools/ha-panel-developer-tools"),
cache: true,
},
logs: {
@@ -18,7 +18,7 @@ import {
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { fixStatisticsIssue } from "../tools/statistics/fix-statistics";
import { fixStatisticsIssue } from "../developer-tools/statistics/fix-statistics";
import { showVacuumSegmentMappingDialog } from "../entities/dialogs/show-dialog-vacuum-segment-mapping";
import { showRepairsFlowDialog } from "./show-dialog-repair-flow";
import { showRepairsIssueDialog } from "./show-repair-issue-dialog";
@@ -171,7 +171,7 @@ class HaConfigRepairs extends LitElement {
issue.translation_key &&
STATISTIC_TYPES.includes(issue.translation_key as any)
) {
this.hass.loadFragmentTranslation("config");
this.hass.loadFragmentTranslation("developer-tools");
const data = await fetchRepairsIssueData(
this.hass.connection,
issue.domain,
+1 -7
View File
@@ -273,13 +273,7 @@ const computeLogbookValue = (
if (item.entity_id && item.state) {
return {
text: stateObj
? localizeStateMessage(
hass,
item.state,
stateObj,
domain!,
item.attributes
)
? localizeStateMessage(hass, item.state, stateObj, domain!)
: item.state,
type: "state",
};
@@ -2,7 +2,6 @@ import { mdiVolumeHigh, mdiVolumeOff } from "@mdi/js";
import { html, nothing } from "lit";
import type { TemplateResult } from "lit";
import { supportsFeature } from "../../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-control-button";
import "../../../../components/ha-svg-icon";
import { forwardHaptic } from "../../../../data/haptics";
@@ -13,7 +12,7 @@ import {
import type { HomeAssistant } from "../../../../types";
export const renderMuteButton = (
localize: LocalizeFunc,
hass: HomeAssistant,
stateObj: MediaPlayerEntity,
showMuteButton: boolean | undefined,
disabled: boolean,
@@ -29,7 +28,7 @@ export const renderMuteButton = (
return html`
<ha-control-button
class="mute"
.label=${localize(
.label=${hass.localize(
`ui.card.media_player.${isMuted ? "media_volume_unmute" : "media_volume_mute"}`
)}
.disabled=${disabled}
@@ -44,13 +43,13 @@ export const renderMuteButton = (
export const toggleMediaPlayerMute = (
ev: Event,
callService: HomeAssistant["callService"],
hass: HomeAssistant,
stateObj: MediaPlayerEntity,
el: HTMLElement
): void => {
ev.stopPropagation();
forwardHaptic(el, "light");
callService("media_player", "volume_mute", {
hass.callService("media_player", "volume_mute", {
entity_id: stateObj.entity_id,
is_volume_muted: !stateObj.attributes.is_volume_muted,
});
@@ -1,19 +1,12 @@
import { consume } from "@lit/context";
import { mdiShieldOff } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { stateColorCss } from "../../../common/entity/state_color";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-control-select";
@@ -28,9 +21,8 @@ import {
setProtectedAlarmControlPanelMode,
supportedAlarmModes,
} from "../../../data/alarm_control_panel";
import { apiContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
@@ -39,11 +31,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsAlarmModesCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "alarm_control_panel";
};
export const supportsAlarmModesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -52,7 +39,8 @@ export const supportsAlarmModesCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsAlarmModesCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "alarm_control_panel";
};
@customElement("hui-alarm-modes-card-feature")
@@ -60,20 +48,10 @@ class HuiAlarmModeCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: AlarmControlPanelEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: AlarmModesCardFeatureConfig;
@state() _currentMode?: AlarmMode;
@@ -96,10 +74,25 @@ class HuiAlarmModeCardFeature
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as
AlarmControlPanelEntity | undefined;
}
protected willUpdate(changedProp: PropertyValues<this>): void {
super.willUpdate(changedProp);
if (changedProp.has("_stateObj") && this._stateObj) {
this._currentMode = this._getCurrentMode(this._stateObj);
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentMode = this._getCurrentMode(this._stateObj);
}
}
}
@@ -133,11 +126,7 @@ class HuiAlarmModeCardFeature
private async _setMode(mode: AlarmMode) {
await setProtectedAlarmControlPanelMode(
this,
{
callService: this._api.callService,
callWS: this._api.callWS,
localize: this._localize,
},
this.hass!,
this._stateObj!,
mode
);
@@ -146,9 +135,10 @@ class HuiAlarmModeCardFeature
protected render(): TemplateResult | typeof nothing {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsAlarmModesCardFeatureFromState(this._stateObj)
!supportsAlarmModesCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -162,7 +152,7 @@ class HuiAlarmModeCardFeature
this._config.modes
).map<ControlSelectOption>((mode) => ({
value: mode,
label: this._localize(`ui.card.alarm_control_panel.modes.${mode}`),
label: this.hass!.localize(`ui.card.alarm_control_panel.modes.${mode}`),
path: ALARM_MODES[mode].path,
}));
@@ -170,7 +160,7 @@ class HuiAlarmModeCardFeature
return html`
<ha-control-button-group>
<ha-control-button
.label=${this._localize("ui.card.alarm_control_panel.disarm")}
.label=${this.hass.localize("ui.card.alarm_control_panel.disarm")}
@click=${this._disarm}
>
<ha-svg-icon .path=${mdiShieldOff}></ha-svg-icon>
@@ -185,7 +175,7 @@ class HuiAlarmModeCardFeature
.value=${this._currentMode}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this._localize("ui.card.alarm_control_panel.modes_label")}
.label=${this.hass.localize("ui.card.alarm_control_panel.modes_label")}
style=${styleMap({
"--control-select-color": color,
"--modes-count": options.length.toString(),
@@ -1,7 +1,5 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { css, LitElement, nothing, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { consumeEntityState } from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import { isNumericFromAttributes } from "../../../common/number/format_number";
import type { HomeAssistant } from "../../../types";
@@ -11,11 +9,6 @@ import type {
BarGaugeCardFeatureConfig,
} from "./types";
const supportsBarGaugeCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "sensor" && isNumericFromAttributes(stateObj.attributes);
};
export const supportsBarGaugeCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -24,16 +17,15 @@ export const supportsBarGaugeCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsBarGaugeCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "sensor" && isNumericFromAttributes(stateObj.attributes);
};
@customElement("hui-bar-gauge-card-feature")
class HuiBarGaugeCardFeature extends LitElement implements LovelaceCardFeature {
@property({ attribute: false }) public context!: LovelaceCardFeatureContext;
@property({ attribute: false }) public hass!: HomeAssistant;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: HassEntity;
@property({ attribute: false }) public context!: LovelaceCardFeatureContext;
@state() private _config?: BarGaugeCardFeatureConfig;
@@ -58,13 +50,15 @@ class HuiBarGaugeCardFeature extends LitElement implements LovelaceCardFeature {
render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsBarGaugeCardFeatureFromState(this._stateObj)
!this.context.entity_id ||
!this.hass.states[this.context.entity_id] ||
!supportsBarGaugeCardFeature(this.hass, this.context)
) {
return nothing;
}
const stateObj = this._stateObj;
const stateObj = this.hass.states[this.context.entity_id];
const min = this._config.min ?? 0;
const max = this._config.max ?? 100;
const value = parseFloat(stateObj.state);
@@ -1,22 +1,15 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import { apiContext, servicesContext } from "../../../data/context";
import {
hasRequiredScriptFieldsForServices,
requiredScriptFieldsFilledForServices,
hasRequiredScriptFields,
requiredScriptFieldsFilled,
} from "../../../data/script";
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -24,11 +17,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsButtonCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return ["button", "input_button", "scene", "script"].includes(domain);
};
export const supportsButtonCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -37,33 +25,27 @@ export const supportsButtonCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsButtonCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return ["button", "input_button", "scene", "script"].includes(domain);
};
@customElement("hui-button-card-feature")
class HuiButtonCardFeature extends LitElement implements LovelaceCardFeature {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: HassEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: servicesContext, subscribe: true })
private _services!: HomeAssistant["services"];
@state() private _config?: ButtonCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as HassEntity | undefined;
}
private _pressButton() {
if (!this._stateObj) return;
if (!this.hass || !this._stateObj) return;
const domain = computeDomain(this._stateObj.entity_id);
const service =
@@ -72,12 +54,8 @@ class HuiButtonCardFeature extends LitElement implements LovelaceCardFeature {
if (domain === "script") {
const entityId = this._stateObj.entity_id;
if (
hasRequiredScriptFieldsForServices(this._services, entityId) &&
!requiredScriptFieldsFilledForServices(
this._services,
entityId,
this._config?.data
)
hasRequiredScriptFields(this.hass!, entityId) &&
!requiredScriptFieldsFilled(this.hass!, entityId, this._config?.data)
) {
showMoreInfoDialog(this, {
entityId: entityId,
@@ -96,7 +74,7 @@ class HuiButtonCardFeature extends LitElement implements LovelaceCardFeature {
: {}),
};
this._api.callService(domain, service, serviceData);
this.hass.callService(domain, service, serviceData);
}
static getStubConfig(): ButtonCardFeatureConfig {
@@ -115,9 +93,10 @@ class HuiButtonCardFeature extends LitElement implements LovelaceCardFeature {
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsButtonCardFeatureFromState(this._stateObj)
!supportsButtonCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -129,7 +108,10 @@ class HuiButtonCardFeature extends LitElement implements LovelaceCardFeature {
class="press-button"
@click=${this._pressButton}
>
${this._config.action_name ?? this._localize("ui.card.button.press")}
${
this._config.action_name ??
this.hass.localize("ui.card.button.press")
}
</ha-control-button>
</ha-control-button-group>
`;
@@ -1,5 +1,4 @@
import { mdiFan } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
@@ -13,14 +12,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsClimateFanModesCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "climate" &&
supportsFeature(stateObj, ClimateEntityFeature.FAN_MODE)
);
};
export const supportsClimateFanModesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -29,7 +20,11 @@ export const supportsClimateFanModesCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsClimateFanModesCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "climate" &&
supportsFeature(stateObj, ClimateEntityFeature.FAN_MODE)
);
};
@customElement("hui-climate-fan-modes-card-feature")
@@ -68,8 +63,9 @@ class HuiClimateFanModesCardFeature
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsClimateFanModesCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsClimateFanModesCardFeature(this.hass, this.context)
);
}
}
@@ -1,5 +1,4 @@
import { mdiThermostat } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit";
import { html } from "lit";
import { customElement } from "lit/decorators";
@@ -26,11 +25,6 @@ interface HvacModeOption extends HuiModeSelectOption {
iconPath: string;
}
const supportsClimateHvacModesCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "climate";
};
export const supportsClimateHvacModesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -39,7 +33,8 @@ export const supportsClimateHvacModesCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsClimateHvacModesCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "climate";
};
@customElement("hui-climate-hvac-modes-card-feature")
@@ -65,7 +60,7 @@ class HuiClimateHvacModesCardFeature
protected readonly _serviceAction = "set_hvac_mode";
protected get _label(): string {
return this._localize("ui.card.climate.mode");
return this.hass!.localize("ui.card.climate.mode");
}
protected readonly _showDropdownOptionIcons = false;
@@ -99,7 +94,7 @@ class HuiClimateHvacModesCardFeature
}
protected _getOptions(): HvacModeOption[] {
if (!this._stateObj) {
if (!this._stateObj || !this.hass) {
return [];
}
@@ -111,7 +106,7 @@ class HuiClimateHvacModesCardFeature
return filterModes(orderedHvacModes, this._config?.hvac_modes).map(
(mode) => ({
value: mode,
label: this._formatters.formatEntityState(this._stateObj!, mode),
label: this.hass!.formatEntityState(this._stateObj!, mode),
iconPath: climateHvacModeIcon(mode),
})
);
@@ -126,8 +121,9 @@ class HuiClimateHvacModesCardFeature
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsClimateHvacModesCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsClimateHvacModesCardFeature(this.hass, this.context)
);
}
}
@@ -1,5 +1,4 @@
import { mdiTuneVariant } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
@@ -13,16 +12,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsClimatePresetModesCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "climate" &&
supportsFeature(stateObj, ClimateEntityFeature.PRESET_MODE)
);
};
export const supportsClimatePresetModesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -31,7 +20,11 @@ export const supportsClimatePresetModesCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsClimatePresetModesCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "climate" &&
supportsFeature(stateObj, ClimateEntityFeature.PRESET_MODE)
);
};
@customElement("hui-climate-preset-modes-card-feature")
@@ -72,8 +65,9 @@ class HuiClimatePresetModesCardFeature
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsClimatePresetModesCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsClimatePresetModesCardFeature(this.hass, this.context)
);
}
}
@@ -1,5 +1,4 @@
import { mdiArrowOscillating } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
@@ -13,16 +12,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsClimateSwingHorizontalModesCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "climate" &&
supportsFeature(stateObj, ClimateEntityFeature.SWING_HORIZONTAL_MODE)
);
};
export const supportsClimateSwingHorizontalModesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -31,7 +20,11 @@ export const supportsClimateSwingHorizontalModesCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsClimateSwingHorizontalModesCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "climate" &&
supportsFeature(stateObj, ClimateEntityFeature.SWING_HORIZONTAL_MODE)
);
};
@customElement("hui-climate-swing-horizontal-modes-card-feature")
@@ -72,8 +65,9 @@ class HuiClimateSwingHorizontalModesCardFeature
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsClimateSwingHorizontalModesCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsClimateSwingHorizontalModesCardFeature(this.hass, this.context)
);
}
}
@@ -1,5 +1,4 @@
import { mdiArrowOscillating } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
@@ -13,16 +12,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsClimateSwingModesCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "climate" &&
supportsFeature(stateObj, ClimateEntityFeature.SWING_MODE)
);
};
export const supportsClimateSwingModesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -31,7 +20,11 @@ export const supportsClimateSwingModesCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsClimateSwingModesCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "climate" &&
supportsFeature(stateObj, ClimateEntityFeature.SWING_MODE)
);
};
@customElement("hui-climate-swing-modes-card-feature")
@@ -72,8 +65,9 @@ class HuiClimateSwingModesCardFeature
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsClimateSwingModesCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsClimateSwingModesCardFeature(this.hass, this.context)
);
}
}
@@ -1,21 +1,14 @@
import { consume } from "@lit/context";
import { mdiMinus, mdiPlus, mdiRestore } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit";
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-control-select";
import { apiContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import {
@@ -24,11 +17,6 @@ import {
type LovelaceCardFeatureContext,
} from "./types";
const supportsCounterActionsCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "counter";
};
export const supportsCounterActionsCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -37,7 +25,8 @@ export const supportsCounterActionsCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsCounterActionsCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "counter";
};
interface CounterButton {
@@ -76,22 +65,19 @@ class HuiCounterActionsCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: HassEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: CounterActionsCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as HassEntity | undefined;
}
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
await import("../editor/config-elements/hui-counter-actions-card-feature-editor");
return document.createElement("hui-counter-actions-card-feature-editor");
@@ -113,9 +99,10 @@ class HuiCounterActionsCardFeature
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsCounterActionsCardFeatureFromState(this._stateObj)
!supportsCounterActionsCardFeature(this.hass, this.context)
) {
return null;
}
@@ -131,7 +118,7 @@ class HuiCounterActionsCardFeature
return html`
<ha-control-button
.entry=${button}
.label=${this._localize(
.label=${this.hass!.localize(
// @ts-ignore
`ui.card.counter.actions.${button.translationKey}`
)}
@@ -151,7 +138,7 @@ class HuiCounterActionsCardFeature
private _onActionTap(ev): void {
ev.stopPropagation();
const entry = (ev.target! as any).entry as CounterButton;
this._api.callService("counter", entry.serviceName, {
this.hass!.callService("counter", entry.serviceName, {
entity_id: this._stateObj!.entity_id,
});
}
@@ -1,23 +1,15 @@
import { consume } from "@lit/context";
import { mdiStop } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import {
computeCloseIcon,
computeOpenIcon,
} from "../../../common/entity/cover_icon";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-svg-icon";
import { apiContext } from "../../../data/context";
import {
canClose,
canOpen,
@@ -25,7 +17,7 @@ import {
CoverEntityFeature,
type CoverEntity,
} from "../../../data/cover";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -33,15 +25,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsCoverOpenCloseCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "cover" &&
(supportsFeature(stateObj, CoverEntityFeature.OPEN) ||
supportsFeature(stateObj, CoverEntityFeature.CLOSE))
);
};
export const supportsCoverOpenCloseCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -50,7 +33,12 @@ export const supportsCoverOpenCloseCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsCoverOpenCloseCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "cover" &&
(supportsFeature(stateObj, CoverEntityFeature.OPEN) ||
supportsFeature(stateObj, CoverEntityFeature.CLOSE))
);
};
@customElement("hui-cover-open-close-card-feature")
@@ -58,22 +46,19 @@ class HuiCoverOpenCloseCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: CoverEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: CoverOpenCloseCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as CoverEntity | undefined;
}
static getStubConfig(): CoverOpenCloseCardFeatureConfig {
return {
type: "cover-open-close",
@@ -89,21 +74,21 @@ class HuiCoverOpenCloseCardFeature
private _onOpenTap(ev): void {
ev.stopPropagation();
this._api.callService("cover", "open_cover", {
this.hass!.callService("cover", "open_cover", {
entity_id: this._stateObj!.entity_id,
});
}
private _onCloseTap(ev): void {
ev.stopPropagation();
this._api.callService("cover", "close_cover", {
this.hass!.callService("cover", "close_cover", {
entity_id: this._stateObj!.entity_id,
});
}
private _onStopTap(ev): void {
ev.stopPropagation();
this._api.callService("cover", "stop_cover", {
this.hass!.callService("cover", "stop_cover", {
entity_id: this._stateObj!.entity_id,
});
}
@@ -111,9 +96,10 @@ class HuiCoverOpenCloseCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsCoverOpenCloseCardFeatureFromState(this._stateObj)
!supportsCoverOpenCloseCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -124,7 +110,7 @@ class HuiCoverOpenCloseCardFeature
supportsFeature(this._stateObj, CoverEntityFeature.OPEN)
? html`
<ha-control-button
.label=${this._localize("ui.card.cover.open_cover")}
.label=${this.hass.localize("ui.card.cover.open_cover")}
@click=${this._onOpenTap}
.disabled=${!canOpen(this._stateObj)}
>
@@ -139,7 +125,7 @@ class HuiCoverOpenCloseCardFeature
supportsFeature(this._stateObj, CoverEntityFeature.STOP)
? html`
<ha-control-button
.label=${this._localize("ui.card.cover.stop_cover")}
.label=${this.hass.localize("ui.card.cover.stop_cover")}
@click=${this._onStopTap}
.disabled=${!canStop(this._stateObj)}
>
@@ -152,7 +138,7 @@ class HuiCoverOpenCloseCardFeature
supportsFeature(this._stateObj, CoverEntityFeature.CLOSE)
? html`
<ha-control-button
.label=${this._localize("ui.card.cover.close_cover")}
.label=${this.hass.localize("ui.card.cover.close_cover")}
@click=${this._onCloseTap}
.disabled=${!canClose(this._stateObj)}
>
@@ -1,35 +1,17 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { computeCssColor } from "../../../common/color/compute-color";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeAttributeNameDisplay } from "../../../common/entity/compute_attribute_display";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateActive } from "../../../common/entity/state_active";
import { stateColorCss } from "../../../common/entity/state_color";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-slider";
import { coverSupportsPosition, type CoverEntity } from "../../../data/cover";
import {
apiContext,
entitiesContext,
internationalizationContext,
} from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { DOMAIN_ATTRIBUTES_UNITS } from "../../../data/entity/entity_attributes";
import type { FrontendLocaleData } from "../../../data/translation";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -37,11 +19,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsCoverPositionCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "cover" && coverSupportsPosition(stateObj);
};
export const supportsCoverPositionCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -50,7 +27,8 @@ export const supportsCoverPositionCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsCoverPositionCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "cover" && coverSupportsPosition(stateObj);
};
@customElement("hui-cover-position-card-feature")
@@ -58,35 +36,21 @@ class HuiCoverPositionCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@property({ attribute: false }) public color?: string;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: CoverEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: entitiesContext, subscribe: true })
private _entities!: HomeAssistant["entities"];
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state() private _config?: CoverPositionCardFeatureConfig;
private get _stateObj(): CoverEntity | undefined {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!];
}
static getStubConfig(): CoverPositionCardFeatureConfig {
return {
type: "cover-position",
@@ -103,9 +67,10 @@ class HuiCoverPositionCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsCoverPositionCardFeatureFromState(this._stateObj)
!supportsCoverPositionCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -139,14 +104,14 @@ class HuiCoverPositionCardFeature
show-handle
@value-changed=${this._valueChanged}
.label=${computeAttributeNameDisplay(
this._localize,
this.hass.localize,
this._stateObj,
this._entities,
this.hass.entities,
"current_position"
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
.unit=${DOMAIN_ATTRIBUTES_UNITS.cover.current_position}
.locale=${this._locale}
.locale=${this.hass.locale}
></ha-control-slider>
`;
}
@@ -155,7 +120,7 @@ class HuiCoverPositionCardFeature
const { value } = ev.detail;
if (typeof value !== "number" || isNaN(value)) return;
this._api.callService("cover", "set_cover_position", {
this.hass!.callService("cover", "set_cover_position", {
entity_id: this._stateObj!.entity_id,
position: value,
});
@@ -1,19 +1,11 @@
import { consume } from "@lit/context";
import { mdiArrowBottomLeft, mdiArrowTopRight, mdiStop } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { LitElement, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-svg-icon";
import { apiContext } from "../../../data/context";
import {
CoverEntityFeature,
canCloseTilt,
@@ -21,7 +13,7 @@ import {
canStopTilt,
type CoverEntity,
} from "../../../data/cover";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -29,15 +21,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsCoverTiltCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "cover" &&
(supportsFeature(stateObj, CoverEntityFeature.OPEN_TILT) ||
supportsFeature(stateObj, CoverEntityFeature.CLOSE_TILT))
);
};
export const supportsCoverTiltCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -46,7 +29,12 @@ export const supportsCoverTiltCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsCoverTiltCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "cover" &&
(supportsFeature(stateObj, CoverEntityFeature.OPEN_TILT) ||
supportsFeature(stateObj, CoverEntityFeature.CLOSE_TILT))
);
};
@customElement("hui-cover-tilt-card-feature")
@@ -54,22 +42,19 @@ class HuiCoverTiltCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: CoverEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: CoverTiltCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as CoverEntity | undefined;
}
static getStubConfig(): CoverTiltCardFeatureConfig {
return {
type: "cover-tilt",
@@ -85,21 +70,21 @@ class HuiCoverTiltCardFeature
private _onOpenTap(ev): void {
ev.stopPropagation();
this._api.callService("cover", "open_cover_tilt", {
this.hass!.callService("cover", "open_cover_tilt", {
entity_id: this._stateObj!.entity_id,
});
}
private _onCloseTap(ev): void {
ev.stopPropagation();
this._api.callService("cover", "close_cover_tilt", {
this.hass!.callService("cover", "close_cover_tilt", {
entity_id: this._stateObj!.entity_id,
});
}
private _onStopTap(ev): void {
ev.stopPropagation();
this._api.callService("cover", "stop_cover_tilt", {
this.hass!.callService("cover", "stop_cover_tilt", {
entity_id: this._stateObj!.entity_id,
});
}
@@ -107,9 +92,10 @@ class HuiCoverTiltCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsCoverTiltCardFeatureFromState(this._stateObj)
!supportsCoverTiltCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -120,7 +106,7 @@ class HuiCoverTiltCardFeature
supportsFeature(this._stateObj, CoverEntityFeature.OPEN_TILT)
? html`
<ha-control-button
.label=${this._localize("ui.card.cover.open_tilt_cover")}
.label=${this.hass.localize("ui.card.cover.open_tilt_cover")}
@click=${this._onOpenTap}
.disabled=${!canOpenTilt(this._stateObj)}
>
@@ -133,7 +119,7 @@ class HuiCoverTiltCardFeature
supportsFeature(this._stateObj, CoverEntityFeature.STOP_TILT)
? html`
<ha-control-button
.label=${this._localize("ui.card.cover.stop_cover")}
.label=${this.hass.localize("ui.card.cover.stop_cover")}
@click=${this._onStopTap}
.disabled=${!canStopTilt(this._stateObj)}
>
@@ -146,7 +132,7 @@ class HuiCoverTiltCardFeature
supportsFeature(this._stateObj, CoverEntityFeature.CLOSE_TILT)
? html`
<ha-control-button
.label=${this._localize("ui.card.cover.close_tilt_cover")}
.label=${this.hass.localize("ui.card.cover.close_tilt_cover")}
@click=${this._onCloseTap}
.disabled=${!canCloseTilt(this._stateObj)}
>
@@ -1,35 +1,17 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { computeCssColor } from "../../../common/color/compute-color";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeAttributeNameDisplay } from "../../../common/entity/compute_attribute_display";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import type { LocalizeFunc } from "../../../common/translations/localize";
import type { CoverEntity } from "../../../data/cover";
import { coverSupportsTiltPosition } from "../../../data/cover";
import {
apiContext,
entitiesContext,
internationalizationContext,
} from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { DOMAIN_ATTRIBUTES_UNITS } from "../../../data/entity/entity_attributes";
import type { FrontendLocaleData } from "../../../data/translation";
import { generateTiltSliderTrackBackgroundGradient } from "../../../state-control/cover/ha-state-control-cover-tilt-position";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -39,15 +21,6 @@ import type {
const GRADIENT = generateTiltSliderTrackBackgroundGradient();
const supportsCoverTiltPositionCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "cover" && coverSupportsTiltPosition(stateObj as CoverEntity)
);
};
export const supportsCoverTiltPositionCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -56,7 +29,10 @@ export const supportsCoverTiltPositionCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsCoverTiltPositionCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "cover" && coverSupportsTiltPosition(stateObj as CoverEntity)
);
};
@customElement("hui-cover-tilt-position-card-feature")
@@ -64,35 +40,21 @@ class HuiCoverTiltPositionCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@property({ attribute: false }) public color?: string;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: CoverEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: entitiesContext, subscribe: true })
private _entities!: HomeAssistant["entities"];
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state() private _config?: CoverTiltPositionCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as CoverEntity | undefined;
}
static getStubConfig(): CoverTiltPositionCardFeatureConfig {
return {
type: "cover-tilt-position",
@@ -109,9 +71,10 @@ class HuiCoverTiltPositionCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsCoverTiltPositionCardFeatureFromState(this._stateObj)
!supportsCoverTiltPositionCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -142,14 +105,14 @@ class HuiCoverTiltPositionCardFeature
inverted
@value-changed=${this._valueChanged}
.label=${computeAttributeNameDisplay(
this._localize,
this.hass.localize,
this._stateObj,
this._entities,
this.hass.entities,
"current_tilt_position"
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
.unit=${DOMAIN_ATTRIBUTES_UNITS.cover.current_tilt_position}
.locale=${this._locale}
.locale=${this.hass.locale}
>
<div slot="background" class="gradient"></div
></ha-control-slider>
@@ -160,7 +123,7 @@ class HuiCoverTiltPositionCardFeature
const { value } = ev.detail;
if (typeof value !== "number" || isNaN(value)) return;
this._api.callService("cover", "set_cover_tilt_position", {
this.hass!.callService("cover", "set_cover_tilt_position", {
entity_id: this._stateObj!.entity_id,
tilt_position: value,
});
@@ -1,29 +1,15 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { firstWeekdayIndex } from "../../../common/datetime/first_weekday";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import {
fireEvent,
type HASSDomCurrentTargetEvent,
} from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-control-slider";
import { apiContext, internationalizationContext } from "../../../data/context";
import type { FrontendLocaleData } from "../../../data/translation";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -34,14 +20,6 @@ import type {
const loadDatePickerDialog = () =>
import("../../../components/date-picker/ha-dialog-date-picker");
const supportsDateSetCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
(domain === "input_datetime" && stateObj.attributes.has_date) ||
["datetime", "date"].includes(domain)
);
};
export const supportsDateSetCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -50,38 +28,32 @@ export const supportsDateSetCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsDateSetCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
(domain === "input_datetime" && stateObj.attributes.has_date) ||
["datetime", "date"].includes(domain)
);
};
@customElement("hui-date-set-card-feature")
class HuiDateSetCardFeature extends LitElement implements LovelaceCardFeature {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@property({ attribute: false }) public color?: string;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: HassEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: DateSetCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] ?? undefined;
}
private _pressButton(ev: HASSDomCurrentTargetEvent<HTMLElement>) {
if (!this._stateObj || !this._locale) return;
if (!this.hass || !this._stateObj) return;
fireEvent(this, "show-dialog", {
dialogTag: "ha-dialog-date-picker",
@@ -91,14 +63,14 @@ class HuiDateSetCardFeature extends LitElement implements LovelaceCardFeature {
min: "1970-01-01",
value: this._stateObj.state,
onChange: (value) => this._dateChanged(value),
locale: this._locale.language,
firstWeekday: firstWeekdayIndex(this._locale),
locale: this.hass.locale.language,
firstWeekday: firstWeekdayIndex(this.hass.locale),
},
});
}
private _dateChanged(value: string | undefined) {
if (!this._stateObj || !value) return;
if (!this.hass || !this._stateObj || !value) return;
const domain = computeDomain(this._stateObj.entity_id);
const service = domain === "input_datetime" ? "set_datetime" : "set_value";
@@ -113,12 +85,12 @@ class HuiDateSetCardFeature extends LitElement implements LovelaceCardFeature {
selectedDate.getDate()
);
this._api.callService(domain, service, {
this.hass.callService(domain, service, {
entity_id: this._stateObj.entity_id,
datetime: dateObj.toISOString(),
});
} else {
this._api.callService(domain, service, {
this.hass.callService(domain, service, {
entity_id: this._stateObj.entity_id,
date: value,
});
@@ -141,10 +113,10 @@ class HuiDateSetCardFeature extends LitElement implements LovelaceCardFeature {
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!this._locale ||
!supportsDateSetCardFeatureFromState(this._stateObj)
!supportsDateSetCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -156,7 +128,7 @@ class HuiDateSetCardFeature extends LitElement implements LovelaceCardFeature {
class="press-button"
@click=${this._pressButton}
>
${this._localize("ui.card.date.set_date")}
${this.hass.localize("ui.card.date.set_date")}
</ha-control-button>
</ha-control-button-group>
`;
@@ -1,4 +1,3 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
@@ -17,13 +16,6 @@ import type {
const FAN_DIRECTIONS: FanDirection[] = ["forward", "reverse"];
const supportsFanDirectionCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "fan" && supportsFeature(stateObj, FanEntityFeature.DIRECTION)
);
};
export const supportsFanDirectionCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -32,7 +24,10 @@ export const supportsFanDirectionCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsFanDirectionCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "fan" && supportsFeature(stateObj, FanEntityFeature.DIRECTION)
);
};
@customElement("hui-fan-direction-card-feature")
@@ -57,19 +52,21 @@ class HuiFanDirectionCardFeature
}
protected _getOptions(): HuiModeSelectOption[] {
if (!this._stateObj) {
if (!this.hass) {
return [];
}
return FAN_DIRECTIONS.map((direction) => ({
value: direction,
label: this._localize(`ui.card.fan.${direction}`),
label: this.hass!.localize(`ui.card.fan.${direction}`),
}));
}
protected _isSupported(): boolean {
return !!(
this._stateObj && supportsFanDirectionCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsFanDirectionCardFeature(this.hass, this.context)
);
}
}
@@ -1,26 +1,18 @@
import { consume } from "@lit/context";
import { mdiArrowOscillating, mdiArrowOscillatingOff } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues, TemplateResult } from "lit";
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-select";
import type { ControlSelectOption } from "../../../components/ha-control-select";
import { apiContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { FanEntity } from "../../../data/fan";
import { FanEntityFeature } from "../../../data/fan";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -28,13 +20,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsFanOscillateCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "fan" && supportsFeature(stateObj, FanEntityFeature.OSCILLATE)
);
};
export const supportsFanOscilatteCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -43,7 +28,10 @@ export const supportsFanOscilatteCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsFanOscillateCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "fan" && supportsFeature(stateObj, FanEntityFeature.OSCILLATE)
);
};
@customElement("hui-fan-oscillate-card-feature")
@@ -51,24 +39,21 @@ class HuiFanOscillateCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: FanEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: FanOscillateCardFeatureConfig;
@state() _oscillate?: boolean;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as FanEntity | undefined;
}
static getStubConfig(): FanOscillateCardFeatureConfig {
return {
type: "fan-oscillate",
@@ -82,9 +67,16 @@ class HuiFanOscillateCardFeature
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
if (changedProp.has("_stateObj") && this._stateObj) {
this._oscillate = this._stateObj.attributes.oscillating;
protected willUpdate(changedProp: PropertyValues<this>): void {
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._oscillate = this._stateObj.attributes.oscillating;
}
}
}
@@ -106,7 +98,7 @@ class HuiFanOscillateCardFeature
}
private async _updateOscillate(oscillate: boolean) {
await this._api.callService("fan", "oscillate", {
await this.hass!.callService("fan", "oscillate", {
entity_id: this._stateObj!.entity_id,
oscillating: oscillate,
});
@@ -115,9 +107,10 @@ class HuiFanOscillateCardFeature
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsFanOscillateCardFeatureFromState(this._stateObj)
!supportsFanOscilatteCardFeature(this.hass, this.context)
) {
return null;
}
@@ -127,7 +120,7 @@ class HuiFanOscillateCardFeature
const yesNo = ["no", "yes"] as const;
const options = yesNo.map<ControlSelectOption>((oscillating) => ({
value: oscillating,
label: this._localize(`ui.common.${oscillating}`),
label: this.hass!.localize(`ui.common.${oscillating}`),
path:
oscillating === "yes" ? mdiArrowOscillating : mdiArrowOscillatingOff,
}));
@@ -138,7 +131,7 @@ class HuiFanOscillateCardFeature
.value=${this._oscillate ? "yes" : "no"}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this._localize("ui.card.fan.oscillate")}
.label=${this.hass.localize("ui.card.fan.oscillate")}
style=${styleMap({
"--control-select-color": color,
})}
@@ -1,5 +1,4 @@
import { mdiTuneVariant } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
@@ -13,13 +12,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsFanPresetModesCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "fan" && supportsFeature(stateObj, FanEntityFeature.PRESET_MODE)
);
};
export const supportsFanPresetModesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -28,7 +20,10 @@ export const supportsFanPresetModesCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsFanPresetModesCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "fan" && supportsFeature(stateObj, FanEntityFeature.PRESET_MODE)
);
};
@customElement("hui-fan-preset-modes-card-feature")
@@ -67,8 +62,9 @@ class HuiFanPresetModesCardFeature
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsFanPresetModesCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsFanPresetModesCardFeature(this.hass, this.context)
);
}
}
@@ -1,27 +1,13 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeAttributeNameDisplay } from "../../../common/entity/compute_attribute_display";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateActive } from "../../../common/entity/state_active";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-select";
import type { ControlSelectOption } from "../../../components/ha-control-select";
import "../../../components/ha-control-slider";
import {
apiContext,
entitiesContext,
formattersContext,
internationalizationContext,
} from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { DOMAIN_ATTRIBUTES_UNITS } from "../../../data/entity/entity_attributes";
import type { FanEntity, FanSpeed } from "../../../data/fan";
@@ -34,13 +20,7 @@ import {
fanPercentageToSpeed,
fanSpeedToPercentage,
} from "../../../data/fan";
import type { FrontendLocaleData } from "../../../data/translation";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantFormatters,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -48,13 +28,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsFanSpeedCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "fan" && supportsFeature(stateObj, FanEntityFeature.SET_SPEED)
);
};
export const supportsFanSpeedCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -63,42 +36,27 @@ export const supportsFanSpeedCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsFanSpeedCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "fan" && supportsFeature(stateObj, FanEntityFeature.SET_SPEED)
);
};
@customElement("hui-fan-speed-card-feature")
class HuiFanSpeedCardFeature extends LitElement implements LovelaceCardFeature {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: FanEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: formattersContext, subscribe: true })
private _formatters!: HomeAssistantFormatters;
@state()
@consume({ context: entitiesContext, subscribe: true })
private _entities!: HomeAssistant["entities"];
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state() private _config?: FanSpeedCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as FanEntity | undefined;
}
static getStubConfig(): FanSpeedCardFeatureConfig {
return {
type: "fan-speed",
@@ -114,17 +72,18 @@ class HuiFanSpeedCardFeature extends LitElement implements LovelaceCardFeature {
private _localizeSpeed(speed: FanSpeed) {
if (speed === "on" || speed === "off") {
return this._formatters.formatEntityState(this._stateObj!, speed);
return this.hass!.formatEntityState(this._stateObj!, speed);
}
return this._localize(`ui.card.fan.speed.${speed}`) || speed;
return this.hass!.localize(`ui.card.fan.speed.${speed}`) || speed;
}
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsFanSpeedCardFeatureFromState(this._stateObj)
!supportsFanSpeedCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -153,9 +112,9 @@ class HuiFanSpeedCardFeature extends LitElement implements LovelaceCardFeature {
@value-changed=${this._speedValueChanged}
hide-option-label
.label=${computeAttributeNameDisplay(
this._localize,
this.hass.localize,
this._stateObj,
this._entities,
this.hass.entities,
"percentage"
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
@@ -174,14 +133,14 @@ class HuiFanSpeedCardFeature extends LitElement implements LovelaceCardFeature {
.step=${this._stateObj.attributes.percentage_step ?? 1}
@value-changed=${this._valueChanged}
.label=${computeAttributeNameDisplay(
this._localize,
this.hass.localize,
this._stateObj,
this._entities,
this.hass.entities,
"percentage"
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
.unit=${DOMAIN_ATTRIBUTES_UNITS.fan.percentage}
.locale=${this._locale}
.locale=${this.hass.locale}
></ha-control-slider>
`;
}
@@ -191,7 +150,7 @@ class HuiFanSpeedCardFeature extends LitElement implements LovelaceCardFeature {
const percentage = fanSpeedToPercentage(this._stateObj!, speed);
this._api.callService("fan", "set_percentage", {
this.hass!.callService("fan", "set_percentage", {
entity_id: this._stateObj!.entity_id,
percentage: percentage,
});
@@ -201,7 +160,7 @@ class HuiFanSpeedCardFeature extends LitElement implements LovelaceCardFeature {
const { value } = ev.detail;
if (typeof value !== "number" || isNaN(value)) return;
this._api.callService("fan", "set_percentage", {
this.hass!.callService("fan", "set_percentage", {
entity_id: this._stateObj!.entity_id,
percentage: value,
});
@@ -1,5 +1,4 @@
import { mdiTuneVariant } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
@@ -13,14 +12,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsHumidifierModesCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "humidifier" &&
supportsFeature(stateObj, HumidifierEntityFeature.MODES)
);
};
export const supportsHumidifierModesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -29,7 +20,11 @@ export const supportsHumidifierModesCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsHumidifierModesCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "humidifier" &&
supportsFeature(stateObj, HumidifierEntityFeature.MODES)
);
};
@customElement("hui-humidifier-modes-card-feature")
@@ -68,8 +63,9 @@ class HuiHumidifierModesCardFeature
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsHumidifierModesCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsHumidifierModesCardFeature(this.hass, this.context)
);
}
}
@@ -1,31 +1,19 @@
import { consume } from "@lit/context";
import { mdiPower, mdiWaterPercent } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues, TemplateResult } from "lit";
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { stateColorCss } from "../../../common/entity/state_color";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-select";
import type { ControlSelectOption } from "../../../components/ha-control-select";
import { apiContext, formattersContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type {
HumidifierEntity,
HumidifierState,
} from "../../../data/humidifier";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantFormatters,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -33,11 +21,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsHumidifierToggleCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "humidifier";
};
export const supportsHumidifierToggleCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -46,7 +29,8 @@ export const supportsHumidifierToggleCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsHumidifierToggleCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "humidifier";
};
@customElement("hui-humidifier-toggle-card-feature")
@@ -54,28 +38,22 @@ class HuiHumidifierToggleCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: HumidifierEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: formattersContext, subscribe: true })
private _formatters!: HomeAssistantFormatters;
@state() private _config?: HumidifierToggleCardFeatureConfig;
@state() _currentState?: HumidifierState;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
HumidifierEntity | undefined;
}
static getStubConfig(): HumidifierToggleCardFeatureConfig {
return {
type: "humidifier-toggle",
@@ -89,10 +67,17 @@ class HuiHumidifierToggleCardFeature
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
protected willUpdate(changedProp: PropertyValues<this>): void {
super.willUpdate(changedProp);
if (changedProp.has("_stateObj") && this._stateObj) {
this._currentState = this._stateObj.state as HumidifierState;
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentState = this._stateObj.state as HumidifierState;
}
}
}
@@ -114,7 +99,7 @@ class HuiHumidifierToggleCardFeature
}
private async _setState(newState: HumidifierState) {
await this._api.callService(
await this.hass!.callService(
"humidifier",
newState === "on" ? "turn_on" : "turn_off",
{
@@ -126,9 +111,10 @@ class HuiHumidifierToggleCardFeature
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsHumidifierToggleCardFeatureFromState(this._stateObj)
!supportsHumidifierToggleCardFeature(this.hass, this.context)
) {
return null;
}
@@ -137,7 +123,7 @@ class HuiHumidifierToggleCardFeature
const options = ["off", "on"].map<ControlSelectOption>((entityState) => ({
value: entityState,
label: this._formatters.formatEntityState(this._stateObj!, entityState),
label: this.hass!.formatEntityState(this._stateObj!, entityState),
path: entityState === "on" ? mdiWaterPercent : mdiPower,
}));
@@ -147,7 +133,7 @@ class HuiHumidifierToggleCardFeature
.value=${this._currentState}
@value-changed=${this._valueChanged}
hide-option-label
.label=${this._localize("ui.card.humidifier.state")}
.label=${this.hass.localize("ui.card.humidifier.state")}
style=${styleMap({
"--control-select-color": color,
})}
@@ -1,23 +1,16 @@
import { consume } from "@lit/context";
import { mdiHomeImportOutline, mdiPause, mdiPlay } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { LitElement, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-svg-icon";
import { apiContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { LawnMowerEntity } from "../../../data/lawn_mower";
import { LawnMowerEntityFeature, canDock } from "../../../data/lawn_mower";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -82,14 +75,6 @@ export const LAWN_MOWER_COMMANDS_BUTTONS: Record<
}),
};
const supportsLawnMowerCommandCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "lawn_mower" &&
LAWN_MOWER_COMMANDS.some((c) => supportsLawnMowerCommand(stateObj, c))
);
};
export const supportsLawnMowerCommandCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -98,7 +83,11 @@ export const supportsLawnMowerCommandCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsLawnMowerCommandCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "lawn_mower" &&
LAWN_MOWER_COMMANDS.some((c) => supportsLawnMowerCommand(stateObj, c))
);
};
@customElement("hui-lawn-mower-commands-card-feature")
@@ -106,22 +95,20 @@ class HuiLawnMowerCommandCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: LawnMowerEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: LawnMowerCommandsCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
LawnMowerEntity | undefined;
}
static getStubConfig(): LawnMowerCommandsCardFeatureConfig {
return {
type: "lawn-mower-commands",
@@ -145,7 +132,7 @@ class HuiLawnMowerCommandCardFeature
private _onCommandTap(ev): void {
ev.stopPropagation();
const entry = (ev.target! as any).entry as LawnMowerButton;
this._api.callService("lawn_mower", entry.serviceName, {
this.hass!.callService("lawn_mower", entry.serviceName, {
entity_id: this._stateObj!.entity_id,
});
}
@@ -153,9 +140,10 @@ class HuiLawnMowerCommandCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsLawnMowerCommandCardFeatureFromState(this._stateObj)
!supportsLawnMowerCommandCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -173,7 +161,7 @@ class HuiLawnMowerCommandCardFeature
return html`
<ha-control-button
.entry=${button}
.label=${this._localize(
.label=${this.hass!.localize(
// @ts-ignore
`ui.dialogs.more_info_control.lawn_mower.${button.translationKey}`
)}
@@ -1,25 +1,11 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateActive } from "../../../common/entity/state_active";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-slider";
import { apiContext, internationalizationContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { lightSupportsBrightness, type LightEntity } from "../../../data/light";
import type { FrontendLocaleData } from "../../../data/translation";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -27,11 +13,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsLightBrightnessCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "light" && lightSupportsBrightness(stateObj);
};
export const supportsLightBrightnessCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -40,7 +21,8 @@ export const supportsLightBrightnessCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsLightBrightnessCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "light" && lightSupportsBrightness(stateObj);
};
@customElement("hui-light-brightness-card-feature")
@@ -48,29 +30,19 @@ class HuiLightBrightnessCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: LightEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state() private _config?: LightBrightnessCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as LightEntity | undefined;
}
static getStubConfig(): LightBrightnessCardFeatureConfig {
return {
type: "light-brightness",
@@ -87,9 +59,10 @@ class HuiLightBrightnessCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsLightBrightnessCardFeatureFromState(this._stateObj)
!supportsLightBrightnessCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -110,9 +83,9 @@ class HuiLightBrightnessCardFeature
.showHandle=${stateActive(this._stateObj)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
@value-changed=${this._valueChanged}
.label=${this._localize("ui.card.light.brightness")}
.label=${this.hass.localize("ui.card.light.brightness")}
unit="%"
.locale=${this._locale}
.locale=${this.hass.locale}
></ha-control-slider>
`;
}
@@ -121,7 +94,7 @@ class HuiLightBrightnessCardFeature
ev.stopPropagation();
const value = ev.detail.value;
this._api.callService("light", "turn_on", {
this.hass!.callService("light", "turn_on", {
entity_id: this._stateObj!.entity_id,
brightness_pct: value,
});
@@ -1,21 +1,9 @@
import { consume } from "@lit/context";
import { ResizeController } from "@lit-labs/observers/resize-controller";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing, unsafeCSS } from "lit";
import { customElement, property, state } from "lit/decorators";
import type {
Connection,
UnsubscribeFunc,
HassEntity,
} from "home-assistant-js-websocket";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { LocalizeFunc } from "../../../common/translations/localize";
import { apiContext, connectionContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import {
computeDefaultFavoriteColors,
@@ -23,11 +11,7 @@ import {
type LightColor,
lightSupportsFavoriteColors,
} from "../../../data/light";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantConnection,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -45,13 +29,6 @@ import { getMoreInfoHintCardFeatureEditor } from "./get-more-info-hint-card-feat
const PILL_GAP = 8;
const PILL_MIN_SIZE = 32;
const supportsLightColorFavoritesCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "light" && lightSupportsFavoriteColors(stateObj);
};
export const supportsLightColorFavoritesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -60,7 +37,8 @@ export const supportsLightColorFavoritesCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsLightColorFavoritesCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "light" && lightSupportsFavoriteColors(stateObj);
};
@customElement("hui-light-color-favorites-card-feature")
@@ -68,27 +46,10 @@ class HuiLightColorFavoritesCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: LightEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: connectionContext, subscribe: true })
@transform<HomeAssistantConnection, Connection>({
transformer: ({ connection }) => connection,
})
private _connection?: Connection;
@state() private _config?: LightColorFavoritesCardFeatureConfig;
@state() private _entry?: EntityRegistryEntry | null;
@@ -125,11 +86,11 @@ class HuiLightColorFavoritesCardFeature
}
private _subscribeEntityEntry() {
if (this._connection && this.context?.entity_id) {
if (this.hass && this.context?.entity_id) {
const id = this.context.entity_id;
try {
this._unsubEntityRegistry = subscribeEntityRegistry(
this._connection,
this.hass!.connection,
(entries) => {
const entry = entries.find((e) => e.entity_id === id);
if (entry) {
@@ -147,8 +108,15 @@ class HuiLightColorFavoritesCardFeature
return this._resizeController.value ?? 0;
}
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as LightEntity | undefined;
}
protected updated(changedProps: PropertyValues): void {
if (changedProps.has("context") || changedProps.has("_connection")) {
if (changedProps.has("context")) {
this._unsubscribeEntityRegistry();
this._subscribeEntityEntry();
}
@@ -182,9 +150,10 @@ class HuiLightColorFavoritesCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsLightColorFavoritesCardFeatureFromState(this._stateObj)
!supportsLightColorFavoritesCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -196,7 +165,7 @@ class HuiLightColorFavoritesCardFeature
${visibleColors.map(
(color, index) => html`
<ha-favorite-color-button
.label=${this._localize(
.label=${this.hass!.localize(
`ui.dialogs.more_info_control.light.favorite_color.set`,
{ number: index }
)}
@@ -220,7 +189,7 @@ class HuiLightColorFavoritesCardFeature
const index = (ev.target! as any).index!;
const favorite = this._favoriteColors[index];
this._api.callService("light", "turn_on", {
this.hass!.callService("light", "turn_on", {
entity_id: this._stateObj!.entity_id,
...favorite,
});
@@ -1,5 +1,3 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
@@ -8,16 +6,9 @@ import {
DEFAULT_MAX_KELVIN,
DEFAULT_MIN_KELVIN,
} from "../../../common/color/convert-light-color";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateActive } from "../../../common/entity/state_active";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-slider";
import { apiContext, internationalizationContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { DOMAIN_ATTRIBUTES_UNITS } from "../../../data/entity/entity_attributes";
import {
@@ -25,13 +16,8 @@ import {
lightSupportsColorMode,
type LightEntity,
} from "../../../data/light";
import type { FrontendLocaleData } from "../../../data/translation";
import { generateColorTemperatureGradient } from "../../../dialogs/more-info/components/lights/light-color-temp-picker";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -39,14 +25,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsLightColorTempCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "light" &&
lightSupportsColorMode(stateObj, LightColorMode.COLOR_TEMP)
);
};
export const supportsLightColorTempCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -55,7 +33,11 @@ export const supportsLightColorTempCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsLightColorTempCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "light" &&
lightSupportsColorMode(stateObj, LightColorMode.COLOR_TEMP)
);
};
@customElement("hui-light-color-temp-card-feature")
@@ -63,29 +45,19 @@ class HuiLightColorTempCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: LightEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state() private _config?: LightColorTempCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as LightEntity | undefined;
}
static getStubConfig(): LightColorTempCardFeatureConfig {
return {
type: "light-color-temp",
@@ -102,9 +74,10 @@ class HuiLightColorTempCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsLightColorTempCardFeatureFromState(this._stateObj)
!supportsLightColorTempCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -128,14 +101,14 @@ class HuiLightColorTempCardFeature
.showHandle=${stateActive(this._stateObj)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
@value-changed=${this._valueChanged}
.label=${this._localize("ui.card.light.color_temperature")}
.label=${this.hass.localize("ui.card.light.color_temperature")}
.min=${minKelvin}
.max=${maxKelvin}
style=${styleMap({
"--gradient": gradient,
})}
.unit=${DOMAIN_ATTRIBUTES_UNITS.light.color_temp_kelvin}
.locale=${this._locale}
.locale=${this.hass.locale}
></ha-control-slider>
`;
}
@@ -148,7 +121,7 @@ class HuiLightColorTempCardFeature
ev.stopPropagation();
const value = ev.detail.value;
this._api.callService("light", "turn_on", {
this.hass!.callService("light", "turn_on", {
entity_id: this._stateObj!.entity_id,
color_temp_kelvin: value,
});
@@ -1,18 +1,10 @@
import { consume } from "@lit/context";
import { mdiLock, mdiLockOpenVariant } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { LitElement, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import { apiContext } from "../../../data/context";
import { forwardHaptic } from "../../../data/haptics";
import {
callProtectedLockService,
@@ -20,7 +12,7 @@ import {
canUnlock,
type LockEntity,
} from "../../../data/lock";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -28,11 +20,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsLockCommandsCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "lock";
};
export const supportsLockCommandsCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -41,7 +28,8 @@ export const supportsLockCommandsCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsLockCommandsCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "lock";
};
@customElement("hui-lock-commands-card-feature")
@@ -49,22 +37,19 @@ class HuiLockCommandsCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: LockEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: LockCommandsCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as LockEntity | undefined;
}
static getStubConfig(): LockCommandsCardFeatureConfig {
return {
type: "lock-commands",
@@ -81,28 +66,20 @@ class HuiLockCommandsCardFeature
private _onTap(ev): void {
ev.stopPropagation();
const service = ev.target.dataset.service;
if (!this._stateObj || !service) {
if (!this.hass || !this._stateObj || !service) {
return;
}
forwardHaptic(this, "light");
callProtectedLockService(
this,
{
callService: this._api.callService,
callWS: this._api.callWS,
localize: this._localize,
},
this._stateObj,
service
);
callProtectedLockService(this, this.hass, this._stateObj, service);
}
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsLockCommandsCardFeatureFromState(this._stateObj)
!supportsLockCommandsCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -110,7 +87,7 @@ class HuiLockCommandsCardFeature
return html`
<ha-control-button-group>
<ha-control-button
.label=${this._localize("ui.card.lock.lock")}
.label=${this.hass.localize("ui.card.lock.lock")}
.disabled=${!canLock(this._stateObj)}
@click=${this._onTap}
data-service="lock"
@@ -118,7 +95,7 @@ class HuiLockCommandsCardFeature
<ha-svg-icon .path=${mdiLock}></ha-svg-icon>
</ha-control-button>
<ha-control-button
.label=${this._localize("ui.card.lock.unlock")}
.label=${this.hass.localize("ui.card.lock.unlock")}
.disabled=${!canUnlock(this._stateObj)}
@click=${this._onTap}
data-service="unlock"
@@ -1,26 +1,18 @@
import { consume } from "@lit/context";
import { mdiCheck } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import type { CSSResultGroup } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import { apiContext } from "../../../data/context";
import {
callProtectedLockService,
canOpen,
LockEntityFeature,
type LockEntity,
} from "../../../data/lock";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -28,11 +20,6 @@ import type {
LovelaceCardFeatureContext,
} from "./types";
const supportsLockOpenDoorCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "lock" && supportsFeature(stateObj, LockEntityFeature.OPEN);
};
export const supportsLockOpenDoorCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -41,7 +28,8 @@ export const supportsLockOpenDoorCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsLockOpenDoorCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "lock" && supportsFeature(stateObj, LockEntityFeature.OPEN);
};
const CONFIRM_TIMEOUT_SECOND = 5;
@@ -54,26 +42,23 @@ class HuiLockOpenDoorCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: LockEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() public _buttonState: ButtonState = "normal";
@state() private _config?: LockOpenDoorCardFeatureConfig;
private _buttonTimeout?: number;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as LockEntity | undefined;
}
static getStubConfig(): LockOpenDoorCardFeatureConfig {
return {
type: "lock-open-door",
@@ -102,19 +87,10 @@ class HuiLockOpenDoorCardFeature
this._setButtonState("confirm", CONFIRM_TIMEOUT_SECOND);
return;
}
if (!this._stateObj) {
if (!this.hass || !this._stateObj) {
return;
}
callProtectedLockService(
this,
{
callService: this._api.callService,
callWS: this._api.callWS,
localize: this._localize,
},
this._stateObj,
"open"
);
callProtectedLockService(this, this.hass, this._stateObj!, "open");
this._setButtonState("done", DONE_TIMEOUT_SECOND);
}
@@ -122,9 +98,10 @@ class HuiLockOpenDoorCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsLockOpenDoorCardFeatureFromState(this._stateObj)
!supportsLockOpenDoorCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -135,7 +112,7 @@ class HuiLockOpenDoorCardFeature
? html`
<p class="open-done">
<ha-svg-icon path=${mdiCheck}></ha-svg-icon>
${this._localize("ui.card.lock.open_door_done")}
${this.hass.localize("ui.card.lock.open_door_done")}
</p>
`
: html`
@@ -147,8 +124,8 @@ class HuiLockOpenDoorCardFeature
>
${
this._buttonState === "confirm"
? this._localize("ui.card.lock.open_door_confirm")
: this._localize("ui.card.lock.open_door")
? this.hass.localize("ui.card.lock.open_door_confirm")
: this.hass.localize("ui.card.lock.open_door")
}
</ha-control-button>
</ha-control-button-group>
@@ -1,22 +1,14 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import { apiContext } from "../../../data/context";
import type {
ControlButton,
MediaPlayerEntity,
} from "../../../data/media-player";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import { hasConfigChanged } from "../common/has-changed";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
@@ -29,13 +21,6 @@ import type {
MediaPlayerPlaybackCardFeatureConfig,
} from "./types";
const supportsMediaPlayerPlaybackCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "media_player";
};
export const supportsMediaPlayerPlaybackCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -44,7 +29,8 @@ export const supportsMediaPlayerPlaybackCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsMediaPlayerPlaybackCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "media_player";
};
@customElement("hui-media-player-playback-card-feature")
@@ -52,26 +38,24 @@ class HuiMediaPlayerPlaybackCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@property({ attribute: false }) public color?: string;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: MediaPlayerEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: MediaPlayerPlaybackCardFeatureConfig;
@state() private _narrow?: boolean = false;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as
MediaPlayerEntity | undefined;
}
static getStubConfig(): MediaPlayerPlaybackCardFeatureConfig {
return {
type: "media-player-playback",
@@ -98,18 +82,25 @@ class HuiMediaPlayerPlaybackCardFeature
}
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
protected shouldUpdate(changedProps: PropertyValues<this>): boolean {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
const entityId = this.context?.entity_id;
return (
hasConfigChanged(this, changedProps) || changedProps.has("_stateObj")
hasConfigChanged(this, changedProps) ||
(changedProps.has("hass") &&
(!oldHass ||
!entityId ||
oldHass.states[entityId] !== this.hass!.states[entityId]))
);
}
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsMediaPlayerPlaybackCardFeatureFromState(this._stateObj)
!supportsMediaPlayerPlaybackCardFeature(this.hass, this.context) ||
!this._stateObj
) {
return nothing;
}
@@ -122,7 +113,9 @@ class HuiMediaPlayerPlaybackCardFeature
(button) => html`
<ha-control-button
key=${button.action}
.label=${this._localize(`ui.card.media_player.${button.action}`)}
.label=${this.hass?.localize(
`ui.card.media_player.${button.action}`
)}
.disabled=${button.disabled}
@click=${this._action}
>
@@ -173,7 +166,7 @@ class HuiMediaPlayerPlaybackCardFeature
if (!action) return;
if (action === "volume_mute") {
this._api.callService("media_player", "volume_mute", {
this.hass!.callService("media_player", "volume_mute", {
entity_id: this._stateObj.entity_id,
is_volume_muted: !this._stateObj.attributes.is_volume_muted,
});
@@ -181,7 +174,7 @@ class HuiMediaPlayerPlaybackCardFeature
}
if (action === "shuffle") {
this._api.callService("media_player", "shuffle_set", {
this.hass!.callService("media_player", "shuffle_set", {
entity_id: this._stateObj.entity_id,
shuffle: !this._stateObj.attributes.shuffle,
});
@@ -190,14 +183,14 @@ class HuiMediaPlayerPlaybackCardFeature
if (action === "repeat") {
const repeat = this._stateObj.attributes.repeat ?? "off";
this._api.callService("media_player", "repeat_set", {
this.hass!.callService("media_player", "repeat_set", {
entity_id: this._stateObj.entity_id,
repeat: repeat === "off" ? "one" : repeat === "one" ? "all" : "off",
});
return;
}
this._api.callService("media_player", action, {
this.hass!.callService("media_player", action, {
entity_id: this._stateObj.entity_id,
});
}
@@ -1,5 +1,4 @@
import type { PropertyValues } from "lit";
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
@@ -16,17 +15,6 @@ import type {
MediaPlayerSoundModeCardFeatureConfig,
} from "./types";
const supportsMediaPlayerSoundModeCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "media_player" &&
supportsFeature(stateObj, MediaPlayerEntityFeature.SELECT_SOUND_MODE) &&
!!stateObj.attributes.sound_mode_list?.length
);
};
export const supportsMediaPlayerSoundModeCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -35,7 +23,12 @@ export const supportsMediaPlayerSoundModeCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsMediaPlayerSoundModeCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "media_player" &&
supportsFeature(stateObj, MediaPlayerEntityFeature.SELECT_SOUND_MODE) &&
!!stateObj.attributes.sound_mode_list?.length
);
};
@customElement("hui-media-player-sound-mode-card-feature")
@@ -55,7 +48,7 @@ class HuiMediaPlayerSoundModeCardFeature
protected readonly _serviceAction = "select_sound_mode";
protected get _label(): string {
return this._localize("ui.card.media_player.sound_mode");
return this.hass!.localize("ui.card.media_player.sound_mode");
}
protected readonly _hideLabel = false;
@@ -83,18 +76,25 @@ class HuiMediaPlayerSoundModeCardFeature
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
const entityId = this.context?.entity_id;
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
return (
changedProps.has("_currentValue") ||
changedProps.has("context") ||
changedProps.has("_stateObj") ||
hasConfigChanged(this, changedProps)
hasConfigChanged(this, changedProps) ||
(changedProps.has("hass") &&
(!oldHass ||
!entityId ||
oldHass.states[entityId] !== this.hass?.states[entityId]))
);
}
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsMediaPlayerSoundModeCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsMediaPlayerSoundModeCardFeature(this.hass, this.context)
);
}
}
@@ -1,5 +1,4 @@
import type { PropertyValues } from "lit";
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
@@ -16,16 +15,6 @@ import type {
MediaPlayerSourceCardFeatureConfig,
} from "./types";
const supportsMediaPlayerSourceCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "media_player" &&
supportsFeature(stateObj, MediaPlayerEntityFeature.SELECT_SOURCE)
);
};
export const supportsMediaPlayerSourceCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -34,7 +23,11 @@ export const supportsMediaPlayerSourceCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsMediaPlayerSourceCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "media_player" &&
supportsFeature(stateObj, MediaPlayerEntityFeature.SELECT_SOURCE)
);
};
@customElement("hui-media-player-source-card-feature")
@@ -59,7 +52,7 @@ class HuiMediaPlayerSourceCardFeature
protected readonly _serviceAction = "select_source";
protected get _label(): string {
return this._localize("ui.card.media_player.source");
return this.hass!.localize("ui.card.media_player.source");
}
protected readonly _hideLabel = false;
@@ -82,18 +75,25 @@ class HuiMediaPlayerSourceCardFeature
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
const entityId = this.context?.entity_id;
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
return (
changedProps.has("_currentValue") ||
changedProps.has("context") ||
changedProps.has("_stateObj") ||
hasConfigChanged(this, changedProps)
hasConfigChanged(this, changedProps) ||
(changedProps.has("hass") &&
(!oldHass ||
!entityId ||
oldHass.states[entityId] !== this.hass?.states[entityId]))
);
}
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsMediaPlayerSourceCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsMediaPlayerSourceCardFeature(this.hass, this.context)
);
}
}
@@ -1,29 +1,15 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import { clamp } from "../../../common/number/clamp";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-number-buttons";
import { apiContext, internationalizationContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import {
MediaPlayerEntityFeature,
type MediaPlayerEntity,
} from "../../../data/media-player";
import type { FrontendLocaleData } from "../../../data/translation";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import {
@@ -35,16 +21,6 @@ import type {
MediaPlayerVolumeButtonsCardFeatureConfig,
} from "./types";
const supportsMediaPlayerVolumeButtonsCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "media_player" &&
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_SET)
);
};
export const supportsMediaPlayerVolumeButtonsCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -53,7 +29,11 @@ export const supportsMediaPlayerVolumeButtonsCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsMediaPlayerVolumeButtonsCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "media_player" &&
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_SET)
);
};
@customElement("hui-media-player-volume-buttons-card-feature")
@@ -61,29 +41,20 @@ class HuiMediaPlayerVolumeButtonsCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: MediaPlayerEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state() private _config?: MediaPlayerVolumeButtonsCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as
MediaPlayerEntity | undefined;
}
static getStubConfig(): MediaPlayerVolumeButtonsCardFeatureConfig {
return {
type: "media-player-volume-buttons",
@@ -108,9 +79,10 @@ class HuiMediaPlayerVolumeButtonsCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsMediaPlayerVolumeButtonsCardFeatureFromState(this._stateObj)
!supportsMediaPlayerVolumeButtonsCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -126,7 +98,7 @@ class HuiMediaPlayerVolumeButtonsCardFeature
return html`
<ha-control-number-buttons
.disabled=${disabled}
.locale=${this._locale}
.locale=${this.hass.locale}
min="0"
max="100"
.step=${this._config.step ?? 5}
@@ -135,7 +107,7 @@ class HuiMediaPlayerVolumeButtonsCardFeature
@value-changed=${this._valueChanged}
></ha-control-number-buttons>
${renderMuteButton(
this._localize,
this.hass,
stateObj,
this._config.show_mute_button,
disabled,
@@ -147,14 +119,14 @@ class HuiMediaPlayerVolumeButtonsCardFeature
private _valueChanged(ev: CustomEvent) {
ev.stopPropagation();
this._api.callService("media_player", "volume_set", {
this.hass!.callService("media_player", "volume_set", {
entity_id: this._stateObj!.entity_id,
volume_level: clamp(ev.detail.value, 0, 100) / 100,
});
}
private _toggleMute = (ev: Event) => {
toggleMediaPlayerMute(ev, this._api!.callService, this._stateObj!, this);
toggleMediaPlayerMute(ev, this.hass!, this._stateObj!, this);
};
static get styles() {
@@ -1,29 +1,15 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateActive } from "../../../common/entity/state_active";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-slider";
import { apiContext, internationalizationContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import {
MediaPlayerEntityFeature,
type MediaPlayerEntity,
} from "../../../data/media-player";
import type { FrontendLocaleData } from "../../../data/translation";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import {
@@ -35,16 +21,6 @@ import type {
MediaPlayerVolumeSliderCardFeatureConfig,
} from "./types";
const supportsMediaPlayerVolumeSliderCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "media_player" &&
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_SET)
);
};
export const supportsMediaPlayerVolumeSliderCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -53,7 +29,11 @@ export const supportsMediaPlayerVolumeSliderCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsMediaPlayerVolumeSliderCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "media_player" &&
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_SET)
);
};
@customElement("hui-media-player-volume-slider-card-feature")
@@ -61,29 +41,20 @@ class HuiMediaPlayerVolumeSliderCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: MediaPlayerEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state() private _config?: MediaPlayerVolumeSliderCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
MediaPlayerEntity | undefined;
}
static getStubConfig(): MediaPlayerVolumeSliderCardFeatureConfig {
return {
type: "media-player-volume-slider",
@@ -107,9 +78,10 @@ class HuiMediaPlayerVolumeSliderCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsMediaPlayerVolumeSliderCardFeatureFromState(this._stateObj)
!supportsMediaPlayerVolumeSliderCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -131,10 +103,10 @@ class HuiMediaPlayerVolumeSliderCardFeature
.disabled=${disabled}
@value-changed=${this._valueChanged}
unit="%"
.locale=${this._locale}
.locale=${this.hass.locale}
></ha-control-slider>
${renderMuteButton(
this._localize,
this.hass,
stateObj,
this._config.show_mute_button,
disabled,
@@ -147,14 +119,14 @@ class HuiMediaPlayerVolumeSliderCardFeature
ev.stopPropagation();
const value = ev.detail.value;
this._api.callService("media_player", "volume_set", {
this.hass!.callService("media_player", "volume_set", {
entity_id: this._stateObj!.entity_id,
volume_level: value / 100,
});
}
private _toggleMute = (ev: Event) => {
toggleMediaPlayerMute(ev, this._api!.callService, this._stateObj!, this);
toggleMediaPlayerMute(ev, this.hass!, this._stateObj!, this);
};
static get styles() {
@@ -1,21 +1,14 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues, TemplateResult } from "lit";
import { html, LitElement, nothing } from "lit";
import { property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-attribute-icon";
import "../../../components/ha-control-select";
import "../../../components/ha-control-select-menu";
import "../../../components/ha-svg-icon";
import { apiContext, formattersContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HomeAssistantApi, HomeAssistantFormatters } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import { filterModes } from "./common/filter-modes";
@@ -45,24 +38,10 @@ export abstract class HuiModeSelectCardFeatureBase<
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
protected _stateObj?: TEntity;
@state()
@consumeLocalize()
protected _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
protected _api!: HomeAssistantApi;
@state()
@consume({ context: formattersContext, subscribe: true })
protected _formatters!: HomeAssistantFormatters;
@state() protected _config?: TConfig;
@state() protected _currentValue?: string;
@@ -84,7 +63,7 @@ export abstract class HuiModeSelectCardFeatureBase<
protected abstract _isSupported(): boolean;
protected get _label(): string {
return this._formatters.formatEntityAttributeName(
return this.hass!.formatEntityAttributeName(
this._stateObj!,
this._attribute
);
@@ -111,6 +90,14 @@ export abstract class HuiModeSelectCardFeatureBase<
return true;
}
protected get _stateObj(): TEntity | undefined {
if (!this.hass || !this.context?.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as TEntity | undefined;
}
public setConfig(config: TConfig): void {
if (!config) {
throw new Error("Invalid configuration");
@@ -119,17 +106,28 @@ export abstract class HuiModeSelectCardFeatureBase<
this._config = config;
}
protected willUpdate(changedProps: PropertyValues): void {
protected willUpdate(changedProps: PropertyValues<this>): void {
super.willUpdate(changedProps);
if (changedProps.has("_stateObj") && this._stateObj) {
this._currentValue = this._getValue(this._stateObj);
if (
(changedProps.has("hass") || changedProps.has("context")) &&
this._stateObj
) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
const oldStateObj = this.context?.entity_id
? (oldHass?.states[this.context.entity_id] as TEntity | undefined)
: undefined;
if (oldStateObj !== this._stateObj) {
this._currentValue = this._getValue(this._stateObj);
}
}
}
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!this._isSupported()
@@ -193,7 +191,7 @@ export abstract class HuiModeSelectCardFeatureBase<
}
protected _getOptions(): HuiModeSelectOption[] {
if (!this._stateObj) {
if (!this._stateObj || !this.hass) {
return [];
}
@@ -202,7 +200,7 @@ export abstract class HuiModeSelectCardFeatureBase<
this._configuredModes
).map((mode) => ({
value: mode,
label: this._formatters.formatEntityAttributeValue(
label: this.hass!.formatEntityAttributeValue(
this._stateObj!,
this._attribute,
mode
@@ -227,7 +225,7 @@ export abstract class HuiModeSelectCardFeatureBase<
></ha-attribute-icon>`;
private async _valueChanged(ev: AttributeModeChangeEvent) {
if (!this._stateObj) {
if (!this.hass || !this._stateObj) {
return;
}
@@ -245,7 +243,7 @@ export abstract class HuiModeSelectCardFeatureBase<
this._currentValue = value;
try {
await this._api.callService(
await this.hass.callService(
this._getServiceDomain(this._stateObj),
this._serviceAction,
{
@@ -1,40 +1,22 @@
import { consume } from "@lit/context";
import { ResizeController } from "@lit-labs/observers/resize-controller";
import type {
Connection,
HassEntity,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import type { PropertyValues, TemplateResult } from "lit";
import { css, html, LitElement } from "lit";
import { property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { computeCssColor } from "../../../common/color/compute-color";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import type {
LocalizeFunc,
LocalizeKeys,
} from "../../../common/translations/localize";
import type { LocalizeKeys } from "../../../common/translations/localize";
import "../../../components/ha-control-select";
import { apiContext, connectionContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { ExtEntityRegistryEntry } from "../../../data/entity/entity_registry";
import {
getExtendedEntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity/entity_registry";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantConnection,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -68,15 +50,6 @@ export interface NumericFavoriteCardFeatureDefinition<
featureLabelKey: LocalizeKeys;
}
const supportsNumericFavoriteCardFeatureFromState = <
TEntity extends NumericFavoriteEntity,
>(
stateObj: TEntity,
definition: NumericFavoriteCardFeatureDefinition<TEntity>
) =>
computeDomain(stateObj.entity_id) === definition.domain &&
definition.supportsPosition(stateObj);
export const supportsNumericFavoriteCardFeature = <
TEntity extends NumericFavoriteEntity,
>(
@@ -92,7 +65,10 @@ export const supportsNumericFavoriteCardFeature = <
return false;
}
return supportsNumericFavoriteCardFeatureFromState(stateObj, definition);
return (
computeDomain(stateObj.entity_id) === definition.domain &&
definition.supportsPosition(stateObj)
);
};
export abstract class HuiNumericFavoriteCardFeatureBase<
@@ -102,29 +78,12 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@property({ attribute: false }) public color?: string;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
protected _stateObj?: TEntity;
@state()
@consumeLocalize()
protected _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
protected _api!: HomeAssistantApi;
@state()
@consume({ context: connectionContext, subscribe: true })
@transform<HomeAssistantConnection, Connection>({
transformer: ({ connection }) => connection,
})
protected _connection?: Connection;
@state() protected _config?: TConfig;
@state() protected _entry?: ExtEntityRegistryEntry | null;
@@ -149,6 +108,14 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
protected abstract get _definition(): NumericFavoriteCardFeatureDefinition<TEntity>;
protected get _stateObj(): TEntity | undefined {
if (!this.hass || !this.context?.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id] as TEntity | undefined;
}
public connectedCallback() {
super.connectedCallback();
this._refreshEntitySubscription();
@@ -167,14 +134,38 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
this._config = config as TConfig;
}
protected willUpdate(changedProp: PropertyValues): void {
protected willUpdate(changedProp: PropertyValues<this>): void {
super.willUpdate(changedProp);
if (changedProp.has("_stateObj") && this._stateObj) {
this._currentPosition = this._definition.getCurrentValue(this._stateObj);
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = this.context?.entity_id
? (oldHass?.states[this.context.entity_id] as TEntity | undefined)
: undefined;
if (oldStateObj !== this._stateObj) {
this._currentPosition = this._definition.getCurrentValue(
this._stateObj
);
}
}
if (changedProp.has("context") || changedProp.has("_connection")) {
if (
changedProp.has("context") &&
(changedProp.get("context") as LovelaceCardFeatureContext | undefined)
?.entity_id !== this.context?.entity_id
) {
this._refreshEntitySubscription();
}
if (
changedProp.has("hass") &&
(changedProp.get("hass") as HomeAssistant | undefined)?.connection !==
this.hass?.connection
) {
this._refreshEntitySubscription();
}
}
@@ -191,8 +182,12 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
}
private async _loadEntityEntry(entityId: string): Promise<void> {
if (!this.hass) {
return;
}
try {
const entry = await getExtendedEntityRegistryEntry(this._api, entityId);
const entry = await getExtendedEntityRegistryEntry(this.hass, entityId);
if (this.context?.entity_id === entityId) {
this._entry = entry;
@@ -211,7 +206,7 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
try {
this._unsubEntityRegistry = subscribeEntityRegistry(
this._connection!,
this.hass!.connection,
async (entries) => {
if (this.context?.entity_id !== entityId) {
return;
@@ -232,9 +227,9 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
private async _ensureEntitySubscription(): Promise<void> {
const entityId = this.context?.entity_id;
const connection = this._connection;
const connection = this.hass?.connection;
if (!entityId || !connection) {
if (!this.hass || !entityId || !connection) {
this._unsubscribeEntityRegistry();
this._subscribedEntityId = undefined;
this._subscribedConnection = undefined;
@@ -261,7 +256,7 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
) {
const value = ev.detail.value;
if (value == null || !this._stateObj) {
if (value == null || !this.hass || !this._stateObj) {
return;
}
@@ -280,7 +275,7 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
this._currentPosition = position;
try {
await this._api.callService(
await this.hass.callService(
this._definition.domain,
this._definition.setPositionService,
{
@@ -296,10 +291,12 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
protected render(): TemplateResult | null {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsNumericFavoriteCardFeatureFromState(
this._stateObj,
!supportsNumericFavoriteCardFeature(
this.hass,
this.context,
this._definition
)
) {
@@ -311,7 +308,9 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
this._definition.defaultFavoritePositions
);
if (positions.length === 0) {
const hass = this.hass;
if (positions.length === 0 || !hass) {
return null;
}
@@ -322,7 +321,7 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
const options = visiblePositions.map((position) => ({
value: String(position),
label: `${position}%`,
ariaLabel: this._localize(this._definition.setPositionLabelKey, {
ariaLabel: hass.localize(this._definition.setPositionLabelKey, {
value: `${position}%`,
}),
}));
@@ -340,7 +339,7 @@ export abstract class HuiNumericFavoriteCardFeatureBase<
.options=${options}
.value=${currentValue}
@value-changed=${this._valueChanged}
.label=${this._localize(this._definition.featureLabelKey)}
.label=${hass.localize(this._definition.featureLabelKey)}
.disabled=${this._stateObj.state === UNAVAILABLE}
>
</ha-control-select>
@@ -1,24 +1,15 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { consumeEntityState } from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import { computeDomain } from "../../../common/entity/compute_domain";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-control-number-buttons";
import "../../../components/ha-control-slider";
import "../../../components/ha-icon";
import { apiContext, internationalizationContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { FrontendLocaleData } from "../../../data/translation";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -26,11 +17,6 @@ import type {
NumericInputCardFeatureConfig,
} from "./types";
const supportsNumericInputCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "input_number" || domain === "number";
};
export const supportsNumericInputCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -39,7 +25,8 @@ export const supportsNumericInputCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsNumericInputCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "input_number" || domain === "number";
};
@customElement("hui-numeric-input-card-feature")
@@ -47,23 +34,10 @@ class HuiNumericInputCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: HassEntity;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state() private _config?: NumericInputCardFeatureConfig;
@state() _currentState?: string;
@@ -75,6 +49,13 @@ class HuiNumericInputCardFeature
};
}
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as HassEntity | undefined;
}
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
await import("../editor/config-elements/hui-numeric-input-card-feature-editor");
return document.createElement("hui-numeric-input-card-feature-editor");
@@ -87,10 +68,17 @@ class HuiNumericInputCardFeature
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
protected willUpdate(changedProp: PropertyValues<this>): void {
super.willUpdate(changedProp);
if (changedProp.has("_stateObj") && this._stateObj) {
this._currentState = this._stateObj.state;
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._currentState = this._stateObj.state;
}
}
}
@@ -99,7 +87,7 @@ class HuiNumericInputCardFeature
const domain = computeDomain(stateObj.entity_id);
await this._api.callService(domain, "set_value", {
await this.hass!.callService(domain, "set_value", {
entity_id: stateObj.entity_id,
value: ev.detail.value,
});
@@ -108,9 +96,10 @@ class HuiNumericInputCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsNumericInputCardFeatureFromState(this._stateObj)
!supportsNumericInputCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -130,7 +119,7 @@ class HuiNumericInputCardFeature
@value-changed=${this._setValue}
.disabled=${stateObj.state === UNAVAILABLE}
.unit=${stateObj.attributes.unit_of_measurement}
.locale=${this._locale}
.locale=${this.hass.locale}
></ha-control-number-buttons>
`;
}
@@ -143,7 +132,7 @@ class HuiNumericInputCardFeature
@value-changed=${this._setValue}
.disabled=${stateObj.state === UNAVAILABLE}
.unit=${stateObj.attributes.unit_of_measurement}
.locale=${this._locale}
.locale=${this.hass.locale}
></ha-control-slider>
`;
}
@@ -1,4 +1,3 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { InputSelectEntity } from "../../../data/input_select";
@@ -17,11 +16,6 @@ import type {
type SelectOptionEntity = SelectEntity | InputSelectEntity;
const supportsSelectOptionsCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "select" || domain === "input_select";
};
export const supportsSelectOptionsCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -30,7 +24,8 @@ export const supportsSelectOptionsCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsSelectOptionsCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "select" || domain === "input_select";
};
@customElement("hui-select-options-card-feature")
@@ -54,7 +49,7 @@ class HuiSelectOptionsCardFeature
protected readonly _serviceAction = "select_option";
protected get _label(): string {
return this._localize("ui.card.select.option");
return this.hass!.localize("ui.card.select.option");
}
protected readonly _allowIconsStyle = false;
@@ -77,7 +72,7 @@ class HuiSelectOptionsCardFeature
}
protected _getOptions(): HuiModeSelectOption[] {
if (!this._stateObj) {
if (!this._stateObj || !this.hass) {
return [];
}
@@ -86,7 +81,7 @@ class HuiSelectOptionsCardFeature
this._config?.options
).map((option) => ({
value: option,
label: this._formatters.formatEntityState(this._stateObj!, option),
label: this.hass!.formatEntityState(this._stateObj!, option),
}));
}
@@ -103,8 +98,9 @@ class HuiSelectOptionsCardFeature
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsSelectOptionsCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsSelectOptionsCardFeature(this.hass, this.context)
);
}
}
@@ -1,27 +1,12 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { consumeEntityState } from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import { computeDomain } from "../../../common/entity/compute_domain";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-control-slider";
import {
apiContext,
formattersContext,
internationalizationContext,
} from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { HumidifierEntity } from "../../../data/humidifier";
import type { FrontendLocaleData } from "../../../data/translation";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantFormatters,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -29,11 +14,6 @@ import type {
TargetHumidityCardFeatureConfig,
} from "./types";
const supportsTargetHumidityCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "humidifier";
};
export const supportsTargetHumidityCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -42,7 +22,8 @@ export const supportsTargetHumidityCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsTargetHumidityCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "humidifier";
};
@customElement("hui-target-humidity-card-feature")
@@ -50,31 +31,22 @@ class HuiTargetHumidityCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: HumidifierEntity;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: formattersContext, subscribe: true })
private _formatters!: HomeAssistantFormatters;
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state() private _config?: TargetHumidityCardFeatureConfig;
@state() private _targetHumidity?: number;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
HumidifierEntity | undefined;
}
static getStubConfig(): TargetHumidityCardFeatureConfig {
return {
type: "target-humidity",
@@ -88,10 +60,17 @@ class HuiTargetHumidityCardFeature
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
protected willUpdate(changedProp: PropertyValues<this>): void {
super.willUpdate(changedProp);
if (changedProp.has("_stateObj") && this._stateObj) {
this._targetHumidity = this._stateObj.attributes.humidity;
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._targetHumidity = this._stateObj!.attributes.humidity;
}
}
}
@@ -113,7 +92,7 @@ class HuiTargetHumidityCardFeature
}
private _callService() {
this._api.callService("humidifier", "set_humidity", {
this.hass!.callService("humidifier", "set_humidity", {
entity_id: this._stateObj!.entity_id,
humidity: this._targetHumidity,
});
@@ -122,9 +101,10 @@ class HuiTargetHumidityCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsTargetHumidityCardFeatureFromState(this._stateObj)
!supportsTargetHumidityCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -137,12 +117,12 @@ class HuiTargetHumidityCardFeature
.step=${this._step}
.disabled=${this._stateObj!.state === UNAVAILABLE}
@value-changed=${this._valueChanged}
.label=${this._formatters.formatEntityAttributeName(
.label=${this.hass.formatEntityAttributeName(
this._stateObj,
"humidity"
)}
unit="%"
.locale=${this._locale}
.locale=${this.hass.locale}
></ha-control-slider>
`;
}
@@ -1,12 +1,8 @@
import { consume } from "@lit/context";
import type { HassConfig, HassEntity } from "home-assistant-js-websocket";
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { UNIT_F } from "../../../common/const";
import { consumeEntityState } from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
@@ -17,23 +13,10 @@ import "../../../components/ha-control-button-group";
import "../../../components/ha-control-number-buttons";
import type { ClimateEntity } from "../../../data/climate";
import { ClimateEntityFeature } from "../../../data/climate";
import {
apiContext,
configContext,
formattersContext,
internationalizationContext,
} from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { FrontendLocaleData } from "../../../data/translation";
import type { WaterHeaterEntity } from "../../../data/water_heater";
import { WaterHeaterEntityFeature } from "../../../data/water_heater";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantConfig,
HomeAssistantFormatters,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -43,9 +26,14 @@ import type {
type Target = "value" | "low" | "high";
const supportsTargetTemperatureCardFeatureFromState = (
stateObj: HassEntity
export const supportsTargetTemperatureCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
) => {
const stateObj = context.entity_id
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
const domain = computeDomain(stateObj.entity_id);
return (
(domain === "climate" &&
@@ -59,54 +47,27 @@ const supportsTargetTemperatureCardFeatureFromState = (
);
};
export const supportsTargetTemperatureCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
) => {
const stateObj = context.entity_id
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsTargetTemperatureCardFeatureFromState(stateObj);
};
@customElement("hui-target-temperature-card-feature")
class HuiTargetTemperatureCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: WaterHeaterEntity | ClimateEntity;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: formattersContext, subscribe: true })
private _formatters!: HomeAssistantFormatters;
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state()
@consume({ context: configContext, subscribe: true })
@transform<HomeAssistantConfig, HassConfig>({
transformer: ({ config }) => config,
})
private _hassConfig?: HassConfig;
@state() private _config?: TargetTemperatureCardFeatureConfig;
@state() private _targetTemperature: Partial<Record<Target, number>> = {};
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
WaterHeaterEntity | ClimateEntity | undefined;
}
static getStubConfig(): TargetTemperatureCardFeatureConfig {
return {
type: "target-temperature",
@@ -120,27 +81,34 @@ class HuiTargetTemperatureCardFeature
this._config = config;
}
protected willUpdate(changedProp: PropertyValues): void {
protected willUpdate(changedProp: PropertyValues<this>): void {
super.willUpdate(changedProp);
if (changedProp.has("_stateObj") && this._stateObj) {
this._targetTemperature = {
value: this._stateObj.attributes.temperature,
low:
"target_temp_low" in this._stateObj.attributes
? this._stateObj.attributes.target_temp_low
: undefined,
high:
"target_temp_high" in this._stateObj.attributes
? this._stateObj.attributes.target_temp_high
: undefined,
};
if (
(changedProp.has("hass") || changedProp.has("context")) &&
this._stateObj
) {
const oldHass = changedProp.get("hass") as HomeAssistant | undefined;
const oldStateObj = oldHass?.states[this.context!.entity_id!];
if (oldStateObj !== this._stateObj) {
this._targetTemperature = {
value: this._stateObj!.attributes.temperature,
low:
"target_temp_low" in this._stateObj!.attributes
? this._stateObj!.attributes.target_temp_low
: undefined,
high:
"target_temp_high" in this._stateObj!.attributes
? this._stateObj!.attributes.target_temp_high
: undefined,
};
}
}
}
private get _step() {
return (
this._stateObj!.attributes.target_temp_step ||
(this._hassConfig?.unit_system.temperature === UNIT_F ? 1 : 0.5)
(this.hass!.config.unit_system.temperature === UNIT_F ? 1 : 0.5)
);
}
@@ -175,14 +143,14 @@ class HuiTargetTemperatureCardFeature
private _callService(type: string) {
const domain = computeStateDomain(this._stateObj!);
if (type === "high" || type === "low") {
this._api.callService(domain, "set_temperature", {
this.hass!.callService(domain, "set_temperature", {
entity_id: this._stateObj!.entity_id,
target_temp_low: this._targetTemperature.low,
target_temp_high: this._targetTemperature.high,
});
return;
}
this._api.callService(domain, "set_temperature", {
this.hass!.callService(domain, "set_temperature", {
entity_id: this._stateObj!.entity_id,
temperature: this._targetTemperature.value,
});
@@ -218,9 +186,10 @@ class HuiTargetTemperatureCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsTargetTemperatureCardFeatureFromState(this._stateObj)
!supportsTargetTemperatureCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -244,12 +213,12 @@ class HuiTargetTemperatureCardFeature
.formatOptions=${options}
.target=${"value"}
.value=${this._stateObj.attributes.temperature}
.unit=${this._hassConfig?.unit_system.temperature}
.unit=${this.hass.config.unit_system.temperature}
.min=${this._min}
.max=${this._max}
.step=${this._step}
@value-changed=${this._valueChanged}
.label=${this._formatters.formatEntityAttributeName(
.label=${this.hass.formatEntityAttributeName(
this._stateObj,
"temperature"
)}
@@ -257,7 +226,7 @@ class HuiTargetTemperatureCardFeature
"--control-number-buttons-focus-color": stateColor,
})}
.disabled=${this._stateObj!.state === UNAVAILABLE}
.locale=${this._locale}
.locale=${this.hass.locale}
>
</ha-control-number-buttons>
</ha-control-button-group>
@@ -276,7 +245,7 @@ class HuiTargetTemperatureCardFeature
.formatOptions=${options}
.target=${"low"}
.value=${this._targetTemperature.low}
.unit=${this._hassConfig?.unit_system.temperature}
.unit=${this.hass.config.unit_system.temperature}
.min=${this._min}
.max=${Math.min(
this._max,
@@ -284,7 +253,7 @@ class HuiTargetTemperatureCardFeature
)}
.step=${this._step}
@value-changed=${this._valueChanged}
.label=${this._formatters.formatEntityAttributeName(
.label=${this.hass.formatEntityAttributeName(
this._stateObj,
"target_temp_low"
)}
@@ -292,14 +261,14 @@ class HuiTargetTemperatureCardFeature
"--control-number-buttons-focus-color": stateColor,
})}
.disabled=${this._stateObj!.state === UNAVAILABLE}
.locale=${this._locale}
.locale=${this.hass.locale}
>
</ha-control-number-buttons>
<ha-control-number-buttons
.formatOptions=${options}
.target=${"high"}
.value=${this._targetTemperature.high}
.unit=${this._hassConfig?.unit_system.temperature}
.unit=${this.hass.config.unit_system.temperature}
.min=${Math.max(
this._min,
this._targetTemperature.low ?? this._min
@@ -307,7 +276,7 @@ class HuiTargetTemperatureCardFeature
.max=${this._max}
.step=${this._step}
@value-changed=${this._valueChanged}
.label=${this._formatters.formatEntityAttributeName(
.label=${this.hass.formatEntityAttributeName(
this._stateObj,
"target_temp_high"
)}
@@ -315,7 +284,7 @@ class HuiTargetTemperatureCardFeature
"--control-number-buttons-focus-color": stateColor,
})}
.disabled=${this._stateObj!.state === UNAVAILABLE}
.locale=${this._locale}
.locale=${this.hass.locale}
>
</ha-control-number-buttons>
</ha-control-button-group>
@@ -326,15 +295,15 @@ class HuiTargetTemperatureCardFeature
<ha-control-button-group>
<ha-control-number-buttons
.disabled=${this._stateObj!.state === UNAVAILABLE}
.unit=${this._hassConfig?.unit_system.temperature}
.label=${this._formatters.formatEntityAttributeName(
.unit=${this.hass.config.unit_system.temperature}
.label=${this.hass.formatEntityAttributeName(
this._stateObj,
"temperature"
)}
style=${styleMap({
"--control-number-buttons-focus-color": stateColor,
})}
.locale=${this._locale}
.locale=${this.hass.locale}
>
</ha-control-number-buttons>
</ha-control-button-group>
@@ -8,26 +8,19 @@ import {
mdiVolumeHigh,
mdiVolumeOff,
} from "@mdi/js";
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-control-switch";
import { apiContext } from "../../../data/context";
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
import { forwardHaptic } from "../../../data/haptics";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -35,18 +28,6 @@ import type {
ToggleCardFeatureConfig,
} from "./types";
const supportsToggleCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return [
"switch",
"input_boolean",
"light",
"fan",
"siren",
"automation",
].includes(domain);
};
export const supportsToggleCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -55,7 +36,15 @@ export const supportsToggleCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsToggleCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return [
"switch",
"input_boolean",
"light",
"fan",
"siren",
"automation",
].includes(domain);
};
const DOMAIN_ICONS: Record<string, { on: string; off: string }> = {
@@ -75,22 +64,19 @@ const DOMAIN_ICONS: Record<string, { on: string; off: string }> = {
@customElement("hui-toggle-card-feature")
class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: HassEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: ToggleCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as HassEntity | undefined;
}
static getStubConfig(): ToggleCardFeatureConfig {
return {
type: "toggle",
@@ -123,7 +109,7 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
}
private async _callService(turnOn): Promise<void> {
if (!this._stateObj) {
if (!this.hass || !this._stateObj) {
return;
}
forwardHaptic(this, "light");
@@ -131,7 +117,7 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
const serviceDomain = stateDomain;
const service = turnOn ? "turn_on" : "turn_off";
await this._api.callService(serviceDomain, service, {
await this.hass.callService(serviceDomain, service, {
entity_id: this._stateObj.entity_id,
});
}
@@ -139,9 +125,10 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsToggleCardFeatureFromState(this._stateObj)
!supportsToggleCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -163,7 +150,7 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
return html`
<ha-control-button-group>
<ha-control-button
.label=${this._localize("ui.card.common.turn_off")}
.label=${this.hass.localize("ui.card.common.turn_off")}
@click=${this._turnOff}
.disabled=${this._stateObj.state === UNAVAILABLE}
class=${classMap({
@@ -176,7 +163,7 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
<ha-svg-icon .path=${offIcon}></ha-svg-icon>
</ha-control-button>
<ha-control-button
.label=${this._localize("ui.card.common.turn_on")}
.label=${this.hass.localize("ui.card.common.turn_on")}
@click=${this._turnOn}
.disabled=${this._stateObj.state === UNAVAILABLE}
class=${classMap({
@@ -198,7 +185,7 @@ class HuiToggleCardFeature extends LitElement implements LovelaceCardFeature {
.pathOff=${offIcon}
.checked=${isOn}
@change=${this._valueChanged}
.label=${this._localize("ui.card.common.toggle")}
.label=${this.hass.localize("ui.card.common.toggle")}
.disabled=${this._stateObj.state === UNAVAILABLE}
>
</ha-control-switch>
@@ -1,24 +1,16 @@
import { consume } from "@lit/context";
import { mdiCancel, mdiCellphoneArrowDown } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { LitElement, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateActive } from "../../../common/entity/state_active";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import { apiContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { UpdateEntity } from "../../../data/update";
import { UpdateEntityFeature, updateIsInstalling } from "../../../data/update";
import { showUpdateBackupDialogParams } from "../../../dialogs/update_backup/show-update-backup-dialog";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -28,14 +20,6 @@ import type {
export const DEFAULT_UPDATE_BACKUP_OPTION = "no";
const supportsUpdateActionsCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "update" &&
supportsFeature(stateObj, UpdateEntityFeature.INSTALL)
);
};
export const supportsUpdateActionsCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -44,7 +28,11 @@ export const supportsUpdateActionsCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsUpdateActionsCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "update" &&
supportsFeature(stateObj, UpdateEntityFeature.INSTALL)
);
};
@customElement("hui-update-actions-card-feature")
@@ -52,22 +40,20 @@ class HuiUpdateActionsCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: UpdateEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: UpdateActionsCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
UpdateEntity | undefined;
}
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
await import("../editor/config-elements/hui-update-actions-card-feature-editor");
return document.createElement("hui-update-actions-card-feature-editor");
@@ -129,14 +115,14 @@ class HuiUpdateActionsCardFeature
backup = response;
}
this._api.callService("update", "install", {
this.hass!.callService("update", "install", {
entity_id: this._stateObj!.entity_id,
backup: backup,
});
}
private async _skip(): Promise<void> {
this._api.callService("update", "skip", {
this.hass!.callService("update", "skip", {
entity_id: this._stateObj!.entity_id,
});
}
@@ -144,9 +130,10 @@ class HuiUpdateActionsCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsUpdateActionsCardFeatureFromState(this._stateObj)
!supportsUpdateActionsCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -154,14 +141,16 @@ class HuiUpdateActionsCardFeature
return html`
<ha-control-button-group>
<ha-control-button
.label=${this._localize("ui.dialogs.more_info_control.update.skip")}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.update.skip"
)}
@click=${this._skip}
.disabled=${this._skipDisabled}
>
<ha-svg-icon .path=${mdiCancel}></ha-svg-icon>
</ha-control-button>
<ha-control-button
.label=${this._localize(
.label=${this.hass.localize(
"ui.dialogs.more_info_control.update.install"
)}
@click=${this._install}
@@ -7,21 +7,14 @@ import {
mdiStop,
mdiTargetVariant,
} from "@mdi/js";
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { LitElement, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-svg-icon";
import { apiContext } from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import type { VacuumEntity } from "../../../data/vacuum";
import {
@@ -31,7 +24,7 @@ import {
canStop,
isCleaning,
} from "../../../data/vacuum";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -132,14 +125,6 @@ export const VACUUM_COMMANDS_BUTTONS: Record<
}),
};
const supportsVacuumCommandsCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "vacuum" &&
VACUUM_COMMANDS.some((c) => supportsVacuumCommand(stateObj, c))
);
};
export const supportsVacuumCommandsCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -148,7 +133,11 @@ export const supportsVacuumCommandsCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsVacuumCommandsCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "vacuum" &&
VACUUM_COMMANDS.some((c) => supportsVacuumCommand(stateObj, c))
);
};
@customElement("hui-vacuum-commands-card-feature")
@@ -156,22 +145,20 @@ class HuiVacuumCommandCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: VacuumEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: VacuumCommandsCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as
VacuumEntity | undefined;
}
static getStubConfig(): VacuumCommandsCardFeatureConfig {
return {
type: "vacuum-commands",
@@ -193,7 +180,7 @@ class HuiVacuumCommandCardFeature
private _onCommandTap(ev): void {
ev.stopPropagation();
const entry = (ev.target! as any).entry as VacuumButton;
this._api.callService("vacuum", entry.serviceName, {
this.hass!.callService("vacuum", entry.serviceName, {
entity_id: this._stateObj!.entity_id,
});
}
@@ -201,9 +188,10 @@ class HuiVacuumCommandCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsVacuumCommandsCardFeatureFromState(this._stateObj)
!supportsVacuumCommandsCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -221,7 +209,7 @@ class HuiVacuumCommandCardFeature
return html`
<ha-control-button
.entry=${button}
.label=${this._localize(
.label=${this.hass!.localize(
// @ts-ignore
`ui.dialogs.more_info_control.vacuum.${button.translationKey}`
)}
@@ -1,23 +1,15 @@
import { consume } from "@lit/context";
import { mdiStop, mdiValveClosed, mdiValveOpen } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-button";
import "../../../components/ha-control-button-group";
import "../../../components/ha-control-switch";
import "../../../components/ha-svg-icon";
import { apiContext } from "../../../data/context";
import { UNAVAILABLE, UNKNOWN } from "../../../data/entity/entity";
import {
canClose,
@@ -26,7 +18,7 @@ import {
ValveEntityFeature,
type ValveEntity,
} from "../../../data/valve";
import type { HomeAssistant, HomeAssistantApi } from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -34,15 +26,6 @@ import type {
ValveOpenCloseCardFeatureConfig,
} from "./types";
const supportsValveOpenCloseCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "valve" &&
(supportsFeature(stateObj, ValveEntityFeature.OPEN) ||
supportsFeature(stateObj, ValveEntityFeature.CLOSE))
);
};
export const supportsValveOpenCloseCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -51,7 +34,12 @@ export const supportsValveOpenCloseCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsValveOpenCloseCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "valve" &&
(supportsFeature(stateObj, ValveEntityFeature.OPEN) ||
supportsFeature(stateObj, ValveEntityFeature.CLOSE))
);
};
@customElement("hui-valve-open-close-card-feature")
@@ -59,22 +47,19 @@ class HuiValveOpenCloseCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: ValveEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state() private _config?: ValveOpenCloseCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as ValveEntity | undefined;
}
static getStubConfig(): ValveOpenCloseCardFeatureConfig {
return {
type: "valve-open-close",
@@ -89,13 +74,13 @@ class HuiValveOpenCloseCardFeature
}
private _onOpenValve(): void {
this._api.callService("valve", "open_valve", {
this.hass!.callService("valve", "open_valve", {
entity_id: this._stateObj!.entity_id,
});
}
private _onCloseValve(): void {
this._api.callService("valve", "close_valve", {
this.hass!.callService("valve", "close_valve", {
entity_id: this._stateObj!.entity_id,
});
}
@@ -112,7 +97,7 @@ class HuiValveOpenCloseCardFeature
private _onStopTap(ev): void {
ev.stopPropagation();
this._api.callService("valve", "stop_valve", {
this.hass!.callService("valve", "stop_valve", {
entity_id: this._stateObj!.entity_id,
});
}
@@ -131,9 +116,10 @@ class HuiValveOpenCloseCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsValveOpenCloseCardFeatureFromState(this._stateObj)
!supportsValveOpenCloseCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -160,7 +146,7 @@ class HuiValveOpenCloseCardFeature
supportsFeature(this._stateObj, ValveEntityFeature.CLOSE)
? html`
<ha-control-button
.label=${this._localize("ui.card.valve.close_valve")}
.label=${this.hass.localize("ui.card.valve.close_valve")}
@click=${this._onCloseTap}
.disabled=${!canClose(this._stateObj)}
class=${classMap({
@@ -179,7 +165,7 @@ class HuiValveOpenCloseCardFeature
supportsFeature(this._stateObj, ValveEntityFeature.STOP)
? html`
<ha-control-button
.label=${this._localize("ui.card.valve.stop_valve")}
.label=${this.hass.localize("ui.card.valve.stop_valve")}
@click=${this._onStopTap}
.disabled=${!canStop(this._stateObj)}
>
@@ -192,7 +178,7 @@ class HuiValveOpenCloseCardFeature
supportsFeature(this._stateObj, ValveEntityFeature.OPEN)
? html`
<ha-control-button
.label=${this._localize("ui.card.valve.open_valve")}
.label=${this.hass.localize("ui.card.valve.open_valve")}
@click=${this._onOpenTap}
.disabled=${!canOpen(this._stateObj)}
class=${classMap({
@@ -217,7 +203,7 @@ class HuiValveOpenCloseCardFeature
.pathOff=${closedIcon}
.checked=${isOpen}
@change=${this._valueChanged}
.label=${this._localize("ui.card.common.toggle")}
.label=${this.hass.localize("ui.card.common.toggle")}
.disabled=${this._stateObj.state === UNAVAILABLE}
>
</ha-control-switch>
@@ -1,36 +1,18 @@
import { consume } from "@lit/context";
import type { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { computeCssColor } from "../../../common/color/compute-color";
import {
consumeEntityState,
consumeLocalize,
} from "../../../common/decorators/consume-context-entry";
import { transform } from "../../../common/decorators/transform";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeAttributeNameDisplay } from "../../../common/entity/compute_attribute_display";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateActive } from "../../../common/entity/state_active";
import { stateColorCss } from "../../../common/entity/state_color";
import { supportsFeature } from "../../../common/entity/supports-feature";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-control-slider";
import {
apiContext,
entitiesContext,
internationalizationContext,
} from "../../../data/context";
import { UNAVAILABLE } from "../../../data/entity/entity";
import { DOMAIN_ATTRIBUTES_UNITS } from "../../../data/entity/entity_attributes";
import type { FrontendLocaleData } from "../../../data/translation";
import { ValveEntityFeature, type ValveEntity } from "../../../data/valve";
import type {
HomeAssistant,
HomeAssistantApi,
HomeAssistantInternationalization,
} from "../../../types";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCardFeature } from "../types";
import { cardFeatureStyles } from "./common/card-feature-styles";
import type {
@@ -38,14 +20,6 @@ import type {
ValvePositionCardFeatureConfig,
} from "./types";
const supportsValvePositionCardFeatureFromState = (stateObj: HassEntity) => {
const domain = computeDomain(stateObj.entity_id);
return (
domain === "valve" &&
supportsFeature(stateObj, ValveEntityFeature.SET_POSITION)
);
};
export const supportsValvePositionCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -54,7 +28,11 @@ export const supportsValvePositionCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsValvePositionCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return (
domain === "valve" &&
supportsFeature(stateObj, ValveEntityFeature.SET_POSITION)
);
};
@customElement("hui-valve-position-card-feature")
@@ -62,35 +40,21 @@ class HuiValvePositionCardFeature
extends LitElement
implements LovelaceCardFeature
{
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
@property({ attribute: false }) public color?: string;
@state()
@consumeEntityState({ entityIdPath: ["context", "entity_id"] })
private _stateObj?: ValveEntity;
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
@state()
@consume({ context: apiContext, subscribe: true })
private _api!: HomeAssistantApi;
@state()
@consume({ context: entitiesContext, subscribe: true })
private _entities!: HomeAssistant["entities"];
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform<HomeAssistantInternationalization, FrontendLocaleData>({
transformer: ({ locale }) => locale,
})
private _locale?: FrontendLocaleData;
@state() private _config?: ValvePositionCardFeatureConfig;
private get _stateObj() {
if (!this.hass || !this.context || !this.context.entity_id) {
return undefined;
}
return this.hass.states[this.context.entity_id!] as ValveEntity | undefined;
}
static getStubConfig(): ValvePositionCardFeatureConfig {
return {
type: "valve-position",
@@ -107,9 +71,10 @@ class HuiValvePositionCardFeature
protected render() {
if (
!this._config ||
!this.hass ||
!this.context ||
!this._stateObj ||
!supportsValvePositionCardFeatureFromState(this._stateObj)
!supportsValvePositionCardFeature(this.hass, this.context)
) {
return nothing;
}
@@ -143,14 +108,14 @@ class HuiValvePositionCardFeature
show-handle
@value-changed=${this._valueChanged}
.label=${computeAttributeNameDisplay(
this._localize,
this.hass.localize,
this._stateObj,
this._entities,
this.hass.entities,
"current_position"
)}
.disabled=${this._stateObj!.state === UNAVAILABLE}
.unit=${DOMAIN_ATTRIBUTES_UNITS.valve.current_position}
.locale=${this._locale}
.locale=${this.hass.locale}
></ha-control-slider>
`;
}
@@ -159,7 +124,7 @@ class HuiValvePositionCardFeature
const { value } = ev.detail;
if (typeof value !== "number" || isNaN(value)) return;
this._api.callService("valve", "set_valve_position", {
this.hass!.callService("valve", "set_valve_position", {
entity_id: this._stateObj!.entity_id,
position: value,
});
@@ -1,5 +1,4 @@
import { mdiWaterBoiler } from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
import { customElement } from "lit/decorators";
import { computeDomain } from "../../../common/entity/compute_domain";
import { stateColorCss } from "../../../common/entity/state_color";
@@ -14,13 +13,6 @@ import type {
WaterHeaterOperationModesCardFeatureConfig,
} from "./types";
const supportsWaterHeaterOperationModesCardFeatureFromState = (
stateObj: HassEntity
) => {
const domain = computeDomain(stateObj.entity_id);
return domain === "water_heater";
};
export const supportsWaterHeaterOperationModesCardFeature = (
hass: HomeAssistant,
context: LovelaceCardFeatureContext
@@ -29,7 +21,8 @@ export const supportsWaterHeaterOperationModesCardFeature = (
? hass.states[context.entity_id]
: undefined;
if (!stateObj) return false;
return supportsWaterHeaterOperationModesCardFeatureFromState(stateObj);
const domain = computeDomain(stateObj.entity_id);
return domain === "water_heater";
};
@customElement("hui-water-heater-operation-modes-card-feature")
@@ -55,7 +48,7 @@ class HuiWaterHeaterOperationModeCardFeature
protected readonly _serviceAction = "set_operation_mode";
protected get _label(): string {
return this._localize("ui.card.water_heater.mode");
return this.hass!.localize("ui.card.water_heater.mode");
}
protected readonly _defaultStyle = "icons";
@@ -89,7 +82,7 @@ class HuiWaterHeaterOperationModeCardFeature
}
protected _getOptions() {
if (!this._stateObj) {
if (!this._stateObj || !this.hass) {
return [];
}
@@ -101,15 +94,16 @@ class HuiWaterHeaterOperationModeCardFeature
return filterModes(orderedModes, this._config?.operation_modes).map(
(mode) => ({
value: mode,
label: this._formatters.formatEntityState(this._stateObj!, mode),
label: this.hass!.formatEntityState(this._stateObj!, mode),
})
);
}
protected _isSupported(): boolean {
return !!(
this._stateObj &&
supportsWaterHeaterOperationModesCardFeatureFromState(this._stateObj)
this.hass &&
this.context &&
supportsWaterHeaterOperationModesCardFeature(this.hass, this.context)
);
}
}
+1 -1
View File
@@ -245,7 +245,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
protected shouldUpdate(changedProps: PropertyValues<this>): boolean {
// Side Effect used to update footer hass while keeping optimizations
if (this._footerElement && "hass" in this._footerElement) {
if (this._footerElement) {
this._footerElement.hass = this.hass;
}
+5 -16
View File
@@ -1,12 +1,9 @@
import { mdiAlertCircleOutline, mdiAlertOutline } from "@mdi/js";
import { consume, type ContextType } from "@lit/context";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { consumeLocalize } from "../../../common/decorators/consume-context-entry";
import type { LocalizeFunc } from "../../../common/translations/localize";
import "../../../components/ha-card";
import "../../../components/ha-svg-icon";
import { configContext } from "../../../data/context";
import type { HomeAssistant } from "../../../types";
import type { LovelaceCard, LovelaceGridOptions } from "../types";
import type { ErrorCardConfig } from "./types";
@@ -17,13 +14,7 @@ const ERROR_ICONS = {
@customElement("hui-error-card")
export class HuiErrorCard extends LitElement implements LovelaceCard {
@state()
@consumeLocalize()
private _localize?: LocalizeFunc;
@state()
@consume({ context: configContext, subscribe: true })
private _hassConfig?: ContextType<typeof configContext>;
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ attribute: false }) public preview = false;
@@ -54,12 +45,10 @@ export class HuiErrorCard extends LitElement implements LovelaceCard {
const error =
this._config?.error ||
(this.severity === "warning" &&
this._localize?.("ui.errors.config.configuration_warning")) ||
this._localize?.("ui.errors.config.configuration_error");
this.hass?.localize("ui.errors.config.configuration_warning")) ||
this.hass?.localize("ui.errors.config.configuration_error");
const showTitle =
this._hassConfig === undefined ||
this._hassConfig.user?.is_admin ||
this.preview;
this.hass === undefined || this.hass?.user?.is_admin || this.preview;
const showMessage = this.preview;
return html`
@@ -68,6 +68,9 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
this._cards.forEach((card) => {
card.hass = this.hass;
});
if (this._errorCard) {
this._errorCard.hass = this.hass;
}
}
if (changedProperties.has("preview")) {
this._cards.forEach((card) => {
@@ -254,7 +254,7 @@ export class HuiStatisticCard extends LitElement implements LovelaceCard {
protected shouldUpdate(changedProps: PropertyValues): boolean {
// Side Effect used to update footer hass while keeping optimizations
if (this._footerElement && "hass" in this._footerElement) {
if (this._footerElement) {
this._footerElement.hass = this.hass;
}
if (
@@ -1,13 +1,13 @@
import { STATE_NOT_RUNNING } from "home-assistant-js-websocket";
import type { TemplateResult } from "lit";
import { html, LitElement } from "lit";
import { customElement } from "lit/decorators";
import { customElement, property } from "lit/decorators";
import "../../../components/ha-alert";
import type { HomeAssistant } from "../../../types";
import "../cards/hui-error-card";
export const createEntityNotFoundWarning = (
hass: Pick<HomeAssistant, "config" | "localize">,
hass: HomeAssistant,
// left for backwards compatibility for custom cards
_entityId: string
) =>
@@ -17,8 +17,10 @@ export const createEntityNotFoundWarning = (
@customElement("hui-warning")
export class HuiWarning extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
protected render(): TemplateResult {
return html`<hui-error-card severity="warning"
return html`<hui-error-card .hass=${this.hass} severity="warning"
><slot></slot
></hui-error-card>`;
}
@@ -30,9 +30,7 @@ class EntityRowDirective extends Directive {
}
this._entityId = entityId;
this._name = name;
if ("hass" in this._element) {
this._element.hass = hass;
}
this._element.hass = hass;
return this._element;
}
}
+9 -46
View File
@@ -25,65 +25,35 @@ export const getMyRedirects = (): Redirects => ({
application_credentials: {
redirect: "/config/application_credentials",
},
tools_assist: {
redirect: "/config/tools/assist",
},
tools_debug: {
redirect: "/config/tools/debug",
},
tools_states: {
redirect: "/config/tools/state",
},
tools_actions: {
redirect: "/config/tools/action",
},
tools_perform_action: {
redirect: "/config/tools/action",
params: {
service: "string",
},
},
tools_template: {
redirect: "/config/tools/template",
},
tools_events: {
redirect: "/config/tools/event",
},
tools_statistics: {
redirect: "/config/tools/statistics",
},
tools_yaml: {
redirect: "/config/tools/yaml",
},
developer_assist: {
redirect: "/config/tools/assist",
redirect: "/config/developer-tools/assist",
},
developer_debug: {
redirect: "/config/tools/debug",
redirect: "/config/developer-tools/debug",
},
developer_states: {
redirect: "/config/tools/state",
redirect: "/config/developer-tools/state",
},
developer_services: {
redirect: "/config/tools/action",
redirect: "/config/developer-tools/action",
},
developer_call_service: {
redirect: "/config/tools/action",
redirect: "/config/developer-tools/action",
params: {
service: "string",
},
},
developer_template: {
redirect: "/config/tools/template",
redirect: "/config/developer-tools/template",
},
developer_events: {
redirect: "/config/tools/event",
redirect: "/config/developer-tools/event",
},
developer_statistics: {
redirect: "/config/tools/statistics",
redirect: "/config/developer-tools/statistics",
},
server_controls: {
redirect: "/config/tools/yaml",
redirect: "/config/developer-tools/yaml",
},
calendar: {
component: "calendar",
@@ -179,13 +149,6 @@ export const getMyRedirects = (): Redirects => ({
component: "energy",
redirect: "/config/energy",
},
config_infrared: {
redirect: "/config/infrared",
},
config_radiofrequency: {
component: "radio_frequency",
redirect: "/config/radio-frequency",
},
config_ssdp: {
component: "ssdp",
redirect: "/config/ssdp",
+54 -62
View File
@@ -1459,7 +1459,7 @@
"automations": "[%key:ui::panel::config::automation::caption%]",
"scenes": "[%key:ui::panel::config::scene::caption%]",
"scripts": "[%key:ui::panel::config::script::caption%]",
"tools": "[%key:ui::panel::config::dashboard::tools::main%]",
"developer_tools": "[%key:ui::panel::config::dashboard::developer_tools::main%]",
"integrations": "[%key:ui::panel::config::integrations::caption%]",
"devices": "[%key:ui::panel::config::devices::caption%]",
"entities": "[%key:ui::panel::config::entities::caption%]"
@@ -1485,45 +1485,45 @@
"navigate_title": "Navigate",
"commands": {
"reload": {
"all": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::all%]",
"reload": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::reload%]",
"core": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::core%]",
"group": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::group%]",
"automation": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::automation%]",
"script": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::script%]",
"scene": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::scene%]",
"person": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::person%]",
"zone": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::zone%]",
"input_boolean": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::input_boolean%]",
"input_button": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::input_button%]",
"input_text": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::input_text%]",
"input_number": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::input_number%]",
"input_datetime": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::input_datetime%]",
"input_select": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::input_select%]",
"template": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::template%]",
"universal": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::universal%]",
"rest": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::rest%]",
"command_line": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::command_line%]",
"filter": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::filter%]",
"statistics": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::statistics%]",
"generic": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::generic%]",
"generic_thermostat": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::generic_thermostat%]",
"homekit": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::homekit%]",
"min_max": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::min_max%]",
"history_stats": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::history_stats%]",
"trend": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::trend%]",
"ping": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::ping%]",
"filesize": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::filesize%]",
"telegram": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::telegram%]",
"smtp": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::smtp%]",
"mqtt": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::mqtt%]",
"rpi_gpio": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::rpi_gpio%]",
"themes": "[%key:ui::panel::config::tools::tabs::yaml::section::reloading::themes%]"
"all": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::all%]",
"reload": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::reload%]",
"core": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::core%]",
"group": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::group%]",
"automation": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::automation%]",
"script": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::script%]",
"scene": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::scene%]",
"person": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::person%]",
"zone": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::zone%]",
"input_boolean": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::input_boolean%]",
"input_button": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::input_button%]",
"input_text": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::input_text%]",
"input_number": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::input_number%]",
"input_datetime": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::input_datetime%]",
"input_select": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::input_select%]",
"template": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::template%]",
"universal": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::universal%]",
"rest": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::rest%]",
"command_line": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::command_line%]",
"filter": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::filter%]",
"statistics": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::statistics%]",
"generic": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::generic%]",
"generic_thermostat": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::generic_thermostat%]",
"homekit": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::homekit%]",
"min_max": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::min_max%]",
"history_stats": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::history_stats%]",
"trend": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::trend%]",
"ping": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::ping%]",
"filesize": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::filesize%]",
"telegram": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::telegram%]",
"smtp": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::smtp%]",
"mqtt": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::mqtt%]",
"rpi_gpio": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::rpi_gpio%]",
"themes": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::reloading::themes%]"
},
"home_assistant_control": {
"perform_action": "{action} Home Assistant",
"restart": "[%key:ui::panel::config::tools::tabs::yaml::section::server_management::restart%]",
"stop": "[%key:ui::panel::config::tools::tabs::yaml::section::server_management::stop%]"
"restart": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::server_management::restart%]",
"stop": "[%key:ui::panel::config::developer-tools::tabs::yaml::section::server_management::stop%]"
},
"types": {
"reload": "Reload",
@@ -1559,14 +1559,14 @@
"analytics": "[%key:ui::panel::config::analytics::caption%]",
"system_health": "[%key:ui::panel::config::system_health::caption%]",
"blueprint": "[%key:ui::panel::config::blueprint::caption%]",
"server_control": "[%key:ui::panel::config::tools::tabs::yaml::title%]",
"server_control": "[%key:ui::panel::config::developer-tools::tabs::yaml::title%]",
"system": "[%key:ui::panel::config::dashboard::system::main%]",
"apps": "Apps",
"app_store": "App store",
"app_info": "{app} info",
"shortcuts": "[%key:ui::panel::config::info::shortcuts%]",
"labs": "[%key:ui::panel::config::labs::caption%]",
"tools": "[%key:ui::panel::config::dashboard::tools::main%]",
"developer-tools": "[%key:ui::panel::config::dashboard::developer_tools::main%]",
"matter": "[%key:ui::panel::config::dashboard::matter::main%]",
"zha": "[%key:ui::panel::config::dashboard::zha::main%]",
"zwave_js": "[%key:ui::panel::config::dashboard::zwave_js::main%]",
@@ -2154,12 +2154,12 @@
"data": "Additional data"
},
"template": {
"time": "[%key:ui::panel::config::tools::tabs::templates::time%]",
"all_listeners": "[%key:ui::panel::config::tools::tabs::templates::all_listeners%]",
"no_listeners": "[%key:ui::panel::config::tools::tabs::templates::no_listeners%]",
"listeners": "[%key:ui::panel::config::tools::tabs::templates::listeners%]",
"entity": "[%key:ui::panel::config::tools::tabs::templates::entity%]",
"domain": "[%key:ui::panel::config::tools::tabs::templates::domain%]"
"time": "[%key:ui::panel::config::developer-tools::tabs::templates::time%]",
"all_listeners": "[%key:ui::panel::config::developer-tools::tabs::templates::all_listeners%]",
"no_listeners": "[%key:ui::panel::config::developer-tools::tabs::templates::no_listeners%]",
"listeners": "[%key:ui::panel::config::developer-tools::tabs::templates::listeners%]",
"entity": "[%key:ui::panel::config::developer-tools::tabs::templates::entity%]",
"domain": "[%key:ui::panel::config::developer-tools::tabs::templates::domain%]"
}
},
"options_flow": {
@@ -2638,9 +2638,9 @@
"main": "System",
"secondary": "Create backups, check logs, or reboot your system"
},
"tools": {
"main": "Tools",
"secondary": "Inspect and debug your system"
"developer_tools": {
"main": "Developer tools",
"secondary": "Tools to inspect and debug your system"
},
"about": {
"main": "About",
@@ -3786,7 +3786,7 @@
"companion_apps": "Companion apps"
}
},
"tools": {
"developer-tools": {
"tabs": {
"assist": {
"tab": "Assist",
@@ -3904,9 +3904,7 @@
},
"templates": {
"title": "Template",
"description": "Templates let you generate dynamic content from your Home Assistant data, such as a notification that lists which lights are on, or a sensor whose value is calculated from several other entities.",
"engine_info": "Home Assistant uses the Jinja templating engine, extended with functions for working with your entities, areas, devices, and more. Write a template in the editor below and its result updates live as your states change.",
"learn_more": "Learn more",
"description": "Templates are rendered using the Jinja2 template engine with some Home Assistant specific extensions.",
"about": "About templates",
"editor": "Template editor",
"result": "Result",
@@ -3914,14 +3912,8 @@
"confirm_reset": "Do you want to reset your current template back to the demo template?",
"confirm_clear": "Do you want to clear your current template?",
"result_type": "Result type",
"docs_introduction": "Introduction to templating",
"docs_introduction_description": "Start here for a step-by-step guide.",
"docs_states": "Working with states",
"docs_states_description": "Read entity states and attributes in templates.",
"docs_debugging": "Debugging templates",
"docs_debugging_description": "Find and fix problems in your templates.",
"docs_functions": "Template functions reference",
"docs_functions_description": "Search every available function, filter, and test.",
"jinja_documentation": "Jinja2 template documentation",
"template_extensions": "Home Assistant template extensions",
"unknown_error_template": "Unknown error rendering template",
"time": "This template updates at the start of each minute.",
"all_listeners": "This template listens for all state changed events.",
@@ -4827,7 +4819,7 @@
"run_text_pipeline": "Run text pipeline",
"run_audio_pipeline": "Run audio pipeline",
"run_audio_with_wake": "Run audio pipeline with wake word detection",
"response": "[%key:ui::panel::config::tools::tabs::actions::response%]",
"response": "[%key:ui::panel::config::developer-tools::tabs::actions::response%]",
"send": "Send",
"continue_listening": "Continue listening for wake word",
"continue_talking": "Continue talking",
@@ -5128,7 +5120,7 @@
"type_script_plural": "[%key:ui::panel::config::blueprint::overview::types_plural::script%]",
"type_scene_plural": "scenes",
"new_automation_setup_failed_title": "New {type} setup timed out",
"new_automation_setup_failed_text": "Your new {type} was saved, but waiting for it to set up has timed out. This could be due to errors parsing your configuration.yaml, please check the configuration in the Tools panel. Your {type} will not be visible until this is corrected and {types} are reloaded. Changes to area, category, or labels were not saved and must be reapplied.",
"new_automation_setup_failed_text": "Your new {type} was saved, but waiting for it to set up has timed out. This could be due to errors parsing your configuration.yaml, please check the configuration in developer tools. Your {type} will not be visible until this is corrected and {types} are reloaded. Changes to area, category, or labels were not saved and must be reapplied.",
"new_automation_setup_keep_waiting": "You may continue to wait for a response from the server, in case it is just taking an unusually long time to process this {type}.",
"new_automation_setup_timedout_success": "The server has responded and this has now set up successfully. You may now close this dialog.",
"item_pasted": "{item} pasted",
+8 -73
View File
@@ -167,6 +167,14 @@ test.describe("Panel navigation", () => {
});
});
test("navigates to developer-tools panel", async ({ page }) => {
// Since 2026.2 developer-tools is part of the config panel
await goToPanel(page, "/config/developer-tools");
await expect(
page.locator("ha-panel-config, developer-tools-main").first()
).toBeAttached({ timeout: PANEL_TIMEOUT });
});
test("navigates to profile panel", async ({ page }) => {
await goToPanel(page, "/profile");
await expect(
@@ -175,79 +183,6 @@ test.describe("Panel navigation", () => {
});
});
// ---------------------------------------------------------------------------
// Tools panel (formerly Developer tools)
// ---------------------------------------------------------------------------
/**
* Every tool sub-page reachable under /config/tools, mapped to the custom
* element tools-router mounts for it (see tools-router.ts). Asserting on the
* specific element proves the route actually rendered its tool, not just the
* shared ha-panel-tools shell.
*/
const TOOLS_SUBPAGES: { route: string; element: string }[] = [
{ route: "yaml", element: "tools-yaml-config" },
{ route: "state", element: "tools-state" },
{ route: "action", element: "tools-action" },
{ route: "template", element: "tools-template" },
{ route: "event", element: "tools-event" },
{ route: "statistics", element: "tools-statistics" },
{ route: "assist", element: "tools-assist" },
{ route: "debug", element: "tools-debug" },
];
test.describe("Tools panel", () => {
test("base path renders the tools panel", async ({ page }) => {
await goToPanel(page, "/config/tools");
await expect(page.locator("ha-panel-tools")).toBeAttached({
timeout: PANEL_TIMEOUT,
});
});
for (const { route, element } of TOOLS_SUBPAGES) {
test(`renders the ${route} sub-page`, async ({ page }) => {
await goToPanel(page, `/config/tools/${route}`);
await expect(page.locator(element)).toBeAttached({
timeout: PANEL_TIMEOUT,
});
});
}
test("service is an alias for the action tool", async ({ page }) => {
await goToPanel(page, "/config/tools/service");
await expect(page.locator("tools-action")).toBeAttached({
timeout: PANEL_TIMEOUT,
});
});
});
// ---------------------------------------------------------------------------
// Tools redirects (old developer-tools URLs)
// ---------------------------------------------------------------------------
test.describe("Tools redirects", () => {
// The panel moved from top-level /developer-tools (pre-2026.2) to
// /config/developer-tools (2026.2), then was renamed to /config/tools
// (2026.8). Both old locations must redirect to the new one, and deep links
// must keep their sub-page. See the updateRoute() redirect in
// src/layouts/home-assistant.ts.
for (const oldBase of ["/developer-tools", "/config/developer-tools"]) {
test(`redirects ${oldBase} to the tools panel`, async ({ page }) => {
await goToPanel(page, oldBase);
await expect(page.locator("ha-panel-tools")).toBeAttached({
timeout: PANEL_TIMEOUT,
});
});
test(`redirects ${oldBase}/state to the state tool`, async ({ page }) => {
await goToPanel(page, `${oldBase}/state`);
await expect(page.locator("tools-state")).toBeAttached({
timeout: PANEL_TIMEOUT,
});
});
}
});
// ---------------------------------------------------------------------------
// Lovelace
// ---------------------------------------------------------------------------
+7
View File
@@ -43,4 +43,11 @@ export const e2eTestPanels: Panels = {
config: null,
url_path: "profile",
},
"developer-tools": {
component_name: "developer-tools",
icon: "mdi:hammer",
title: "developer_tools",
config: null,
url_path: "developer-tools",
},
};
+1 -1
View File
@@ -54,7 +54,7 @@ export class HaTest extends HomeAssistantAppEl {
: scenarios.default;
const initial: Partial<MockHomeAssistant> = {
// Use the full panel map (history + config enabled)
// Use the full panel map (history + config + developer-tools enabled)
panels: e2eTestPanels,
panelUrl: (() => {
const path = window.location.pathname;
+55 -55
View File
@@ -4618,22 +4618,22 @@ __metadata:
languageName: node
linkType: hard
"@rsdoctor/client@npm:1.5.17":
version: 1.5.17
resolution: "@rsdoctor/client@npm:1.5.17"
checksum: 10/0eb788455390a1b41aa31d982d93ceab3dd30671776e40e8a4ea3256b4713f6441066e079ff9a14413825e21d547b9b7d4ba52059f8995644e26724ff07bbf56
"@rsdoctor/client@npm:1.5.16":
version: 1.5.16
resolution: "@rsdoctor/client@npm:1.5.16"
checksum: 10/dcda4e8034a296090b073102423050764e636f9801f2e5a5904e1f2744b1fe26d5f8202aed7a785c9fc809044e1aef5c4b6a16b63974caec79d1b5996ec35d34
languageName: node
linkType: hard
"@rsdoctor/core@npm:1.5.17":
version: 1.5.17
resolution: "@rsdoctor/core@npm:1.5.17"
"@rsdoctor/core@npm:1.5.16":
version: 1.5.16
resolution: "@rsdoctor/core@npm:1.5.16"
dependencies:
"@rsbuild/plugin-check-syntax": "npm:^1.6.1"
"@rsdoctor/graph": "npm:1.5.17"
"@rsdoctor/sdk": "npm:1.5.17"
"@rsdoctor/types": "npm:1.5.17"
"@rsdoctor/utils": "npm:1.5.17"
"@rsdoctor/graph": "npm:1.5.16"
"@rsdoctor/sdk": "npm:1.5.16"
"@rsdoctor/types": "npm:1.5.16"
"@rsdoctor/utils": "npm:1.5.16"
"@rspack/resolver": "npm:^0.2.8"
browserslist-load-config: "npm:^1.0.2"
es-toolkit: "npm:^1.47.0"
@@ -4641,60 +4641,60 @@ __metadata:
fs-extra: "npm:^11.1.1"
semver: "npm:^7.7.4"
source-map: "npm:^0.7.6"
checksum: 10/a797d5243d1d3f758d8b38cea1a3195345525c3158c4061f5e78d875fe2001198c8967587153059eb7c5ff764f27b4685ce13fd55a27dccdcfe7cd8061fb30c9
checksum: 10/be7b03b5a5a8a9be47f94159469c35488f98046c99e2ccd7daed325c3dd2a8b21c654c12ac6d40c2775546efade373f429f630e8905cc17a5c9151978a0caaf9
languageName: node
linkType: hard
"@rsdoctor/graph@npm:1.5.17":
version: 1.5.17
resolution: "@rsdoctor/graph@npm:1.5.17"
"@rsdoctor/graph@npm:1.5.16":
version: 1.5.16
resolution: "@rsdoctor/graph@npm:1.5.16"
dependencies:
"@rsdoctor/types": "npm:1.5.17"
"@rsdoctor/utils": "npm:1.5.17"
"@rsdoctor/types": "npm:1.5.16"
"@rsdoctor/utils": "npm:1.5.16"
es-toolkit: "npm:^1.47.0"
path-browserify: "npm:1.0.1"
source-map: "npm:^0.7.6"
checksum: 10/e58ed532ea8cc743e45dd66b678e1da3d48939fe711ebfade47834ffc581be6089d611d18c97c3e12010e2c609049cb325d07021a5dc51ef651314f8fe9f5741
checksum: 10/949e3a2cc48ccbb2d554becb2270c4df4b4fc8a6e10bd55bf9dc4d5f9a5fb2823c3e11a30dce890beaaa1b0ab0039bba1554ad0e7ddc5e2ea47641222d454633
languageName: node
linkType: hard
"@rsdoctor/rspack-plugin@npm:1.5.17":
version: 1.5.17
resolution: "@rsdoctor/rspack-plugin@npm:1.5.17"
"@rsdoctor/rspack-plugin@npm:1.5.16":
version: 1.5.16
resolution: "@rsdoctor/rspack-plugin@npm:1.5.16"
dependencies:
"@rsdoctor/core": "npm:1.5.17"
"@rsdoctor/graph": "npm:1.5.17"
"@rsdoctor/sdk": "npm:1.5.17"
"@rsdoctor/types": "npm:1.5.17"
"@rsdoctor/utils": "npm:1.5.17"
"@rsdoctor/core": "npm:1.5.16"
"@rsdoctor/graph": "npm:1.5.16"
"@rsdoctor/sdk": "npm:1.5.16"
"@rsdoctor/types": "npm:1.5.16"
"@rsdoctor/utils": "npm:1.5.16"
peerDependencies:
"@rspack/core": "*"
peerDependenciesMeta:
"@rspack/core":
optional: true
checksum: 10/336bd813010a7c164770033ae5a30644bf165ce0dff250b9160c7c003401c214bfd96e528c5941c09834bb21949a4815c46ecae0ca4d9200b2b946b9c3164f8a
checksum: 10/2bebf2b8dfc5ffde77b46b45fedc7d5d9b96f4fe3e5e1b3762bff5de6f278d9288fe6c361fa1df5bb17926a45ae3c6038e165d4f1f5e867724df0a530590b36a
languageName: node
linkType: hard
"@rsdoctor/sdk@npm:1.5.17":
version: 1.5.17
resolution: "@rsdoctor/sdk@npm:1.5.17"
"@rsdoctor/sdk@npm:1.5.16":
version: 1.5.16
resolution: "@rsdoctor/sdk@npm:1.5.16"
dependencies:
"@rsdoctor/client": "npm:1.5.17"
"@rsdoctor/graph": "npm:1.5.17"
"@rsdoctor/types": "npm:1.5.17"
"@rsdoctor/utils": "npm:1.5.17"
"@rsdoctor/client": "npm:1.5.16"
"@rsdoctor/graph": "npm:1.5.16"
"@rsdoctor/types": "npm:1.5.16"
"@rsdoctor/utils": "npm:1.5.16"
launch-editor: "npm:^2.13.2"
safer-buffer: "npm:2.1.2"
socket.io: "npm:4.8.1"
tapable: "npm:2.3.3"
checksum: 10/d8a146a43726d61a9d7d2cfca7e2cd48c42a7ba28c9d280353f986f1647c0a33f05ec68a45af4f22104df8a8dae7dc14d621e56a15f11c3967d78c21216be234
checksum: 10/8a845468e13c66b93f9784c7887f7040b1df24f43e9304b50c3a7258c6b172c2bc3ca5f6e5e9d15801d1634e3e48f6bc78115a7a0242890548d111ae512caf7d
languageName: node
linkType: hard
"@rsdoctor/types@npm:1.5.17":
version: 1.5.17
resolution: "@rsdoctor/types@npm:1.5.17"
"@rsdoctor/types@npm:1.5.16":
version: 1.5.16
resolution: "@rsdoctor/types@npm:1.5.16"
dependencies:
"@types/connect": "npm:3.4.38"
"@types/estree": "npm:1.0.5"
@@ -4708,16 +4708,16 @@ __metadata:
optional: true
webpack:
optional: true
checksum: 10/4767825ae55498e25d1dfbecc0aebe2685b67701dae004617f4d95a68b85f19f29afe75f72b8efeab4fe49185bb9a3c85dcfce8f99852c49d4ee37a4f6b7d888
checksum: 10/f470a7047474669bd466c9cee15b5ef3e4b854d4347e3dd4f251f1d1f92bd9b7b5e6863349f5d3550485c3484497a1e8be71cefc56e883c81f8a734960d1fc5e
languageName: node
linkType: hard
"@rsdoctor/utils@npm:1.5.17":
version: 1.5.17
resolution: "@rsdoctor/utils@npm:1.5.17"
"@rsdoctor/utils@npm:1.5.16":
version: 1.5.16
resolution: "@rsdoctor/utils@npm:1.5.16"
dependencies:
"@babel/code-frame": "npm:7.26.2"
"@rsdoctor/types": "npm:1.5.17"
"@rsdoctor/types": "npm:1.5.16"
"@types/estree": "npm:1.0.5"
acorn: "npm:^8.10.0"
acorn-import-attributes: "npm:^1.9.5"
@@ -4731,7 +4731,7 @@ __metadata:
picocolors: "npm:^1.1.1"
rslog: "npm:^2.1.2"
strip-ansi: "npm:^6.0.1"
checksum: 10/7c9b4a3824de61f6254df50f80c5efe53df662f30cb07047afa956a9bc3917dc71bf0aa73c8edde7f73f9577a9cb051e1a81caaffa118db414a4b655223fe92a
checksum: 10/d73062cc01f4e2def276d6515f2810f54bfd7f819f1d3a00dd884621f9c008eda4b31ae6e6d96efaec81d1bbad98f0f49cb77e14a028d5f53c97aca84b6b07d4
languageName: node
linkType: hard
@@ -8565,9 +8565,9 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-import-x@npm:4.17.1":
version: 4.17.1
resolution: "eslint-plugin-import-x@npm:4.17.1"
"eslint-plugin-import-x@npm:4.17.0":
version: 4.17.0
resolution: "eslint-plugin-import-x@npm:4.17.0"
dependencies:
"@typescript-eslint/types": "npm:^8.56.0"
comment-parser: "npm:^1.4.1"
@@ -8587,7 +8587,7 @@ __metadata:
optional: true
eslint-import-resolver-node:
optional: true
checksum: 10/1cb95284765cf0ff937f7ab44cf965278939c25dedc72ac41d00d954f4c0bd607bcaffacdeb22a3796aa8f2775c63dd25f818c7f897f061f2339035826cbaf5c
checksum: 10/143081e0a2cb418990d5d61c08ad4dd46f4f10dd7664939cc4be8454c2f51cd69134746d2d8b7534786f3a13857d176456bb0c1d1ffc9c168830b4ce93d2c0a8
languageName: node
linkType: hard
@@ -9771,7 +9771,7 @@ __metadata:
"@octokit/rest": "npm:22.0.1"
"@playwright/test": "npm:1.61.1"
"@replit/codemirror-indentation-markers": "npm:6.5.3"
"@rsdoctor/rspack-plugin": "npm:1.5.17"
"@rsdoctor/rspack-plugin": "npm:1.5.16"
"@rspack/core": "npm:2.1.1"
"@rspack/dev-server": "npm:2.1.0"
"@swc/helpers": "npm:0.5.23"
@@ -9817,7 +9817,7 @@ __metadata:
eslint: "npm:10.6.0"
eslint-config-prettier: "npm:10.1.8"
eslint-import-resolver-webpack: "npm:0.13.11"
eslint-plugin-import-x: "npm:4.17.1"
eslint-plugin-import-x: "npm:4.17.0"
eslint-plugin-lit: "npm:2.3.1"
eslint-plugin-lit-a11y: "npm:5.1.1"
eslint-plugin-unused-imports: "npm:4.4.1"
@@ -9837,7 +9837,7 @@ __metadata:
home-assistant-js-websocket: "npm:9.6.0"
html-minifier-terser: "npm:7.2.0"
husky: "npm:9.1.7"
idb-keyval: "npm:6.2.6"
idb-keyval: "npm:6.2.5"
intl-messageformat: "npm:11.2.9"
js-yaml: "npm:5.2.0"
jsdom: "npm:29.1.1"
@@ -10056,10 +10056,10 @@ __metadata:
languageName: node
linkType: hard
"idb-keyval@npm:6.2.6":
version: 6.2.6
resolution: "idb-keyval@npm:6.2.6"
checksum: 10/8d0f8b9bd5eead685731a900510095dbc58936968739755bfd1de1c69a710daa5eb2b5cf185d0a7c7e9ce1daf4544fa5f58a2c7a37258a6826dd40f9e2614245
"idb-keyval@npm:6.2.5":
version: 6.2.5
resolution: "idb-keyval@npm:6.2.5"
checksum: 10/ac645882b3258ff07347d085baab91b871bac7be4f46ff8e20a7c036c2df35d3f695a30050009f27237b99045203568f2a842a35295a48f9b815959ee51a347e
languageName: node
linkType: hard